Working out a good interface to reduce plugin boilerplate
This commit is contained in:
parent
3177c4f3f6
commit
f81acdc962
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -913,6 +913,15 @@ dependencies = [
|
|||||||
"waycast-plugins",
|
"waycast-plugins",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "waycast-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "waycast-plugins"
|
name = "waycast-plugins"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -923,6 +932,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"waycast-core",
|
"waycast-core",
|
||||||
|
"waycast-macros",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"waycast-core",
|
"waycast-core",
|
||||||
|
"waycast-macros",
|
||||||
"waycast-plugins",
|
"waycast-plugins",
|
||||||
"waycast-gtk"
|
"waycast-gtk"
|
||||||
]
|
]
|
||||||
|
@ -13,9 +13,9 @@ fn main() {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
app.connect_activate(|app| {
|
app.connect_activate(|app| {
|
||||||
let mut file_search_plugin = waycast_plugins::file_search::new();
|
let file_search_plugin = waycast_plugins::file_search::new();
|
||||||
|
|
||||||
match file_search_plugin.add_search_path("/home/javi/working-files/DJ Music/") {
|
match waycast_plugins::file_search::add_search_path("/home/javi/working-files/DJ Music/") {
|
||||||
Err(e) => eprintln!("{}", e),
|
Err(e) => eprintln!("{}", e),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
12
waycast-macros/Cargo.toml
Normal file
12
waycast-macros/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "waycast-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
proc-macro2 = "1.0"
|
||||||
|
syn = { version = "2.0", features = ["full"] }
|
||||||
|
quote = "1.0"
|
269
waycast-macros/src/lib.rs
Normal file
269
waycast-macros/src/lib.rs
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
use proc_macro::TokenStream;
|
||||||
|
use proc_macro2::Ident;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
parse_macro_input,
|
||||||
|
Expr, ExprLit, Lit, LitInt, LitStr, Result, Token,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Plugin configuration parsed from the macro input
|
||||||
|
struct PluginConfig {
|
||||||
|
struct_name: Ident,
|
||||||
|
name: LitStr,
|
||||||
|
priority: Option<LitInt>,
|
||||||
|
description: Option<LitStr>,
|
||||||
|
prefix: Option<LitStr>,
|
||||||
|
by_prefix_only: Option<bool>,
|
||||||
|
init_fn: Option<Ident>,
|
||||||
|
default_list_fn: Option<Ident>,
|
||||||
|
filter_fn: Option<Ident>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for PluginConfig {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
// Parse "struct StructName;"
|
||||||
|
input.parse::<Token![struct]>()?;
|
||||||
|
let struct_name = input.parse::<Ident>()?;
|
||||||
|
input.parse::<Token![;]>()?;
|
||||||
|
|
||||||
|
let mut name = None;
|
||||||
|
let mut priority = None;
|
||||||
|
let mut description = None;
|
||||||
|
let mut prefix = None;
|
||||||
|
let mut by_prefix_only = None;
|
||||||
|
let mut init_fn = None;
|
||||||
|
let mut default_list_fn = None;
|
||||||
|
let mut filter_fn = None;
|
||||||
|
|
||||||
|
// Parse comma-separated key: value pairs
|
||||||
|
while !input.is_empty() {
|
||||||
|
if input.peek(Token![,]) {
|
||||||
|
input.parse::<Token![,]>()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let key: Ident = input.parse()?;
|
||||||
|
input.parse::<Token![:]>()?;
|
||||||
|
|
||||||
|
match key.to_string().as_str() {
|
||||||
|
"name" => {
|
||||||
|
let lit: LitStr = input.parse()?;
|
||||||
|
name = Some(lit);
|
||||||
|
}
|
||||||
|
"priority" => {
|
||||||
|
let lit: LitInt = input.parse()?;
|
||||||
|
priority = Some(lit);
|
||||||
|
}
|
||||||
|
"description" => {
|
||||||
|
let lit: LitStr = input.parse()?;
|
||||||
|
description = Some(lit);
|
||||||
|
}
|
||||||
|
"prefix" => {
|
||||||
|
let lit: LitStr = input.parse()?;
|
||||||
|
prefix = Some(lit);
|
||||||
|
}
|
||||||
|
"by_prefix_only" => {
|
||||||
|
let expr: Expr = input.parse()?;
|
||||||
|
if let Expr::Lit(ExprLit { lit: Lit::Bool(lit_bool), .. }) = expr {
|
||||||
|
by_prefix_only = Some(lit_bool.value);
|
||||||
|
} else {
|
||||||
|
return Err(syn::Error::new_spanned(expr, "Expected boolean literal"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"init" => {
|
||||||
|
let fn_name: Ident = input.parse()?;
|
||||||
|
init_fn = Some(fn_name);
|
||||||
|
}
|
||||||
|
"default_list" => {
|
||||||
|
let fn_name: Ident = input.parse()?;
|
||||||
|
default_list_fn = Some(fn_name);
|
||||||
|
}
|
||||||
|
"filter" => {
|
||||||
|
let fn_name: Ident = input.parse()?;
|
||||||
|
filter_fn = Some(fn_name);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let key_str = key.to_string();
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
key,
|
||||||
|
format!("Unknown plugin configuration key: {}", key_str),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
let name = name.ok_or_else(|| {
|
||||||
|
syn::Error::new_spanned(&struct_name, "Plugin must have a 'name' field")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(PluginConfig {
|
||||||
|
struct_name,
|
||||||
|
name,
|
||||||
|
priority,
|
||||||
|
description,
|
||||||
|
prefix,
|
||||||
|
by_prefix_only,
|
||||||
|
init_fn,
|
||||||
|
default_list_fn,
|
||||||
|
filter_fn,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PluginConfig {
|
||||||
|
/// Generate the full plugin struct name with "Plugin" suffix
|
||||||
|
fn plugin_struct_name(&self) -> Ident {
|
||||||
|
let name_str = format!("{}Plugin", self.struct_name);
|
||||||
|
Ident::new(&name_str, self.struct_name.span())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate the implementation of LauncherPlugin trait
|
||||||
|
fn generate_plugin_impl(&self) -> proc_macro2::TokenStream {
|
||||||
|
let plugin_struct_name = self.plugin_struct_name();
|
||||||
|
let name_str = &self.name;
|
||||||
|
|
||||||
|
// Generate priority method
|
||||||
|
let priority = if let Some(ref priority_lit) = self.priority {
|
||||||
|
quote! { #priority_lit }
|
||||||
|
} else {
|
||||||
|
quote! { 100 }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate description method
|
||||||
|
let description = if let Some(ref desc_lit) = self.description {
|
||||||
|
quote! { Some(#desc_lit.to_string()) }
|
||||||
|
} else {
|
||||||
|
quote! { None }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate prefix method
|
||||||
|
let prefix = if let Some(ref prefix_lit) = self.prefix {
|
||||||
|
quote! { Some(#prefix_lit.to_string()) }
|
||||||
|
} else {
|
||||||
|
quote! { None }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate by_prefix_only method
|
||||||
|
let by_prefix_only = if let Some(value) = self.by_prefix_only {
|
||||||
|
quote! { #value }
|
||||||
|
} else {
|
||||||
|
quote! { false }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate init method
|
||||||
|
let init_method = if let Some(ref init_fn) = self.init_fn {
|
||||||
|
quote! {
|
||||||
|
fn init(&self) {
|
||||||
|
#init_fn(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
fn init(&self) {
|
||||||
|
// Default empty init
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate default_list method
|
||||||
|
let default_list_method = if let Some(ref default_list_fn) = self.default_list_fn {
|
||||||
|
quote! {
|
||||||
|
fn default_list(&self) -> Vec<Box<dyn waycast_core::LauncherListItem>> {
|
||||||
|
#default_list_fn(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
fn default_list(&self) -> Vec<Box<dyn waycast_core::LauncherListItem>> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate filter method
|
||||||
|
let filter_method = if let Some(ref filter_fn) = self.filter_fn {
|
||||||
|
quote! {
|
||||||
|
fn filter(&self, query: &str) -> Vec<Box<dyn waycast_core::LauncherListItem>> {
|
||||||
|
#filter_fn(self, query)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
fn filter(&self, query: &str) -> Vec<Box<dyn waycast_core::LauncherListItem>> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
impl waycast_core::LauncherPlugin for #plugin_struct_name {
|
||||||
|
#init_method
|
||||||
|
|
||||||
|
fn name(&self) -> String {
|
||||||
|
#name_str.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn priority(&self) -> i32 {
|
||||||
|
#priority
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> Option<String> {
|
||||||
|
#description
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prefix(&self) -> Option<String> {
|
||||||
|
#prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
fn by_prefix_only(&self) -> bool {
|
||||||
|
#by_prefix_only
|
||||||
|
}
|
||||||
|
|
||||||
|
#default_list_method
|
||||||
|
|
||||||
|
#filter_method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate the complete plugin code
|
||||||
|
fn generate(&self) -> proc_macro2::TokenStream {
|
||||||
|
let plugin_struct_name = self.plugin_struct_name();
|
||||||
|
let plugin_impl = self.generate_plugin_impl();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
pub struct #plugin_struct_name {}
|
||||||
|
|
||||||
|
impl #plugin_struct_name {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
#plugin_struct_name {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#plugin_impl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The main plugin! proc macro
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// ```rust
|
||||||
|
/// plugin! {
|
||||||
|
/// struct Calculator;
|
||||||
|
/// name: "calculator",
|
||||||
|
/// priority: 500,
|
||||||
|
/// prefix: "calc",
|
||||||
|
/// filter: calc_filter,
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn plugin(input: TokenStream) -> TokenStream {
|
||||||
|
let config = parse_macro_input!(input as PluginConfig);
|
||||||
|
config.generate().into()
|
||||||
|
}
|
@ -5,6 +5,7 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
waycast-core = { path = "../waycast-core" }
|
waycast-core = { path = "../waycast-core" }
|
||||||
|
waycast-macros = { path = "../waycast-macros" }
|
||||||
directories = "6.0.0"
|
directories = "6.0.0"
|
||||||
gio = "0.21.1"
|
gio = "0.21.1"
|
||||||
glib = "0.21.1"
|
glib = "0.21.1"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use gio::{prelude::*, AppInfo, DesktopAppInfo, Icon};
|
use gio::{prelude::*, AppInfo, DesktopAppInfo, Icon};
|
||||||
use waycast_core::{LaunchError, LauncherListItem, LauncherPlugin};
|
use waycast_core::{LaunchError, LauncherListItem};
|
||||||
|
use crate::plugin;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DesktopEntry {
|
pub struct DesktopEntry {
|
||||||
@ -85,63 +86,43 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin! {
|
||||||
|
struct Drun;
|
||||||
|
name: "drun",
|
||||||
|
priority: 1000,
|
||||||
|
description: "List and launch an installed application",
|
||||||
|
prefix: "app",
|
||||||
|
default_list: drun_default_list,
|
||||||
|
filter: drun_filter,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new() -> DrunPlugin {
|
pub fn new() -> DrunPlugin {
|
||||||
DrunPlugin {}
|
DrunPlugin::new()
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DrunPlugin {}
|
|
||||||
|
|
||||||
impl LauncherPlugin for DrunPlugin {
|
|
||||||
fn init(&self) {
|
|
||||||
// TODO: Load apps into memory
|
|
||||||
// TODO: Find and cache Icons
|
|
||||||
}
|
|
||||||
fn name(&self) -> String {
|
|
||||||
return String::from("drun");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn priority(&self) -> i32 {
|
|
||||||
return 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description(&self) -> Option<String> {
|
|
||||||
return Some(String::from("List and launch an installed application"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prefix to isolate results to only use this plugin
|
|
||||||
fn prefix(&self) -> Option<String> {
|
|
||||||
return Some(String::from("app"));
|
|
||||||
}
|
|
||||||
// Only search/use this plugin if the prefix was typed
|
|
||||||
fn by_prefix_only(&self) -> bool {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Actual item searching functions
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,9 @@ use std::sync::Arc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use walkdir::{DirEntry, WalkDir};
|
use walkdir::{DirEntry, WalkDir};
|
||||||
|
use waycast_macros::plugin;
|
||||||
|
|
||||||
use waycast_core::{LaunchError, LauncherListItem, LauncherPlugin};
|
use waycast_core::{LaunchError, LauncherListItem};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct FileEntry {
|
struct FileEntry {
|
||||||
@ -103,27 +104,16 @@ pub fn default_search_list() -> Vec<PathBuf> {
|
|||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new() -> FileSearchPlugin {
|
// Global state for file search
|
||||||
return FileSearchPlugin {
|
static mut FILE_SEARCH_DATA: Option<FileSearchData> = None;
|
||||||
search_paths: default_search_list(),
|
|
||||||
skip_dirs: vec![
|
|
||||||
String::from("vendor"),
|
|
||||||
String::from("node_modules"),
|
|
||||||
String::from("cache"),
|
|
||||||
String::from("zig-cache"),
|
|
||||||
],
|
|
||||||
files: Arc::new(Mutex::new(Vec::new())),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FileSearchPlugin {
|
struct FileSearchData {
|
||||||
search_paths: Vec<PathBuf>,
|
search_paths: Vec<PathBuf>,
|
||||||
skip_dirs: Vec<String>,
|
skip_dirs: Vec<String>,
|
||||||
// Running list of files in memory
|
|
||||||
files: Arc<Mutex<Vec<FileEntry>>>,
|
files: Arc<Mutex<Vec<FileEntry>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileSearchPlugin {
|
impl FileSearchData {
|
||||||
pub fn add_search_path<P: AsRef<Path>>(&mut self, path: P) -> Result<(), String> {
|
pub fn add_search_path<P: AsRef<Path>>(&mut self, path: P) -> Result<(), String> {
|
||||||
let p = path.as_ref();
|
let p = path.as_ref();
|
||||||
|
|
||||||
@ -180,6 +170,43 @@ impl FileSearchPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_file_search_data() -> &'static FileSearchData {
|
||||||
|
unsafe {
|
||||||
|
FILE_SEARCH_DATA.as_ref().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_file_search_data_mut() -> &'static mut FileSearchData {
|
||||||
|
unsafe {
|
||||||
|
FILE_SEARCH_DATA.as_mut().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new() -> FileSearchPlugin {
|
||||||
|
unsafe {
|
||||||
|
FILE_SEARCH_DATA = Some(FileSearchData {
|
||||||
|
search_paths: default_search_list(),
|
||||||
|
skip_dirs: vec![
|
||||||
|
String::from("vendor"),
|
||||||
|
String::from("node_modules"),
|
||||||
|
String::from("cache"),
|
||||||
|
String::from("zig-cache"),
|
||||||
|
],
|
||||||
|
files: Arc::new(Mutex::new(Vec::new())),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSearchPlugin::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_search_path<P: AsRef<Path>>(path: P) -> Result<(), String> {
|
||||||
|
get_file_search_data_mut().add_search_path(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_skip_dir(directory_name: String) -> Result<(), String> {
|
||||||
|
get_file_search_data_mut().add_skip_dir(directory_name)
|
||||||
|
}
|
||||||
|
|
||||||
fn skip_hidden(entry: &DirEntry) -> bool {
|
fn skip_hidden(entry: &DirEntry) -> bool {
|
||||||
entry
|
entry
|
||||||
.file_name()
|
.file_name()
|
||||||
@ -196,67 +223,58 @@ fn skip_dir(entry: &DirEntry, dirs: &Vec<String>) -> bool {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LauncherPlugin for FileSearchPlugin {
|
fn file_search_default_list(_plugin: &FileSearchPlugin) -> Vec<Box<dyn LauncherListItem>> {
|
||||||
fn init(&self) {
|
Vec::new()
|
||||||
// Start async file scanning with 500ms timeout
|
}
|
||||||
let self_clone = FileSearchPlugin {
|
|
||||||
search_paths: self.search_paths.clone(),
|
|
||||||
skip_dirs: self.skip_dirs.clone(),
|
|
||||||
files: Arc::clone(&self.files),
|
|
||||||
};
|
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
fn file_search_filter(_plugin: &FileSearchPlugin, query: &str) -> Vec<Box<dyn LauncherListItem>> {
|
||||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
if query.is_empty() {
|
||||||
rt.block_on(async {
|
return file_search_default_list(_plugin);
|
||||||
self_clone
|
|
||||||
.init_with_timeout(Duration::from_millis(2000))
|
|
||||||
.await;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
fn name(&self) -> String {
|
|
||||||
return String::from("File search");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn priority(&self) -> i32 {
|
let mut entries: Vec<Box<dyn LauncherListItem>> = Vec::new();
|
||||||
return 900;
|
let data = get_file_search_data();
|
||||||
}
|
|
||||||
|
|
||||||
fn description(&self) -> Option<String> {
|
// Try to get files without blocking - if indexing is still in progress, return empty
|
||||||
None
|
if let Ok(files) = data.files.try_lock() {
|
||||||
}
|
for f in files.iter() {
|
||||||
|
if let Some(file_name) = f.path.file_name() {
|
||||||
fn prefix(&self) -> Option<String> {
|
let cmp = file_name.to_string_lossy().to_lowercase();
|
||||||
Some(String::from("f"))
|
if cmp.contains(&query.to_lowercase()) {
|
||||||
}
|
entries.push(Box::new(f.clone()));
|
||||||
|
|
||||||
fn by_prefix_only(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_list(&self) -> Vec<Box<dyn LauncherListItem>> {
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn filter(&self, query: &str) -> Vec<Box<dyn LauncherListItem>> {
|
|
||||||
if query.is_empty() {
|
|
||||||
return self.default_list();
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entries
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file_search_init(_plugin: &FileSearchPlugin) {
|
||||||
|
let data = get_file_search_data();
|
||||||
|
let data_clone = FileSearchData {
|
||||||
|
search_paths: data.search_paths.clone(),
|
||||||
|
skip_dirs: data.skip_dirs.clone(),
|
||||||
|
files: Arc::clone(&data.files),
|
||||||
|
};
|
||||||
|
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
|
rt.block_on(async {
|
||||||
|
data_clone
|
||||||
|
.init_with_timeout(Duration::from_millis(2000))
|
||||||
|
.await;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin! {
|
||||||
|
struct FileSearch;
|
||||||
|
name: "Files",
|
||||||
|
priority: 500,
|
||||||
|
description: "Search and open files",
|
||||||
|
prefix: "f",
|
||||||
|
init: file_search_init,
|
||||||
|
default_list: file_search_default_list,
|
||||||
|
filter: file_search_filter
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,5 @@
|
|||||||
pub mod drun;
|
pub mod drun;
|
||||||
pub mod file_search;
|
pub mod file_search;
|
||||||
|
|
||||||
|
// Re-export the macro for external use
|
||||||
|
pub use waycast_macros::plugin;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user