Skip to main content

sift_core/search/output/
style.rs

1use std::io::IsTerminal;
2
3use crate::search::output::format::ColumnLimit;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
6pub enum FilenameMode {
7    #[default]
8    Auto,
9    Always,
10    Never,
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
14pub enum ColorChoice {
15    #[default]
16    Auto,
17    Never,
18    Always,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
22pub enum PathDisplay {
23    #[default]
24    Relative,
25    Absolute,
26}
27
28bitflags::bitflags! {
29    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
30    pub struct LineStyleFlags: u8 {
31        const HEADING     = 1 << 0;
32        const LINE_NUMBER = 1 << 1;
33        const BYTE_OFFSET = 1 << 2;
34        const TRIM        = 1 << 3;
35        const COLUMN      = 1 << 4;
36    }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
40pub struct SearchLineStyle {
41    pub filename_mode: FilenameMode,
42    pub flags: LineStyleFlags,
43    pub path_display: PathDisplay,
44    pub columns: Option<ColumnLimit>,
45}
46
47impl SearchLineStyle {
48    #[must_use]
49    pub const fn heading(self) -> bool {
50        self.flags.contains(LineStyleFlags::HEADING)
51    }
52
53    #[must_use]
54    pub const fn line_number(self) -> bool {
55        self.flags.contains(LineStyleFlags::LINE_NUMBER)
56    }
57
58    #[must_use]
59    pub const fn byte_offset(self) -> bool {
60        self.flags.contains(LineStyleFlags::BYTE_OFFSET)
61    }
62
63    #[must_use]
64    pub const fn trim(self) -> bool {
65        self.flags.contains(LineStyleFlags::TRIM)
66    }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
70pub enum RecordTerminator {
71    #[default]
72    Newline,
73    Nul,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
77pub struct SearchRecordStyle {
78    pub terminator: RecordTerminator,
79    pub color: ColorChoice,
80    pub path_separator: Option<u8>,
81}
82
83impl SearchRecordStyle {
84    #[must_use]
85    pub fn should_color(&self) -> bool {
86        match self.color {
87            ColorChoice::Never => false,
88            ColorChoice::Always => true,
89            ColorChoice::Auto => std::io::stdout().is_terminal(),
90        }
91    }
92}
93
94impl RecordTerminator {
95    pub fn write_to(&self, out: &mut Vec<u8>) {
96        match self {
97            Self::Nul => out.push(0),
98            Self::Newline => out.push(b'\n'),
99        }
100    }
101}
102
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct SearchSeparators {
105    pub context_separator: Option<Vec<u8>>,
106    pub field_match_separator: Vec<u8>,
107    pub field_context_separator: Vec<u8>,
108}
109
110impl Default for SearchSeparators {
111    fn default() -> Self {
112        Self {
113            context_separator: Some(b"--".to_vec()),
114            field_match_separator: b":".to_vec(),
115            field_context_separator: b"-".to_vec(),
116        }
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn search_line_style_defaults() {
126        let style = SearchLineStyle::default();
127        assert!(!style.heading());
128        assert!(!style.line_number());
129        assert!(!style.byte_offset());
130        assert!(!style.trim());
131    }
132
133    #[test]
134    fn search_record_style_defaults() {
135        let style = SearchRecordStyle::default();
136        assert!(matches!(style.terminator, RecordTerminator::Newline));
137        assert_eq!(style.color, ColorChoice::Auto);
138        assert!(style.path_separator.is_none());
139    }
140
141    #[test]
142    fn search_separators_defaults() {
143        let sep = SearchSeparators::default();
144        assert_eq!(sep.context_separator, Some(b"--".to_vec()));
145        assert_eq!(sep.field_match_separator, b":".to_vec());
146        assert_eq!(sep.field_context_separator, b"-".to_vec());
147    }
148}