1use crate::engine::{self, CompiledRegex, EngineFlags, EngineKind, RegexEngine};
4use crate::filter::FilterOptions;
5use crate::input::editor::Editor;
6
7pub struct FilterApp {
8 pub pattern_editor: Editor,
9 pub options: FilterOptions,
10 pub lines: Vec<String>,
11 pub json_extracted: Option<Vec<Option<String>>>,
16 pub matched: Vec<usize>,
18 pub match_spans: Vec<Vec<std::ops::Range<usize>>>,
22 pub selected: usize,
24 pub scroll: usize,
26 pub error: Option<String>,
28 pub should_quit: bool,
30 pub outcome: Outcome,
32 engine: Box<dyn RegexEngine>,
33 engine_flags: EngineFlags,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum Outcome {
38 Pending,
39 Emit,
40 Discard,
41}
42
43impl FilterApp {
44 pub fn new(lines: Vec<String>, initial_pattern: &str, options: FilterOptions) -> Self {
45 Self::build(lines, None, initial_pattern, options)
46 }
47
48 pub fn with_json_extracted(
52 lines: Vec<String>,
53 extracted: Vec<Option<String>>,
54 initial_pattern: &str,
55 options: FilterOptions,
56 ) -> Self {
57 assert_eq!(
58 lines.len(),
59 extracted.len(),
60 "extracted length must match lines length"
61 );
62 Self::build(lines, Some(extracted), initial_pattern, options)
63 }
64
65 fn build(
66 lines: Vec<String>,
67 json_extracted: Option<Vec<Option<String>>>,
68 initial_pattern: &str,
69 options: FilterOptions,
70 ) -> Self {
71 let pattern_editor = Editor::with_content(initial_pattern.to_string());
72 let engine_flags = EngineFlags {
73 case_insensitive: options.case_insensitive,
74 ..EngineFlags::default()
75 };
76 let engine = engine::create_engine(EngineKind::RustRegex);
77 let mut app = Self {
78 pattern_editor,
79 options,
80 lines,
81 json_extracted,
82 matched: Vec::new(),
83 match_spans: Vec::new(),
84 selected: 0,
85 scroll: 0,
86 error: None,
87 should_quit: false,
88 outcome: Outcome::Pending,
89 engine,
90 engine_flags,
91 };
92 app.recompute();
93 app
94 }
95
96 pub fn pattern(&self) -> &str {
97 self.pattern_editor.content()
98 }
99
100 pub fn recompute(&mut self) {
101 self.error = None;
102 let pattern = self.pattern().to_string();
103 if pattern.is_empty() {
104 self.matched = if let Some(extracted) = self.json_extracted.as_ref() {
108 extracted
109 .iter()
110 .enumerate()
111 .filter_map(|(idx, v)| {
112 if v.is_some() && !self.options.invert {
113 Some(idx)
114 } else {
115 None
116 }
117 })
118 .collect()
119 } else if self.options.invert {
120 Vec::new()
121 } else {
122 (0..self.lines.len()).collect()
123 };
124 self.match_spans = vec![Vec::new(); self.matched.len()];
126 self.clamp_selection();
127 return;
128 }
129 match self.engine.compile(&pattern, &self.engine_flags) {
130 Ok(compiled) => {
131 let (indices, spans) = self.collect_matches(&*compiled);
132 self.matched = indices;
133 self.match_spans = spans;
134 self.clamp_selection();
135 }
136 Err(err) => {
137 self.error = Some(err.to_string());
138 self.matched.clear();
139 self.match_spans.clear();
140 self.selected = 0;
141 self.scroll = 0;
142 }
143 }
144 }
145
146 fn collect_matches(
147 &self,
148 compiled: &dyn CompiledRegex,
149 ) -> (Vec<usize>, Vec<Vec<std::ops::Range<usize>>>) {
150 let mut indices = Vec::with_capacity(self.lines.len());
151 let mut all_spans = Vec::with_capacity(self.lines.len());
152 for idx in 0..self.lines.len() {
153 let haystack: &str = if let Some(extracted) = self.json_extracted.as_ref() {
157 match &extracted[idx] {
158 Some(s) => s.as_str(),
159 None => continue,
160 }
161 } else {
162 &self.lines[idx]
163 };
164 let line_matches = compiled.find_matches(haystack).unwrap_or_default();
165 let hit = !line_matches.is_empty();
166 if hit != self.options.invert {
167 indices.push(idx);
168 if self.options.invert {
171 all_spans.push(Vec::new());
172 } else {
173 all_spans.push(line_matches.into_iter().map(|m| m.start..m.end).collect());
174 }
175 }
176 }
177 (indices, all_spans)
178 }
179
180 fn clamp_selection(&mut self) {
181 if self.matched.is_empty() {
182 self.selected = 0;
183 self.scroll = 0;
184 } else if self.selected >= self.matched.len() {
185 self.selected = self.matched.len() - 1;
186 }
187 }
188
189 pub fn select_next(&mut self) {
190 if self.selected + 1 < self.matched.len() {
191 self.selected += 1;
192 }
193 }
194
195 pub fn select_prev(&mut self) {
196 self.selected = self.selected.saturating_sub(1);
197 }
198
199 pub fn toggle_case_insensitive(&mut self) {
200 self.options.case_insensitive = !self.options.case_insensitive;
201 self.engine_flags.case_insensitive = self.options.case_insensitive;
202 self.recompute();
203 }
204
205 pub fn toggle_invert(&mut self) {
206 self.options.invert = !self.options.invert;
207 self.recompute();
208 }
209}