1
0
mirror of https://github.com/asciinema/avt.git synced 2025-04-19 05:22:19 +03:00

Add util::TextCollector for collecting text output incrementally

This commit is contained in:
Marcin Kulik 2024-01-28 18:10:00 +01:00
parent 7a88f15af2
commit e1cd9b5efd
6 changed files with 165 additions and 37 deletions

View File

@ -3,6 +3,7 @@ use crate::dump::Dump;
use crate::line::Line;
use crate::pen::Pen;
use std::cmp::Ordering;
use std::convert::Infallible;
use std::ops::{Index, IndexMut, Range};
#[derive(Debug)]
@ -15,13 +16,19 @@ pub(crate) struct Buffer {
}
pub trait ScrolbackCollector {
fn collect(&mut self, lines: impl Iterator<Item = Line>);
type Error;
fn collect(&mut self, lines: impl Iterator<Item = Line>) -> Result<(), Self::Error>;
}
pub struct NullScrollbackCollector;
impl ScrolbackCollector for NullScrollbackCollector {
fn collect(&mut self, _lines: impl Iterator<Item = Line>) {}
type Error = Infallible;
fn collect(&mut self, _lines: impl Iterator<Item = Line>) -> Result<(), Self::Error> {
Ok(())
}
}
pub(crate) enum EraseMode {
@ -329,11 +336,13 @@ impl Buffer {
&self.lines[..]
}
pub fn gc(&mut self, sc: impl ScrolbackCollector) {
pub fn gc<C: ScrolbackCollector>(&mut self, sc: C) -> Result<(), C::Error> {
if self.trim_needed {
self.trim_scrollback(sc);
self.trim_scrollback(sc)?;
self.trim_needed = false;
}
Ok(())
}
fn view_mut(&mut self) -> &mut [Line] {
@ -352,16 +361,18 @@ impl Buffer {
self.lines.extend(filler);
}
fn trim_scrollback(&mut self, mut sc: impl ScrolbackCollector) {
fn trim_scrollback<C: ScrolbackCollector>(&mut self, mut sc: C) -> Result<(), C::Error> {
if let Some(limit) = self.scrollback_limit {
let line_count = self.lines.len();
let scrollback_size = line_count - self.rows;
if scrollback_size > limit {
let excess = scrollback_size - limit;
sc.collect(self.lines.drain(..excess));
sc.collect(self.lines.drain(..excess))?;
}
}
Ok(())
}
#[cfg(test)]
@ -620,7 +631,7 @@ mod tests {
assert_eq!(buf.lines.len(), 12);
buf.gc(NullScrollbackCollector);
buf.gc(NullScrollbackCollector).unwrap();
assert_eq!(buf.lines.len(), 7);
@ -632,7 +643,7 @@ mod tests {
assert_eq!(buf.lines.len(), 12);
buf.gc(NullScrollbackCollector);
buf.gc(NullScrollbackCollector).unwrap();
assert_eq!(buf.lines.len(), 10);
}

View File

@ -10,8 +10,8 @@ mod saved_ctx;
mod segment;
mod tabs;
mod terminal;
pub mod util;
mod vt;
pub use buffer::ScrolbackCollector;
pub use color::Color;
pub use line::Line;
pub use pen::Pen;

View File

@ -89,10 +89,15 @@ impl Terminal {
self.cursor
}
pub fn gc(&mut self, sc: impl ScrolbackCollector) {
pub fn gc<C: ScrolbackCollector>(&mut self, sc: C) -> Result<(), C::Error> {
match self.active_buffer_type {
BufferType::Primary => self.buffer.gc(sc),
BufferType::Alternate => self.buffer.gc(NullScrollbackCollector),
BufferType::Alternate => {
let _ = self.buffer.gc(NullScrollbackCollector);
Ok(())
}
}
}

125
src/util.rs Normal file
View File

@ -0,0 +1,125 @@
use crate::buffer::ScrolbackCollector;
use crate::line::Line;
use crate::vt::Vt;
use std::convert::Infallible;
use std::mem;
pub struct TextCollector<O: TextCollectorOutput> {
vt: Vt,
stc: ScrollbackTextCollector<O>,
}
pub trait TextCollectorOutput {
type Error;
fn push(&mut self, line: String) -> Result<(), Self::Error>;
}
struct ScrollbackTextCollector<O: TextCollectorOutput> {
wrapped_line: String,
output: O,
}
impl<O: TextCollectorOutput> TextCollector<O> {
pub fn new(vt: Vt, output: O) -> Self {
Self {
vt,
stc: ScrollbackTextCollector {
wrapped_line: String::new(),
output,
},
}
}
}
impl<O: TextCollectorOutput> TextCollector<O> {
pub fn feed_str(&mut self, s: &str) -> Result<(), O::Error> {
self.vt.feed_str_sc(s, &mut self.stc)?;
Ok(())
}
pub fn resize(&mut self, cols: u16, rows: u16) -> Result<(), O::Error> {
self.vt
.feed_str_sc(&format!("\x1b[8;{rows};{cols}t"), &mut self.stc)?;
Ok(())
}
pub fn flush(&mut self) -> Result<(), O::Error> {
let mut lines = self.vt.text();
while !lines.is_empty() && lines[lines.len() - 1].is_empty() {
lines.truncate(lines.len() - 1);
}
for line in lines {
self.stc.push(line)?;
}
Ok(())
}
}
impl<O: TextCollectorOutput> ScrollbackTextCollector<O> {
fn push(&mut self, line: String) -> Result<(), O::Error> {
self.output.push(line)
}
}
impl<O: TextCollectorOutput> ScrolbackCollector for &mut ScrollbackTextCollector<O> {
type Error = O::Error;
fn collect(&mut self, lines: impl Iterator<Item = Line>) -> Result<(), Self::Error> {
for line in lines {
if line.wrapped {
self.wrapped_line.push_str(&line.text());
} else {
self.wrapped_line.push_str(line.text().trim_end());
self.output.push(mem::take(&mut self.wrapped_line))?;
}
}
Ok(())
}
}
impl TextCollectorOutput for &mut Vec<String> {
type Error = Infallible;
fn push(&mut self, line: String) -> Result<(), Self::Error> {
Vec::push(self, line);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::TextCollector;
use crate::Vt;
#[test]
fn text_collector_no_scrollback() {
let mut output: Vec<String> = Vec::new();
let vt = Vt::builder().size(10, 2).scrollback_limit(0).build();
let mut tc = TextCollector::new(vt, &mut output);
tc.feed_str("a\r\nb\r\nc\r\nd\r\n").unwrap();
tc.flush().unwrap();
assert_eq!(output, vec!["a", "b", "c", "d"]);
}
#[test]
fn text_collector_unlimited_scrollback() {
let mut output: Vec<String> = Vec::new();
let vt = Vt::builder().size(10, 2).build();
let mut tc = TextCollector::new(vt, &mut output);
tc.feed_str("a\r\nb\r\nc\r\nd\r\n").unwrap();
tc.flush().unwrap();
assert_eq!(output, vec!["a", "b", "c", "d"]);
}
}

View File

@ -20,17 +20,23 @@ impl Vt {
}
pub fn feed_str(&mut self, s: &str) -> (Vec<usize>, bool) {
self.feed_str_sc(s, NullScrollbackCollector)
}
#[inline(always)]
pub fn feed_str_sc(&mut self, s: &str, sc: impl ScrolbackCollector) -> (Vec<usize>, bool) {
self.parser.feed_str(s, &mut self.terminal);
self.terminal.gc(sc);
let _ = self.terminal.gc(NullScrollbackCollector);
self.terminal.changes()
}
pub fn feed_str_sc<C: ScrolbackCollector>(
&mut self,
s: &str,
sc: C,
) -> Result<(Vec<usize>, bool), C::Error> {
self.parser.feed_str(s, &mut self.terminal);
self.terminal.gc(sc)?;
Ok(self.terminal.changes())
}
pub fn feed(&mut self, input: char) {
self.parser.feed(input, &mut self.terminal);
}

View File

@ -1,4 +1,4 @@
use avt::{Line, ScrolbackCollector, Vt};
use avt::Vt;
use rand::RngCore;
#[test]
@ -10,22 +10,3 @@ fn feed_str() {
vt.feed_str(&str);
// no assertions - just check it doesn't panic on random input
}
struct TestScrollbackCollector(Vec<String>);
impl ScrolbackCollector for &mut TestScrollbackCollector {
fn collect(&mut self, lines: impl Iterator<Item = Line>) {
self.0.extend(lines.map(|l| l.text().trim_end().to_owned()));
}
}
#[test]
fn feed_str_sc() {
let mut sc = TestScrollbackCollector(Vec::new());
let mut vt = Vt::builder().size(10, 2).scrollback_limit(0).build();
vt.feed_str_sc("a\r\nb\r\nc\r\nd\r\n", &mut sc);
assert_eq!(sc.0, vec!["a", "b", "c"]);
assert_eq!(vt.text(), vec!["d", ""]);
}