envelope_cli/tui/
terminal.rs

1//! Terminal setup and teardown
2//!
3//! This module handles initializing and restoring the terminal state,
4//! including setting up the panic hook to restore the terminal on crash.
5
6use anyhow::Result;
7use crossterm::{
8    execute,
9    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
10};
11use ratatui::{backend::CrosstermBackend, Terminal};
12use std::io::{self, Stdout};
13use std::panic;
14
15use crate::config::paths::EnvelopePaths;
16use crate::config::settings::Settings;
17use crate::storage::Storage;
18
19use super::app::App;
20use super::event::{Event, EventHandler};
21use super::handler::handle_event;
22
23/// Type alias for our terminal
24pub type Tui = Terminal<CrosstermBackend<Stdout>>;
25
26/// Initialize the terminal for TUI mode
27pub fn init_terminal() -> Result<Tui> {
28    // Set up panic hook to restore terminal on panic
29    let original_hook = panic::take_hook();
30    panic::set_hook(Box::new(move |panic_info| {
31        // Restore terminal before printing panic info
32        let _ = restore_terminal_impl();
33        original_hook(panic_info);
34    }));
35
36    // Enable raw mode and enter alternate screen
37    enable_raw_mode()?;
38    let mut stdout = io::stdout();
39    execute!(stdout, EnterAlternateScreen)?;
40
41    // Create terminal
42    let backend = CrosstermBackend::new(stdout);
43    let terminal = Terminal::new(backend)?;
44
45    Ok(terminal)
46}
47
48/// Restore the terminal to its original state
49pub fn restore_terminal() -> Result<()> {
50    restore_terminal_impl()?;
51    Ok(())
52}
53
54/// Internal implementation of terminal restoration
55fn restore_terminal_impl() -> Result<()> {
56    disable_raw_mode()?;
57    execute!(io::stdout(), LeaveAlternateScreen)?;
58    Ok(())
59}
60
61/// Run the TUI application
62pub fn run_tui(storage: &Storage, settings: &Settings, paths: &EnvelopePaths) -> Result<()> {
63    // Initialize terminal
64    let mut terminal = init_terminal()?;
65
66    // Create app state
67    let mut app = App::new(storage, settings, paths);
68
69    // Initialize account selection
70    if let Ok(accounts) = storage.accounts.get_active() {
71        if let Some(first) = accounts.first() {
72            app.selected_account = Some(first.id);
73        }
74    }
75
76    // Create event handler
77    let events = EventHandler::default();
78
79    // Main event loop
80    loop {
81        // Render
82        terminal.draw(|frame| {
83            super::views::render(frame, &mut app);
84        })?;
85
86        // Handle events
87        match events.next()? {
88            Event::Key(key_event) => {
89                handle_event(&mut app, Event::Key(key_event))?;
90            }
91            Event::Mouse(mouse_event) => {
92                handle_event(&mut app, Event::Mouse(mouse_event))?;
93            }
94            Event::Resize(_, _) => {
95                // Terminal will redraw automatically
96            }
97            Event::Tick => {
98                // Clear transient status messages after some time
99                // (could add a timestamp check here)
100            }
101        }
102
103        // Check if we should quit
104        if app.should_quit {
105            break;
106        }
107    }
108
109    // Restore terminal
110    restore_terminal()?;
111
112    Ok(())
113}