@@ -13,8 +13,10 @@ use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
13
13
use fuzzy_matcher:: FuzzyMatcher ;
14
14
use tui:: widgets:: Widget ;
15
15
16
+ use std:: time:: Instant ;
16
17
use std:: {
17
18
borrow:: Cow ,
19
+ cmp:: Reverse ,
18
20
collections:: HashMap ,
19
21
io:: Read ,
20
22
path:: { Path , PathBuf } ,
@@ -286,7 +288,8 @@ pub struct Picker<T> {
286
288
cursor : usize ,
287
289
// pattern: String,
288
290
prompt : Prompt ,
289
- /// Wheather to truncate the start (default true)
291
+ previous_pattern : String ,
292
+ /// Whether to truncate the start (default true)
290
293
pub truncate_start : bool ,
291
294
292
295
format_fn : Box < dyn Fn ( & T ) -> Cow < str > > ,
@@ -303,9 +306,7 @@ impl<T> Picker<T> {
303
306
"" . into ( ) ,
304
307
None ,
305
308
ui:: completers:: none,
306
- |_editor : & mut Context , _pattern : & str , _event : PromptEvent | {
307
- //
308
- } ,
309
+ |_editor : & mut Context , _pattern : & str , _event : PromptEvent | { } ,
309
310
) ;
310
311
311
312
let mut picker = Self {
@@ -315,44 +316,99 @@ impl<T> Picker<T> {
315
316
filters : Vec :: new ( ) ,
316
317
cursor : 0 ,
317
318
prompt,
319
+ previous_pattern : String :: new ( ) ,
318
320
truncate_start : true ,
319
321
format_fn : Box :: new ( format_fn) ,
320
322
callback_fn : Box :: new ( callback_fn) ,
321
323
completion_height : 0 ,
322
324
} ;
323
325
324
- // TODO: scoring on empty input should just use a fastpath
325
- picker. score ( ) ;
326
+ // scoring on empty input:
327
+ // TODO: just reuse score()
328
+ picker. matches . extend (
329
+ picker
330
+ . options
331
+ . iter ( )
332
+ . enumerate ( )
333
+ . map ( |( index, _option) | ( index, 0 ) ) ,
334
+ ) ;
326
335
327
336
picker
328
337
}
329
338
330
339
pub fn score ( & mut self ) {
340
+ let now = Instant :: now ( ) ;
341
+
331
342
let pattern = & self . prompt . line ;
332
343
333
- // reuse the matches allocation
334
- self . matches . clear ( ) ;
335
- self . matches . extend (
336
- self . options
337
- . iter ( )
338
- . enumerate ( )
339
- . filter_map ( |( index, option) | {
340
- // filter options first before matching
341
- if !self . filters . is_empty ( ) {
342
- self . filters . binary_search ( & index) . ok ( ) ?;
344
+ if pattern == & self . previous_pattern {
345
+ return ;
346
+ }
347
+
348
+ if pattern. is_empty ( ) {
349
+ // Fast path for no pattern.
350
+ self . matches . clear ( ) ;
351
+ self . matches . extend (
352
+ self . options
353
+ . iter ( )
354
+ . enumerate ( )
355
+ . map ( |( index, _option) | ( index, 0 ) ) ,
356
+ ) ;
357
+ } else if pattern. starts_with ( & self . previous_pattern ) {
358
+ // TODO: remove when retain_mut is in stable rust
359
+ use retain_mut:: RetainMut ;
360
+
361
+ // optimization: if the pattern is a more specific version of the previous one
362
+ // then we can score the filtered set.
363
+ #[ allow( unstable_name_collisions) ]
364
+ self . matches . retain_mut ( |( index, score) | {
365
+ let option = & self . options [ * index] ;
366
+ // TODO: maybe using format_fn isn't the best idea here
367
+ let text = ( self . format_fn ) ( option) ;
368
+
369
+ match self . matcher . fuzzy_match ( & text, pattern) {
370
+ Some ( s) => {
371
+ // Update the score
372
+ * score = s;
373
+ true
343
374
}
344
- // TODO: maybe using format_fn isn't the best idea here
345
- let text = ( self . format_fn ) ( option) ;
346
- // Highlight indices are computed lazily in the render function
347
- self . matcher
348
- . fuzzy_match ( & text, pattern)
349
- . map ( |score| ( index, score) )
350
- } ) ,
351
- ) ;
352
- self . matches . sort_unstable_by_key ( |( _, score) | -score) ;
375
+ None => false ,
376
+ }
377
+ } ) ;
378
+
379
+ self . matches
380
+ . sort_unstable_by_key ( |( _, score) | Reverse ( * score) ) ;
381
+ } else {
382
+ self . matches . clear ( ) ;
383
+ self . matches . extend (
384
+ self . options
385
+ . iter ( )
386
+ . enumerate ( )
387
+ . filter_map ( |( index, option) | {
388
+ // filter options first before matching
389
+ if !self . filters . is_empty ( ) {
390
+ // TODO: this filters functionality seems inefficient,
391
+ // instead store and operate on filters if any
392
+ self . filters . binary_search ( & index) . ok ( ) ?;
393
+ }
394
+
395
+ // TODO: maybe using format_fn isn't the best idea here
396
+ let text = ( self . format_fn ) ( option) ;
397
+
398
+ self . matcher
399
+ . fuzzy_match ( & text, pattern)
400
+ . map ( |score| ( index, score) )
401
+ } ) ,
402
+ ) ;
403
+ self . matches
404
+ . sort_unstable_by_key ( |( _, score) | Reverse ( * score) ) ;
405
+ }
406
+
407
+ log:: debug!( "picker score {:?}" , Instant :: now( ) . duration_since( now) ) ;
353
408
354
409
// reset cursor position
355
410
self . cursor = 0 ;
411
+ self . previous_pattern . clone_from ( pattern) ;
356
412
}
357
413
358
414
/// Move the cursor by a number of lines, either down (`Forward`) or up (`Backward`)
0 commit comments