diff --git a/README.md b/README.md index b4f6748..99b6161 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ It supports various modes: * DRun * File * Ssh +* Run * // WebSearch * // Emoji -* // Run * Auto Auto mode tries to detect the desired mode automatically, i.e. `ssh`, `?` (for web search), `emoji`, `/` or `~` (for file). @@ -22,7 +22,6 @@ Auto mode tries to detect the desired mode automatically, i.e. `ssh`, `?` (for w ## Not finished -* [ ] run * [ ] key support * [ ] full config support * [ ] web search mode diff --git a/src/lib/desktop.rs b/src/lib/desktop.rs index c86f87b..9a2d538 100644 --- a/src/lib/desktop.rs +++ b/src/lib/desktop.rs @@ -4,13 +4,13 @@ use freedesktop_file_parser::DesktopFile; use rayon::prelude::*; use regex::Regex; use std::collections::HashMap; +use std::os::unix::fs::PermissionsExt; use std::os::unix::prelude::CommandExt; use std::path::Path; use std::path::PathBuf; use std::process::{Command, Stdio}; use std::time::Instant; use std::{env, fs, io}; -use std::os::unix::fs::PermissionsExt; /// Returns a regex with supported image extensions /// # Panics @@ -281,9 +281,9 @@ pub fn create_file_if_not_exists(path: &PathBuf) -> Result<(), Error> { } } - /// Check if the given dir entry is an executable -pub fn is_executable(entry: &PathBuf) -> bool { +#[must_use] +pub fn is_executable(entry: &Path) -> bool { if let Ok(metadata) = entry.metadata() { let permissions = metadata.permissions(); metadata.is_file() && (permissions.mode() & 0o111 != 0) diff --git a/src/lib/gui.rs b/src/lib/gui.rs index bc90e8b..e543ce3 100644 --- a/src/lib/gui.rs +++ b/src/lib/gui.rs @@ -1,5 +1,4 @@ use std::collections::HashMap; -use std::ops::{Deref, DerefMut}; use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::thread; @@ -23,7 +22,7 @@ use gtk4::prelude::{ use gtk4::{ Align, EventControllerKey, Expander, FlowBox, FlowBoxChild, GestureClick, Image, Label, ListBox, ListBoxRow, NaturalWrapMode, Ordering, PolicyType, ScrolledWindow, SearchEntry, - Widget, gdk, + Widget, gdk, glib, }; use gtk4::{Application, ApplicationWindow, CssProvider, Orientation}; use gtk4_layer_shell::{Edge, KeyboardMode, LayerShell}; @@ -217,7 +216,6 @@ fn build_ui( }); let provider_clone = Arc::clone(&meta.item_provider); - let get_provider_elements = thread::spawn(move || { log::debug!("getting items"); provider_clone.lock().unwrap().get_elements(None) @@ -239,8 +237,6 @@ fn build_ui( menu_rows: Arc::new(Mutex::new(HashMap::new())), }); - let window_show = Instant::now(); - // handle keys as soon as possible setup_key_event_handler(&ui_elements, &meta); @@ -260,8 +256,6 @@ fn build_ui( ui_elements.window.set_namespace(Some("worf")); } - let window_done = Instant::now(); - if let Some(location) = config.location() { for anchor in location { ui_elements.window.set_anchor(anchor.into(), true); @@ -293,26 +287,14 @@ fn build_ui( let wait_for_items = Instant::now(); let (_changed, provider_elements) = get_provider_elements.join().unwrap(); log::debug!("got items after {:?}", wait_for_items.elapsed()); - { - let mut lock = ui_elements.menu_rows.lock().unwrap(); - build_ui_from_menu_items(&ui_elements, &meta, &provider_elements, lock.deref_mut()); - } - - let items_sort = ArcMenuMap::clone(&ui_elements.menu_rows); - ui_elements - .main_box - .set_sort_func(move |child1, child2| sort_menu_items_by_score(child1, child2, &items_sort)); + build_ui_from_menu_items(&ui_elements, &meta, provider_elements); + let window_start = Instant::now(); ui_elements.window.show(); - animate_window_show(config, ui_elements.window.clone()); + log::debug!("window show took {:?}", window_start.elapsed()); - let animation_done = Instant::now(); - log::debug!( - "Building UI took {:?}, window show {:?}, animation {:?}", - start.elapsed(), - window_done - window_show, - animation_done - window_done - ); + animate_window_show(config, ui_elements.window.clone()); + log::debug!("Building UI took {:?}", start.elapsed(),); } fn build_main_box(config: &Config, ui_elements: &Rc>) { @@ -343,7 +325,7 @@ fn build_main_box(config: &Config, ui_elements: &Rc(config: &Config, ui_elements: &UiElements) { fn build_ui_from_menu_items( ui: &Rc>, meta: &Rc>, - items: &Vec>, - map: &mut HashMap>, + mut items: Vec>, ) { let start = Instant::now(); { - let got_lock = Instant::now(); - - ui.main_box.unset_sort_func(); - while let Some(b) = ui.main_box.child_at_index(0) { ui.main_box.remove(&b); drop(b); } - map.clear(); + ui.menu_rows.lock().unwrap().clear(); - let cleared_box = Instant::now(); + let meta_clone = Rc::>::clone(meta); + let ui_clone = Rc::>::clone(ui); - for entry in items { - if entry.visible { - map.insert(add_menu_item(ui, meta, entry), (*entry).clone()); + glib::idle_add_local(move || { + ui_clone.main_box.unset_sort_func(); + let mut done = false; + { + let mut lock = ui_clone.menu_rows.lock().unwrap(); + + for _ in 0..100 { + if let Some(item) = items.pop() { + lock.insert(add_menu_item(&ui_clone, &meta_clone, &item), item); + } else { + done = true; + } + } + + let query = ui_clone.search.text(); + let menus = &mut *lock; + set_menu_visibility_for_search(&query, menus, &meta_clone.config); + select_first_visible_child(&*lock, &ui_clone.main_box); } - } - let created_ui = Instant::now(); - log::debug!( - "Creating UI took {:?}, got lock after {:?}, cleared box after {:?}, created UI after {:?}", - start.elapsed(), - got_lock - start, - cleared_box - start, - created_ui - start - ); + let items_sort = ArcMenuMap::clone(&ui_clone.menu_rows); + ui_clone.main_box.set_sort_func(move |child1, child2| { + sort_menu_items_by_score(child1, child2, &items_sort) + }); + + if done { + log::debug!("Created menu items in {:?}", start.elapsed()); + ControlFlow::Break + } else { + ControlFlow::Continue + } + }); } } @@ -419,8 +415,8 @@ fn handle_key_press( ) -> Propagation { let update_view = |query: &String| { let mut lock = ui.menu_rows.lock().unwrap(); - let mut menus = lock.iter_mut().map(|(s, v)| v).collect::>(); - set_menu_visibility_for_search(query, menus.as_mut_slice(), &meta.config); + let menus = &mut *lock; + set_menu_visibility_for_search(query, menus, &meta.config); for (fb, item) in lock.iter() { fb.set_visible(item.visible); } @@ -431,18 +427,13 @@ fn handle_key_press( let update_view_from_provider = |query: &String| { let (changed, filtered_list) = meta.item_provider.lock().unwrap().get_elements(Some(query)); if changed { - - let mut lock = ui.menu_rows.lock().unwrap(); - build_ui_from_menu_items(&ui, &meta, &filtered_list, lock.deref_mut()); + build_ui_from_menu_items(ui, meta, filtered_list); } update_view(query); }; match keyboard_key { - Key::Down => { - return Propagation::Stop; - } Key::Escape => { if let Err(e) = meta.selected_sender.send(Err(anyhow!("No item selected"))) { log::error!("failed to send message {e}"); @@ -481,8 +472,7 @@ fn handle_key_press( let items = items.unwrap_or_default(); if changed { - let mut lock = ui.menu_rows.lock().unwrap(); - build_ui_from_menu_items(ui, meta, &items, &mut *lock); + build_ui_from_menu_items(ui, meta, items); } let query = menu_item.label.clone(); @@ -526,16 +516,20 @@ fn sort_menu_items_by_score( match (m1, m2) { (Some(menu1), Some(menu2)) => { - if menu1.search_sort_score > 0.0 || menu2.search_sort_score > 0.0 { - if menu1.search_sort_score < menu2.search_sort_score { + fn compare(a: f64, b: f64) -> Ordering { + if a > b { Ordering::Smaller - } else { + } else if a < b { Ordering::Larger + } else { + Ordering::Equal } - } else if menu1.initial_sort_score > menu2.initial_sort_score { - Ordering::Smaller + } + + if menu1.search_sort_score > 0.0 || menu2.search_sort_score > 0.0 { + compare(menu1.search_sort_score, menu2.search_sort_score) } else { - Ordering::Larger + compare(menu1.initial_sort_score, menu2.initial_sort_score) } } (Some(_), None) => Ordering::Larger, @@ -863,14 +857,15 @@ fn lookup_icon(menu_item: &MenuItem, config: &Config) -> Option( query: &str, - items: &mut [&mut MenuItem], + items: &mut HashMap>, config: &Config, ) { { if query.is_empty() { - for menu_item in items.iter_mut() { - menu_item.search_sort_score = menu_item.initial_sort_score; + 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); } } else { let query = if config.insensitive() { @@ -878,7 +873,7 @@ fn set_menu_visibility_for_search( } else { query.to_owned() }; - for menu_item in items.iter_mut() { + for (fb, menu_item) in items.iter_mut() { let menu_item_search = format!( "{} {}", menu_item @@ -917,6 +912,7 @@ fn set_menu_visibility_for_search( menu_item.search_sort_score = search_sort_score + menu_item.initial_sort_score; menu_item.visible = visible; + fb.set_visible(menu_item.visible); } } } diff --git a/src/lib/mode.rs b/src/lib/mode.rs index b504aa7..f63e47f 100644 --- a/src/lib/mode.rs +++ b/src/lib/mode.rs @@ -1,5 +1,8 @@ use crate::config::{Config, expand_path}; -use crate::desktop::{create_file_if_not_exists, find_desktop_files, get_locale_variants, is_executable, load_cache_file, lookup_name_with_locale, save_cache_file, spawn_fork}; +use crate::desktop::{ + create_file_if_not_exists, find_desktop_files, get_locale_variants, is_executable, + load_cache_file, lookup_name_with_locale, save_cache_file, spawn_fork, +}; use crate::gui::{ItemProvider, MenuItem}; use crate::{Error, gui}; use freedesktop_file_parser::EntryType; @@ -7,11 +10,11 @@ use rayon::prelude::*; use regex::Regex; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; +use std::ffi::CString; use std::io::Read; use std::path::{Path, PathBuf}; use std::time::Instant; use std::{env, fs, io}; -use std::ffi::CString; #[derive(Debug, Deserialize, Serialize, Clone)] struct DRunCache { @@ -143,45 +146,43 @@ impl DRunProvider { } } -impl ItemProvider for RunProvider { - fn get_elements(&mut self, _: Option<&str>) -> (bool, Vec>) { +impl ItemProvider for RunProvider { + fn get_elements(&mut self, _: Option<&str>) -> (bool, Vec>) { if self.items.is_none() { self.items = Some(self.load().clone()); } (false, self.items.clone().unwrap()) } - fn get_sub_elements(&mut self, _: &MenuItem) -> (bool, std::option::Option>>) { + fn get_sub_elements(&mut self, _: &MenuItem) -> (bool, Option>>) { (false, None) } } #[derive(Clone)] -struct RunProvider { - items: Option>>, +struct RunProvider { + items: Option>>, cache_path: Option, cache: HashMap, - data: T, } -impl RunProvider { - fn new(menu_item_data: T) -> Self { +impl RunProvider { + fn new() -> Self { let (cache_path, d_run_cache) = load_run_cache(); RunProvider { items: None, cache_path, cache: d_run_cache, - data: menu_item_data, } } #[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_precision_loss)] - fn load(&self) -> Vec> { + fn load(&self) -> Vec> { let path_var = env::var("PATH").unwrap_or_default(); let paths = env::split_paths(&path_var); - let entries : Vec<_>= paths + let entries: Vec<_> = paths .filter(|dir| dir.is_dir()) .flat_map(|dir| { fs::read_dir(dir) @@ -212,9 +213,8 @@ impl RunProvider { }) .collect(); - let mut seen_actions = HashSet::new(); - let mut entries: Vec> = entries + let mut entries: Vec> = entries .into_iter() .filter(|entry| { if let Some(action) = &entry.action { @@ -229,26 +229,24 @@ impl RunProvider { }) .collect(); - gui::sort_menu_items_alphabetically_honor_initial_score(&mut entries); entries } } impl ItemProvider for DRunProvider { - fn get_elements(&mut self, _: Option<&str>) -> (bool, Vec>) { + fn get_elements(&mut self, _: Option<&str>) -> (bool, Vec>) { if self.items.is_none() { self.items = Some(self.load().clone()); } (false, self.items.clone().unwrap()) } - fn get_sub_elements(&mut self, _: &MenuItem) -> (bool, std::option::Option>>) { + fn get_sub_elements(&mut self, _: &MenuItem) -> (bool, Option>>) { (false, None) } } - #[derive(Clone)] struct FileItemProvider { last_result: Option>>, @@ -304,7 +302,7 @@ impl FileItemProvider { } impl ItemProvider for FileItemProvider { - fn get_elements(&mut self, search: Option<&str>) -> (bool, Vec>) { + fn get_elements(&mut self, search: Option<&str>) -> (bool, Vec>) { let default_path = if let Some(home) = dirs::home_dir() { home.display().to_string() } else { @@ -389,7 +387,7 @@ impl ItemProvider for FileItemProvider { (true, items) } - fn get_sub_elements(&mut self, _: &MenuItem) -> (bool, std::option::Option>>) { + fn get_sub_elements(&mut self, _: &MenuItem) -> (bool, Option>>) { (false, self.last_result.clone()) } } @@ -435,11 +433,11 @@ impl SshProvider { } impl ItemProvider for SshProvider { - fn get_elements(&mut self, _: Option<&str>) -> (bool, Vec>) { + fn get_elements(&mut self, _: Option<&str>) -> (bool, Vec>) { (false, self.elements.clone()) } - fn get_sub_elements(&mut self, _: &MenuItem) -> (bool, std::option::Option>>) { + fn get_sub_elements(&mut self, _: &MenuItem) -> (bool, Option>>) { (false, None) } } @@ -477,7 +475,7 @@ impl MathProvider { } impl ItemProvider for MathProvider { - fn get_elements(&mut self, search: Option<&str>) -> (bool, Vec>) { + fn get_elements(&mut self, search: Option<&str>) -> (bool, Vec>) { if let Some(search_text) = search { let result = match meval::eval_str(search_text) { Ok(result) => result.to_string(), @@ -501,7 +499,7 @@ impl ItemProvider for MathProvider { } } - fn get_sub_elements(&mut self, _: &MenuItem) -> (bool, std::option::Option>>) { + fn get_sub_elements(&mut self, _: &MenuItem) -> (bool, Option>>) { (false, None) } } @@ -529,11 +527,11 @@ impl DMenuProvider { } impl ItemProvider for DMenuProvider { - fn get_elements(&mut self, _: Option<&str>) -> (bool, Vec>) { + fn get_elements(&mut self, _: Option<&str>) -> (bool, Vec>) { (false, self.items.clone()) } - fn get_sub_elements(&mut self, _: &MenuItem) -> (bool, Option>>) { + fn get_sub_elements(&mut self, _: &MenuItem) -> (bool, Option>>) { (false, None) } } @@ -569,7 +567,7 @@ impl AutoItemProvider { } impl ItemProvider for AutoItemProvider { - fn get_elements(&mut self, search_opt: Option<&str>) -> (bool, Vec>) { + fn get_elements(&mut self, search_opt: Option<&str>) -> (bool, Vec>) { if let Some(search) = search_opt { let trimmed_search = search.trim(); if trimmed_search.is_empty() { @@ -599,7 +597,7 @@ impl ItemProvider for AutoItemProvider { fn get_sub_elements( &mut self, item: &MenuItem, - ) -> (bool, std::option::Option>>) { + ) -> (bool, Option>>) { let (changed, items) = self.get_elements(Some(item.label.as_ref())); (changed, Some(items)) } @@ -631,7 +629,7 @@ pub fn d_run(config: &Config) -> Result<(), Error> { /// /// Will return `Err` if it was not able to spawn the process pub fn run(config: &Config) -> Result<(), Error> { - let provider = RunProvider::new(String::new()); + let provider = RunProvider::new(); let cache_path = provider.cache_path.clone(); let mut cache = provider.cache.clone(); @@ -646,7 +644,6 @@ pub fn run(config: &Config) -> Result<(), Error> { Ok(()) } - /// Shows the auto mode /// # Errors /// @@ -809,7 +806,6 @@ fn update_drun_cache_and_run( } } - fn update_run_cache_and_run( cache_path: Option, cache: &mut HashMap, @@ -836,7 +832,7 @@ fn update_run_cache_and_run( fn load_d_run_cache() -> (Option, HashMap) { let cache_path = dirs::cache_dir().map(|x| x.join("worf-drun")); - load_cache(cache_path) + load_cache(cache_path) } fn load_run_cache() -> (Option, HashMap) { @@ -844,7 +840,6 @@ fn load_run_cache() -> (Option, HashMap) { load_cache(cache_path) } - fn load_cache(cache_path: Option) -> (Option, HashMap) { let cache = { if let Some(ref cache_path) = cache_path {