use anyhow::Result;
use crossterm::{
cursor::MoveTo,
event::{
self, DisableMouseCapture, EnableMouseCapture, Event, KeyEventKind, MouseEvent,
MouseEventKind,
},
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
ExecutableCommand,
};
use ratatui::{backend::CrosstermBackend, Terminal, TerminalOptions, Viewport};
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, AtomicU16, Ordering};
use crate::app::App;
pub const COMPACT_HEIGHT: u16 = 8;
static IS_COMPACT_MODE: AtomicBool = AtomicBool::new(false);
static COMPACT_START_ROW: AtomicU16 = AtomicU16::new(0);
pub fn install_panic_hook() {
let original_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |panic_info| {
if IS_COMPACT_MODE.load(Ordering::Relaxed) {
let _ = cleanup_terminal_compact();
} else {
let _ = cleanup_terminal();
}
original_hook(panic_info);
}));
}
#[allow(dead_code)]
pub fn setup_terminal() -> Result<Terminal<CrosstermBackend<std::io::Stderr>>> {
install_panic_hook();
IS_COMPACT_MODE.store(false, Ordering::Relaxed);
enable_raw_mode()?;
std::io::stderr().execute(EnterAlternateScreen)?;
std::io::stderr().execute(EnableMouseCapture)?;
let backend = CrosstermBackend::new(std::io::stderr());
let terminal = Terminal::new(backend)?;
Ok(terminal)
}
pub fn setup_terminal_compact() -> Result<Terminal<CrosstermBackend<std::io::Stderr>>> {
install_panic_hook();
IS_COMPACT_MODE.store(true, Ordering::Relaxed);
enable_raw_mode()?;
let (_, cursor_row) = crossterm::cursor::position().unwrap_or((0, 0));
let (_, terminal_height) = crossterm::terminal::size().unwrap_or((80, 24));
let start_row = if cursor_row + COMPACT_HEIGHT >= terminal_height {
terminal_height.saturating_sub(COMPACT_HEIGHT)
} else {
cursor_row
};
COMPACT_START_ROW.store(start_row, Ordering::Relaxed);
std::io::stderr().execute(EnableMouseCapture)?;
let backend = CrosstermBackend::new(std::io::stderr());
let terminal = Terminal::with_options(
backend,
TerminalOptions {
viewport: Viewport::Inline(COMPACT_HEIGHT),
},
)?;
Ok(terminal)
}
pub fn cleanup_terminal_compact() -> Result<()> {
use std::io::Write;
IS_COMPACT_MODE.store(false, Ordering::Relaxed);
let _ = write!(std::io::stderr(), "\x1b[?1000l");
let _ = write!(std::io::stderr(), "\x1b[?1002l");
let _ = write!(std::io::stderr(), "\x1b[?1003l");
let _ = write!(std::io::stderr(), "\x1b[?1006l");
let _ = write!(std::io::stderr(), "\x1b[?1015l");
let _ = std::io::stderr().execute(DisableMouseCapture);
let _ = std::io::stderr().flush();
std::thread::sleep(std::time::Duration::from_millis(20));
let mut drain_count = 0;
while event::poll(std::time::Duration::from_millis(0)).unwrap_or(false) && drain_count < 100 {
let _ = event::read();
drain_count += 1;
}
let _ = disable_raw_mode();
let start_row = COMPACT_START_ROW.load(Ordering::Relaxed);
let _ = std::io::stderr().execute(MoveTo(0, start_row));
let _ = write!(std::io::stderr(), "\x1b[0J");
std::thread::sleep(std::time::Duration::from_millis(10));
let mut drain_count2 = 0;
while event::poll(std::time::Duration::from_millis(0)).unwrap_or(false) && drain_count2 < 50 {
let _ = event::read();
drain_count2 += 1;
}
let _ = write!(std::io::stderr(), "\x1b[0m\x1b[?25h");
let _ = std::io::stderr().flush();
Ok(())
}
pub fn cleanup_terminal() -> Result<()> {
use crossterm::terminal::{Clear, ClearType};
use std::io::Write;
let _ = write!(std::io::stderr(), "\x1b[?1000l"); let _ = write!(std::io::stderr(), "\x1b[?1002l"); let _ = write!(std::io::stderr(), "\x1b[?1003l"); let _ = write!(std::io::stderr(), "\x1b[?1006l"); let _ = write!(std::io::stderr(), "\x1b[?1015l"); let _ = std::io::stderr().execute(DisableMouseCapture);
let _ = std::io::stderr().flush();
std::thread::sleep(std::time::Duration::from_millis(20));
let mut drain_count = 0;
while event::poll(std::time::Duration::from_millis(0)).unwrap_or(false) && drain_count < 100 {
let _ = event::read();
drain_count += 1;
}
let _ = std::io::stderr().execute(Clear(ClearType::All));
let _ = std::io::stderr().flush();
let _ = std::io::stderr().execute(LeaveAlternateScreen);
let _ = std::io::stderr().flush();
std::thread::sleep(std::time::Duration::from_millis(10));
let mut drain_count2 = 0;
while event::poll(std::time::Duration::from_millis(0)).unwrap_or(false) && drain_count2 < 50 {
let _ = event::read();
drain_count2 += 1;
}
let _ = disable_raw_mode();
let _ = write!(std::io::stderr(), "\x1b[0m");
let _ = write!(std::io::stderr(), "\x1b[?25h");
let _ = std::io::stderr().flush();
std::thread::sleep(std::time::Duration::from_millis(10));
Ok(())
}
pub fn run_app(
terminal: &mut Terminal<CrosstermBackend<std::io::Stderr>>,
app: &mut App,
) -> Result<Option<PathBuf>> {
loop {
if app.needs_redraw() {
terminal.draw(|f| app.render(f))?;
app.clear_dirty();
}
if !event::poll(std::time::Duration::from_millis(8))? {
let _ = app.poll_search();
continue;
}
let mut scroll_up_event: Option<MouseEvent> = None;
let mut scroll_down_event: Option<MouseEvent> = None;
loop {
if event::poll(std::time::Duration::from_millis(0))? {
match event::read()? {
Event::Key(key) => {
if matches!(key.kind, KeyEventKind::Press | KeyEventKind::Repeat) {
match app.handle_key(key)? {
Some(path) if !path.as_os_str().is_empty() => {
return Ok(Some(path));
}
None => {
return Ok(None);
}
_ => {}
}
}
}
Event::Mouse(mouse) => match mouse.kind {
MouseEventKind::ScrollUp => scroll_up_event = Some(mouse),
MouseEventKind::ScrollDown => scroll_down_event = Some(mouse),
_ => {
let _ = app.handle_mouse(mouse);
}
},
Event::Resize(_width, _height) => {
app.mark_dirty();
}
_ => {}
}
} else {
break;
}
}
if let Some(mouse) = scroll_up_event {
let _ = app.handle_mouse(mouse);
}
if let Some(mouse) = scroll_down_event {
let _ = app.handle_mouse(mouse);
}
}
}