Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add entire screen viewer to vt100 #7699

Merged
merged 1 commit into from Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions crates/turborepo-vt100/src/entire_screen.rs
@@ -0,0 +1,21 @@
pub struct EntireScreen<'a>(pub(crate) &'a crate::Screen);

impl<'a> EntireScreen<'a> {
#[must_use]
pub fn cell(&self, row: u16, col: u16) -> Option<&crate::Cell> {
self.0.grid().all_row(row).and_then(|r| r.get(col))
}

#[must_use]
pub fn contents(&self) -> String {
let mut s = String::new();
self.0.grid().write_full_contents(&mut s);
s
}

/// Size required to render all contents
#[must_use]
pub fn size(&self) -> (usize, u16) {
self.0.grid().size_with_contents()
}
}
46 changes: 46 additions & 0 deletions crates/turborepo-vt100/src/grid.rs
Expand Up @@ -63,6 +63,22 @@ impl Grid {
self.size
}

/// Size of screen without trailing blank rows
pub fn size_with_contents(&self) -> (usize, u16) {
let cols = self.size.cols;
let last_row_with_content = self.all_rows().enumerate().fold(
0,
|last_row_with_contents, (row_index, row)| {
if row.is_blank() {
last_row_with_contents
} else {
row_index
}
},
);
(last_row_with_content + 1, cols)
}

pub fn set_size(&mut self, size: Size) {
if size.cols != self.size.cols {
for row in &mut self.rows {
Expand Down Expand Up @@ -129,6 +145,21 @@ impl Grid {
self.rows.iter()
}

pub fn all_rows(&self) -> impl Iterator<Item = &crate::row::Row> {
self.scrollback.iter().chain(self.rows.iter())
}

pub(crate) fn all_row(&self, row: u16) -> Option<&crate::row::Row> {
let row = usize::from(row);
if row < self.scrollback.len() {
self.scrollback.get(row)
} else if row < self.scrollback.len() + self.rows.len() {
self.rows.get(row - self.scrollback.len())
} else {
None
}
}

pub fn drawing_rows_mut(
&mut self,
) -> impl Iterator<Item = &mut crate::row::Row> {
Expand Down Expand Up @@ -196,6 +227,21 @@ impl Grid {
}
}

pub fn write_full_contents(&self, contents: &mut String) {
let mut wrapping = false;
for row in self.all_rows() {
row.write_contents(contents, 0, self.size.cols, wrapping);
if !row.wrapped() {
contents.push('\n');
}
wrapping = row.wrapped();
}

while contents.ends_with('\n') {
contents.truncate(contents.len() - 1);
}
}

pub fn write_contents_formatted(
&self,
contents: &mut Vec<u8>,
Expand Down
2 changes: 2 additions & 0 deletions crates/turborepo-vt100/src/lib.rs
Expand Up @@ -50,6 +50,7 @@
mod attrs;
mod callbacks;
mod cell;
mod entire_screen;
mod grid;
mod parser;
mod perform;
Expand All @@ -60,5 +61,6 @@ mod term;
pub use attrs::Color;
pub use callbacks::Callbacks;
pub use cell::Cell;
pub use entire_screen::EntireScreen;
pub use parser::Parser;
pub use screen::{MouseProtocolEncoding, MouseProtocolMode, Screen};
7 changes: 7 additions & 0 deletions crates/turborepo-vt100/src/parser.rs
Expand Up @@ -57,6 +57,13 @@ impl Parser {
pub fn screen_mut(&mut self) -> &mut crate::Screen {
&mut self.screen.0
}

/// Returns a reference to an `EntireScreen` object containing the
/// terminal state where all contents including scrollback and displayed.
#[must_use]
pub fn entire_screen(&self) -> crate::EntireScreen {
crate::EntireScreen(self.screen())
}
}

impl Default for Parser {
Expand Down
4 changes: 4 additions & 0 deletions crates/turborepo-vt100/src/row.rs
Expand Up @@ -29,6 +29,10 @@ impl Row {
self.wrapped = false;
}

pub fn is_blank(&self) -> bool {
self.cells().all(|cell| !cell.has_contents())
}

fn cells(&self) -> impl Iterator<Item = &crate::Cell> {
self.cells.iter()
}
Expand Down
33 changes: 33 additions & 0 deletions crates/turborepo-vt100/tests/entire_screen.rs
@@ -0,0 +1,33 @@
use turborepo_vt100 as vt100;

#[test]
fn test_screen_includes_scrollback() {
let mut parser = vt100::Parser::new(2, 20, 100);
parser.process(b"foo\r\nbar\r\nbaz\r\n");
let screen = parser.entire_screen();
assert_eq!(screen.contents(), "foo\nbar\nbaz");
assert_eq!(screen.size(), (3, 20));
}

#[test]
fn test_screen_trims_trailing_blank_lines() {
let mut parser = vt100::Parser::new(8, 20, 0);
parser.process(b"foo\r\nbar\r\n");
let screen = parser.entire_screen();
assert_eq!(screen.contents(), "foo\nbar");
assert_eq!(screen.size(), (2, 20));
}

#[test]
fn test_wrapped_lines_size() {
let mut parser = vt100::Parser::new(8, 8, 10);
parser.process(b"one long line\r\nbar\r\n");
let screen = parser.entire_screen();
assert_eq!(screen.contents(), "one long line\nbar");
assert_eq!(screen.size(), (3, 8));
assert_eq!(screen.cell(0, 0).unwrap().contents(), "o");
assert_eq!(screen.cell(1, 0).unwrap().contents(), " ");
// "one long line"
// ^ last char that fits on line, rest will appear on next row
assert_eq!(screen.cell(2, 0).unwrap().contents(), "b");
}