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:
parent
7a88f15af2
commit
e1cd9b5efd
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
125
src/util.rs
Normal 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"]);
|
||||
}
|
||||
}
|
18
src/vt.rs
18
src/vt.rs
@ -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);
|
||||
}
|
||||
|
@ -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", ""]);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user