diff --git a/Cargo.lock b/Cargo.lock index 3bb909b..3313342 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,12 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "ahash" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0453232ace82dee0dd0b4c87a59bd90f7b53b314f3e0f61fe2ee7c8a16482289" + [[package]] name = "ahash" version = "0.8.12" @@ -37,6 +43,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "aligned" version = "0.4.3" @@ -351,7 +366,7 @@ dependencies = [ "anyhow", "arrayvec", "log", - "nom", + "nom 8.0.0", "num-rational", "v_frame", ] @@ -903,6 +918,15 @@ dependencies = [ "libloading", ] +[[package]] +name = "dlv-list" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68df3f2b690c1b86e65ef7830956aededf3cb0a16f898f79b9a6f421a7b6211b" +dependencies = [ + "rand 0.8.5", +] + [[package]] name = "document-features" version = "0.2.12" @@ -940,7 +964,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0dfe0859f3fb1bc6424c57d41e10e9093fe938f426b691e42272c2f336d915c" dependencies = [ - "ahash", + "ahash 0.8.12", "bytemuck", "document-features", "egui", @@ -976,7 +1000,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25dd34cec49ab55d85ebf70139cb1ccd29c977ef6b6ba4fe85489d6877ee9ef3" dependencies = [ - "ahash", + "ahash 0.8.12", "bitflags 2.11.0", "emath", "epaint", @@ -991,7 +1015,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d319dfef570f699b6e9114e235e862a2ddcf75f0d1a061de9e1328d92146d820" dependencies = [ - "ahash", + "ahash 0.8.12", "bytemuck", "document-features", "egui", @@ -1011,7 +1035,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d9dfbb78fe4eb9c3a39ad528b90ee5915c252e77bbab9d4ebc576541ab67e13" dependencies = [ - "ahash", + "ahash 0.8.12", "arboard", "bytemuck", "egui", @@ -1030,7 +1054,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "910906e3f042ea6d2378ec12a6fd07698e14ddae68aed2d819ffe944a73aab9e" dependencies = [ - "ahash", + "ahash 0.8.12", "bytemuck", "egui", "glow", @@ -1091,7 +1115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fcc0f5a7c613afd2dee5e4b30c3e6acafb8ad6f0edb06068811f708a67c562" dependencies = [ "ab_glyph", - "ahash", + "ahash 0.8.12", "bytemuck", "ecolor", "emath", @@ -1246,6 +1270,15 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "file-locker" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75ae8b5984a4863d8a32109a848d038bd6d914f20f010cc141375f7a183c41cf" +dependencies = [ + "nix", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1306,7 +1339,7 @@ checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" dependencies = [ "fontconfig-parser", "log", - "memmap2", + "memmap2 0.9.10", "slotmap", "tinyvec", "ttf-parser", @@ -1348,6 +1381,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "freedesktop_entry_parser" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db9c27b72f19a99a895f8ca89e2d26e4ef31013376e56fdafef697627306c3e4" +dependencies = [ + "nom 7.1.3", + "thiserror 1.0.69", +] + [[package]] name = "futures" version = "0.3.32" @@ -1696,6 +1739,15 @@ dependencies = [ "smallvec", ] +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +dependencies = [ + "ahash 0.4.8", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -2227,6 +2279,7 @@ dependencies = [ "plugin-cmd", "plugin-files", "tokio", + "tracing-subscriber", ] [[package]] @@ -2339,6 +2392,12 @@ dependencies = [ "smallvec", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "leb128fmt" version = "0.1.0" @@ -2410,6 +2469,29 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" +[[package]] +name = "linicon" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee8c5653188a809616c97296180a0547a61dba205bcdcbdd261dbd022a25fd9" +dependencies = [ + "file-locker", + "freedesktop_entry_parser", + "linicon-theme", + "memmap2 0.5.10", + "thiserror 1.0.69", +] + +[[package]] +name = "linicon-theme" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f8240c33bb08c5d8b8cdea87b683b05e61037aa76ff26bef40672cc6ecbb80" +dependencies = [ + "freedesktop_entry_parser", + "rust-ini", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -2473,6 +2555,15 @@ dependencies = [ "libc", ] +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "maybe-rayon" version = "0.1.1" @@ -2489,6 +2580,15 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + [[package]] name = "memmap2" version = "0.9.10" @@ -2537,6 +2637,12 @@ dependencies = [ "paste", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2692,12 +2798,34 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nohash-hasher" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nom" version = "8.0.0" @@ -2713,6 +2841,25 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "nucleo-matcher" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf33f538733d1a5a3494b836ba913207f14d9d4a1d3cd67030c5061bdd2cac85" +dependencies = [ + "memchr", + "unicode-segmentation", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -3185,6 +3332,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered-multimap" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c672c7ad9ec066e428c00eb917124a06f08db19e2584de982cc34b1f4c12485" +dependencies = [ + "dlv-list", + "hashbrown 0.9.1", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -3318,9 +3475,12 @@ version = "0.1.0" dependencies = [ "async-trait", "k-launcher-kernel", + "linicon", + "nucleo-matcher", "serde", "serde_json", "tokio", + "tracing", "xdg", ] @@ -3540,14 +3700,35 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "rand_chacha", - "rand_core", + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", ] [[package]] @@ -3557,7 +3738,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", ] [[package]] @@ -3608,8 +3798,8 @@ dependencies = [ "num-traits", "paste", "profiling", - "rand", - "rand_chacha", + "rand 0.9.2", + "rand_chacha 0.9.0", "simd_helpers", "thiserror 2.0.18", "v_frame", @@ -3706,6 +3896,23 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + [[package]] name = "renderdoc-sys" version = "1.1.0" @@ -3744,6 +3951,16 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" +[[package]] +name = "rust-ini" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63471c4aa97a1cf8332a5f97709a79a4234698de6a1f5087faf66f2dae810e22" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -3844,7 +4061,7 @@ checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" dependencies = [ "ab_glyph", "log", - "memmap2", + "memmap2 0.9.10", "smithay-client-toolkit 0.19.2", "tiny-skia", ] @@ -3924,6 +4141,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -4029,7 +4255,7 @@ dependencies = [ "cursor-icon", "libc", "log", - "memmap2", + "memmap2 0.9.10", "rustix 0.38.44", "thiserror 1.0.69", "wayland-backend", @@ -4054,7 +4280,7 @@ dependencies = [ "cursor-icon", "libc", "log", - "memmap2", + "memmap2 0.9.10", "rustix 1.1.4", "thiserror 2.0.18", "wayland-backend", @@ -4099,7 +4325,7 @@ dependencies = [ "bytemuck", "fastrand", "js-sys", - "memmap2", + "memmap2 0.9.10", "ndk", "objc2 0.6.4", "objc2-core-foundation", @@ -4292,6 +4518,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "tiff" version = "0.10.3" @@ -4477,6 +4712,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -4647,6 +4912,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "version_check" version = "0.9.5" @@ -5607,7 +5878,7 @@ version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" dependencies = [ - "ahash", + "ahash 0.8.12", "android-activity", "atomic-waker", "bitflags 2.11.0", @@ -5622,7 +5893,7 @@ dependencies = [ "dpi", "js-sys", "libc", - "memmap2", + "memmap2 0.9.10", "ndk", "objc2 0.5.2", "objc2-app-kit 0.2.2", diff --git a/crates/k-launcher-config/src/lib.rs b/crates/k-launcher-config/src/lib.rs index 7432300..21b184f 100644 --- a/crates/k-launcher-config/src/lib.rs +++ b/crates/k-launcher-config/src/lib.rs @@ -107,8 +107,7 @@ impl Default for PluginsCfg { } pub fn load() -> Config { - let path = dirs::config_dir() - .map(|d| d.join("k-launcher").join("config.toml")); + let path = dirs::config_dir().map(|d| d.join("k-launcher").join("config.toml")); let Some(path) = path else { return Config::default(); }; diff --git a/crates/k-launcher-kernel/src/lib.rs b/crates/k-launcher-kernel/src/lib.rs index 76b6cde..3558460 100644 --- a/crates/k-launcher-kernel/src/lib.rs +++ b/crates/k-launcher-kernel/src/lib.rs @@ -29,7 +29,9 @@ impl ResultTitle { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] +#[derive( + Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, +)] pub struct Score(u32); impl Score { @@ -104,7 +106,10 @@ pub struct Kernel { impl Kernel { pub fn new(plugins: Vec>, max_results: usize) -> Self { - Self { plugins, max_results } + Self { + plugins, + max_results, + } } pub async fn search(&self, query: &str) -> Vec { diff --git a/crates/k-launcher-os-bridge/src/unix_launcher.rs b/crates/k-launcher-os-bridge/src/unix_launcher.rs index 9c67ae7..9c8173e 100644 --- a/crates/k-launcher-os-bridge/src/unix_launcher.rs +++ b/crates/k-launcher-os-bridge/src/unix_launcher.rs @@ -1,5 +1,5 @@ -use std::process::{Command, Stdio}; use std::os::unix::process::CommandExt; +use std::process::{Command, Stdio}; use k_launcher_kernel::{AppLauncher, LaunchAction}; @@ -77,21 +77,31 @@ impl AppLauncher for UnixAppLauncher { .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()) - .pre_exec(|| { libc::setsid(); Ok(()) }) + .pre_exec(|| { + libc::setsid(); + Ok(()) + }) .spawn() }; } } LaunchAction::SpawnInTerminal(cmd) => { - let Some((term_bin, term_args)) = resolve_terminal() else { return }; + let Some((term_bin, term_args)) = resolve_terminal() else { + return; + }; let _ = unsafe { Command::new(&term_bin) .args(&term_args) - .arg("sh").arg("-c").arg(cmd) + .arg("sh") + .arg("-c") + .arg(cmd) .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()) - .pre_exec(|| { libc::setsid(); Ok(()) }) + .pre_exec(|| { + libc::setsid(); + Ok(()) + }) .spawn() }; } diff --git a/crates/k-launcher-plugin-host/src/lib.rs b/crates/k-launcher-plugin-host/src/lib.rs index 7849326..e601d75 100644 --- a/crates/k-launcher-plugin-host/src/lib.rs +++ b/crates/k-launcher-plugin-host/src/lib.rs @@ -43,7 +43,9 @@ async fn do_search( io: &mut ProcessIo, query: &str, ) -> Result, Box> { - let line = serde_json::to_string(&Query { query: query.to_string() })?; + let line = serde_json::to_string(&Query { + query: query.to_string(), + })?; io.stdin.write_all(line.as_bytes()).await?; io.stdin.write_all(b"\n").await?; io.stdin.flush().await?; @@ -143,7 +145,9 @@ mod tests { #[test] fn query_serializes_correctly() { - let q = Query { query: "firefox".to_string() }; + let q = Query { + query: "firefox".to_string(), + }; assert_eq!(serde_json::to_string(&q).unwrap(), r#"{"query":"firefox"}"#); } @@ -155,21 +159,27 @@ mod tests { assert_eq!(results[0].id, "1"); assert_eq!(results[0].title, "Firefox"); assert_eq!(results[0].score, 80); - assert!(matches!(&results[0].action, ExternalAction::SpawnProcess { cmd } if cmd == "firefox")); + assert!( + matches!(&results[0].action, ExternalAction::SpawnProcess { cmd } if cmd == "firefox") + ); } #[test] fn result_parses_copy_action() { let json = r#"[{"id":"c","title":"= 4","score":90,"action":{"type":"CopyToClipboard","text":"4"}}]"#; let results: Vec = serde_json::from_str(json).unwrap(); - assert!(matches!(&results[0].action, ExternalAction::CopyToClipboard { text } if text == "4")); + assert!( + matches!(&results[0].action, ExternalAction::CopyToClipboard { text } if text == "4") + ); } #[test] fn result_parses_open_path_action() { let json = r#"[{"id":"f","title":"/home/user","score":50,"action":{"type":"OpenPath","path":"/home/user"}}]"#; let results: Vec = serde_json::from_str(json).unwrap(); - assert!(matches!(&results[0].action, ExternalAction::OpenPath { path } if path == "/home/user")); + assert!( + matches!(&results[0].action, ExternalAction::OpenPath { path } if path == "/home/user") + ); } #[test] @@ -182,7 +192,8 @@ mod tests { #[test] fn result_parses_missing_optional_fields() { - let json = r#"[{"id":"x","title":"X","score":10,"action":{"type":"SpawnProcess","cmd":"x"}}]"#; + let json = + r#"[{"id":"x","title":"X","score":10,"action":{"type":"SpawnProcess","cmd":"x"}}]"#; let results: Vec = serde_json::from_str(json).unwrap(); assert!(results[0].description.is_none()); assert!(results[0].icon.is_none()); diff --git a/crates/k-launcher-ui-egui/src/app.rs b/crates/k-launcher-ui-egui/src/app.rs index 391669f..4f46a3e 100644 --- a/crates/k-launcher-ui-egui/src/app.rs +++ b/crates/k-launcher-ui-egui/src/app.rs @@ -127,11 +127,20 @@ impl eframe::App for KLauncherApp { ui.set_width(ui.available_width()); for (i, result) in self.results.iter().enumerate() { let is_selected = i == self.selected; - let bg = if is_selected { SELECTED_BG } else { Color32::TRANSPARENT }; + let bg = if is_selected { + SELECTED_BG + } else { + Color32::TRANSPARENT + }; let row_frame = egui::Frame::new() .fill(bg) - .inner_margin(egui::Margin { left: 8, right: 8, top: 6, bottom: 6 }) + .inner_margin(egui::Margin { + left: 8, + right: 8, + top: 6, + bottom: 6, + }) .corner_radius(egui::CornerRadius::same(4)); row_frame.show(ui, |ui| { diff --git a/crates/k-launcher-ui/src/app.rs b/crates/k-launcher-ui/src/app.rs index 6c8a686..cfaedc8 100644 --- a/crates/k-launcher-ui/src/app.rs +++ b/crates/k-launcher-ui/src/app.rs @@ -1,10 +1,9 @@ use std::sync::Arc; use iced::{ - Border, Color, Element, Length, Size, Subscription, Task, - event, + Border, Color, Element, Length, Size, Subscription, Task, event, keyboard::{Event as KeyEvent, Key, key::Named}, - widget::{column, container, image, row, scrollable, svg, text, text_input, Space}, + widget::{Space, column, container, image, row, scrollable, svg, text, text_input}, window, }; @@ -126,8 +125,13 @@ fn view(state: &KLauncherApp) -> Element<'_, Message> { .padding(12) .size(cfg.search_font_size) .style(|theme, _status| { - let mut s = iced::widget::text_input::default(theme, iced::widget::text_input::Status::Active); - s.border = Border { color: Color::TRANSPARENT, width: 0.0, radius: 0.0.into() }; + let mut s = + iced::widget::text_input::default(theme, iced::widget::text_input::Status::Active); + s.border = Border { + color: Color::TRANSPARENT, + width: 0.0, + radius: 0.0.into(), + }; s }); @@ -147,26 +151,27 @@ fn view(state: &KLauncherApp) -> Element<'_, Message> { Color::from_rgba8(255, 255, 255, 0.07) }; let icon_el: Element<'_, Message> = match &result.icon { - Some(p) if p.ends_with(".svg") => - svg(svg::Handle::from_path(p)).width(24).height(24).into(), - Some(p) => - image(image::Handle::from_path(p)).width(24).height(24).into(), + Some(p) if p.ends_with(".svg") => { + svg(svg::Handle::from_path(p)).width(24).height(24).into() + } + Some(p) => image(image::Handle::from_path(p)) + .width(24) + .height(24) + .into(), None => Space::new().width(24).height(24).into(), }; let title_col: Element<'_, Message> = if let Some(desc) = &result.description { column![ text(result.title.as_str()).size(title_size), - text(desc).size(desc_size).color(Color::from_rgba8(210, 215, 230, 1.0)), + text(desc) + .size(desc_size) + .color(Color::from_rgba8(210, 215, 230, 1.0)), ] .into() } else { text(result.title.as_str()).size(title_size).into() }; - container( - row![icon_el, title_col] - .spacing(8) - .align_y(iced::Center), - ) + container(row![icon_el, title_col].spacing(8).align_y(iced::Center)) .width(Length::Fill) .padding([6, 12]) .style(move |_theme| container::Style { @@ -209,7 +214,8 @@ fn view(state: &KLauncherApp) -> Element<'_, Message> { .into() }); - let mut content_children: Vec> = vec![search_bar.into(), results_list.into()]; + let mut content_children: Vec> = + vec![search_bar.into(), results_list.into()]; if let Some(err) = maybe_error { content_children.push(err); } @@ -261,15 +267,15 @@ pub fn run( update, view, ) - .title("K-Launcher") - .subscription(subscription) - .window(window::Settings { - size: Size::new(wc.width, wc.height), - position: window::Position::Centered, - decorations: wc.decorations, - transparent: wc.transparent, - resizable: wc.resizable, - ..Default::default() - }) - .run() + .title("K-Launcher") + .subscription(subscription) + .window(window::Settings { + size: Size::new(wc.width, wc.height), + position: window::Position::Centered, + decorations: wc.decorations, + transparent: wc.transparent, + resizable: wc.resizable, + ..Default::default() + }) + .run() } diff --git a/crates/k-launcher/src/client.rs b/crates/k-launcher/src/client.rs index 5975aef..658b3d2 100644 --- a/crates/k-launcher/src/client.rs +++ b/crates/k-launcher/src/client.rs @@ -1,8 +1,8 @@ use std::io::Write; pub fn send_show() -> Result<(), Box> { - let runtime_dir = std::env::var("XDG_RUNTIME_DIR") - .unwrap_or_else(|_| "/run/user/1000".to_string()); + let runtime_dir = + std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/run/user/1000".to_string()); let socket_path = format!("{runtime_dir}/k-launcher.sock"); let mut stream = std::os::unix::net::UnixStream::connect(&socket_path)?; stream.write_all(b"show\n")?; diff --git a/crates/k-launcher/src/main.rs b/crates/k-launcher/src/main.rs index 107b57b..76d0189 100644 --- a/crates/k-launcher/src/main.rs +++ b/crates/k-launcher/src/main.rs @@ -5,9 +5,9 @@ use std::sync::Arc; use k_launcher_kernel::Kernel; use k_launcher_os_bridge::UnixAppLauncher; use k_launcher_plugin_host::ExternalPlugin; -use plugin_apps::{AppsPlugin, frecency::FrecencyStore}; #[cfg(target_os = "linux")] use plugin_apps::linux::FsDesktopEntrySource; +use plugin_apps::{AppsPlugin, frecency::FrecencyStore}; use plugin_calc::CalcPlugin; use plugin_cmd::CmdPlugin; use plugin_files::FilesPlugin; @@ -36,14 +36,27 @@ fn run_ui() -> iced::Result { let frecency = FrecencyStore::load(); let mut plugins: Vec> = vec![]; - if cfg.plugins.cmd { plugins.push(Arc::new(CmdPlugin::new())); } - if cfg.plugins.calc { plugins.push(Arc::new(CalcPlugin::new())); } - if cfg.plugins.files { plugins.push(Arc::new(FilesPlugin::new())); } - if cfg.plugins.apps { - plugins.push(Arc::new(AppsPlugin::new(FsDesktopEntrySource::new(), frecency))); + if cfg.plugins.cmd { + plugins.push(Arc::new(CmdPlugin::new())); + } + if cfg.plugins.calc { + plugins.push(Arc::new(CalcPlugin::new())); + } + if cfg.plugins.files { + plugins.push(Arc::new(FilesPlugin::new())); + } + if cfg.plugins.apps { + plugins.push(Arc::new(AppsPlugin::new( + FsDesktopEntrySource::new(), + frecency, + ))); } for ext in &cfg.plugins.external { - plugins.push(Arc::new(ExternalPlugin::new(&ext.name, &ext.path, ext.args.clone()))); + plugins.push(Arc::new(ExternalPlugin::new( + &ext.name, + &ext.path, + ext.args.clone(), + ))); } let kernel: Arc = diff --git a/crates/k-launcher/src/main_egui.rs b/crates/k-launcher/src/main_egui.rs index 05219ad..c72bc21 100644 --- a/crates/k-launcher/src/main_egui.rs +++ b/crates/k-launcher/src/main_egui.rs @@ -2,9 +2,9 @@ use std::sync::Arc; use k_launcher_kernel::Kernel; use k_launcher_os_bridge::UnixAppLauncher; -use plugin_apps::{AppsPlugin, frecency::FrecencyStore}; #[cfg(target_os = "linux")] use plugin_apps::linux::FsDesktopEntrySource; +use plugin_apps::{AppsPlugin, frecency::FrecencyStore}; use plugin_calc::CalcPlugin; use plugin_cmd::CmdPlugin; use plugin_files::FilesPlugin; @@ -12,12 +12,15 @@ use plugin_files::FilesPlugin; fn main() -> Result<(), Box> { let launcher = Arc::new(UnixAppLauncher::new()); let frecency = FrecencyStore::load(); - let kernel: Arc = Arc::new(Kernel::new(vec![ - Arc::new(CmdPlugin::new()), - Arc::new(CalcPlugin::new()), - Arc::new(FilesPlugin::new()), - Arc::new(AppsPlugin::new(FsDesktopEntrySource::new(), frecency)), - ], 8)); + let kernel: Arc = Arc::new(Kernel::new( + vec![ + Arc::new(CmdPlugin::new()), + Arc::new(CalcPlugin::new()), + Arc::new(FilesPlugin::new()), + Arc::new(AppsPlugin::new(FsDesktopEntrySource::new(), frecency)), + ], + 8, + )); k_launcher_ui_egui::run(kernel, launcher)?; Ok(()) } diff --git a/crates/plugins/plugin-apps/src/frecency.rs b/crates/plugins/plugin-apps/src/frecency.rs index c7e2160..fe997fb 100644 --- a/crates/plugins/plugin-apps/src/frecency.rs +++ b/crates/plugins/plugin-apps/src/frecency.rs @@ -24,7 +24,10 @@ impl FrecencyStore { .ok() .and_then(|s| serde_json::from_str(&s).ok()) .unwrap_or_default(); - Arc::new(Self { path, data: Mutex::new(data) }) + Arc::new(Self { + path, + data: Mutex::new(data), + }) } #[cfg(test)] @@ -53,7 +56,10 @@ impl FrecencyStore { .unwrap_or_default() .as_secs(); let mut data = self.data.lock().unwrap(); - let entry = data.entry(id.to_string()).or_insert(Entry { count: 0, last_used: 0 }); + let entry = data.entry(id.to_string()).or_insert(Entry { + count: 0, + last_used: 0, + }); entry.count += 1; entry.last_used = now; if let Some(parent) = self.path.parent() { @@ -72,7 +78,13 @@ impl FrecencyStore { .unwrap_or_default() .as_secs(); let age_secs = now.saturating_sub(entry.last_used); - let decay = if age_secs < 3600 { 4 } else if age_secs < 86400 { 2 } else { 1 }; + let decay = if age_secs < 3600 { + 4 + } else if age_secs < 86400 { + 2 + } else { + 1 + }; entry.count * decay } @@ -86,7 +98,13 @@ impl FrecencyStore { .iter() .map(|(id, entry)| { let age_secs = now.saturating_sub(entry.last_used); - let decay = if age_secs < 3600 { 4 } else if age_secs < 86400 { 2 } else { 1 }; + let decay = if age_secs < 3600 { + 4 + } else if age_secs < 86400 { + 2 + } else { + 1 + }; (id.clone(), entry.count * decay) }) .collect(); diff --git a/crates/plugins/plugin-apps/src/lib.rs b/crates/plugins/plugin-apps/src/lib.rs index 5823ec5..e000a57 100644 --- a/crates/plugins/plugin-apps/src/lib.rs +++ b/crates/plugins/plugin-apps/src/lib.rs @@ -91,7 +91,10 @@ impl AppsPlugin { let id = format!("app-{}", e.name.as_str()); let keywords_lc = e.keywords.iter().map(|k| k.to_lowercase()).collect(); #[cfg(target_os = "linux")] - let icon = e.icon.as_ref().and_then(|p| linux::resolve_icon_path(p.as_str())); + let icon = e + .icon + .as_ref() + .and_then(|p| linux::resolve_icon_path(p.as_str())); #[cfg(not(target_os = "linux"))] let icon: Option = None; let exec = e.exec.as_str().to_string(); @@ -117,13 +120,16 @@ impl AppsPlugin { } fn initials(name_lc: &str) -> String { - name_lc.split_whitespace().filter_map(|w| w.chars().next()).collect() + name_lc + .split_whitespace() + .filter_map(|w| w.chars().next()) + .collect() } fn score_match(name: &str, query: &str) -> Option { use nucleo_matcher::{ - pattern::{CaseMatching, Normalization, Pattern}, Config, Matcher, Utf32Str, + pattern::{CaseMatching, Normalization, Pattern}, }; let mut matcher = Matcher::new(Config::DEFAULT); @@ -136,7 +142,11 @@ fn score_match(name: &str, query: &str) -> Option { if let Some(s) = score { let name_lc = name.to_lowercase(); let query_lc = query.to_lowercase(); - let bonus: u32 = if initials(&name_lc).starts_with(&query_lc) { 20 } else { 0 }; + let bonus: u32 = if initials(&name_lc).starts_with(&query_lc) { + 20 + } else { + 0 + }; Some(s.saturating_add(bonus)) } else { None @@ -162,7 +172,9 @@ impl Plugin for AppsPlugin { async fn search(&self, query: &str) -> Vec { if query.is_empty() { - return self.frecency.top_ids(5) + return self + .frecency + .top_ids(5) .iter() .filter_map(|id| { let e = self.entries.get(id)?; @@ -185,7 +197,10 @@ impl Plugin for AppsPlugin { .values() .filter_map(|e| { let score = score_match(e.name.as_str(), query).or_else(|| { - e.keywords_lc.iter().any(|k| k.contains(&query_lc)).then_some(50) + e.keywords_lc + .iter() + .any(|k| k.contains(&query_lc)) + .then_some(50) })?; Some(SearchResult { id: ResultId::new(&e.id), @@ -238,7 +253,14 @@ mod tests { Self { entries: entries .into_iter() - .map(|(n, e, kw)| (n.to_string(), e.to_string(), None, kw.into_iter().map(|s| s.to_string()).collect())) + .map(|(n, e, kw)| { + ( + n.to_string(), + e.to_string(), + None, + kw.into_iter().map(|s| s.to_string()).collect(), + ) + }) .collect(), } } @@ -261,20 +283,29 @@ mod tests { #[tokio::test] async fn apps_prefix_match() { - let p = AppsPlugin::new(MockSource::with(vec![("Firefox", "firefox")]), ephemeral_frecency()); + let p = AppsPlugin::new( + MockSource::with(vec![("Firefox", "firefox")]), + ephemeral_frecency(), + ); let results = p.search("fire").await; assert_eq!(results[0].title.as_str(), "Firefox"); } #[tokio::test] async fn apps_no_match_returns_empty() { - let p = AppsPlugin::new(MockSource::with(vec![("Firefox", "firefox")]), ephemeral_frecency()); + let p = AppsPlugin::new( + MockSource::with(vec![("Firefox", "firefox")]), + ephemeral_frecency(), + ); assert!(p.search("zz").await.is_empty()); } #[tokio::test] async fn apps_empty_query_no_frecency_returns_empty() { - let p = AppsPlugin::new(MockSource::with(vec![("Firefox", "firefox")]), ephemeral_frecency()); + let p = AppsPlugin::new( + MockSource::with(vec![("Firefox", "firefox")]), + ephemeral_frecency(), + ); assert!(p.search("").await.is_empty()); } @@ -322,9 +353,15 @@ mod tests { #[tokio::test] async fn apps_fuzzy_typo_match() { - let p = AppsPlugin::new(MockSource::with(vec![("Firefox", "firefox")]), ephemeral_frecency()); + let p = AppsPlugin::new( + MockSource::with(vec![("Firefox", "firefox")]), + ephemeral_frecency(), + ); let results = p.search("frefox").await; - assert!(!results.is_empty(), "nucleo should fuzzy-match 'frefox' to 'Firefox'"); + assert!( + !results.is_empty(), + "nucleo should fuzzy-match 'frefox' to 'Firefox'" + ); assert!(results[0].score.value() > 0); } diff --git a/crates/plugins/plugin-apps/src/linux.rs b/crates/plugins/plugin-apps/src/linux.rs index 2e52a9f..6aa8c07 100644 --- a/crates/plugins/plugin-apps/src/linux.rs +++ b/crates/plugins/plugin-apps/src/linux.rs @@ -1,7 +1,7 @@ use std::path::Path; -use crate::{AppName, DesktopEntry, DesktopEntrySource, ExecCommand, IconPath}; use crate::humanize_category; +use crate::{AppName, DesktopEntry, DesktopEntrySource, ExecCommand, IconPath}; pub struct FsDesktopEntrySource; @@ -154,13 +154,15 @@ fn parse_desktop_file(path: &Path) -> Option { "Type" if !is_application => is_application = value.trim() == "Application", "NoDisplay" => no_display = value.trim().eq_ignore_ascii_case("true"), "Categories" if category.is_none() => { - category = value.trim() + category = value + .trim() .split(';') .find(|s| !s.is_empty()) .map(|s| humanize_category(s.trim())); } "Keywords" if keywords.is_empty() => { - keywords = value.trim() + keywords = value + .trim() .split(';') .filter(|s| !s.is_empty()) .map(|s| s.trim().to_string()) @@ -202,7 +204,10 @@ mod exec_tests { #[test] fn preserves_quoted_value() { - assert_eq!(clean_exec(r#"app --arg="value" %U"#), r#"app --arg="value""#); + assert_eq!( + clean_exec(r#"app --arg="value" %U"#), + r#"app --arg="value""# + ); } #[test] diff --git a/crates/plugins/plugin-calc/src/lib.rs b/crates/plugins/plugin-calc/src/lib.rs index 3fd010d..a7bfcf6 100644 --- a/crates/plugins/plugin-calc/src/lib.rs +++ b/crates/plugins/plugin-calc/src/lib.rs @@ -22,8 +22,8 @@ fn strip_numeric_separators(expr: &str) -> String { } const MATH_FNS: &[&str] = &[ - "sqrt", "sin", "cos", "tan", "asin", "acos", "atan", - "ln", "log2", "log10", "exp", "abs", "ceil", "floor", "round", + "sqrt", "sin", "cos", "tan", "asin", "acos", "atan", "ln", "log2", "log10", "exp", "abs", + "ceil", "floor", "round", ]; fn should_eval(query: &str) -> bool { @@ -36,8 +36,8 @@ fn should_eval(query: &str) -> bool { || MATH_FNS.iter().any(|f| q.starts_with(f)) } -static MATH_CTX: LazyLock> = - LazyLock::new(|| { +static MATH_CTX: LazyLock> = LazyLock::new( + || { use evalexpr::*; context_map! { "pi" => float std::f64::consts::PI, @@ -59,7 +59,8 @@ static MATH_CTX: LazyLock Function::new(|a: &Value| Ok(Value::from_float(a.as_number()?.round()))) } .expect("static math context must be valid") - }); + }, +); #[async_trait] impl Plugin for CalcPlugin { diff --git a/crates/plugins/plugin-files/src/lib.rs b/crates/plugins/plugin-files/src/lib.rs index 447d73b..08aa3dc 100644 --- a/crates/plugins/plugin-files/src/lib.rs +++ b/crates/plugins/plugin-files/src/lib.rs @@ -76,11 +76,7 @@ impl Plugin for FilesPlugin { 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 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}")),