diff --git a/src/buffer.rs b/src/buffer.rs index 12b1630..c4744e6 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -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); + type Error; + + fn collect(&mut self, lines: impl Iterator) -> Result<(), Self::Error>; } pub struct NullScrollbackCollector; impl ScrolbackCollector for NullScrollbackCollector { - fn collect(&mut self, _lines: impl Iterator) {} + type Error = Infallible; + + fn collect(&mut self, _lines: impl Iterator) -> 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(&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(&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); } diff --git a/src/lib.rs b/src/lib.rs index 16ce7fe..47e6f91 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/terminal.rs b/src/terminal.rs index 594c5b5..407f36a 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -89,10 +89,15 @@ impl Terminal { self.cursor } - pub fn gc(&mut self, sc: impl ScrolbackCollector) { + pub fn gc(&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(()) + } } } diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..6cd36c1 --- /dev/null +++ b/src/util.rs @@ -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 { + vt: Vt, + stc: ScrollbackTextCollector, +} + +pub trait TextCollectorOutput { + type Error; + + fn push(&mut self, line: String) -> Result<(), Self::Error>; +} + +struct ScrollbackTextCollector { + wrapped_line: String, + output: O, +} + +impl TextCollector { + pub fn new(vt: Vt, output: O) -> Self { + Self { + vt, + stc: ScrollbackTextCollector { + wrapped_line: String::new(), + output, + }, + } + } +} + +impl TextCollector { + 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 ScrollbackTextCollector { + fn push(&mut self, line: String) -> Result<(), O::Error> { + self.output.push(line) + } +} + +impl ScrolbackCollector for &mut ScrollbackTextCollector { + type Error = O::Error; + + fn collect(&mut self, lines: impl Iterator) -> 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 { + 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 = 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 = 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"]); + } +} diff --git a/src/vt.rs b/src/vt.rs index b0c196b..c8ab7a9 100644 --- a/src/vt.rs +++ b/src/vt.rs @@ -20,17 +20,23 @@ impl Vt { } pub fn feed_str(&mut self, s: &str) -> (Vec, bool) { - self.feed_str_sc(s, NullScrollbackCollector) - } - - #[inline(always)] - pub fn feed_str_sc(&mut self, s: &str, sc: impl ScrolbackCollector) -> (Vec, 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( + &mut self, + s: &str, + sc: C, + ) -> Result<(Vec, 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); } diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 090dcf4..d2a1045 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -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); - -impl ScrolbackCollector for &mut TestScrollbackCollector { - fn collect(&mut self, lines: impl Iterator) { - 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", ""]); -}