Prioritise prefix matches in tag autocomplete results (#4212)

* prioritise prefix matches in tag autocomplete results

* update/add tests

* fix lint

was fine locally though?
This commit is contained in:
llama 2025-07-22 19:11:33 +08:00 committed by GitHub
parent 65b5aefd07
commit 35a889e1ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -12,14 +12,20 @@ impl Collection {
.map(component_to_regex) .map(component_to_regex)
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
let mut tags = vec![]; let mut tags = vec![];
let mut priority = vec![];
self.storage.get_tags_by_predicate(|tag| { self.storage.get_tags_by_predicate(|tag| {
if tags.len() <= limit && filters_match(&filters, tag) { if priority.len() + tags.len() <= limit {
tags.push(tag.to_string()); match filters_match(&filters, tag) {
Some(true) => priority.push(tag.to_string()),
Some(_) => tags.push(tag.to_string()),
_ => {}
}
} }
// we only need the tag name // we only need the tag name
false false
})?; })?;
Ok(tags) priority.append(&mut tags);
Ok(priority)
} }
} }
@ -27,20 +33,26 @@ fn component_to_regex(component: &str) -> Result<Regex> {
Regex::new(&format!("(?i){}", regex::escape(component))).map_err(Into::into) Regex::new(&format!("(?i){}", regex::escape(component))).map_err(Into::into)
} }
fn filters_match(filters: &[Regex], tag: &str) -> bool { /// Returns None if tag wasn't a match, otherwise whether it was a consecutive
/// prefix match
fn filters_match(filters: &[Regex], tag: &str) -> Option<bool> {
let mut remaining_tag_components = tag.split("::"); let mut remaining_tag_components = tag.split("::");
let mut is_prefix = true;
'outer: for filter in filters { 'outer: for filter in filters {
loop { loop {
if let Some(component) = remaining_tag_components.next() { if let Some(component) = remaining_tag_components.next() {
if filter.is_match(component) { if let Some(m) = filter.find(component) {
is_prefix &= m.start() == 0;
continue 'outer; continue 'outer;
} else {
is_prefix = false;
} }
} else { } else {
return false; return None;
} }
} }
} }
true Some(is_prefix)
} }
#[cfg(test)] #[cfg(test)]
@ -50,28 +62,32 @@ mod test {
#[test] #[test]
fn matching() -> Result<()> { fn matching() -> Result<()> {
let filters = &[component_to_regex("b")?]; let filters = &[component_to_regex("b")?];
assert!(filters_match(filters, "ABC")); assert!(filters_match(filters, "ABC").is_some());
assert!(filters_match(filters, "ABC::def")); assert!(filters_match(filters, "ABC::def").is_some());
assert!(filters_match(filters, "def::abc")); assert!(filters_match(filters, "def::abc").is_some());
assert!(!filters_match(filters, "def")); assert!(filters_match(filters, "def").is_none());
let filters = &[component_to_regex("b")?, component_to_regex("E")?]; let filters = &[component_to_regex("b")?, component_to_regex("E")?];
assert!(!filters_match(filters, "ABC")); assert!(filters_match(filters, "ABC").is_none());
assert!(filters_match(filters, "ABC::def")); assert!(filters_match(filters, "ABC::def").is_some());
assert!(!filters_match(filters, "def::abc")); assert!(filters_match(filters, "def::abc").is_none());
assert!(!filters_match(filters, "def")); assert!(filters_match(filters, "def").is_none());
let filters = &[ let filters = &[
component_to_regex("a")?, component_to_regex("a")?,
component_to_regex("c")?, component_to_regex("c")?,
component_to_regex("e")?, component_to_regex("e")?,
]; ];
assert!(!filters_match(filters, "ace")); assert!(filters_match(filters, "ace").is_none());
assert!(!filters_match(filters, "a::c")); assert!(filters_match(filters, "a::c").is_none());
assert!(!filters_match(filters, "c::e")); assert!(filters_match(filters, "c::e").is_none());
assert!(filters_match(filters, "a::c::e")); assert!(filters_match(filters, "a::c::e").is_some());
assert!(filters_match(filters, "a::b::c::d::e")); assert!(filters_match(filters, "a::b::c::d::e").is_some());
assert!(filters_match(filters, "1::a::b::c::d::e::f")); assert!(filters_match(filters, "1::a::b::c::d::e::f").is_some());
assert_eq!(filters_match(filters, "a1::c2::e3"), Some(true));
assert_eq!(filters_match(filters, "a1::c2::?::e4"), Some(false));
assert_eq!(filters_match(filters, "a1::c2::3e"), Some(false));
Ok(()) Ok(())
} }