pub mod app;
pub mod events;
pub mod preview;
pub mod render;
pub mod theme;
use std::io::{self, IsTerminal, Stderr};
use anyhow::{Context as _, Result};
use crossterm::{execute, terminal};
use ratatui::Terminal;
use ratatui::backend::CrosstermBackend;
use crate::error::Canceled;
use self::app::{App, Entry, Mode, Outcome};
use self::theme::Theme;
pub fn run_picker(entries: Vec<Entry>, mode: Mode, theme: Theme) -> Result<std::path::PathBuf> {
ensure_tty()?;
let mut stderr = io::stderr();
let _guard = RawTerminalGuard::enter(&mut stderr)?;
let backend = CrosstermBackend::new(io::stderr());
let mut term = Terminal::new(backend).context("initialize ratatui terminal on stderr")?;
term.clear().ok();
let mut app = App::new(entries, mode, theme);
loop {
if let Some(e) = app.selected_entry() {
let _ = app.preview.get_or_compute(&e.worktree.path.clone());
}
term.draw(|f| render::draw(f, &mut app))
.context("tui draw failed")?;
match events::next(&mut app)? {
Outcome::Continue => {}
Outcome::Cancel => return Err(Canceled.into()),
Outcome::Select(path) => return Ok(path),
}
}
}
fn ensure_tty() -> Result<()> {
if !io::stdin().is_terminal() {
anyhow::bail!("limb pick requires a terminal; stdin is not a tty");
}
if !io::stderr().is_terminal() {
anyhow::bail!("limb pick requires a terminal; stderr is not a tty");
}
Ok(())
}
struct RawTerminalGuard;
impl RawTerminalGuard {
fn enter(stderr: &mut Stderr) -> Result<Self> {
terminal::enable_raw_mode().context("enable raw mode")?;
execute!(
stderr,
terminal::EnterAlternateScreen,
crossterm::event::EnableMouseCapture
)
.context("enter alternate screen")?;
Ok(Self)
}
}
impl Drop for RawTerminalGuard {
fn drop(&mut self) {
let _ = execute!(
io::stderr(),
crossterm::event::DisableMouseCapture,
terminal::LeaveAlternateScreen
);
let _ = terminal::disable_raw_mode();
}
}