breaking api changes: improve API for gui and reduce cloning
* change signature of gui::show to include option with factory trait to create new element instead of boolean * change signature of get_[sub_]elements the newer signature makes it way clearer what the semantic behind the value is. * improve how completion with tab is handled * added config option, so tab can be used to submit
This commit is contained in:
parent
d6e3d91c5c
commit
6ce81a56d5
17 changed files with 700 additions and 444 deletions
153
Cargo.lock
generated
153
Cargo.lock
generated
|
@ -2,6 +2,16 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "Inflector"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.24.2"
|
||||
|
@ -361,9 +371,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.38"
|
||||
version = "4.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000"
|
||||
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
@ -371,9 +381,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.38"
|
||||
version = "4.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120"
|
||||
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
@ -383,9 +393,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.32"
|
||||
version = "4.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
|
||||
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
|
@ -1462,6 +1472,16 @@ dependencies = [
|
|||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-kit"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
|
@ -1941,6 +1961,12 @@ dependencies = [
|
|||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
|
@ -2069,6 +2095,25 @@ version = "0.11.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
|
@ -2093,15 +2138,16 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.34.2"
|
||||
version = "0.35.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4b93974b3d3aeaa036504b8eefd4c039dced109171c1ae973f1dc63b2c7e4b2"
|
||||
checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"memchr",
|
||||
"ntapi",
|
||||
"objc2-core-foundation",
|
||||
"windows 0.57.0",
|
||||
"objc2-io-kit",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2131,7 +2177,7 @@ checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9"
|
|||
dependencies = [
|
||||
"quick-xml",
|
||||
"thiserror 2.0.12",
|
||||
"windows 0.61.1",
|
||||
"windows",
|
||||
"windows-version",
|
||||
]
|
||||
|
||||
|
@ -2501,16 +2547,6 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143"
|
||||
dependencies = [
|
||||
"windows-core 0.57.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.61.1"
|
||||
|
@ -2518,7 +2554,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419"
|
||||
dependencies = [
|
||||
"windows-collections",
|
||||
"windows-core 0.61.2",
|
||||
"windows-core",
|
||||
"windows-future",
|
||||
"windows-link",
|
||||
"windows-numerics",
|
||||
|
@ -2530,19 +2566,7 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
|
||||
dependencies = [
|
||||
"windows-core 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
|
||||
dependencies = [
|
||||
"windows-implement 0.57.0",
|
||||
"windows-interface 0.57.0",
|
||||
"windows-result 0.1.2",
|
||||
"windows-targets 0.52.6",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2551,10 +2575,10 @@ version = "0.61.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
dependencies = [
|
||||
"windows-implement 0.60.0",
|
||||
"windows-interface 0.59.1",
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result 0.3.4",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
|
@ -2564,22 +2588,11 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
|
||||
dependencies = [
|
||||
"windows-core 0.61.2",
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
"windows-threading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
|
@ -2591,17 +2604,6 @@ dependencies = [
|
|||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
|
@ -2625,19 +2627,10 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
|
||||
dependencies = [
|
||||
"windows-core 0.61.2",
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
|
@ -2867,7 +2860,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "worf"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"crossbeam",
|
||||
|
@ -2896,15 +2889,29 @@ dependencies = [
|
|||
"wl-clipboard-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "worf-hyprspace"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"hyprland",
|
||||
"log",
|
||||
"regex",
|
||||
"serde",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"worf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "worf-hyprswitch"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dirs 6.0.0",
|
||||
"env_logger",
|
||||
"freedesktop-icons",
|
||||
"hyprland",
|
||||
"log",
|
||||
"rayon",
|
||||
"sysinfo",
|
||||
"toml",
|
||||
|
|
|
@ -3,6 +3,7 @@ members = [
|
|||
"worf",
|
||||
"examples/worf-warden",
|
||||
"examples/worf-hyprswitch",
|
||||
"examples/worf-hyprspace",
|
||||
]
|
||||
|
||||
resolver = "3"
|
||||
|
|
|
@ -7,9 +7,7 @@ edition = "2024"
|
|||
worf = {path = "../../worf"}
|
||||
env_logger = "0.11.8"
|
||||
hyprland = "0.4.0-beta.2"
|
||||
sysinfo = "0.34.2"
|
||||
sysinfo = "0.35.2"
|
||||
freedesktop-icons = "0.4.0"
|
||||
rayon = "1.10.0"
|
||||
toml = "0.8.22"
|
||||
log = "0.4.27"
|
||||
dirs = "6.0.0"
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
use std::{collections::HashMap, env, fs, path::PathBuf, sync::Arc, thread};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
env, fs,
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
thread,
|
||||
};
|
||||
|
||||
use hyprland::{
|
||||
dispatch::{DispatchType, WindowIdentifier},
|
||||
|
@ -12,7 +18,7 @@ use worf::{
|
|||
config::{self, Config},
|
||||
desktop,
|
||||
desktop::EntryType,
|
||||
gui::{self, ItemProvider, MenuItem},
|
||||
gui::{self, ExpandMode, ItemProvider, MenuItem, ProviderData},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -108,12 +114,18 @@ impl WindowProvider {
|
|||
}
|
||||
|
||||
impl ItemProvider<Window> for WindowProvider {
|
||||
fn get_elements(&mut self, _: Option<&str>) -> (bool, Vec<MenuItem<Window>>) {
|
||||
(false, self.windows.clone())
|
||||
fn get_elements(&mut self, query: Option<&str>) -> ProviderData<Window> {
|
||||
if query.is_some() {
|
||||
ProviderData { items: None }
|
||||
} else {
|
||||
ProviderData {
|
||||
items: Some(self.windows.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sub_elements(&mut self, _: &MenuItem<Window>) -> (bool, Option<Vec<MenuItem<Window>>>) {
|
||||
(false, None)
|
||||
fn get_sub_elements(&mut self, _: &MenuItem<Window>) -> ProviderData<Window> {
|
||||
ProviderData { items: None }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,15 +144,21 @@ fn main() -> Result<(), String> {
|
|||
.init();
|
||||
|
||||
let args = config::parse_args();
|
||||
let config = config::load_config(Some(&args)).unwrap_or(args);
|
||||
let config = Arc::new(RwLock::new(
|
||||
config::load_config(Some(&args)).unwrap_or(args),
|
||||
));
|
||||
|
||||
let cache_path =
|
||||
desktop::cache_file_path(&config, "worf-hyprswitch").map_err(|err| err.to_string())?;
|
||||
let cache_path = desktop::cache_file_path(&config.read().unwrap(), "worf-hyprswitch")
|
||||
.map_err(|err| err.to_string())?;
|
||||
let mut cache = load_icon_cache(&cache_path).map_err(|e| e.to_string())?;
|
||||
|
||||
let provider = WindowProvider::new(&config, &cache)?;
|
||||
let windows = provider.windows.clone();
|
||||
let result = gui::show(config, provider, false, None, None).map_err(|e| e.to_string())?;
|
||||
let provider = Arc::new(Mutex::new(WindowProvider::new(
|
||||
&config.read().unwrap(),
|
||||
&cache,
|
||||
)?));
|
||||
let windows = provider.lock().unwrap().windows.clone();
|
||||
let result = gui::show(config, provider, None, None, ExpandMode::Verbatim, None)
|
||||
.map_err(|e| e.to_string())?;
|
||||
let update_cache = thread::spawn(move || {
|
||||
windows.iter().for_each(|item| {
|
||||
if let Some(window) = &item.data {
|
||||
|
@ -158,13 +176,15 @@ fn main() -> Result<(), String> {
|
|||
}
|
||||
});
|
||||
|
||||
if let Some(window) = result.menu.data {
|
||||
let return_value = if let Some(window) = result.menu.data {
|
||||
hyprland::dispatch::Dispatch::call(DispatchType::FocusWindow(WindowIdentifier::Address(
|
||||
window.address,
|
||||
)))
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(update_cache.join().unwrap().map_err(|e| e.to_string())?)
|
||||
.map_err(|e| e.to_string())
|
||||
} else {
|
||||
Err("No window data found".to_owned())
|
||||
}
|
||||
};
|
||||
|
||||
update_cache.join().unwrap().map_err(|e| e.to_string())?;
|
||||
return_value
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::{collections::HashMap, env, process::Command, thread::sleep, time::Duration};
|
||||
use std::{collections::HashMap, env, process::Command, thread::sleep, time::Duration, sync::{Arc, Mutex, RwLock}};
|
||||
|
||||
use worf::{
|
||||
config::{self, Config, CustomKeyHintLocation, Key},
|
||||
desktop::{copy_to_clipboard, spawn_fork},
|
||||
gui::{self, CustomKeyHint, CustomKeys, ItemProvider, KeyBinding, MenuItem, Modifier},
|
||||
gui::{self, CustomKeyHint, CustomKeys, ItemProvider, KeyBinding, MenuItem, Modifier, ProviderData, ExpandMode},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -75,15 +75,21 @@ impl PasswordProvider {
|
|||
}
|
||||
|
||||
impl ItemProvider<MenuItemMetaData> for PasswordProvider {
|
||||
fn get_elements(&mut self, _: Option<&str>) -> (bool, Vec<MenuItem<MenuItemMetaData>>) {
|
||||
(false, self.items.clone())
|
||||
fn get_elements(&mut self, query: Option<&str>) -> ProviderData<MenuItemMetaData> {
|
||||
if query.is_some() {
|
||||
ProviderData { items: None }
|
||||
} else {
|
||||
ProviderData {
|
||||
items: Some(self.items.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sub_elements(
|
||||
&mut self,
|
||||
_: &MenuItem<MenuItemMetaData>,
|
||||
) -> (bool, Option<Vec<MenuItem<MenuItemMetaData>>>) {
|
||||
(false, None)
|
||||
) -> ProviderData<MenuItemMetaData> {
|
||||
ProviderData { items: None }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -265,12 +271,13 @@ fn key_lock() -> KeyBinding {
|
|||
}
|
||||
}
|
||||
|
||||
fn show(config: Config, provider: PasswordProvider) -> Result<(), String> {
|
||||
fn show(config: Arc<RwLock<Config>>, provider: Arc<Mutex<PasswordProvider>>) -> Result<(), String> {
|
||||
match gui::show(
|
||||
config.clone(),
|
||||
Arc::clone(&config),
|
||||
provider,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
ExpandMode::Verbatim,
|
||||
Some(CustomKeys {
|
||||
bindings: vec![
|
||||
key_type_all(),
|
||||
|
@ -294,7 +301,7 @@ fn show(config: Config, provider: PasswordProvider) -> Result<(), String> {
|
|||
Ok(selection) => {
|
||||
if let Some(meta) = selection.menu.data {
|
||||
if meta.ids.len() > 1 {
|
||||
return show(config, PasswordProvider::sub_provider(meta.ids)?);
|
||||
return show(config, Arc::new(Mutex::new(PasswordProvider::sub_provider(meta.ids)?)));
|
||||
}
|
||||
|
||||
let id = meta.ids.first().unwrap_or(&selection.menu.label);
|
||||
|
@ -344,7 +351,7 @@ fn main() -> Result<(), String> {
|
|||
.init();
|
||||
|
||||
let args = config::parse_args();
|
||||
let config = config::load_config(Some(&args)).unwrap_or(args);
|
||||
let config = Arc::new(RwLock::new(config::load_config(Some(&args)).unwrap_or(args)));
|
||||
|
||||
if !groups().contains("input") {
|
||||
log::error!(
|
||||
|
@ -359,6 +366,6 @@ fn main() -> Result<(), String> {
|
|||
}
|
||||
|
||||
// todo eventually use a propper rust client for this, for now rbw is good enough
|
||||
let provider = PasswordProvider::new(&config)?;
|
||||
let provider = Arc::new(Mutex::new(PasswordProvider::new(&config.read().unwrap())?));
|
||||
show(config, provider)
|
||||
}
|
||||
|
|
|
@ -420,7 +420,9 @@ impl FromStr for Key {
|
|||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Parser)]
|
||||
#[clap(about = "Worf is a wofi clone written in rust, it aims to be a drop-in replacement")]
|
||||
#[clap(
|
||||
about = "Worf is a wofi like launcher, written in rust, it aims to be a drop-in replacement"
|
||||
)]
|
||||
#[derive(Default)]
|
||||
pub struct Config {
|
||||
/// Forks the menu so you can close the terminal
|
||||
|
@ -681,6 +683,10 @@ pub struct Config {
|
|||
/// Defaults to false.
|
||||
#[clap(long = "blurred-background-fullscreen")]
|
||||
blurred_background_fullscreen: Option<bool>,
|
||||
|
||||
/// Allow submitting selected entry with expand key if there is only 1 item left.
|
||||
#[clap(long = "submit-with-expand")]
|
||||
submit_with_expand: Option<bool>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
@ -786,6 +792,10 @@ impl Config {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn set_prompt(&mut self, val: String) {
|
||||
self.prompt = Some(val);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn height(&self) -> String {
|
||||
self.height.clone().unwrap_or("40%".to_owned())
|
||||
|
@ -978,16 +988,17 @@ impl Config {
|
|||
pub fn blurred_background_fullscreen(&self) -> bool {
|
||||
self.blurred_background_fullscreen.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn submit_with_expand(&self) -> bool {
|
||||
self.submit_with_expand.unwrap_or(true)
|
||||
}
|
||||
}
|
||||
|
||||
fn default_false() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
// fn default_true() -> bool {
|
||||
// true
|
||||
// }
|
||||
|
||||
#[must_use]
|
||||
pub fn parse_args() -> Config {
|
||||
Config::parse()
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
marker::PhantomData,
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
thread,
|
||||
|
@ -39,8 +40,9 @@ use crate::{
|
|||
desktop::known_image_extension_regex_pattern,
|
||||
};
|
||||
|
||||
type ArcMenuMap<T> = Arc<RwLock<HashMap<FlowBoxChild, MenuItem<T>>>>;
|
||||
type ArcProvider<T> = Arc<Mutex<dyn ItemProvider<T> + Send>>;
|
||||
pub type ArcMenuMap<T> = Arc<RwLock<HashMap<FlowBoxChild, MenuItem<T>>>>;
|
||||
pub type ArcProvider<T> = Arc<Mutex<dyn ItemProvider<T> + Send>>;
|
||||
pub type ArcFactory<T> = Arc<Mutex<dyn ItemFactory<T> + Send>>;
|
||||
|
||||
pub struct Selection<T: Clone + Send> {
|
||||
pub menu: MenuItem<T>,
|
||||
|
@ -48,9 +50,42 @@ pub struct Selection<T: Clone + Send> {
|
|||
}
|
||||
type SelectionSender<T> = Sender<Result<Selection<T>, Error>>;
|
||||
|
||||
pub struct ProviderData<T: Clone> {
|
||||
pub items: Option<Vec<MenuItem<T>>>,
|
||||
}
|
||||
|
||||
pub trait ItemProvider<T: Clone> {
|
||||
fn get_elements(&mut self, search: Option<&str>) -> (bool, Vec<MenuItem<T>>);
|
||||
fn get_sub_elements(&mut self, item: &MenuItem<T>) -> (bool, Option<Vec<MenuItem<T>>>);
|
||||
fn get_elements(&mut self, search: Option<&str>) -> ProviderData<T>;
|
||||
|
||||
/// Get elements below the given menu entry.
|
||||
/// Will be called for completion
|
||||
/// If (true, None) is returned and submit-accept is set in the config, this
|
||||
/// will be handled the name way as pressing enter (or the configured submit key).
|
||||
fn get_sub_elements(&mut self, item: &MenuItem<T>) -> ProviderData<T>;
|
||||
}
|
||||
|
||||
pub trait ItemFactory<T: Clone> {
|
||||
fn new_menu_item(&self, label: String) -> Option<MenuItem<T>>;
|
||||
}
|
||||
|
||||
/// Default generic item factory that creates an almost empty menu item
|
||||
/// Without data, no icon, and sort score of 0.
|
||||
pub struct DefaultItemFactory<T: Clone> {
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Clone> DefaultItemFactory<T> {
|
||||
pub fn new() -> DefaultItemFactory<T> {
|
||||
DefaultItemFactory::<T> {
|
||||
_marker: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> ItemFactory<T> for DefaultItemFactory<T> {
|
||||
fn new_menu_item(&self, label: String) -> Option<MenuItem<T>> {
|
||||
Some(MenuItem::new(label, None, None, vec![], None, 0.0, None))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Anchor> for Edge {
|
||||
|
@ -121,6 +156,10 @@ pub struct MenuItem<T: Clone> {
|
|||
/// Allows to store arbitrary additional information
|
||||
pub data: Option<T>,
|
||||
|
||||
// /// If set to true, the item is _not_ an intermediate thing
|
||||
// /// and is acceptable, i.e. will close the UI
|
||||
// pub allow_submit: bool,
|
||||
// todo
|
||||
/// Score the item got in the current search
|
||||
search_sort_score: f64,
|
||||
/// True if the item is visible
|
||||
|
@ -351,6 +390,12 @@ pub enum Modifier {
|
|||
None,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum ExpandMode {
|
||||
Verbatim,
|
||||
WithSpace,
|
||||
}
|
||||
|
||||
fn modifiers_from_mask(mask: gdk4::ModifierType) -> HashSet<Modifier> {
|
||||
let mut modifiers = HashSet::new();
|
||||
|
||||
|
@ -421,6 +466,7 @@ impl<T: Clone> MenuItem<T> {
|
|||
working_dir: Option<String>,
|
||||
initial_sort_score: f64,
|
||||
data: Option<T>,
|
||||
//allow_submit: bool,
|
||||
) -> Self {
|
||||
MenuItem {
|
||||
label,
|
||||
|
@ -430,6 +476,7 @@ impl<T: Clone> MenuItem<T> {
|
|||
working_dir,
|
||||
initial_sort_score,
|
||||
data,
|
||||
//allow_submit,
|
||||
search_sort_score: 0.0,
|
||||
visible: true,
|
||||
}
|
||||
|
@ -444,10 +491,11 @@ impl<T: Clone> AsRef<MenuItem<T>> for MenuItem<T> {
|
|||
|
||||
struct MetaData<T: Clone + Send> {
|
||||
item_provider: ArcProvider<T>,
|
||||
item_factory: Option<ArcFactory<T>>,
|
||||
selected_sender: SelectionSender<T>,
|
||||
config: Rc<Config>,
|
||||
new_on_empty: bool,
|
||||
config: Arc<RwLock<Config>>,
|
||||
search_ignored_words: Option<Vec<Regex>>,
|
||||
expand_mode: ExpandMode,
|
||||
}
|
||||
|
||||
struct UiElements<T: Clone> {
|
||||
|
@ -468,20 +516,20 @@ struct UiElements<T: Clone> {
|
|||
/// # Errors
|
||||
///
|
||||
/// Will return Err when the channel between the UI and this is broken
|
||||
pub fn show<T, P>(
|
||||
config: Config,
|
||||
item_provider: P,
|
||||
new_on_empty: bool,
|
||||
pub fn show<T>(
|
||||
config: Arc<RwLock<Config>>,
|
||||
item_provider: ArcProvider<T>,
|
||||
item_factory: Option<ArcFactory<T>>,
|
||||
search_ignored_words: Option<Vec<Regex>>,
|
||||
expand_mode: ExpandMode,
|
||||
custom_keys: Option<CustomKeys>,
|
||||
) -> Result<Selection<T>, Error>
|
||||
where
|
||||
T: Clone + 'static + Send,
|
||||
P: ItemProvider<T> + 'static + Clone + Send,
|
||||
{
|
||||
gtk4::init().map_err(|e| Error::Graphics(e.to_string()))?;
|
||||
log::debug!("Starting GUI");
|
||||
if let Some(ref css) = config.style() {
|
||||
if let Some(ref css) = config.read().unwrap().style() {
|
||||
log::debug!("loading css from {css}");
|
||||
let provider = CssProvider::new();
|
||||
let css_file_path = File::for_path(css);
|
||||
|
@ -498,16 +546,18 @@ where
|
|||
let app = Application::builder().application_id("worf").build();
|
||||
let (sender, receiver) = channel::bounded(1);
|
||||
|
||||
let meta = Rc::new(MetaData {
|
||||
item_provider,
|
||||
item_factory,
|
||||
selected_sender: sender,
|
||||
config: config.clone(),
|
||||
search_ignored_words,
|
||||
expand_mode,
|
||||
});
|
||||
|
||||
let connect_cfg = Arc::clone(&config);
|
||||
app.connect_activate(move |app| {
|
||||
build_ui(
|
||||
&config,
|
||||
item_provider.clone(),
|
||||
sender.clone(),
|
||||
app.clone(),
|
||||
new_on_empty,
|
||||
search_ignored_words.clone(),
|
||||
custom_keys.as_ref(),
|
||||
);
|
||||
build_ui::<T>(&connect_cfg, &meta, app.clone(), custom_keys.as_ref());
|
||||
});
|
||||
|
||||
let gtk_args: [&str; 0] = [];
|
||||
|
@ -524,28 +574,16 @@ where
|
|||
receiver_result?
|
||||
}
|
||||
|
||||
fn build_ui<T, P>(
|
||||
config: &Config,
|
||||
item_provider: P,
|
||||
sender: Sender<Result<Selection<T>, Error>>,
|
||||
fn build_ui<T>(
|
||||
config: &Arc<RwLock<Config>>,
|
||||
meta: &Rc<MetaData<T>>,
|
||||
app: Application,
|
||||
new_on_empty: bool,
|
||||
search_ignored_words: Option<Vec<Regex>>,
|
||||
custom_keys: Option<&CustomKeys>,
|
||||
) where
|
||||
T: Clone + 'static + Send,
|
||||
P: ItemProvider<T> + 'static + Send,
|
||||
{
|
||||
let start = Instant::now();
|
||||
|
||||
let meta = Rc::new(MetaData {
|
||||
item_provider: Arc::new(Mutex::new(item_provider)),
|
||||
selected_sender: sender,
|
||||
config: Rc::new(config.clone()),
|
||||
new_on_empty,
|
||||
search_ignored_words,
|
||||
});
|
||||
|
||||
let provider_clone = Arc::clone(&meta.item_provider);
|
||||
let get_provider_elements = thread::spawn(move || {
|
||||
log::debug!("getting items");
|
||||
|
@ -560,7 +598,7 @@ fn build_ui<T, P>(
|
|||
.default_height(1)
|
||||
.build();
|
||||
|
||||
let background = create_background(config);
|
||||
let background = create_background(&config.read().unwrap());
|
||||
|
||||
let ui_elements = Rc::new(UiElements {
|
||||
app,
|
||||
|
@ -571,20 +609,22 @@ fn build_ui<T, P>(
|
|||
menu_rows: Arc::new(RwLock::new(HashMap::new())),
|
||||
search_text: Arc::new(Mutex::new(String::new())),
|
||||
search_delete_event: Arc::new(Mutex::new(None)),
|
||||
outer_box: gtk4::Box::new(config.orientation().into(), 0),
|
||||
outer_box: gtk4::Box::new(config.read().unwrap().orientation().into(), 0),
|
||||
scroll: ScrolledWindow::new(),
|
||||
custom_key_box: gtk4::Box::new(Orientation::Vertical, 0),
|
||||
});
|
||||
|
||||
// handle keys as soon as possible
|
||||
setup_key_event_handler(&ui_elements, &meta, custom_keys);
|
||||
setup_key_event_handler(&ui_elements, meta, custom_keys);
|
||||
|
||||
log::debug!("keyboard ready after {:?}", start.elapsed());
|
||||
|
||||
if !config.normal_window() {
|
||||
if !config.read().unwrap().normal_window() {
|
||||
// Initialize the window as a layer
|
||||
ui_elements.window.init_layer_shell();
|
||||
ui_elements.window.set_layer(config.layer().into());
|
||||
ui_elements
|
||||
.window
|
||||
.set_layer(config.read().unwrap().layer().into());
|
||||
ui_elements
|
||||
.window
|
||||
.set_keyboard_mode(KeyboardMode::Exclusive);
|
||||
|
@ -593,7 +633,7 @@ fn build_ui<T, P>(
|
|||
ui_elements.window.set_widget_name("window");
|
||||
ui_elements.window.set_namespace(Some("worf"));
|
||||
|
||||
if let Some(location) = config.location() {
|
||||
if let Some(location) = config.read().unwrap().location() {
|
||||
for anchor in location {
|
||||
ui_elements.window.set_anchor(anchor.into(), true);
|
||||
}
|
||||
|
@ -615,31 +655,33 @@ fn build_ui<T, P>(
|
|||
ui_elements.scroll.set_hexpand(true);
|
||||
ui_elements.scroll.set_vexpand(true);
|
||||
|
||||
if config.hide_scroll() {
|
||||
if config.read().unwrap().hide_scroll() {
|
||||
ui_elements
|
||||
.scroll
|
||||
.set_policy(PolicyType::External, PolicyType::External);
|
||||
}
|
||||
ui_elements.outer_box.append(&ui_elements.scroll);
|
||||
|
||||
build_main_box(config, &ui_elements);
|
||||
build_search_entry(config, &ui_elements, &meta);
|
||||
build_main_box(&config.read().unwrap(), &ui_elements);
|
||||
build_search_entry(&config.read().unwrap(), &ui_elements, meta);
|
||||
|
||||
let wrapper_box = gtk4::Box::new(Orientation::Vertical, 0);
|
||||
wrapper_box.append(&ui_elements.main_box);
|
||||
ui_elements.scroll.set_child(Some(&wrapper_box));
|
||||
|
||||
let wait_for_items = Instant::now();
|
||||
let (_changed, provider_elements) = get_provider_elements.join().unwrap();
|
||||
let provider_elements = get_provider_elements.join().unwrap();
|
||||
log::debug!("got items after {:?}", wait_for_items.elapsed());
|
||||
|
||||
let cfg = config.clone();
|
||||
let ui = Rc::clone(&ui_elements);
|
||||
ui_elements.window.connect_is_active_notify(move |_| {
|
||||
window_show_resize(&cfg.clone(), &ui);
|
||||
window_show_resize(&cfg.read().unwrap(), &ui);
|
||||
});
|
||||
|
||||
build_ui_from_menu_items(&ui_elements, &meta, provider_elements);
|
||||
if let Some(elements) = provider_elements.items {
|
||||
build_ui_from_menu_items(&ui_elements, meta, elements);
|
||||
}
|
||||
|
||||
let window_start = Instant::now();
|
||||
ui_elements.window.present();
|
||||
|
@ -833,7 +875,7 @@ fn set_search_text<T: Clone + Send + 'static>(
|
|||
search_stop_listen_delete_event(ui);
|
||||
let mut lock = ui.search_text.lock().unwrap();
|
||||
query.clone_into(&mut lock);
|
||||
if let Some(pw) = meta.config.password() {
|
||||
if let Some(pw) = meta.config.read().unwrap().password() {
|
||||
let mut ui_text = String::new();
|
||||
for _ in 0..query.len() {
|
||||
ui_text += &pw;
|
||||
|
@ -850,7 +892,7 @@ fn build_ui_from_menu_items<T: Clone + 'static + Send>(
|
|||
meta: &Rc<MetaData<T>>,
|
||||
mut items: Vec<MenuItem<T>>,
|
||||
) {
|
||||
if meta.config.sort_order() != SortOrder::Default {
|
||||
if meta.config.read().unwrap().sort_order() != SortOrder::Default {
|
||||
items.reverse();
|
||||
}
|
||||
let start = Instant::now();
|
||||
|
@ -968,7 +1010,7 @@ fn handle_key_press<T: Clone + 'static + Send>(
|
|||
|
||||
// hide search
|
||||
let propagate = if is_key_match(
|
||||
meta.config.key_hide_search(),
|
||||
meta.config.read().unwrap().key_hide_search(),
|
||||
&detection_type,
|
||||
key_code,
|
||||
keyboard_key,
|
||||
|
@ -976,7 +1018,7 @@ fn handle_key_press<T: Clone + 'static + Send>(
|
|||
handle_key_hide_search(ui)
|
||||
// submit
|
||||
} else if is_key_match(
|
||||
Some(meta.config.key_submit()),
|
||||
Some(meta.config.read().unwrap().key_submit()),
|
||||
&detection_type,
|
||||
key_code,
|
||||
keyboard_key,
|
||||
|
@ -985,7 +1027,7 @@ fn handle_key_press<T: Clone + 'static + Send>(
|
|||
}
|
||||
// exit
|
||||
else if is_key_match(
|
||||
Some(meta.config.key_exit()),
|
||||
Some(meta.config.read().unwrap().key_exit()),
|
||||
&detection_type,
|
||||
key_code,
|
||||
keyboard_key,
|
||||
|
@ -993,7 +1035,7 @@ fn handle_key_press<T: Clone + 'static + Send>(
|
|||
handle_key_exit(ui, meta)
|
||||
// copy
|
||||
} else if is_key_match(
|
||||
meta.config.key_copy(),
|
||||
meta.config.read().unwrap().key_copy(),
|
||||
&detection_type,
|
||||
key_code,
|
||||
keyboard_key,
|
||||
|
@ -1001,7 +1043,7 @@ fn handle_key_press<T: Clone + 'static + Send>(
|
|||
handle_key_copy(ui, meta)
|
||||
// expand
|
||||
} else if is_key_match(
|
||||
Some(meta.config.key_expand()),
|
||||
Some(meta.config.read().unwrap().key_expand()),
|
||||
&detection_type,
|
||||
key_code,
|
||||
keyboard_key,
|
||||
|
@ -1029,7 +1071,7 @@ fn handle_key_press<T: Clone + 'static + Send>(
|
|||
} else {
|
||||
pos
|
||||
};
|
||||
if let Some((start, ch)) = query.char_indices().nth((del_pos) as usize) {
|
||||
if let Some((start, ch)) = query.char_indices().nth(del_pos as usize) {
|
||||
let end = start + ch.len_utf8();
|
||||
query.replace_range(start..end, "");
|
||||
}
|
||||
|
@ -1084,7 +1126,7 @@ fn handle_custom_keys<T: Clone + 'static + Send>(
|
|||
modifier_type: gdk4::ModifierType,
|
||||
custom_keys: Option<&CustomKeys>,
|
||||
) -> KeyDetectionType {
|
||||
let detection_type = meta.config.key_detection_type();
|
||||
let detection_type = meta.config.read().unwrap().key_detection_type();
|
||||
if let Some(custom_keys) = custom_keys {
|
||||
let mods = modifiers_from_mask(modifier_type);
|
||||
for custom_key in &custom_keys.bindings {
|
||||
|
@ -1098,14 +1140,9 @@ fn handle_custom_keys<T: Clone + 'static + Send>(
|
|||
|
||||
if custom_key_match {
|
||||
let search_lock = ui.search_text.lock().unwrap();
|
||||
if let Err(e) = handle_selected_item(
|
||||
ui,
|
||||
Rc::<MetaData<T>>::clone(meta),
|
||||
Some(&search_lock),
|
||||
None,
|
||||
meta.new_on_empty,
|
||||
Some(custom_key),
|
||||
) {
|
||||
if let Err(e) =
|
||||
handle_selected_item(ui, meta, Some(&search_lock), None, Some(custom_key))
|
||||
{
|
||||
log::error!("{e}");
|
||||
}
|
||||
}
|
||||
|
@ -1118,8 +1155,8 @@ fn update_view_from_provider<T>(ui: &Rc<UiElements<T>>, meta: &Rc<MetaData<T>>,
|
|||
where
|
||||
T: Clone + Send + 'static,
|
||||
{
|
||||
let (changed, filtered_list) = meta.item_provider.lock().unwrap().get_elements(Some(query));
|
||||
if changed {
|
||||
let data = meta.item_provider.lock().unwrap().get_elements(Some(query));
|
||||
if let Some(filtered_list) = data.items {
|
||||
build_ui_from_menu_items(ui, meta, filtered_list);
|
||||
}
|
||||
update_view(ui, meta, query);
|
||||
|
@ -1139,9 +1176,10 @@ where
|
|||
|
||||
select_first_visible_child(&*lock, &ui.main_box);
|
||||
drop(lock);
|
||||
if meta.config.dynamic_lines() {
|
||||
if meta.config.read().unwrap().dynamic_lines() {
|
||||
if let Some(geometry) = get_monitor_geometry(ui.window.surface().as_ref()) {
|
||||
let height = calculate_dynamic_lines_window_height(&meta.config, ui, geometry);
|
||||
let height =
|
||||
calculate_dynamic_lines_window_height(&meta.config.read().unwrap(), ui, geometry);
|
||||
ui.window.set_height_request(height);
|
||||
}
|
||||
}
|
||||
|
@ -1168,7 +1206,7 @@ where
|
|||
if let Some(expander) = expander {
|
||||
expander.set_expanded(true);
|
||||
} else {
|
||||
let opt_changed = {
|
||||
let data = {
|
||||
let lock = ui.menu_rows.read().unwrap();
|
||||
let menu_item = lock.get(fb);
|
||||
menu_item.map(|menu_item| {
|
||||
|
@ -1177,20 +1215,30 @@ where
|
|||
.lock()
|
||||
.unwrap()
|
||||
.get_sub_elements(menu_item),
|
||||
menu_item.label.clone(),
|
||||
menu_item.clone(),
|
||||
)
|
||||
})
|
||||
};
|
||||
|
||||
if let Some(changed) = opt_changed {
|
||||
let items = changed.0.1.unwrap_or_default();
|
||||
if changed.0.0 {
|
||||
if let Some((provider_data, menu_item)) = data {
|
||||
if let Some(items) = provider_data.items {
|
||||
build_ui_from_menu_items(ui, meta, items);
|
||||
let query = match meta.expand_mode {
|
||||
ExpandMode::Verbatim => menu_item.label.clone(),
|
||||
ExpandMode::WithSpace => format!("{} ", menu_item.label.clone()),
|
||||
};
|
||||
|
||||
set_search_text(ui, meta, &query);
|
||||
if let Ok(new_pos) = i32::try_from(query.len() + 1) {
|
||||
ui.search.set_position(new_pos);
|
||||
}
|
||||
|
||||
let query = changed.1;
|
||||
set_search_text(ui, meta, &query);
|
||||
update_view(ui, meta, &query);
|
||||
} else if let Err(e) =
|
||||
handle_selected_item(ui, meta, None, Some(menu_item), None)
|
||||
{
|
||||
log::error!("{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1221,14 +1269,7 @@ where
|
|||
T: Clone + Send + 'static,
|
||||
{
|
||||
let search_lock = ui.search_text.lock().unwrap();
|
||||
if let Err(e) = handle_selected_item(
|
||||
ui,
|
||||
Rc::<MetaData<T>>::clone(meta),
|
||||
Some(&search_lock),
|
||||
None,
|
||||
meta.new_on_empty,
|
||||
None,
|
||||
) {
|
||||
if let Err(e) = handle_selected_item(ui, meta, Some(&search_lock), None, None) {
|
||||
log::error!("{e}");
|
||||
}
|
||||
Propagation::Stop
|
||||
|
@ -1431,10 +1472,9 @@ where
|
|||
}
|
||||
fn handle_selected_item<T>(
|
||||
ui: &Rc<UiElements<T>>,
|
||||
meta: Rc<MetaData<T>>,
|
||||
meta: &Rc<MetaData<T>>,
|
||||
query: Option<&str>,
|
||||
item: Option<MenuItem<T>>,
|
||||
new_on_empty: bool,
|
||||
custom_key: Option<&KeyBinding>,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
|
@ -1448,37 +1488,31 @@ where
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
if new_on_empty {
|
||||
let item = MenuItem {
|
||||
label: query.unwrap_or("").to_owned(),
|
||||
icon_path: None,
|
||||
action: None,
|
||||
sub_elements: Vec::new(),
|
||||
working_dir: None,
|
||||
initial_sort_score: 0.0,
|
||||
search_sort_score: 0.0,
|
||||
data: None,
|
||||
visible: true,
|
||||
};
|
||||
|
||||
if let Some(factory) = meta.item_factory.as_ref() {
|
||||
let factory = factory.lock().unwrap();
|
||||
let label = filtered_query(meta.search_ignored_words.as_ref(), query.unwrap_or(""));
|
||||
let item = factory.new_menu_item(label);
|
||||
if let Some(item) = item {
|
||||
send_selected_item(ui, meta, custom_key.cloned(), item);
|
||||
Ok(())
|
||||
} else {
|
||||
Err("selected item cannot be resolved".to_owned())
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Err("selected item cannot be resolved".to_owned())
|
||||
}
|
||||
|
||||
fn send_selected_item<T>(
|
||||
ui: &Rc<UiElements<T>>,
|
||||
meta: Rc<MetaData<T>>,
|
||||
meta: &Rc<MetaData<T>>,
|
||||
custom_key: Option<KeyBinding>,
|
||||
selected_item: MenuItem<T>,
|
||||
) where
|
||||
T: Clone + Send + 'static,
|
||||
{
|
||||
let ui_clone = Rc::clone(ui);
|
||||
let meta_clone = Rc::clone(meta);
|
||||
ui.window.connect_hide(move |_| {
|
||||
if let Err(e) = meta.selected_sender.send(Ok(Selection {
|
||||
if let Err(e) = meta_clone.selected_sender.send(Ok(Selection {
|
||||
menu: selected_item.clone(),
|
||||
custom_key: custom_key.clone(),
|
||||
})) {
|
||||
|
@ -1548,7 +1582,7 @@ fn create_menu_row<T: Clone + 'static + Send>(
|
|||
row.set_halign(Align::Fill);
|
||||
row.set_widget_name("row");
|
||||
|
||||
let row_box = gtk4::Box::new(meta.config.row_box_orientation().into(), 0);
|
||||
let row_box = gtk4::Box::new(meta.config.read().unwrap().row_box_orientation().into(), 0);
|
||||
row_box.set_hexpand(true);
|
||||
row_box.set_vexpand(false);
|
||||
row_box.set_halign(Align::Fill);
|
||||
|
@ -1557,15 +1591,13 @@ fn create_menu_row<T: Clone + 'static + Send>(
|
|||
|
||||
let (label_img, label_text) = parse_label(&element_to_add.label);
|
||||
|
||||
if meta.config.allow_images() {
|
||||
let config = meta.config.read().unwrap();
|
||||
if meta.config.read().unwrap().allow_images() {
|
||||
let img = lookup_icon(
|
||||
element_to_add.icon_path.as_ref().map(AsRef::as_ref),
|
||||
&meta.config,
|
||||
&config,
|
||||
)
|
||||
.or(lookup_icon(
|
||||
label_img.as_ref().map(AsRef::as_ref),
|
||||
&meta.config,
|
||||
));
|
||||
.or(lookup_icon(label_img.as_ref().map(AsRef::as_ref), &config));
|
||||
|
||||
if let Some(image) = img {
|
||||
image.set_widget_name("img");
|
||||
|
@ -1574,16 +1606,16 @@ fn create_menu_row<T: Clone + 'static + Send>(
|
|||
}
|
||||
|
||||
let label = Label::new(label_text.as_ref().map(AsRef::as_ref));
|
||||
label.set_use_markup(meta.config.allow_markup());
|
||||
label.set_natural_wrap_mode(meta.config.line_wrap().into());
|
||||
label.set_use_markup(meta.config.read().unwrap().allow_markup());
|
||||
label.set_natural_wrap_mode(meta.config.read().unwrap().line_wrap().into());
|
||||
label.set_hexpand(true);
|
||||
label.set_widget_name("text");
|
||||
label.set_wrap(true);
|
||||
if let Some(max_width_chars) = meta.config.line_max_width_chars() {
|
||||
if let Some(max_width_chars) = meta.config.read().unwrap().line_max_width_chars() {
|
||||
label.set_max_width_chars(max_width_chars);
|
||||
}
|
||||
|
||||
if let Some(max_len) = meta.config.line_max_chars() {
|
||||
if let Some(max_len) = meta.config.read().unwrap().line_max_chars() {
|
||||
if let Some(text) = label_text.as_ref() {
|
||||
if text.chars().count() > max_len {
|
||||
let end = text
|
||||
|
@ -1597,8 +1629,18 @@ fn create_menu_row<T: Clone + 'static + Send>(
|
|||
|
||||
row_box.append(&label);
|
||||
|
||||
if meta.config.content_halign().eq(&config::Align::Start)
|
||||
|| meta.config.content_halign().eq(&config::Align::Fill)
|
||||
if meta
|
||||
.config
|
||||
.read()
|
||||
.unwrap()
|
||||
.content_halign()
|
||||
.eq(&config::Align::Start)
|
||||
|| meta
|
||||
.config
|
||||
.read()
|
||||
.unwrap()
|
||||
.content_halign()
|
||||
.eq(&config::Align::Fill)
|
||||
{
|
||||
label.set_xalign(0.0);
|
||||
}
|
||||
|
@ -1610,16 +1652,19 @@ fn create_menu_row<T: Clone + 'static + Send>(
|
|||
let click = GestureClick::new();
|
||||
click.set_button(gtk4::gdk::BUTTON_PRIMARY);
|
||||
|
||||
let presses = if meta.config.single_click() { 1 } else { 2 };
|
||||
let presses = if meta.config.read().unwrap().single_click() {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
};
|
||||
|
||||
click.connect_pressed(move |_gesture, n_press, _x, _y| {
|
||||
if n_press == presses {
|
||||
if let Err(e) = handle_selected_item(
|
||||
&click_ui,
|
||||
Rc::<MetaData<T>>::clone(&click_meta),
|
||||
&click_meta,
|
||||
None,
|
||||
Some(element_clone.clone()),
|
||||
false,
|
||||
None,
|
||||
) {
|
||||
log::error!("{e}");
|
||||
|
@ -1703,30 +1748,24 @@ fn lookup_icon(icon_path: Option<&str>, config: &Config) -> Option<Image> {
|
|||
fn set_menu_visibility_for_search<T: Clone>(
|
||||
query: &str,
|
||||
items: &mut HashMap<FlowBoxChild, MenuItem<T>>,
|
||||
config: &Config,
|
||||
config: &Arc<RwLock<Config>>,
|
||||
search_ignored_words: Option<&Vec<Regex>>,
|
||||
) {
|
||||
{
|
||||
if query.is_empty() {
|
||||
for (fb, menu_item) in items.iter_mut() {
|
||||
menu_item.search_sort_score = 0.0;
|
||||
menu_item.visible = true;
|
||||
fb.set_visible(menu_item.visible);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let mut query = if config.insensitive() {
|
||||
let mut query = if config.read().unwrap().insensitive() {
|
||||
query.to_owned().to_lowercase()
|
||||
} else {
|
||||
query.to_owned()
|
||||
};
|
||||
|
||||
if let Some(s) = search_ignored_words.as_ref() {
|
||||
s.iter().for_each(|rgx| {
|
||||
query = rgx.replace_all(&query, "").to_string();
|
||||
});
|
||||
}
|
||||
query = filtered_query(search_ignored_words, &query);
|
||||
|
||||
for (fb, menu_item) in items.iter_mut() {
|
||||
let menu_item_search = format!(
|
||||
|
@ -1735,28 +1774,31 @@ fn set_menu_visibility_for_search<T: Clone>(
|
|||
.action
|
||||
.as_ref()
|
||||
.map(|a| {
|
||||
if config.insensitive() {
|
||||
if config.read().unwrap().insensitive() {
|
||||
a.to_lowercase()
|
||||
} else {
|
||||
a.clone()
|
||||
}
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
if config.insensitive() {
|
||||
if config.read().unwrap().insensitive() {
|
||||
menu_item.label.to_lowercase()
|
||||
} else {
|
||||
menu_item.label.clone()
|
||||
}
|
||||
);
|
||||
|
||||
let (search_sort_score, visible) = match config.match_method() {
|
||||
let (search_sort_score, visible) = match config.read().unwrap().match_method() {
|
||||
MatchMethod::Fuzzy => {
|
||||
let mut score = strsim::jaro_winkler(&query, &menu_item_search);
|
||||
if score == 0.0 {
|
||||
score = -1.0;
|
||||
}
|
||||
|
||||
(score, score > config.fuzzy_min_score() && score > 0.0)
|
||||
(
|
||||
score,
|
||||
score > config.read().unwrap().fuzzy_min_score() && score > 0.0,
|
||||
)
|
||||
}
|
||||
MatchMethod::Contains => {
|
||||
if menu_item_search.contains(&query) {
|
||||
|
@ -1779,6 +1821,16 @@ fn set_menu_visibility_for_search<T: Clone>(
|
|||
fb.set_visible(menu_item.visible);
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn filtered_query(search_ignored_words: Option<&Vec<Regex>>, query: &str) -> String {
|
||||
let mut query = query.to_owned();
|
||||
if let Some(s) = search_ignored_words.as_ref() {
|
||||
s.iter().for_each(|rgx| {
|
||||
query = rgx.replace_all(&query, "").to_string();
|
||||
});
|
||||
}
|
||||
query
|
||||
}
|
||||
|
||||
fn select_first_visible_child<T: Clone>(
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use regex::Regex;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
use crate::{
|
||||
Error,
|
||||
config::Config,
|
||||
desktop::spawn_fork,
|
||||
gui::{self, ItemProvider, MenuItem},
|
||||
gui::{self, DefaultItemFactory, ExpandMode, ItemProvider, MenuItem, ProviderData},
|
||||
modes::{
|
||||
drun::{DRunProvider, update_drun_cache_and_run},
|
||||
file::FileItemProvider,
|
||||
|
@ -14,6 +15,7 @@ use crate::{
|
|||
ssh::SshProvider,
|
||||
},
|
||||
};
|
||||
use crate::gui::ArcProvider;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum AutoRunType {
|
||||
|
@ -22,6 +24,7 @@ enum AutoRunType {
|
|||
File,
|
||||
Ssh,
|
||||
WebSearch,
|
||||
Auto,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -46,18 +49,25 @@ impl AutoItemProvider {
|
|||
}
|
||||
}
|
||||
|
||||
fn default_auto_elements(
|
||||
&mut self,
|
||||
search_opt: Option<&str>,
|
||||
) -> (bool, Vec<MenuItem<AutoRunType>>) {
|
||||
fn default_auto_elements(&mut self) -> ProviderData<AutoRunType> {
|
||||
// return ssh and drun items
|
||||
let (changed, mut items) = self.drun.get_elements(search_opt);
|
||||
items.append(&mut self.ssh.get_elements(search_opt).1);
|
||||
if self.last_mode == Some(AutoRunType::DRun) {
|
||||
(changed, items)
|
||||
if self.last_mode.is_none()
|
||||
|| self
|
||||
.last_mode
|
||||
.as_ref()
|
||||
.is_some_and(|t| t != &AutoRunType::Auto)
|
||||
{
|
||||
let mut data = self.drun.get_elements(None);
|
||||
if let Some(items) = data.items.as_mut() {
|
||||
if let Some(mut ssh) = self.ssh.get_elements(None).items {
|
||||
items.append(&mut ssh);
|
||||
}
|
||||
}
|
||||
|
||||
self.last_mode = Some(AutoRunType::Auto);
|
||||
data
|
||||
} else {
|
||||
self.last_mode = Some(AutoRunType::DRun);
|
||||
(true, items)
|
||||
ProviderData { items: None }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,13 +86,13 @@ fn contains_math_functions_or_starts_with_number(input: &str) -> bool {
|
|||
}
|
||||
|
||||
impl ItemProvider<AutoRunType> for AutoItemProvider {
|
||||
fn get_elements(&mut self, search_opt: Option<&str>) -> (bool, Vec<MenuItem<AutoRunType>>) {
|
||||
fn get_elements(&mut self, search_opt: Option<&str>) -> ProviderData<AutoRunType> {
|
||||
let search = match search_opt {
|
||||
Some(s) if !s.trim().is_empty() => s.trim(),
|
||||
_ => return self.default_auto_elements(search_opt),
|
||||
_ => "",
|
||||
};
|
||||
|
||||
let (mode, (changed, items)) = if contains_math_functions_or_starts_with_number(search) {
|
||||
let (mode, provider_data) = if contains_math_functions_or_starts_with_number(search) {
|
||||
(AutoRunType::Math, self.math.get_elements(search_opt))
|
||||
} else if search.starts_with('$') || search.starts_with('/') || search.starts_with('~') {
|
||||
(AutoRunType::File, self.file.get_elements(search_opt))
|
||||
|
@ -96,23 +106,34 @@ impl ItemProvider<AutoRunType> for AutoItemProvider {
|
|||
self.search.get_elements(Some(&query)),
|
||||
)
|
||||
} else {
|
||||
return self.default_auto_elements(search_opt);
|
||||
(AutoRunType::Auto, self.default_auto_elements())
|
||||
};
|
||||
|
||||
if self.last_mode.as_ref().is_some_and(|m| m == &mode) {
|
||||
(changed, items)
|
||||
} else {
|
||||
self.last_mode = Some(mode);
|
||||
(true, items)
|
||||
}
|
||||
provider_data
|
||||
|
||||
// if mode == AutoRunType::DRun && self.last_mode.as_ref().is_some_and(|l| l == &mode) {
|
||||
// ProviderData {
|
||||
// items: None,
|
||||
// }
|
||||
// } else {
|
||||
// self.default_auto_elements()
|
||||
// }
|
||||
}
|
||||
|
||||
fn get_sub_elements(
|
||||
&mut self,
|
||||
item: &MenuItem<AutoRunType>,
|
||||
) -> (bool, Option<Vec<MenuItem<AutoRunType>>>) {
|
||||
let (changed, items) = self.get_elements(Some(item.label.as_ref()));
|
||||
(changed, Some(items))
|
||||
fn get_sub_elements(&mut self, item: &MenuItem<AutoRunType>) -> ProviderData<AutoRunType> {
|
||||
if let Some(auto_run_type) = item.data.as_ref() {
|
||||
match auto_run_type {
|
||||
AutoRunType::Math => self.math.get_sub_elements(item),
|
||||
AutoRunType::DRun => self.drun.get_sub_elements(item),
|
||||
AutoRunType::File => self.file.get_sub_elements(item),
|
||||
AutoRunType::Ssh => self.ssh.get_sub_elements(item),
|
||||
AutoRunType::WebSearch => self.search.get_sub_elements(item),
|
||||
AutoRunType::Auto => ProviderData { items: None },
|
||||
}
|
||||
} else {
|
||||
ProviderData { items: None }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,23 +145,24 @@ impl ItemProvider<AutoRunType> for AutoItemProvider {
|
|||
///
|
||||
/// # Panics
|
||||
/// Panics if an internal static regex cannot be passed anymore, should never happen
|
||||
pub fn show(config: &Config) -> Result<(), Error> {
|
||||
let mut provider = AutoItemProvider::new(config);
|
||||
let cache_path = provider.drun.cache_path.clone();
|
||||
let mut cache = provider.drun.cache.clone();
|
||||
pub fn show(config: &Arc<RwLock<Config>>) -> Result<(), Error> {
|
||||
let provider = Arc::new(Mutex::new(AutoItemProvider::new(&config.read().unwrap())));
|
||||
let arc_provider = Arc::clone(&provider) as ArcProvider<AutoRunType>;
|
||||
let cache_path = provider.lock().unwrap().drun.cache_path.clone();
|
||||
let mut cache = provider.lock().unwrap().drun.cache.clone();
|
||||
|
||||
loop {
|
||||
// todo ues a arc instead of cloning the config
|
||||
let selection_result = gui::show(
|
||||
config.clone(),
|
||||
provider.clone(),
|
||||
true,
|
||||
Arc::clone(&config),
|
||||
Arc::clone(&arc_provider),
|
||||
Some(Arc::new(Mutex::new(DefaultItemFactory::new()))),
|
||||
Some(
|
||||
vec!["ssh", "emoji", "^\\$\\w+", "^\\?\\s*"]
|
||||
.into_iter()
|
||||
.map(|s| Regex::new(s).unwrap())
|
||||
.collect(),
|
||||
),
|
||||
ExpandMode::Verbatim,
|
||||
None,
|
||||
);
|
||||
|
||||
|
@ -149,7 +171,12 @@ pub fn show(config: &Config) -> Result<(), Error> {
|
|||
if let Some(data) = &selection_result.data {
|
||||
match data {
|
||||
AutoRunType::Math => {
|
||||
provider.math.elements.push(selection_result);
|
||||
provider
|
||||
.lock()
|
||||
.unwrap()
|
||||
.math
|
||||
.elements
|
||||
.push(selection_result);
|
||||
}
|
||||
AutoRunType::DRun => {
|
||||
update_drun_cache_and_run(&cache_path, &mut cache, selection_result)?;
|
||||
|
@ -162,7 +189,7 @@ pub fn show(config: &Config) -> Result<(), Error> {
|
|||
break;
|
||||
}
|
||||
AutoRunType::Ssh => {
|
||||
ssh::launch(&selection_result, config)?;
|
||||
ssh::launch(&selection_result, &config.read().unwrap())?;
|
||||
break;
|
||||
}
|
||||
AutoRunType::WebSearch => {
|
||||
|
@ -171,10 +198,13 @@ pub fn show(config: &Config) -> Result<(), Error> {
|
|||
}
|
||||
break;
|
||||
}
|
||||
AutoRunType::Auto => {
|
||||
unreachable!("Auto mode should never be set for show.")
|
||||
}
|
||||
}
|
||||
} else if selection_result.label.starts_with("ssh") {
|
||||
selection_result.label = selection_result.label.chars().skip(4).collect();
|
||||
ssh::launch(&selection_result, config)?;
|
||||
ssh::launch(&selection_result, &config.read().unwrap())?;
|
||||
}
|
||||
} else {
|
||||
log::error!("No item selected");
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
use std::io::{self, Read};
|
||||
use std::{
|
||||
io::{self, Read},
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
Error,
|
||||
config::{Config, SortOrder},
|
||||
gui::{self, ItemProvider, MenuItem},
|
||||
gui::{self, DefaultItemFactory, ExpandMode, ItemProvider, MenuItem, ProviderData},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -30,12 +33,18 @@ impl DMenuProvider {
|
|||
}
|
||||
}
|
||||
impl ItemProvider<String> for DMenuProvider {
|
||||
fn get_elements(&mut self, _: Option<&str>) -> (bool, Vec<MenuItem<String>>) {
|
||||
(false, self.items.clone())
|
||||
fn get_elements(&mut self, query: Option<&str>) -> ProviderData<String> {
|
||||
if query.is_some() {
|
||||
ProviderData { items: None }
|
||||
} else {
|
||||
ProviderData {
|
||||
items: Some(self.items.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sub_elements(&mut self, _: &MenuItem<String>) -> (bool, Option<Vec<MenuItem<String>>>) {
|
||||
(false, None)
|
||||
fn get_sub_elements(&mut self, _: &MenuItem<String>) -> ProviderData<String> {
|
||||
ProviderData { items: None }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,10 +52,19 @@ impl ItemProvider<String> for DMenuProvider {
|
|||
/// # Errors
|
||||
///
|
||||
/// Forwards errors from the gui. See `gui::show` for details.
|
||||
pub fn show(config: &Config) -> Result<(), Error> {
|
||||
let provider = DMenuProvider::new(&config.sort_order());
|
||||
pub fn show(config: Arc<RwLock<Config>>) -> Result<(), Error> {
|
||||
let provider = Arc::new(Mutex::new(DMenuProvider::new(
|
||||
&config.read().unwrap().sort_order(),
|
||||
)));
|
||||
|
||||
let selection_result = gui::show(config.clone(), provider, true, None, None);
|
||||
let selection_result = gui::show(
|
||||
config,
|
||||
provider,
|
||||
Some(Arc::new(Mutex::new(DefaultItemFactory::new()))),
|
||||
None,
|
||||
ExpandMode::Verbatim,
|
||||
None,
|
||||
);
|
||||
match selection_result {
|
||||
Ok(s) => {
|
||||
println!("{}", s.menu.label);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
|
@ -8,6 +9,7 @@ use freedesktop_file_parser::EntryType;
|
|||
use rayon::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::gui::ArcProvider;
|
||||
use crate::{
|
||||
Error,
|
||||
config::{Config, SortOrder},
|
||||
|
@ -15,7 +17,7 @@ use crate::{
|
|||
find_desktop_files, get_locale_variants, lookup_name_with_locale, save_cache_file,
|
||||
spawn_fork,
|
||||
},
|
||||
gui::{self, ItemProvider, MenuItem},
|
||||
gui::{self, ExpandMode, ItemProvider, MenuItem, ProviderData},
|
||||
modes::load_cache,
|
||||
};
|
||||
|
||||
|
@ -37,15 +39,21 @@ pub(crate) struct DRunProvider<T: Clone> {
|
|||
}
|
||||
|
||||
impl<T: Clone + Send + Sync> ItemProvider<T> for DRunProvider<T> {
|
||||
fn get_elements(&mut self, _: Option<&str>) -> (bool, Vec<MenuItem<T>>) {
|
||||
fn get_elements(&mut self, query: Option<&str>) -> ProviderData<T> {
|
||||
if self.items.is_none() {
|
||||
self.items = Some(self.load().clone());
|
||||
}
|
||||
(false, self.items.clone().unwrap())
|
||||
if query.is_some() {
|
||||
ProviderData { items: None }
|
||||
} else {
|
||||
ProviderData {
|
||||
items: self.items.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sub_elements(&mut self, _: &MenuItem<T>) -> (bool, Option<Vec<MenuItem<T>>>) {
|
||||
(false, None)
|
||||
fn get_sub_elements(&mut self, _: &MenuItem<T>) -> ProviderData<T> {
|
||||
ProviderData { items: None }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,15 +219,22 @@ pub(crate) fn update_drun_cache_and_run<T: Clone>(
|
|||
/// # Errors
|
||||
///
|
||||
/// Will return `Err` if it was not able to spawn the process
|
||||
pub fn show(config: &Config) -> Result<(), Error> {
|
||||
let provider = DRunProvider::new(0, config);
|
||||
let cache_path = provider.cache_path.clone();
|
||||
let mut cache = provider.cache.clone();
|
||||
|
||||
// todo ues a arc instead of cloning the config
|
||||
let selection_result = gui::show(config.clone(), provider, false, None, None);
|
||||
pub fn show(config: Arc<RwLock<Config>>) -> Result<(), Error> {
|
||||
let provider = Arc::new(Mutex::new(DRunProvider::new((), &config.read().unwrap())));
|
||||
let arc_provider = Arc::clone(&provider) as ArcProvider<()>;
|
||||
let selection_result = gui::show(
|
||||
config.clone(),
|
||||
arc_provider,
|
||||
None,
|
||||
None,
|
||||
ExpandMode::Verbatim,
|
||||
None,
|
||||
);
|
||||
match selection_result {
|
||||
Ok(s) => update_drun_cache_and_run(&cache_path, &mut cache, s.menu)?,
|
||||
Ok(s) => {
|
||||
let p = provider.lock().unwrap();
|
||||
update_drun_cache_and_run(&p.cache_path, &mut p.cache.clone(), s.menu)?;
|
||||
}
|
||||
Err(_) => {
|
||||
log::error!("No item selected");
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
use std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
use crate::{
|
||||
Error,
|
||||
config::{Config, SortOrder},
|
||||
desktop::copy_to_clipboard,
|
||||
gui::{self, ItemProvider, MenuItem},
|
||||
gui::{self, ExpandMode, ItemProvider, MenuItem, ProviderData},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -41,12 +43,18 @@ impl EmojiProvider {
|
|||
}
|
||||
|
||||
impl ItemProvider<String> for EmojiProvider {
|
||||
fn get_elements(&mut self, _: Option<&str>) -> (bool, Vec<MenuItem<String>>) {
|
||||
(false, self.elements.clone())
|
||||
fn get_elements(&mut self, query: Option<&str>) -> ProviderData<String> {
|
||||
if query.is_some() {
|
||||
ProviderData { items: None }
|
||||
} else {
|
||||
ProviderData {
|
||||
items: Some(self.elements.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sub_elements(&mut self, _: &MenuItem<String>) -> (bool, Option<Vec<MenuItem<String>>>) {
|
||||
(false, None)
|
||||
fn get_sub_elements(&mut self, _: &MenuItem<String>) -> ProviderData<String> {
|
||||
ProviderData { items: None }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,9 +62,22 @@ impl ItemProvider<String> for EmojiProvider {
|
|||
/// # Errors
|
||||
///
|
||||
/// Forwards errors from the gui. See `gui::show` for details.
|
||||
pub fn show(config: &Config) -> Result<(), Error> {
|
||||
let provider = EmojiProvider::new(&config.sort_order(), config.emoji_hide_label());
|
||||
let selection_result = gui::show(config.clone(), provider, true, None, None)?;
|
||||
pub fn show(config: Arc<RwLock<Config>>) -> Result<(), Error> {
|
||||
let cfg = config.read().unwrap();
|
||||
let provider = Arc::new(Mutex::new(EmojiProvider::new(
|
||||
&cfg.sort_order(),
|
||||
cfg.emoji_hide_label(),
|
||||
)));
|
||||
drop(cfg);
|
||||
|
||||
let selection_result = gui::show(
|
||||
config.clone(),
|
||||
provider,
|
||||
None,
|
||||
None,
|
||||
ExpandMode::Verbatim,
|
||||
None,
|
||||
)?;
|
||||
match selection_result.menu.data {
|
||||
None => Err(Error::MissingAction),
|
||||
Some(action) => copy_to_clipboard(action, None),
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
use regex::Regex;
|
||||
use std::sync::Mutex;
|
||||
use std::{
|
||||
fs,
|
||||
os::unix::fs::FileTypeExt,
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use crate::{
|
||||
Error,
|
||||
config::{Config, SortOrder, expand_path},
|
||||
desktop::spawn_fork,
|
||||
gui::{self, ItemProvider, MenuItem},
|
||||
gui::{self, ExpandMode, ItemProvider, MenuItem, ProviderData},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -98,7 +99,7 @@ impl<T: Clone> FileItemProvider<T> {
|
|||
}
|
||||
|
||||
impl<T: Clone> ItemProvider<T> for FileItemProvider<T> {
|
||||
fn get_elements(&mut self, search: Option<&str>) -> (bool, Vec<MenuItem<T>>) {
|
||||
fn get_elements(&mut self, search: Option<&str>) -> ProviderData<T> {
|
||||
let default_path = if let Some(home) = dirs::home_dir() {
|
||||
home.display().to_string()
|
||||
} else {
|
||||
|
@ -117,11 +118,7 @@ impl<T: Clone> ItemProvider<T> for FileItemProvider<T> {
|
|||
let mut items: Vec<MenuItem<T>> = Vec::new();
|
||||
|
||||
if !path.exists() {
|
||||
if let Some(last) = &self.last_result {
|
||||
return (false, last.clone());
|
||||
}
|
||||
|
||||
return (true, vec![]);
|
||||
return ProviderData { items: None };
|
||||
}
|
||||
|
||||
if path.is_dir() {
|
||||
|
@ -183,11 +180,15 @@ impl<T: Clone> ItemProvider<T> for FileItemProvider<T> {
|
|||
gui::apply_sort(&mut items, &self.sort_order);
|
||||
|
||||
self.last_result = Some(items.clone());
|
||||
(true, items)
|
||||
ProviderData { items: Some(items) }
|
||||
}
|
||||
|
||||
fn get_sub_elements(&mut self, _: &MenuItem<T>) -> (bool, Option<Vec<MenuItem<T>>>) {
|
||||
(false, self.last_result.clone())
|
||||
fn get_sub_elements(&mut self, item: &MenuItem<T>) -> ProviderData<T> {
|
||||
if self.last_result.as_ref().is_some_and(|lr| lr.len() == 1) {
|
||||
ProviderData { items: None }
|
||||
} else {
|
||||
self.get_elements(Some(&item.label))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,15 +200,19 @@ impl<T: Clone> ItemProvider<T> for FileItemProvider<T> {
|
|||
///
|
||||
/// # Panics
|
||||
/// In case an internal regex does not parse anymore, this should never happen
|
||||
pub fn show(config: &Config) -> Result<(), Error> {
|
||||
let provider = FileItemProvider::new(0, config.sort_order());
|
||||
pub fn show(config: Arc<RwLock<Config>>) -> Result<(), Error> {
|
||||
let provider = Arc::new(Mutex::new(FileItemProvider::new(
|
||||
0,
|
||||
config.read().unwrap().sort_order(),
|
||||
)));
|
||||
|
||||
// todo ues a arc instead of cloning the config
|
||||
let selection_result = gui::show(
|
||||
config.clone(),
|
||||
provider,
|
||||
false,
|
||||
None,
|
||||
Some(vec![Regex::new("^\\$\\w+").unwrap()]),
|
||||
ExpandMode::Verbatim,
|
||||
None,
|
||||
)?;
|
||||
if let Some(action) = selection_result.menu.action {
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
use std::{
|
||||
collections::VecDeque,
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
};
|
||||
|
||||
use regex::Regex;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
gui::{self, ItemProvider, MenuItem},
|
||||
gui::{
|
||||
self, ArcFactory, ArcProvider, DefaultItemFactory, ExpandMode, ItemProvider, MenuItem,
|
||||
ProviderData,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -26,7 +33,7 @@ impl<T: Clone> MathProvider<T> {
|
|||
|
||||
impl<T: Clone> ItemProvider<T> for MathProvider<T> {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn get_elements(&mut self, search: Option<&str>) -> (bool, Vec<MenuItem<T>>) {
|
||||
fn get_elements(&mut self, search: Option<&str>) -> ProviderData<T> {
|
||||
if let Some(search_text) = search {
|
||||
let result = calc(search_text);
|
||||
|
||||
|
@ -41,14 +48,16 @@ impl<T: Clone> ItemProvider<T> for MathProvider<T> {
|
|||
);
|
||||
let mut result = vec![item];
|
||||
result.append(&mut self.elements.clone());
|
||||
(true, result)
|
||||
ProviderData {
|
||||
items: Some(result),
|
||||
}
|
||||
} else {
|
||||
(false, self.elements.clone())
|
||||
ProviderData { items: None }
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sub_elements(&mut self, _: &MenuItem<T>) -> (bool, Option<Vec<MenuItem<T>>>) {
|
||||
(false, None)
|
||||
fn get_sub_elements(&mut self, _: &MenuItem<T>) -> ProviderData<T> {
|
||||
ProviderData { items: None }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,12 +248,21 @@ fn calc(input: &str) -> String {
|
|||
}
|
||||
|
||||
/// Shows the math mode
|
||||
pub fn show(config: &Config) {
|
||||
let mut calc: Vec<MenuItem<String>> = vec![];
|
||||
pub fn show(config: Arc<RwLock<Config>>) {
|
||||
let mut calc: Vec<MenuItem<()>> = vec![];
|
||||
let provider = Arc::new(Mutex::new(MathProvider::new(())));
|
||||
let factory: ArcFactory<()> = Arc::new(Mutex::new(DefaultItemFactory::new()));
|
||||
let arc_provider = Arc::clone(&provider) as ArcProvider<()>;
|
||||
loop {
|
||||
let mut provider = MathProvider::new(String::new());
|
||||
provider.add_elements(&mut calc.clone());
|
||||
let selection_result = gui::show(config.clone(), provider, true, None, None);
|
||||
provider.lock().unwrap().add_elements(&mut calc.clone());
|
||||
let selection_result = gui::show(
|
||||
config.clone(),
|
||||
Arc::clone(&arc_provider),
|
||||
Some(Arc::clone(&factory)),
|
||||
None,
|
||||
ExpandMode::Verbatim,
|
||||
None,
|
||||
);
|
||||
if let Ok(mi) = selection_result {
|
||||
calc.push(mi.menu);
|
||||
} else {
|
||||
|
|
|
@ -4,32 +4,42 @@ use std::{
|
|||
ffi::CString,
|
||||
fs,
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
};
|
||||
|
||||
use crate::gui::ArcProvider;
|
||||
use crate::{
|
||||
Error,
|
||||
config::{Config, SortOrder},
|
||||
desktop::{is_executable, save_cache_file},
|
||||
gui::{self, ItemProvider, MenuItem},
|
||||
gui::{self, ExpandMode, ItemProvider, MenuItem, ProviderData},
|
||||
modes::load_cache,
|
||||
};
|
||||
|
||||
impl ItemProvider<i32> for RunProvider {
|
||||
fn get_elements(&mut self, _: Option<&str>) -> (bool, Vec<MenuItem<i32>>) {
|
||||
impl ItemProvider<()> for RunProvider {
|
||||
fn get_elements(&mut self, query: Option<&str>) -> ProviderData<()> {
|
||||
if self.items.is_none() {
|
||||
self.items = Some(self.load().clone());
|
||||
}
|
||||
(false, self.items.clone().unwrap())
|
||||
if query.is_some() {
|
||||
ProviderData { items: None }
|
||||
} else {
|
||||
ProviderData {
|
||||
items: self.items.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sub_elements(&mut self, _: &MenuItem<i32>) -> (bool, Option<Vec<MenuItem<i32>>>) {
|
||||
(false, None)
|
||||
fn get_sub_elements(&mut self, _: &MenuItem<()>) -> ProviderData<()> {
|
||||
ProviderData {
|
||||
items: self.items.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RunProvider {
|
||||
items: Option<Vec<MenuItem<i32>>>,
|
||||
items: Option<Vec<MenuItem<()>>>,
|
||||
cache_path: PathBuf,
|
||||
cache: HashMap<String, i64>,
|
||||
sort_order: SortOrder,
|
||||
|
@ -48,7 +58,7 @@ impl RunProvider {
|
|||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
fn load(&self) -> Vec<MenuItem<i32>> {
|
||||
fn load(&self) -> Vec<MenuItem<()>> {
|
||||
let path_var = env::var("PATH").unwrap_or_default();
|
||||
let paths = env::split_paths(&path_var);
|
||||
|
||||
|
@ -82,7 +92,7 @@ impl RunProvider {
|
|||
.collect();
|
||||
|
||||
let mut seen_actions = HashSet::new();
|
||||
let mut entries: Vec<MenuItem<i32>> = entries
|
||||
let mut entries: Vec<MenuItem<()>> = entries
|
||||
.into_iter()
|
||||
.filter(|entry| {
|
||||
entry
|
||||
|
@ -124,13 +134,23 @@ fn update_run_cache_and_run<T: Clone>(
|
|||
/// # Errors
|
||||
///
|
||||
/// Will return `Err` if it was not able to spawn the process
|
||||
pub fn show(config: &Config) -> Result<(), Error> {
|
||||
let provider = RunProvider::new(config)?;
|
||||
let cache_path = provider.cache_path.clone();
|
||||
let mut cache = provider.cache.clone();
|
||||
let selection_result = gui::show(config.clone(), provider, false, None, None);
|
||||
pub fn show(config: Arc<RwLock<Config>>) -> Result<(), Error> {
|
||||
let provider = Arc::new(Mutex::new(RunProvider::new(&config.read().unwrap())?));
|
||||
let arc_provider = Arc::clone(&provider) as ArcProvider<()>;
|
||||
|
||||
let selection_result = gui::show(
|
||||
config,
|
||||
arc_provider,
|
||||
None,
|
||||
None,
|
||||
ExpandMode::Verbatim,
|
||||
None,
|
||||
);
|
||||
match selection_result {
|
||||
Ok(s) => update_run_cache_and_run(&cache_path, &mut cache, s.menu)?,
|
||||
Ok(s) => {
|
||||
let prov = provider.lock().unwrap();
|
||||
update_run_cache_and_run(&prov.cache_path, &mut prov.cache.clone(), s.menu)?;
|
||||
}
|
||||
Err(_) => {
|
||||
log::error!("No item selected");
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use urlencoding::encode;
|
||||
|
||||
use crate::desktop::spawn_fork;
|
||||
use crate::{
|
||||
Error,
|
||||
config::Config,
|
||||
gui::{self, ItemProvider, MenuItem},
|
||||
desktop::spawn_fork,
|
||||
gui::{self, ArcFactory, DefaultItemFactory, ExpandMode, ItemProvider, MenuItem, ProviderData},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -23,7 +24,7 @@ impl<T: Clone> SearchProvider<T> {
|
|||
}
|
||||
|
||||
impl<T: Clone> ItemProvider<T> for SearchProvider<T> {
|
||||
fn get_elements(&mut self, query: Option<&str>) -> (bool, Vec<MenuItem<T>>) {
|
||||
fn get_elements(&mut self, query: Option<&str>) -> ProviderData<T> {
|
||||
if let Some(query) = query {
|
||||
let url = format!("{}{}", self.search_query, encode(query));
|
||||
let run_search = MenuItem::new(
|
||||
|
@ -35,14 +36,17 @@ impl<T: Clone> ItemProvider<T> for SearchProvider<T> {
|
|||
0.0,
|
||||
Some(self.data.clone()),
|
||||
);
|
||||
(true, vec![run_search])
|
||||
|
||||
ProviderData {
|
||||
items: Some(vec![run_search]),
|
||||
}
|
||||
} else {
|
||||
(false, vec![])
|
||||
ProviderData { items: None }
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sub_elements(&mut self, _: &MenuItem<T>) -> (bool, Option<Vec<MenuItem<T>>>) {
|
||||
(false, None)
|
||||
fn get_sub_elements(&mut self, _: &MenuItem<T>) -> ProviderData<T> {
|
||||
ProviderData { items: None }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,9 +54,20 @@ impl<T: Clone> ItemProvider<T> for SearchProvider<T> {
|
|||
/// # Errors
|
||||
///
|
||||
/// Forwards errors from the gui. See `gui::show` for details.
|
||||
pub fn show(config: &Config) -> Result<(), Error> {
|
||||
let provider = SearchProvider::new(String::new(), config.search_query());
|
||||
let selection_result = gui::show(config.clone(), provider, true, None, None)?;
|
||||
pub fn show(config: Arc<RwLock<Config>>) -> Result<(), Error> {
|
||||
let provider = Arc::new(Mutex::new(SearchProvider::new(
|
||||
(),
|
||||
config.read().unwrap().search_query(),
|
||||
)));
|
||||
let factory: ArcFactory<()> = Arc::new(Mutex::new(DefaultItemFactory::new()));
|
||||
let selection_result = gui::show(
|
||||
config.clone(),
|
||||
provider,
|
||||
Some(factory),
|
||||
None,
|
||||
ExpandMode::Verbatim,
|
||||
None,
|
||||
)?;
|
||||
match selection_result.menu.action {
|
||||
None => Err(Error::MissingAction),
|
||||
Some(action) => spawn_fork(&action, None),
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use std::fs;
|
||||
|
||||
use regex::Regex;
|
||||
use std::fs;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
use crate::gui::{ExpandMode, ProviderData};
|
||||
use crate::{
|
||||
Error,
|
||||
config::{Config, SortOrder},
|
||||
|
@ -11,7 +12,7 @@ use crate::{
|
|||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct SshProvider<T: Clone> {
|
||||
elements: Vec<MenuItem<T>>,
|
||||
items: Vec<MenuItem<T>>,
|
||||
}
|
||||
|
||||
impl<T: Clone> SshProvider<T> {
|
||||
|
@ -46,17 +47,23 @@ impl<T: Clone> SshProvider<T> {
|
|||
.collect();
|
||||
|
||||
gui::apply_sort(&mut items, order);
|
||||
Self { elements: items }
|
||||
Self { items }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> ItemProvider<T> for SshProvider<T> {
|
||||
fn get_elements(&mut self, _: Option<&str>) -> (bool, Vec<MenuItem<T>>) {
|
||||
(false, self.elements.clone())
|
||||
fn get_elements(&mut self, query: Option<&str>) -> ProviderData<T> {
|
||||
if query.is_some() {
|
||||
ProviderData { items: None }
|
||||
} else {
|
||||
ProviderData {
|
||||
items: Some(self.items.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sub_elements(&mut self, _: &MenuItem<T>) -> (bool, Option<Vec<MenuItem<T>>>) {
|
||||
(false, None)
|
||||
fn get_sub_elements(&mut self, _: &MenuItem<T>) -> ProviderData<T> {
|
||||
ProviderData { items: None }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,11 +94,21 @@ pub(crate) fn launch<T: Clone>(menu_item: &MenuItem<T>, config: &Config) -> Resu
|
|||
/// Will return `Err`
|
||||
/// * if it was not able to spawn the process
|
||||
/// * if it didn't find a terminal
|
||||
pub fn show(config: &Config) -> Result<(), Error> {
|
||||
let provider = SshProvider::new(0, &config.sort_order());
|
||||
let selection_result = gui::show(config.clone(), provider, true, None, None);
|
||||
pub fn show(config: Arc<RwLock<Config>>) -> Result<(), Error> {
|
||||
let provider = Arc::new(Mutex::new(SshProvider::new(
|
||||
0,
|
||||
&config.read().unwrap().sort_order(),
|
||||
)));
|
||||
let selection_result = gui::show(
|
||||
Arc::clone(&config),
|
||||
provider,
|
||||
None,
|
||||
None,
|
||||
ExpandMode::Verbatim,
|
||||
None,
|
||||
);
|
||||
if let Ok(mi) = selection_result {
|
||||
launch(&mi.menu, config)?;
|
||||
launch(&mi.menu, &config.read().unwrap())?;
|
||||
} else {
|
||||
log::error!("No item selected");
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::env;
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
use worf::{Error, config, config::Mode, desktop::fork_if_configured, modes};
|
||||
|
||||
fn main() {
|
||||
|
@ -27,19 +27,20 @@ fn main() {
|
|||
fork_if_configured(&config); // may exit the program
|
||||
|
||||
if let Some(show) = &config.show() {
|
||||
let config = Arc::new(RwLock::new(config));
|
||||
let result = match show {
|
||||
Mode::Run => modes::run::show(&config),
|
||||
Mode::Drun => modes::drun::show(&config),
|
||||
Mode::Dmenu => modes::dmenu::show(&config),
|
||||
Mode::File => modes::file::show(&config),
|
||||
Mode::Run => modes::run::show(config),
|
||||
Mode::Drun => modes::drun::show(config),
|
||||
Mode::Dmenu => modes::dmenu::show(config),
|
||||
Mode::File => modes::file::show(config),
|
||||
Mode::Math => {
|
||||
modes::math::show(&config);
|
||||
modes::math::show(config);
|
||||
Ok(())
|
||||
}
|
||||
Mode::Ssh => modes::ssh::show(&config),
|
||||
Mode::Emoji => modes::emoji::show(&config),
|
||||
Mode::Ssh => modes::ssh::show(config),
|
||||
Mode::Emoji => modes::emoji::show(config),
|
||||
Mode::Auto => modes::auto::show(&config),
|
||||
Mode::WebSearch => modes::search::show(&config),
|
||||
Mode::WebSearch => modes::search::show(config),
|
||||
};
|
||||
|
||||
if let Err(err) = result {
|
||||
|
|
Loading…
Add table
Reference in a new issue