@@ -13,8 +13,10 @@ use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
13use fuzzy_matcher::FuzzyMatcher;
13use fuzzy_matcher::FuzzyMatcher;
14use tui::widgets::Widget;
14use tui::widgets::Widget;
16+use std::time::Instant;
17 borrow::Cow,
18 borrow::Cow,
18 collections::HashMap,
20 collections::HashMap,
20 path::{Path, PathBuf},
22 path::{Path, PathBuf},
@@ -286,7 +288,8 @@ pub struct Picker<T> {
286 cursor: usize,
288 cursor: usize,
287 // pattern: String,
289 // pattern: String,
288 prompt: Prompt,
290 prompt: Prompt,
291+ previous_pattern: String,
289− /// Wheather to truncate the start (default true)
292+ /// Whether to truncate the start (default true)
290 pub truncate_start: bool,
293 pub truncate_start: bool,
292 format_fn: Box<dyn Fn(&T) -> Cow<str>>,
295 format_fn: Box<dyn Fn(&T) -> Cow<str>>,
@@ -303,9 +306,7 @@ impl<T> Picker<T> {
303 "".into(),
306 "".into(),
305 ui::completers::none,
308 ui::completers::none,
306− |_editor: &mut Context, _pattern: &str, _event: PromptEvent| {
309+ |_editor: &mut Context, _pattern: &str, _event: PromptEvent| {},
311 let mut picker = Self {
312 let mut picker = Self {
@@ -315,44 +316,99 @@ impl<T> Picker<T> {
315 filters: Vec::new(),
316 filters: Vec::new(),
316 cursor: 0,
317 cursor: 0,
319+ previous_pattern: String::new(),
318 truncate_start: true,
320 truncate_start: true,
319 format_fn: Box::new(format_fn),
321 format_fn: Box::new(format_fn),
320 callback_fn: Box::new(callback_fn),
322 callback_fn: Box::new(callback_fn),
321 completion_height: 0,
323 completion_height: 0,
324− // TODO: scoring on empty input should just use a fastpath
326+ // scoring on empty input:
325− picker.score();
327+ // TODO: just reuse score()
328+ picker.matches.extend(
333+ .map(|(index, _option)| (index, 0)),
330 pub fn score(&mut self) {
339 pub fn score(&mut self) {
340+ let now = Instant::now();
331 let pattern = &self.prompt.line;
342 let pattern = &self.prompt.line;
333− // reuse the matches allocation
344+ if pattern == &self.previous_pattern {
348+ if pattern.is_empty() {
349+ // Fast path for no pattern.
334− self.matches.clear();
350+ self.matches.clear();
335− self.matches.extend(
351+ self.matches.extend(
336− self.options
352+ self.options
338− .enumerate()
354+ .enumerate()
339− .filter_map(|(index, option)| {
355+ .map(|(index, _option)| (index, 0)),
340− // filter options first before matching
356+ );
341− if !self.filters.is_empty() {
357+ } else if pattern.starts_with(&self.previous_pattern) {
342− self.filters.binary_search(&index).ok()?;
358+ // TODO: remove when retain_mut is in stable rust
359+ use retain_mut::RetainMut;
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);
369+ match self.matcher.fuzzy_match(&text, pattern) {
380+ .sort_unstable_by_key(|(_, score)| Reverse(*score));
382+ self.matches.clear();
383+ self.matches.extend(
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()?;
344− // TODO: maybe using format_fn isn't the best idea here
395+ // TODO: maybe using format_fn isn't the best idea here
345− let text = (self.format_fn)(option);
396+ let text = (self.format_fn)(option);
346− // Highlight indices are computed lazily in the render function
397+
347− self.matcher
398+ self.matcher
348− .fuzzy_match(&text, pattern)
399+ .fuzzy_match(&text, pattern)
349− .map(|score| (index, score))
400+ .map(|score| (index, score))
352− self.matches.sort_unstable_by_key(|(_, score)| -score);
404+ .sort_unstable_by_key(|(_, score)| Reverse(*score));
407+ log::debug!("picker score {:?}", Instant::now().duration_since(now));
354 // reset cursor position
409 // reset cursor position
355 self.cursor = 0;
410 self.cursor = 0;
411+ self.previous_pattern.clone_from(pattern);
358 /// Move the cursor by a number of lines, either down (`Forward`) or up (`Backward`)
414 /// Move the cursor by a number of lines, either down (`Forward`) or up (`Backward`)