Skip to main content

rtcom_tui/
terminal.rs

1//! Terminal mode guards.
2//!
3//! [`RawModeGuard`] puts the terminal into raw mode on construction and
4//! restores cooked mode on drop. [`AltScreenGuard`] enters the alternate
5//! screen on construction and leaves it on drop.
6//! [`MouseCaptureGuard`] enables mouse-event reporting on construction
7//! and disables it on drop. Use in RAII order — drop in the reverse of
8//! construction for clean restoration.
9
10use std::io::{self, Stdout, Write};
11
12use anyhow::{Context, Result};
13use crossterm::{
14    event::{DisableMouseCapture, EnableMouseCapture},
15    execute,
16    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
17};
18
19/// RAII guard that keeps the terminal in raw mode.
20#[must_use = "dropping this guard disables raw mode immediately"]
21pub struct RawModeGuard {
22    _private: (),
23}
24
25impl RawModeGuard {
26    /// Enable raw mode on the controlling terminal. Returns an error if
27    /// the terminal API rejects the call (e.g. not a TTY).
28    ///
29    /// # Errors
30    ///
31    /// Propagates the crossterm error from `enable_raw_mode`.
32    pub fn enter() -> Result<Self> {
33        enable_raw_mode().context("enable raw mode")?;
34        Ok(Self { _private: () })
35    }
36}
37
38impl Drop for RawModeGuard {
39    fn drop(&mut self) {
40        let _ = disable_raw_mode();
41    }
42}
43
44/// RAII guard that keeps the terminal on the alternate screen.
45#[must_use = "dropping this guard leaves the alternate screen immediately"]
46pub struct AltScreenGuard {
47    stdout: Stdout,
48}
49
50impl AltScreenGuard {
51    /// Enter the alternate screen on stdout.
52    ///
53    /// # Errors
54    ///
55    /// Propagates the crossterm error from `EnterAlternateScreen`.
56    pub fn enter() -> Result<Self> {
57        let mut stdout = io::stdout();
58        execute!(stdout, EnterAlternateScreen).context("enter alt screen")?;
59        Ok(Self { stdout })
60    }
61}
62
63impl Drop for AltScreenGuard {
64    fn drop(&mut self) {
65        let _ = execute!(self.stdout, LeaveAlternateScreen);
66        let _ = self.stdout.flush();
67    }
68}
69
70/// RAII guard that enables terminal mouse-event reporting.
71///
72/// While this guard is alive, mouse click / drag / wheel events arrive
73/// via the crossterm event stream as [`crossterm::event::Event::Mouse`].
74/// On drop, mouse capture is disabled so the terminal emulator's own
75/// selection + copy bindings work again once rtcom exits.
76///
77/// Note: with capture enabled, the terminal's native text selection
78/// typically does *not* work on bare click-drag — most emulators
79/// (xterm, gnome-terminal, kitty, alacritty, iterm2) treat `Shift+drag`
80/// as a bypass, returning native selection to the user on demand. That
81/// is the v0.2 workflow for copying text; a native rtcom selection
82/// implementation ships in v0.2.1.
83#[must_use = "dropping this guard disables mouse capture immediately"]
84pub struct MouseCaptureGuard {
85    stdout: Stdout,
86}
87
88impl MouseCaptureGuard {
89    /// Enable mouse-event reporting on stdout.
90    ///
91    /// # Errors
92    ///
93    /// Propagates the crossterm error from `EnableMouseCapture`.
94    pub fn enable() -> Result<Self> {
95        let mut stdout = io::stdout();
96        execute!(stdout, EnableMouseCapture).context("enable mouse capture")?;
97        Ok(Self { stdout })
98    }
99}
100
101impl Drop for MouseCaptureGuard {
102    fn drop(&mut self) {
103        let _ = execute!(self.stdout, DisableMouseCapture);
104        let _ = self.stdout.flush();
105    }
106}