1use std::collections::VecDeque;
8
9pub(crate) const DEFAULT_SCROLLBACK_LIMIT: usize = 2000;
10
11#[derive(Clone, Default)]
16pub struct Cell {
17 pub ch: char,
18 pub attrs: CellAttrs,
19}
20
21#[derive(Clone, Default)]
22pub struct CellAttrs {
23 pub bold: bool,
24 pub dim: bool,
25 pub italic: bool,
26 pub underline: bool,
27 pub fg: Color,
28 pub bg: Color,
29}
30
31#[derive(Clone, Default, PartialEq, Debug)]
32pub enum Color {
33 #[default]
34 Default,
35 Indexed(u8),
36 Rgb(u8, u8, u8),
37}
38
39#[derive(Clone, Copy, Debug, Default)]
40pub struct CursorPos {
41 pub row: u16,
42 pub col: u16,
43}
44
45pub struct ScreenState {
50 cols: u16,
51 rows: u16,
52 grid: Vec<Vec<Cell>>,
53 cursor: CursorPos,
54 saved_cursor: Option<CursorPos>,
55 attrs: CellAttrs,
56 alternate_screen: bool,
57 alternate_grid: Option<Vec<Vec<Cell>>>,
58 alternate_cursor: Option<CursorPos>,
59 title: String,
60 cursor_visible: bool,
61 scroll_top: u16,
62 scroll_bottom: u16,
63 scrollback: VecDeque<String>,
64 scrollback_limit: usize,
65}
66
67fn make_grid(cols: u16, rows: u16) -> Vec<Vec<Cell>> {
68 vec![vec![Cell { ch: ' ', attrs: CellAttrs::default() }; cols as usize]; rows as usize]
69}
70
71fn blank_row(cols: u16) -> Vec<Cell> {
72 vec![Cell { ch: ' ', attrs: CellAttrs::default() }; cols as usize]
73}
74
75impl ScreenState {
76 fn new(cols: u16, rows: u16, scrollback_limit: usize) -> Self {
77 Self {
78 cols,
79 rows,
80 grid: make_grid(cols, rows),
81 cursor: CursorPos::default(),
82 saved_cursor: None,
83 attrs: CellAttrs::default(),
84 alternate_screen: false,
85 alternate_grid: None,
86 alternate_cursor: None,
87 title: String::new(),
88 cursor_visible: true,
89 scroll_top: 0,
90 scroll_bottom: rows.saturating_sub(1),
91 scrollback: VecDeque::new(),
92 scrollback_limit,
93 }
94 }
95
96 fn scroll_up(&mut self) {
98 let top = self.scroll_top as usize;
99 let bottom = self.scroll_bottom as usize;
100 if top < self.grid.len() && bottom < self.grid.len() && top <= bottom {
101 if !self.alternate_screen && self.scroll_top == 0 {
103 let line: String = self.grid[top]
104 .iter()
105 .map(|c| c.ch)
106 .collect::<String>()
107 .trim_end()
108 .to_string();
109 self.scrollback.push_back(line);
110 if self.scrollback.len() > self.scrollback_limit {
111 self.scrollback.pop_front();
112 }
113 }
114 self.grid.remove(top);
115 self.grid.insert(bottom, blank_row(self.cols));
116 }
117 }
118
119 fn scroll_down(&mut self) {
121 let top = self.scroll_top as usize;
122 let bottom = self.scroll_bottom as usize;
123 if top < self.grid.len() && bottom < self.grid.len() && top <= bottom {
124 self.grid.remove(bottom);
125 self.grid.insert(top, blank_row(self.cols));
126 }
127 }
128
129 fn clamp_cursor(&mut self) {
130 if self.cursor.row >= self.rows {
131 self.cursor.row = self.rows.saturating_sub(1);
132 }
133 if self.cursor.col >= self.cols {
134 self.cursor.col = self.cols.saturating_sub(1);
135 }
136 }
137
138 fn handle_sgr(&mut self, params: &vte::Params) {
140 let raw: Vec<Vec<u16>> = params.iter().map(|p| p.to_vec()).collect();
141 let mut i = 0;
142 while i < raw.len() {
143 let p = raw[i].first().copied().unwrap_or(0);
144 match p {
145 0 => self.attrs = CellAttrs::default(),
146 1 => self.attrs.bold = true,
147 2 => self.attrs.dim = true,
148 3 => self.attrs.italic = true,
149 4 => self.attrs.underline = true,
150 22 => {
151 self.attrs.bold = false;
152 self.attrs.dim = false;
153 }
154 23 => self.attrs.italic = false,
155 24 => self.attrs.underline = false,
156 30..=37 => self.attrs.fg = Color::Indexed((p - 30) as u8),
157 38 => {
158 i += 1;
159 if i < raw.len() {
160 let mode = raw[i].first().copied().unwrap_or(0);
161 if mode == 5 {
162 i += 1;
163 if i < raw.len() {
164 let n = raw[i].first().copied().unwrap_or(0);
165 self.attrs.fg = Color::Indexed(n as u8);
166 }
167 } else if mode == 2 && i + 3 < raw.len() {
168 let r = raw.get(i + 1).and_then(|v| v.first()).copied().unwrap_or(0) as u8;
169 let g = raw.get(i + 2).and_then(|v| v.first()).copied().unwrap_or(0) as u8;
170 let b = raw.get(i + 3).and_then(|v| v.first()).copied().unwrap_or(0) as u8;
171 self.attrs.fg = Color::Rgb(r, g, b);
172 i += 3;
173 }
174 }
175 }
176 39 => self.attrs.fg = Color::Default,
177 40..=47 => self.attrs.bg = Color::Indexed((p - 40) as u8),
178 48 => {
179 i += 1;
180 if i < raw.len() {
181 let mode = raw[i].first().copied().unwrap_or(0);
182 if mode == 5 {
183 i += 1;
184 if i < raw.len() {
185 let n = raw[i].first().copied().unwrap_or(0);
186 self.attrs.bg = Color::Indexed(n as u8);
187 }
188 } else if mode == 2 && i + 3 < raw.len() {
189 let r = raw.get(i + 1).and_then(|v| v.first()).copied().unwrap_or(0) as u8;
190 let g = raw.get(i + 2).and_then(|v| v.first()).copied().unwrap_or(0) as u8;
191 let b = raw.get(i + 3).and_then(|v| v.first()).copied().unwrap_or(0) as u8;
192 self.attrs.bg = Color::Rgb(r, g, b);
193 i += 3;
194 }
195 }
196 }
197 49 => self.attrs.bg = Color::Default,
198 90..=97 => self.attrs.fg = Color::Indexed((p - 90 + 8) as u8),
199 100..=107 => self.attrs.bg = Color::Indexed((p - 100 + 8) as u8),
200 _ => {}
201 }
202 i += 1;
203 }
204 }
205
206 fn get_param(params: &vte::Params, idx: usize, default: u16) -> u16 {
207 params
208 .iter()
209 .nth(idx)
210 .and_then(|p| p.first().copied())
211 .map(|v| if v == 0 { default } else { v })
212 .unwrap_or(default)
213 }
214
215 fn get_param_raw(params: &vte::Params, idx: usize) -> u16 {
216 params
217 .iter()
218 .nth(idx)
219 .and_then(|p| p.first().copied())
220 .unwrap_or(0)
221 }
222
223 fn reset(&mut self) {
224 self.grid = make_grid(self.cols, self.rows);
225 self.cursor = CursorPos::default();
226 self.saved_cursor = None;
227 self.attrs = CellAttrs::default();
228 self.alternate_screen = false;
229 self.alternate_grid = None;
230 self.alternate_cursor = None;
231 self.cursor_visible = true;
232 self.scroll_top = 0;
233 self.scroll_bottom = self.rows.saturating_sub(1);
234 self.scrollback.clear();
235 }
236
237 fn capture_scrollback_last(&self, n: usize) -> Vec<String> {
238 let len = self.scrollback.len();
239 let start = len.saturating_sub(n);
240 self.scrollback.range(start..).cloned().collect()
241 }
242
243 fn capture_scrollback_all(&self) -> Vec<String> {
244 self.scrollback.iter().cloned().collect()
245 }
246
247 fn scrollback_len(&self) -> usize {
248 self.scrollback.len()
249 }
250
251 fn enter_alternate_screen(&mut self) {
252 if !self.alternate_screen {
253 let main_grid = std::mem::replace(
254 &mut self.grid,
255 make_grid(self.cols, self.rows),
256 );
257 self.alternate_grid = Some(main_grid);
258 self.alternate_cursor = Some(self.cursor);
259 self.cursor = CursorPos::default();
260 self.alternate_screen = true;
261 }
262 }
263
264 fn exit_alternate_screen(&mut self) {
265 if self.alternate_screen {
266 if let Some(main_grid) = self.alternate_grid.take() {
267 self.grid = main_grid;
268 }
269 if let Some(saved) = self.alternate_cursor.take() {
270 self.cursor = saved;
271 }
272 self.alternate_screen = false;
273 }
274 }
275}
276
277impl vte::Perform for ScreenState {
278 fn print(&mut self, c: char) {
279 if self.cursor.col >= self.cols {
281 self.cursor.col = 0;
282 self.cursor.row += 1;
283 if self.cursor.row > self.scroll_bottom {
284 self.cursor.row = self.scroll_bottom;
285 self.scroll_up();
286 }
287 }
288
289 let row = self.cursor.row as usize;
290 let col = self.cursor.col as usize;
291 if row < self.grid.len() && col < self.grid[row].len() {
292 self.grid[row][col] = Cell {
293 ch: c,
294 attrs: self.attrs.clone(),
295 };
296 }
297 self.cursor.col += 1;
298 }
299
300 fn execute(&mut self, byte: u8) {
301 match byte {
302 0x08 => {
303 self.cursor.col = self.cursor.col.saturating_sub(1);
305 }
306 0x09 => {
307 let next = ((self.cursor.col / 8) + 1) * 8;
309 self.cursor.col = next.min(self.cols.saturating_sub(1));
310 }
311 0x0A => {
312 if self.cursor.row >= self.scroll_bottom {
314 self.scroll_up();
315 } else {
316 self.cursor.row += 1;
317 }
318 }
319 0x0D => {
320 self.cursor.col = 0;
322 }
323 0x07 => {
324 }
326 _ => {}
327 }
328 }
329
330 fn csi_dispatch(&mut self, params: &vte::Params, intermediates: &[u8], _ignore: bool, action: char) {
331 let is_private = intermediates.first() == Some(&b'?');
332
333 match action {
334 'A' => {
336 let n = Self::get_param(params, 0, 1);
337 self.cursor.row = self.cursor.row.saturating_sub(n);
338 }
339 'B' => {
340 let n = Self::get_param(params, 0, 1);
341 self.cursor.row = (self.cursor.row + n).min(self.rows.saturating_sub(1));
342 }
343 'C' => {
344 let n = Self::get_param(params, 0, 1);
345 self.cursor.col = (self.cursor.col + n).min(self.cols.saturating_sub(1));
346 }
347 'D' if !is_private => {
348 let n = Self::get_param(params, 0, 1);
349 self.cursor.col = self.cursor.col.saturating_sub(n);
350 }
351 'G' => {
352 let n = Self::get_param(params, 0, 1);
353 self.cursor.col = (n - 1).min(self.cols.saturating_sub(1));
354 }
355 'H' | 'f' => {
356 let row = Self::get_param(params, 0, 1);
357 let col = Self::get_param(params, 1, 1);
358 self.cursor.row = (row - 1).min(self.rows.saturating_sub(1));
359 self.cursor.col = (col - 1).min(self.cols.saturating_sub(1));
360 }
361 'd' => {
362 let n = Self::get_param(params, 0, 1);
363 self.cursor.row = (n - 1).min(self.rows.saturating_sub(1));
364 }
365
366 'J' => {
368 let mode = Self::get_param_raw(params, 0);
369 let cols = self.cols;
370 match mode {
371 0 => {
372 let row = self.cursor.row as usize;
373 let col = self.cursor.col as usize;
374 if row < self.grid.len() {
375 for c in col..self.grid[row].len() {
376 self.grid[row][c] = Cell { ch: ' ', attrs: CellAttrs::default() };
377 }
378 for r in (row + 1)..self.grid.len() {
379 self.grid[r] = blank_row(cols);
380 }
381 }
382 }
383 1 => {
384 let row = self.cursor.row as usize;
385 let col = self.cursor.col as usize;
386 for r in 0..row {
387 self.grid[r] = blank_row(cols);
388 }
389 if row < self.grid.len() {
390 for c in 0..=col.min(self.grid[row].len().saturating_sub(1)) {
391 self.grid[row][c] = Cell { ch: ' ', attrs: CellAttrs::default() };
392 }
393 }
394 }
395 2 | 3 => {
396 for row in self.grid.iter_mut() {
397 *row = blank_row(cols);
398 }
399 }
400 _ => {}
401 }
402 }
403 'K' => {
404 let mode = Self::get_param_raw(params, 0);
405 let row = self.cursor.row as usize;
406 let col = self.cursor.col as usize;
407 let cols = self.cols;
408 if row < self.grid.len() {
409 match mode {
410 0 => {
411 for c in col..self.grid[row].len() {
412 self.grid[row][c] = Cell { ch: ' ', attrs: CellAttrs::default() };
413 }
414 }
415 1 => {
416 for c in 0..=col.min(self.grid[row].len().saturating_sub(1)) {
417 self.grid[row][c] = Cell { ch: ' ', attrs: CellAttrs::default() };
418 }
419 }
420 2 => {
421 self.grid[row] = blank_row(cols);
422 }
423 _ => {}
424 }
425 }
426 }
427 'X' => {
428 let n = Self::get_param(params, 0, 1) as usize;
429 let row = self.cursor.row as usize;
430 let col = self.cursor.col as usize;
431 if row < self.grid.len() {
432 for c in col..(col + n).min(self.grid[row].len()) {
433 self.grid[row][c] = Cell { ch: ' ', attrs: CellAttrs::default() };
434 }
435 }
436 }
437 'P' => {
438 let n = Self::get_param(params, 0, 1) as usize;
439 let row = self.cursor.row as usize;
440 let col = self.cursor.col as usize;
441 let cols = self.cols as usize;
442 if row < self.grid.len() {
443 let end = (col + n).min(self.grid[row].len());
444 for _ in col..end {
445 if col < self.grid[row].len() {
446 self.grid[row].remove(col);
447 }
448 }
449 while self.grid[row].len() < cols {
450 self.grid[row].push(Cell { ch: ' ', attrs: CellAttrs::default() });
451 }
452 }
453 }
454
455 'L' => {
457 let n = Self::get_param(params, 0, 1);
458 let row = self.cursor.row as usize;
459 let bottom = self.scroll_bottom as usize;
460 let cols = self.cols;
461 for _ in 0..n {
462 if bottom < self.grid.len() {
463 self.grid.remove(bottom);
464 }
465 if row <= self.grid.len() {
466 self.grid.insert(row, blank_row(cols));
467 }
468 }
469 }
470 'M' => {
471 let n = Self::get_param(params, 0, 1);
472 let row = self.cursor.row as usize;
473 let bottom = self.scroll_bottom as usize;
474 let cols = self.cols;
475 for _ in 0..n {
476 if row < self.grid.len() {
477 self.grid.remove(row);
478 }
479 if bottom <= self.grid.len() {
480 self.grid.insert(bottom, blank_row(cols));
481 }
482 }
483 }
484
485 'S' if !is_private => {
487 let n = Self::get_param(params, 0, 1);
488 for _ in 0..n {
489 self.scroll_up();
490 }
491 }
492 'T' if !is_private => {
493 let n = Self::get_param(params, 0, 1);
494 for _ in 0..n {
495 self.scroll_down();
496 }
497 }
498
499 'm' => {
501 self.handle_sgr(params);
502 }
503
504 'r' if !is_private => {
506 let top = Self::get_param(params, 0, 1);
507 let bottom = Self::get_param(params, 1, self.rows);
508 self.scroll_top = (top - 1).min(self.rows.saturating_sub(1));
509 self.scroll_bottom = (bottom - 1).min(self.rows.saturating_sub(1));
510 self.cursor = CursorPos::default();
511 }
512
513 'h' if is_private => {
515 let mode = Self::get_param_raw(params, 0);
516 match mode {
517 1049 => self.enter_alternate_screen(),
518 25 => self.cursor_visible = true,
519 _ => {}
520 }
521 }
522 'l' if is_private => {
523 let mode = Self::get_param_raw(params, 0);
524 match mode {
525 1049 => self.exit_alternate_screen(),
526 25 => self.cursor_visible = false,
527 _ => {}
528 }
529 }
530
531 _ => {}
532 }
533 }
534
535 fn esc_dispatch(&mut self, intermediates: &[u8], _ignore: bool, byte: u8) {
536 if !intermediates.is_empty() {
537 return;
538 }
539 match byte {
540 b'7' => {
541 self.saved_cursor = Some(self.cursor);
542 }
543 b'8' => {
544 if let Some(saved) = self.saved_cursor {
545 self.cursor = saved;
546 }
547 }
548 b'D' => {
549 if self.cursor.row >= self.scroll_bottom {
550 self.scroll_up();
551 } else {
552 self.cursor.row += 1;
553 }
554 }
555 b'M' => {
556 if self.cursor.row <= self.scroll_top {
557 self.scroll_down();
558 } else {
559 self.cursor.row -= 1;
560 }
561 }
562 b'c' => {
563 self.reset();
564 }
565 _ => {}
566 }
567 }
568
569 fn osc_dispatch(&mut self, params: &[&[u8]], _bell_terminated: bool) {
570 if params.is_empty() {
571 return;
572 }
573 let code = params[0];
574 if (code == b"0" || code == b"2") && params.len() > 1 {
575 self.title = String::from_utf8_lossy(params[1]).to_string();
576 }
577 }
578
579 fn hook(&mut self, _params: &vte::Params, _intermediates: &[u8], _ignore: bool, _action: char) {}
580 fn put(&mut self, _byte: u8) {}
581 fn unhook(&mut self) {}
582}
583
584pub struct Screen {
589 state: ScreenState,
590 parser: vte::Parser,
591}
592
593impl Screen {
594 pub fn new(cols: u16, rows: u16, scrollback_limit: usize) -> Self {
595 Self {
596 state: ScreenState::new(cols, rows, scrollback_limit),
597 parser: vte::Parser::new(),
598 }
599 }
600
601 pub fn feed(&mut self, data: &[u8]) {
603 self.parser.advance(&mut self.state, data);
604 }
605
606 pub fn capture_text(&self) -> Vec<String> {
608 self.state
609 .grid
610 .iter()
611 .map(|row| {
612 let line: String = row.iter().map(|c| c.ch).collect();
613 line.trim_end().to_string()
614 })
615 .collect()
616 }
617
618 pub fn cursor(&self) -> CursorPos {
620 self.state.cursor
621 }
622
623 pub fn size(&self) -> (u16, u16) {
625 (self.state.cols, self.state.rows)
626 }
627
628 pub fn title(&self) -> &str {
630 &self.state.title
631 }
632
633 pub fn capture_scrollback_last(&self, n: usize) -> Vec<String> {
634 self.state.capture_scrollback_last(n)
635 }
636
637 pub fn capture_scrollback_all(&self) -> Vec<String> {
638 self.state.capture_scrollback_all()
639 }
640
641 pub fn scrollback_len(&self) -> usize {
642 self.state.scrollback_len()
643 }
644
645 pub fn set_scrollback_limit(&mut self, limit: usize) {
646 self.state.scrollback_limit = limit;
647 while self.state.scrollback.len() > limit {
648 self.state.scrollback.pop_front();
649 }
650 }
651
652 pub fn scrollback_limit(&self) -> usize {
653 self.state.scrollback_limit
654 }
655
656 pub fn cursor_visible(&self) -> bool {
657 self.state.cursor_visible
658 }
659
660 pub fn grid(&self) -> &[Vec<Cell>] {
661 &self.state.grid
662 }
663
664 pub fn resize(&mut self, cols: u16, rows: u16) {
666 let old_cols = self.state.cols as usize;
667 let new_cols = cols as usize;
668 let new_rows = rows as usize;
669
670 for row in self.state.grid.iter_mut() {
671 if new_cols > old_cols {
672 row.resize(new_cols, Cell { ch: ' ', attrs: CellAttrs::default() });
673 } else {
674 row.truncate(new_cols);
675 }
676 }
677
678 let current_rows = self.state.grid.len();
679 if new_rows > current_rows {
680 for _ in 0..(new_rows - current_rows) {
681 self.state.grid.push(vec![Cell { ch: ' ', attrs: CellAttrs::default() }; new_cols]);
682 }
683 } else {
684 self.state.grid.truncate(new_rows);
685 }
686
687 self.state.cols = cols;
688 self.state.rows = rows;
689 self.state.scroll_bottom = rows.saturating_sub(1);
690 if self.state.scroll_top >= rows {
691 self.state.scroll_top = 0;
692 }
693 self.state.clamp_cursor();
694 }
695}
696
697#[cfg(test)]
702mod tests {
703 use super::*;
704
705 #[test]
706 fn basic_text() {
707 let mut screen = Screen::new(80, 24, DEFAULT_SCROLLBACK_LIMIT);
708 screen.feed(b"hello\r\nworld");
709 let lines = screen.capture_text();
710 assert_eq!(lines[0], "hello");
711 assert_eq!(lines[1], "world");
712 }
713
714 #[test]
715 fn cursor_movement() {
716 let mut screen = Screen::new(80, 24, DEFAULT_SCROLLBACK_LIMIT);
717 screen.feed(b"\x1b[4;6HX");
719 let lines = screen.capture_text();
720 assert_eq!(lines[3].chars().nth(5), Some('X'));
721 assert_eq!(screen.cursor().row, 3);
722 assert_eq!(screen.cursor().col, 6);
723 }
724
725 #[test]
726 fn erase_display() {
727 let mut screen = Screen::new(80, 24, DEFAULT_SCROLLBACK_LIMIT);
728 screen.feed(b"hello");
729 screen.feed(b"\x1b[2J");
730 let lines = screen.capture_text();
731 assert!(lines.iter().all(|l| l.is_empty()));
732 }
733
734 #[test]
735 fn erase_line() {
736 let mut screen = Screen::new(80, 24, DEFAULT_SCROLLBACK_LIMIT);
737 screen.feed(b"hello world");
738 screen.feed(b"\r\x1b[6G\x1b[K");
740 let lines = screen.capture_text();
741 assert_eq!(lines[0], "hello");
742 }
743
744 #[test]
745 fn sgr_attributes() {
746 let mut screen = Screen::new(80, 24, DEFAULT_SCROLLBACK_LIMIT);
747 screen.feed(b"\x1b[1mA\x1b[0m");
748 assert!(screen.state.grid[0][0].attrs.bold);
749 assert_eq!(screen.state.grid[0][0].ch, 'A');
750 assert!(!screen.state.attrs.bold);
751 }
752
753 #[test]
754 fn scrolling() {
755 let mut screen = Screen::new(80, 4, DEFAULT_SCROLLBACK_LIMIT);
756 screen.feed(b"line1\r\nline2\r\nline3\r\nline4");
757 screen.feed(b"\r\nline5");
758 let lines = screen.capture_text();
759 assert_eq!(lines[0], "line2");
760 assert_eq!(lines[1], "line3");
761 assert_eq!(lines[2], "line4");
762 assert_eq!(lines[3], "line5");
763 }
764
765 #[test]
766 fn alternate_screen() {
767 let mut screen = Screen::new(80, 24, DEFAULT_SCROLLBACK_LIMIT);
768 screen.feed(b"original");
769 screen.feed(b"\x1b[?1049h");
770 assert!(screen.state.alternate_screen);
771 screen.feed(b"alternate");
772 let lines = screen.capture_text();
773 assert_eq!(lines[0], "alternate");
774 screen.feed(b"\x1b[?1049l");
775 assert!(!screen.state.alternate_screen);
776 let lines = screen.capture_text();
777 assert_eq!(lines[0], "original");
778 }
779
780 #[test]
781 fn tab_stops() {
782 let mut screen = Screen::new(80, 24, DEFAULT_SCROLLBACK_LIMIT);
783 screen.feed(b"A\tB");
784 let lines = screen.capture_text();
785 assert_eq!(lines[0].chars().nth(0), Some('A'));
786 assert_eq!(lines[0].chars().nth(8), Some('B'));
787 }
788
789 #[test]
790 fn line_wrapping() {
791 let mut screen = Screen::new(10, 4, DEFAULT_SCROLLBACK_LIMIT);
792 screen.feed(b"0123456789X");
793 let lines = screen.capture_text();
794 assert_eq!(lines[0], "0123456789");
795 assert_eq!(lines[1], "X");
796 }
797
798 #[test]
799 fn window_title() {
800 let mut screen = Screen::new(80, 24, DEFAULT_SCROLLBACK_LIMIT);
801 screen.feed(b"\x1b]0;my title\x07");
802 assert_eq!(screen.title(), "my title");
803 screen.feed(b"\x1b]2;new title\x1b\\");
804 assert_eq!(screen.title(), "new title");
805 }
806
807 #[test]
808 fn resize() {
809 let mut screen = Screen::new(80, 24, DEFAULT_SCROLLBACK_LIMIT);
810 screen.feed(b"hello");
811 screen.resize(40, 12);
812 assert_eq!(screen.size(), (40, 12));
813 let lines = screen.capture_text();
814 assert_eq!(lines.len(), 12);
815 assert_eq!(lines[0], "hello");
816 }
817
818 #[test]
819 fn cursor_save_restore() {
820 let mut screen = Screen::new(80, 24, DEFAULT_SCROLLBACK_LIMIT);
821 screen.feed(b"\x1b[5;10H");
822 screen.feed(b"\x1b7");
823 screen.feed(b"\x1b[1;1H");
824 assert_eq!(screen.cursor().row, 0);
825 screen.feed(b"\x1b8");
826 assert_eq!(screen.cursor().row, 4);
827 assert_eq!(screen.cursor().col, 9);
828 }
829
830 #[test]
831 fn scroll_region() {
832 let mut screen = Screen::new(80, 5, DEFAULT_SCROLLBACK_LIMIT);
833 screen.feed(b"line0\r\nline1\r\nline2\r\nline3\r\nline4");
834 screen.feed(b"\x1b[2;4r");
835 assert_eq!(screen.cursor().row, 0);
836 assert_eq!(screen.cursor().col, 0);
837 screen.feed(b"\x1b[4;1H");
838 screen.feed(b"\n");
839 let lines = screen.capture_text();
840 assert_eq!(lines[0], "line0");
841 assert_eq!(lines[1], "line2");
842 assert_eq!(lines[2], "line3");
843 assert_eq!(lines[3], "");
844 assert_eq!(lines[4], "line4");
845 }
846
847 #[test]
848 fn insert_lines() {
849 let mut screen = Screen::new(80, 5, DEFAULT_SCROLLBACK_LIMIT);
850 screen.feed(b"aaa\r\nbbb\r\nccc\r\nddd\r\neee");
851 screen.feed(b"\x1b[2;1H\x1b[L");
852 let lines = screen.capture_text();
853 assert_eq!(lines[0], "aaa");
854 assert_eq!(lines[1], "");
855 assert_eq!(lines[2], "bbb");
856 assert_eq!(lines[3], "ccc");
857 assert_eq!(lines[4], "ddd");
858 }
859
860 #[test]
861 fn delete_lines() {
862 let mut screen = Screen::new(80, 5, DEFAULT_SCROLLBACK_LIMIT);
863 screen.feed(b"aaa\r\nbbb\r\nccc\r\nddd\r\neee");
864 screen.feed(b"\x1b[2;1H\x1b[M");
865 let lines = screen.capture_text();
866 assert_eq!(lines[0], "aaa");
867 assert_eq!(lines[1], "ccc");
868 assert_eq!(lines[2], "ddd");
869 assert_eq!(lines[3], "eee");
870 assert_eq!(lines[4], "");
871 }
872
873 #[test]
874 fn sgr_256_color() {
875 let mut screen = Screen::new(80, 24, DEFAULT_SCROLLBACK_LIMIT);
876 screen.feed(b"\x1b[38;5;196mR\x1b[0m");
877 assert_eq!(screen.state.grid[0][0].attrs.fg, Color::Indexed(196));
878 }
879
880 #[test]
881 fn sgr_rgb_color() {
882 let mut screen = Screen::new(80, 24, DEFAULT_SCROLLBACK_LIMIT);
883 screen.feed(b"\x1b[38;2;255;128;0mO\x1b[0m");
884 assert_eq!(screen.state.grid[0][0].attrs.fg, Color::Rgb(255, 128, 0));
885 }
886
887 #[test]
888 fn erase_characters() {
889 let mut screen = Screen::new(80, 24, DEFAULT_SCROLLBACK_LIMIT);
890 screen.feed(b"ABCDEF");
891 screen.feed(b"\x1b[1;3H\x1b[2X");
892 let lines = screen.capture_text();
893 assert_eq!(&lines[0][..6], "AB EF");
894 }
895
896 #[test]
897 fn delete_characters() {
898 let mut screen = Screen::new(80, 24, DEFAULT_SCROLLBACK_LIMIT);
899 screen.feed(b"ABCDEF");
900 screen.feed(b"\x1b[1;3H\x1b[2P");
901 let lines = screen.capture_text();
902 assert_eq!(&lines[0][..4], "ABEF");
903 }
904
905 #[test]
906 fn full_reset() {
907 let mut screen = Screen::new(80, 24, DEFAULT_SCROLLBACK_LIMIT);
908 screen.feed(b"hello");
909 screen.feed(b"\x1bc");
910 let lines = screen.capture_text();
911 assert!(lines.iter().all(|l| l.is_empty()));
912 assert_eq!(screen.cursor().row, 0);
913 assert_eq!(screen.cursor().col, 0);
914 }
915
916 #[test]
921 fn scrollback_fills_on_scroll() {
922 let mut screen = Screen::new(80, 4, DEFAULT_SCROLLBACK_LIMIT);
923 screen.feed(b"line1\r\nline2\r\nline3\r\nline4");
924 screen.feed(b"\r\nline5");
925 assert_eq!(screen.scrollback_len(), 1);
926 assert_eq!(screen.capture_scrollback_all(), vec!["line1"]);
927 }
928
929 #[test]
930 fn scrollback_respects_limit() {
931 let mut screen = Screen::new(80, 4, 3);
932 screen.feed(b"line1\r\nline2\r\nline3\r\nline4");
934 screen.feed(b"\r\nline5\r\nline6\r\nline7\r\nline8\r\nline9");
935 assert_eq!(screen.scrollback_len(), 3);
936 assert_eq!(screen.capture_scrollback_all(), vec!["line3", "line4", "line5"]);
937 }
938
939 #[test]
940 fn capture_scrollback_last_returns_subset() {
941 let mut screen = Screen::new(80, 4, DEFAULT_SCROLLBACK_LIMIT);
942 screen.feed(b"line1\r\nline2\r\nline3\r\nline4");
943 screen.feed(b"\r\nline5\r\nline6\r\nline7");
944 assert_eq!(screen.scrollback_len(), 3);
946 assert_eq!(screen.capture_scrollback_last(2), vec!["line2", "line3"]);
947 assert_eq!(screen.capture_scrollback_last(1), vec!["line3"]);
948 assert_eq!(screen.capture_scrollback_last(100), vec!["line1", "line2", "line3"]);
950 }
951
952 #[test]
953 fn capture_scrollback_all_returns_everything() {
954 let mut screen = Screen::new(80, 4, DEFAULT_SCROLLBACK_LIMIT);
955 screen.feed(b"aaa\r\nbbb\r\nccc\r\nddd");
956 screen.feed(b"\r\neee\r\nfff");
957 assert_eq!(screen.capture_scrollback_all(), vec!["aaa", "bbb"]);
958 }
959
960 #[test]
961 fn alternate_screen_does_not_populate_scrollback() {
962 let mut screen = Screen::new(80, 4, DEFAULT_SCROLLBACK_LIMIT);
963 screen.feed(b"line1\r\nline2\r\nline3\r\nline4");
964 screen.feed(b"\x1b[?1049h");
966 screen.feed(b"alt1\r\nalt2\r\nalt3\r\nalt4\r\nalt5");
968 assert_eq!(screen.scrollback_len(), 0);
969 screen.feed(b"\x1b[?1049l");
971 assert_eq!(screen.scrollback_len(), 0);
972 }
973
974 #[test]
975 fn scroll_region_does_not_populate_scrollback() {
976 let mut screen = Screen::new(80, 5, DEFAULT_SCROLLBACK_LIMIT);
977 screen.feed(b"line0\r\nline1\r\nline2\r\nline3\r\nline4");
978 screen.feed(b"\x1b[2;4r");
980 screen.feed(b"\x1b[4;1H");
982 screen.feed(b"\n");
983 assert_eq!(screen.scrollback_len(), 0);
985 }
986
987 #[test]
988 fn full_reset_clears_scrollback() {
989 let mut screen = Screen::new(80, 4, DEFAULT_SCROLLBACK_LIMIT);
990 screen.feed(b"line1\r\nline2\r\nline3\r\nline4");
991 screen.feed(b"\r\nline5");
992 assert_eq!(screen.scrollback_len(), 1);
993 screen.feed(b"\x1bc");
995 assert_eq!(screen.scrollback_len(), 0);
996 }
997
998 #[test]
1003 fn set_scrollback_limit_trims_excess() {
1004 let mut screen = Screen::new(80, 4, DEFAULT_SCROLLBACK_LIMIT);
1005 screen.feed(b"line1\r\nline2\r\nline3\r\nline4");
1007 screen.feed(b"\r\nline5\r\nline6\r\nline7\r\nline8\r\nline9");
1008 assert_eq!(screen.scrollback_len(), 5);
1009
1010 screen.set_scrollback_limit(2);
1012 assert_eq!(screen.scrollback_len(), 2);
1013 assert_eq!(screen.capture_scrollback_all(), vec!["line4", "line5"]);
1014 assert_eq!(screen.scrollback_limit(), 2);
1015 }
1016
1017 #[test]
1018 fn set_scrollback_limit_raise_keeps_all() {
1019 let mut screen = Screen::new(80, 4, 3);
1020 screen.feed(b"line1\r\nline2\r\nline3\r\nline4");
1021 screen.feed(b"\r\nline5\r\nline6\r\nline7\r\nline8\r\nline9");
1022 assert_eq!(screen.scrollback_len(), 3);
1023
1024 screen.set_scrollback_limit(10);
1026 assert_eq!(screen.scrollback_len(), 3);
1027 assert_eq!(screen.scrollback_limit(), 10);
1028 }
1029
1030 #[test]
1031 fn screen_new_respects_scrollback_limit() {
1032 let screen = Screen::new(80, 24, 500);
1033 assert_eq!(screen.scrollback_limit(), 500);
1034 }
1035}