breed/src/main.rs

133 lines
3.8 KiB
Rust

/*
* Copyright (c) 2023 Marceline Cramer
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
* 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::<Vec<String>>();
let mut buf = Vec::new();
let file_name: Option<OsString>;
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
}