sort fuzzy matches with equal score by length in picker (#4698)

pull/4578/head^2
Pascal Kuthe 2 years ago committed by GitHub
parent fe11ae2218
commit 4b89177e53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -11,9 +11,8 @@ use tui::{
use fuzzy_matcher::skim::SkimMatcherV2 as Matcher; use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
use tui::widgets::Widget; use tui::widgets::Widget;
use std::time::Instant; use std::{cmp::Ordering, time::Instant};
use std::{ use std::{
cmp::Reverse,
collections::HashMap, collections::HashMap,
io::Read, io::Read,
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -309,13 +308,34 @@ impl<T: Item + 'static> Component for FilePicker<T> {
} }
} }
#[derive(PartialEq, Eq, Debug)]
struct PickerMatch {
index: usize,
score: i64,
len: usize,
}
impl PartialOrd for PickerMatch {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PickerMatch {
fn cmp(&self, other: &Self) -> Ordering {
self.score
.cmp(&other.score)
.reverse()
.then_with(|| self.len.cmp(&other.len))
}
}
pub struct Picker<T: Item> { pub struct Picker<T: Item> {
options: Vec<T>, options: Vec<T>,
editor_data: T::Data, editor_data: T::Data,
// filter: String, // filter: String,
matcher: Box<Matcher>, matcher: Box<Matcher>,
/// (index, score) matches: Vec<PickerMatch>,
matches: Vec<(usize, i64)>,
/// Current height of the completions box /// Current height of the completions box
completion_height: u16, completion_height: u16,
@ -361,13 +381,16 @@ impl<T: Item> Picker<T> {
// scoring on empty input: // scoring on empty input:
// TODO: just reuse score() // TODO: just reuse score()
picker.matches.extend( picker
picker .matches
.options .extend(picker.options.iter().enumerate().map(|(index, option)| {
.iter() let text = option.filter_text(&picker.editor_data);
.enumerate() PickerMatch {
.map(|(index, _option)| (index, 0)), index,
); score: 0,
len: text.chars().count(),
}
}));
picker picker
} }
@ -384,32 +407,34 @@ impl<T: Item> Picker<T> {
if pattern.is_empty() { if pattern.is_empty() {
// Fast path for no pattern. // Fast path for no pattern.
self.matches.clear(); self.matches.clear();
self.matches.extend( self.matches
self.options .extend(self.options.iter().enumerate().map(|(index, option)| {
.iter() let text = option.filter_text(&self.editor_data);
.enumerate() PickerMatch {
.map(|(index, _option)| (index, 0)), index,
); score: 0,
len: text.chars().count(),
}
}));
} else if pattern.starts_with(&self.previous_pattern) { } else if pattern.starts_with(&self.previous_pattern) {
let query = FuzzyQuery::new(pattern); let query = FuzzyQuery::new(pattern);
// optimization: if the pattern is a more specific version of the previous one // optimization: if the pattern is a more specific version of the previous one
// then we can score the filtered set. // then we can score the filtered set.
self.matches.retain_mut(|(index, score)| { self.matches.retain_mut(|pmatch| {
let option = &self.options[*index]; let option = &self.options[pmatch.index];
let text = option.sort_text(&self.editor_data); let text = option.sort_text(&self.editor_data);
match query.fuzzy_match(&text, &self.matcher) { match query.fuzzy_match(&text, &self.matcher) {
Some(s) => { Some(s) => {
// Update the score // Update the score
*score = s; pmatch.score = s;
true true
} }
None => false, None => false,
} }
}); });
self.matches self.matches.sort_unstable();
.sort_unstable_by_key(|(_, score)| Reverse(*score));
} else { } else {
let query = FuzzyQuery::new(pattern); let query = FuzzyQuery::new(pattern);
self.matches.clear(); self.matches.clear();
@ -422,11 +447,14 @@ impl<T: Item> Picker<T> {
query query
.fuzzy_match(&text, &self.matcher) .fuzzy_match(&text, &self.matcher)
.map(|score| (index, score)) .map(|score| PickerMatch {
index,
score,
len: text.chars().count(),
})
}), }),
); );
self.matches self.matches.sort_unstable();
.sort_unstable_by_key(|(_, score)| Reverse(*score));
} }
log::debug!("picker score {:?}", Instant::now().duration_since(now)); log::debug!("picker score {:?}", Instant::now().duration_since(now));
@ -478,7 +506,7 @@ impl<T: Item> Picker<T> {
pub fn selection(&self) -> Option<&T> { pub fn selection(&self) -> Option<&T> {
self.matches self.matches
.get(self.cursor) .get(self.cursor)
.map(|(index, _score)| &self.options[*index]) .map(|pmatch| &self.options[pmatch.index])
} }
pub fn toggle_preview(&mut self) { pub fn toggle_preview(&mut self) {
@ -625,7 +653,7 @@ impl<T: Item + 'static> Component for Picker<T> {
.matches .matches
.iter() .iter()
.skip(offset) .skip(offset)
.map(|(index, _score)| (*index, self.options.get(*index).unwrap())); .map(|pmatch| (pmatch.index, self.options.get(pmatch.index).unwrap()));
for (i, (_index, option)) in files.take(rows as usize).enumerate() { for (i, (_index, option)) in files.take(rows as usize).enumerate() {
let is_active = i == (self.cursor - offset); let is_active = i == (self.cursor - offset);

Loading…
Cancel
Save