gnostr_asyncgit/gitui/term/
mod.rs

1use 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
19///
20pub type Term = Terminal<TermBackend>;
21
22// TODO It would be more logical if the following top-level functions also were in 'TermBackend'.
23//      However left here for now.
24
25///
26pub 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
37///
38pub 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
54///
55pub fn cleanup_alternate_screen() {
56    print_err(stderr().execute(LeaveAlternateScreen));
57}
58
59///
60pub fn cleanup_raw_mode() {
61    print_err(disable_raw_mode());
62}
63
64///
65fn print_err<T, E: Display>(result: Result<T, E>) {
66    match result {
67        Ok(_) => (),
68        Err(error) => eprintln!("Error: {}", error),
69    };
70}
71
72///
73pub fn backend() -> TermBackend {
74    TermBackend::Crossterm(CrosstermBackend::new(stderr()))
75}
76
77///
78pub enum TermBackend {
79    ///
80    Crossterm(CrosstermBackend<Stderr>),
81    #[allow(dead_code)]
82    ///
83    Test {
84        ///
85        backend: TestBackend,
86        ///
87        events: Vec<Event>,
88    },
89}
90
91///
92impl 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    ///
104    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    ///
112    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    ///
120    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    ///
128    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    ///
136    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    ///
144    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    ///
152    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    ///
160    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
168///
169impl TermBackend {
170    ///
171    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    ///
182    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    ///
190    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    ///
198    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    ///
212    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}