feat: add FilesPlugin for file searching and integrate into KLauncher
This commit is contained in:
@@ -39,18 +39,34 @@ impl Plugin for CalcPlugin {
|
||||
let expr = query.strip_prefix('=').unwrap_or(query);
|
||||
match evalexpr::eval_number(expr) {
|
||||
Ok(n) if n.is_finite() => {
|
||||
let display = if n.fract() == 0.0 {
|
||||
format!("= {}", n as i64)
|
||||
let value_str = if n.fract() == 0.0 {
|
||||
format!("{}", n as i64)
|
||||
} else {
|
||||
format!("= {n}")
|
||||
format!("{n}")
|
||||
};
|
||||
let display = format!("= {value_str}");
|
||||
let expr_owned = expr.to_string();
|
||||
let clipboard_val = value_str;
|
||||
vec![SearchResult {
|
||||
id: ResultId::new("calc-result"),
|
||||
title: ResultTitle::new(display),
|
||||
description: None,
|
||||
description: Some(format!("{expr_owned} · Enter to copy")),
|
||||
icon: None,
|
||||
score: Score::new(90),
|
||||
on_execute: Arc::new(|| {}),
|
||||
on_execute: Arc::new(move || {
|
||||
if std::process::Command::new("wl-copy").arg(&clipboard_val).spawn().is_err() {
|
||||
use std::io::Write;
|
||||
if let Ok(mut child) = std::process::Command::new("xclip")
|
||||
.args(["-selection", "clipboard"])
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
{
|
||||
if let Some(stdin) = child.stdin.as_mut() {
|
||||
let _ = stdin.write_all(clipboard_val.as_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
}]
|
||||
}
|
||||
_ => vec![],
|
||||
|
||||
@@ -8,3 +8,6 @@ name = "plugin_files"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
k-launcher-kernel = { path = "../../k-launcher-kernel" }
|
||||
tokio = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use k_launcher_kernel::{Plugin, PluginName, ResultId, ResultTitle, Score, SearchResult};
|
||||
|
||||
pub struct FilesPlugin;
|
||||
|
||||
impl FilesPlugin {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FilesPlugin {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_query(query: &str) -> Option<String> {
|
||||
if query.starts_with("~/") {
|
||||
let home = std::env::var("HOME").ok()?;
|
||||
Some(format!("{}{}", home, &query[1..]))
|
||||
} else if query.starts_with('/') {
|
||||
Some(query.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Plugin for FilesPlugin {
|
||||
fn name(&self) -> PluginName {
|
||||
"files"
|
||||
}
|
||||
|
||||
async fn search(&self, query: &str) -> Vec<SearchResult> {
|
||||
let expanded = match expand_query(query) {
|
||||
Some(p) => p,
|
||||
None => return vec![],
|
||||
};
|
||||
let path = Path::new(&expanded);
|
||||
let (parent, prefix) = if path.is_dir() {
|
||||
(path.to_path_buf(), String::new())
|
||||
} else {
|
||||
let parent = path.parent().unwrap_or(Path::new("/")).to_path_buf();
|
||||
let prefix = path
|
||||
.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.unwrap_or("")
|
||||
.to_lowercase();
|
||||
(parent, prefix)
|
||||
};
|
||||
|
||||
let entries = match std::fs::read_dir(&parent) {
|
||||
Ok(e) => e,
|
||||
Err(_) => return vec![],
|
||||
};
|
||||
|
||||
entries
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| {
|
||||
if prefix.is_empty() {
|
||||
return true;
|
||||
}
|
||||
e.file_name()
|
||||
.to_str()
|
||||
.map(|n| n.to_lowercase().starts_with(&prefix))
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.take(20)
|
||||
.enumerate()
|
||||
.map(|(i, entry)| {
|
||||
let full_path = entry.path();
|
||||
let name = entry.file_name().to_string_lossy().to_string();
|
||||
let is_dir = full_path.is_dir();
|
||||
let title = if is_dir {
|
||||
format!("{name}/")
|
||||
} else {
|
||||
name
|
||||
};
|
||||
let path_str = full_path.to_string_lossy().to_string();
|
||||
SearchResult {
|
||||
id: ResultId::new(format!("file-{i}")),
|
||||
title: ResultTitle::new(title),
|
||||
description: Some(path_str.clone()),
|
||||
icon: None,
|
||||
score: Score::new(50),
|
||||
on_execute: Arc::new(move || {
|
||||
let _ = std::process::Command::new("xdg-open").arg(&path_str).spawn();
|
||||
}),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn files_ignores_non_path_query() {
|
||||
let p = FilesPlugin::new();
|
||||
assert!(p.search("firefox").await.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn files_handles_root() {
|
||||
let p = FilesPlugin::new();
|
||||
let results = p.search("/").await;
|
||||
assert!(!results.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user