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)]
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
69impl Default for SearchLineStyle {
70    fn default() -> Self {
71        Self {
72            filename_mode: FilenameMode::Auto,
73            flags: LineStyleFlags::empty(),
74            path_display: PathDisplay::default(),
75            columns: None,
76        }
77    }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub enum RecordTerminator {
82    Newline,
83    Nul,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub struct SearchRecordStyle {
88    pub terminator: RecordTerminator,
89    pub color: ColorChoice,
90    pub path_separator: Option<u8>,
91}
92
93impl SearchRecordStyle {
94    #[must_use]
95    pub fn should_color(&self) -> bool {
96        match self.color {
97            ColorChoice::Never => false,
98            ColorChoice::Always => true,
99            ColorChoice::Auto => std::io::stdout().is_terminal(),
100        }
101    }
102}
103
104impl RecordTerminator {
105    pub fn write_to(&self, out: &mut Vec<u8>) {
106        match self {
107            Self::Nul => out.push(0),
108            Self::Newline => out.push(b'\n'),
109        }
110    }
111}
112
113impl Default for SearchRecordStyle {
114    fn default() -> Self {
115        Self {
116            terminator: RecordTerminator::Newline,
117            color: ColorChoice::Auto,
118            path_separator: None,
119        }
120    }
121}
122
123#[derive(Debug, Clone, PartialEq, Eq)]
124pub struct SearchSeparators {
125    pub context_separator: Option<Vec<u8>>,
126    pub field_match_separator: Vec<u8>,
127    pub field_context_separator: Vec<u8>,
128}
129
130impl Default for SearchSeparators {
131    fn default() -> Self {
132        Self {
133            context_separator: Some(b"--".to_vec()),
134            field_match_separator: b":".to_vec(),
135            field_context_separator: b"-".to_vec(),
136        }
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn search_line_style_defaults() {
146        let style = SearchLineStyle::default();
147        assert!(!style.heading());
148        assert!(!style.line_number());
149        assert!(!style.byte_offset());
150        assert!(!style.trim());
151    }
152
153    #[test]
154    fn search_record_style_defaults() {
155        let style = SearchRecordStyle::default();
156        assert!(matches!(style.terminator, RecordTerminator::Newline));
157        assert_eq!(style.color, ColorChoice::Auto);
158        assert!(style.path_separator.is_none());
159    }
160
161    #[test]
162    fn search_separators_defaults() {
163        let sep = SearchSeparators::default();
164        assert_eq!(sep.context_separator, Some(b"--".to_vec()));
165        assert_eq!(sep.field_match_separator, b":".to_vec());
166        assert_eq!(sep.field_context_separator, b"-".to_vec());
167    }
168}