hojicha-runtime 0.2.2

Event handling and async runtime for Hojicha TUI framework
Documentation
//! Terminal management using crossterm for direct control
//!
//! This module handles raw terminal setup, alternate screen management,
//! and terminal state restoration for clean program exits.

use crate::program::MouseMode;
use crossterm::{
    cursor, execute,
    terminal::{
        self, disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
    },
};
use std::io::{self, Stdout, Write};

/// Configuration for terminal setup
#[derive(Debug, Clone)]
pub struct TerminalConfig {
    /// Whether to use alternate screen buffer
    pub alt_screen: bool,
    /// Mouse input mode configuration
    pub mouse_mode: MouseMode,
    /// Whether to enable bracketed paste mode
    pub bracketed_paste: bool,
    /// Whether to enable terminal focus reporting
    pub focus_reporting: bool,
    /// Whether to run in headless mode (no terminal setup)
    pub headless: bool,
}

impl Default for TerminalConfig {
    fn default() -> Self {
        Self {
            alt_screen: true,
            mouse_mode: MouseMode::None,
            bracketed_paste: false,
            focus_reporting: false,
            headless: false,
        }
    }
}

/// Manages terminal setup, teardown, and state
pub struct TerminalManager {
    stdout: Option<Stdout>,
    config: TerminalConfig,
    is_released: bool,
}

impl TerminalManager {
    /// Create a new terminal manager
    pub fn new(config: TerminalConfig) -> io::Result<Self> {
        let headless = config.headless;
        let mut manager = Self {
            stdout: if !headless { Some(io::stdout()) } else { None },
            config,
            is_released: false,
        };

        if !headless {
            manager.setup()?;
        }

        Ok(manager)
    }

    /// Set up the terminal with the given configuration
    fn setup(&mut self) -> io::Result<()> {
        if let Some(ref mut stdout) = self.stdout {
            // Enable raw mode
            enable_raw_mode()?;

            // Enter alternate screen if requested
            if self.config.alt_screen {
                execute!(stdout, EnterAlternateScreen)?;
            }

            // Clear screen
            execute!(stdout, terminal::Clear(terminal::ClearType::All))?;
            execute!(stdout, cursor::MoveTo(0, 0))?;

            // Hide cursor by default (components can show it when needed)
            execute!(stdout, cursor::Hide)?;

            // Set up mouse mode
            match self.config.mouse_mode {
                MouseMode::CellMotion => {
                    execute!(stdout, crossterm::event::EnableMouseCapture)?;
                }
                MouseMode::AllMotion => {
                    execute!(stdout, crossterm::event::EnableMouseCapture,)?;
                }
                MouseMode::None => {}
            }

            // Enable bracketed paste if requested
            if self.config.bracketed_paste {
                execute!(stdout, crossterm::event::EnableBracketedPaste)?;
            }

            // Enable focus reporting if requested
            if self.config.focus_reporting {
                execute!(stdout, crossterm::event::EnableFocusChange)?;
            }

            stdout.flush()?;
        }

        Ok(())
    }

    /// Clean up terminal settings
    pub fn cleanup(&mut self) -> io::Result<()> {
        if self.is_released || self.config.headless {
            return Ok(());
        }

        if let Some(ref mut stdout) = self.stdout {
            // Show cursor
            execute!(stdout, cursor::Show)?;

            // Disable focus reporting
            if self.config.focus_reporting {
                execute!(stdout, crossterm::event::DisableFocusChange)?;
            }

            // Disable bracketed paste
            if self.config.bracketed_paste {
                execute!(stdout, crossterm::event::DisableBracketedPaste)?;
            }

            // Disable mouse capture
            if self.config.mouse_mode != MouseMode::None {
                execute!(stdout, crossterm::event::DisableMouseCapture)?;
            }

            // Leave alternate screen
            if self.config.alt_screen {
                execute!(stdout, LeaveAlternateScreen)?;
            }

            // Disable raw mode
            disable_raw_mode()?;

            stdout.flush()?;
        }

        self.is_released = true;
        Ok(())
    }

    /// Get a mutable reference to stdout
    pub fn stdout_mut(&mut self) -> Option<&mut Stdout> {
        self.stdout.as_mut()
    }

    /// Check if terminal is set up
    pub fn is_setup(&self) -> bool {
        !self.is_released && self.stdout.is_some()
    }

    /// Get terminal size
    pub fn size(&self) -> io::Result<(u16, u16)> {
        terminal::size()
    }

    /// Release the terminal (alias for cleanup)
    pub fn release(&mut self) -> io::Result<()> {
        self.cleanup()
    }

    /// Restore the terminal (alias for cleanup)
    pub fn restore(&mut self) -> io::Result<()> {
        self.cleanup()
    }
}

impl Drop for TerminalManager {
    fn drop(&mut self) {
        // Attempt cleanup, but don't panic if it fails
        let _ = self.cleanup();
    }
}