Added projects plugin

This commit is contained in:
Javier Feliz 2025-09-06 00:43:57 -04:00
parent 8c5b9f9295
commit bf443a27fd
4 changed files with 275 additions and 1 deletions

84
tmp.txt Normal file
View File

@ -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

View File

@ -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

View File

@ -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};

View File

@ -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<PathBuf>,
skip_dirs: Vec<String>,
// Running list of files in memory
files: Arc<Mutex<Vec<ProjectEntry>>>,
}
impl ProjectsPlugin {
pub fn add_search_path<P: AsRef<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<Box<dyn LauncherListItem>> {
Vec::new()
}
fn projects_filter(plugin: &ProjectsPlugin, query: &str) -> Vec<Box<dyn LauncherListItem>> {
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) = 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())),
}
}