1use crate::engine::{self, CompiledRegex, EngineFlags, RegexEngine};
4use crate::filter::{match_haystack, 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(
55 lines: Vec<String>,
56 extracted: Vec<Option<String>>,
57 initial_pattern: &str,
58 options: FilterOptions,
59 ) -> Result<Self, String> {
60 if lines.len() != extracted.len() {
61 return Err(format!(
62 "extracted length ({}) must match lines length ({})",
63 extracted.len(),
64 lines.len()
65 ));
66 }
67 Ok(Self::build(
68 lines,
69 Some(extracted),
70 initial_pattern,
71 options,
72 ))
73 }
74
75 fn build(
76 lines: Vec<String>,
77 json_extracted: Option<Vec<Option<String>>>,
78 initial_pattern: &str,
79 options: FilterOptions,
80 ) -> Self {
81 let pattern_editor = Editor::with_content(initial_pattern.to_string());
82 let engine_flags = EngineFlags {
83 case_insensitive: options.case_insensitive,
84 ..EngineFlags::default()
85 };
86 let engine = engine::create_engine(engine::detect_minimum_engine(initial_pattern));
87 let mut app = Self {
88 pattern_editor,
89 options,
90 lines,
91 json_extracted,
92 matched: Vec::new(),
93 match_spans: Vec::new(),
94 selected: 0,
95 scroll: 0,
96 error: None,
97 should_quit: false,
98 outcome: Outcome::Pending,
99 engine,
100 engine_flags,
101 };
102 app.recompute();
103 app
104 }
105
106 pub fn pattern(&self) -> &str {
107 self.pattern_editor.content()
108 }
109
110 pub fn recompute(&mut self) {
111 self.error = None;
112 let pattern = self.pattern().to_string();
113 if pattern.is_empty() {
114 self.matched = if self.options.invert {
118 Vec::new()
119 } else if let Some(extracted) = self.json_extracted.as_ref() {
120 extracted
121 .iter()
122 .enumerate()
123 .filter_map(|(idx, v)| v.as_ref().map(|_| idx))
124 .collect()
125 } else {
126 (0..self.lines.len()).collect()
127 };
128 self.match_spans = vec![Vec::new(); self.matched.len()];
130 self.clamp_selection();
131 return;
132 }
133 self.engine = engine::create_engine(engine::detect_minimum_engine(&pattern));
134 match self.engine.compile(&pattern, &self.engine_flags) {
135 Ok(compiled) => {
136 let (indices, spans) = self.collect_matches(&*compiled);
137 self.matched = indices;
138 self.match_spans = spans;
139 self.clamp_selection();
140 }
141 Err(err) => {
142 self.error = Some(err.to_string());
143 self.matched.clear();
144 self.match_spans.clear();
145 self.selected = 0;
146 self.scroll = 0;
147 }
148 }
149 }
150
151 fn collect_matches(
152 &self,
153 compiled: &dyn CompiledRegex,
154 ) -> (Vec<usize>, Vec<Vec<std::ops::Range<usize>>>) {
155 let mut indices = Vec::with_capacity(self.lines.len());
156 let mut all_spans = Vec::with_capacity(self.lines.len());
157 for idx in 0..self.lines.len() {
158 let haystack: &str = if let Some(extracted) = self.json_extracted.as_ref() {
162 match &extracted[idx] {
163 Some(s) => s.as_str(),
164 None => continue,
165 }
166 } else {
167 &self.lines[idx]
168 };
169 if let Some(spans) = match_haystack(compiled, haystack, self.options.invert) {
170 indices.push(idx);
171 all_spans.push(spans);
172 }
173 }
174 (indices, all_spans)
175 }
176
177 fn clamp_selection(&mut self) {
178 if self.matched.is_empty() {
179 self.selected = 0;
180 self.scroll = 0;
181 } else if self.selected >= self.matched.len() {
182 self.selected = self.matched.len() - 1;
183 }
184 }
185
186 pub fn select_next(&mut self) {
187 if self.selected + 1 < self.matched.len() {
188 self.selected += 1;
189 }
190 }
191
192 pub fn select_prev(&mut self) {
193 self.selected = self.selected.saturating_sub(1);
194 }
195
196 pub fn toggle_case_insensitive(&mut self) {
197 self.options.case_insensitive = !self.options.case_insensitive;
198 self.engine_flags.case_insensitive = self.options.case_insensitive;
199 self.recompute();
200 }
201
202 pub fn toggle_invert(&mut self) {
203 self.options.invert = !self.options.invert;
204 self.recompute();
205 }
206}