From 092d78f7ca1ddd0a447d63b06efdeb7c0d79cd37 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 29 Sep 2025 13:02:23 -0400 Subject: [PATCH] fix: introduce decay factor for keyword weight based on index, and shrink the description match weight. We could tweak the values to be smaller if this seems to affect anything else. --- service/src/lib.rs | 156 +++++++++++++++++++++++++++++++++------------ 1 file changed, 117 insertions(+), 39 deletions(-) diff --git a/service/src/lib.rs b/service/src/lib.rs index b7b9a09..ecb679d 100644 --- a/service/src/lib.rs +++ b/service/src/lib.rs @@ -567,45 +567,6 @@ impl + Unpin> Service { } else { active_search.sort_by(|a, b| { // Weight is calculated between 0.0 and 1.0, with higher values being most similar - fn calculate_weight(meta: &PluginSearchResult, query: &str) -> f64 { - let mut weight: f64 = 0.0; - - let name = meta.name.to_ascii_lowercase(); - let description = meta.description.to_ascii_lowercase(); - let exec = meta - .exec - .as_ref() - .map(|exec| exec.to_ascii_lowercase()) - .unwrap_or_default(); - - for name in name.split_ascii_whitespace().flat_map(|x| x.split('_')) { - if name.starts_with(query) { - return 1.0; - } - } - - if exec.contains(query) { - if exec.starts_with(query) { - return 1.0; - } - - weight = strsim::jaro_winkler(query, &exec) - 0.1; - } - - weight - .max(strsim::jaro_winkler(&name, query)) - .max(strsim::jaro_winkler(&description, query) - 0.1) - .max(match meta.keywords.as_ref() { - Some(keywords) => keywords - .iter() - .flat_map(|word| word.split_ascii_whitespace()) - .fold(0.0, |acc, keyword| { - let keyword = keyword.to_ascii_lowercase(); - acc.max(strsim::jaro_winkler(query, &keyword) - 0.1) - }), - None => 0.0, - }) - } let plug1 = match plugins.get(a.0) { Some(plug) => plug, @@ -704,3 +665,120 @@ fn serialize_out(output: &mut io::StdoutLock, event: &E) { let _res = output.write_all(&vec); } } + +fn calculate_weight(meta: &PluginSearchResult, query: &str) -> f64 { + let mut weight: f64 = 0.0; + + let name = meta.name.to_ascii_lowercase(); + let description = meta.description.to_ascii_lowercase(); + let exec = meta + .exec + .as_ref() + .map(|exec| exec.to_ascii_lowercase()) + .unwrap_or_default(); + + for name in name.split_ascii_whitespace().flat_map(|x| x.split('_')) { + if name.starts_with(query) { + return 1.0; + } + } + + if exec.contains(query) { + if exec.starts_with(query) { + return 1.0; + } + + weight = strsim::jaro_winkler(query, &exec) - 0.1; + } + + weight + .max(strsim::jaro_winkler(&name, query)) + .max(match meta.keywords.as_ref() { + Some(keywords) => keywords + .iter() + .flat_map(|word| word.split_ascii_whitespace()) + .enumerate() + .fold(0.0, |acc, (i, keyword)| { + let keyword = keyword.to_ascii_lowercase(); + let mut v = acc.max(strsim::jaro_winkler(query, &keyword) - 0.1); + // small decay factor for keywords later in the list. + v *= (90. + 10usize.saturating_sub(i) as f64) / 100.; + v + }), + None => 0.0, + }) + // deprioritize description matches the most + .max(strsim::jaro_winkler(&description, query) * 0.9 - 0.1) +} + +#[cfg(test)] +mod tests { + use super::*; + use pop_launcher::PluginSearchResult; + + #[test] + fn test_script_calculate_weight() { + // Test queries for each of the prompt's entries + let entries = vec![ + ( + "Enter BIOS", + PluginSearchResult { + id: 0, + name: "Enter BIOS".to_string(), + description: "Reboot into BIOS".to_string(), + icon: None, + window: None, + exec: None, + keywords: Some(vec![ + "bios".to_string(), + "uefi".to_string(), + "reboot".to_string(), + "restart".to_string(), + ]), + }, + ), + ( + "Restart", + PluginSearchResult { + id: 3, + name: "Restart".to_string(), + description: "Reboot the system".to_string(), + icon: None, + window: None, + exec: None, + keywords: Some(vec![ + "power".to_string(), + "reboot".to_string(), + "restart".to_string(), + ]), + }, + ), + ]; + + let query_reboot = "reboot"; + let weights_reboot: Vec = entries + .iter() + .map(|(_, entry)| calculate_weight(entry, query_reboot)) + .collect(); + + let idx_restart = entries.iter().position(|(n, _)| *n == "Restart").unwrap(); + let idx_bios = entries + .iter() + .position(|(n, _)| *n == "Enter BIOS") + .unwrap(); + + assert!( + weights_reboot[idx_restart] > weights_reboot[idx_bios], + "Restart should be top for 'reboot', then Enter BIOS" + ); + + assert!( + weights_reboot[idx_restart] >= 0.85, + "Restart should be high for 'reboot'" + ); + assert!( + weights_reboot[idx_bios] >= 0.85, + "Enter BIOS should be high for 'reboot'" + ); + } +}