grit_lib/
sideband_lines.rs1#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum RemoteLine {
10 Update(String),
12 Line(String),
14}
15
16#[derive(Debug, Default)]
18pub struct SidebandLines {
19 pending: Vec<u8>,
20}
21
22impl SidebandLines {
23 #[must_use]
25 pub fn new() -> Self {
26 Self {
27 pending: Vec::new(),
28 }
29 }
30
31 pub fn feed<F>(&mut self, chunk: &[u8], mut emit: F)
33 where
34 F: FnMut(RemoteLine),
35 {
36 if chunk.is_empty() {
37 return;
38 }
39 self.pending.extend_from_slice(chunk);
40 while let Some(i) = self.pending.iter().position(|&b| b == b'\r' || b == b'\n') {
41 let delim = self.pending[i];
42 let line = self.pending.drain(..i).collect::<Vec<_>>();
43 self.pending.drain(..1);
44 let text = String::from_utf8_lossy(&line).into_owned();
45 let item = if delim == b'\r' {
46 RemoteLine::Update(text)
47 } else {
48 RemoteLine::Line(text)
49 };
50 emit(item);
51 }
52 }
53
54 pub fn finish<F>(&mut self, mut emit: F)
56 where
57 F: FnMut(RemoteLine),
58 {
59 if self.pending.is_empty() {
60 return;
61 }
62 let text = String::from_utf8_lossy(&self.pending).into_owned();
63 self.pending.clear();
64 if !text.is_empty() {
65 emit(RemoteLine::Line(text));
66 }
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 fn collect(chunks: &[&[u8]]) -> Vec<RemoteLine> {
75 let mut splitter = SidebandLines::new();
76 let mut out = Vec::new();
77 for chunk in chunks {
78 splitter.feed(*chunk, |line| out.push(line));
79 }
80 out
81 }
82
83 #[test]
84 fn cr_updates() {
85 assert_eq!(
86 collect(&[b"one\r two\r"]),
87 vec![
88 RemoteLine::Update("one".into()),
89 RemoteLine::Update(" two".into()),
90 ]
91 );
92 }
93
94 #[test]
95 fn lf_lines() {
96 assert_eq!(
97 collect(&[b"first\nsecond\n"]),
98 vec![
99 RemoteLine::Line("first".into()),
100 RemoteLine::Line("second".into()),
101 ]
102 );
103 }
104
105 #[test]
106 fn split_across_chunks() {
107 assert_eq!(
108 collect(&[b"hel", b"lo\rwor", b"ld\n"]),
109 vec![
110 RemoteLine::Update("hello".into()),
111 RemoteLine::Line("world".into()),
112 ]
113 );
114 }
115
116 #[test]
117 fn finish_emits_trailing_bytes_without_delimiter() {
118 let mut splitter = SidebandLines::new();
119 let mut out = Vec::new();
120 splitter.feed(b"no trailing newline", |line| out.push(line));
121 splitter.finish(|line| out.push(line));
122 assert_eq!(out, vec![RemoteLine::Line("no trailing newline".into())]);
123 }
124
125 #[test]
126 fn invalid_utf8_lossy() {
127 let mut splitter = SidebandLines::new();
128 let mut out = Vec::new();
129 splitter.feed(b"\xFF\xFEok\r", |line| out.push(line));
130 assert_eq!(out, vec![RemoteLine::Update("\u{FFFD}\u{FFFD}ok".into())]);
131 }
132}