tracexec_tui/
event_line.rs

1use 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  /// The range of the spans to mask
23  pub range: Range<usize>,
24  /// The value of the spans to mask
25  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// Original Copyright Notice for the following code:
84
85// Copyright (c) 2024 Pascal Kuthe
86
87// Permission is hereby granted, free of charge, to any
88// person obtaining a copy of this software and associated
89// documentation files (the "Software"), to deal in the
90// Software without restriction, including without
91// limitation the rights to use, copy, modify, merge,
92// publish, distribute, sublicense, and/or sell copies of
93// the Software, and to permit persons to whom the Software
94// is furnished to do so, subject to the following
95// conditions:
96
97// The above copyright notice and this permission notice
98// shall be included in all copies or substantial portions
99// of the Software.
100
101// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
102// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
103// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
104// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
105// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
106// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
107// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
108// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
109// DEALINGS IN THE SOFTWARE.
110
111#[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 chunk
120  current: &'a [u8],
121  /// Cursor position
122  position: CursorPosition,
123  /// Length in bytes
124  len: usize,
125  /// Byte offset
126  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}