@@ -34,15 +34,21 @@ pub struct GlobPane {
3434 /// and is treated as 'one character'. If not, it will be off, which isn't the end of the world.
3535 // TODO: use `tui-textarea` for proper cursor handling, needs native crossterm events.
3636 cursor_grapheme_idx : usize ,
37+ /// Whether the glob search should be case-sensitive
38+ pub case_sensitive : bool ,
3739}
3840
3941impl GlobPane {
4042 pub fn process_events ( & mut self , key : Key ) {
4143 use crosstermion:: crossterm:: event:: KeyCode :: * ;
44+ use crosstermion:: crossterm:: event:: KeyModifiers ;
4245 if key. kind == KeyEventKind :: Release {
4346 return ;
4447 }
4548 match key. code {
49+ Char ( 'i' ) if key. modifiers . contains ( KeyModifiers :: CONTROL ) => {
50+ self . case_sensitive = !self . case_sensitive ;
51+ }
4652 Char ( to_insert) => {
4753 self . enter_char ( to_insert) ;
4854 }
@@ -113,7 +119,11 @@ impl GlobPane {
113119 has_focus,
114120 } = props. borrow ( ) ;
115121
116- let title = "Git-Glob" ;
122+ let title = if self . case_sensitive {
123+ "Git-Glob (case-sensitive)"
124+ } else {
125+ "Git-Glob (case-insensitive)"
126+ } ;
117127 let block = Block :: default ( )
118128 . title ( title)
119129 . border_style ( * border_style)
@@ -146,7 +156,7 @@ impl GlobPane {
146156}
147157
148158fn draw_top_right_help ( area : Rect , title : & str , buf : & mut Buffer ) -> Rect {
149- let help_text = " search = enter | cancel = esc " ;
159+ let help_text = " search = enter | case = ^I | cancel = esc " ;
150160 let help_text_block_width = block_width ( help_text) ;
151161 let bound = Rect {
152162 width : area. width . saturating_sub ( 1 ) ,
@@ -178,6 +188,7 @@ fn glob_search_neighbours(
178188 root_index : TreeIndex ,
179189 glob : & gix_glob:: Pattern ,
180190 path : & mut BString ,
191+ case_sensitive : bool ,
181192) {
182193 for node_index in tree. neighbors_directed ( root_index, Direction :: Outgoing ) {
183194 if let Some ( node) = tree. node_weight ( node_index) {
@@ -189,27 +200,79 @@ fn glob_search_neighbours(
189200 Some ( previous_len + 1 )
190201 } ;
191202 path. extend_from_slice ( gix_path:: into_bstr ( & node. name ) . as_ref ( ) ) ;
203+ let case_mode = if case_sensitive {
204+ gix_glob:: pattern:: Case :: Sensitive
205+ } else {
206+ gix_glob:: pattern:: Case :: Fold
207+ } ;
192208 if glob. matches_repo_relative_path (
193209 path. as_ref ( ) ,
194210 basename_start,
195211 Some ( node. is_dir ) ,
196- gix_glob :: pattern :: Case :: Fold ,
212+ case_mode ,
197213 gix_glob:: wildmatch:: Mode :: NO_MATCH_SLASH_LITERAL ,
198214 ) {
199215 results. push ( node_index) ;
200216 } else {
201- glob_search_neighbours ( results, tree, node_index, glob, path) ;
217+ glob_search_neighbours ( results, tree, node_index, glob, path, case_sensitive ) ;
202218 }
203219 path. truncate ( previous_len) ;
204220 }
205221 }
206222}
207223
208- pub fn glob_search ( tree : & Tree , root_index : TreeIndex , glob : & str ) -> Result < Vec < TreeIndex > > {
224+ pub fn glob_search ( tree : & Tree , root_index : TreeIndex , glob : & str , case_sensitive : bool ) -> Result < Vec < TreeIndex > > {
209225 let glob = gix_glob:: Pattern :: from_bytes_without_negation ( glob. as_bytes ( ) )
210226 . with_context ( || anyhow ! ( "Glob was empty or only whitespace" ) ) ?;
211227 let mut results = Vec :: new ( ) ;
212228 let mut path = Default :: default ( ) ;
213- glob_search_neighbours ( & mut results, tree, root_index, & glob, & mut path) ;
229+ glob_search_neighbours ( & mut results, tree, root_index, & glob, & mut path, case_sensitive ) ;
214230 Ok ( results)
215231}
232+
233+ #[ cfg( test) ]
234+ mod tests {
235+ use super :: * ;
236+ use crosstermion:: crossterm:: event:: { KeyCode , KeyEventKind , KeyModifiers } ;
237+
238+ #[ test]
239+ fn test_i_key_types_into_input ( ) {
240+ let mut glob_pane = GlobPane :: default ( ) ;
241+ assert_eq ! ( glob_pane. input, "" ) ;
242+ assert ! ( !glob_pane. case_sensitive) ; // default is case-insensitive
243+
244+ // Test that typing 'I' adds it to the input
245+ let key_i = Key {
246+ code : KeyCode :: Char ( 'I' ) ,
247+ modifiers : KeyModifiers :: empty ( ) ,
248+ kind : KeyEventKind :: Press ,
249+ state : crosstermion:: crossterm:: event:: KeyEventState :: empty ( ) ,
250+ } ;
251+ glob_pane. process_events ( key_i) ;
252+
253+ assert_eq ! ( glob_pane. input, "I" ) ;
254+ assert ! ( !glob_pane. case_sensitive) ; // should remain unchanged
255+ }
256+
257+ #[ test]
258+ fn test_ctrl_i_toggles_case_sensitivity ( ) {
259+ let mut glob_pane = GlobPane :: default ( ) ;
260+ assert ! ( !glob_pane. case_sensitive) ; // default is case-insensitive
261+
262+ // Test that Ctrl+I toggles case sensitivity
263+ let key_ctrl_i = Key {
264+ code : KeyCode :: Char ( 'i' ) ,
265+ modifiers : KeyModifiers :: CONTROL ,
266+ kind : KeyEventKind :: Press ,
267+ state : crosstermion:: crossterm:: event:: KeyEventState :: empty ( ) ,
268+ } ;
269+ glob_pane. process_events ( key_ctrl_i) ;
270+
271+ assert_eq ! ( glob_pane. input, "" ) ; // input should remain empty
272+ assert ! ( glob_pane. case_sensitive) ; // should toggle to case-sensitive
273+
274+ // Test toggling back
275+ glob_pane. process_events ( key_ctrl_i) ;
276+ assert ! ( ! glob_pane. case_sensitive) ; // should toggle back to case-insensitive
277+ }
278+ }
0 commit comments