Skip to content

Commit 479ae29

Browse files
azatclaude
andcommitted
Implement search by regexp in logs
Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 8edccd0 commit 479ae29

File tree

1 file changed

+44
-25
lines changed

1 file changed

+44
-25
lines changed

src/view/log_view.rs

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use cursive::{
99
views::{Dialog, EditView, NamedView, OnEventView},
1010
wrap_impl,
1111
};
12+
use regex::Regex;
1213
use std::collections::{HashMap, hash_map::DefaultHasher};
1314
use std::fs;
1415
use std::hash::{Hash, Hasher};
@@ -216,9 +217,10 @@ pub struct LogViewBase {
216217
scroll_core: scroll::Core,
217218

218219
search_direction_forward: bool,
219-
search_term: String,
220+
search_regex: Option<Regex>,
220221
matched_row: Option<usize>,
221222
matched_col: Option<usize>,
223+
matched_len: usize,
222224
skip_scroll: bool,
223225

224226
cluster: bool,
@@ -252,9 +254,10 @@ impl Default for LogViewBase {
252254
update_content: false,
253255
scroll_core: scroll::Core::default(),
254256
search_direction_forward: false,
255-
search_term: String::new(),
257+
search_regex: None,
256258
matched_row: None,
257259
matched_col: None,
260+
matched_len: 0,
258261
skip_scroll: false,
259262
cluster: false,
260263
wrap: false,
@@ -459,7 +462,7 @@ impl LogViewBase {
459462
}
460463

461464
fn search_in_direction(&mut self, forward: bool) -> bool {
462-
if self.search_term.is_empty() {
465+
if self.search_regex.is_none() {
463466
return false;
464467
}
465468

@@ -549,14 +552,18 @@ impl LogViewBase {
549552
current_row: usize,
550553
forward: bool,
551554
) -> bool {
555+
let re = match &self.search_regex {
556+
Some(re) => re,
557+
None => return false,
558+
};
552559
let mut x = 0;
553560
for span in row.resolve_stream(styled) {
554-
if let Some(pos) = span.content.find(&self.search_term) {
561+
if let Some(m) = re.find(span.content) {
555562
self.matched_row = Some(current_row);
556-
self.matched_col = Some(x + pos);
563+
self.matched_col = Some(x + span.content[..m.start()].width());
564+
self.matched_len = m.as_str().width();
557565
log::trace!(
558-
"search_term: {}, matched_row: {:?} ({}-search)",
559-
&self.search_term,
566+
"search regex matched_row: {:?} ({}-search)",
560567
self.matched_row,
561568
if forward { "forward" } else { "reverse" }
562569
);
@@ -788,29 +795,26 @@ impl LogViewBase {
788795
let mut x = 0;
789796

790797
for span in row.resolve_stream(&styled) {
791-
// Check if the span contains the search term
792-
if !self.search_term.is_empty() && span.content.contains(&self.search_term)
793-
{
798+
if let Some(ref re) = self.search_regex {
794799
let content = span.content;
795-
let search_term = &self.search_term;
796800
let mut last_pos = 0;
801+
let mut has_match = false;
797802

798-
for (match_start, _) in content.match_indices(search_term) {
799-
// Print text before match with normal style
800-
if match_start > last_pos {
801-
let before = &content[last_pos..match_start];
803+
for m in re.find_iter(content) {
804+
has_match = true;
805+
if m.start() > last_pos {
806+
let before = &content[last_pos..m.start()];
802807
printer.with_style(*span.attr, |printer| {
803808
printer.print((x, y), before);
804809
});
805810
x += before.width();
806811
}
807812

813+
let matched = m.as_str();
808814
// Use the same highlight theme as less(1):
809815
// - Always use black as text color
810816
// - Use original text color as background
811817
// - For no-style use white as background
812-
let matched =
813-
&content[match_start..match_start + search_term.len()];
814818
let bg_color = if *span.attr == Style::default() {
815819
Color::Rgb(255, 255, 255).into()
816820
} else {
@@ -822,16 +826,22 @@ impl LogViewBase {
822826
});
823827
x += matched.width();
824828

825-
last_pos = match_start + search_term.len();
829+
last_pos = m.end();
826830
}
827831

828-
// Print remaining text after last match
829-
if last_pos < content.len() {
830-
let after = &content[last_pos..];
832+
if has_match {
833+
if last_pos < content.len() {
834+
let after = &content[last_pos..];
835+
printer.with_style(*span.attr, |printer| {
836+
printer.print((x, y), after);
837+
});
838+
x += after.width();
839+
}
840+
} else {
831841
printer.with_style(*span.attr, |printer| {
832-
printer.print((x, y), after);
842+
printer.print((x, y), span.content);
833843
});
834-
x += after.width();
844+
x += span.content.width();
835845
}
836846
} else {
837847
// No match in this span or row, print normally
@@ -1033,10 +1043,19 @@ impl LogView {
10331043

10341044
let search_prompt_impl = |siv: &mut Cursive, forward: bool| {
10351045
let find = move |siv: &mut Cursive, text: &str| {
1046+
let re = match Regex::new(text) {
1047+
Ok(re) => re,
1048+
Err(err) => {
1049+
siv.pop_layer();
1050+
siv.add_layer(Dialog::info(format!("Invalid regex: {err}")));
1051+
return;
1052+
}
1053+
};
10361054
let found = siv.call_on_name("logs", |base: &mut LogViewBase| {
1037-
base.search_term = text.to_string();
1055+
base.search_regex = Some(re);
10381056
base.matched_row = None;
10391057
base.matched_col = None;
1058+
base.matched_len = 0;
10401059
base.skip_scroll = false;
10411060

10421061
base.search_direction_forward = forward;
@@ -1314,7 +1333,7 @@ impl View for LogViewBase {
13141333
self.skip_scroll = false;
13151334
} else if let Some(matched_row) = self.matched_row {
13161335
let match_start = self.matched_col.unwrap_or(0);
1317-
let match_end = match_start + self.search_term.len();
1336+
let match_end = match_start + self.matched_len;
13181337
let viewport_width = self.scroll_core.last_available_size().x;
13191338
let current_offset = self.scroll_core.content_viewport().left();
13201339

0 commit comments

Comments
 (0)