@@ -8,7 +8,7 @@ use ratatui::{
8
8
widgets:: { Block , Borders , Paragraph } ,
9
9
Frame ,
10
10
} ;
11
- use unicode_width :: UnicodeWidthChar ;
11
+ use regex :: RegexBuilder ;
12
12
13
13
pub enum SearchAction {
14
14
None ,
@@ -17,7 +17,7 @@ pub enum SearchAction {
17
17
}
18
18
19
19
pub struct Filter {
20
- search_input : Vec < char > ,
20
+ search_input : String ,
21
21
in_search_mode : bool ,
22
22
input_position : usize ,
23
23
items : Vec < ListEntry > ,
@@ -27,7 +27,7 @@ pub struct Filter {
27
27
impl Filter {
28
28
pub fn new ( ) -> Self {
29
29
Self {
30
- search_input : vec ! [ ] ,
30
+ search_input : String :: new ( ) ,
31
31
in_search_mode : false ,
32
32
input_position : 0 ,
33
33
items : vec ! [ ] ,
@@ -62,56 +62,56 @@ impl Filter {
62
62
. collect ( ) ;
63
63
} else {
64
64
self . items . clear ( ) ;
65
-
66
- let query_lower = self . search_input . iter ( ) . collect :: < String > ( ) . to_lowercase ( ) ;
67
- for tab in tabs. iter ( ) {
68
- let mut stack = vec ! [ tab. tree. root( ) . id( ) ] ;
69
- while let Some ( node_id) = stack. pop ( ) {
70
- let node = tab. tree . get ( node_id) . unwrap ( ) ;
71
-
72
- if node. value ( ) . name . to_lowercase ( ) . contains ( & query_lower)
73
- && !node. has_children ( )
74
- {
75
- self . items . push ( ListEntry {
76
- node : node. value ( ) . clone ( ) ,
77
- id : node. id ( ) ,
78
- has_children : false ,
79
- } ) ;
65
+ if let Ok ( regex) = self . regex_builder ( & regex:: escape ( & self . search_input ) ) {
66
+ for tab in tabs {
67
+ let mut stack = vec ! [ tab. tree. root( ) . id( ) ] ;
68
+ while let Some ( node_id) = stack. pop ( ) {
69
+ let node = tab. tree . get ( node_id) . unwrap ( ) ;
70
+ if regex. is_match ( & node. value ( ) . name ) && !node. has_children ( ) {
71
+ self . items . push ( ListEntry {
72
+ node : node. value ( ) . clone ( ) ,
73
+ id : node. id ( ) ,
74
+ has_children : false ,
75
+ } ) ;
76
+ }
77
+ stack. extend ( node. children ( ) . map ( |child| child. id ( ) ) ) ;
80
78
}
81
-
82
- stack. extend ( node. children ( ) . map ( |child| child. id ( ) ) ) ;
83
79
}
80
+ self . items
81
+ . sort_unstable_by ( |a, b| a. node . name . cmp ( & b. node . name ) ) ;
82
+ } else {
83
+ self . search_input . clear ( ) ;
84
84
}
85
- self . items . sort_by ( |a, b| a. node . name . cmp ( & b. node . name ) ) ;
86
85
}
87
-
88
86
self . update_completion_preview ( ) ;
89
87
}
90
88
91
89
fn update_completion_preview ( & mut self ) {
92
- if self . search_input . is_empty ( ) {
93
- self . completion_preview = None ;
94
- return ;
95
- }
96
-
97
- let input = self . search_input . iter ( ) . collect :: < String > ( ) . to_lowercase ( ) ;
98
- self . completion_preview = self . items . iter ( ) . find_map ( |item| {
99
- let item_name_lower = item. node . name . to_lowercase ( ) ;
100
- if item_name_lower . starts_with ( & input ) {
101
- Some ( item_name_lower [ input . len ( ) .. ] . to_string ( ) )
90
+ self . completion_preview = if self . items . is_empty ( ) || self . search_input . is_empty ( ) {
91
+ None
92
+ } else {
93
+ let pattern = format ! ( "(?i)^{}" , regex :: escape ( & self . search_input ) ) ;
94
+ if let Ok ( regex ) = self . regex_builder ( & pattern ) {
95
+ self . items . iter ( ) . find_map ( |item| {
96
+ regex
97
+ . find ( & item. node . name )
98
+ . map ( |mat| item . node . name [ mat . end ( ) .. ] . to_string ( ) )
99
+ } )
102
100
} else {
103
101
None
104
102
}
105
- } ) ;
103
+ }
106
104
}
107
105
108
106
pub fn draw_searchbar ( & self , frame : & mut Frame , area : Rect , theme : & Theme ) {
109
107
//Set the search bar text (If empty use the placeholder)
110
108
let display_text = if !self . in_search_mode && self . search_input . is_empty ( ) {
111
109
Span :: raw ( "Press / to search" )
112
110
} else {
113
- let input_text = self . search_input . iter ( ) . collect :: < String > ( ) ;
114
- Span :: styled ( input_text, Style :: default ( ) . fg ( theme. focused_color ( ) ) )
111
+ Span :: styled (
112
+ & self . search_input ,
113
+ Style :: default ( ) . fg ( theme. focused_color ( ) ) ,
114
+ )
115
115
} ;
116
116
117
117
let search_color = if self . in_search_mode {
@@ -135,25 +135,16 @@ impl Filter {
135
135
136
136
// Render cursor in search bar
137
137
if self . in_search_mode {
138
- let cursor_position: usize = self . search_input [ ..self . input_position ]
139
- . iter ( )
140
- . map ( |c| c. width ( ) . unwrap_or ( 1 ) )
141
- . sum ( ) ;
142
- let x = area. x + cursor_position as u16 + 1 ;
138
+ let x = area. x + self . input_position as u16 + 1 ;
143
139
let y = area. y + 1 ;
144
140
frame. set_cursor_position ( Position :: new ( x, y) ) ;
145
141
146
142
if let Some ( preview) = & self . completion_preview {
143
+ let preview_x = area. x + self . search_input . len ( ) as u16 + 1 ;
147
144
let preview_span =
148
145
Span :: styled ( preview, Style :: default ( ) . fg ( theme. search_preview_color ( ) ) ) ;
149
- let preview_paragraph = Paragraph :: new ( preview_span) . style ( Style :: default ( ) ) ;
150
- let preview_area = Rect :: new (
151
- x,
152
- y,
153
- ( preview. len ( ) as u16 ) . min ( area. width - cursor_position as u16 - 1 ) ,
154
- 1 ,
155
- ) ;
156
- frame. render_widget ( preview_paragraph, preview_area) ;
146
+ let preview_area = Rect :: new ( preview_x, y, preview. len ( ) as u16 , 1 ) ;
147
+ frame. render_widget ( Paragraph :: new ( preview_span) , preview_area) ;
157
148
}
158
149
}
159
150
}
@@ -220,14 +211,35 @@ impl Filter {
220
211
}
221
212
}
222
213
214
+ fn regex_builder ( & self , pattern : & str ) -> Result < regex:: Regex , regex:: Error > {
215
+ RegexBuilder :: new ( pattern) . case_insensitive ( true ) . build ( )
216
+ }
217
+
223
218
fn complete_search ( & mut self ) -> SearchAction {
224
- if let Some ( completion) = self . completion_preview . take ( ) {
225
- self . search_input . extend ( completion. chars ( ) ) ;
226
- self . input_position = self . search_input . len ( ) ;
227
- self . update_completion_preview ( ) ;
228
- SearchAction :: Update
229
- } else {
219
+ if self . completion_preview . is_none ( ) {
230
220
SearchAction :: None
221
+ } else {
222
+ let pattern = format ! ( "(?i)^{}" , self . search_input) ;
223
+ if let Ok ( regex) = self . regex_builder ( & pattern) {
224
+ self . search_input = self
225
+ . items
226
+ . iter ( )
227
+ . find_map ( |item| {
228
+ if regex. is_match ( & item. node . name ) {
229
+ Some ( item. node . name . clone ( ) )
230
+ } else {
231
+ None
232
+ }
233
+ } )
234
+ . unwrap_or_default ( ) ;
235
+
236
+ self . completion_preview = None ;
237
+ self . input_position = self . search_input . len ( ) ;
238
+
239
+ SearchAction :: Update
240
+ } else {
241
+ SearchAction :: None
242
+ }
231
243
}
232
244
}
233
245
0 commit comments