@@ -13,8 +13,10 @@ use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
1313use fuzzy_matcher::FuzzyMatcher;
1414use tui::widgets::Widget;
1515
16+use std::time::Instant;
1617use std::{
1718 borrow::Cow,
19+ cmp::Reverse,
1820 collections::HashMap,
1921 io::Read,
2022 path::{Path, PathBuf},
@@ -286,7 +288,8 @@ pub struct Picker<T> {
286288 cursor: usize,
287289 // pattern: String,
288290 prompt: Prompt,
289− /// Wheather to truncate the start (default true)
291+ previous_pattern: String,
292+ /// Whether to truncate the start (default true)
290293 pub truncate_start: bool,
291294
292295 format_fn: Box<dyn Fn(&T) -> Cow<str>>,
@@ -303,9 +306,7 @@ impl<T> Picker<T> {
303306 "".into(),
304307 None,
305308 ui::completers::none,
306− |_editor: &mut Context, _pattern: &str, _event: PromptEvent| {
307− //
308− },
309+ |_editor: &mut Context, _pattern: &str, _event: PromptEvent| {},
309310 );
310311
311312 let mut picker = Self {
@@ -315,44 +316,99 @@ impl<T> Picker<T> {
315316 filters: Vec::new(),
316317 cursor: 0,
317318 prompt,
319+ previous_pattern: String::new(),
318320 truncate_start: true,
319321 format_fn: Box::new(format_fn),
320322 callback_fn: Box::new(callback_fn),
321323 completion_height: 0,
322324 };
323325
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+ );
326335
327336 picker
328337 }
329338
330339 pub fn score(&mut self) {
340+ let now = Instant::now();
341+
331342 let pattern = &self.prompt.line;
332343
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
343374 }
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));
353408
354409 // reset cursor position
355410 self.cursor = 0;
411+ self.previous_pattern.clone_from(pattern);
356412 }
357413
358414 /// Move the cursor by a number of lines, either down (`Forward`) or up (`Backward`)