collet 0.1.0

Relentless agentic coding orchestrator with zero-drop agent loops
Documentation
use anyhow::Result;
use crossterm::{
    event::{
        DisableBracketedPaste, EnableBracketedPaste, KeyboardEnhancementFlags,
        PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
    },
    execute,
    terminal::{
        EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
        supports_keyboard_enhancement,
    },
};

/// Enable mouse reporting for scroll and click only — intentionally omits
/// button-event tracking (1002h / drag) so the terminal emulator can still
/// handle native text selection on mouse drag without holding Shift.
///
/// Sequences sent: 1000h (normal tracking) + 1006h (SGR extended).
struct EnableScrollMouseCapture;
impl crossterm::Command for EnableScrollMouseCapture {
    fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
        f.write_str("\x1b[?1000h\x1b[?1006h")
    }
    #[cfg(windows)]
    fn execute_winapi(&self) -> std::io::Result<()> {
        use crossterm::event::EnableMouseCapture;
        EnableMouseCapture.execute_winapi()
    }
}

/// Disable the scroll/click mouse reporting enabled by [`EnableScrollMouseCapture`].
struct DisableScrollMouseCapture;
impl crossterm::Command for DisableScrollMouseCapture {
    fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
        f.write_str("\x1b[?1006l\x1b[?1000l")
    }
    #[cfg(windows)]
    fn execute_winapi(&self) -> std::io::Result<()> {
        use crossterm::event::DisableMouseCapture;
        DisableMouseCapture.execute_winapi()
    }
}
use ratatui::prelude::*;
use std::io::{self, Stdout};

/// Redirect stderr to `<log_dir>/stderr.YYYY-MM-DD.log` before TUI starts.
///
/// Any stray `eprintln!` or background-thread errors (e.g. from tracing-appender)
/// would otherwise appear as raw text over the alternate screen. Files older
/// than 7 days are pruned. Silently ignored if the file cannot be opened.
#[cfg(unix)]
pub fn redirect_stderr(log_dir: &std::path::Path) {
    use std::os::unix::io::IntoRawFd;
    let path = crate::config::dated_log_path(log_dir, "stderr", 7);
    if let Ok(f) = std::fs::OpenOptions::new()
        .create(true)
        .append(true)
        .open(&path)
    {
        unsafe {
            libc::dup2(f.into_raw_fd(), 2);
        }
    }
}

#[cfg(not(unix))]
pub fn redirect_stderr(_log_dir: &std::path::Path) {}

pub type Tui = Terminal<CrosstermBackend<Stdout>>;

/// Initialize terminal: raw mode + alternate screen.
pub fn init() -> Result<Tui> {
    // Install panic hook to restore terminal on crash.
    // Use a flag to prevent infinite recursion if restore() itself panics.
    let original_hook = std::panic::take_hook();
    std::panic::set_hook(Box::new(move |panic_info| {
        use std::sync::atomic::{AtomicBool, Ordering};
        static IN_PANIC: AtomicBool = AtomicBool::new(false);
        if !IN_PANIC.swap(true, Ordering::SeqCst) {
            let _ = restore();
        }
        original_hook(panic_info);
    }));

    enable_raw_mode()?;
    let mut stdout = io::stdout();
    execute!(
        stdout,
        EnterAlternateScreen,
        EnableScrollMouseCapture,
        EnableBracketedPaste
    )?;

    // Enable keyboard enhancement protocol (kitty/Windows Terminal) so that
    // Shift+Enter, Ctrl+Enter, etc. are distinguishable from plain Enter.
    // Only DISAMBIGUATE_ESCAPE_CODES — REPORT_ALL_KEYS_AS_ESCAPE_CODES causes
    // ESC sequence parser races that corrupt SGR mouse events into raw text.
    if supports_keyboard_enhancement().unwrap_or(false) {
        let _ = execute!(
            stdout,
            PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES)
        );
    }

    let backend = CrosstermBackend::new(stdout);
    let terminal = Terminal::new(backend)?;
    Ok(terminal)
}

/// Restore terminal to normal state.
pub fn restore() -> Result<()> {
    // Pop keyboard enhancement if it was pushed.
    if supports_keyboard_enhancement().unwrap_or(false) {
        let _ = execute!(io::stdout(), PopKeyboardEnhancementFlags);
    }
    disable_raw_mode()?;
    execute!(
        io::stdout(),
        LeaveAlternateScreen,
        DisableScrollMouseCapture,
        DisableBracketedPaste
    )?;
    Ok(())
}