pub mod app;
pub mod highlight;
pub mod highlight_cache;
pub mod render_rows;
pub mod sidebar;
pub mod theme;
use anyhow::{Context, Result};
use crossterm::event::{
DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture,
KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
};
use crossterm::execute;
use crossterm::terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
};
use ratatui::prelude::*;
use std::io::stderr;
use std::sync::Once;
use crate::comments::model::CommentStore;
use crate::diff::model::Changeset;
fn reattach_stdin_to_tty() -> Result<()> {
use std::os::fd::IntoRawFd;
let is_tty = |fd: i32| unsafe { libc::isatty(fd) } == 1;
if is_tty(libc::STDIN_FILENO) {
return Ok(());
}
if is_tty(libc::STDOUT_FILENO) || is_tty(libc::STDERR_FILENO) {
let src = if is_tty(libc::STDOUT_FILENO) {
libc::STDOUT_FILENO
} else {
libc::STDERR_FILENO
};
if unsafe { libc::dup2(src, libc::STDIN_FILENO) } < 0 {
return Err(std::io::Error::last_os_error())
.context("redirecting stdin to the inherited terminal");
}
return Ok(());
}
let tty = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open("/dev/tty")
.context("opening /dev/tty to read interactive input")?;
let fd = tty.into_raw_fd();
let rc = unsafe { libc::dup2(fd, libc::STDIN_FILENO) };
let dup_err = std::io::Error::last_os_error();
unsafe { libc::close(fd) };
if rc < 0 {
return Err(dup_err).context("redirecting stdin to /dev/tty");
}
Ok(())
}
fn restore_terminal() {
let _ = execute!(stderr(), PopKeyboardEnhancementFlags);
let _ = disable_raw_mode();
let _ = execute!(
stderr(),
DisableBracketedPaste,
LeaveAlternateScreen,
DisableMouseCapture
);
}
fn install_panic_hook() {
static HOOK: Once = Once::new();
HOOK.call_once(|| {
let original = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
restore_terminal();
original(info);
}));
});
}
fn detect_truecolor() -> bool {
matches!(
std::env::var("COLORTERM").as_deref(),
Ok("truecolor") | Ok("24bit")
)
}
pub fn run(changeset: Changeset, comments: CommentStore) -> Result<CommentStore> {
if changeset.is_empty() {
eprintln!("hew: no changes to review");
return Ok(comments);
}
theme::init_theme(&highlight::default_theme(), detect_truecolor());
reattach_stdin_to_tty()?;
install_panic_hook();
enable_raw_mode()?;
let mut out = stderr();
execute!(
out,
EnterAlternateScreen,
EnableMouseCapture,
EnableBracketedPaste
)?;
let _ = execute!(
out,
PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES)
);
let backend = CrosstermBackend::new(out);
let mut terminal = Terminal::new(backend)?;
let mut app = app::App::with_comments(changeset, comments);
let result = app.run(&mut terminal);
restore_terminal();
let _ = terminal.show_cursor();
result.map(|()| app.into_comments())
}