@@ -11,9 +11,8 @@ use tui::{
11use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
11use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
12use tui::widgets::Widget;
12use tui::widgets::Widget;
14−use std::time::Instant;
14+use std::{cmp::Ordering, time::Instant};
17 collections::HashMap,
16 collections::HashMap,
19 path::{Path, PathBuf},
18 path::{Path, PathBuf},
@@ -309,13 +308,34 @@ impl<T: Item + 'static> Component for FilePicker<T> {
311+#[derive(PartialEq, Eq, Debug)]
318+impl PartialOrd for PickerMatch {
319+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
320+ Some(self.cmp(other))
324+impl Ord for PickerMatch {
325+ fn cmp(&self, other: &Self) -> Ordering {
329+ .then_with(|| self.len.cmp(&other.len))
312pub struct Picker<T: Item> {
333pub struct Picker<T: Item> {
313 options: Vec<T>,
334 options: Vec<T>,
314 editor_data: T::Data,
335 editor_data: T::Data,
315 // filter: String,
336 // filter: String,
316 matcher: Box<Matcher>,
337 matcher: Box<Matcher>,
318− matches: Vec<(usize, i64)>,
338+ matches: Vec<PickerMatch>,
320 /// Current height of the completions box
340 /// Current height of the completions box
321 completion_height: u16,
341 completion_height: u16,
@@ -361,13 +381,16 @@ impl<T: Item> Picker<T> {
362 // scoring on empty input:
382 // scoring on empty input:
363 // TODO: just reuse score()
383 // TODO: just reuse score()
364− picker.matches.extend(
385+ .matches
365− picker
386+ .extend(picker.options.iter().enumerate().map(|(index, option)| {
366− .options
387+ let text = option.filter_text(&picker.editor_data);
367− .iter()
388+ PickerMatch {
368− .enumerate()
389+ index,
369− .map(|(index, _option)| (index, 0)),
390+ score: 0,
391+ len: text.chars().count(),
@@ -384,32 +407,34 @@ impl<T: Item> Picker<T> {
384 if pattern.is_empty() {
407 if pattern.is_empty() {
385 // Fast path for no pattern.
408 // Fast path for no pattern.
386 self.matches.clear();
409 self.matches.clear();
387− self.matches.extend(
410+ self.matches
388− self.options
411+ .extend(self.options.iter().enumerate().map(|(index, option)| {
389− .iter()
412+ let text = option.filter_text(&self.editor_data);
390− .enumerate()
413+ PickerMatch {
391− .map(|(index, _option)| (index, 0)),
414+ index,
416+ len: text.chars().count(),
393 } else if pattern.starts_with(&self.previous_pattern) {
419 } else if pattern.starts_with(&self.previous_pattern) {
394 let query = FuzzyQuery::new(pattern);
420 let query = FuzzyQuery::new(pattern);
395 // optimization: if the pattern is a more specific version of the previous one
421 // optimization: if the pattern is a more specific version of the previous one
396 // then we can score the filtered set.
422 // then we can score the filtered set.
397− self.matches.retain_mut(|(index, score)| {
423+ self.matches.retain_mut(|pmatch| {
398− let option = &self.options[*index];
424+ let option = &self.options[pmatch.index];
399 let text = option.sort_text(&self.editor_data);
425 let text = option.sort_text(&self.editor_data);
401 match query.fuzzy_match(&text, &self.matcher) {
427 match query.fuzzy_match(&text, &self.matcher) {
402 Some(s) => {
428 Some(s) => {
403 // Update the score
429 // Update the score
404− *score = s;
430+ pmatch.score = s;
407 None => false,
433 None => false,
411− self.matches
437+ self.matches.sort_unstable();
412− .sort_unstable_by_key(|(_, score)| Reverse(*score));
414 let query = FuzzyQuery::new(pattern);
439 let query = FuzzyQuery::new(pattern);
415 self.matches.clear();
440 self.matches.clear();
@@ -422,11 +447,14 @@ impl<T: Item> Picker<T> {
424 .fuzzy_match(&text, &self.matcher)
449 .fuzzy_match(&text, &self.matcher)
425− .map(|score| (index, score))
450+ .map(|score| PickerMatch {
453+ len: text.chars().count(),
428− self.matches
457+ self.matches.sort_unstable();
429− .sort_unstable_by_key(|(_, score)| Reverse(*score));
432 log::debug!("picker score {:?}", Instant::now().duration_since(now));
460 log::debug!("picker score {:?}", Instant::now().duration_since(now));
@@ -478,7 +506,7 @@ impl<T: Item> Picker<T> {
478 pub fn selection(&self) -> Option<&T> {
506 pub fn selection(&self) -> Option<&T> {
479 self.matches
507 self.matches
480 .get(self.cursor)
508 .get(self.cursor)
481− .map(|(index, _score)| &self.options[*index])
509+ .map(|pmatch| &self.options[pmatch.index])
484 pub fn toggle_preview(&mut self) {
512 pub fn toggle_preview(&mut self) {
@@ -625,7 +653,7 @@ impl<T: Item + 'static> Component for Picker<T> {
627 .skip(offset)
655 .skip(offset)
628− .map(|(index, _score)| (*index, self.options.get(*index).unwrap()));
656+ .map(|pmatch| (pmatch.index, self.options.get(pmatch.index).unwrap()));
630 for (i, (_index, option)) in files.take(rows as usize).enumerate() {
658 for (i, (_index, option)) in files.take(rows as usize).enumerate() {
631 let is_active = i == (self.cursor - offset);
659 let is_active = i == (self.cursor - offset);