From bf443a27fdc35a2fc05f58a54b7a98050273347e Mon Sep 17 00:00:00 2001 From: Javier Feliz Date: Sat, 6 Sep 2025 00:43:57 -0400 Subject: [PATCH] Added projects plugin --- tmp.txt | 84 +++++++++++++++ waycast-gtk/src/main.rs | 7 ++ waycast-plugins/src/lib.rs | 3 +- waycast-plugins/src/projects.rs | 182 ++++++++++++++++++++++++++++++++ 4 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 tmp.txt create mode 100644 waycast-plugins/src/projects.rs diff --git a/tmp.txt b/tmp.txt new file mode 100644 index 0000000..de302d5 --- /dev/null +++ b/tmp.txt @@ -0,0 +1,84 @@ +cargo run -p waycast-gtk +/home/javi/projects/homelab-ansible +/home/javi/projects/gla-old +/home/javi/projects/filebrowser-agent +/home/javi/projects/ttrpg-cms +/home/javi/projects/desktop-setup +/home/javi/projects/ravensofravnica.stream +/home/javi/projects/nix +/home/javi/projects/demos +/home/javi/projects/resumeforge.app +/home/javi/projects/videos +/home/javi/projects/authentikate.dev +/home/javi/projects/blogimage.app +/home/javi/projects/arch-hyprland-setup +/home/javi/projects/laravel-dockerized +/home/javi/projects/alex-nix-config +/home/javi/projects/thegrind-tools +/home/javi/projects/imgsrc-app +/home/javi/projects/scripthost +/home/javi/projects/foundryvtt-docker +/home/javi/projects/launchlet.app +/home/javi/projects/yourfluence.dev +/home/javi/projects/thegrind.dev +/home/javi/projects/opnsensebackup +/home/javi/projects/project-picker +/home/javi/projects/thegrind.dev-old-jigsaw +/home/javi/projects/og-cli +/home/javi/projects/do-flow +/home/javi/projects/carti-python +/home/javi/projects/rack-planner +/home/javi/projects/ember-db +/home/javi/projects/local-ai +/home/javi/projects/zap +/home/javi/projects/flowtodo +/home/javi/projects/temp +/home/javi/projects/blog-backup +/home/javi/projects/javierfeliz.com +/home/javi/projects/thc-demo +/home/javi/projects/crawler-linkedin +/home/javi/projects/vikunja-planka-transfer +/home/javi/projects/desktop-backup +/home/javi/projects/authentikate +/home/javi/projects/scratchfl.com +/home/javi/projects/cs-trade-bot +/home/javi/projects/leetcode +/home/javi/projects/screenshotter +/home/javi/projects/docker-compose-builder +/home/javi/projects/javif89 +/home/javi/projects/papibot +/home/javi/projects/crypt +/home/javi/projects/analysis +/home/javi/projects/go-starter-kit +/home/javi/projects/vue3-learning +/home/javi/projects/homelab-docker +/home/javi/projects/sitemapper +/home/javi/projects/dotnet-deployment-ansible +/home/javi/projects/planka +/home/javi/projects/waycast-cpp +/home/javi/projects/hubspot-assessment +/home/javi/projects/go-doit +/home/javi/projects/alex-demo +/home/javi/projects/docker-laravel-base +/home/javi/projects/staticforge +/home/javi/projects/jigsaw +/home/javi/projects/theresaleconcierge +/home/javi/projects/nico-stuff +/home/javi/projects/zold-javierfeliz.com +/home/javi/projects/ansible-starter-kit +/home/javi/projects/oidctester +/home/javi/projects/waycast +/home/javi/projects/ubuntu-setup +/home/javi/projects/caddy-test +/home/javi/projects/dotfiles +/home/javi/projects/bazzitouchedhisconfig.dev +/home/javi/projects/cs2-server-manager +/home/javi/projects/noconfig +/home/javi/projects/gamelineanalytics.com +/home/javi/projects/thegrind-de +/home/javi/projects/data-parser +/home/javi/projects/test-dotnet-blazor-project +/home/javi/projects/homies +/home/javi/projects/ogimage-click +/home/javi/projects/ansible-on-prem +Projects plugin: Found 82 projects diff --git a/waycast-gtk/src/main.rs b/waycast-gtk/src/main.rs index 7901de3..6494d8a 100644 --- a/waycast-gtk/src/main.rs +++ b/waycast-gtk/src/main.rs @@ -20,10 +20,17 @@ fn main() { _ => (), } + let mut project_plugin = waycast_plugins::projects::new(); + match project_plugin.add_search_path("/home/javi/projects") { + Err(e) => eprintln!("{}", e), + _ => (), + } + // Create the core launcher let launcher = WaycastLauncher::new() .add_plugin(Box::new(waycast_plugins::drun::new())) .add_plugin(Box::new(file_search_plugin)) + .add_plugin(Box::new(project_plugin)) .init(); // Create and show the GTK UI diff --git a/waycast-plugins/src/lib.rs b/waycast-plugins/src/lib.rs index b0dda10..f55ae5c 100644 --- a/waycast-plugins/src/lib.rs +++ b/waycast-plugins/src/lib.rs @@ -1,5 +1,6 @@ pub mod drun; pub mod file_search; +pub mod projects; // Re-export the macros for external use -pub use waycast_macros::{plugin, launcher_entry}; +pub use waycast_macros::{launcher_entry, plugin}; diff --git a/waycast-plugins/src/projects.rs b/waycast-plugins/src/projects.rs new file mode 100644 index 0000000..88a25c8 --- /dev/null +++ b/waycast-plugins/src/projects.rs @@ -0,0 +1,182 @@ +// TODO: Use the user's preferred editor. +// This should just be in the config when I implement +// that eventually since figuring out every editor's +// launch option would be a pain. The user can just +// configure launch_command and pass a parameter. +// Example: code -n {path} +// and I'll just regex in the path. +// TODO: Project type detection and icon +use std::{ + fs, + path::{Path, PathBuf}, + process::Command, + sync::Arc, +}; + +use tokio::sync::Mutex; +use waycast_core::{LaunchError, LauncherListItem, LauncherPlugin}; +use waycast_macros::{launcher_entry, plugin}; + +#[derive(Clone)] +pub struct ProjectEntry { + path: PathBuf, +} + +impl LauncherListItem for ProjectEntry { + launcher_entry! { + id: self.path.to_string_lossy().to_string(), + title: String::from(self.path.file_name().unwrap().to_string_lossy()), + description: Some(self.path.to_string_lossy().to_string()), + icon: { + String::from("vscode") + }, + execute: { + println!("Executing: {}", self.path.display()); + + // Use xdg-open directly since it works properly with music files + match Command::new("code").arg("-n").arg(&self.path).spawn() { + Ok(_) => { + println!("Successfully opened with code"); + Ok(()) + } + Err(_) => Err(LaunchError::CouldNotLaunch("Failed to open project folder".into())), + } + } + } +} + +pub struct ProjectsPlugin { + search_paths: Vec, + skip_dirs: Vec, + // Running list of files in memory + files: Arc>>, +} + +impl ProjectsPlugin { + pub fn add_search_path>(&mut self, path: P) -> Result<(), String> { + let p = path.as_ref(); + + if !p.exists() { + return Err(format!("Path does not exist: {}", p.display())); + } + + if !p.is_dir() { + return Err(format!("Path is not a directory: {}", p.display())); + } + + self.search_paths.push(p.to_path_buf()); + Ok(()) + } + + pub fn add_skip_dir(&mut self, directory_name: String) -> Result<(), String> { + self.skip_dirs.push(directory_name); + Ok(()) + } +} + +fn should_skip_dir(dir_name: &str, skip_dirs: &[String]) -> bool { + skip_dirs.iter().any(|skip| skip == dir_name) +} + +impl LauncherPlugin for ProjectsPlugin { + plugin! { + name: "Projects", + priority: 800, + description: "Search and open code projects", + prefix: "proj", + init: projects_init, + default_list: projects_default_list, + filter: projects_filter + } +} + +fn projects_default_list(_plugin: &ProjectsPlugin) -> Vec> { + Vec::new() +} + +fn projects_filter(plugin: &ProjectsPlugin, query: &str) -> Vec> { + if query.is_empty() { + return projects_default_list(plugin); + } + + let mut entries: Vec> = Vec::new(); + + // Try to get files without blocking - if indexing is still in progress, return empty + if let Ok(files) = plugin.files.try_lock() { + for f in files.iter() { + if let Some(file_name) = f.path.file_name() { + let cmp = file_name.to_string_lossy().to_lowercase(); + if cmp.contains(&query.to_lowercase()) { + entries.push(Box::new(f.clone())); + } + } + } + } + + entries +} + +fn projects_init(plugin: &ProjectsPlugin) { + let files_clone = Arc::clone(&plugin.files); + let search_paths = plugin.search_paths.clone(); + let skip_dirs = plugin.skip_dirs.clone(); + + std::thread::spawn(move || { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { + let mut project_entries = Vec::new(); + + for search_path in &search_paths { + if let Ok(entries) = fs::read_dir(search_path) { + for entry in entries.flatten() { + if let Ok(file_type) = entry.file_type() { + if file_type.is_dir() { + let path = entry.path(); + + // Skip hidden directories (starting with .) + if let Some(file_name) = path.file_name() { + if let Some(name_str) = file_name.to_str() { + // Skip hidden directories + if name_str.starts_with('.') { + continue; + } + + // Skip directories in skip list + if should_skip_dir(name_str, &skip_dirs) { + continue; + } + + println!("{}", path.display()); + + project_entries.push(ProjectEntry { path }); + } + } + } + } + } + } + } + + // Update the shared files collection + let mut files_guard = files_clone.lock().await; + *files_guard = project_entries; + + println!("Projects plugin: Found {} projects", files_guard.len()); + }); + }); +} + +pub fn new() -> ProjectsPlugin { + ProjectsPlugin { + search_paths: Vec::new(), + skip_dirs: vec![ + String::from("vendor"), + String::from("node_modules"), + String::from("cache"), + String::from("zig-cache"), + String::from(".git"), + String::from(".svn"), + ], + files: Arc::new(Mutex::new(Vec::new())), + } +}