aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2019-11-09 14:34:47 -0500
committerJesse Luehrs <doy@tozt.net>2019-11-09 14:58:57 -0500
commit0d0258fb44ab483a4aa7650ce246b9418506aba5 (patch)
tree0a221fe05c56d6d50eddb98a3e3767a1a5af8962
parent44359ce5f1d4f13df5426175a5cbff45448c0c08 (diff)
downloadvt100-rust-0d0258fb44ab483a4aa7650ce246b9418506aba5.tar.gz
vt100-rust-0d0258fb44ab483a4aa7650ce246b9418506aba5.zip
implement scrolling back
-rw-r--r--src/grid.rs79
-rw-r--r--src/lib.rs2
-rw-r--r--src/parser.rs8
-rw-r--r--src/screen.rs40
-rw-r--r--tests/scroll.rs84
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<crate::row::Row>,
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<Item = &crate::row::Row> {
+ pub fn visible_rows(&self) -> impl Iterator<Item = &crate::row::Row> {
+ 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<Item = &crate::row::Row> {
self.rows.iter()
}
- pub fn rows_mut(&mut self) -> impl Iterator<Item = &mut crate::row::Row> {
+ pub fn drawing_rows_mut(
+ &mut self,
+ ) -> impl Iterator<Item = &mut crate::row::Row> {
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<Item = String> + '_ {
- 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<Item = Vec<u8>> + '_ {
- 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<Item = Vec<u8>> + '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");
+}