on master help sign in
sort fuzzy matches with equal score by length in picker (#4698)
commit4b89177e53a467ceda2e3ee95fc77874f77d2b85 authorPascal Kuthe <pascal.kuthe@semimod.de> dateThu 17 Nov 2022, 00:28 (4 years ago) parentfe11ae2
1 file changed+55−27
helix-term/src/ui/picker.rs+55−27
helix-term/src/ui/picker.rs+55−27
@@ -11,9 +11,8 @@ use tui::{
1111use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
1212use tui::widgets::Widget;
1313
14−use std::time::Instant;
14+use std::{cmp::Ordering, time::Instant};
1515use std::{
16− cmp::Reverse,
1716 collections::HashMap,
1817 io::Read,
1918 path::{Path, PathBuf},
@@ -309,13 +308,34 @@ impl<T: Item + 'static> Component for FilePicker<T> {
309308 }
310309}
311310
311+#[derive(PartialEq, Eq, Debug)]
312+struct PickerMatch {
313+ index: usize,
314+ score: i64,
315+ len: usize,
316+}
317+
318+impl PartialOrd for PickerMatch {
319+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
320+ Some(self.cmp(other))
321+ }
322+}
323+
324+impl Ord for PickerMatch {
325+ fn cmp(&self, other: &Self) -> Ordering {
326+ self.score
327+ .cmp(&other.score)
328+ .reverse()
329+ .then_with(|| self.len.cmp(&other.len))
330+ }
331+}
332+
312333pub struct Picker<T: Item> {
313334 options: Vec<T>,
314335 editor_data: T::Data,
315336 // filter: String,
316337 matcher: Box<Matcher>,
317− /// (index, score)
318− matches: Vec<(usize, i64)>,
338+ matches: Vec<PickerMatch>,
319339
320340 /// Current height of the completions box
321341 completion_height: u16,
@@ -361,13 +381,16 @@ impl<T: Item> Picker<T> {
361381
362382 // scoring on empty input:
363383 // TODO: just reuse score()
364− picker.matches.extend(
365− picker
366− .options
367− .iter()
368− .enumerate()
369− .map(|(index, _option)| (index, 0)),
370− );
384+ picker
385+ .matches
386+ .extend(picker.options.iter().enumerate().map(|(index, option)| {
387+ let text = option.filter_text(&picker.editor_data);
388+ PickerMatch {
389+ index,
390+ score: 0,
391+ len: text.chars().count(),
392+ }
393+ }));
371394
372395 picker
373396 }
@@ -384,32 +407,34 @@ impl<T: Item> Picker<T> {
384407 if pattern.is_empty() {
385408 // Fast path for no pattern.
386409 self.matches.clear();
387− self.matches.extend(
388− self.options
389− .iter()
390− .enumerate()
391− .map(|(index, _option)| (index, 0)),
392− );
410+ self.matches
411+ .extend(self.options.iter().enumerate().map(|(index, option)| {
412+ let text = option.filter_text(&self.editor_data);
413+ PickerMatch {
414+ index,
415+ score: 0,
416+ len: text.chars().count(),
417+ }
418+ }));
393419 } else if pattern.starts_with(&self.previous_pattern) {
394420 let query = FuzzyQuery::new(pattern);
395421 // optimization: if the pattern is a more specific version of the previous one
396422 // then we can score the filtered set.
397− self.matches.retain_mut(|(index, score)| {
398− let option = &self.options[*index];
423+ self.matches.retain_mut(|pmatch| {
424+ let option = &self.options[pmatch.index];
399425 let text = option.sort_text(&self.editor_data);
400426
401427 match query.fuzzy_match(&text, &self.matcher) {
402428 Some(s) => {
403429 // Update the score
404− *score = s;
430+ pmatch.score = s;
405431 true
406432 }
407433 None => false,
408434 }
409435 });
410436
411− self.matches
412− .sort_unstable_by_key(|(_, score)| Reverse(*score));
437+ self.matches.sort_unstable();
413438 } else {
414439 let query = FuzzyQuery::new(pattern);
415440 self.matches.clear();
@@ -422,11 +447,14 @@ impl<T: Item> Picker<T> {
422447
423448 query
424449 .fuzzy_match(&text, &self.matcher)
425− .map(|score| (index, score))
450+ .map(|score| PickerMatch {
451+ index,
452+ score,
453+ len: text.chars().count(),
454+ })
426455 }),
427456 );
428− self.matches
429− .sort_unstable_by_key(|(_, score)| Reverse(*score));
457+ self.matches.sort_unstable();
430458 }
431459
432460 log::debug!("picker score {:?}", Instant::now().duration_since(now));
@@ -478,7 +506,7 @@ impl<T: Item> Picker<T> {
478506 pub fn selection(&self) -> Option<&T> {
479507 self.matches
480508 .get(self.cursor)
481− .map(|(index, _score)| &self.options[*index])
509+ .map(|pmatch| &self.options[pmatch.index])
482510 }
483511
484512 pub fn toggle_preview(&mut self) {
@@ -625,7 +653,7 @@ impl<T: Item + 'static> Component for Picker<T> {
625653 .matches
626654 .iter()
627655 .skip(offset)
628− .map(|(index, _score)| (*index, self.options.get(*index).unwrap()));
656+ .map(|pmatch| (pmatch.index, self.options.get(pmatch.index).unwrap()));
629657
630658 for (i, (_index, option)) in files.take(rows as usize).enumerate() {
631659 let is_active = i == (self.cursor - offset);