Progress
This commit is contained in:
parent
9fcd0269a8
commit
919e6a44f6
@ -14,8 +14,8 @@ fn main() {
|
|||||||
app.connect_activate(|app| {
|
app.connect_activate(|app| {
|
||||||
// Create the core launcher
|
// Create the core launcher
|
||||||
let launcher = WaycastLauncher::new()
|
let launcher = WaycastLauncher::new()
|
||||||
.add_plugin(Box::new(waycast_plugins::drun::new()))
|
// .add_plugin(Box::new(waycast_plugins::drun::new()))
|
||||||
.add_plugin(Box::new(waycast_plugins::file_search::new()))
|
// .add_plugin(Box::new(waycast_plugins::file_search::new()))
|
||||||
.add_plugin(Box::new(waycast_plugins::projects::new()))
|
.add_plugin(Box::new(waycast_plugins::projects::new()))
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
|
@ -261,7 +261,8 @@ impl GtkLauncherUI {
|
|||||||
|
|
||||||
let current_generation = *search_generation.borrow();
|
let current_generation = *search_generation.borrow();
|
||||||
let generation_check = search_generation.clone();
|
let generation_check = search_generation.clone();
|
||||||
let _timeout_id = glib::timeout_add_local(std::time::Duration::from_millis(150), move || {
|
let _timeout_id =
|
||||||
|
glib::timeout_add_local(std::time::Duration::from_millis(150), move || {
|
||||||
// Check if this search is still the current one
|
// Check if this search is still the current one
|
||||||
if *generation_check.borrow() != current_generation {
|
if *generation_check.borrow() != current_generation {
|
||||||
return glib::ControlFlow::Break; // This search was superseded
|
return glib::ControlFlow::Break; // This search was superseded
|
||||||
@ -277,14 +278,17 @@ impl GtkLauncherUI {
|
|||||||
let items: Vec<LauncherItemObject> = {
|
let items: Vec<LauncherItemObject> = {
|
||||||
let mut launcher_ref = launcher_clone.borrow_mut();
|
let mut launcher_ref = launcher_clone.borrow_mut();
|
||||||
let results = launcher_ref.search(&query);
|
let results = launcher_ref.search(&query);
|
||||||
results.iter().map(|entry| {
|
results
|
||||||
|
.iter()
|
||||||
|
.map(|entry| {
|
||||||
LauncherItemObject::new(
|
LauncherItemObject::new(
|
||||||
entry.title(),
|
entry.title(),
|
||||||
entry.description(),
|
entry.description(),
|
||||||
entry.icon(),
|
entry.icon(),
|
||||||
entry.id(),
|
entry.id(),
|
||||||
)
|
)
|
||||||
}).collect()
|
})
|
||||||
|
.collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update UI on main thread
|
// Update UI on main thread
|
||||||
@ -464,6 +468,7 @@ fn find_icon_file(
|
|||||||
size: &str,
|
size: &str,
|
||||||
icon_theme: &IconTheme,
|
icon_theme: &IconTheme,
|
||||||
) -> Option<std::path::PathBuf> {
|
) -> Option<std::path::PathBuf> {
|
||||||
|
println!("Icon: {}", icon_name);
|
||||||
let cache_key = format!("icon:{}:{}", icon_name, size);
|
let cache_key = format!("icon:{}:{}", icon_name, size);
|
||||||
let cache = waycast_core::cache::get();
|
let cache = waycast_core::cache::get();
|
||||||
|
|
||||||
|
@ -5,7 +5,12 @@ use waycast_plugins::projects::{
|
|||||||
type_scanner::TypeScanner,
|
type_scanner::TypeScanner,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Project {
|
||||||
|
project_type: String,
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
|
let mut projects: Vec<Project> = Vec::new();
|
||||||
let scanner = TypeScanner::new();
|
let scanner = TypeScanner::new();
|
||||||
let framework_detector = FrameworkDetector::new();
|
let framework_detector = FrameworkDetector::new();
|
||||||
if let Ok(entries) = std::fs::read_dir(PathBuf::from("/home/javi/projects")) {
|
if let Ok(entries) = std::fs::read_dir(PathBuf::from("/home/javi/projects")) {
|
||||||
@ -17,17 +22,25 @@ pub fn main() {
|
|||||||
{
|
{
|
||||||
let fw = framework_detector.detect(e.path().to_string_lossy().to_string().as_str());
|
let fw = framework_detector.detect(e.path().to_string_lossy().to_string().as_str());
|
||||||
|
|
||||||
|
let mut project_type: String = String::from("NONE");
|
||||||
|
|
||||||
if let Some(name) = fw {
|
if let Some(name) = fw {
|
||||||
println!("{}: {}", e.path().display(), name);
|
project_type = name;
|
||||||
} else {
|
} else {
|
||||||
println!("{}: {}", e.path().display(), "NONE");
|
let langs = scanner.scan(e.path(), Some(1));
|
||||||
|
if let Some(l) = langs.first() {
|
||||||
|
project_type = l.name.to_owned()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// let langs = scanner.scan(e.path(), Some(3));
|
|
||||||
// // let langs = lang_breakdown(&[e.path().to_str().unwrap()], &[]);
|
|
||||||
|
|
||||||
// let top: Vec<String> = langs.iter().map(|l| l.name.to_owned()).collect();
|
projects.push(Project {
|
||||||
|
project_type,
|
||||||
|
path: e.path().to_path_buf(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// println!("{}: {:?}", e.path().display(), top);
|
for p in projects {
|
||||||
}
|
println!("{}: {}", p.path.display(), p.project_type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,10 @@ pub enum Framework {
|
|||||||
Rails,
|
Rails,
|
||||||
Vue,
|
Vue,
|
||||||
NextJS,
|
NextJS,
|
||||||
|
Svelte,
|
||||||
|
Django,
|
||||||
|
Flask,
|
||||||
|
Fiber,
|
||||||
Ansible,
|
Ansible,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13,6 +17,98 @@ crate::frameworks! {
|
|||||||
files: ["composer.json"],
|
files: ["composer.json"],
|
||||||
json_checks: [("composer.json", "require.laravel/framework")],
|
json_checks: [("composer.json", "require.laravel/framework")],
|
||||||
},
|
},
|
||||||
|
Rails {
|
||||||
|
files: ["Gemfile"],
|
||||||
|
json_checks: [("package.json", "dependencies.rails"), ("package.json", "devDependencies.rails")],
|
||||||
|
custom: |project_path: &str| {
|
||||||
|
use crate::projects::framework_macro::{has_file, read_json_config};
|
||||||
|
|
||||||
|
// Check for Gemfile with rails gem
|
||||||
|
if let Ok(content) = std::fs::read_to_string(format!("{}/Gemfile", project_path)) {
|
||||||
|
if content.contains("gem 'rails'") || content.contains("gem \"rails\"") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Rails-specific directories
|
||||||
|
has_file(project_path, "config/application.rb") ||
|
||||||
|
has_file(project_path, "app/controllers") ||
|
||||||
|
has_file(project_path, "config/routes.rb")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NextJS {
|
||||||
|
files: ["package.json"],
|
||||||
|
json_checks: [("package.json", "dependencies.next"), ("package.json", "devDependencies.next")],
|
||||||
|
custom: |project_path: &str| {
|
||||||
|
use crate::projects::framework_macro::has_file;
|
||||||
|
has_file(project_path, "next.config.js") || has_file(project_path, "next.config.mjs")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Vue {
|
||||||
|
files: ["package.json"],
|
||||||
|
json_checks: [("package.json", "dependencies.vue"), ("package.json", "devDependencies.vue")],
|
||||||
|
custom: |project_path: &str| {
|
||||||
|
use crate::projects::framework_macro::has_file;
|
||||||
|
has_file(project_path, "vue.config.js") ||
|
||||||
|
has_file(project_path, "src/App.vue")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Svelte {
|
||||||
|
files: ["package.json"],
|
||||||
|
json_checks: [("package.json", "dependencies.svelte"), ("package.json", "devDependencies.svelte")],
|
||||||
|
custom: |project_path: &str| {
|
||||||
|
use crate::projects::framework_macro::has_file;
|
||||||
|
has_file(project_path, "svelte.config.js") ||
|
||||||
|
has_file(project_path, "src/App.svelte")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Django {
|
||||||
|
files: ["manage.py"],
|
||||||
|
custom: |project_path: &str| {
|
||||||
|
use crate::projects::framework_macro::has_file;
|
||||||
|
|
||||||
|
// Check for requirements.txt with Django
|
||||||
|
if let Ok(content) = std::fs::read_to_string(format!("{}/requirements.txt", project_path)) {
|
||||||
|
if content.contains("Django") || content.contains("django") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Django-specific files
|
||||||
|
has_file(project_path, "settings.py") ||
|
||||||
|
has_file(project_path, "wsgi.py") ||
|
||||||
|
has_file(project_path, "urls.py")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Flask {
|
||||||
|
custom: |project_path: &str| {
|
||||||
|
use crate::projects::framework_macro::has_file;
|
||||||
|
|
||||||
|
// Check for requirements.txt with Flask
|
||||||
|
if let Ok(content) = std::fs::read_to_string(format!("{}/requirements.txt", project_path)) {
|
||||||
|
if content.contains("Flask") || content.contains("flask") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for common Flask files
|
||||||
|
has_file(project_path, "app.py") ||
|
||||||
|
has_file(project_path, "main.py") ||
|
||||||
|
has_file(project_path, "run.py")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Fiber {
|
||||||
|
files: ["go.mod"],
|
||||||
|
custom: |project_path: &str| {
|
||||||
|
// Check for go.mod with fiber dependency
|
||||||
|
if let Ok(content) = std::fs::read_to_string(format!("{}/go.mod", project_path)) {
|
||||||
|
if content.contains("github.com/gofiber/fiber") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
},
|
||||||
|
},
|
||||||
Ansible {
|
Ansible {
|
||||||
directories: ["playbooks"],
|
directories: ["playbooks"],
|
||||||
custom: |project_path: &str| {
|
custom: |project_path: &str| {
|
||||||
|
@ -95,15 +95,19 @@ macro_rules! frameworks {
|
|||||||
}
|
}
|
||||||
)?
|
)?
|
||||||
|
|
||||||
// If we have files specified but no other checks, files existing means match
|
// If we reach here and ONLY files were specified (no other validation),
|
||||||
#[allow(unreachable_code)]
|
// then files existing means it's a match
|
||||||
{
|
{
|
||||||
$(
|
let has_other_checks = false
|
||||||
$(
|
$(|| { $(let _ = $dir;)* true })? // has directories
|
||||||
let _ = $file; // Use the file variable to indicate files were specified
|
$(|| { $(let _ = ($json_file, $json_path);)* true })? // has json_checks
|
||||||
return true;
|
$(|| { let _ = $custom_fn; true })?; // has custom
|
||||||
)*
|
|
||||||
)?
|
if !has_other_checks {
|
||||||
|
// Only files specified - if we got this far, files exist so it's a match
|
||||||
|
$($(let _ = $file; return true;)*)?
|
||||||
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
pub mod framework_detector;
|
pub mod framework_detector;
|
||||||
pub mod framework_macro;
|
pub mod framework_macro;
|
||||||
pub mod type_scanner;
|
pub mod type_scanner;
|
||||||
// TODO: Project type detection and icon
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
fs,
|
fs,
|
||||||
@ -10,14 +9,24 @@ use std::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use std::sync::LazyLock;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use waycast_core::{LaunchError, LauncherListItem, LauncherPlugin};
|
use waycast_core::{
|
||||||
|
cache::{Cache, CacheTTL},
|
||||||
|
LaunchError, LauncherListItem, LauncherPlugin,
|
||||||
|
};
|
||||||
use waycast_macros::{launcher_entry, plugin};
|
use waycast_macros::{launcher_entry, plugin};
|
||||||
|
|
||||||
|
use crate::projects::{framework_detector::FrameworkDetector, type_scanner::TypeScanner};
|
||||||
|
|
||||||
|
static TOKEI_SCANNER: LazyLock<TypeScanner> = LazyLock::new(TypeScanner::new);
|
||||||
|
static FRAMEWORK_DETECTOR: LazyLock<FrameworkDetector> = LazyLock::new(FrameworkDetector::new);
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ProjectEntry {
|
pub struct ProjectEntry {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
exec_command: Arc<str>,
|
exec_command: Arc<str>,
|
||||||
|
project_type: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LauncherListItem for ProjectEntry {
|
impl LauncherListItem for ProjectEntry {
|
||||||
@ -26,6 +35,11 @@ impl LauncherListItem for ProjectEntry {
|
|||||||
title: String::from(self.path.file_name().unwrap().to_string_lossy()),
|
title: String::from(self.path.file_name().unwrap().to_string_lossy()),
|
||||||
description: Some(self.path.to_string_lossy().to_string()),
|
description: Some(self.path.to_string_lossy().to_string()),
|
||||||
icon: {
|
icon: {
|
||||||
|
if let Some(t) = &self.project_type {
|
||||||
|
let icon_path = PathBuf::from("./devicons");
|
||||||
|
return icon_path.join(format!("{}.svg", t.to_lowercase())).to_string_lossy().to_string();
|
||||||
|
}
|
||||||
|
|
||||||
String::from("vscode")
|
String::from("vscode")
|
||||||
},
|
},
|
||||||
execute: {
|
execute: {
|
||||||
@ -118,9 +132,13 @@ impl LauncherPlugin for ProjectsPlugin {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let project_type = detect_project_type(
|
||||||
|
path.to_string_lossy().to_string().as_str(),
|
||||||
|
);
|
||||||
project_entries.push(ProjectEntry {
|
project_entries.push(ProjectEntry {
|
||||||
path,
|
path,
|
||||||
exec_command: Arc::clone(&exec_command),
|
exec_command: Arc::clone(&exec_command),
|
||||||
|
project_type,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,6 +157,19 @@ impl LauncherPlugin for ProjectsPlugin {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_list(&self) -> Vec<Box<dyn LauncherListItem>> {
|
||||||
|
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() {
|
||||||
|
for f in files.iter() {
|
||||||
|
entries.push(Box::new(f.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entries
|
||||||
|
}
|
||||||
|
|
||||||
fn filter(&self, query: &str) -> Vec<Box<dyn LauncherListItem>> {
|
fn filter(&self, query: &str) -> Vec<Box<dyn LauncherListItem>> {
|
||||||
if query.is_empty() {
|
if query.is_empty() {
|
||||||
return self.default_list();
|
return self.default_list();
|
||||||
@ -162,10 +193,6 @@ impl LauncherPlugin for ProjectsPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn get_config_value<T>(key: &str) -> Result<T> {
|
|
||||||
// waycast_config::config_file().get::<T>(format!("plugins.projects.{}", key))
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub fn new() -> ProjectsPlugin {
|
pub fn new() -> ProjectsPlugin {
|
||||||
let search_paths =
|
let search_paths =
|
||||||
match waycast_config::get::<HashSet<PathBuf>>("plugins.projects.search_paths") {
|
match waycast_config::get::<HashSet<PathBuf>>("plugins.projects.search_paths") {
|
||||||
@ -190,3 +217,31 @@ pub fn new() -> ProjectsPlugin {
|
|||||||
files: Arc::new(Mutex::new(Vec::new())),
|
files: Arc::new(Mutex::new(Vec::new())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn detect_project_type(path: &str) -> Option<String> {
|
||||||
|
let cache_key = format!("project_type:{}", path);
|
||||||
|
let cache = waycast_core::cache::get();
|
||||||
|
|
||||||
|
let detect_fn = |path| {
|
||||||
|
let fw = FRAMEWORK_DETECTOR.detect(path);
|
||||||
|
if let Some(name) = fw {
|
||||||
|
return Some(name);
|
||||||
|
} else {
|
||||||
|
let langs = TOKEI_SCANNER.scan(path, Some(1));
|
||||||
|
if let Some(l) = langs.first() {
|
||||||
|
return Some(l.name.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let result: Result<Option<String>, waycast_core::cache::errors::CacheError> =
|
||||||
|
cache.remember_with_ttl(&cache_key, CacheTTL::hours(24), || detect_fn(path));
|
||||||
|
|
||||||
|
if let Ok(project_type) = result {
|
||||||
|
return project_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_fn(path)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user