git_iblame/extensions/
terminal_raw_mode_scope.rs

1use std::io;
2
3use crossterm::{execute, terminal};
4use log::*;
5
6/// Enable or disable the
7/// [terminal raw mode](https://docs.rs/crossterm/latest/crossterm/terminal/index.html#raw-mode)
8/// while its instance is in scope.
9///
10/// While it automatically resets the mode when it's out of scope,
11/// `reset()` should be called explicitly when the mode should be reset
12/// or at the end of the scope
13/// to avoid it being dropped earlier by the compiler.
14/// # Examples
15/// ```no_run
16/// # fn main() -> std::io::Result<()> {
17/// use git_iblame::extensions::TerminalRawModeScope;
18///
19/// let mut terminal_raw_mode = TerminalRawModeScope::new(true)?;
20/// // Do the work.
21/// // If it returns early, the terminal raw mode will be reset automatically.
22/// terminal_raw_mode.reset()?;
23/// # Ok(())
24/// # }
25/// ```
26#[derive(Debug)]
27pub struct TerminalRawModeScope {
28    is_enabled: bool,
29    is_alt_screen_enabled: bool,
30    is_reset: bool,
31}
32
33impl TerminalRawModeScope {
34    /// Enable the raw mode if `enable` is true,
35    /// or disable it if `enable` is false.
36    pub fn new(enable: bool) -> io::Result<Self> {
37        Self::enable(enable)?;
38        Ok(Self {
39            is_enabled: enable,
40            is_alt_screen_enabled: false,
41            is_reset: false,
42        })
43    }
44
45    /// Switches to the alternate screen, in addition to the raw mode.
46    /// See `crossterm::terminal::EnterAlternateScreen`.
47    pub fn new_with_alternate_screen() -> io::Result<Self> {
48        Self::enable(true)?;
49        Self::enable_alternate_screen(true)?;
50        Ok(Self {
51            is_enabled: true,
52            is_alt_screen_enabled: true,
53            is_reset: false,
54        })
55    }
56
57    /// Reset the terminal raw mode.
58    /// This should be called when the mode should be reset,
59    /// or at the end of the scope.
60    ///
61    /// Even if the mode should be reset at the end of the scope,
62    /// and that the `Drop` trait should reset the raw mode,
63    /// it should be called to avoid it being dropped earlier by the compiler.
64    pub fn reset(&mut self) -> io::Result<()> {
65        if self.is_reset {
66            return Ok(());
67        }
68        Self::enable(!self.is_enabled)?;
69        if self.is_alt_screen_enabled {
70            Self::enable_alternate_screen(false)?;
71        }
72        self.is_reset = true;
73        Ok(())
74    }
75
76    fn enable(enable: bool) -> io::Result<()> {
77        debug!("TerminalRawModeScope.enable({enable})");
78        if enable {
79            terminal::enable_raw_mode()
80        } else {
81            terminal::disable_raw_mode()
82        }
83    }
84
85    fn enable_alternate_screen(enable: bool) -> io::Result<()> {
86        debug!("TerminalRawModeScope.enable_alternate_screen({enable})");
87        if enable {
88            execute!(io::stdout(), terminal::EnterAlternateScreen)
89        } else {
90            execute!(io::stdout(), terminal::LeaveAlternateScreen)
91        }
92    }
93}
94
95impl Drop for TerminalRawModeScope {
96    /// Calls `reset()` if it's not already reset.
97    /// This is called when the instance goes out of scope.
98    fn drop(&mut self) {
99        if let Err(error) = self.reset() {
100            warn!("Failed to change terminal raw mode, ignored: {error}");
101        }
102    }
103}