fix(terminal): switch to alternate screen buffer for clean exit
TUI frame was left in terminal after exit. Use EnterAlternateScreen/ LeaveAlternateScreen so exiting restores the original terminal content.
This commit is contained in:
+15
-26
@@ -1,22 +1,11 @@
|
|||||||
//! Terminal initialization, restoration, and panic hook for bbs-md.
|
//! Terminal initialization, restoration, and panic hook for bbs-md.
|
||||||
//!
|
//!
|
||||||
//! # Design
|
|
||||||
//!
|
|
||||||
//! This module uses the main screen buffer (no alternate screen) for the immersive BBS feel.
|
|
||||||
//! On exit, TUI output remains visible in the terminal scroll history — the user can scroll
|
|
||||||
//! up to review what they read, and the goodbye message persists after the process exits.
|
|
||||||
//!
|
|
||||||
//! # Broken Pipe Safety (LIFE-04)
|
//! # Broken Pipe Safety (LIFE-04)
|
||||||
//!
|
//!
|
||||||
//! All write operations in the TUI must use `let _ =` or handle `ErrorKind::BrokenPipe`.
|
//! All write operations in the TUI must use `let _ =` or handle `ErrorKind::BrokenPipe`.
|
||||||
//! SSH connections can close mid-write; Rust propagates this as `ErrorKind::BrokenPipe`.
|
//! SSH connections can close mid-write; Rust propagates this as `ErrorKind::BrokenPipe`.
|
||||||
//! The actual enforcement happens in the event loop (Plan 03), but `restore_terminal()`
|
//! The actual enforcement happens in the event loop, but `restore_terminal()`
|
||||||
//! already follows this pattern with `let _ =` on every call.
|
//! already follows this pattern with `let _ =` on every call.
|
||||||
//!
|
|
||||||
//! # Important
|
|
||||||
//!
|
|
||||||
//! Do NOT call `ratatui::restore()` — it enters alternate screen via `LeaveAlternateScreen`,
|
|
||||||
//! which we never entered. Use `restore_terminal()` from this module instead.
|
|
||||||
|
|
||||||
// Functions in this module are used in Plan 02 and 03; suppress Phase 1 dead code warnings.
|
// Functions in this module are used in Plan 02 and 03; suppress Phase 1 dead code warnings.
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
@@ -24,26 +13,24 @@
|
|||||||
use std::io::stdout;
|
use std::io::stdout;
|
||||||
use ratatui::crossterm::{
|
use ratatui::crossterm::{
|
||||||
execute,
|
execute,
|
||||||
terminal::{enable_raw_mode, disable_raw_mode, Clear, ClearType},
|
terminal::{enable_raw_mode, disable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
cursor::{MoveTo, Show},
|
cursor::Show,
|
||||||
};
|
};
|
||||||
use ratatui::{backend::CrosstermBackend, Terminal, TerminalOptions, Viewport};
|
use ratatui::{backend::CrosstermBackend, Terminal, TerminalOptions, Viewport};
|
||||||
|
|
||||||
/// Type alias for the terminal used throughout the application.
|
/// Type alias for the terminal used throughout the application.
|
||||||
pub type Term = Terminal<CrosstermBackend<std::io::Stdout>>;
|
pub type Term = Terminal<CrosstermBackend<std::io::Stdout>>;
|
||||||
|
|
||||||
/// Initialize the terminal on the main screen buffer with raw mode enabled.
|
/// Initialize the terminal with alternate screen buffer and raw mode.
|
||||||
///
|
///
|
||||||
/// This does NOT enter the alternate screen buffer. Instead it clears the main screen
|
/// Uses the alternate screen so the TUI exits cleanly without leaving artifacts.
|
||||||
/// so the TUI occupies the user's actual terminal. On exit, TUI output remains in
|
|
||||||
/// scroll history.
|
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns an error if raw mode cannot be enabled or if the screen cannot be cleared.
|
/// Returns an error if raw mode cannot be enabled or the alternate screen cannot be entered.
|
||||||
pub fn init_terminal() -> std::io::Result<Term> {
|
pub fn init_terminal() -> std::io::Result<Term> {
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
execute!(stdout(), Clear(ClearType::All), MoveTo(0, 0))?;
|
execute!(stdout(), EnterAlternateScreen)?;
|
||||||
let backend = CrosstermBackend::new(stdout());
|
let backend = CrosstermBackend::new(stdout());
|
||||||
Terminal::with_options(backend, TerminalOptions {
|
Terminal::with_options(backend, TerminalOptions {
|
||||||
viewport: Viewport::Fullscreen,
|
viewport: Viewport::Fullscreen,
|
||||||
@@ -52,13 +39,11 @@ pub fn init_terminal() -> std::io::Result<Term> {
|
|||||||
|
|
||||||
/// Restore the terminal to a usable state.
|
/// Restore the terminal to a usable state.
|
||||||
///
|
///
|
||||||
/// Disables raw mode and shows the cursor. Every operation uses `let _ =` to suppress
|
/// Leaves alternate screen, disables raw mode, and shows the cursor. Every operation
|
||||||
/// errors — this function is called from cleanup paths including the panic hook, where
|
/// uses `let _ =` to suppress errors — this function is called from cleanup paths
|
||||||
/// panicking would hide the original error.
|
/// including the panic hook, where panicking would hide the original error.
|
||||||
///
|
|
||||||
/// Do NOT call `ratatui::restore()` — it unconditionally calls `LeaveAlternateScreen`,
|
|
||||||
/// which corrupts the terminal since we never entered the alternate screen.
|
|
||||||
pub fn restore_terminal() {
|
pub fn restore_terminal() {
|
||||||
|
let _ = execute!(std::io::stdout(), LeaveAlternateScreen);
|
||||||
let _ = disable_raw_mode();
|
let _ = disable_raw_mode();
|
||||||
let _ = execute!(std::io::stdout(), Show);
|
let _ = execute!(std::io::stdout(), Show);
|
||||||
}
|
}
|
||||||
@@ -75,6 +60,10 @@ pub fn install_panic_hook() {
|
|||||||
let original_hook = std::panic::take_hook();
|
let original_hook = std::panic::take_hook();
|
||||||
std::panic::set_hook(Box::new(move |panic_info| {
|
std::panic::set_hook(Box::new(move |panic_info| {
|
||||||
// Restore terminal — use let _ to avoid double-panic if cleanup itself fails
|
// Restore terminal — use let _ to avoid double-panic if cleanup itself fails
|
||||||
|
let _ = ratatui::crossterm::execute!(
|
||||||
|
std::io::stdout(),
|
||||||
|
ratatui::crossterm::terminal::LeaveAlternateScreen
|
||||||
|
);
|
||||||
let _ = ratatui::crossterm::terminal::disable_raw_mode();
|
let _ = ratatui::crossterm::terminal::disable_raw_mode();
|
||||||
let _ = ratatui::crossterm::execute!(
|
let _ = ratatui::crossterm::execute!(
|
||||||
std::io::stdout(),
|
std::io::stdout(),
|
||||||
|
|||||||
Reference in New Issue
Block a user