use std::collections::HashMap; use std::sync::Arc; #[derive(Debug)] pub enum LaunchError { CouldNotLaunch(String), } pub trait LauncherListItem { fn id(&self) -> String; fn title(&self) -> String; fn description(&self) -> Option; fn execute(&self) -> Result<(), LaunchError>; fn icon(&self) -> String; } pub trait LauncherPlugin { fn init(&self) { // Default empty init - plugins can override this } fn name(&self) -> String; fn priority(&self) -> i32; fn description(&self) -> Option; // Prefix to isolate results to only use this plugin fn prefix(&self) -> Option; // Only search/use this plugin if the prefix was typed fn by_prefix_only(&self) -> bool; // Actual item searching functions fn default_list(&self) -> Vec> { // Default empty list - plugins can override this Vec::new() } fn filter(&self, query: &str) -> Vec>; } pub struct WaycastLauncher { plugins: Vec>, plugins_show_always: Vec>, plugins_by_prefix: HashMap>, current_results: Vec>, current_results_by_id: HashMap, } impl WaycastLauncher { pub fn new() -> Self { WaycastLauncher { plugins: Vec::new(), plugins_show_always: Vec::new(), plugins_by_prefix: HashMap::new(), current_results: Vec::new(), current_results_by_id: HashMap::new(), } } pub fn add_plugin(mut self, plugin: Box) -> Self { let p: Arc = plugin.into(); if !p.by_prefix_only() { self.plugins_show_always.push(Arc::clone(&p)); } if let Some(prefix) = p.prefix() { self.plugins_by_prefix.insert(prefix, Arc::clone(&p)); } self.plugins.push(p); self } pub fn init(mut self) -> Self { for p in &self.plugins { p.init(); } self.plugins.sort_by(|a, b| b.priority().cmp(&a.priority())); self.plugins_show_always .sort_by(|a, b| b.priority().cmp(&a.priority())); self } fn add_current_item(&mut self, item: Box) { let id = item.id(); let index = self.current_results.len(); self.current_results.push(item); self.current_results_by_id.insert(id, index); } fn clear_current_results(&mut self) { self.current_results.clear(); self.current_results_by_id.clear(); } pub fn get_default_results(&mut self) -> &Vec> { self.clear_current_results(); let mut all_entries = Vec::new(); for plugin in &self.plugins_show_always { all_entries.extend(plugin.default_list()); } for entry in all_entries { self.add_current_item(entry); } &self.current_results } pub fn search(&mut self, query: &str) -> &Vec> { self.clear_current_results(); let mut all_entries = Vec::new(); for plugin in &self.plugins { all_entries.extend(plugin.filter(query)); } for entry in all_entries { self.add_current_item(entry); } &self.current_results } pub fn execute_item(&self, index: usize) -> Result<(), LaunchError> { if let Some(item) = self.current_results.get(index) { item.execute() } else { Err(LaunchError::CouldNotLaunch("Invalid index".into())) } } pub fn execute_item_by_id(&self, id: &str) -> Result<(), LaunchError> { if let Some(&index) = self.current_results_by_id.get(id) { if let Some(item) = self.current_results.get(index) { item.execute() } else { Err(LaunchError::CouldNotLaunch( "Item index out of bounds".into(), )) } } else { Err(LaunchError::CouldNotLaunch("Item not found".into())) } } pub fn current_results(&self) -> &Vec> { &self.current_results } }