From 0d0258fb44ab483a4aa7650ce246b9418506aba5 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Sat, 9 Nov 2019 14:34:47 -0500 Subject: implement scrolling back --- src/grid.rs | 79 ++++++++++++++++++++++++++++++++++++++--------------- src/lib.rs | 2 +- src/parser.rs | 8 ++++++ src/screen.rs | 40 ++++++++++++++++----------- tests/scroll.rs | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 175 insertions(+), 38 deletions(-) diff --git a/src/grid.rs b/src/grid.rs index 5a53c6e..db96326 100644 --- a/src/grid.rs +++ b/src/grid.rs @@ -12,8 +12,9 @@ pub struct Grid { scroll_bottom: u16, origin_mode: bool, saved_origin_mode: bool, - scrollback: std::collections::VecDeque<(usize, crate::row::Row)>, + scrollback: std::collections::VecDeque, scrollback_len: usize, + scrollback_offset: usize, } impl Grid { @@ -29,9 +30,18 @@ impl Grid { saved_origin_mode: false, scrollback: std::collections::VecDeque::new(), scrollback_len, + scrollback_offset: 0, } } + pub fn scrollback(&self) -> usize { + self.scrollback_offset + } + + pub fn set_scrollback(&mut self, rows: usize) { + self.scrollback_offset = rows.min(self.scrollback.len()); + } + fn new_row(&self) -> crate::row::Row { crate::row::Row::new(self.size.cols) } @@ -39,7 +49,7 @@ impl Grid { pub fn clear(&mut self) { self.pos = Pos::default(); self.saved_pos = Pos::default(); - for row in self.rows_mut() { + for row in self.drawing_rows_mut() { row.clear(crate::attrs::Color::Default); } self.scroll_top = 0; @@ -97,37 +107,58 @@ impl Grid { self.origin_mode = self.saved_origin_mode; } - pub fn rows(&self) -> impl Iterator { + pub fn visible_rows(&self) -> impl Iterator { + let scrollback_len = self.scrollback.len(); + let rows_len = self.rows.len(); + self.scrollback + .iter() + .skip(scrollback_len - self.scrollback_offset) + .chain(self.rows.iter().take(rows_len - self.scrollback_offset)) + } + + pub fn drawing_rows(&self) -> impl Iterator { self.rows.iter() } - pub fn rows_mut(&mut self) -> impl Iterator { + pub fn drawing_rows_mut( + &mut self, + ) -> impl Iterator { self.rows.iter_mut() } - pub fn row(&self, pos: Pos) -> Option<&crate::row::Row> { - self.rows.get(pos.row as usize) + pub fn visible_row(&self, pos: Pos) -> Option<&crate::row::Row> { + self.visible_rows().nth(pos.row as usize) + } + + pub fn drawing_row(&self, pos: Pos) -> Option<&crate::row::Row> { + self.drawing_rows().nth(pos.row as usize) } - pub fn row_mut(&mut self, pos: Pos) -> Option<&mut crate::row::Row> { - self.rows.get_mut(pos.row as usize) + pub fn drawing_row_mut( + &mut self, + pos: Pos, + ) -> Option<&mut crate::row::Row> { + self.drawing_rows_mut().nth(pos.row as usize) } pub fn current_row_mut(&mut self) -> &mut crate::row::Row { - self.row_mut(self.pos) + self.drawing_row_mut(self.pos) .expect("cursor not pointing to a cell") } - pub fn cell(&self, pos: Pos) -> Option<&crate::cell::Cell> { - self.row(pos).and_then(|r| r.get(pos.col)) + pub fn visible_cell(&self, pos: Pos) -> Option<&crate::cell::Cell> { + self.visible_row(pos).and_then(|r| r.get(pos.col)) } - pub fn cell_mut(&mut self, pos: Pos) -> Option<&mut crate::cell::Cell> { - self.row_mut(pos).and_then(|r| r.get_mut(pos.col)) + pub fn drawing_cell_mut( + &mut self, + pos: Pos, + ) -> Option<&mut crate::cell::Cell> { + self.drawing_row_mut(pos).and_then(|r| r.get_mut(pos.col)) } pub fn current_cell_mut(&mut self) -> &mut crate::cell::Cell { - self.cell_mut(self.pos) + self.drawing_cell_mut(self.pos) .expect("cursor not pointing to a cell") } @@ -136,7 +167,7 @@ impl Grid { } pub fn write_contents(&self, contents: &mut String) { - for row in self.rows() { + for row in self.visible_rows() { row.write_contents(contents, 0, self.size.cols); if !row.wrapped() { writeln!(contents).unwrap(); @@ -159,7 +190,7 @@ impl Grid { let mut prev_attrs = crate::attrs::Attrs::default(); let mut final_col = 0; - for row in self.rows() { + for row in self.visible_rows() { let (new_attrs, new_col) = row.write_contents_formatted( contents, 0, @@ -192,7 +223,8 @@ impl Grid { let mut prev_attrs = crate::attrs::Attrs::default(); let mut final_row = prev.pos.row; let mut final_col = prev.pos.col; - for (idx, (row, prev_row)) in self.rows().zip(prev.rows()).enumerate() + for (idx, (row, prev_row)) in + self.visible_rows().zip(prev.visible_rows()).enumerate() { let idx = idx.try_into().unwrap(); let (new_attrs, new_col) = row.write_contents_diff( @@ -224,14 +256,14 @@ impl Grid { } pub fn erase_all(&mut self, bgcolor: crate::attrs::Color) { - for row in self.rows_mut() { + for row in self.drawing_rows_mut() { row.clear(bgcolor); } } pub fn erase_all_forward(&mut self, bgcolor: crate::attrs::Color) { let pos = self.pos; - for row in self.rows_mut().skip(pos.row as usize + 1) { + for row in self.drawing_rows_mut().skip(pos.row as usize + 1) { row.clear(bgcolor); } @@ -240,7 +272,7 @@ impl Grid { pub fn erase_all_backward(&mut self, bgcolor: crate::attrs::Color) { let pos = self.pos; - for row in self.rows_mut().take(pos.row as usize) { + for row in self.drawing_rows_mut().take(pos.row as usize) { row.clear(bgcolor); } @@ -319,11 +351,14 @@ impl Grid { .insert(self.scroll_bottom as usize + 1, self.new_row()); let removed = self.rows.remove(self.scroll_top as usize); if self.scrollback_len > 0 && !self.scroll_region_active() { - let idx = self.scrollback.back().map_or(0, |r| r.0 + 1); - self.scrollback.push_back((idx, removed)); + self.scrollback.push_back(removed); while self.scrollback.len() > self.scrollback_len { self.scrollback.pop_front(); } + if self.scrollback_offset > 0 { + self.scrollback_offset = + self.scrollback.len().min(self.scrollback_offset + 1); + } } } } diff --git a/src/lib.rs b/src/lib.rs index 9424fb9..d14b78f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,7 @@ //! # Synopsis //! //! ``` -//! let mut parser = vt100::Parser::new(24, 80); +//! let mut parser = vt100::Parser::new(24, 80, 0); //! parser.process(b"this text is \x1b[31mRED\x1b[m"); //! assert_eq!( //! parser.screen().cell(0, 13).unwrap().fgcolor(), diff --git a/src/parser.rs b/src/parser.rs index 0cabb1a..a18f7e8 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -36,4 +36,12 @@ impl Parser { pub fn screen_mut(&mut self) -> &mut crate::screen::Screen { &mut self.screen } + + pub fn scroll_pos(&self) -> usize { + self.screen.scrollback() + } + + pub fn scroll_to(&mut self, idx: usize) { + self.screen.set_scrollback(idx); + } } diff --git a/src/screen.rs b/src/screen.rs index f3fdf6a..5150e3a 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -112,6 +112,14 @@ impl Screen { } } + pub fn scrollback(&self) -> usize { + self.grid().scrollback() + } + + pub fn set_scrollback(&mut self, rows: usize) { + self.grid_mut().set_scrollback(rows); + } + /// Resizes the terminal. pub fn set_size(&mut self, rows: u16, cols: u16) { self.grid.set_size(crate::grid::Size { rows, cols }); @@ -153,7 +161,7 @@ impl Screen { start: u16, width: u16, ) -> impl Iterator + '_ { - self.grid().rows().map(move |row| { + self.grid().visible_rows().map(move |row| { let mut contents = String::new(); row.write_contents(&mut contents, start, width); contents @@ -199,7 +207,7 @@ impl Screen { start: u16, width: u16, ) -> impl Iterator> + '_ { - self.grid().rows().map(move |row| { + self.grid().visible_rows().map(move |row| { let mut contents = vec![]; row.write_contents_formatted( &mut contents, @@ -252,8 +260,10 @@ impl Screen { start: u16, width: u16, ) -> impl Iterator> + 'a { - self.grid().rows().zip(prev.grid().rows()).map( - move |(row, prev_row)| { + self.grid() + .visible_rows() + .zip(prev.grid().visible_rows()) + .map(move |(row, prev_row)| { let mut contents = vec![]; row.write_contents_diff( &mut contents, @@ -264,14 +274,13 @@ impl Screen { crate::attrs::Attrs::default(), ); contents - }, - ) + }) } /// Returns the `Cell` object at the given location in the terminal, if it /// exists. pub fn cell(&self, row: u16, col: u16) -> Option<&crate::cell::Cell> { - self.grid().cell(crate::grid::Pos { row, col }) + self.grid().visible_cell(crate::grid::Pos { row, col }) } /// Returns the current cursor position of the terminal. @@ -350,15 +359,15 @@ impl Screen { } } - fn row(&self, pos: crate::grid::Pos) -> Option<&crate::row::Row> { - self.grid().row(pos) + fn drawing_row(&self, pos: crate::grid::Pos) -> Option<&crate::row::Row> { + self.grid().drawing_row(pos) } - fn cell_mut( + fn drawing_cell_mut( &mut self, pos: crate::grid::Pos, ) -> Option<&mut crate::cell::Cell> { - self.grid_mut().cell_mut(pos) + self.grid_mut().drawing_cell_mut(pos) } fn current_cell_mut(&mut self) -> &mut crate::cell::Cell { @@ -366,6 +375,7 @@ impl Screen { } fn enter_alternate_grid(&mut self) { + self.grid_mut().set_scrollback(0); self.set_mode(Mode::AlternateScreen); } @@ -436,7 +446,7 @@ impl Screen { if pos.col > 0 { let bgcolor = self.attrs.bgcolor; let prev_cell = self - .cell_mut(crate::grid::Pos { + .drawing_cell_mut(crate::grid::Pos { row: pos.row, col: pos.col - 1, }) @@ -455,7 +465,7 @@ impl Screen { if width == 0 { if pos.col > 0 { let prev_cell = self - .cell_mut(crate::grid::Pos { + .drawing_cell_mut(crate::grid::Pos { row: pos.row, col: pos.col - 1, }) @@ -463,14 +473,14 @@ impl Screen { prev_cell.append(c); } else if pos.row > 0 { let prev_row = self - .row(crate::grid::Pos { + .drawing_row(crate::grid::Pos { row: pos.row - 1, col: 0, }) .unwrap(); if prev_row.wrapped() { let prev_cell = self - .cell_mut(crate::grid::Pos { + .drawing_cell_mut(crate::grid::Pos { row: pos.row - 1, col: self.grid().size().cols - 1, }) diff --git a/tests/scroll.rs b/tests/scroll.rs index 2881da6..4bbe1e9 100644 --- a/tests/scroll.rs +++ b/tests/scroll.rs @@ -69,3 +69,87 @@ fn origin_mode() { parser.process(b"\x1b[?47l\x1b[H"); assert_eq!(parser.screen().cursor_position(), (4, 0)); } + +#[allow(clippy::cognitive_complexity)] +#[test] +fn scrollback() { + let mut parser = vt100::Parser::new(24, 80, 10); + + parser.process(b"1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24"); + assert_eq!(parser.screen().contents(), "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24"); + + parser.process(b"\r\n25\r\n26\r\n27\r\n28\r\n29\r\n30"); + assert_eq!(parser.screen().contents(), "7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30"); + + parser.scroll_to(0); + assert_eq!(parser.scroll_pos(), 0); + assert_eq!(parser.screen().contents(), "7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30"); + + parser.scroll_to(1); + assert_eq!(parser.scroll_pos(), 1); + assert_eq!(parser.screen().contents(), "6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29"); + + parser.scroll_to(3); + assert_eq!(parser.scroll_pos(), 3); + assert_eq!(parser.screen().contents(), "4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27"); + + parser.scroll_to(6); + assert_eq!(parser.scroll_pos(), 6); + assert_eq!(parser.screen().contents(), "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24"); + + parser.scroll_to(7); + assert_eq!(parser.scroll_pos(), 6); + assert_eq!(parser.screen().contents(), "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24"); + + parser.scroll_to(0); + assert_eq!(parser.scroll_pos(), 0); + assert_eq!(parser.screen().contents(), "7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30"); + + parser.scroll_to(7); + assert_eq!(parser.scroll_pos(), 6); + assert_eq!(parser.screen().contents(), "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24"); + + parser.process(b"\r\n31"); + assert_eq!(parser.scroll_pos(), 7); + assert_eq!(parser.screen().contents(), "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24"); + + parser.process(b"\r\n32"); + assert_eq!(parser.scroll_pos(), 8); + assert_eq!(parser.screen().contents(), "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24"); + + parser.process(b"\r\n33"); + assert_eq!(parser.scroll_pos(), 9); + assert_eq!(parser.screen().contents(), "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24"); + + parser.process(b"\r\n34"); + assert_eq!(parser.scroll_pos(), 10); + assert_eq!(parser.screen().contents(), "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24"); + + parser.process(b"\r\n35"); + assert_eq!(parser.scroll_pos(), 10); + assert_eq!(parser.screen().contents(), "2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25"); + + parser.process(b"\r\n36"); + assert_eq!(parser.scroll_pos(), 10); + assert_eq!(parser.screen().contents(), "3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26"); + + parser.scroll_to(12); + assert_eq!(parser.scroll_pos(), 10); + assert_eq!(parser.screen().contents(), "3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26"); + + parser.scroll_to(0); + assert_eq!(parser.scroll_pos(), 0); + assert_eq!(parser.screen().contents(), "13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36"); + + parser.process(b"\r\n37\r\n38"); + assert_eq!(parser.scroll_pos(), 0); + assert_eq!(parser.screen().contents(), "15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38"); + + parser.scroll_to(5); + assert_eq!(parser.scroll_pos(), 5); + assert_eq!(parser.screen().contents(), "10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33"); + + parser.process(b"\r\n39\r\n40"); + assert_eq!(parser.scroll_pos(), 7); + assert_eq!(parser.screen().contents(), "10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33"); +} -- cgit v1.2.3-54-g00ecf