Skip to content

Commit 0b2e1e3

Browse files
committed
feat: add filter progress percentage and streaming filter
- Show filter progress as percentage in status bar - Add streaming_filter module with SIMD-accelerated search (memmem) - Grep-style search for case-sensitive patterns (lazy line counting) - Track lines_processed instead of match count for accurate progress - Reuse buffer for case-insensitive search to reduce allocations
1 parent b440ba1 commit 0b2e1e3

File tree

8 files changed

+754
-58
lines changed

8 files changed

+754
-58
lines changed

src/app.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub enum FilterState {
2121
#[default]
2222
Inactive,
2323
Processing {
24-
progress: usize,
24+
lines_processed: usize,
2525
},
2626
Complete {
2727
matches: usize,
@@ -217,7 +217,11 @@ impl App {
217217
}
218218

219219
/// Merge partial filter results (for immediate display while filtering continues)
220-
pub fn merge_partial_filter_results(&mut self, new_indices: Vec<usize>) {
220+
pub fn merge_partial_filter_results(
221+
&mut self,
222+
new_indices: Vec<usize>,
223+
lines_processed: usize,
224+
) {
221225
let tab = self.active_tab_mut();
222226

223227
// Check if we need to clear old results (new filter started)
@@ -275,10 +279,8 @@ impl App {
275279
tab.viewport.adjust_scroll_for_prepend(prepended_count);
276280
}
277281

278-
// Update filter state to show partial match count
279-
tab.filter.state = FilterState::Processing {
280-
progress: tab.line_indices.len(),
281-
};
282+
// Update filter state with lines processed for progress display
283+
tab.filter.state = FilterState::Processing { lines_processed };
282284
}
283285

284286
/// Clear filter
@@ -589,13 +591,14 @@ impl App {
589591

590592
// Filter progress events
591593
AppEvent::FilterProgress(lines_processed) => {
592-
self.active_tab_mut().filter.state = FilterState::Processing {
593-
progress: lines_processed,
594-
};
594+
self.active_tab_mut().filter.state = FilterState::Processing { lines_processed };
595595
}
596-
AppEvent::FilterPartialResults(indices) => {
596+
AppEvent::FilterPartialResults {
597+
matches,
598+
lines_processed,
599+
} => {
597600
// Merge partial results for immediate display
598-
self.merge_partial_filter_results(indices);
601+
self.merge_partial_filter_results(matches, lines_processed);
599602
}
600603
AppEvent::FilterComplete {
601604
indices,
@@ -732,7 +735,7 @@ impl App {
732735
// Defer clearing until first results arrive (prevents blink)
733736
let tab = self.active_tab_mut();
734737
tab.filter.needs_clear = true;
735-
tab.filter.state = FilterState::Processing { progress: 0 };
738+
tab.filter.state = FilterState::Processing { lines_processed: 0 };
736739
}
737740
// Actual filter execution is handled in main loop
738741
}

src/event.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ pub enum AppEvent {
3434
},
3535
FilterProgress(usize),
3636
/// Partial filter results (for immediate display while filtering continues)
37-
FilterPartialResults(Vec<usize>),
37+
FilterPartialResults {
38+
matches: Vec<usize>,
39+
lines_processed: usize,
40+
},
3841
FilterComplete {
3942
indices: Vec<usize>,
4043
incremental: bool,

src/filter/engine.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ pub enum FilterProgress {
1212
/// Currently processing (lines processed so far)
1313
Processing(usize),
1414
/// Partial results found (sent periodically so UI can show matches immediately)
15-
PartialResults(Vec<usize>),
15+
PartialResults {
16+
matches: Vec<usize>,
17+
lines_processed: usize,
18+
},
1619
/// Filtering complete (final matching line indices)
1720
Complete(Vec<usize>),
1821
/// Error occurred
@@ -245,6 +248,9 @@ impl FilterEngine {
245248
}
246249
}
247250

251+
// Calculate lines processed (we process from end to start)
252+
let lines_processed = end - batch_start;
253+
248254
// Send partial results immediately if we found matches
249255
if !batch_matches.is_empty() {
250256
// Sort this batch (it's from a contiguous range, so already mostly sorted)
@@ -255,7 +261,10 @@ impl FilterEngine {
255261
return Ok(());
256262
}
257263
// Send these matches so UI can show them right away
258-
let _ = tx.send(FilterProgress::PartialResults(batch_matches));
264+
let _ = tx.send(FilterProgress::PartialResults {
265+
matches: batch_matches,
266+
lines_processed,
267+
});
259268
}
260269

261270
current_end = batch_start;
@@ -344,6 +353,9 @@ impl FilterEngine {
344353
}
345354
}
346355

356+
// Calculate lines processed (we process from end to start)
357+
let lines_processed = end - batch_start;
358+
347359
// Send partial results immediately if we found matches
348360
if !batch_matches.is_empty() {
349361
batch_matches.sort_unstable();
@@ -352,7 +364,10 @@ impl FilterEngine {
352364
if cancel.is_cancelled() {
353365
return Ok(());
354366
}
355-
let _ = tx.send(FilterProgress::PartialResults(batch_matches));
367+
let _ = tx.send(FilterProgress::PartialResults {
368+
matches: batch_matches,
369+
lines_processed,
370+
});
356371
}
357372

358373
current_end = batch_start;
@@ -524,7 +539,7 @@ mod tests {
524539
FilterProgress::Processing(line_num) => {
525540
progress_updates.push(line_num);
526541
}
527-
FilterProgress::PartialResults(_) => {
542+
FilterProgress::PartialResults { .. } => {
528543
// Partial results are fine, just continue
529544
}
530545
FilterProgress::Complete(indices) => {

src/filter/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod cancel;
22
pub mod engine;
33
pub mod parallel_engine;
44
pub mod regex_filter;
5+
pub mod streaming_filter;
56
pub mod string_filter;
67

78
/// Trait for extensible filtering

0 commit comments

Comments
 (0)