Better plugin creation interface

This commit is contained in:
Javier Feliz 2025-09-06 01:00:35 -04:00
parent a5a597611e
commit 0cd68cb58c
5 changed files with 80 additions and 94 deletions

View File

@ -26,7 +26,10 @@ pub trait LauncherPlugin {
// Only search/use this plugin if the prefix was typed // Only search/use this plugin if the prefix was typed
fn by_prefix_only(&self) -> bool; fn by_prefix_only(&self) -> bool;
// Actual item searching functions // Actual item searching functions
fn default_list(&self) -> Vec<Box<dyn LauncherListItem>>; fn default_list(&self) -> Vec<Box<dyn LauncherListItem>> {
// Default empty list - plugins can override this
Vec::new()
}
fn filter(&self, query: &str) -> Vec<Box<dyn LauncherListItem>>; fn filter(&self, query: &str) -> Vec<Box<dyn LauncherListItem>>;
} }

View File

@ -154,7 +154,7 @@ impl PluginConfig {
quote! {} quote! {}
}; };
// Generate default_list method // Generate default_list method only if explicitly specified in config
let default_list_method = if let Some(ref default_list_fn) = self.default_list_fn { let default_list_method = if let Some(ref default_list_fn) = self.default_list_fn {
quote! { quote! {
fn default_list(&self) -> Vec<Box<dyn waycast_core::LauncherListItem>> { fn default_list(&self) -> Vec<Box<dyn waycast_core::LauncherListItem>> {
@ -162,14 +162,11 @@ impl PluginConfig {
} }
} }
} else { } else {
quote! { // Don't generate default_list method - user must implement it themselves
fn default_list(&self) -> Vec<Box<dyn waycast_core::LauncherListItem>> { quote! {}
Vec::new()
}
}
}; };
// Generate filter method // Generate filter method only if explicitly specified in config
let filter_method = if let Some(ref filter_fn) = self.filter_fn { let filter_method = if let Some(ref filter_fn) = self.filter_fn {
quote! { quote! {
fn filter(&self, query: &str) -> Vec<Box<dyn waycast_core::LauncherListItem>> { fn filter(&self, query: &str) -> Vec<Box<dyn waycast_core::LauncherListItem>> {
@ -177,11 +174,8 @@ impl PluginConfig {
} }
} }
} else { } else {
quote! { // Don't generate filter method - user must implement it themselves
fn filter(&self, query: &str) -> Vec<Box<dyn waycast_core::LauncherListItem>> { quote! {}
Vec::new()
}
}
}; };
quote! { quote! {

View File

@ -79,32 +79,7 @@ pub fn get_desktop_entries() -> Vec<DesktopEntry> {
entries entries
} }
fn drun_default_list(_plugin: &DrunPlugin) -> Vec<Box<dyn LauncherListItem>> {
let mut entries: Vec<Box<dyn LauncherListItem>> = Vec::new();
for e in get_desktop_entries() {
entries.push(Box::new(e));
}
entries
}
fn drun_filter(plugin: &DrunPlugin, query: &str) -> Vec<Box<dyn LauncherListItem>> {
if query.is_empty() {
return drun_default_list(plugin);
}
let query_lower = query.to_lowercase();
let mut entries: Vec<Box<dyn LauncherListItem>> = Vec::new();
for entry in drun_default_list(plugin) {
let title_lower = entry.title().to_lowercase();
if title_lower.contains(&query_lower) {
entries.push(entry);
}
}
entries
}
pub struct DrunPlugin; pub struct DrunPlugin;
@ -119,9 +94,34 @@ impl LauncherPlugin for DrunPlugin {
name: "drun", name: "drun",
priority: 1000, priority: 1000,
description: "List and launch an installed application", description: "List and launch an installed application",
prefix: "app", prefix: "app"
default_list: drun_default_list, }
filter: drun_filter
fn default_list(&self) -> Vec<Box<dyn LauncherListItem>> {
let mut entries: Vec<Box<dyn LauncherListItem>> = Vec::new();
for e in get_desktop_entries() {
entries.push(Box::new(e));
}
entries
}
fn filter(&self, query: &str) -> Vec<Box<dyn LauncherListItem>> {
if query.is_empty() {
return self.default_list();
}
let query_lower = query.to_lowercase();
let mut entries: Vec<Box<dyn LauncherListItem>> = Vec::new();
for entry in self.default_list() {
let title_lower = entry.title().to_lowercase();
if title_lower.contains(&query_lower) {
entries.push(entry);
}
}
entries
} }
} }

View File

