sift_core/search/output/
style.rs1use 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}