Skip to main content

sel/
types.rs

1//! Shared types used across pipeline stages.
2
3use std::ops::Range;
4
5/// A line of input with its 1-indexed line number.
6#[derive(Debug, Clone)]
7pub struct Line {
8    pub no: u64,
9    pub bytes: Vec<u8>,
10}
11
12impl Line {
13    pub fn new(no: u64, bytes: Vec<u8>) -> Self {
14        Self { no, bytes }
15    }
16
17    /// Borrow the line content as a string, substituting U+FFFD for invalid UTF-8.
18    pub fn as_str_lossy(&self) -> std::borrow::Cow<'_, str> {
19        String::from_utf8_lossy(&self.bytes)
20    }
21}
22
23/// Result of running a `Matcher` on a `Line`.
24#[derive(Debug, Default, Clone)]
25pub struct MatchInfo {
26    /// Did this line hit?
27    pub hit: bool,
28    /// Byte ranges to highlight (for regex matches).
29    pub spans: Vec<Range<usize>>,
30    /// Target column (1-indexed) for positional matches.
31    pub col: Option<usize>,
32}
33
34/// Role of an emitted line in the output stream.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum Role {
37    /// A line that matched (primary output).
38    Target,
39    /// A neighbouring line included as context.
40    Context,
41}
42
43/// One line being emitted by the pipeline.
44#[derive(Debug, Clone)]
45pub struct Emit<'a> {
46    pub line: &'a Line,
47    pub role: Role,
48    pub match_info: &'a MatchInfo,
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn line_as_str_lossy_handles_invalid_utf8() {
57        let line = Line::new(1, vec![0xFF, b'a']);
58        let s = line.as_str_lossy();
59        assert!(s.ends_with('a'));
60    }
61
62    #[test]
63    fn match_info_default_is_miss() {
64        let mi = MatchInfo::default();
65        assert!(!mi.hit);
66        assert!(mi.spans.is_empty());
67        assert!(mi.col.is_none());
68    }
69}