hojicha_runtime/program/
terminal_manager.rs1use crate::program::MouseMode;
7use crossterm::{
8 cursor, execute,
9 terminal::{
10 self, disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
11 },
12};
13use std::io::{self, Stdout, Write};
14
15#[derive(Debug, Clone)]
17pub struct TerminalConfig {
18 pub alt_screen: bool,
20 pub mouse_mode: MouseMode,
22 pub bracketed_paste: bool,
24 pub focus_reporting: bool,
26 pub headless: bool,
28}
29
30impl Default for TerminalConfig {
31 fn default() -> Self {
32 Self {
33 alt_screen: true,
34 mouse_mode: MouseMode::None,
35 bracketed_paste: false,
36 focus_reporting: false,
37 headless: false,
38 }
39 }
40}
41
42pub struct TerminalManager {
44 stdout: Option<Stdout>,
45 config: TerminalConfig,
46 is_released: bool,
47}
48
49impl TerminalManager {
50 pub fn new(config: TerminalConfig) -> io::Result<Self> {
52 let headless = config.headless;
53 let mut manager = Self {
54 stdout: if !headless { Some(io::stdout()) } else { None },
55 config,
56 is_released: false,
57 };
58
59 if !headless {
60 manager.setup()?;
61 }
62
63 Ok(manager)
64 }
65
66 fn setup(&mut self) -> io::Result<()> {
68 if let Some(ref mut stdout) = self.stdout {
69 enable_raw_mode()?;
71
72 if self.config.alt_screen {
74 execute!(stdout, EnterAlternateScreen)?;
75 }
76
77 execute!(stdout, terminal::Clear(terminal::ClearType::All))?;
79 execute!(stdout, cursor::MoveTo(0, 0))?;
80
81 execute!(stdout, cursor::Hide)?;
83
84 match self.config.mouse_mode {
86 MouseMode::CellMotion => {
87 execute!(stdout, crossterm::event::EnableMouseCapture)?;
88 }
89 MouseMode::AllMotion => {
90 execute!(stdout, crossterm::event::EnableMouseCapture,)?;
91 }
92 MouseMode::None => {}
93 }
94
95 if self.config.bracketed_paste {
97 execute!(stdout, crossterm::event::EnableBracketedPaste)?;
98 }
99
100 if self.config.focus_reporting {
102 execute!(stdout, crossterm::event::EnableFocusChange)?;
103 }
104
105 stdout.flush()?;
106 }
107
108 Ok(())
109 }
110
111 pub fn cleanup(&mut self) -> io::Result<()> {
113 if self.is_released || self.config.headless {
114 return Ok(());
115 }
116
117 if let Some(ref mut stdout) = self.stdout {
118 execute!(stdout, cursor::Show)?;
120
121 if self.config.focus_reporting {
123 execute!(stdout, crossterm::event::DisableFocusChange)?;
124 }
125
126 if self.config.bracketed_paste {
128 execute!(stdout, crossterm::event::DisableBracketedPaste)?;
129 }
130
131 if self.config.mouse_mode != MouseMode::None {
133 execute!(stdout, crossterm::event::DisableMouseCapture)?;
134 }
135
136 if self.config.alt_screen {
138 execute!(stdout, LeaveAlternateScreen)?;
139 }
140
141 disable_raw_mode()?;
143
144 stdout.flush()?;
145 }
146
147 self.is_released = true;
148 Ok(())
149 }
150
151 pub fn stdout_mut(&mut self) -> Option<&mut Stdout> {
153 self.stdout.as_mut()
154 }
155
156 pub fn is_setup(&self) -> bool {
158 !self.is_released && self.stdout.is_some()
159 }
160
161 pub fn size(&self) -> io::Result<(u16, u16)> {
163 terminal::size()
164 }
165
166 pub fn release(&mut self) -> io::Result<()> {
168 self.cleanup()
169 }
170
171 pub fn restore(&mut self) -> io::Result<()> {
173 self.cleanup()
174 }
175}
176
177impl Drop for TerminalManager {
178 fn drop(&mut self) {
179 let _ = self.cleanup();
181 }
182}