/* * Copyright (c) 2023 Marceline Cramer * Copyright (c) 2023 Emma Tebibyte * SPDX-License-Identifier: AGPL-3.0-or-later * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ use std::{ env::args, ffi::OsString, fs::File, io::{Read, Stdout, Write}, os::fd::FromRawFd, path::Path, }; use crossterm::{terminal, QueueableCommand, Result}; use yacexits::{exit, EX_DATAERR, EX_UNAVAILABLE}; mod actions; mod buffer; mod config; mod keybinds; mod state; mod theme; #[derive(Copy, Clone, Debug, Default)] pub struct Cursor { pub column: usize, pub line: usize, } #[derive(Copy, Clone, Debug)] pub enum Direction { Left, Down, Up, Right, } fn screen_main(stdout: &mut Stdout, mut state: state::State) -> Result<()> { let poll_timeout = std::time::Duration::from_millis(100); while !state.quit { state.draw(stdout)?; if let true = crossterm::event::poll(poll_timeout)? { let event = crossterm::event::read()?; state.on_event(event); } } Ok(()) } fn main() -> Result<()> { let argv = args().collect::>(); let mut buf = Vec::new(); let file_name: Option; match argv.get(1).map(|s| s.as_str()) { Some("-") | None => { file_name = None; let stdin = 0; // get stdin as a file descriptor if unsafe { libc::isatty(stdin) } == 0 { unsafe { File::from_raw_fd(stdin) } } else { File::open("/dev/null")? } } Some(path) => { let input_file = Path::new(path); file_name = Some(OsString::from(&path)); File::open(input_file).unwrap_or_else(|_| { let mut err = String::new(); if !input_file.exists() { err = "No such file or directory.".to_string(); } else if input_file.is_dir() { err = "Is a directory.".to_string(); } eprintln!("{}: {}: {}", argv[0], path, err); exit(EX_UNAVAILABLE); }) } } .read_to_end(&mut buf)?; let text = String::from_utf8(buf).unwrap_or_else(|_| { eprintln!( "{}: {}: File contents are not valid UTF-8.", argv[0], argv[1] ); exit(EX_DATAERR); }); let state = state::State::from_str(file_name, &text)?; // begin to enter alternate screen let mut stdout = std::io::stdout(); terminal::enable_raw_mode()?; stdout.queue(terminal::EnterAlternateScreen)?; // register a panic handler to exit the alternate screen let old_panic_handler = std::panic::take_hook(); std::panic::set_hook(Box::new(move |info| { let mut stdout = std::io::stdout(); let _ = stdout.queue(terminal::LeaveAlternateScreen); let _ = terminal::disable_raw_mode(); let _ = stdout.flush(); old_panic_handler(info) })); // run inner event loop let result = screen_main(&mut stdout, state); // exit alternate screen stdout.queue(terminal::LeaveAlternateScreen)?; terminal::disable_raw_mode()?; result }