1use crossterm::{
2 self as ct,
3 cursor::{MoveDown, MoveLeft, MoveRight, MoveUp},
4 style::Print,
5 terminal::{Clear, ClearType},
6};
7use std::cmp::Ordering;
8use std::io::{self, Write as _W};
9
10const MAX_PATCH_LINES: usize = 3;
13
14pub struct TermBuffer {
25 state: State,
26 flushed: State,
27
28 stdout: io::Stderr,
31}
32
33impl Drop for TermBuffer {
34 fn drop(&mut self) {
35 if !std::thread::panicking() {
36 self.state = Default::default();
37 self.render_frame();
38 }
39 self.cursor_to_end();
40
41 ct::queue!(self.stdout, Print("".to_string())).unwrap();
42 self.flush();
43 }
44}
45
46impl Default for TermBuffer {
47 fn default() -> Self {
48 Self::new()
49 }
50}
51
52impl TermBuffer {
53 pub fn new() -> Self {
54 TermBuffer {
55 state: Default::default(),
56 flushed: Default::default(),
57
58 stdout: io::stderr(),
61 }
62 }
63
64 pub fn push_line(&mut self, row: impl Into<String>) {
66 self.state.push(row);
67 }
68
69 pub fn lines(&self) -> u16 {
70 self.state.len() as u16
71 }
72
73 pub fn set_next_cursor(&mut self, cursor: (u16, u16)) {
75 self.state.set_cursor(cursor);
76 }
77
78 pub fn forget(&mut self) -> usize {
83 let lines = self.flushed.len();
84
85 self.cursor_to_end();
86 self.state = Default::default();
87 self.flushed = Default::default();
88
89 lines
90 }
91
92 pub fn render_frame(&mut self) {
95 let same_line_count = self.state.len() == self.flushed.len();
96
97 if !same_line_count {
98 return self.render_full();
99 }
100
101 let changed_lines: Vec<_> = self
102 .state
103 .iter()
104 .zip(self.flushed.iter())
105 .enumerate()
106 .filter_map(|(i, (a, b))| if a == b { None } else { Some(i) })
107 .collect();
108
109 let changed_cursor = self.state.cursor != self.flushed.cursor;
110
111 if changed_lines.is_empty() && !changed_cursor {
112 self.flushed = self.state.reset();
113 } else if changed_lines.is_empty() && changed_cursor {
114 match self.state.cursor.1 == self.flushed.cursor.1 {
115 true => self.render_one_line(self.state.cursor.1 as usize),
116 false => {
117 self.render_one_line(self.flushed.cursor.1 as usize);
118 self.render_one_line(self.state.cursor.1 as usize);
119 }
120 }
121 self.flushed = self.state.reset();
122 } else if !changed_lines.is_empty() && changed_lines.len() <= MAX_PATCH_LINES {
123 for line_num in changed_lines {
124 self.render_one_line(line_num);
125 }
126 self.flushed = self.state.reset();
127 } else {
128 self.render_full();
129 }
130 }
131
132 fn queue_move_cursor_y(&mut self, down: isize) {
133 match down.cmp(&0) {
134 Ordering::Greater => {
135 let down = down as u16;
136 ct::queue!(self.stdout, MoveDown(down), MoveLeft(1000)).unwrap();
137 }
138 Ordering::Less => {
139 let up = (-down) as u16;
140 ct::queue!(self.stdout, MoveUp(up), MoveLeft(1000)).unwrap();
141 }
142 _ => ct::queue!(self.stdout, MoveLeft(1000)).unwrap(),
143 }
144 }
145
146 pub fn render_one_line(&mut self, line_index: usize) {
147 let down = line_index as isize - self.flushed.cursor.1 as isize;
148
149 let state = self.state.clone();
150
151 self.queue_move_cursor_y(down);
152 let new_y = (self.flushed.cursor.1 as isize + down) as u16;
153
154 let (dx, dy) = state.cursor;
155
156 ct::queue!(self.stdout, Clear(ClearType::UntilNewLine)).unwrap();
157
158 ct::queue!(self.stdout, Print(state.rows[line_index].to_string())).unwrap();
159
160 ct::queue!(self.stdout, MoveLeft(1000)).unwrap();
164
165 self.queue_move_cursor_y(dy as isize - new_y as isize);
166 if dx > 0 {
167 ct::queue!(self.stdout, MoveRight(dx)).unwrap();
168 }
169
170 self.flushed.cursor = (dx, dy);
171 }
172
173 pub fn render_full(&mut self) {
175 self.cursor_to_start();
176 self.queue_clear();
177
178 let state = self.state.reset();
179
180 for item in state.rows.iter() {
181 ct::queue!(
182 self.stdout,
183 Print(item.to_string()),
184 Print("\n".to_string()),
185 MoveLeft(1000)
186 )
187 .unwrap();
188 }
189
190 let (cx, cy) = (0, state.len() as u16);
191 let (dx, dy) = state.get_cursor();
192 match dy.cmp(&cy) {
193 Ordering::Less => ct::queue!(self.stdout, MoveUp(cy - dy)).unwrap(),
194 Ordering::Greater => ct::queue!(self.stdout, MoveDown(dy - cy)).unwrap(),
195 _ => {}
196 }
197 match dx.cmp(&cx) {
198 Ordering::Less => ct::queue!(self.stdout, MoveLeft(cx - dx)).unwrap(),
199 Ordering::Greater => ct::queue!(self.stdout, MoveRight(dx - cx)).unwrap(),
200 _ => {}
201 }
202
203 ct::queue!(self.stdout, crate::color::reset_item()).unwrap();
204
205 self.flushed = state;
206 }
207
208 pub fn flush(&mut self) {
209 self.stdout.flush().expect("flush failed");
210 }
211
212 fn cursor_to_end(&mut self) {
213 let (cursor_x, cursor_y) = self.flushed.get_cursor();
214 let height = self.flushed.len() as u16;
215 let down = height.saturating_sub(cursor_y);
216
217 let move_down = down > 0;
218 let move_left = cursor_x > 0;
219 if move_down {
220 ct::queue!(self.stdout, MoveDown(down)).unwrap();
221 }
222 if move_left {
223 ct::queue!(self.stdout, MoveLeft(cursor_x)).unwrap();
224 }
225
226 if move_down || move_left {
227 self.flush();
228 }
229 }
230
231 fn queue_clear(&mut self) {
233 ct::queue!(self.stdout, Clear(ClearType::FromCursorDown)).unwrap();
234 }
235
236 fn cursor_to_start(&mut self) {
237 let (_, y) = self.flushed.cursor;
238
239 ct::queue!(self.stdout, MoveLeft(1000)).unwrap();
241 if y > 0 {
243 ct::queue!(self.stdout, MoveUp(y)).unwrap();
244 }
245 }
246}
247
248#[derive(Clone, Debug)]
250struct State {
251 cursor: (u16, u16),
252 rows: Vec<String>,
253 first_row: u16,
254}
255
256impl PartialEq for State {
257 fn eq(&self, other: &Self) -> bool {
258 self.cursor == other.cursor && self.rows == other.rows
259 }
260}
261
262impl Default for State {
263 fn default() -> Self {
264 State {
265 cursor: (0, 0),
266 rows: vec![],
267 first_row: 0,
268 }
269 }
270}
271
272impl State {
273 pub fn len(&self) -> usize {
274 self.rows.len()
275 }
276
277 pub fn push(&mut self, row: impl Into<String>) {
278 self.rows.push(row.into());
279 }
280
281 pub fn set_cursor(&mut self, cursor: (u16, u16)) {
282 self.cursor = cursor;
283 }
284
285 pub fn get_cursor(&self) -> (u16, u16) {
286 self.cursor
287 }
288
289 pub fn reset(&mut self) -> Self {
290 std::mem::take(self)
291 }
292
293 pub fn iter(&self) -> impl Iterator<Item = &str> {
294 self.rows.iter().map(|s| s.as_str())
295 }
296}