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>;
20
21pub fn alternate_screen<T, F: Fn() -> Res<T>>(fun: F) -> Res<T> {
25 stderr()
26 .execute(EnterAlternateScreen)
27 .map_err(Error::Term)?;
28 let result = fun();
29 stderr()
30 .execute(LeaveAlternateScreen)
31 .map_err(Error::Term)?;
32 result
33}
34
35pub fn raw_mode<T, F: Fn() -> Res<T>>(fun: F) -> Res<T> {
36 let was_raw_mode_enabled = is_raw_mode_enabled().map_err(Error::Term)?;
37
38 if !was_raw_mode_enabled {
39 enable_raw_mode().map_err(Error::Term)?;
40 }
41
42 let result = fun();
43
44 if !was_raw_mode_enabled {
45 disable_raw_mode().map_err(Error::Term)?;
46 }
47
48 result
49}
50
51pub fn cleanup_alternate_screen() {
52 print_err(stderr().execute(LeaveAlternateScreen));
53}
54
55pub fn cleanup_raw_mode() {
56 print_err(disable_raw_mode());
57}
58
59fn print_err<T, E: Display>(result: Result<T, E>) {
60 match result {
61 Ok(_) => (),
62 Err(error) => eprintln!("Error: {}", error),
63 };
64}
65
66pub fn backend() -> TermBackend {
67 TermBackend::Crossterm(CrosstermBackend::new(stderr()))
68}
69
70pub enum TermBackend {
71 Crossterm(CrosstermBackend<Stderr>),
72 #[allow(dead_code)]
73 Test {
74 backend: TestBackend,
75 events: Vec<Event>,
76 },
77}
78
79impl Backend for TermBackend {
80 fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
81 where
82 I: Iterator<Item = (u16, u16, &'a Cell)>,
83 {
84 match self {
85 TermBackend::Crossterm(t) => t.draw(content),
86 TermBackend::Test { backend, .. } => backend.draw(content),
87 }
88 }
89
90 fn hide_cursor(&mut self) -> io::Result<()> {
91 match self {
92 TermBackend::Crossterm(t) => t.hide_cursor(),
93 TermBackend::Test { backend, .. } => backend.hide_cursor(),
94 }
95 }
96
97 fn show_cursor(&mut self) -> io::Result<()> {
98 match self {
99 TermBackend::Crossterm(t) => t.show_cursor(),
100 TermBackend::Test { backend, .. } => backend.show_cursor(),
101 }
102 }
103
104 fn get_cursor_position(&mut self) -> io::Result<Position> {
105 match self {
106 TermBackend::Crossterm(t) => t.get_cursor_position(),
107 TermBackend::Test { backend, .. } => backend.get_cursor_position(),
108 }
109 }
110
111 fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> io::Result<()> {
112 match self {
113 TermBackend::Crossterm(t) => t.set_cursor_position(position),
114 TermBackend::Test { backend, .. } => backend.set_cursor_position(position),
115 }
116 }
117
118 fn clear(&mut self) -> io::Result<()> {
119 match self {
120 TermBackend::Crossterm(t) => t.clear(),
121 TermBackend::Test { backend, .. } => backend.clear(),
122 }
123 }
124
125 fn size(&self) -> io::Result<Size> {
126 match self {
127 TermBackend::Crossterm(t) => t.size(),
128 TermBackend::Test { backend, .. } => backend.size(),
129 }
130 }
131
132 fn window_size(&mut self) -> io::Result<WindowSize> {
133 match self {
134 TermBackend::Crossterm(t) => t.window_size(),
135 TermBackend::Test { backend, .. } => backend.window_size(),
136 }
137 }
138
139 fn flush(&mut self) -> io::Result<()> {
140 match self {
141 TermBackend::Crossterm(t) => t.flush(),
142 TermBackend::Test { backend, .. } => backend.flush(),
143 }
144 }
145}
146
147impl TermBackend {
148 pub fn enter_alternate_screen(&mut self) -> Res<()> {
149 match self {
150 TermBackend::Crossterm(c) => c
151 .execute(EnterAlternateScreen)
152 .map_err(Error::Term)
153 .map(|_| ()),
154 TermBackend::Test { .. } => Ok(()),
155 }
156 }
157
158 pub fn enable_raw_mode(&self) -> Res<()> {
159 match self {
160 TermBackend::Crossterm(_) => enable_raw_mode().map_err(Error::Term),
161 TermBackend::Test { .. } => Ok(()),
162 }
163 }
164
165 pub fn disable_raw_mode(&self) -> Res<()> {
166 match self {
167 TermBackend::Crossterm(_) => disable_raw_mode().map_err(Error::Term),
168 TermBackend::Test { .. } => Ok(()),
169 }
170 }
171
172 pub fn poll_event(&self, timeout: Duration) -> Res<bool> {
173 match self {
174 TermBackend::Crossterm(_) => crossterm::event::poll(timeout).map_err(Error::Term),
175 TermBackend::Test { events, .. } => {
176 if events.is_empty() {
177 Err(Error::NoMoreEvents)
178 } else {
179 Ok(true)
180 }
181 }
182 }
183 }
184
185 pub fn read_event(&mut self) -> Res<Event> {
186 match self {
187 TermBackend::Crossterm(_) => crossterm::event::read().map_err(Error::Term),
188 TermBackend::Test { events, .. } => events.pop().ok_or(Error::NoMoreEvents),
189 }
190 }
191}