Skip to content

Commit 0bb2c9d

Browse files
committed
feat: add regex support to the search
1 parent 7a5bda9 commit 0bb2c9d

3 files changed

Lines changed: 85 additions & 65 deletions

File tree

src/app.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ impl App {
245245
self.output_widget.highlight(
246246
self.search_widget.input.value(),
247247
self.search_widget.case_sensitive,
248+
self.search_widget.regex,
248249
);
249250
self.search_widget
250251
.update_highlight_info(self.output_widget.highlight_info());
@@ -272,20 +273,23 @@ impl App {
272273
self.output_widget.highlight(
273274
self.search_widget.input.value(),
274275
self.search_widget.case_sensitive,
276+
self.search_widget.regex,
275277
);
276278
self.search_widget
277279
.update_highlight_info(self.output_widget.highlight_info());
278280
}
279281
_ => match to_ui_command(key_bindings, code, mods) {
280282
None => {
281283
if self.searching {
282-
self.search_widget.handle_event(event);
283-
self.output_widget.highlight(
284-
self.search_widget.input.value(),
285-
self.search_widget.case_sensitive,
286-
);
287-
self.search_widget
288-
.update_highlight_info(self.output_widget.highlight_info());
284+
if self.search_widget.handle_event(event) {
285+
self.output_widget.highlight(
286+
self.search_widget.input.value(),
287+
self.search_widget.case_sensitive,
288+
self.search_widget.regex,
289+
);
290+
self.search_widget
291+
.update_highlight_info(self.output_widget.highlight_info());
292+
};
289293
} else {
290294
if self.rura_widget.handle_event(event) {
291295
match self.input_mode {

src/output_widget.rs

Lines changed: 58 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ pub struct OutputWidget {
3232
error_pane_placement: ErrorPanePlacement,
3333
visible_range_x: Range<usize>,
3434
visible_range_y: Range<usize>,
35-
highlight: String,
3635
highlight_positions: Vec<(usize, Range<usize>)>,
3736
highlight_index: usize,
3837
pub error_display_mode: ErrorDisplayMode,
@@ -55,7 +54,6 @@ impl OutputWidget {
5554
error_display_mode,
5655
output_height: 0u16,
5756
error_pane_placement,
58-
highlight: String::new(),
5957
highlight_positions: vec![],
6058
visible_range_x: 0..0,
6159
visible_range_y: 0..0,
@@ -94,57 +92,66 @@ impl OutputWidget {
9492
}
9593
}
9694

97-
pub fn highlight(&mut self, search_str: &str, case_sensitive: bool) {
98-
self.highlight = search_str.to_string();
99-
if !search_str.is_empty() {
100-
let pattern = if case_sensitive {
101-
Regex::new(&regex::escape(&search_str)).unwrap()
95+
pub fn highlight(&mut self, search_str: &str, case_sensitive: bool, regex: bool) {
96+
if search_str.is_empty() {
97+
self.highlight_positions = vec![];
98+
} else {
99+
let mut search_str = String::from(search_str);
100+
101+
if !case_sensitive {
102+
search_str = search_str.to_lowercase();
103+
}
104+
105+
let pattern_res = if regex {
106+
Regex::new(&search_str)
102107
} else {
103-
Regex::new(&regex::escape(&search_str.to_lowercase())).unwrap()
108+
Regex::new(&regex::escape(&search_str))
104109
};
105110

106-
let positions = self
107-
.output
108-
.lines
109-
.iter()
110-
.enumerate()
111-
.filter_map(|(i, line)| {
112-
let line_to_match = if case_sensitive {
113-
line
114-
} else {
115-
&line.to_lowercase()
116-
};
117-
let matches = pattern
118-
.find_iter(line_to_match)
119-
.map(|m| (i, m.start()..m.start() + search_str.len()))
120-
.collect_vec();
121-
if !matches.is_empty() {
122-
Some(matches)
123-
} else {
124-
None
125-
}
126-
})
127-
.flatten()
128-
.collect::<Vec<(usize, Range<usize>)>>();
129-
130-
// find the first match in the visible range otherwise start from the beginning
131-
match positions
132-
.iter()
133-
.find_position(|(line, _range)| line >= &self.visible_range_y.start)
134-
{
135-
Some((z, _)) => self.highlight_index = z,
136-
None => self.highlight_index = 0,
137-
}
111+
if let Ok(pattern) = pattern_res {
112+
let positions = self
113+
.output
114+
.lines
115+
.iter()
116+
.enumerate()
117+
.filter_map(|(i, line)| {
118+
let line_to_match = if case_sensitive {
119+
line
120+
} else {
121+
&line.to_lowercase()
122+
};
123+
let matches = pattern
124+
.find_iter(line_to_match)
125+
.map(|m| (i, m.start()..m.end()))
126+
.collect_vec();
127+
if !matches.is_empty() {
128+
Some(matches)
129+
} else {
130+
None
131+
}
132+
})
133+
.flatten()
134+
.collect::<Vec<(usize, Range<usize>)>>();
135+
136+
// find the first match in the visible range otherwise start from the beginning
137+
match positions
138+
.iter()
139+
.find_position(|(line, _range)| line >= &self.visible_range_y.start)
140+
{
141+
Some((z, _)) => self.highlight_index = z,
142+
None => self.highlight_index = 0,
143+
}
138144

139-
self.highlight_positions = positions;
145+
self.highlight_positions = positions;
140146

141-
// focus on the first match
142-
if !self.highlight_positions.is_empty() {
143-
let (line, range) = self.highlight_positions[self.highlight_index].clone();
144-
self.adjust_viewport_for_highlight(line, range);
147+
// focus on the first match
148+
if !self.highlight_positions.is_empty() {
149+
let (line, range) = self.highlight_positions[self.highlight_index].clone();
150+
self.adjust_viewport_for_highlight(line, range);
151+
}
152+
} else {
153+
self.highlight_positions = vec![];
145154
}
146-
} else {
147-
self.highlight_positions = vec![];
148155
}
149156
}
150157

@@ -155,7 +162,7 @@ impl OutputWidget {
155162

156163
if !self.visible_range_x.contains(&range.start) {
157164
if range.start < self.visible_range_x.len() {
158-
// scroll fully to the left if highligh is in the first "horizontal "page"
165+
// scroll fully to the left if highlight is in the first "horizontal page"
159166
self.offset.x = 0;
160167
} else {
161168
self.offset.x = range.start.saturating_sub(self.visible_range_x.len() / 4);
@@ -181,7 +188,6 @@ impl OutputWidget {
181188

182189
self.highlight_index = 0;
183190
self.highlight_positions = vec![];
184-
self.highlight = String::new();
185191
}
186192

187193
pub fn handle_event(&mut self, event: &Event) {
@@ -593,7 +599,7 @@ mod tests {
593599
.unwrap();
594600
assert_snapshot!("highlight base", terminal.backend());
595601

596-
widget.highlight("line2", false);
602+
widget.highlight("line2", false, false);
597603
terminal
598604
.draw(|frame| widget.render(frame.area(), frame.buffer_mut()))
599605
.unwrap();
@@ -624,7 +630,7 @@ mod tests {
624630
.unwrap();
625631
assert_snapshot!("highlight prev 4x", terminal.backend());
626632

627-
widget.highlight("line50", false);
633+
widget.highlight("line50", false, false);
628634
terminal
629635
.draw(|frame| widget.render(frame.area(), frame.buffer_mut()))
630636
.unwrap();
@@ -649,7 +655,7 @@ mod tests {
649655
.unwrap();
650656
assert_snapshot!("highlight horizontal base", terminal.backend());
651657

652-
widget.highlight("hl", false);
658+
widget.highlight("hl", false, false);
653659
for i in 1..6 {
654660
widget.highlight_next();
655661
terminal

src/search_widget.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use tui_input::backend::crossterm::EventHandler;
1111
pub struct SearchWidget {
1212
pub input: Input,
1313
pub case_sensitive: bool,
14+
pub regex: bool,
1415
current: usize,
1516
total: usize,
1617
}
@@ -21,17 +22,19 @@ impl Widget for &SearchWidget {
2122
Self: Sized,
2223
{
2324
let par = Paragraph::new(self.input.value()).block(Block::bordered().title(format!(
24-
" Search: {} / {} | {} ",
25+
" Search: {} / {} | {} {} ",
2526
if self.total == 0 { 0 } else { self.current + 1 },
2627
self.total,
27-
if self.case_sensitive { "[Cc]" } else { "Cc" }
28+
if self.regex { "[.*]" } else { ".*" },
29+
if self.case_sensitive { "[Cc]" } else { "Cc" },
2830
)));
2931
par.render(area, buf);
3032
}
3133
}
3234

3335
impl SearchWidget {
34-
pub fn handle_event(&mut self, event: &Event) {
36+
// returns boolean telling if the value was changed
37+
pub fn handle_event(&mut self, event: &Event) -> bool {
3538
match event {
3639
Event::Key(key_event) => {
3740
let code = key_event.code;
@@ -40,13 +43,20 @@ impl SearchWidget {
4043
match (code, mods) {
4144
(Char('c'), KeyModifiers::ALT) => {
4245
self.case_sensitive = !self.case_sensitive;
46+
true
4347
}
44-
_ => {
45-
self.input.handle_event(event);
48+
(Char('x'), KeyModifiers::ALT) => {
49+
self.regex = !self.regex;
50+
true
4651
}
52+
_ => self
53+
.input
54+
.handle_event(event)
55+
.map(|change| change.value)
56+
.unwrap_or(false),
4757
}
4858
}
49-
_ => {}
59+
_ => false,
5060
}
5161
}
5262

0 commit comments

Comments
 (0)