feat: reuse fuzzy search in the file picker

This commit is contained in:
Valdemar Lorenzen
2026-10-16 10:54:27 +02:00
parent d1048df345
commit e37fa509c9
4 changed files with 128 additions and 74 deletions
+16 -72
View File
@@ -1,5 +1,5 @@
use crate::editor::{BackspaceKind, Input};
use crate::files::{FilePicker, FilePresentation, FileView};
use crate::files::{FilePicker, FilePresentation, FileView, fuzzy_match};
use std::{
collections::{BTreeMap, BTreeSet},
path::{Path, PathBuf},
@@ -963,18 +963,16 @@ impl DiffState {
.iter()
.enumerate()
.filter_map(|(field_index, field)| {
revision_match(field, token).map(
|[kind, boundaries, gaps, start, len]| {
[
kind,
usize::from(field_index == 2),
boundaries,
gaps,
start,
len,
]
},
)
fuzzy_match(field, token).map(|[kind, boundaries, gaps, start, len]| {
[
kind,
usize::from(field_index == 2),
boundaries,
gaps,
start,
len,
]
})
})
.min()?;
// A scattered token never outranks an all-contiguous match.
@@ -2168,60 +2166,6 @@ impl DiffState {
}
}
// Match class, missed word/reference boundaries, gaps, start, field length.
fn revision_match(field: &str, token: &str) -> Option<[usize; 5]> {
if field == token {
return Some([0, 0, 0, 0, field.len()]);
}
if field.starts_with(token) {
return Some([1, 0, 0, 0, field.len()]);
}
let contiguous = field
.match_indices(token)
.map(|(start, _)| {
let boundary = field[..start]
.chars()
.next_back()
.is_none_or(|ch| !ch.is_alphanumeric());
[2, usize::from(!boundary), 0, start, field.len()]
})
.min();
if contiguous.is_some() {
return contiguous;
}
// ponytail: greedy subsequences can miss tighter later alignments; use dynamic
// programming if that ranking precision becomes necessary.
let mut wanted = token.chars();
let mut next = wanted.next()?;
let mut start = 0;
let mut matched = 0;
let mut boundaries = 0;
let mut previous_is_word = false;
for (position, ch) in field.chars().enumerate() {
if ch == next {
if matched == 0 {
start = position;
}
matched += 1;
boundaries += usize::from(previous_is_word);
if let Some(ch) = wanted.next() {
next = ch;
} else {
return Some([
3,
boundaries,
position - start + 1 - matched,
start,
field.len(),
]);
}
}
previous_is_word = ch.is_alphanumeric();
}
None
}
fn append_rows(
rows: &mut Vec<AlignedRow>,
before: usize,
@@ -3468,11 +3412,11 @@ mod tests {
#[test]
fn revision_matches_rank_later_contiguous_boundaries_and_tight_subsequences() {
assert_eq!(revision_match("r---f---s/topic/rfs", "rfs").unwrap()[0], 2);
assert!(revision_match("topic/rfs", "rfs") < revision_match("topicxrfs", "rfs"));
assert!(revision_match("r-f-s", "rfs") < revision_match("r---f---s", "rfs"));
assert!(revision_match("über-parser", "üpr").is_some());
assert!(revision_match("parser", "srp").is_none());
assert_eq!(fuzzy_match("r---f---s/topic/rfs", "rfs").unwrap()[0], 2);
assert!(fuzzy_match("topic/rfs", "rfs") < fuzzy_match("topicxrfs", "rfs"));
assert!(fuzzy_match("r-f-s", "rfs") < fuzzy_match("r---f---s", "rfs"));
assert!(fuzzy_match("über-parser", "üpr").is_some());
assert!(fuzzy_match("parser", "srp").is_none());
}
#[test]