From ea9c17f1120b09f07a0d86bf2d21533e3decab75 Mon Sep 17 00:00:00 2001 From: Sebastian Wiesner Date: Sun, 11 Feb 2024 19:30:17 +0100 Subject: [PATCH] Always refresh recent projects when searching --- CHANGELOG.md | 8 ++ src/main.rs | 6 +- src/reload.rs | 65 ----------- src/searchprovider.rs | 101 ++++++++++-------- .../gnome-search-providers-jetbrains.service | 1 - 5 files changed, 66 insertions(+), 115 deletions(-) delete mode 100644 src/reload.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 436616d..36dbc7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ and this project doesn't really care for versioning. ## [Unreleased] +### Changed +- Refresh recent projects (see [GH-55]). + +### Removed +- Reload interface and `systemctl reload` support (see [GH-55]). + +[GH-55]: https://github.com/swsnr/gnome-search-providers-jetbrains/pull/55 + ## [1.16.0] – 2023-09-30 ### Added diff --git a/src/main.rs b/src/main.rs index fa2904b..e2fca7c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,13 +17,11 @@ use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::Registry; use providers::*; -use reload::*; use searchprovider::*; mod config; mod launch; mod providers; -mod reload; mod searchprovider; mod systemd; @@ -106,11 +104,10 @@ fn main() -> Result<()> { .filter_map(|provider| { gio::DesktopAppInfo::new(provider.desktop_id).map(|gio_app| { event!(Level::INFO, "Found app {}", provider.desktop_id); - let mut search_provider = JetbrainsProductSearchProvider::new( + let search_provider = JetbrainsProductSearchProvider::new( App::from(gio_app), &provider.config, ); - let _ = search_provider.reload_recent_projects(); (provider.objpath(), search_provider) }) }) @@ -129,7 +126,6 @@ fn main() -> Result<()> { builder.serve_at(path, provider) }, )? - .serve_at("/", ReloadAll)? .serve_log_control(LogControl1::new(control))? .name(BUSNAME)? .build() diff --git a/src/reload.rs b/src/reload.rs deleted file mode 100644 index ce70431..0000000 --- a/src/reload.rs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright Sebastian Wiesner -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -//! Reload all recent projects across all exposed provider interfaces. - -use crate::providers::PROVIDERS; -use crate::searchprovider::JetbrainsProductSearchProvider; -use tracing::{event, instrument, Level}; -use zbus::{dbus_interface, ObjectServer}; - -#[derive(Debug)] -pub struct ReloadAll; - -#[dbus_interface(name = "de.swsnr.searchprovider.ReloadAll")] -impl ReloadAll { - /// Reload all recent projects in all registered search providers.. - #[instrument(skip(self, server))] - pub async fn reload_all( - &self, - #[zbus(object_server)] server: &ObjectServer, - ) -> zbus::fdo::Result<()> { - event!( - Level::DEBUG, - "Reloading recent projects of all registered search providers" - ); - let mut is_failed = false; - for provider in PROVIDERS { - match server - .interface::<_, JetbrainsProductSearchProvider>(provider.objpath()) - .await - { - Err(error) => { - event!( - Level::DEBUG, - "Skipping {} ({}): {error}", - provider.label, - provider.desktop_id - ); - } - Ok(search_provider_interface) => { - if let Err(error) = search_provider_interface - .get_mut() - .await - .reload_recent_projects() - { - is_failed = true; - let iface = search_provider_interface.get().await; - let app_id = iface.app().id(); - event!(Level::ERROR, %app_id, "Failed to reload recent projects of {}: {}", app_id, error); - } - } - } - } - if is_failed { - Err(zbus::fdo::Error::Failed( - "Failed to reload recent projects of some providers".to_string(), - )) - } else { - Ok(()) - } - } -} diff --git a/src/searchprovider.rs b/src/searchprovider.rs index b819ffe..31fbdf7 100644 --- a/src/searchprovider.rs +++ b/src/searchprovider.rs @@ -268,6 +268,33 @@ async fn launch_app_in_new_scope( }) } +/// Calculate how well `recent_projects` matches all of the given `terms`. +/// +/// If all terms match the name of the `recent_projects`, the project receives a base score of 10. +/// If all terms match the directory of the `recent_projects`, the project gets scored for each +/// term according to how far right the term appears in the directory, under the assumption that +/// the right most part of a directory path is the most specific. +/// +/// All matches are done on the lowercase text, i.e. case insensitve. +fn score_recent_project(recent_project: &JetbrainsRecentProject, terms: &[&str]) -> f64 { + let name = recent_project.name.to_lowercase(); + let directory = recent_project.directory.to_lowercase(); + terms + .iter() + .try_fold(0.0, |score, term| { + directory + .rfind(&term.to_lowercase()) + // We add 1 to avoid returning zero if the term matches right at the beginning. + .map(|index| score + ((index + 1) as f64 / recent_project.directory.len() as f64)) + }) + .unwrap_or(0.0) + + if terms.iter().all(|term| name.contains(&term.to_lowercase())) { + 10.0 + } else { + 0.0 + } +} + /// A search provider for recent Jetbrains products. #[derive(Debug)] pub struct JetbrainsProductSearchProvider { @@ -295,11 +322,31 @@ impl JetbrainsProductSearchProvider { } /// Reload all recent projects provided by this search provider. - pub fn reload_recent_projects(&mut self) -> Result<()> { + fn reload_recent_projects(&mut self) -> Result<()> { self.recent_projects = read_recent_projects(self.config, self.app.id())?; Ok(()) } + /// Find all projects matching the given `terms`. + /// + /// Return a list of IDs of matching projects. + fn find_project_ids_by_terms(&self, terms: &[&str]) -> Vec<&str> { + let mut scored_ids = self + .recent_projects + .iter() + .filter_map(|(id, item)| { + let score = score_recent_project(item, terms); + if 0.0 < score { + Some((id.as_ref(), score)) + } else { + None + } + }) + .collect::>(); + scored_ids.sort_by_key(|(_, score)| -((score * 1000.0) as i64)); + scored_ids.into_iter().map(|(id, _)| id).collect() + } + #[instrument(skip(self, connection), fields(app_id = %self.app.id()))] async fn launch_app_on_default_main_context( &self, @@ -324,33 +371,6 @@ impl JetbrainsProductSearchProvider { } } -/// Calculate how well `recent_projects` matches all of the given `terms`. -/// -/// If all terms match the name of the `recent_projects`, the project receives a base score of 10. -/// If all terms match the directory of the `recent_projects`, the project gets scored for each -/// term according to how far right the term appears in the directory, under the assumption that -/// the right most part of a directory path is the most specific. -/// -/// All matches are done on the lowercase text, i.e. case insensitve. -fn score_recent_project(recent_project: &JetbrainsRecentProject, terms: &[&str]) -> f64 { - let name = recent_project.name.to_lowercase(); - let directory = recent_project.directory.to_lowercase(); - terms - .iter() - .try_fold(0.0, |score, term| { - directory - .rfind(&term.to_lowercase()) - // We add 1 to avoid returning zero if the term matches right at the beginning. - .map(|index| score + ((index + 1) as f64 / recent_project.directory.len() as f64)) - }) - .unwrap_or(0.0) - + if terms.iter().all(|term| name.contains(&term.to_lowercase())) { - 10.0 - } else { - 0.0 - } -} - /// The DBus interface of the search provider. /// /// See for information. @@ -362,22 +382,15 @@ impl JetbrainsProductSearchProvider { /// and should return an array of result IDs. gnome-shell will call GetResultMetas for (some) of these result /// IDs to get details about the result that can be be displayed in the result list. #[instrument(skip(self), fields(app_id = %self.app.id()))] - fn get_initial_result_set(&self, terms: Vec<&str>) -> Vec<&str> { + fn get_initial_result_set(&mut self, terms: Vec<&str>) -> Vec<&str> { + event!(Level::DEBUG, "Reloading recent projects"); + if let Err(error) = self.reload_recent_projects() { + // Ignore errors while reloading recent projects, and just resume + // search with what we've got. + event!(Level::ERROR, "Failed to reload recent projects: {}", error); + } event!(Level::DEBUG, "Searching for {:?}", terms); - let mut scored_ids = self - .recent_projects - .iter() - .filter_map(|(id, item)| { - let score = score_recent_project(item, &terms); - if 0.0 < score { - Some((id.as_ref(), score)) - } else { - None - } - }) - .collect::>(); - scored_ids.sort_by_key(|(_, score)| -((score * 1000.0) as i64)); - let ids = scored_ids.into_iter().map(|(id, _)| id).collect(); + let ids = self.find_project_ids_by_terms(&terms); event!(Level::DEBUG, "Found ids {:?}", ids); ids } @@ -397,7 +410,7 @@ impl JetbrainsProductSearchProvider { ); // For simplicity just run the overall search again, and filter out everything not already matched. let ids = self - .get_initial_result_set(terms) + .find_project_ids_by_terms(&terms) .into_iter() .filter(|id| previous_results.contains(id)) .collect(); diff --git a/systemd/gnome-search-providers-jetbrains.service b/systemd/gnome-search-providers-jetbrains.service index 107b30c..af3b41c 100644 --- a/systemd/gnome-search-providers-jetbrains.service +++ b/systemd/gnome-search-providers-jetbrains.service @@ -5,4 +5,3 @@ Description=Jetbrains projects search provider for Gnome shell Type=dbus BusName=de.swsnr.searchprovider.Jetbrains ExecStart=gnome-search-providers-jetbrains -ExecReload=busctl --user call de.swsnr.searchprovider.Jetbrains / de.swsnr.searchprovider.ReloadAll ReloadAll