mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 11:37:47 +01:00
feat: return sub-exts when extension type exts themselves are matched (#928)
Take the 'Spotify Control' extension as an example: - Spotify Control - Toggle Play/Pause - Next Track - Previous Track Previously, these sub-extensions were only returned when the query string matched them, and thus counterintuitively, searching for 'Spotify Control' would not hit them. This commit changes that behavior: when a main extension (of type Extension) matches the query, all of its sub-extensions are now included in the results.
This commit is contained in:
@@ -17,6 +17,7 @@ feat: support switching groups via keyboard shortcuts #911
|
|||||||
feat: support opening logs from about page #915
|
feat: support opening logs from about page #915
|
||||||
feat: support moving cursor with home and end keys #918
|
feat: support moving cursor with home and end keys #918
|
||||||
feat: support pageup/pagedown to navigate search results #920
|
feat: support pageup/pagedown to navigate search results #920
|
||||||
|
feat: return sub-exts when extension type exts themselves are matched #928
|
||||||
|
|
||||||
### 🐛 Bug fix
|
### 🐛 Bug fix
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ use tauri_plugin_global_shortcut::GlobalShortcutExt;
|
|||||||
use tauri_plugin_global_shortcut::ShortcutState;
|
use tauri_plugin_global_shortcut::ShortcutState;
|
||||||
|
|
||||||
pub(crate) const EXTENSION_ID: &str = "Window Management";
|
pub(crate) const EXTENSION_ID: &str = "Window Management";
|
||||||
|
pub(crate) const EXTENSION_NAME_LOWERCASE: &str = "window management";
|
||||||
|
|
||||||
/// JSON file for this extension.
|
/// JSON file for this extension.
|
||||||
pub(crate) const PLUGIN_JSON_FILE: &str = include_str!("./plugin.json");
|
pub(crate) const PLUGIN_JSON_FILE: &str = include_str!("./plugin.json");
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use super::EXTENSION_ID;
|
use super::EXTENSION_ID;
|
||||||
|
use super::EXTENSION_NAME_LOWERCASE;
|
||||||
use crate::common::document::{DataSourceReference, Document};
|
use crate::common::document::{DataSourceReference, Document};
|
||||||
use crate::common::{
|
use crate::common::{
|
||||||
error::SearchError,
|
error::SearchError,
|
||||||
@@ -81,6 +82,16 @@ impl SearchSource for WindowManagementSearchSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An "extension" type extension should return all its
|
||||||
|
// sub-extensions when the query string matches its name.
|
||||||
|
// To do this, we score the extension name and take that
|
||||||
|
// into account.
|
||||||
|
if let Some(main_extension_score) =
|
||||||
|
calculate_text_similarity(&query_string_lowercase, &EXTENSION_NAME_LOWERCASE)
|
||||||
|
{
|
||||||
|
score += main_extension_score;
|
||||||
|
}
|
||||||
|
|
||||||
score
|
score
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
63
src-tauri/src/extension/third_party/mod.rs
vendored
63
src-tauri/src/extension/third_party/mod.rs
vendored
@@ -15,6 +15,7 @@ use crate::common::search::QuerySource;
|
|||||||
use crate::common::search::SearchQuery;
|
use crate::common::search::SearchQuery;
|
||||||
use crate::common::traits::SearchSource;
|
use crate::common::traits::SearchSource;
|
||||||
use crate::extension::ExtensionBundleIdBorrowed;
|
use crate::extension::ExtensionBundleIdBorrowed;
|
||||||
|
use crate::extension::ExtensionType;
|
||||||
use crate::extension::calculate_text_similarity;
|
use crate::extension::calculate_text_similarity;
|
||||||
use crate::extension::canonicalize_relative_page_path;
|
use crate::extension::canonicalize_relative_page_path;
|
||||||
use crate::util::platform::Platform;
|
use crate::util::platform::Platform;
|
||||||
@@ -757,11 +758,22 @@ impl SearchSource for ThirdPartyExtensionsSearchSource {
|
|||||||
|
|
||||||
for extension in extensions_read_lock.iter().filter(|ext| ext.enabled) {
|
for extension in extensions_read_lock.iter().filter(|ext| ext.enabled) {
|
||||||
if extension.r#type.contains_sub_items() {
|
if extension.r#type.contains_sub_items() {
|
||||||
|
let opt_main_extension_lowercase_name =
|
||||||
|
if extension.r#type == ExtensionType::Extension {
|
||||||
|
Some(extension.name.to_lowercase())
|
||||||
|
} else {
|
||||||
|
// None if it is of type `ExtensionType::Group`
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(ref commands) = extension.commands {
|
if let Some(ref commands) = extension.commands {
|
||||||
for command in commands.iter().filter(|cmd| cmd.enabled) {
|
for command in commands.iter().filter(|cmd| cmd.enabled) {
|
||||||
if let Some(hit) =
|
if let Some(hit) = extension_to_hit(
|
||||||
extension_to_hit(command, &query_lower, opt_data_source.as_deref())
|
command,
|
||||||
{
|
&query_lower,
|
||||||
|
opt_data_source.as_deref(),
|
||||||
|
opt_main_extension_lowercase_name.as_deref(),
|
||||||
|
) {
|
||||||
hits.push(hit);
|
hits.push(hit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -769,9 +781,12 @@ impl SearchSource for ThirdPartyExtensionsSearchSource {
|
|||||||
|
|
||||||
if let Some(ref scripts) = extension.scripts {
|
if let Some(ref scripts) = extension.scripts {
|
||||||
for script in scripts.iter().filter(|script| script.enabled) {
|
for script in scripts.iter().filter(|script| script.enabled) {
|
||||||
if let Some(hit) =
|
if let Some(hit) = extension_to_hit(
|
||||||
extension_to_hit(script, &query_lower, opt_data_source.as_deref())
|
script,
|
||||||
{
|
&query_lower,
|
||||||
|
opt_data_source.as_deref(),
|
||||||
|
opt_main_extension_lowercase_name.as_deref(),
|
||||||
|
) {
|
||||||
hits.push(hit);
|
hits.push(hit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -783,6 +798,7 @@ impl SearchSource for ThirdPartyExtensionsSearchSource {
|
|||||||
quicklink,
|
quicklink,
|
||||||
&query_lower,
|
&query_lower,
|
||||||
opt_data_source.as_deref(),
|
opt_data_source.as_deref(),
|
||||||
|
opt_main_extension_lowercase_name.as_deref(),
|
||||||
) {
|
) {
|
||||||
hits.push(hit);
|
hits.push(hit);
|
||||||
}
|
}
|
||||||
@@ -791,16 +807,19 @@ impl SearchSource for ThirdPartyExtensionsSearchSource {
|
|||||||
|
|
||||||
if let Some(ref views) = extension.views {
|
if let Some(ref views) = extension.views {
|
||||||
for view in views.iter().filter(|link| link.enabled) {
|
for view in views.iter().filter(|link| link.enabled) {
|
||||||
if let Some(hit) =
|
if let Some(hit) = extension_to_hit(
|
||||||
extension_to_hit(view, &query_lower, opt_data_source.as_deref())
|
view,
|
||||||
{
|
&query_lower,
|
||||||
|
opt_data_source.as_deref(),
|
||||||
|
opt_main_extension_lowercase_name.as_deref(),
|
||||||
|
) {
|
||||||
hits.push(hit);
|
hits.push(hit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let Some(hit) =
|
if let Some(hit) =
|
||||||
extension_to_hit(extension, &query_lower, opt_data_source.as_deref())
|
extension_to_hit(extension, &query_lower, opt_data_source.as_deref(), None)
|
||||||
{
|
{
|
||||||
hits.push(hit);
|
hits.push(hit);
|
||||||
}
|
}
|
||||||
@@ -839,10 +858,18 @@ pub(crate) async fn uninstall_extension(
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Argument `opt_main_extension_lowercase_name`: If `extension` is a sub-extension
|
||||||
|
/// of an `extension` type extension, then this argument contains the lowercase
|
||||||
|
/// name of that extension. Otherwise, None.
|
||||||
|
///
|
||||||
|
/// This argument is needed as an "extension" type extension should return all its
|
||||||
|
/// sub-extensions when the query string matches its name. To do this, we pass the
|
||||||
|
/// extension name, score it and take that into account.
|
||||||
pub(crate) fn extension_to_hit(
|
pub(crate) fn extension_to_hit(
|
||||||
extension: &Extension,
|
extension: &Extension,
|
||||||
query_lower: &str,
|
query_lower: &str,
|
||||||
opt_data_source: Option<&str>,
|
opt_data_source: Option<&str>,
|
||||||
|
opt_main_extension_lowercase_name: Option<&str>,
|
||||||
) -> Option<(Document, f64)> {
|
) -> Option<(Document, f64)> {
|
||||||
if !extension.searchable() {
|
if !extension.searchable() {
|
||||||
return None;
|
return None;
|
||||||
@@ -865,14 +892,26 @@ pub(crate) fn extension_to_hit(
|
|||||||
if let Some(title_score) =
|
if let Some(title_score) =
|
||||||
calculate_text_similarity(&query_lower, &extension.name.to_lowercase())
|
calculate_text_similarity(&query_lower, &extension.name.to_lowercase())
|
||||||
{
|
{
|
||||||
total_score += title_score * 1.0; // Weight for title
|
total_score += title_score;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Score based on alias match if available
|
// Score based on alias match if available
|
||||||
// Alias is considered less important than title, so it gets a lower weight.
|
// Alias is considered less important than title, so it gets a lower weight.
|
||||||
if let Some(alias) = &extension.alias {
|
if let Some(alias) = &extension.alias {
|
||||||
if let Some(alias_score) = calculate_text_similarity(&query_lower, &alias.to_lowercase()) {
|
if let Some(alias_score) = calculate_text_similarity(&query_lower, &alias.to_lowercase()) {
|
||||||
total_score += alias_score * 0.7; // Weight for alias
|
total_score += alias_score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An "extension" type extension should return all its
|
||||||
|
// sub-extensions when the query string matches its ID.
|
||||||
|
// To do this, we score the extension ID and take that
|
||||||
|
// into account.
|
||||||
|
if let Some(main_extension_lowercase_id) = opt_main_extension_lowercase_name {
|
||||||
|
if let Some(main_extension_score) =
|
||||||
|
calculate_text_similarity(&query_lower, main_extension_lowercase_id)
|
||||||
|
{
|
||||||
|
total_score += main_extension_score;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -265,13 +265,6 @@ async fn query_coco_fusion_multi_query_sources(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Re-rank the hits
|
|
||||||
*/
|
|
||||||
if n_sources > 1 {
|
|
||||||
boosted_levenshtein_rerank(&query_keyword, &mut all_hits_grouped_by_source_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Sort hits within each source by score (descending) in case data sources
|
* Sort hits within each source by score (descending) in case data sources
|
||||||
* do not sort them
|
* do not sort them
|
||||||
@@ -363,6 +356,13 @@ async fn query_coco_fusion_multi_query_sources(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Re-rank the final hits
|
||||||
|
*/
|
||||||
|
if n_sources > 1 {
|
||||||
|
boosted_levenshtein_rerank(&query_keyword, &mut final_hits_grouped_by_source_id);
|
||||||
|
}
|
||||||
|
|
||||||
let mut final_hits = Vec::new();
|
let mut final_hits = Vec::new();
|
||||||
for (_source_id, hits) in final_hits_grouped_by_source_id {
|
for (_source_id, hits) in final_hits_grouped_by_source_id {
|
||||||
final_hits.extend(hits);
|
final_hits.extend(hits);
|
||||||
|
|||||||
Reference in New Issue
Block a user