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}