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, 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}