aboutsummaryrefslogtreecommitdiff
path: root/helix-term/src/ui/picker.rs
diff options
context:
space:
mode:
Diffstat (limited to 'helix-term/src/ui/picker.rs')
-rw-r--r--helix-term/src/ui/picker.rs106
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`)