gnostr_asyncgit/gitui/term/
mod.rs1use crate::{gitui::gitui_error::Error, gitui::Res};
2use crossterm::{
3 event::Event,
4 terminal::{
5 disable_raw_mode, enable_raw_mode, is_raw_mode_enabled, EnterAlternateScreen,
6 LeaveAlternateScreen,
7 },
8 ExecutableCommand,
9};
10use ratatui::{
11 backend::{Backend, CrosstermBackend, TestBackend},
12 layout::Size,
13 prelude::{backend::WindowSize, buffer::Cell, Position},
14 Terminal,
15};
16use std::io::{self, stderr, Stderr};
17use std::{fmt::Display, time::Duration};
18
19pub type Term = Terminal<TermBackend>;
21
22pub fn alternate_screen<T, F: Fn() -> Res<T>>(fun: F) -> Res<T> {
27 stderr()
28 .execute(EnterAlternateScreen)
29 .map_err(Error::Term)?;
30 let result = fun();
31 stderr()
32 .execute(LeaveAlternateScreen)
33 .map_err(Error::Term)?;
34 result
35}
36
37pub fn raw_mode<T, F: Fn() -> Res<T>>(fun: F) -> Res<T> {
39 let was_raw_mode_enabled = is_raw_mode_enabled().map_err(Error::Term)?;
40
41 if !was_raw_mode_enabled {
42 enable_raw_mode().map_err(Error::Term)?;
43 }
44
45 let result = fun();
46
47 if !was_raw_mode_enabled {
48 disable_raw_mode().map_err(Error::Term)?;
49 }
50
51 result
52}
53
54pub fn cleanup_alternate_screen() {
56 print_err(stderr().execute(LeaveAlternateScreen));
57}
58
59pub fn cleanup_raw_mode() {
61 print_err(disable_raw_mode());
62}
63
64fn print_err<T, E: Display>(result: Result<T, E>) {
66 match result {
67 Ok(_) => (),
68 Err(error) => eprintln!("Error: {}", error),
69 };
70}
71
72pub fn backend() -> TermBackend {
74 TermBackend::Crossterm(CrosstermBackend::new(stderr()))
75}
76
77pub enum TermBackend {
79 Crossterm(CrosstermBackend<Stderr>),
81 #[allow(dead_code)]
82 Test {
84 backend: TestBackend,
86 events: Vec<Event>,
88 },
89}
90
91impl Backend for TermBackend {
93 fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
94 where
95 I: Iterator<Item = (u16, u16, &'a Cell)>,
96 {
97 match self {
98 TermBackend::Crossterm(t) => t.draw(content),
99 TermBackend::Test { backend, .. } => backend.draw(content),
100 }
101 }
102
103 fn hide_cursor(&mut self) -> io::Result<()> {
105 match self {
106 TermBackend::Crossterm(t) => t.hide_cursor(),
107 TermBackend::Test { backend, .. } => backend.hide_cursor(),
108 }
109 }
110
111 fn show_cursor(&mut self) -> io::Result<()> {
113 match self {
114 TermBackend::Crossterm(t) => t.show_cursor(),
115 TermBackend::Test { backend, .. } => backend.show_cursor(),
116 }
117 }
118
119 fn get_cursor_position(&mut self) -> io::Result<Position> {
121 match self {
122 TermBackend::Crossterm(t) => t.get_cursor_position(),
123 TermBackend::Test { backend, .. } => backend.get_cursor_position(),
124 }
125 }
126
127 fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> io::Result<()> {
129 match self {
130 TermBackend::Crossterm(t) => t.set_cursor_position(position),
131 TermBackend::Test { backend, .. } => backend.set_cursor_position(position),
132 }
133 }
134
135 fn clear(&mut self) -> io::Result<()> {
137 match self {
138 TermBackend::Crossterm(t) => t.clear(),
139 TermBackend::Test { backend, .. } => backend.clear(),
140 }
141 }
142
143 fn size(&self) -> io::Result<Size> {
145 match self {
146 TermBackend::Crossterm(t) => t.size(),
147 TermBackend::Test { backend, .. } => backend.size(),
148 }
149 }
150
151 fn window_size(&mut self) -> io::Result<WindowSize> {
153 match self {
154 TermBackend::Crossterm(t) => t.window_size(),
155 TermBackend::Test { backend, .. } => backend.window_size(),
156 }
157 }
158
159 fn flush(&mut self) -> io::Result<()> {
161 match self {
162 TermBackend::Crossterm(t) => t.flush(),
163 TermBackend::Test { backend, .. } => backend.flush(),
164 }
165 }
166}
167
168impl TermBackend {
170 pub fn enter_alternate_screen(&mut self) -> Res<()> {
172 match self {
173 TermBackend::Crossterm(c) => c
174 .execute(EnterAlternateScreen)
175 .map_err(Error::Term)
176 .map(|_| ()),
177 TermBackend::Test { .. } => Ok(()),
178 }
179 }
180
181 pub fn enable_raw_mode(&self) -> Res<()> {
183 match self {
184 TermBackend::Crossterm(_) => enable_raw_mode().map_err(Error::Term),
185 TermBackend::Test { .. } => Ok(()),
186 }
187 }
188
189 pub fn disable_raw_mode(&self) -> Res<()> {
191 match self {
192 TermBackend::Crossterm(_) => disable_raw_mode().map_err(Error::Term),
193 TermBackend::Test { .. } => Ok(()),
194 }
195 }
196
197 pub fn poll_event(&self, timeout: Duration) -> Res<bool> {
199 match self {
200 TermBackend::Crossterm(_) => crossterm::event::poll(timeout).map_err(Error::Term),
201 TermBackend::Test { events, .. } => {
202 if events.is_empty() {
203 Err(Error::NoMoreEvents)
204 } else {
205 Ok(true)
206 }
207 }
208 }
209 }
210
211 pub fn read_event(&mut self) -> Res<Event> {
213 match self {
214 TermBackend::Crossterm(_) => crossterm::event::read().map_err(Error::Term),
215 TermBackend::Test { events, .. } => events.pop().ok_or(Error::NoMoreEvents),
216 }
217 }
218}