@ -192,9 +192,7 @@ impl LauncherPlugin for FileSearchPlugin {
name: "Files", name: "Files",
priority: 500, priority: 500,
description: "Search and open files", description: "Search and open files",
prefix: "f", prefix: "f"
default_list: file_search_default_list,
filter: file_search_filter
} }
fn init(&self) { fn init(&self) {
@ -214,35 +212,33 @@ impl LauncherPlugin for FileSearchPlugin {
}); });
}); });
} }
}
fn file_search_default_list(_plugin: &FileSearchPlugin) -> Vec<Box<dyn LauncherListItem>> { fn filter(&self, query: &str) -> Vec<Box<dyn LauncherListItem>> {
Vec::new() if query.is_empty() {
} return self.default_list();
}
fn file_search_filter(plugin: &FileSearchPlugin, query: &str) -> Vec<Box<dyn LauncherListItem>> { let mut entries: Vec<Box<dyn LauncherListItem>> = Vec::new();
if query.is_empty() {
return file_search_default_list(plugin);
}
let mut entries: Vec<Box<dyn LauncherListItem>> = Vec::new(); // Try to get files without blocking - if indexing is still in progress, return empty
if let Ok(files) = self.files.try_lock() {
// Try to get files without blocking - if indexing is still in progress, return empty for f in files.iter() {
if let Ok(files) = plugin.files.try_lock() { if let Some(file_name) = f.path.file_name() {
for f in files.iter() { let cmp = file_name.to_string_lossy().to_lowercase();
if let Some(file_name) = f.path.file_name() { if cmp.contains(&query.to_lowercase()) {
let cmp = file_name.to_string_lossy().to_lowercase(); entries.push(Box::new(f.clone()));
if cmp.contains(&query.to_lowercase()) { }
entries.push(Box::new(f.clone()));
} }
} }
} }
}
entries entries
}
} }
pub fn new() -> FileSearchPlugin { pub fn new() -> FileSearchPlugin {
FileSearchPlugin::new() FileSearchPlugin::new()
} }

View File

@ -81,28 +81,26 @@ impl LauncherPlugin for ProjectsPlugin {
name: "Projects", name: "Projects",
priority: 800, priority: 800,
description: "Search and open code projects", description: "Search and open code projects",
prefix: "proj", prefix: "proj"
default_list: projects_default_list,
filter: projects_filter
} }
fn init(&self) { fn init(&self) {
let files_clone = Arc::clone(&self.files); let files_clone = Arc::clone(&self.files);
let search_paths = self.search_paths.clone(); let search_paths = self.search_paths.clone();
let skip_dirs = self.skip_dirs.clone(); let skip_dirs = self.skip_dirs.clone();
std::thread::spawn(move || { std::thread::spawn(move || {
let rt = tokio::runtime::Runtime::new().unwrap(); let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async { rt.block_on(async {
let mut project_entries = Vec::new(); let mut project_entries = Vec::new();
for search_path in &search_paths { for search_path in &search_paths {
if let Ok(entries) = fs::read_dir(search_path) { if let Ok(entries) = fs::read_dir(search_path) {
for entry in entries.flatten() { for entry in entries.flatten() {
if let Ok(file_type) = entry.file_type() { if let Ok(file_type) = entry.file_type() {
if file_type.is_dir() { if file_type.is_dir() {
let path = entry.path(); let path = entry.path();
// Skip hidden directories (starting with .) // Skip hidden directories (starting with .)
if let Some(file_name) = path.file_name() { if let Some(file_name) = path.file_name() {
if let Some(name_str) = file_name.to_str() { if let Some(name_str) = file_name.to_str() {
@ -110,12 +108,12 @@ impl LauncherPlugin for ProjectsPlugin {
if name_str.starts_with('.') { if name_str.starts_with('.') {
continue; continue;
} }
// Skip directories in skip list // Skip directories in skip list
if should_skip_dir(name_str, &skip_dirs) { if should_skip_dir(name_str, &skip_dirs) {
continue; continue;
} }
project_entries.push(ProjectEntry { path }); project_entries.push(ProjectEntry { path });
} }
} }
@ -124,44 +122,39 @@ impl LauncherPlugin for ProjectsPlugin {
} }
} }
} }
// Update the shared files collection // Update the shared files collection
let mut files_guard = files_clone.lock().await; let mut files_guard = files_clone.lock().await;
*files_guard = project_entries; *files_guard = project_entries;
println!("Projects plugin: Found {} projects", files_guard.len()); println!("Projects plugin: Found {} projects", files_guard.len());
}); });
}); });
} }
}
fn projects_default_list(_plugin: &ProjectsPlugin) -> Vec<Box<dyn LauncherListItem>> { fn filter(&self, query: &str) -> Vec<Box<dyn LauncherListItem>> {
Vec::new() if query.is_empty() {
} return self.default_list();
}
fn projects_filter(plugin: &ProjectsPlugin, query: &str) -> Vec<Box<dyn LauncherListItem>> { let mut entries: Vec<Box<dyn LauncherListItem>> = Vec::new();
if query.is_empty() {
return projects_default_list(plugin);
}
let mut entries: Vec<Box<dyn LauncherListItem>> = Vec::new(); // Try to get files without blocking - if indexing is still in progress, return empty
if let Ok(files) = self.files.try_lock() {
// Try to get files without blocking - if indexing is still in progress, return empty for f in files.iter() {
if let Ok(files) = plugin.files.try_lock() { if let Some(file_name) = f.path.file_name() {
for f in files.iter() { let cmp = file_name.to_string_lossy().to_lowercase();
if let Some(file_name) = f.path.file_name() { if cmp.contains(&query.to_lowercase()) {
let cmp = file_name.to_string_lossy().to_lowercase(); entries.push(Box::new(f.clone()));
if cmp.contains(&query.to_lowercase()) { }
entries.push(Box::new(f.clone()));
} }
} }
} }
entries
} }
entries
} }
pub fn new() -> ProjectsPlugin { pub fn new() -> ProjectsPlugin {
ProjectsPlugin { ProjectsPlugin {
search_paths: Vec::new(), search_paths: Vec::new(),