tracexec_tui/
event_line.rs1use std::{
2 borrow::Cow,
3 fmt::Display,
4 mem,
5 ops::Range,
6};
7
8use ratatui::text::{
9 Line,
10 Span,
11};
12use tracexec_core::primitives::regex::{
13 BidirectionalIter,
14 BidirectionalIterator,
15 Cursor,
16 IntoBidirectionalIterator,
17 IntoCursor,
18};
19
20#[derive(Debug, Clone)]
21pub struct Mask {
22 pub range: Range<usize>,
24 pub values: Vec<Cow<'static, str>>,
26}
27
28impl Mask {
29 pub fn new(range: Range<usize>) -> Self {
30 Self {
31 values: vec![Default::default(); range.len()],
32 range,
33 }
34 }
35
36 pub fn toggle(&mut self, line: &mut Line<'static>) {
37 for (span, value) in line.spans[self.range.clone()]
38 .iter_mut()
39 .zip(self.values.iter_mut())
40 {
41 mem::swap(&mut span.content, value);
42 }
43 }
44}
45
46#[derive(Debug, Clone, Default)]
47pub struct EventLine {
48 pub line: Line<'static>,
49 pub cwd_mask: Option<Mask>,
50 pub env_mask: Option<Mask>,
51}
52
53impl From<Line<'static>> for EventLine {
54 fn from(line: Line<'static>) -> Self {
55 Self {
56 line,
57 cwd_mask: None,
58 env_mask: None,
59 }
60 }
61}
62
63impl Display for EventLine {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 write!(f, "{}", self.line)
66 }
67}
68
69impl EventLine {
70 pub fn toggle_cwd_mask(&mut self) {
71 if let Some(mask) = &mut self.cwd_mask {
72 mask.toggle(&mut self.line);
73 }
74 }
75
76 pub fn toggle_env_mask(&mut self) {
77 if let Some(mask) = &mut self.env_mask {
78 mask.toggle(&mut self.line);
79 }
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112enum CursorPosition {
113 ChunkStart,
114 ChunkEnd,
115}
116
117pub struct EventLineCursor<'a, 'b> {
118 iter: BidirectionalIter<'a, Span<'b>>,
119 current: &'a [u8],
121 position: CursorPosition,
123 len: usize,
125 offset: usize,
127}
128
129impl<'a> IntoCursor for &'a EventLine {
130 type Cursor = EventLineCursor<'a, 'static>;
131
132 fn into_cursor(self) -> Self::Cursor {
133 EventLineCursor::new(self.line.spans.as_slice())
134 }
135}
136
137impl<'a, 'b> EventLineCursor<'a, 'b>
138where
139 'b: 'a,
140{
141 fn new(slice: &'a [Span<'b>]) -> Self {
142 let mut res = Self {
143 iter: slice.into_bidirectional_iter(),
144 current: &[],
145 position: CursorPosition::ChunkEnd,
146 len: slice.iter().map(|s| s.content.len()).sum(),
147 offset: 0,
148 };
149 res.advance();
150 res
151 }
152}
153
154impl<'a, 'b> Cursor for EventLineCursor<'a, 'b>
155where
156 'b: 'a,
157{
158 fn chunk(&self) -> &[u8] {
159 self.current
160 }
161
162 fn advance(&mut self) -> bool {
163 match self.position {
164 CursorPosition::ChunkStart => {
165 self.iter.next();
166 self.position = CursorPosition::ChunkEnd;
167 }
168 CursorPosition::ChunkEnd => (),
169 }
170 for next in self.iter.by_ref() {
171 if next.content.is_empty() {
172 continue;
173 }
174 self.offset += self.current.len();
175 self.current = next.content.as_bytes();
176 return true;
177 }
178 false
179 }
180
181 fn backtrack(&mut self) -> bool {
182 match self.position {
183 CursorPosition::ChunkStart => {}
184 CursorPosition::ChunkEnd => {
185 self.iter.prev();
186 self.position = CursorPosition::ChunkStart;
187 }
188 }
189 while let Some(prev) = self.iter.prev() {
190 if prev.content.is_empty() {
191 continue;
192 }
193 self.offset -= prev.content.len();
194 self.current = prev.content.as_bytes();
195 return true;
196 }
197 false
198 }
199
200 fn total_bytes(&self) -> Option<usize> {
201 Some(self.len)
202 }
203
204 fn offset(&self) -> usize {
205 self.offset
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use ratatui::text::Span;
212
213 use super::*;
214
215 #[test]
216 fn smoke_test() {
217 let single = vec![Span::raw("abc")];
218 let mut cursor = EventLineCursor::new(single.as_slice());
219 assert_eq!(cursor.chunk(), b"abc");
220 assert!(!cursor.advance());
221 assert_eq!(cursor.chunk(), b"abc");
222 assert!(!cursor.backtrack());
223 assert_eq!(cursor.chunk(), b"abc");
224 let multi = vec![Span::raw("abc"); 100];
225 let mut cursor = EventLineCursor::new(multi.as_slice());
226 let mut offset = 0;
227 loop {
228 assert_eq!(cursor.offset(), offset);
229 offset += cursor.chunk().len();
230 if !cursor.advance() {
231 break;
232 }
233 }
234 loop {
235 offset -= cursor.chunk().len();
236 assert_eq!(cursor.offset(), offset);
237 if !cursor.backtrack() {
238 break;
239 }
240 }
241 assert_eq!(cursor.offset(), 0);
242 assert_eq!(offset, 0);
243 }
244}