diff options
Diffstat (limited to 'helix-term/src/ui/picker.rs')
-rw-r--r-- | helix-term/src/ui/picker.rs | 106 |
1 files changed, 81 insertions, 25 deletions
diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 06e50f51..5d88622c 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -13,8 +13,10 @@ use fuzzy_matcher::skim::SkimMatcherV2 as Matcher; use fuzzy_matcher::FuzzyMatcher; use tui::widgets::Widget; +use std::time::Instant; use std::{ borrow::Cow, + cmp::Reverse, collections::HashMap, io::Read, path::{Path, PathBuf}, @@ -286,7 +288,8 @@ pub struct Picker<T> { cursor: usize, // pattern: String, prompt: Prompt, - /// Wheather to truncate the start (default true) + previous_pattern: String, + /// Whether to truncate the start (default true) pub truncate_start: bool, format_fn: Box<dyn Fn(&T) -> Cow<str>>, @@ -303,9 +306,7 @@ impl<T> Picker<T> { "".into(), None, ui::completers::none, - |_editor: &mut Context, _pattern: &str, _event: PromptEvent| { - // - }, + |_editor: &mut Context, _pattern: &str, _event: PromptEvent| {}, ); let mut picker = Self { @@ -315,44 +316,99 @@ impl<T> Picker<T> { filters: Vec::new(), cursor: 0, prompt, + previous_pattern: String::new(), truncate_start: true, format_fn: Box::new(format_fn), callback_fn: Box::new(callback_fn), completion_height: 0, }; - // TODO: scoring on empty input should just use a fastpath - picker.score(); + // scoring on empty input: + // TODO: just reuse score() + picker.matches.extend( + picker + .options + .iter() + .enumerate() + .map(|(index, _option)| (index, 0)), + ); picker } pub fn score(&mut self) { + let now = Instant::now(); + let pattern = &self.prompt.line; - // reuse the matches allocation - self.matches.clear(); - self.matches.extend( - self.options - .iter() - .enumerate() - .filter_map(|(index, option)| { - // filter options first before matching - if !self.filters.is_empty() { - self.filters.binary_search(&index).ok()?; + if pattern == &self.previous_pattern { + return; + } + + if pattern.is_empty() { + // Fast path for no pattern. + self.matches.clear(); + self.matches.extend( + self.options + .iter() + .enumerate() + .map(|(index, _option)| (index, 0)), + ); + } else if pattern.starts_with(&self.previous_pattern) { + // TODO: remove when retain_mut is in stable rust + use retain_mut::RetainMut; + + // optimization: if the pattern is a more specific version of the previous one + // then we can score the filtered set. + #[allow(unstable_name_collisions)] + self.matches.retain_mut(|(index, score)| { + let option = &self.options[*index]; + // TODO: maybe using format_fn isn't the best idea here + let text = (self.format_fn)(option); + + match self.matcher.fuzzy_match(&text, pattern) { + Some(s) => { + // Update the score + *score = s; + true } - // TODO: maybe using format_fn isn't the best idea here - let text = (self.format_fn)(option); - // Highlight indices are computed lazily in the render function - self.matcher - .fuzzy_match(&text, pattern) - .map(|score| (index, score)) - }), - ); - self.matches.sort_unstable_by_key(|(_, score)| -score); + None => false, + } + }); + + self.matches + .sort_unstable_by_key(|(_, score)| Reverse(*score)); + } else { + self.matches.clear(); + self.matches.extend( + self.options + .iter() + .enumerate() + .filter_map(|(index, option)| { + // filter options first before matching + if !self.filters.is_empty() { + // TODO: this filters functionality seems inefficient, + // instead store and operate on filters if any + self.filters.binary_search(&index).ok()?; + } + + // TODO: maybe using format_fn isn't the best idea here + let text = (self.format_fn)(option); + + self.matcher + .fuzzy_match(&text, pattern) + .map(|score| (index, score)) + }), + ); + self.matches + .sort_unstable_by_key(|(_, score)| Reverse(*score)); + } + + log::debug!("picker score {:?}", Instant::now().duration_since(now)); // reset cursor position self.cursor = 0; + self.previous_pattern.clone_from(pattern); } /// Move the cursor by a number of lines, either down (`Forward`) or up (`Backward`) |