From cd2630b2cdc7ebee2cd3d8d16e71ab829fd8c698 Mon Sep 17 00:00:00 2001 From: multiplex55 <6619098+multiplex55@users.noreply.github.com> Date: Wed, 31 Dec 2025 20:11:33 -0500 Subject: [PATCH 1/3] Handle multibyte selections in note panel --- src/gui/note_panel.rs | 181 +++++++++++++++++++++++++++++------------- 1 file changed, 128 insertions(+), 53 deletions(-) diff --git a/src/gui/note_panel.rs b/src/gui/note_panel.rs index 1ba35928..fc6fe6df 100644 --- a/src/gui/note_panel.rs +++ b/src/gui/note_panel.rs @@ -74,7 +74,7 @@ pub struct NotePanel { show_open_with_menu: bool, tags_expanded: bool, links_expanded: bool, - pending_selection: Option<(usize, usize)>, + pending_selection: Option<(usize, usize)>, // byte offsets link_dialog_open: bool, link_text: String, link_url: String, @@ -431,7 +431,8 @@ impl NotePanel { if let Some(range) = state.cursor.char_range() { let [min, max] = range.sorted(); if min.index != max.index { - self.pending_selection = Some((min.index, max.index)); + self.pending_selection = + char_range_to_byte_range(&self.note.content, range); } else { self.pending_selection = None; } @@ -480,32 +481,44 @@ impl NotePanel { } if ctx.input(|i| i.key_pressed(Key::J)) { ctx.input_mut(|i| i.consume_key(egui::Modifiers::NONE, Key::J)); - if let Some(pos) = self.note.content[idx..].find('\n') { - idx += pos + 1; - } else { - idx = self.note.content.chars().count(); - } + let after = + self.note.content.chars().skip(idx).position(|c| c == '\n'); + idx = after + .map(|pos| idx + pos + 1) + .unwrap_or_else(|| self.note.content.chars().count()); moved = true; } if ctx.input(|i| i.key_pressed(Key::K)) { ctx.input_mut(|i| i.consume_key(egui::Modifiers::NONE, Key::K)); - if let Some(pos) = self.note.content[..idx].rfind('\n') { - idx = pos; - } else { - idx = 0; - } + idx = self + .note + .content + .chars() + .take(idx) + .rposition(|c| c == '\n') + .unwrap_or(0); moved = true; } if ctx.input(|i| i.key_pressed(Key::Y)) { ctx.input_mut(|i| i.consume_key(egui::Modifiers::NONE, Key::Y)); - let start = self.note.content[..idx] - .rfind('\n') + let start_char = self + .note + .content + .chars() + .take(idx) + .rposition(|c| c == '\n') .map(|p| p + 1) .unwrap_or(0); - let end = self.note.content[idx..] - .find('\n') + let end_char = self + .note + .content + .chars() + .skip(idx) + .position(|c| c == '\n') .map(|p| idx + p) - .unwrap_or_else(|| self.note.content.len()); + .unwrap_or_else(|| self.note.content.chars().count()); + let start = char_to_byte_index(&self.note.content, start_char); + let end = char_to_byte_index(&self.note.content, end_char); ctx.output_mut(|o| { o.copied_text = self.note.content[start..end].to_string(); }); @@ -722,10 +735,8 @@ impl NotePanel { if self.pending_selection.is_none() { let state = egui::widgets::text_edit::TextEditState::load(ctx, id).unwrap_or_default(); if let Some(range) = state.cursor.char_range() { - let [min, max] = range.sorted(); - if min.index != max.index { - self.pending_selection = Some((min.index, max.index)); - } + self.pending_selection = + char_range_to_byte_range(&self.note.content, range).or(self.pending_selection); } } @@ -733,16 +744,20 @@ impl NotePanel { if ui.button("Add Checkbox").clicked() { let mut state = egui::widgets::text_edit::TextEditState::load(ctx, id).unwrap_or_default(); - let idx = state + let idx_chars = state .cursor .char_range() .map(|r| r.primary.index) .unwrap_or_else(|| self.note.content.chars().count()); + let idx = char_to_byte_index(&self.note.content, idx_chars); self.note.content.insert_str(idx, "- [ ] "); state .cursor .set_char_range(Some(egui::text::CCursorRange::one( - egui::text::CCursor::new(idx + 6), + egui::text::CCursor::new(byte_to_char_index( + &self.note.content, + idx + "- [ ] ".len(), + )), ))); state.store(ctx, id); ui.close_menu(); @@ -784,16 +799,20 @@ impl NotePanel { let insert = format!("[[{title}]]"); let mut state = egui::widgets::text_edit::TextEditState::load(ctx, id) .unwrap_or_default(); - let idx = state + let idx_chars = state .cursor .char_range() .map(|r| r.primary.index) .unwrap_or_else(|| self.note.content.chars().count()); + let idx = char_to_byte_index(&self.note.content, idx_chars); self.note.content.insert_str(idx, &insert); state .cursor .set_char_range(Some(egui::text::CCursorRange::one( - egui::text::CCursor::new(idx + insert.chars().count()), + egui::text::CCursor::new(byte_to_char_index( + &self.note.content, + idx + insert.len(), + )), ))); state.store(ctx, id); self.link_search.clear(); @@ -818,16 +837,20 @@ impl NotePanel { let insert = format!("![{0}](assets/{0})", fname); let mut state = egui::widgets::text_edit::TextEditState::load(ctx, id) .unwrap_or_default(); - let idx = state + let idx_chars = state .cursor .char_range() .map(|r| r.primary.index) .unwrap_or_else(|| self.note.content.chars().count()); + let idx = char_to_byte_index(&self.note.content, idx_chars); self.note.content.insert_str(idx, &insert); state .cursor .set_char_range(Some(egui::text::CCursorRange::one( - egui::text::CCursor::new(idx + insert.chars().count()), + egui::text::CCursor::new(byte_to_char_index( + &self.note.content, + idx + insert.len(), + )), ))); state.store(ctx, id); self.image_search.clear(); @@ -852,16 +875,20 @@ impl NotePanel { let mut state = egui::widgets::text_edit::TextEditState::load(ctx, id) .unwrap_or_default(); - let idx = state + let idx_chars = state .cursor .char_range() .map(|r| r.primary.index) .unwrap_or_else(|| self.note.content.chars().count()); + let idx = char_to_byte_index(&self.note.content, idx_chars); self.note.content.insert_str(idx, &insert); state .cursor .set_char_range(Some(egui::text::CCursorRange::one( - egui::text::CCursor::new(idx + insert.chars().count()), + egui::text::CCursor::new(byte_to_char_index( + &self.note.content, + idx + insert.len(), + )), ))); state.store(ctx, id); self.image_search.clear(); @@ -888,16 +915,20 @@ impl NotePanel { let insert = format!("![{0}](assets/{0})", img); let mut state = egui::widgets::text_edit::TextEditState::load(ctx, id) .unwrap_or_default(); - let idx = state + let idx_chars = state .cursor .char_range() .map(|r| r.primary.index) .unwrap_or_else(|| self.note.content.chars().count()); + let idx = char_to_byte_index(&self.note.content, idx_chars); self.note.content.insert_str(idx, &insert); state .cursor .set_char_range(Some(egui::text::CCursorRange::one( - egui::text::CCursor::new(idx + insert.chars().count()), + egui::text::CCursor::new(byte_to_char_index( + &self.note.content, + idx + insert.len(), + )), ))); state.store(ctx, id); self.image_search.clear(); @@ -920,14 +951,10 @@ impl NotePanel { end_marker: &str, ) { let mut state = egui::widgets::text_edit::TextEditState::load(ctx, id).unwrap_or_default(); - let mut range = state.cursor.char_range().and_then(|r| { - let [min, max] = r.sorted(); - if min.index != max.index { - Some((min.index, max.index)) - } else { - None - } - }); + let mut range = state + .cursor + .char_range() + .and_then(|r| char_range_to_byte_range(&self.note.content, r)); if range.is_none() { range = self.pending_selection.take(); @@ -938,13 +965,13 @@ impl NotePanel { if let Some((start, end)) = range { self.note.content.insert_str(end, end_marker); self.note.content.insert_str(start, start_marker); - let new_start = start + start_marker.chars().count(); - let new_end = end + start_marker.chars().count(); + let new_start = start + start_marker.len(); + let new_end = end + start_marker.len(); state .cursor .set_char_range(Some(egui::text::CCursorRange::two( - egui::text::CCursor::new(new_start), - egui::text::CCursor::new(new_end), + egui::text::CCursor::new(byte_to_char_index(&self.note.content, new_start)), + egui::text::CCursor::new(byte_to_char_index(&self.note.content, new_end)), ))); state.store(ctx, id); } @@ -965,7 +992,7 @@ impl NotePanel { self.note.content.replace_range(start..end, &insert); let mut state = egui::widgets::text_edit::TextEditState::load(ctx, id).unwrap_or_default(); - let cursor = start + insert.chars().count(); + let cursor = byte_to_char_index(&self.note.content, start + insert.len()); state .cursor .set_char_range(Some(egui::text::CCursorRange::one( @@ -975,13 +1002,14 @@ impl NotePanel { } else { let mut state = egui::widgets::text_edit::TextEditState::load(ctx, id).unwrap_or_default(); - let idx = state + let idx_chars = state .cursor .char_range() .map(|r| r.primary.index) .unwrap_or_else(|| self.note.content.chars().count()); + let idx = char_to_byte_index(&self.note.content, idx_chars); self.note.content.insert_str(idx, &insert); - let cursor = idx + insert.chars().count(); + let cursor = byte_to_char_index(&self.note.content, idx + insert.len()); state .cursor .set_char_range(Some(egui::text::CCursorRange::one( @@ -1069,16 +1097,20 @@ fn insert_tag_menu( let insert = format!("#{tag}"); let mut state = egui::widgets::text_edit::TextEditState::load(ctx, id).unwrap_or_default(); - let idx = state + let idx_chars = state .cursor .char_range() .map(|r| r.primary.index) .unwrap_or_else(|| content.chars().count()); + let idx = char_to_byte_index(content, idx_chars); content.insert_str(idx, &insert); state .cursor .set_char_range(Some(egui::text::CCursorRange::one( - egui::text::CCursor::new(idx + insert.chars().count()), + egui::text::CCursor::new(byte_to_char_index( + content, + idx + insert.len(), + )), ))); state.store(ctx, id); search.clear(); @@ -1088,6 +1120,33 @@ fn insert_tag_menu( }); } +fn char_to_byte_index(text: &str, char_idx: usize) -> usize { + if char_idx == 0 { + return 0; + } + match text.char_indices().nth(char_idx) { + Some((idx, _)) => idx, + None => text.len(), + } +} + +fn byte_to_char_index(text: &str, byte_idx: usize) -> usize { + let clamped = byte_idx.min(text.len()); + text[..clamped].chars().count() +} + +fn char_range_to_byte_range(text: &str, range: egui::text::CCursorRange) -> Option<(usize, usize)> { + let [min, max] = range.sorted(); + if min.index == max.index { + None + } else { + Some(( + char_to_byte_index(text, min.index), + char_to_byte_index(text, max.index), + )) + } +} + fn detect_shell() -> PathBuf { let ps7_path = env::var("ML_PWSH7_PATH") .map(PathBuf::from) @@ -1272,6 +1331,26 @@ mod tests { assert!(panel.pending_selection.is_none()); } + #[test] + fn wrap_selection_handles_multibyte_content() { + let ctx = egui::Context::default(); + let mut panel = NotePanel::from_note(empty_note("café ☕ note")); + let id = egui::Id::new("note_content"); + let mut state = egui::widgets::text_edit::TextEditState::load(&ctx, id).unwrap_or_default(); + let selection = + egui::text::CCursorRange::two(egui::text::CCursor::new(0), egui::text::CCursor::new(4)); + state.cursor.set_char_range(Some(selection)); + state.store(&ctx, id); + panel.pending_selection = char_range_to_byte_range(&panel.note.content, selection); + + panel.wrap_selection(&ctx, id, "**", "**"); + assert_eq!(panel.note.content, "**café** ☕ note"); + let state = egui::widgets::text_edit::TextEditState::load(&ctx, id).unwrap(); + let range = state.cursor.char_range().unwrap().sorted(); + assert_eq!((range[0].index, range[1].index), (2, 6)); + assert!(panel.pending_selection.is_none()); + } + #[test] fn insert_link_replaces_selection() { let ctx = egui::Context::default(); @@ -1469,11 +1548,7 @@ mod tests { }); }); assert_eq!( - output - .platform_output - .open_url - .unwrap() - .url, + output.platform_output.open_url.unwrap().url, "https://www.example.com" ); } From 8dc3ab734a707362d85474edf19166e6cec3eaee Mon Sep 17 00:00:00 2001 From: multiplex55 <6619098+multiplex55@users.noreply.github.com> Date: Wed, 31 Dec 2025 20:27:05 -0500 Subject: [PATCH 2/3] Fix newline search without ExactSizeIterator --- benches/search.rs | 4 +- src/actions/calc.rs | 11 +- src/actions/clipboard.rs | 15 +- src/actions/fav.rs | 12 +- src/actions/folders.rs | 10 +- src/actions/history.rs | 1 - src/actions/mod.rs | 22 +- src/actions/screenshot.rs | 8 +- src/actions/snippets.rs | 11 +- src/actions/system.rs | 237 +++++++++++----------- src/actions/timer.rs | 8 +- src/actions/todo.rs | 19 +- src/actions_editor.rs | 88 ++++---- src/common/json_watch.rs | 49 +++-- src/common/lru.rs | 1 - src/common/mod.rs | 2 +- src/common/slug.rs | 3 +- src/global_hotkey.rs | 5 +- src/gui/add_action_dialog.rs | 25 ++- src/gui/alias_dialog.rs | 9 +- src/gui/bookmark_alias_dialog.rs | 8 +- src/gui/brightness_dialog.rs | 19 +- src/gui/convert_panel.rs | 8 +- src/gui/fav_dialog.rs | 4 +- src/gui/note_panel.rs | 40 ++-- src/gui/screenshot_editor.rs | 3 +- src/gui/unused_assets_dialog.rs | 24 ++- src/gui/volume_dialog.rs | 22 +- src/plugin.rs | 40 ++-- src/plugins/bookmarks.rs | 4 +- src/plugins/brightness.rs | 4 +- src/plugins/calc_history.rs | 8 +- src/plugins/clipboard.rs | 2 +- src/plugins/color_picker.rs | 34 +++- src/plugins/convert_panel.rs | 1 - src/plugins/dropcalc.rs | 7 +- src/plugins/help.rs | 13 +- src/plugins/lorem.rs | 22 +- src/plugins/mod.rs | 76 +++---- src/plugins/processes.rs | 27 ++- src/plugins/random.rs | 8 +- src/plugins/recycle.rs | 8 +- src/plugins/reddit.rs | 12 +- src/plugins/runescape.rs | 20 +- src/plugins/screenshot.rs | 8 +- src/plugins/settings.rs | 1 - src/plugins/sysinfo.rs | 28 ++- src/plugins/system.rs | 7 +- src/plugins/task_manager.rs | 1 - src/plugins/text_case.rs | 333 +++++++++++++++++++++++++++---- src/plugins/timestamp.rs | 18 +- src/plugins/weather.rs | 13 +- src/plugins/wikipedia.rs | 8 +- src/plugins/windows.rs | 16 +- src/plugins/youtube.rs | 12 +- src/plugins_builtin.rs | 15 +- src/sound.rs | 1 - src/visibility.rs | 16 +- tests/bookmarks_plugin.rs | 37 +++- tests/brightness_plugin.rs | 2 +- tests/clipboard_persistence.rs | 2 +- tests/convert_panel_plugin.rs | 1 - tests/fav_plugin.rs | 4 +- tests/file_drop.rs | 11 +- tests/focus_visibility.rs | 9 +- tests/folders_plugin.rs | 2 +- tests/follow_mouse.rs | 4 +- tests/gui_visibility.rs | 8 +- tests/history.rs | 15 +- tests/hotkey.rs | 2 +- tests/hotkey_events.rs | 9 +- tests/indexer.rs | 6 +- tests/macros_plugin.rs | 1 - tests/missing_plugin.rs | 2 +- tests/note_panel_scroll.rs | 1 - tests/offscreen.rs | 13 +- tests/omni_search_plugin.rs | 65 ++++-- tests/processes_plugin.rs | 24 ++- tests/quit_finished.rs | 4 +- tests/quit_hotkey.rs | 10 +- tests/ranking.rs | 40 +++- tests/recycle_plugin.rs | 6 +- tests/resilience.rs | 6 +- tests/runescape_plugin.rs | 5 +- tests/screenshot_plugin.rs | 6 +- tests/settings_plugin.rs | 15 +- tests/system_plugin.rs | 2 +- tests/text_case_plugin.rs | 1 - tests/timer_plugin.rs | 165 ++++++++------- tests/timestamp_plugin.rs | 10 +- tests/tmp_rm_refresh.rs | 13 +- tests/todo_dialog.rs | 43 +++- tests/trigger_visibility.rs | 7 +- tests/usage.rs | 2 +- tests/weather_plugin.rs | 5 +- tests/wikipedia_plugin.rs | 5 +- tests/window_manager.rs | 8 +- tests/windows_plugin.rs | 8 +- 98 files changed, 1285 insertions(+), 725 deletions(-) diff --git a/benches/search.rs b/benches/search.rs index 3dee353c..401fc1c9 100644 --- a/benches/search.rs +++ b/benches/search.rs @@ -1,6 +1,8 @@ use criterion::{criterion_group, criterion_main, Criterion}; use eframe::egui; -use multi_launcher::{actions::Action, gui::LauncherApp, plugin::PluginManager, settings::Settings}; +use multi_launcher::{ + actions::Action, gui::LauncherApp, plugin::PluginManager, settings::Settings, +}; use std::sync::Arc; fn bench_search(c: &mut Criterion) { diff --git a/src/actions/calc.rs b/src/actions/calc.rs index a8559775..b62eb4f5 100644 --- a/src/actions/calc.rs +++ b/src/actions/calc.rs @@ -1,12 +1,11 @@ use arboard::Clipboard; pub fn copy_history_result(index: usize) -> anyhow::Result<()> { - if let Some(entry) = crate::plugins::calc_history::load_history( - crate::plugins::calc_history::CALC_HISTORY_FILE, - ) - .unwrap_or_default() - .get(index) - .cloned() + if let Some(entry) = + crate::plugins::calc_history::load_history(crate::plugins::calc_history::CALC_HISTORY_FILE) + .unwrap_or_default() + .get(index) + .cloned() { let mut cb = Clipboard::new()?; cb.set_text(entry.result)?; diff --git a/src/actions/clipboard.rs b/src/actions/clipboard.rs index f4e5f3e0..5ef17bf2 100644 --- a/src/actions/clipboard.rs +++ b/src/actions/clipboard.rs @@ -1,19 +1,16 @@ use arboard::Clipboard; pub fn clear_history() -> anyhow::Result<()> { - crate::plugins::clipboard::clear_history_file( - crate::plugins::clipboard::CLIPBOARD_FILE, - )?; + crate::plugins::clipboard::clear_history_file(crate::plugins::clipboard::CLIPBOARD_FILE)?; Ok(()) } pub fn copy_entry(i: usize) -> anyhow::Result<()> { - if let Some(entry) = crate::plugins::clipboard::load_history( - crate::plugins::clipboard::CLIPBOARD_FILE, - ) - .unwrap_or_default() - .get(i) - .cloned() + if let Some(entry) = + crate::plugins::clipboard::load_history(crate::plugins::clipboard::CLIPBOARD_FILE) + .unwrap_or_default() + .get(i) + .cloned() { let mut cb = Clipboard::new()?; cb.set_text(entry)?; diff --git a/src/actions/fav.rs b/src/actions/fav.rs index 6cffc269..4a28a83f 100644 --- a/src/actions/fav.rs +++ b/src/actions/fav.rs @@ -1,17 +1,9 @@ pub fn add(label: &str, action: &str, args: Option<&str>) -> anyhow::Result<()> { - crate::plugins::fav::set_fav( - crate::plugins::fav::FAV_FILE, - label, - action, - args, - )?; + crate::plugins::fav::set_fav(crate::plugins::fav::FAV_FILE, label, action, args)?; Ok(()) } pub fn remove(label: &str) -> anyhow::Result<()> { - crate::plugins::fav::remove_fav( - crate::plugins::fav::FAV_FILE, - label, - )?; + crate::plugins::fav::remove_fav(crate::plugins::fav::FAV_FILE, label)?; Ok(()) } diff --git a/src/actions/folders.rs b/src/actions/folders.rs index b120a744..b4c0c38f 100644 --- a/src/actions/folders.rs +++ b/src/actions/folders.rs @@ -1,15 +1,9 @@ pub fn add(path: &str) -> anyhow::Result<()> { - crate::plugins::folders::append_folder( - crate::plugins::folders::FOLDERS_FILE, - path, - )?; + crate::plugins::folders::append_folder(crate::plugins::folders::FOLDERS_FILE, path)?; Ok(()) } pub fn remove(path: &str) -> anyhow::Result<()> { - crate::plugins::folders::remove_folder( - crate::plugins::folders::FOLDERS_FILE, - path, - )?; + crate::plugins::folders::remove_folder(crate::plugins::folders::FOLDERS_FILE, path)?; Ok(()) } diff --git a/src/actions/history.rs b/src/actions/history.rs index dd067807..ad7d86a3 100644 --- a/src/actions/history.rs +++ b/src/actions/history.rs @@ -1,4 +1,3 @@ - pub fn clear() -> anyhow::Result<()> { crate::history::clear_history()?; Ok(()) diff --git a/src/actions/mod.rs b/src/actions/mod.rs index 6c3cedc5..bd900d56 100644 --- a/src/actions/mod.rs +++ b/src/actions/mod.rs @@ -21,19 +21,19 @@ pub fn save_actions(path: &str, actions: &[Action]) -> anyhow::Result<()> { Ok(()) } -pub mod clipboard; -pub mod timer; -pub mod stopwatch; -pub mod shell; pub mod bookmarks; +pub mod calc; +pub mod clipboard; +pub mod exec; +pub mod fav; pub mod folders; pub mod history; -pub mod todo; -pub mod snippets; -pub mod tempfiles; pub mod media; -pub mod system; -pub mod exec; -pub mod fav; pub mod screenshot; -pub mod calc; +pub mod shell; +pub mod snippets; +pub mod stopwatch; +pub mod system; +pub mod tempfiles; +pub mod timer; +pub mod todo; diff --git a/src/actions/screenshot.rs b/src/actions/screenshot.rs index bacae829..5d55088d 100644 --- a/src/actions/screenshot.rs +++ b/src/actions/screenshot.rs @@ -44,9 +44,10 @@ pub fn capture_raw(mode: Mode) -> anyhow::Result { // Wait for the snipping tool to provide a new clipboard image let mut cb = arboard::Clipboard::new()?; - let old = cb.get_image().ok().map(|img| { - (img.width, img.height, img.bytes.into_owned()) - }); + let old = cb + .get_image() + .ok() + .map(|img| (img.width, img.height, img.bytes.into_owned())); let _ = Command::new("explorer").arg("ms-screenclip:").status(); @@ -101,4 +102,3 @@ pub fn capture(mode: Mode, clipboard: bool) -> anyhow::Result { } Ok(path) } - diff --git a/src/actions/snippets.rs b/src/actions/snippets.rs index 66fa71e6..f6ec33a6 100644 --- a/src/actions/snippets.rs +++ b/src/actions/snippets.rs @@ -1,16 +1,9 @@ pub fn remove(alias: &str) -> anyhow::Result<()> { - crate::plugins::snippets::remove_snippet( - crate::plugins::snippets::SNIPPETS_FILE, - alias, - )?; + crate::plugins::snippets::remove_snippet(crate::plugins::snippets::SNIPPETS_FILE, alias)?; Ok(()) } pub fn add(alias: &str, text: &str) -> anyhow::Result<()> { - crate::plugins::snippets::append_snippet( - crate::plugins::snippets::SNIPPETS_FILE, - alias, - text, - )?; + crate::plugins::snippets::append_snippet(crate::plugins::snippets::SNIPPETS_FILE, alias, text)?; Ok(()) } diff --git a/src/actions/system.rs b/src/actions/system.rs index 90b1d7c5..5a785218 100644 --- a/src/actions/system.rs +++ b/src/actions/system.rs @@ -70,59 +70,59 @@ pub fn recycle_clean() { pub fn browser_tab_switch(runtime_id: &[i32]) { use windows::core::VARIANT; - use windows::Win32::System::Com::{ - CoCreateInstance, CoInitializeEx, CoUninitialize, CLSCTX_INPROC_SERVER, - COINIT_APARTMENTTHREADED, - }; - use windows::Win32::System::Ole::{ - SafeArrayCreateVector, SafeArrayDestroy, SafeArrayPutElement, - }; - use windows::Win32::System::Variant::VT_I4; - use windows::Win32::UI::Accessibility::*; - use windows::Win32::Foundation::{HWND, POINT}; - use windows::Win32::UI::WindowsAndMessaging::{GetCursorPos, SetCursorPos}; - use windows::Win32::UI::Input::KeyboardAndMouse::{ - SendInput, INPUT, INPUT_0, INPUT_MOUSE, MOUSEINPUT, MOUSEEVENTF_LEFTDOWN, - MOUSEEVENTF_LEFTUP, - }; + use windows::Win32::Foundation::{HWND, POINT}; + use windows::Win32::System::Com::{ + CoCreateInstance, CoInitializeEx, CoUninitialize, CLSCTX_INPROC_SERVER, + COINIT_APARTMENTTHREADED, + }; + use windows::Win32::System::Ole::{ + SafeArrayCreateVector, SafeArrayDestroy, SafeArrayPutElement, + }; + use windows::Win32::System::Variant::VT_I4; + use windows::Win32::UI::Accessibility::*; + use windows::Win32::UI::Input::KeyboardAndMouse::{ + SendInput, INPUT, INPUT_0, INPUT_MOUSE, MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, + MOUSEINPUT, + }; + use windows::Win32::UI::WindowsAndMessaging::{GetCursorPos, SetCursorPos}; - unsafe { - let _ = CoInitializeEx(None, COINIT_APARTMENTTHREADED); - if let Ok(automation) = - CoCreateInstance::<_, IUIAutomation>(&CUIAutomation, None, CLSCTX_INPROC_SERVER) - { - // Build SAFEARRAY from runtime ID pieces - let psa = SafeArrayCreateVector(VT_I4, 0, runtime_id.len() as u32); - if !psa.is_null() { - for (i, v) in runtime_id.iter().enumerate() { - let mut idx = i as i32; - let val = *v; - let _ = SafeArrayPutElement( - psa, - &mut idx, - &val as *const _ as *const core::ffi::c_void, - ); - } + unsafe { + let _ = CoInitializeEx(None, COINIT_APARTMENTTHREADED); + if let Ok(automation) = + CoCreateInstance::<_, IUIAutomation>(&CUIAutomation, None, CLSCTX_INPROC_SERVER) + { + // Build SAFEARRAY from runtime ID pieces + let psa = SafeArrayCreateVector(VT_I4, 0, runtime_id.len() as u32); + if !psa.is_null() { + for (i, v) in runtime_id.iter().enumerate() { + let mut idx = i as i32; + let val = *v; + let _ = SafeArrayPutElement( + psa, + &mut idx, + &val as *const _ as *const core::ffi::c_void, + ); + } - // Enumerate tab elements and find matching runtime ID - if let Ok(cond) = automation.CreatePropertyCondition( - UIA_ControlTypePropertyId, - &VARIANT::from(UIA_TabItemControlTypeId.0), - ) { - if let Ok(root) = automation.GetRootElement() { - if let Ok(tabs) = root.FindAll(TreeScope_Subtree, &cond) { - if let Ok(count) = tabs.Length() { - 'outer: for i in 0..count { - if let Ok(elem) = tabs.GetElement(i) { - if let Ok(elem_id) = elem.GetRuntimeId() { - if !elem_id.is_null() { - if let Ok(same) = automation.CompareRuntimeIds( - elem_id as *const _, - psa as *const _, - ) { - if same.as_bool() { - let mut activated = false; - if let Ok(sel) = elem + // Enumerate tab elements and find matching runtime ID + if let Ok(cond) = automation.CreatePropertyCondition( + UIA_ControlTypePropertyId, + &VARIANT::from(UIA_TabItemControlTypeId.0), + ) { + if let Ok(root) = automation.GetRootElement() { + if let Ok(tabs) = root.FindAll(TreeScope_Subtree, &cond) { + if let Ok(count) = tabs.Length() { + 'outer: for i in 0..count { + if let Ok(elem) = tabs.GetElement(i) { + if let Ok(elem_id) = elem.GetRuntimeId() { + if !elem_id.is_null() { + if let Ok(same) = automation.CompareRuntimeIds( + elem_id as *const _, + psa as *const _, + ) { + if same.as_bool() { + let mut activated = false; + if let Ok(sel) = elem .GetCurrentPatternAs::< IUIAutomationSelectionItemPattern, >(UIA_SelectionItemPatternId) @@ -142,48 +142,52 @@ pub fn browser_tab_switch(runtime_id: &[i32]) { activated = acc.DoDefaultAction().is_ok(); } - if activated { - if let Ok(focused) = - automation.GetFocusedElement() + if activated { + if let Ok(focused) = + automation.GetFocusedElement() + { + if let Ok(fid) = + focused.GetRuntimeId() { - if let Ok(fid) = - focused.GetRuntimeId() - { - activated = automation - .CompareRuntimeIds( - fid as *const _, - psa as *const _, - ) - .map(|b| b.as_bool()) - .unwrap_or(false); - let _ = SafeArrayDestroy( + activated = automation + .CompareRuntimeIds( fid as *const _, - ); - } else { - activated = false; - } + psa as *const _, + ) + .map(|b| b.as_bool()) + .unwrap_or(false); + let _ = SafeArrayDestroy( + fid as *const _, + ); } else { activated = false; } + } else { + activated = false; } + } - if !activated { - if let Ok(rect) = - elem.CurrentBoundingRectangle() - { - let x = (rect.left + rect.right) / 2; - let y = (rect.top + rect.bottom) / 2; + if !activated { + if let Ok(rect) = + elem.CurrentBoundingRectangle() + { + let x = + (rect.left + rect.right) / 2; + let y = + (rect.top + rect.bottom) / 2; - let mut hwnd = elem - .CurrentNativeWindowHandle() - .unwrap_or(HWND(std::ptr::null_mut())); - if hwnd.0.is_null() { - if let Ok(walker) = - automation.RawViewWalker() - { - let mut cur = elem.clone(); - loop { - if let Ok(h) = cur + let mut hwnd = elem + .CurrentNativeWindowHandle() + .unwrap_or(HWND( + std::ptr::null_mut(), + )); + if hwnd.0.is_null() { + if let Ok(walker) = + automation.RawViewWalker() + { + let mut cur = elem.clone(); + loop { + if let Ok(h) = cur .CurrentNativeWindowHandle() { if !h.0.is_null() { @@ -191,24 +195,26 @@ pub fn browser_tab_switch(runtime_id: &[i32]) { break; } } - if let Ok(p) = walker - .GetParentElement(&cur) - { - cur = p; - } else { - break; - } + if let Ok(p) = walker + .GetParentElement( + &cur, + ) + { + cur = p; + } else { + break; } } } - if !hwnd.0.is_null() { - super::super::window_manager::force_restore_and_foreground(hwnd); - } + } + if !hwnd.0.is_null() { + super::super::window_manager::force_restore_and_foreground(hwnd); + } - let mut old = POINT::default(); - let _ = GetCursorPos(&mut old); - let _ = SetCursorPos(x, y); - let inputs = [ + let mut old = POINT::default(); + let _ = GetCursorPos(&mut old); + let _ = SetCursorPos(x, y); + let inputs = [ INPUT { r#type: INPUT_MOUSE, Anonymous: INPUT_0 { @@ -238,27 +244,25 @@ pub fn browser_tab_switch(runtime_id: &[i32]) { }, }, ]; - let _ = SendInput( - &inputs, - core::mem::size_of::() - as i32, - ); - let _ = SetCursorPos(old.x, old.y); - tracing::debug!( + let _ = SendInput( + &inputs, + core::mem::size_of::() + as i32, + ); + let _ = SetCursorPos(old.x, old.y); + tracing::debug!( "simulated click for browser tab" ); - } } - - let _ = elem.SetFocus(); - let _ = SafeArrayDestroy( - elem_id as *const _, - ); - break 'outer; } + + let _ = elem.SetFocus(); + let _ = + SafeArrayDestroy(elem_id as *const _); + break 'outer; } - let _ = SafeArrayDestroy(elem_id as *const _); } + let _ = SafeArrayDestroy(elem_id as *const _); } } } @@ -266,9 +270,10 @@ pub fn browser_tab_switch(runtime_id: &[i32]) { } } } - let _ = SafeArrayDestroy(psa as *const _); } + let _ = SafeArrayDestroy(psa as *const _); } - CoUninitialize(); } + CoUninitialize(); + } } diff --git a/src/actions/timer.rs b/src/actions/timer.rs index 9ecdcdc3..42fc0db8 100644 --- a/src/actions/timer.rs +++ b/src/actions/timer.rs @@ -25,7 +25,13 @@ pub fn set_alarm(time: &str, name: &str) { if name.is_empty() { crate::plugins::timer::start_alarm(h, m, date, "None".to_string()); } else { - crate::plugins::timer::start_alarm_named(h, m, date, Some(name.to_string()), "None".to_string()); + crate::plugins::timer::start_alarm_named( + h, + m, + date, + Some(name.to_string()), + "None".to_string(), + ); } } } diff --git a/src/actions/todo.rs b/src/actions/todo.rs index 9d8cc0f1..baf4c6eb 100644 --- a/src/actions/todo.rs +++ b/src/actions/todo.rs @@ -1,28 +1,15 @@ pub fn add(text: &str, priority: u8, tags: &[String]) -> anyhow::Result<()> { - crate::plugins::todo::append_todo( - crate::plugins::todo::TODO_FILE, - text, - priority, - tags, - )?; + crate::plugins::todo::append_todo(crate::plugins::todo::TODO_FILE, text, priority, tags)?; Ok(()) } pub fn set_priority(idx: usize, priority: u8) -> anyhow::Result<()> { - crate::plugins::todo::set_priority( - crate::plugins::todo::TODO_FILE, - idx, - priority, - )?; + crate::plugins::todo::set_priority(crate::plugins::todo::TODO_FILE, idx, priority)?; Ok(()) } pub fn set_tags(idx: usize, tags: &[String]) -> anyhow::Result<()> { - crate::plugins::todo::set_tags( - crate::plugins::todo::TODO_FILE, - idx, - tags, - )?; + crate::plugins::todo::set_tags(crate::plugins::todo::TODO_FILE, idx, tags)?; Ok(()) } diff --git a/src/actions_editor.rs b/src/actions_editor.rs index 1db8c7d2..5c98db30 100644 --- a/src/actions_editor.rs +++ b/src/actions_editor.rs @@ -50,56 +50,56 @@ impl ActionsEditor { egui::Window::new("App Editor") .open(&mut open) .show(ctx, |ui| { - ui.horizontal(|ui| { - ui.label("Search"); - ui.text_edit_singleline(&mut self.search); - if ui.button("New App").clicked() { - self.dialog.open_add(); - } - }); + ui.horizontal(|ui| { + ui.label("Search"); + ui.text_edit_singleline(&mut self.search); + if ui.button("New App").clicked() { + self.dialog.open_add(); + } + }); - self.dialog.ui(ctx, app); + self.dialog.ui(ctx, app); - ui.separator(); - let mut remove: Option = None; - // Allow horizontal scrolling to avoid clipping long command strings - egui::ScrollArea::both().max_height(200.0).show(ui, |ui| { - for (idx, act) in app.actions.iter().enumerate() { - if !self.search.trim().is_empty() { - let q = self.search.to_lowercase(); - let label = act.label.to_lowercase(); - let desc = act.desc.to_lowercase(); - let action = act.action.to_lowercase(); - if !label.contains(&q) && !desc.contains(&q) && !action.contains(&q) { - continue; + ui.separator(); + let mut remove: Option = None; + // Allow horizontal scrolling to avoid clipping long command strings + egui::ScrollArea::both().max_height(200.0).show(ui, |ui| { + for (idx, act) in app.actions.iter().enumerate() { + if !self.search.trim().is_empty() { + let q = self.search.to_lowercase(); + let label = act.label.to_lowercase(); + let desc = act.desc.to_lowercase(); + let action = act.action.to_lowercase(); + if !label.contains(&q) && !desc.contains(&q) && !action.contains(&q) { + continue; + } } + ui.horizontal(|ui| { + ui.label(format!("{} : {} -> {}", act.label, act.desc, act.action)); + if ui.button("Edit").clicked() { + self.dialog.open_edit(idx, act); + } + if ui.button("Remove").clicked() { + remove = Some(idx); + } + }); } - ui.horizontal(|ui| { - ui.label(format!("{} : {} -> {}", act.label, act.desc, act.action)); - if ui.button("Edit").clicked() { - self.dialog.open_edit(idx, act); - } - if ui.button("Remove").clicked() { - remove = Some(idx); - } - }); - } - }); + }); - if let Some(i) = remove { - let mut new_actions = (*app.actions).clone(); - new_actions.remove(i); - if i < app.custom_len { - app.custom_len -= 1; - } - app.actions = Arc::new(new_actions); - app.search(); - if let Err(e) = save_actions(&app.actions_path, &app.actions[..app.custom_len]) { - app.set_error(format!("Failed to save: {e}")); + if let Some(i) = remove { + let mut new_actions = (*app.actions).clone(); + new_actions.remove(i); + if i < app.custom_len { + app.custom_len -= 1; + } + app.actions = Arc::new(new_actions); + app.search(); + if let Err(e) = save_actions(&app.actions_path, &app.actions[..app.custom_len]) + { + app.set_error(format!("Failed to save: {e}")); + } } - } - - }); + }); app.show_editor = open; } diff --git a/src/common/json_watch.rs b/src/common/json_watch.rs index a84a3143..2adc129b 100644 --- a/src/common/json_watch.rs +++ b/src/common/json_watch.rs @@ -2,7 +2,10 @@ use notify::{Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; use once_cell::sync::Lazy; use std::collections::HashMap; use std::path::{Path, PathBuf}; -use std::sync::{atomic::{AtomicUsize, Ordering}, Arc, Mutex}; +use std::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, Mutex, +}; /// Handle to a registered JSON file watcher. /// Dropping the handle removes the associated callback. @@ -17,7 +20,8 @@ struct WatchEntry { callbacks: Arc>>>, } -static WATCHERS: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); +static WATCHERS: Lazy>> = + Lazy::new(|| Mutex::new(HashMap::new())); static NEXT_ID: AtomicUsize = AtomicUsize::new(1); impl Drop for JsonWatcher { @@ -49,43 +53,46 @@ where let mut map = WATCHERS.lock().unwrap(); if let Some(entry) = map.get_mut(&path_buf) { - entry.callbacks.lock().unwrap().insert(id, Box::new(callback)); + entry + .callbacks + .lock() + .unwrap() + .insert(id, Box::new(callback)); return Ok(JsonWatcher { path: path_buf, id }); } - let callbacks: Arc>>> = Arc::new(Mutex::new(HashMap::new())); + let callbacks: Arc>>> = + Arc::new(Mutex::new(HashMap::new())); callbacks.lock().unwrap().insert(id, Box::new(callback)); let callbacks_clone = callbacks.clone(); let mut watcher = RecommendedWatcher::new( - move |res: notify::Result| { - match res { - Ok(event) => { - if matches!(event.kind, EventKind::Modify(_) | EventKind::Create(_) | EventKind::Remove(_)) { - let mut cbs = callbacks_clone.lock().unwrap(); - for cb in cbs.values_mut() { - cb(); - } + move |res: notify::Result| match res { + Ok(event) => { + if matches!( + event.kind, + EventKind::Modify(_) | EventKind::Create(_) | EventKind::Remove(_) + ) { + let mut cbs = callbacks_clone.lock().unwrap(); + for cb in cbs.values_mut() { + cb(); } } - Err(e) => tracing::error!("watch error: {:?}", e), } + Err(e) => tracing::error!("watch error: {:?}", e), }, Config::default(), )?; - if watcher.watch(&path_buf, RecursiveMode::NonRecursive).is_err() { + if watcher + .watch(&path_buf, RecursiveMode::NonRecursive) + .is_err() + { let parent = path_buf.parent().unwrap_or_else(|| Path::new(".")); watcher.watch(parent, RecursiveMode::NonRecursive)?; } - map.insert( - path_buf.clone(), - WatchEntry { - watcher, - callbacks, - }, - ); + map.insert(path_buf.clone(), WatchEntry { watcher, callbacks }); Ok(JsonWatcher { path: path_buf, id }) } diff --git a/src/common/lru.rs b/src/common/lru.rs index 4a3370c7..a4a136bf 100644 --- a/src/common/lru.rs +++ b/src/common/lru.rs @@ -2,4 +2,3 @@ //! depend on it through this crate's common module. pub use hashlink::LruCache; - diff --git a/src/common/mod.rs b/src/common/mod.rs index 477ba458..cc1bed60 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -7,5 +7,5 @@ pub fn strip_prefix_ci<'a>(s: &'a str, prefix: &str) -> Option<&'a str> { } pub mod json_watch; -pub mod slug; pub mod lru; +pub mod slug; diff --git a/src/common/slug.rs b/src/common/slug.rs index 9107273a..5ab6e577 100644 --- a/src/common/slug.rs +++ b/src/common/slug.rs @@ -3,8 +3,7 @@ use std::collections::HashMap; use std::sync::Mutex; /// Global lookup of slug base -> next suffix index. -static SLUGS: Lazy>> = - Lazy::new(|| Mutex::new(HashMap::new())); +static SLUGS: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); /// Reset the slug lookup. Should be called before scanning existing notes. pub fn reset_slug_lookup() { diff --git a/src/global_hotkey.rs b/src/global_hotkey.rs index 379418d5..bc99b145 100644 --- a/src/global_hotkey.rs +++ b/src/global_hotkey.rs @@ -3,7 +3,7 @@ use log::{error, info, warn}; use serde::{Deserialize, Serialize}; use std::fmt; use windows::Win32::UI::Input::KeyboardAndMouse::{ - RegisterHotKey, HOT_KEY_MODIFIERS, MOD_CONTROL, MOD_ALT, MOD_SHIFT, MOD_WIN, + RegisterHotKey, HOT_KEY_MODIFIERS, MOD_ALT, MOD_CONTROL, MOD_SHIFT, MOD_WIN, }; #[derive(Clone, Serialize, Deserialize)] @@ -66,7 +66,4 @@ impl Hotkey { false } - - - } diff --git a/src/gui/add_action_dialog.rs b/src/gui/add_action_dialog.rs index a2b5bc90..169d6163 100644 --- a/src/gui/add_action_dialog.rs +++ b/src/gui/add_action_dialog.rs @@ -1,8 +1,8 @@ use crate::actions::{save_actions, Action}; use crate::gui::LauncherApp; use eframe::egui; -use std::sync::Arc; use rfd::FileDialog; +use std::sync::Arc; /// Dialog state used when adding a new user defined command. /// @@ -34,7 +34,9 @@ enum DialogMode { } impl Default for DialogMode { - fn default() -> Self { DialogMode::Add } + fn default() -> Self { + DialogMode::Add + } } impl Default for AddActionDialog { @@ -142,7 +144,8 @@ impl AddActionDialog { label: self.label.clone(), desc: self.desc.clone(), action: self.path.clone(), - args: if self.show_args && !self.args.trim().is_empty() { + args: if self.show_args && !self.args.trim().is_empty() + { Some(self.args.clone()) } else { None @@ -158,11 +161,12 @@ impl AddActionDialog { act.label = self.label.clone(); act.desc = self.desc.clone(); act.action = self.path.clone(); - act.args = if self.show_args && !self.args.trim().is_empty() { - Some(self.args.clone()) - } else { - None - }; + act.args = + if self.show_args && !self.args.trim().is_empty() { + Some(self.args.clone()) + } else { + None + }; app.actions = Arc::new(new_actions); app.update_action_cache(); } @@ -175,7 +179,9 @@ impl AddActionDialog { self.show_args = false; should_close = true; app.search(); - if let Err(e) = save_actions(&app.actions_path, &app.actions[..app.custom_len]) { + if let Err(e) = + save_actions(&app.actions_path, &app.actions[..app.custom_len]) + { app.set_error(format!("Failed to save: {e}")); } } @@ -194,4 +200,3 @@ impl AddActionDialog { } } } - diff --git a/src/gui/alias_dialog.rs b/src/gui/alias_dialog.rs index e522d189..c32e0666 100644 --- a/src/gui/alias_dialog.rs +++ b/src/gui/alias_dialog.rs @@ -1,5 +1,5 @@ use crate::gui::LauncherApp; -use crate::plugins::folders::{set_alias, FOLDERS_FILE, load_folders}; +use crate::plugins::folders::{load_folders, set_alias, FOLDERS_FILE}; use eframe::egui; pub struct AliasDialog { @@ -10,7 +10,11 @@ pub struct AliasDialog { impl Default for AliasDialog { fn default() -> Self { - Self { open: false, path: String::new(), alias: String::new() } + Self { + open: false, + path: String::new(), + alias: String::new(), + } } } @@ -60,4 +64,3 @@ impl AliasDialog { } } } - diff --git a/src/gui/bookmark_alias_dialog.rs b/src/gui/bookmark_alias_dialog.rs index 62941b22..bf27ca0a 100644 --- a/src/gui/bookmark_alias_dialog.rs +++ b/src/gui/bookmark_alias_dialog.rs @@ -1,5 +1,5 @@ use crate::gui::LauncherApp; -use crate::plugins::bookmarks::{set_alias, BOOKMARKS_FILE, load_bookmarks}; +use crate::plugins::bookmarks::{load_bookmarks, set_alias, BOOKMARKS_FILE}; use eframe::egui; pub struct BookmarkAliasDialog { @@ -10,7 +10,11 @@ pub struct BookmarkAliasDialog { impl Default for BookmarkAliasDialog { fn default() -> Self { - Self { open: false, url: String::new(), alias: String::new() } + Self { + open: false, + url: String::new(), + alias: String::new(), + } } } diff --git a/src/gui/brightness_dialog.rs b/src/gui/brightness_dialog.rs index 364d3287..07336bc2 100644 --- a/src/gui/brightness_dialog.rs +++ b/src/gui/brightness_dialog.rs @@ -1,6 +1,6 @@ +use crate::actions::Action; use crate::gui::LauncherApp; use crate::launcher::launch_action; -use crate::actions::Action; use eframe::egui; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -20,7 +20,9 @@ impl BrightnessDialog { } pub fn ui(&mut self, ctx: &egui::Context, app: &mut LauncherApp) { - if !self.open { return; } + if !self.open { + return; + } if !self.value_loaded { self.value = get_main_display_brightness().unwrap_or(50); self.value_loaded = true; @@ -47,17 +49,19 @@ impl BrightnessDialog { } }); }); - if close { self.open = false; } + if close { + self.open = false; + } } } fn get_main_display_brightness() -> Option { - use windows::Win32::Foundation::{BOOL, LPARAM, RECT}; - use windows::Win32::Graphics::Gdi::{EnumDisplayMonitors, HDC, HMONITOR}; use windows::Win32::Devices::Display::{ - DestroyPhysicalMonitors, GetNumberOfPhysicalMonitorsFromHMONITOR, - GetPhysicalMonitorsFromHMONITOR, GetMonitorBrightness, PHYSICAL_MONITOR, + DestroyPhysicalMonitors, GetMonitorBrightness, GetNumberOfPhysicalMonitorsFromHMONITOR, + GetPhysicalMonitorsFromHMONITOR, PHYSICAL_MONITOR, }; + use windows::Win32::Foundation::{BOOL, LPARAM, RECT}; + use windows::Win32::Graphics::Gdi::{EnumDisplayMonitors, HDC, HMONITOR}; unsafe extern "system" fn enum_monitors( hmonitor: HMONITOR, @@ -100,4 +104,3 @@ fn get_main_display_brightness() -> Option { BRIGHTNESS_QUERIES.fetch_add(1, Ordering::Relaxed); Some(percent as u8) } - diff --git a/src/gui/convert_panel.rs b/src/gui/convert_panel.rs index 2d169fe8..16775e70 100644 --- a/src/gui/convert_panel.rs +++ b/src/gui/convert_panel.rs @@ -237,7 +237,9 @@ impl ConvertPanel { match self.category.as_str() { "Distance" => { if let Ok(v) = self.input.trim().parse::() { - if let (Some(ff), Some(tf)) = (distance_factor(&self.from), distance_factor(&self.to)) { + if let (Some(ff), Some(tf)) = + (distance_factor(&self.from), distance_factor(&self.to)) + { let res = v * ff / tf; self.result = res.to_string(); } @@ -253,7 +255,9 @@ impl ConvertPanel { } "Volume" => { if let Ok(v) = self.input.trim().parse::() { - if let (Some(ff), Some(tf)) = (volume_factor(&self.from), volume_factor(&self.to)) { + if let (Some(ff), Some(tf)) = + (volume_factor(&self.from), volume_factor(&self.to)) + { let res = v * ff / tf; self.result = res.to_string(); } diff --git a/src/gui/fav_dialog.rs b/src/gui/fav_dialog.rs index 07f2259b..d589bcf8 100644 --- a/src/gui/fav_dialog.rs +++ b/src/gui/fav_dialog.rs @@ -1,7 +1,5 @@ use crate::gui::LauncherApp; -use crate::plugins::fav::{ - load_favs, save_favs, resolve_with_plugin, FavEntry, FAV_FILE, -}; +use crate::plugins::fav::{load_favs, resolve_with_plugin, save_favs, FavEntry, FAV_FILE}; use eframe::egui; #[derive(Default)] diff --git a/src/gui/note_panel.rs b/src/gui/note_panel.rs index fc6fe6df..8b7fe65c 100644 --- a/src/gui/note_panel.rs +++ b/src/gui/note_panel.rs @@ -490,32 +490,15 @@ impl NotePanel { } if ctx.input(|i| i.key_pressed(Key::K)) { ctx.input_mut(|i| i.consume_key(egui::Modifiers::NONE, Key::K)); - idx = self - .note - .content - .chars() - .take(idx) - .rposition(|c| c == '\n') - .unwrap_or(0); + idx = prev_newline_char_idx(&self.note.content, idx).unwrap_or(0); moved = true; } if ctx.input(|i| i.key_pressed(Key::Y)) { ctx.input_mut(|i| i.consume_key(egui::Modifiers::NONE, Key::Y)); - let start_char = self - .note - .content - .chars() - .take(idx) - .rposition(|c| c == '\n') + let start_char = prev_newline_char_idx(&self.note.content, idx) .map(|p| p + 1) .unwrap_or(0); - let end_char = self - .note - .content - .chars() - .skip(idx) - .position(|c| c == '\n') - .map(|p| idx + p) + let end_char = next_newline_char_idx(&self.note.content, idx) .unwrap_or_else(|| self.note.content.chars().count()); let start = char_to_byte_index(&self.note.content, start_char); let end = char_to_byte_index(&self.note.content, end_char); @@ -1135,6 +1118,23 @@ fn byte_to_char_index(text: &str, byte_idx: usize) -> usize { text[..clamped].chars().count() } +fn prev_newline_char_idx(text: &str, before: usize) -> Option { + text.chars() + .take(before) + .enumerate() + .rev() + .find(|(_, c)| *c == '\n') + .map(|(i, _)| i) +} + +fn next_newline_char_idx(text: &str, from: usize) -> Option { + text.chars() + .skip(from) + .enumerate() + .find(|(_, c)| *c == '\n') + .map(|(offset, _)| from + offset) +} + fn char_range_to_byte_range(text: &str, range: egui::text::CCursorRange) -> Option<(usize, usize)> { let [min, max] = range.sorted(); if min.index == max.index { diff --git a/src/gui/screenshot_editor.rs b/src/gui/screenshot_editor.rs index 4d2184be..e09b9c16 100644 --- a/src/gui/screenshot_editor.rs +++ b/src/gui/screenshot_editor.rs @@ -173,7 +173,8 @@ impl ScreenshotEditor { } if let Some(start) = self.ann_start { if let Some(pos) = response.interact_pointer_pos() { - self.annotations.push(Rect::from_two_pos(start, to_img(pos))); + self.annotations + .push(Rect::from_two_pos(start, to_img(pos))); self.ann_start = None; } } diff --git a/src/gui/unused_assets_dialog.rs b/src/gui/unused_assets_dialog.rs index 45b55edc..a10f49d3 100644 --- a/src/gui/unused_assets_dialog.rs +++ b/src/gui/unused_assets_dialog.rs @@ -27,18 +27,20 @@ impl UnusedAssetsDialog { .resizable(true) .default_size((300.0, 200.0)) .show(ctx, |ui| { - egui::ScrollArea::vertical().max_height(200.0).show(ui, |ui| { - if self.assets.is_empty() { - ui.label("No unused assets found"); - } else { - for (idx, asset) in self.assets.iter().enumerate() { - ui.horizontal(|ui| { - ui.checkbox(&mut self.selected[idx], ""); - ui.label(asset); - }); + egui::ScrollArea::vertical() + .max_height(200.0) + .show(ui, |ui| { + if self.assets.is_empty() { + ui.label("No unused assets found"); + } else { + for (idx, asset) in self.assets.iter().enumerate() { + ui.horizontal(|ui| { + ui.checkbox(&mut self.selected[idx], ""); + ui.label(asset); + }); + } } - } - }); + }); ui.separator(); if ui.button("Delete Selected").clicked() { for i in (0..self.assets.len()).rev() { diff --git a/src/gui/volume_dialog.rs b/src/gui/volume_dialog.rs index 9e449c5f..75b79d60 100644 --- a/src/gui/volume_dialog.rs +++ b/src/gui/volume_dialog.rs @@ -12,7 +12,11 @@ pub struct VolumeDialog { impl Default for VolumeDialog { fn default() -> Self { - Self { open: false, value: 50, processes: Vec::new() } + Self { + open: false, + value: 50, + processes: Vec::new(), + } } } @@ -45,7 +49,9 @@ impl VolumeDialog { } pub fn ui(&mut self, ctx: &egui::Context, app: &mut LauncherApp) { - if !self.open { return; } + if !self.open { + return; + } let mut close = false; egui::Window::new("Volume") .resizable(false) @@ -71,7 +77,8 @@ impl VolumeDialog { for proc in &mut self.processes { ui.horizontal(|ui| { ui.label(format!("{} (PID {})", proc.name, proc.pid)); - let resp = ui.add(egui::Slider::new(&mut proc.value, 0..=100).text("Level")); + let resp = + ui.add(egui::Slider::new(&mut proc.value, 0..=100).text("Level")); if resp.changed() { if let Some(action) = proc.slider_changed() { let _ = launch_action(&Action { @@ -105,7 +112,9 @@ impl VolumeDialog { }); } }); - if close { self.open = false; } + if close { + self.open = false; + } } } @@ -209,10 +218,7 @@ mod tests { muted: true, }; let action = proc.slider_changed(); - assert_eq!( - action, - Some("volume:pid_toggle_mute:1".to_string()) - ); + assert_eq!(action, Some("volume:pid_toggle_mute:1".to_string())); assert!(!proc.muted); } } diff --git a/src/plugin.rs b/src/plugin.rs index 570de603..103268de 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -1,57 +1,57 @@ use crate::actions::Action; use crate::plugins::asciiart::AsciiArtPlugin; -use crate::plugins::emoji::EmojiPlugin; -use crate::plugins::screenshot::ScreenshotPlugin; -use crate::plugins::text_case::TextCasePlugin; +use crate::plugins::base_convert::BaseConvertPlugin; use crate::plugins::bookmarks::BookmarksPlugin; use crate::plugins::brightness::BrightnessPlugin; +use crate::plugins::browser_tabs::BrowserTabsPlugin; use crate::plugins::clipboard::ClipboardPlugin; +use crate::plugins::color_picker::ColorPickerPlugin; +use crate::plugins::convert_panel::ConvertPanelPlugin; use crate::plugins::dropcalc::DropCalcPlugin; +use crate::plugins::emoji::EmojiPlugin; +use crate::plugins::fav::FavPlugin; use crate::plugins::folders::FoldersPlugin; use crate::plugins::help::HelpPlugin; use crate::plugins::history::HistoryPlugin; +use crate::plugins::ip::IpPlugin; +use crate::plugins::lorem::LoremPlugin; +use crate::plugins::macros::MacrosPlugin; use crate::plugins::media::MediaPlugin; +use crate::plugins::missing::MissingPlugin; use crate::plugins::network::NetworkPlugin; use crate::plugins::note::NotePlugin; +use crate::plugins::omni_search::OmniSearchPlugin; use crate::plugins::processes::ProcessesPlugin; +use crate::plugins::random::RandomPlugin; use crate::plugins::recycle::RecyclePlugin; use crate::plugins::reddit::RedditPlugin; use crate::plugins::runescape::RunescapeSearchPlugin; +use crate::plugins::screenshot::ScreenshotPlugin; +use crate::plugins::settings::SettingsPlugin; use crate::plugins::shell::ShellPlugin; use crate::plugins::snippets::SnippetsPlugin; -use crate::plugins::fav::FavPlugin; -use crate::plugins::macros::MacrosPlugin; -use crate::plugins::omni_search::OmniSearchPlugin; +use crate::plugins::stopwatch::StopwatchPlugin; use crate::plugins::sysinfo::SysInfoPlugin; use crate::plugins::system::SystemPlugin; -use crate::plugins::settings::SettingsPlugin; -use crate::plugins::missing::MissingPlugin; use crate::plugins::task_manager::TaskManagerPlugin; use crate::plugins::tempfile::TempfilePlugin; +use crate::plugins::text_case::TextCasePlugin; use crate::plugins::timer::TimerPlugin; -use crate::plugins::stopwatch::StopwatchPlugin; +use crate::plugins::timestamp::TimestampPlugin; use crate::plugins::todo::TodoPlugin; use crate::plugins::unit_convert::UnitConvertPlugin; -use crate::plugins::base_convert::BaseConvertPlugin; use crate::plugins::volume::VolumePlugin; use crate::plugins::weather::WeatherPlugin; use crate::plugins::wikipedia::WikipediaPlugin; use crate::plugins::windows::WindowsPlugin; -use crate::plugins::browser_tabs::BrowserTabsPlugin; use crate::plugins::youtube::YoutubePlugin; -use crate::plugins::ip::IpPlugin; -use crate::plugins::timestamp::TimestampPlugin; -use crate::plugins::random::RandomPlugin; -use crate::plugins::lorem::LoremPlugin; -use crate::plugins::convert_panel::ConvertPanelPlugin; -use crate::plugins::color_picker::ColorPickerPlugin; use crate::plugins_builtin::{CalculatorPlugin, WebSearchPlugin}; use crate::settings::NetUnit; -use std::collections::HashSet; -use std::sync::Arc; +use eframe::egui; use libloading::Library; use serde_json::Value; -use eframe::egui; +use std::collections::HashSet; +use std::sync::Arc; pub trait Plugin: Send + Sync { /// Return actions based on the query string diff --git a/src/plugins/bookmarks.rs b/src/plugins/bookmarks.rs index 2cc0aabb..b9b5ba3a 100644 --- a/src/plugins/bookmarks.rs +++ b/src/plugins/bookmarks.rs @@ -1,12 +1,12 @@ use crate::actions::Action; -use crate::plugin::Plugin; use crate::common::lru::LruCache; +use crate::plugin::Plugin; use fuzzy_matcher::skim::SkimMatcherV2; use fuzzy_matcher::FuzzyMatcher; use notify::{Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; +use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use std::sync::{Arc, Mutex}; -use once_cell::sync::Lazy; pub const BOOKMARKS_FILE: &str = "bookmarks.json"; diff --git a/src/plugins/brightness.rs b/src/plugins/brightness.rs index b288a20c..cb3f93d8 100644 --- a/src/plugins/brightness.rs +++ b/src/plugins/brightness.rs @@ -30,7 +30,9 @@ impl Plugin for BrightnessPlugin { Vec::new() } - fn name(&self) -> &str { "brightness" } + fn name(&self) -> &str { + "brightness" + } fn description(&self) -> &str { "Adjust display brightness (prefix: `bright`)" diff --git a/src/plugins/calc_history.rs b/src/plugins/calc_history.rs index 8ff36d8e..2e8750c8 100644 --- a/src/plugins/calc_history.rs +++ b/src/plugins/calc_history.rs @@ -1,4 +1,4 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use std::collections::VecDeque; pub const CALC_HISTORY_FILE: &str = "calc_history.json"; @@ -48,7 +48,10 @@ pub fn clear_history_file(path: &str) -> anyhow::Result<()> { /// Append an entry to calc history at `path` keeping up to `max` items. pub fn append_entry(path: &str, entry: CalcHistoryEntry, max: usize) -> anyhow::Result<()> { let mut history = load_history(path).unwrap_or_default(); - if let Some(pos) = history.iter().position(|e| e.expr == entry.expr && e.result == entry.result) { + if let Some(pos) = history + .iter() + .position(|e| e.expr == entry.expr && e.result == entry.result) + { history.remove(pos); } history.push_front(entry); @@ -57,4 +60,3 @@ pub fn append_entry(path: &str, entry: CalcHistoryEntry, max: usize) -> anyhow:: } save_history(path, &history) } - diff --git a/src/plugins/clipboard.rs b/src/plugins/clipboard.rs index 0922db9f..5041c1cb 100644 --- a/src/plugins/clipboard.rs +++ b/src/plugins/clipboard.rs @@ -1,8 +1,8 @@ use crate::actions::Action; +use crate::common::json_watch::{watch_json, JsonWatcher}; use crate::plugin::Plugin; use arboard::Clipboard; use eframe::egui; -use crate::common::json_watch::{watch_json, JsonWatcher}; use serde_json; use std::collections::VecDeque; use std::sync::{Arc, Mutex}; diff --git a/src/plugins/color_picker.rs b/src/plugins/color_picker.rs index 1c3b2344..46170116 100644 --- a/src/plugins/color_picker.rs +++ b/src/plugins/color_picker.rs @@ -24,7 +24,9 @@ impl Plugin for ColorPickerPlugin { fn search(&self, query: &str) -> Vec { const PREFIX: &str = "color"; let trimmed = query.trim(); - let Some(rest) = crate::common::strip_prefix_ci(trimmed, PREFIX) else { return Vec::new(); }; + let Some(rest) = crate::common::strip_prefix_ci(trimmed, PREFIX) else { + return Vec::new(); + }; let arg = rest.trim(); let mut color = self.color; @@ -94,22 +96,39 @@ impl Plugin for ColorPickerPlugin { fn default_settings(&self) -> Option { serde_json::to_value(ColorPickerSettings { - color: [self.color.r(), self.color.g(), self.color.b(), self.color.a()], + color: [ + self.color.r(), + self.color.g(), + self.color.b(), + self.color.a(), + ], }) .ok() } fn apply_settings(&mut self, value: &serde_json::Value) { if let Ok(cfg) = serde_json::from_value::(value.clone()) { - self.color = Color32::from_rgba_unmultiplied(cfg.color[0], cfg.color[1], cfg.color[2], cfg.color[3]); + self.color = Color32::from_rgba_unmultiplied( + cfg.color[0], + cfg.color[1], + cfg.color[2], + cfg.color[3], + ); } } fn settings_ui(&mut self, ui: &mut egui::Ui, value: &mut serde_json::Value) { - let mut cfg: ColorPickerSettings = serde_json::from_value(value.clone()).unwrap_or(ColorPickerSettings { - color: [self.color.r(), self.color.g(), self.color.b(), self.color.a()], - }); - let mut col = Color32::from_rgba_unmultiplied(cfg.color[0], cfg.color[1], cfg.color[2], cfg.color[3]); + let mut cfg: ColorPickerSettings = + serde_json::from_value(value.clone()).unwrap_or(ColorPickerSettings { + color: [ + self.color.r(), + self.color.g(), + self.color.b(), + self.color.a(), + ], + }); + let mut col = + Color32::from_rgba_unmultiplied(cfg.color[0], cfg.color[1], cfg.color[2], cfg.color[3]); if ui.color_edit_button_srgba(&mut col).changed() { cfg.color = [col.r(), col.g(), col.b(), col.a()]; self.color = col; @@ -164,4 +183,3 @@ fn rgb_to_hsl(r: u8, g: u8, b: u8) -> (f32, f32, f32) { (h / 6.0 * 360.0, s * 100.0, l * 100.0) } } - diff --git a/src/plugins/convert_panel.rs b/src/plugins/convert_panel.rs index db661e66..d7a543ed 100644 --- a/src/plugins/convert_panel.rs +++ b/src/plugins/convert_panel.rs @@ -49,4 +49,3 @@ impl Plugin for ConvertPanelPlugin { ] } } - diff --git a/src/plugins/dropcalc.rs b/src/plugins/dropcalc.rs index a42e8be0..4d21c49c 100644 --- a/src/plugins/dropcalc.rs +++ b/src/plugins/dropcalc.rs @@ -10,7 +10,11 @@ fn parse_prob(input: &str) -> Option { if let Some((num, den)) = input.split_once('/') { let num: f64 = num.trim().parse().ok()?; let den: f64 = den.trim().parse().ok()?; - if den != 0.0 { Some(num / den) } else { None } + if den != 0.0 { + Some(num / den) + } else { + None + } } else { input.trim().parse::().ok() } @@ -67,4 +71,3 @@ impl Plugin for DropCalcPlugin { }] } } - diff --git a/src/plugins/help.rs b/src/plugins/help.rs index 8c318ddb..76d23207 100644 --- a/src/plugins/help.rs +++ b/src/plugins/help.rs @@ -8,12 +8,12 @@ impl Plugin for HelpPlugin { let q = query.trim(); if let Some(rest) = crate::common::strip_prefix_ci(q, "help") { if rest.is_empty() || rest.starts_with(' ') { - return vec![Action { - label: "Show command list".into(), - desc: "Display available command prefixes".into(), - action: "help:show".into(), - args: None, - }]; + return vec![Action { + label: "Show command list".into(), + desc: "Display available command prefixes".into(), + action: "help:show".into(), + args: None, + }]; } } Vec::new() @@ -40,4 +40,3 @@ impl Plugin for HelpPlugin { }] } } - diff --git a/src/plugins/lorem.rs b/src/plugins/lorem.rs index 48607325..74fc895a 100644 --- a/src/plugins/lorem.rs +++ b/src/plugins/lorem.rs @@ -61,10 +61,24 @@ impl Plugin for LoremPlugin { fn commands(&self) -> Vec { vec![ - Action { label: "lorem w ".into(), desc: "Lorem words".into(), action: "query:lorem w ".into(), args: None }, - Action { label: "lorem s ".into(), desc: "Lorem sentences".into(), action: "query:lorem s ".into(), args: None }, - Action { label: "lorem p ".into(), desc: "Lorem paragraphs".into(), action: "query:lorem p ".into(), args: None }, + Action { + label: "lorem w ".into(), + desc: "Lorem words".into(), + action: "query:lorem w ".into(), + args: None, + }, + Action { + label: "lorem s ".into(), + desc: "Lorem sentences".into(), + action: "query:lorem s ".into(), + args: None, + }, + Action { + label: "lorem p ".into(), + desc: "Lorem paragraphs".into(), + action: "query:lorem p ".into(), + args: None, + }, ] } } - diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index f4481b39..740c28eb 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -1,47 +1,47 @@ -pub mod clipboard; -pub mod shell; +pub mod asciiart; +pub mod base_convert; pub mod bookmarks; -pub mod runescape; -pub mod history; +pub mod brightness; +pub mod browser_tabs; +pub mod calc_history; +pub mod clipboard; +pub mod color_picker; +pub mod convert_panel; +pub mod dropcalc; +pub mod emoji; +pub mod fav; pub mod folders; -pub mod system; -pub mod settings; pub mod help; -pub mod youtube; -pub mod reddit; -pub mod wikipedia; -pub mod processes; -pub mod sysinfo; +pub mod history; +pub mod ip; +pub mod lorem; +pub mod macros; +pub mod media; +pub mod missing; pub mod network; -pub mod weather; pub mod note; -pub mod todo; -pub mod timer; -pub mod stopwatch; -pub mod snippets; -pub mod media; -pub mod volume; -pub mod brightness; -pub mod unit_convert; -pub mod base_convert; -pub mod dropcalc; +pub mod omni_search; +pub mod processes; +pub mod random; pub mod recycle; -pub mod tempfile; -pub mod asciiart; -pub mod emoji; -pub mod task_manager; -pub mod windows; -pub mod browser_tabs; +pub mod reddit; +pub mod runescape; pub mod screenshot; -pub mod ip; -pub mod omni_search; -pub mod macros; -pub mod fav; +pub mod settings; +pub mod shell; +pub mod snippets; +pub mod stopwatch; +pub mod sysinfo; +pub mod system; +pub mod task_manager; +pub mod tempfile; pub mod text_case; +pub mod timer; pub mod timestamp; -pub mod random; -pub mod lorem; -pub mod convert_panel; -pub mod color_picker; -pub mod missing; -pub mod calc_history; +pub mod todo; +pub mod unit_convert; +pub mod volume; +pub mod weather; +pub mod wikipedia; +pub mod windows; +pub mod youtube; diff --git a/src/plugins/processes.rs b/src/plugins/processes.rs index e7b75084..a5b58989 100644 --- a/src/plugins/processes.rs +++ b/src/plugins/processes.rs @@ -31,10 +31,7 @@ impl Plugin for ProcessesPlugin { if filter.is_empty() { true } else { - p.name() - .to_string_lossy() - .to_lowercase() - .contains(&filter) + p.name().to_string_lossy().to_lowercase().contains(&filter) } }) .flat_map(|p| { @@ -75,10 +72,24 @@ impl Plugin for ProcessesPlugin { fn commands(&self) -> Vec { vec![ - Action { label: "ps".into(), desc: "Processes".into(), action: "query:ps ".into(), args: None }, - Action { label: "psk".into(), desc: "Kill process".into(), action: "query:psk ".into(), args: None }, - Action { label: "pss".into(), desc: "Switch process".into(), action: "query:pss ".into(), args: None }, + Action { + label: "ps".into(), + desc: "Processes".into(), + action: "query:ps ".into(), + args: None, + }, + Action { + label: "psk".into(), + desc: "Kill process".into(), + action: "query:psk ".into(), + args: None, + }, + Action { + label: "pss".into(), + desc: "Switch process".into(), + action: "query:pss ".into(), + args: None, + }, ] } } - diff --git a/src/plugins/random.rs b/src/plugins/random.rs index 808c1656..8a50498f 100644 --- a/src/plugins/random.rs +++ b/src/plugins/random.rs @@ -12,12 +12,16 @@ pub struct RandomPlugin { impl RandomPlugin { /// Create a new plugin using randomness from the operating system. pub fn new() -> Self { - Self { rng: Mutex::new(StdRng::from_entropy()) } + Self { + rng: Mutex::new(StdRng::from_entropy()), + } } /// Create a plugin with a fixed seed (useful for deterministic tests). pub fn from_seed(seed: u64) -> Self { - Self { rng: Mutex::new(StdRng::seed_from_u64(seed)) } + Self { + rng: Mutex::new(StdRng::seed_from_u64(seed)), + } } } diff --git a/src/plugins/recycle.rs b/src/plugins/recycle.rs index 3e439c9a..b7db1173 100644 --- a/src/plugins/recycle.rs +++ b/src/plugins/recycle.rs @@ -31,7 +31,11 @@ impl Plugin for RecyclePlugin { } fn commands(&self) -> Vec { - vec![Action { label: "rec".into(), desc: "Recycle".into(), action: "query:rec".into(), args: None }] + vec![Action { + label: "rec".into(), + desc: "Recycle".into(), + action: "query:rec".into(), + args: None, + }] } } - diff --git a/src/plugins/reddit.rs b/src/plugins/reddit.rs index 646551d1..381ac3fb 100644 --- a/src/plugins/reddit.rs +++ b/src/plugins/reddit.rs @@ -13,10 +13,7 @@ impl Plugin for RedditPlugin { return vec![Action { label: format!("Search Reddit for {q}"), desc: "Web search".into(), - action: format!( - "https://www.reddit.com/search/?q={}", - encode(q) - ), + action: format!("https://www.reddit.com/search/?q={}", encode(q)), args: None, }]; } @@ -37,6 +34,11 @@ impl Plugin for RedditPlugin { } fn commands(&self) -> Vec { - vec![Action { label: "red".into(), desc: "Reddit".into(), action: "query:red ".into(), args: None }] + vec![Action { + label: "red".into(), + desc: "Reddit".into(), + action: "query:red ".into(), + args: None, + }] } } diff --git a/src/plugins/runescape.rs b/src/plugins/runescape.rs index 84035da5..745bd6bd 100644 --- a/src/plugins/runescape.rs +++ b/src/plugins/runescape.rs @@ -25,10 +25,7 @@ impl Plugin for RunescapeSearchPlugin { return vec![Action { label: format!("Search Old School RuneScape Wiki for {q}"), desc: "Web search".into(), - action: format!( - "https://oldschool.runescape.wiki/?search={}", - encode(q) - ), + action: format!("https://oldschool.runescape.wiki/?search={}", encode(q)), args: None, }]; } @@ -50,9 +47,18 @@ impl Plugin for RunescapeSearchPlugin { fn commands(&self) -> Vec { vec![ - Action { label: "rs".into(), desc: "Runescape".into(), action: "query:rs ".into(), args: None }, - Action { label: "osrs".into(), desc: "Runescape".into(), action: "query:osrs ".into(), args: None }, + Action { + label: "rs".into(), + desc: "Runescape".into(), + action: "query:rs ".into(), + args: None, + }, + Action { + label: "osrs".into(), + desc: "Runescape".into(), + action: "query:osrs ".into(), + args: None, + }, ] } } - diff --git a/src/plugins/screenshot.rs b/src/plugins/screenshot.rs index d958de97..f7b57198 100644 --- a/src/plugins/screenshot.rs +++ b/src/plugins/screenshot.rs @@ -2,9 +2,9 @@ use crate::actions::Action; use crate::plugin::Plugin; use crate::settings::Settings; use eframe::egui; +use rfd::FileDialog; use serde::{Deserialize, Serialize}; use std::path::PathBuf; -use rfd::FileDialog; /// Return the directory used to store screenshots. /// @@ -85,7 +85,6 @@ pub fn launch_editor( Ok(()) } - pub struct ScreenshotPlugin; impl Plugin for ScreenshotPlugin { @@ -183,10 +182,7 @@ impl Plugin for ScreenshotPlugin { "Save file when copying screenshot", ); ui.checkbox(&mut cfg.screenshot_use_editor, "Enable screenshot editor"); - ui.checkbox( - &mut cfg.screenshot_auto_save, - "Auto-save after editing", - ); + ui.checkbox(&mut cfg.screenshot_auto_save, "Auto-save after editing"); if let Ok(v) = serde_json::to_value(&cfg) { *value = v; } diff --git a/src/plugins/settings.rs b/src/plugins/settings.rs index dc4e1cd0..e0b76ca4 100644 --- a/src/plugins/settings.rs +++ b/src/plugins/settings.rs @@ -40,4 +40,3 @@ impl Plugin for SettingsPlugin { }] } } - diff --git a/src/plugins/sysinfo.rs b/src/plugins/sysinfo.rs index 95fedd72..380941d9 100644 --- a/src/plugins/sysinfo.rs +++ b/src/plugins/sysinfo.rs @@ -108,10 +108,30 @@ impl Plugin for SysInfoPlugin { fn commands(&self) -> Vec { vec![ - Action { label: "info".into(), desc: "SysInfo".into(), action: "query:info".into(), args: None }, - Action { label: "info cpu".into(), desc: "SysInfo".into(), action: "query:info cpu".into(), args: None }, - Action { label: "info mem".into(), desc: "SysInfo".into(), action: "query:info mem".into(), args: None }, - Action { label: "info disk".into(), desc: "SysInfo".into(), action: "query:info disk".into(), args: None }, + Action { + label: "info".into(), + desc: "SysInfo".into(), + action: "query:info".into(), + args: None, + }, + Action { + label: "info cpu".into(), + desc: "SysInfo".into(), + action: "query:info cpu".into(), + args: None, + }, + Action { + label: "info mem".into(), + desc: "SysInfo".into(), + action: "query:info mem".into(), + args: None, + }, + Action { + label: "info disk".into(), + desc: "SysInfo".into(), + action: "query:info disk".into(), + args: None, + }, ] } } diff --git a/src/plugins/system.rs b/src/plugins/system.rs index 1b86f906..cfef8e8f 100644 --- a/src/plugins/system.rs +++ b/src/plugins/system.rs @@ -37,6 +37,11 @@ impl Plugin for SystemPlugin { } fn commands(&self) -> Vec { - vec![Action { label: "sys".into(), desc: "System".into(), action: "query:sys ".into(), args: None }] + vec![Action { + label: "sys".into(), + desc: "System".into(), + action: "query:sys ".into(), + args: None, + }] } } diff --git a/src/plugins/task_manager.rs b/src/plugins/task_manager.rs index fe161f29..31643cc8 100644 --- a/src/plugins/task_manager.rs +++ b/src/plugins/task_manager.rs @@ -38,4 +38,3 @@ impl Plugin for TaskManagerPlugin { }] } } - diff --git a/src/plugins/text_case.rs b/src/plugins/text_case.rs index 92b525f0..57dbe11d 100644 --- a/src/plugins/text_case.rs +++ b/src/plugins/text_case.rs @@ -21,11 +21,32 @@ impl Plugin for TextCasePlugin { if words.len() > 1 { let first = words[0].to_lowercase(); let known = [ - "upper", "lower", "capitalized", "camel", "pascal", "snake", - "screaming", "kebab", "train", "dot", "alternating", "mocking", - "inverse", "backwards", "acronym", "initials", "title", - "sentence", "base64", "hex", "binary", "rot13", "clap", - "emoji", "custom", "morse", + "upper", + "lower", + "capitalized", + "camel", + "pascal", + "snake", + "screaming", + "kebab", + "train", + "dot", + "alternating", + "mocking", + "inverse", + "backwards", + "acronym", + "initials", + "title", + "sentence", + "base64", + "hex", + "binary", + "rot13", + "clap", + "emoji", + "custom", + "morse", ]; if known.contains(&first.as_str()) { specific_case = Some(first.clone()); @@ -61,11 +82,27 @@ impl Plugin for TextCasePlugin { }; let pascal = words.iter().map(|w| cap(w)).collect::(); - let snake = words.iter().map(|w| w.to_lowercase()).collect::>().join("_"); - let screaming = words.iter().map(|w| w.to_uppercase()).collect::>().join("_"); - let kebab = words.iter().map(|w| w.to_lowercase()).collect::>().join("-"); + let snake = words + .iter() + .map(|w| w.to_lowercase()) + .collect::>() + .join("_"); + let screaming = words + .iter() + .map(|w| w.to_uppercase()) + .collect::>() + .join("_"); + let kebab = words + .iter() + .map(|w| w.to_lowercase()) + .collect::>() + .join("-"); let train = words.iter().map(|w| cap(w)).collect::>().join("-"); - let dot = words.iter().map(|w| w.to_lowercase()).collect::>().join("."); + let dot = words + .iter() + .map(|w| w.to_lowercase()) + .collect::>() + .join("."); let alt_case = { let mut upper_flag = true; @@ -134,8 +171,8 @@ impl Plugin for TextCasePlugin { .join(" "); let small_words = [ - "a", "an", "and", "or", "the", "in", "on", "of", "for", "to", - "at", "by", "with", "without", + "a", "an", "and", "or", "the", "in", "on", "of", "for", "to", "at", "by", + "with", "without", ]; let small_set: std::collections::HashSet<&str> = small_words.iter().cloned().collect(); @@ -199,13 +236,23 @@ impl Plugin for TextCasePlugin { .collect(); let emoji_case = words .iter() - .map(|w| emoji_map.get(&w.to_lowercase().as_str()).copied().unwrap_or(*w)) + .map(|w| { + emoji_map + .get(&w.to_lowercase().as_str()) + .copied() + .unwrap_or(*w) + }) .collect::>() .join(" "); let custom = words .iter() - .map(|w| w.chars().map(|c| c.to_string()).collect::>().join("-")) + .map(|w| { + w.chars() + .map(|c| c.to_string()) + .collect::>() + .join("-") + }) .collect::>() .join(" "); @@ -265,32 +312,240 @@ impl Plugin for TextCasePlugin { .join(" "); let actions = vec![ - ("upper", Action { label: upper.clone(), desc: "Text Case-Uppercase".into(), action: format!("clipboard:{}", upper), args: None }), - ("lower", Action { label: lower.clone(), desc: "Text Case-Lowercase".into(), action: format!("clipboard:{}", lower), args: None }), - ("capitalized", Action { label: capitalized.clone(), desc: "Text Case-Capitalized".into(), action: format!("clipboard:{}", capitalized), args: None }), - ("camel", Action { label: camel.clone(), desc: "Text Case-Camel".into(), action: format!("clipboard:{}", camel), args: None }), - ("pascal", Action { label: pascal.clone(), desc: "Text Case-Pascal".into(), action: format!("clipboard:{}", pascal), args: None }), - ("snake", Action { label: snake.clone(), desc: "Text Case-Snake".into(), action: format!("clipboard:{}", snake), args: None }), - ("screaming", Action { label: screaming.clone(), desc: "Text Case-Screaming".into(), action: format!("clipboard:{}", screaming), args: None }), - ("kebab", Action { label: kebab.clone(), desc: "Text Case-Kebab".into(), action: format!("clipboard:{}", kebab), args: None }), - ("train", Action { label: train.clone(), desc: "Text Case-Train".into(), action: format!("clipboard:{}", train), args: None }), - ("dot", Action { label: dot.clone(), desc: "Text Case-Dot".into(), action: format!("clipboard:{}", dot), args: None }), - ("alternating", Action { label: alt_case.clone(), desc: "Text Case-Alternating".into(), action: format!("clipboard:{}", alt_case), args: None }), - ("mocking", Action { label: mocking.clone(), desc: "Text Case-Mocking".into(), action: format!("clipboard:{}", mocking), args: None }), - ("inverse", Action { label: inverse.clone(), desc: "Text Case-Inverse".into(), action: format!("clipboard:{}", inverse), args: None }), - ("backwards", Action { label: backwards.clone(), desc: "Text Case-Backwards".into(), action: format!("clipboard:{}", backwards), args: None }), - ("acronym", Action { label: acronym.clone(), desc: "Text Case-Acronym".into(), action: format!("clipboard:{}", acronym), args: None }), - ("initials", Action { label: initial_caps.clone(), desc: "Text Case-Initials".into(), action: format!("clipboard:{}", initial_caps), args: None }), - ("title", Action { label: title_case.clone(), desc: "Text Case-Title".into(), action: format!("clipboard:{}", title_case), args: None }), - ("sentence", Action { label: sentence.clone(), desc: "Text Case-Sentence".into(), action: format!("clipboard:{}", sentence), args: None }), - ("base64", Action { label: b64.clone(), desc: "Text Case-Base64".into(), action: format!("clipboard:{}", b64), args: None }), - ("hex", Action { label: hex_enc.clone(), desc: "Text Case-Hex".into(), action: format!("clipboard:{}", hex_enc), args: None }), - ("binary", Action { label: binary.clone(), desc: "Text Case-Binary".into(), action: format!("clipboard:{}", binary), args: None }), - ("rot13", Action { label: rot13.clone(), desc: "Text Case-ROT13".into(), action: format!("clipboard:{}", rot13), args: None }), - ("clap", Action { label: clap.clone(), desc: "Text Case-Clap".into(), action: format!("clipboard:{}", clap), args: None }), - ("emoji", Action { label: emoji_case.clone(), desc: "Text Case-Emoji".into(), action: format!("clipboard:{}", emoji_case), args: None }), - ("custom", Action { label: custom.clone(), desc: "Text Case-Custom".into(), action: format!("clipboard:{}", custom), args: None }), - ("morse", Action { label: morse.clone(), desc: "Text Case-Morse".into(), action: format!("clipboard:{}", morse), args: None }), + ( + "upper", + Action { + label: upper.clone(), + desc: "Text Case-Uppercase".into(), + action: format!("clipboard:{}", upper), + args: None, + }, + ), + ( + "lower", + Action { + label: lower.clone(), + desc: "Text Case-Lowercase".into(), + action: format!("clipboard:{}", lower), + args: None, + }, + ), + ( + "capitalized", + Action { + label: capitalized.clone(), + desc: "Text Case-Capitalized".into(), + action: format!("clipboard:{}", capitalized), + args: None, + }, + ), + ( + "camel", + Action { + label: camel.clone(), + desc: "Text Case-Camel".into(), + action: format!("clipboard:{}", camel), + args: None, + }, + ), + ( + "pascal", + Action { + label: pascal.clone(), + desc: "Text Case-Pascal".into(), + action: format!("clipboard:{}", pascal), + args: None, + }, + ), + ( + "snake", + Action { + label: snake.clone(), + desc: "Text Case-Snake".into(), + action: format!("clipboard:{}", snake), + args: None, + }, + ), + ( + "screaming", + Action { + label: screaming.clone(), + desc: "Text Case-Screaming".into(), + action: format!("clipboard:{}", screaming), + args: None, + }, + ), + ( + "kebab", + Action { + label: kebab.clone(), + desc: "Text Case-Kebab".into(), + action: format!("clipboard:{}", kebab), + args: None, + }, + ), + ( + "train", + Action { + label: train.clone(), + desc: "Text Case-Train".into(), + action: format!("clipboard:{}", train), + args: None, + }, + ), + ( + "dot", + Action { + label: dot.clone(), + desc: "Text Case-Dot".into(), + action: format!("clipboard:{}", dot), + args: None, + }, + ), + ( + "alternating", + Action { + label: alt_case.clone(), + desc: "Text Case-Alternating".into(), + action: format!("clipboard:{}", alt_case), + args: None, + }, + ), + ( + "mocking", + Action { + label: mocking.clone(), + desc: "Text Case-Mocking".into(), + action: format!("clipboard:{}", mocking), + args: None, + }, + ), + ( + "inverse", + Action { + label: inverse.clone(), + desc: "Text Case-Inverse".into(), + action: format!("clipboard:{}", inverse), + args: None, + }, + ), + ( + "backwards", + Action { + label: backwards.clone(), + desc: "Text Case-Backwards".into(), + action: format!("clipboard:{}", backwards), + args: None, + }, + ), + ( + "acronym", + Action { + label: acronym.clone(), + desc: "Text Case-Acronym".into(), + action: format!("clipboard:{}", acronym), + args: None, + }, + ), + ( + "initials", + Action { + label: initial_caps.clone(), + desc: "Text Case-Initials".into(), + action: format!("clipboard:{}", initial_caps), + args: None, + }, + ), + ( + "title", + Action { + label: title_case.clone(), + desc: "Text Case-Title".into(), + action: format!("clipboard:{}", title_case), + args: None, + }, + ), + ( + "sentence", + Action { + label: sentence.clone(), + desc: "Text Case-Sentence".into(), + action: format!("clipboard:{}", sentence), + args: None, + }, + ), + ( + "base64", + Action { + label: b64.clone(), + desc: "Text Case-Base64".into(), + action: format!("clipboard:{}", b64), + args: None, + }, + ), + ( + "hex", + Action { + label: hex_enc.clone(), + desc: "Text Case-Hex".into(), + action: format!("clipboard:{}", hex_enc), + args: None, + }, + ), + ( + "binary", + Action { + label: binary.clone(), + desc: "Text Case-Binary".into(), + action: format!("clipboard:{}", binary), + args: None, + }, + ), + ( + "rot13", + Action { + label: rot13.clone(), + desc: "Text Case-ROT13".into(), + action: format!("clipboard:{}", rot13), + args: None, + }, + ), + ( + "clap", + Action { + label: clap.clone(), + desc: "Text Case-Clap".into(), + action: format!("clipboard:{}", clap), + args: None, + }, + ), + ( + "emoji", + Action { + label: emoji_case.clone(), + desc: "Text Case-Emoji".into(), + action: format!("clipboard:{}", emoji_case), + args: None, + }, + ), + ( + "custom", + Action { + label: custom.clone(), + desc: "Text Case-Custom".into(), + action: format!("clipboard:{}", custom), + args: None, + }, + ), + ( + "morse", + Action { + label: morse.clone(), + desc: "Text Case-Morse".into(), + action: format!("clipboard:{}", morse), + args: None, + }, + ), ]; if let Some(case) = specific_case { diff --git a/src/plugins/timestamp.rs b/src/plugins/timestamp.rs index 99681d41..fc017614 100644 --- a/src/plugins/timestamp.rs +++ b/src/plugins/timestamp.rs @@ -33,7 +33,11 @@ impl TimestampPlugin { ); } } - for fmt in ["%Y-%m-%d %H:%M:%S%.f", "%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M"] { + for fmt in [ + "%Y-%m-%d %H:%M:%S%.f", + "%Y-%m-%d %H:%M:%S", + "%Y-%m-%d %H:%M", + ] { if let Ok(dt) = NaiveDateTime::parse_from_str(s, fmt) { let midnight = dt.date().and_hms_opt(0, 0, 0)?; return Some((dt - midnight).num_milliseconds()); @@ -77,8 +81,15 @@ impl Plugin for TimestampPlugin { return Vec::new(); } if let Ok(num) = arg.parse::() { - let ts_sec = if num.abs() > 1_000_000_000_000 { num / 1000 } else { num }; - let dt = Local.timestamp_opt(ts_sec, 0).single().or_else(|| Local.timestamp_opt(0, 0).single()); + let ts_sec = if num.abs() > 1_000_000_000_000 { + num / 1000 + } else { + num + }; + let dt = Local + .timestamp_opt(ts_sec, 0) + .single() + .or_else(|| Local.timestamp_opt(0, 0).single()); if let Some(dt) = dt { let out = dt.format("%Y-%m-%d %H:%M:%S").to_string(); return vec![Action { @@ -136,4 +147,3 @@ impl Plugin for TimestampPlugin { ] } } - diff --git a/src/plugins/weather.rs b/src/plugins/weather.rs index 2bf88234..5e860a67 100644 --- a/src/plugins/weather.rs +++ b/src/plugins/weather.rs @@ -15,10 +15,7 @@ impl Plugin for WeatherPlugin { return vec![Action { label: format!("Show weather for {q}"), desc: "Web search".into(), - action: format!( - "https://www.weather.com/weather/today/l/{}", - encode(q) - ), + action: format!("https://www.weather.com/weather/today/l/{}", encode(q)), args: None, }]; } @@ -39,7 +36,11 @@ impl Plugin for WeatherPlugin { } fn commands(&self) -> Vec { - vec![Action { label: "weather".into(), desc: "Weather".into(), action: "query:weather ".into(), args: None }] + vec![Action { + label: "weather".into(), + desc: "Weather".into(), + action: "query:weather ".into(), + args: None, + }] } } - diff --git a/src/plugins/wikipedia.rs b/src/plugins/wikipedia.rs index 056afe70..03a963bd 100644 --- a/src/plugins/wikipedia.rs +++ b/src/plugins/wikipedia.rs @@ -38,7 +38,11 @@ impl Plugin for WikipediaPlugin { } fn commands(&self) -> Vec { - vec![Action { label: "wiki".into(), desc: "Wikipedia".into(), action: "query:wiki ".into(), args: None }] + vec![Action { + label: "wiki".into(), + desc: "Wikipedia".into(), + action: "query:wiki ".into(), + args: None, + }] } } - diff --git a/src/plugins/windows.rs b/src/plugins/windows.rs index 3d000863..a66e2e5a 100644 --- a/src/plugins/windows.rs +++ b/src/plugins/windows.rs @@ -14,8 +14,7 @@ impl Plugin for WindowsPlugin { let filter = rest.to_lowercase(); use windows::Win32::Foundation::{BOOL, HWND, LPARAM}; use windows::Win32::UI::WindowsAndMessaging::{ - EnumWindows, GetWindowTextW, IsWindowVisible, GetWindow, GetWindowTextLengthW, - GW_OWNER, + EnumWindows, GetWindow, GetWindowTextLengthW, GetWindowTextW, IsWindowVisible, GW_OWNER, }; struct Ctx { filter: String, @@ -49,7 +48,10 @@ impl Plugin for WindowsPlugin { } BOOL(1) } - let mut ctx = Ctx { filter, out: Vec::new() }; + let mut ctx = Ctx { + filter, + out: Vec::new(), + }; unsafe { let ctx_ptr = &mut ctx as *mut Ctx; let _ = EnumWindows(Some(enum_cb), LPARAM(ctx_ptr as isize)); @@ -70,7 +72,11 @@ impl Plugin for WindowsPlugin { } fn commands(&self) -> Vec { - vec![Action { label: "win".into(), desc: "Windows".into(), action: "query:win ".into(), args: None }] + vec![Action { + label: "win".into(), + desc: "Windows".into(), + action: "query:win ".into(), + args: None, + }] } } - diff --git a/src/plugins/youtube.rs b/src/plugins/youtube.rs index 6e857089..fc436e9d 100644 --- a/src/plugins/youtube.rs +++ b/src/plugins/youtube.rs @@ -14,10 +14,7 @@ impl Plugin for YoutubePlugin { return vec![Action { label: format!("Search YouTube for {q}"), desc: "Web search".into(), - action: format!( - "https://www.youtube.com/results?search_query={}", - encode(q) - ), + action: format!("https://www.youtube.com/results?search_query={}", encode(q)), args: None, }]; } @@ -38,6 +35,11 @@ impl Plugin for YoutubePlugin { } fn commands(&self) -> Vec { - vec![Action { label: "yt".into(), desc: "YouTube".into(), action: "query:yt ".into(), args: None }] + vec![Action { + label: "yt".into(), + desc: "YouTube".into(), + action: "query:yt ".into(), + args: None, + }] } } diff --git a/src/plugins_builtin.rs b/src/plugins_builtin.rs index cc290212..a5c4a5a8 100644 --- a/src/plugins_builtin.rs +++ b/src/plugins_builtin.rs @@ -1,9 +1,9 @@ use crate::actions::Action; use crate::plugin::Plugin; -use urlencoding::encode; use crate::plugins::calc_history::{self, CalcHistoryEntry, CALC_HISTORY_FILE, MAX_ENTRIES}; use eframe::egui; use serde::{Deserialize, Serialize}; +use urlencoding::encode; pub struct WebSearchPlugin; @@ -35,7 +35,12 @@ impl Plugin for WebSearchPlugin { } fn commands(&self) -> Vec { - vec![Action { label: "g".into(), desc: "Web search".into(), action: "query:g ".into(), args: None }] + vec![Action { + label: "g".into(), + desc: "Web search".into(), + action: "query:g ".into(), + args: None, + }] } } @@ -90,11 +95,7 @@ impl Plugin for CalculatorPlugin { expr: expr.to_string(), result: result.clone(), }; - let _ = calc_history::append_entry( - CALC_HISTORY_FILE, - entry, - MAX_ENTRIES, - ); + let _ = calc_history::append_entry(CALC_HISTORY_FILE, entry, MAX_ENTRIES); vec![Action { label: format!("{} = {}", expr, result), desc: "Calculator".into(), diff --git a/src/sound.rs b/src/sound.rs index 5eab12a6..c9431fbf 100644 --- a/src/sound.rs +++ b/src/sound.rs @@ -96,4 +96,3 @@ pub fn play_sound(name: &str) { } }); } - diff --git a/src/visibility.rs b/src/visibility.rs index 9719833b..aa5fbe2e 100644 --- a/src/visibility.rs +++ b/src/visibility.rs @@ -1,9 +1,11 @@ use eframe::egui; -use std::sync::{Arc, Mutex, atomic::{AtomicBool, Ordering}}; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, +}; use crate::hotkey::HotkeyTrigger; - /// Trait abstracting over an `egui::Context` for viewport commands. pub trait ViewportCtx { fn send_viewport_cmd(&self, cmd: egui::ViewportCommand); @@ -123,15 +125,19 @@ pub fn apply_visibility( if let Some((x, y)) = crate::window_manager::current_mouse_position() { let pos_x = x - window_size.0 / 2.0; let pos_y = y - window_size.1 / 2.0; - ctx.send_viewport_cmd(egui::ViewportCommand::OuterPosition(egui::pos2(pos_x, pos_y))); + ctx.send_viewport_cmd(egui::ViewportCommand::OuterPosition(egui::pos2( + pos_x, pos_y, + ))); } } ctx.send_viewport_cmd(egui::ViewportCommand::Visible(true)); ctx.send_viewport_cmd(egui::ViewportCommand::Minimized(false)); ctx.send_viewport_cmd(egui::ViewportCommand::Focus); } else { - ctx.send_viewport_cmd(egui::ViewportCommand::OuterPosition(egui::pos2(offscreen.0, offscreen.1))); + ctx.send_viewport_cmd(egui::ViewportCommand::OuterPosition(egui::pos2( + offscreen.0, + offscreen.1, + ))); } ctx.request_repaint(); } - diff --git a/tests/bookmarks_plugin.rs b/tests/bookmarks_plugin.rs index e7c4462a..5eba5db3 100644 --- a/tests/bookmarks_plugin.rs +++ b/tests/bookmarks_plugin.rs @@ -1,8 +1,10 @@ use multi_launcher::plugin::Plugin; -use multi_launcher::plugins::bookmarks::{save_bookmarks, load_bookmarks, BookmarkEntry, BookmarksPlugin, BOOKMARKS_FILE}; -use tempfile::tempdir; +use multi_launcher::plugins::bookmarks::{ + load_bookmarks, save_bookmarks, BookmarkEntry, BookmarksPlugin, BOOKMARKS_FILE, +}; use once_cell::sync::Lazy; use std::sync::Mutex; +use tempfile::tempdir; static TEST_MUTEX: Lazy> = Lazy::new(|| Mutex::new(())); @@ -12,7 +14,10 @@ fn alias_roundtrip() { let dir = tempdir().unwrap(); std::env::set_current_dir(dir.path()).unwrap(); - let entries = vec![BookmarkEntry { url: "https://example.com".into(), alias: Some("ex".into()) }]; + let entries = vec![BookmarkEntry { + url: "https://example.com".into(), + alias: Some("ex".into()), + }]; save_bookmarks(BOOKMARKS_FILE, &entries).unwrap(); let loaded = load_bookmarks(BOOKMARKS_FILE).unwrap(); assert_eq!(loaded.len(), 1); @@ -25,7 +30,10 @@ fn search_uses_alias_label() { let dir = tempdir().unwrap(); std::env::set_current_dir(dir.path()).unwrap(); - let entries = vec![BookmarkEntry { url: "https://example.com".into(), alias: Some("ex".into()) }]; + let entries = vec![BookmarkEntry { + url: "https://example.com".into(), + alias: Some("ex".into()), + }]; save_bookmarks(BOOKMARKS_FILE, &entries).unwrap(); let plugin = BookmarksPlugin::default(); @@ -66,8 +74,14 @@ fn bm_list_returns_bookmarks() { std::env::set_current_dir(dir.path()).unwrap(); let entries = vec![ - BookmarkEntry { url: "https://example.com".into(), alias: Some("ex".into()) }, - BookmarkEntry { url: "https://rust-lang.org".into(), alias: None }, + BookmarkEntry { + url: "https://example.com".into(), + alias: Some("ex".into()), + }, + BookmarkEntry { + url: "https://rust-lang.org".into(), + alias: None, + }, ]; save_bookmarks(BOOKMARKS_FILE, &entries).unwrap(); @@ -85,8 +99,14 @@ fn bm_rm_lists_bookmarks_without_filter() { std::env::set_current_dir(dir.path()).unwrap(); let entries = vec![ - BookmarkEntry { url: "https://example.com".into(), alias: Some("ex".into()) }, - BookmarkEntry { url: "https://rust-lang.org".into(), alias: None }, + BookmarkEntry { + url: "https://example.com".into(), + alias: Some("ex".into()), + }, + BookmarkEntry { + url: "https://rust-lang.org".into(), + alias: None, + }, ]; save_bookmarks(BOOKMARKS_FILE, &entries).unwrap(); @@ -100,4 +120,3 @@ fn bm_rm_lists_bookmarks_without_filter() { .iter() .any(|a| a.action == "bookmark:remove:https://rust-lang.org")); } - diff --git a/tests/brightness_plugin.rs b/tests/brightness_plugin.rs index eba49cd8..ac3846a7 100644 --- a/tests/brightness_plugin.rs +++ b/tests/brightness_plugin.rs @@ -1,6 +1,6 @@ +use multi_launcher::gui::BRIGHTNESS_QUERIES; use multi_launcher::plugin::Plugin; use multi_launcher::plugins::brightness::BrightnessPlugin; -use multi_launcher::gui::BRIGHTNESS_QUERIES; use std::sync::atomic::Ordering; #[test] diff --git a/tests/clipboard_persistence.rs b/tests/clipboard_persistence.rs index 1c11339a..fd3fc805 100644 --- a/tests/clipboard_persistence.rs +++ b/tests/clipboard_persistence.rs @@ -1,6 +1,6 @@ +use arboard::Clipboard; use multi_launcher::plugin::Plugin; use multi_launcher::plugins::clipboard::{save_history, ClipboardPlugin, CLIPBOARD_FILE}; -use arboard::Clipboard; use once_cell::sync::Lazy; use std::collections::VecDeque; use std::sync::Mutex; diff --git a/tests/convert_panel_plugin.rs b/tests/convert_panel_plugin.rs index f3f1e298..00c72435 100644 --- a/tests/convert_panel_plugin.rs +++ b/tests/convert_panel_plugin.rs @@ -16,4 +16,3 @@ fn search_convert_prefix() { assert_eq!(results.len(), 1); assert_eq!(results[0].action, "convert:panel"); } - diff --git a/tests/fav_plugin.rs b/tests/fav_plugin.rs index 94e62897..3508f649 100644 --- a/tests/fav_plugin.rs +++ b/tests/fav_plugin.rs @@ -1,9 +1,7 @@ use multi_launcher::launcher::launch_action; use multi_launcher::plugin::Plugin; use multi_launcher::plugins::bookmarks::{load_bookmarks, save_bookmarks, BOOKMARKS_FILE}; -use multi_launcher::plugins::fav::{ - resolve_with_plugin, save_favs, FavEntry, FavPlugin, FAV_FILE, -}; +use multi_launcher::plugins::fav::{resolve_with_plugin, save_favs, FavEntry, FavPlugin, FAV_FILE}; use multi_launcher::plugins_builtin::WebSearchPlugin; use once_cell::sync::Lazy; use std::sync::Mutex; diff --git a/tests/file_drop.rs b/tests/file_drop.rs index 4cd78667..63536d41 100644 --- a/tests/file_drop.rs +++ b/tests/file_drop.rs @@ -1,6 +1,8 @@ -use multi_launcher::{gui::LauncherApp, actions::Action, plugin::PluginManager, settings::Settings}; -use std::sync::{Arc, atomic::AtomicBool}; use eframe::egui; +use multi_launcher::{ + actions::Action, gui::LauncherApp, plugin::PluginManager, settings::Settings, +}; +use std::sync::{atomic::AtomicBool, Arc}; fn new_app(ctx: &egui::Context) -> LauncherApp { let actions: Vec = Vec::new(); @@ -28,7 +30,10 @@ fn dropping_file_opens_add_dialog() { let ctx = egui::Context::default(); let mut app = new_app(&ctx); let tmp = std::env::temp_dir().join("dummy.txt"); - let dropped = egui::DroppedFile { path: Some(tmp), ..Default::default() }; + let dropped = egui::DroppedFile { + path: Some(tmp), + ..Default::default() + }; app.handle_dropped_files(vec![dropped]); assert!(app.show_editor); assert!(app.editor.is_dialog_open()); diff --git a/tests/focus_visibility.rs b/tests/focus_visibility.rs index d622d054..be032f2b 100644 --- a/tests/focus_visibility.rs +++ b/tests/focus_visibility.rs @@ -1,7 +1,10 @@ +use eframe::egui; use multi_launcher::hotkey::{Hotkey, HotkeyTrigger}; use multi_launcher::visibility::handle_visibility_trigger; -use std::sync::{Arc, Mutex, atomic::{AtomicBool, Ordering}}; -use eframe::egui; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, +}; #[path = "mock_ctx.rs"] mod mock_ctx; @@ -52,7 +55,7 @@ fn focus_when_becoming_visible() { _ => panic!("unexpected command"), } match cmds[3] { - egui::ViewportCommand::Focus => {}, + egui::ViewportCommand::Focus => {} _ => panic!("unexpected command"), } } diff --git a/tests/folders_plugin.rs b/tests/folders_plugin.rs index 637a5f1c..9e92f5f0 100644 --- a/tests/folders_plugin.rs +++ b/tests/folders_plugin.rs @@ -1,7 +1,7 @@ use multi_launcher::plugins::folders::{append_folder, load_folders, save_folders, FOLDERS_FILE}; -use tempfile::tempdir; use once_cell::sync::Lazy; use std::sync::Mutex; +use tempfile::tempdir; static TEST_MUTEX: Lazy> = Lazy::new(|| Mutex::new(())); diff --git a/tests/follow_mouse.rs b/tests/follow_mouse.rs index d5698ea6..e344a147 100644 --- a/tests/follow_mouse.rs +++ b/tests/follow_mouse.rs @@ -1,9 +1,7 @@ use eframe::egui; use multi_launcher::visibility::apply_visibility; use multi_launcher::window_manager::{ - clear_mock_mouse_position, - set_mock_mouse_position, - MOCK_MOUSE_LOCK, + clear_mock_mouse_position, set_mock_mouse_position, MOCK_MOUSE_LOCK, }; #[path = "mock_ctx.rs"] diff --git a/tests/gui_visibility.rs b/tests/gui_visibility.rs index fb9c0634..0b764e2c 100644 --- a/tests/gui_visibility.rs +++ b/tests/gui_visibility.rs @@ -1,7 +1,10 @@ +use eframe::egui; use multi_launcher::hotkey::{Hotkey, HotkeyTrigger}; use multi_launcher::visibility::handle_visibility_trigger; -use std::sync::{Arc, Mutex, atomic::{AtomicBool, Ordering}}; -use eframe::egui; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, +}; #[path = "mock_ctx.rs"] mod mock_ctx; @@ -77,4 +80,3 @@ fn queued_visibility_applies_when_context_available() { _ => panic!("unexpected command"), } } - diff --git a/tests/history.rs b/tests/history.rs index 3caf6fda..59fbe2b3 100644 --- a/tests/history.rs +++ b/tests/history.rs @@ -1,6 +1,6 @@ -use multi_launcher::history::{append_history, get_history, clear_history}; use multi_launcher::actions::Action; use multi_launcher::history::HistoryEntry; +use multi_launcher::history::{append_history, clear_history, get_history}; use tempfile::tempdir; #[test] @@ -8,7 +8,16 @@ fn clear_history_empties_file() { let dir = tempdir().unwrap(); std::env::set_current_dir(dir.path()).unwrap(); - let entry = HistoryEntry { query: "test".into(), query_lc: String::new(), action: Action { label: "l".into(), desc: "".into(), action: "run".into(), args: None } }; + let entry = HistoryEntry { + query: "test".into(), + query_lc: String::new(), + action: Action { + label: "l".into(), + desc: "".into(), + action: "run".into(), + args: None, + }, + }; append_history(entry, 10).unwrap(); assert!(!get_history().is_empty()); @@ -21,8 +30,8 @@ fn clear_history_empties_file() { #[test] fn plugin_clear_action() { - use multi_launcher::plugins::history::HistoryPlugin; use multi_launcher::plugin::Plugin; + use multi_launcher::plugins::history::HistoryPlugin; let plugin = HistoryPlugin; let results = plugin.search("hi clear"); diff --git a/tests/hotkey.rs b/tests/hotkey.rs index 9c831e5a..f84172b7 100644 --- a/tests/hotkey.rs +++ b/tests/hotkey.rs @@ -1,5 +1,5 @@ -use multi_launcher::hotkey::{parse_hotkey, Hotkey, HotkeyTrigger}; use multi_launcher::hotkey::Key; +use multi_launcher::hotkey::{parse_hotkey, Hotkey, HotkeyTrigger}; #[test] fn parse_simple_f_key() { diff --git a/tests/hotkey_events.rs b/tests/hotkey_events.rs index ab867041..4c3d5ff1 100644 --- a/tests/hotkey_events.rs +++ b/tests/hotkey_events.rs @@ -1,7 +1,10 @@ -use multi_launcher::hotkey::{parse_hotkey, HotkeyTrigger, process_test_events}; -use multi_launcher::visibility::handle_visibility_trigger; +use multi_launcher::hotkey::{parse_hotkey, process_test_events, HotkeyTrigger}; use multi_launcher::hotkey::{EventType, Key}; -use std::sync::{Arc, Mutex, atomic::{AtomicBool, Ordering}}; +use multi_launcher::visibility::handle_visibility_trigger; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, +}; #[path = "mock_ctx.rs"] mod mock_ctx; diff --git a/tests/indexer.rs b/tests/indexer.rs index a6ecf5ed..182b5bbf 100644 --- a/tests/indexer.rs +++ b/tests/indexer.rs @@ -23,7 +23,10 @@ fn indexer_indexes_files_recursively() { for path in expected.iter() { let label = path.file_name().unwrap().to_str().unwrap(); let display = path.display().to_string(); - assert!(actions.iter().any(|a| a.label == label && a.action == display && a.desc == display && a.args.is_none())); + assert!(actions.iter().any(|a| a.label == label + && a.action == display + && a.desc == display + && a.args.is_none())); } } @@ -35,4 +38,3 @@ fn indexer_errors_on_missing_path() { let result = multi_launcher::indexer::index_paths(&[missing.to_string_lossy().into_owned()]); assert!(result.is_err()); } - diff --git a/tests/macros_plugin.rs b/tests/macros_plugin.rs index 255de036..27a8e5e6 100644 --- a/tests/macros_plugin.rs +++ b/tests/macros_plugin.rs @@ -85,4 +85,3 @@ fn macros_file_change_reload() { } panic!("macros file did not reload"); } - diff --git a/tests/missing_plugin.rs b/tests/missing_plugin.rs index efb51836..2a448aee 100644 --- a/tests/missing_plugin.rs +++ b/tests/missing_plugin.rs @@ -1,8 +1,8 @@ use multi_launcher::plugin::Plugin; -use multi_launcher::plugins::missing::MissingPlugin; use multi_launcher::plugins::bookmarks::BOOKMARKS_FILE; use multi_launcher::plugins::fav::FAV_FILE; use multi_launcher::plugins::folders::FOLDERS_FILE; +use multi_launcher::plugins::missing::MissingPlugin; use once_cell::sync::Lazy; use std::sync::Mutex; use tempfile::tempdir; diff --git a/tests/note_panel_scroll.rs b/tests/note_panel_scroll.rs index 6ea2a915..8d47cf51 100644 --- a/tests/note_panel_scroll.rs +++ b/tests/note_panel_scroll.rs @@ -74,4 +74,3 @@ fn long_note_panel_respects_max_height() { .expect("window rect"); assert!(rect.height() <= 600.0); } - diff --git a/tests/offscreen.rs b/tests/offscreen.rs index 93ef4f62..770af88c 100644 --- a/tests/offscreen.rs +++ b/tests/offscreen.rs @@ -1,5 +1,5 @@ -use multi_launcher::visibility::apply_visibility; use eframe::egui; +use multi_launcher::visibility::apply_visibility; #[path = "mock_ctx.rs"] mod mock_ctx; @@ -8,7 +8,16 @@ use mock_ctx::MockCtx; #[test] fn offscreen_position_when_hidden() { let ctx = MockCtx::default(); - apply_visibility(false, &ctx, (42.0, 84.0), true, false, None, None, (400.0, 220.0)); + apply_visibility( + false, + &ctx, + (42.0, 84.0), + true, + false, + None, + None, + (400.0, 220.0), + ); let cmds = ctx.commands.lock().unwrap(); assert_eq!(cmds.len(), 1); match cmds[0] { diff --git a/tests/omni_search_plugin.rs b/tests/omni_search_plugin.rs index 04002078..8d2f13dc 100644 --- a/tests/omni_search_plugin.rs +++ b/tests/omni_search_plugin.rs @@ -1,12 +1,12 @@ +use multi_launcher::actions::Action; use multi_launcher::plugin::Plugin; -use multi_launcher::plugins::omni_search::OmniSearchPlugin; -use std::sync::Arc; use multi_launcher::plugins::bookmarks::{save_bookmarks, BookmarkEntry, BOOKMARKS_FILE}; use multi_launcher::plugins::folders::{save_folders, FolderEntry, FOLDERS_FILE}; -use multi_launcher::actions::Action; -use tempfile::tempdir; +use multi_launcher::plugins::omni_search::OmniSearchPlugin; use once_cell::sync::Lazy; +use std::sync::Arc; use std::sync::Mutex; +use tempfile::tempdir; static TEST_MUTEX: Lazy> = Lazy::new(|| Mutex::new(())); @@ -16,10 +16,30 @@ fn o_list_combines_all_sources() { let dir = tempdir().unwrap(); std::env::set_current_dir(dir.path()).unwrap(); - save_bookmarks(BOOKMARKS_FILE, &[BookmarkEntry { url: "https://example.com".into(), alias: None }]).unwrap(); - save_folders(FOLDERS_FILE, &[FolderEntry { label: "Foo".into(), path: "/foo".into(), alias: None }]).unwrap(); - - let actions = Arc::new(vec![Action { label: "myapp".into(), desc: "app".into(), action: "myapp".into(), args: None }]); + save_bookmarks( + BOOKMARKS_FILE, + &[BookmarkEntry { + url: "https://example.com".into(), + alias: None, + }], + ) + .unwrap(); + save_folders( + FOLDERS_FILE, + &[FolderEntry { + label: "Foo".into(), + path: "/foo".into(), + alias: None, + }], + ) + .unwrap(); + + let actions = Arc::new(vec![Action { + label: "myapp".into(), + desc: "app".into(), + action: "myapp".into(), + args: None, + }]); let plugin = OmniSearchPlugin::new(actions); let results = plugin.search("o list"); @@ -35,10 +55,30 @@ fn o_list_filters_results() { let dir = tempdir().unwrap(); std::env::set_current_dir(dir.path()).unwrap(); - save_bookmarks(BOOKMARKS_FILE, &[BookmarkEntry { url: "https://example.com".into(), alias: None }]).unwrap(); - save_folders(FOLDERS_FILE, &[FolderEntry { label: "Foo".into(), path: "/foo".into(), alias: None }]).unwrap(); - - let actions = Arc::new(vec![Action { label: "barapp".into(), desc: "app".into(), action: "bar".into(), args: None }]); + save_bookmarks( + BOOKMARKS_FILE, + &[BookmarkEntry { + url: "https://example.com".into(), + alias: None, + }], + ) + .unwrap(); + save_folders( + FOLDERS_FILE, + &[FolderEntry { + label: "Foo".into(), + path: "/foo".into(), + alias: None, + }], + ) + .unwrap(); + + let actions = Arc::new(vec![Action { + label: "barapp".into(), + desc: "app".into(), + action: "bar".into(), + args: None, + }]); let plugin = OmniSearchPlugin::new(actions); let results = plugin.search("o list bar"); @@ -67,4 +107,3 @@ fn label_and_desc_same_returns_action() { assert!(results.iter().any(|a| a.action == "dup_action")); } - diff --git a/tests/processes_plugin.rs b/tests/processes_plugin.rs index 67fcd44e..89a42814 100644 --- a/tests/processes_plugin.rs +++ b/tests/processes_plugin.rs @@ -5,8 +5,12 @@ use multi_launcher::plugins::processes::ProcessesPlugin; fn prefix_ps_returns_both_actions() { let plugin = ProcessesPlugin; let results = plugin.search("ps"); - assert!(results.iter().any(|a| a.action.starts_with("process:switch:"))); - assert!(results.iter().any(|a| a.action.starts_with("process:kill:"))); + assert!(results + .iter() + .any(|a| a.action.starts_with("process:switch:"))); + assert!(results + .iter() + .any(|a| a.action.starts_with("process:kill:"))); } #[test] @@ -14,8 +18,12 @@ fn prefix_psk_returns_only_kill() { let plugin = ProcessesPlugin; let results = plugin.search("psk"); assert!(!results.is_empty()); - assert!(results.iter().all(|a| a.action.starts_with("process:kill:"))); - assert!(!results.iter().any(|a| a.action.starts_with("process:switch:"))); + assert!(results + .iter() + .all(|a| a.action.starts_with("process:kill:"))); + assert!(!results + .iter() + .any(|a| a.action.starts_with("process:switch:"))); } #[test] @@ -23,6 +31,10 @@ fn prefix_pss_returns_only_switch() { let plugin = ProcessesPlugin; let results = plugin.search("pss"); assert!(!results.is_empty()); - assert!(results.iter().all(|a| a.action.starts_with("process:switch:"))); - assert!(!results.iter().any(|a| a.action.starts_with("process:kill:"))); + assert!(results + .iter() + .all(|a| a.action.starts_with("process:switch:"))); + assert!(!results + .iter() + .any(|a| a.action.starts_with("process:kill:"))); } diff --git a/tests/quit_finished.rs b/tests/quit_finished.rs index 544d7736..3687686f 100644 --- a/tests/quit_finished.rs +++ b/tests/quit_finished.rs @@ -1,8 +1,6 @@ use eframe::egui; use multi_launcher::hotkey::{Hotkey, HotkeyTrigger}; -use std::sync::{ - Arc, Mutex, -}; +use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; diff --git a/tests/quit_hotkey.rs b/tests/quit_hotkey.rs index 1dc11721..5c9d7e26 100644 --- a/tests/quit_hotkey.rs +++ b/tests/quit_hotkey.rs @@ -1,6 +1,9 @@ -use multi_launcher::hotkey::{Hotkey, HotkeyTrigger}; use eframe::egui; -use std::sync::{Arc, Mutex, atomic::{AtomicBool, Ordering}}; +use multi_launcher::hotkey::{Hotkey, HotkeyTrigger}; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, +}; use std::thread; use std::time::Duration; @@ -65,8 +68,7 @@ fn quit_hotkey_exits_main_loop() { let cmds = ctx.commands.lock().unwrap(); assert_eq!(cmds.len(), 1); match cmds[0] { - egui::ViewportCommand::Close => {}, + egui::ViewportCommand::Close => {} _ => panic!("unexpected command"), } } - diff --git a/tests/ranking.rs b/tests/ranking.rs index 6a94ec33..ab49e04b 100644 --- a/tests/ranking.rs +++ b/tests/ranking.rs @@ -1,11 +1,15 @@ +use eframe::egui; +use multi_launcher::actions::Action; use multi_launcher::gui::{LauncherApp, APP_PREFIX}; use multi_launcher::plugin::PluginManager; -use multi_launcher::actions::Action; use multi_launcher::settings::Settings; -use std::sync::{Arc, atomic::AtomicBool}; -use eframe::egui; +use std::sync::{atomic::AtomicBool, Arc}; -fn new_app_with_settings(ctx: &egui::Context, actions: Vec, settings: Settings) -> LauncherApp { +fn new_app_with_settings( + ctx: &egui::Context, + actions: Vec, + settings: Settings, +) -> LauncherApp { let custom_len = actions.len(); LauncherApp::new( ctx, @@ -29,8 +33,18 @@ fn new_app_with_settings(ctx: &egui::Context, actions: Vec, settings: Se fn usage_ranking() { let ctx = egui::Context::default(); let actions = vec![ - Action { label: "foo".into(), desc: "".into(), action: "a".into(), args: None }, - Action { label: "foo".into(), desc: "".into(), action: "b".into(), args: None }, + Action { + label: "foo".into(), + desc: "".into(), + action: "a".into(), + args: None, + }, + Action { + label: "foo".into(), + desc: "".into(), + action: "b".into(), + args: None, + }, ]; let settings = Settings::default(); let mut app = new_app_with_settings(&ctx, actions, settings); @@ -44,8 +58,18 @@ fn usage_ranking() { fn fuzzy_vs_usage_weight() { let ctx = egui::Context::default(); let actions = vec![ - Action { label: "abc".into(), desc: "".into(), action: "a".into(), args: None }, - Action { label: "defabc".into(), desc: "".into(), action: "b".into(), args: None }, + Action { + label: "abc".into(), + desc: "".into(), + action: "a".into(), + args: None, + }, + Action { + label: "defabc".into(), + desc: "".into(), + action: "b".into(), + args: None, + }, ]; let mut settings = Settings::default(); settings.fuzzy_weight = 5.0; diff --git a/tests/recycle_plugin.rs b/tests/recycle_plugin.rs index 16a14110..9dd13ac6 100644 --- a/tests/recycle_plugin.rs +++ b/tests/recycle_plugin.rs @@ -1,3 +1,4 @@ +use eframe::egui; use multi_launcher::plugin::Plugin; use multi_launcher::plugins::recycle::RecyclePlugin; use multi_launcher::{ @@ -7,7 +8,6 @@ use multi_launcher::{ plugin::PluginManager, settings::Settings, }; -use eframe::egui; use std::sync::{atomic::AtomicBool, Arc}; fn new_app(ctx: &egui::Context, actions: Vec) -> LauncherApp { @@ -67,9 +67,7 @@ fn command_returns_immediately_and_cleans() { assert!(start.elapsed() < std::time::Duration::from_millis(100)); let start_wait = std::time::Instant::now(); loop { - let remaining = match std::time::Duration::from_secs(3) - .checked_sub(start_wait.elapsed()) - { + let remaining = match std::time::Duration::from_secs(3).checked_sub(start_wait.elapsed()) { Some(dur) => dur, None => panic!("unexpected event"), }; diff --git a/tests/resilience.rs b/tests/resilience.rs index 4d5b9c35..b673ba11 100644 --- a/tests/resilience.rs +++ b/tests/resilience.rs @@ -1,8 +1,10 @@ +use multi_launcher::actions::Action; +use multi_launcher::history::{ + append_history, clear_history, get_history, poison_history_lock, HistoryEntry, +}; use multi_launcher::plugin::Plugin; use multi_launcher::plugins::folders::{FoldersPlugin, FOLDERS_FILE}; use multi_launcher::plugins::timer::{active_timers, ACTIVE_TIMERS}; -use multi_launcher::history::{append_history, clear_history, get_history, poison_history_lock, HistoryEntry}; -use multi_launcher::actions::Action; use once_cell::sync::Lazy; use std::sync::Mutex; use tempfile::tempdir; diff --git a/tests/runescape_plugin.rs b/tests/runescape_plugin.rs index 25f7cfbf..dbf03d8d 100644 --- a/tests/runescape_plugin.rs +++ b/tests/runescape_plugin.rs @@ -17,5 +17,8 @@ fn osrs_search_returns_action() { let plugin = RunescapeSearchPlugin; let results = plugin.search("osrs agility"); assert_eq!(results.len(), 1); - assert_eq!(results[0].action, "https://oldschool.runescape.wiki/?search=agility"); + assert_eq!( + results[0].action, + "https://oldschool.runescape.wiki/?search=agility" + ); } diff --git a/tests/screenshot_plugin.rs b/tests/screenshot_plugin.rs index 8a2f33d1..e703366e 100644 --- a/tests/screenshot_plugin.rs +++ b/tests/screenshot_plugin.rs @@ -15,6 +15,10 @@ fn search_lists_screenshot_actions() { "screenshot:desktop_clip", ]; for prefix in prefixes.iter() { - assert!(results.iter().any(|a| a.action == *prefix), "missing action {}", prefix); + assert!( + results.iter().any(|a| a.action == *prefix), + "missing action {}", + prefix + ); } } diff --git a/tests/settings_plugin.rs b/tests/settings_plugin.rs index 99389e97..d3fd6437 100644 --- a/tests/settings_plugin.rs +++ b/tests/settings_plugin.rs @@ -1,8 +1,10 @@ +use eframe::egui; use multi_launcher::plugin::Plugin; use multi_launcher::plugins::settings::SettingsPlugin; -use multi_launcher::{actions::Action, gui::LauncherApp, plugin::PluginManager, settings::Settings}; -use std::sync::{Arc, atomic::AtomicBool}; -use eframe::egui; +use multi_launcher::{ + actions::Action, gui::LauncherApp, plugin::PluginManager, settings::Settings, +}; +use std::sync::{atomic::AtomicBool, Arc}; fn new_app(ctx: &egui::Context, actions: Vec) -> LauncherApp { let custom_len = actions.len(); @@ -46,7 +48,11 @@ fn search_settings_opens_panel() { let mut app = new_app(&ctx, actions); app.query = "settings".into(); app.search(); - let idx = app.results.iter().position(|a| a.action == "settings:dialog").unwrap(); + let idx = app + .results + .iter() + .position(|a| a.action == "settings:dialog") + .unwrap(); app.selected = Some(idx); let launch_idx = app.handle_key(egui::Key::Enter); assert_eq!(launch_idx, Some(idx)); @@ -58,4 +64,3 @@ fn search_settings_opens_panel() { } assert!(app.show_settings); } - diff --git a/tests/system_plugin.rs b/tests/system_plugin.rs index 1987cdab..429de6be 100644 --- a/tests/system_plugin.rs +++ b/tests/system_plugin.rs @@ -1,5 +1,5 @@ -use multi_launcher::plugins::system::SystemPlugin; use multi_launcher::plugin::Plugin; +use multi_launcher::plugins::system::SystemPlugin; #[test] fn search_shutdown_returns_action() { diff --git a/tests/text_case_plugin.rs b/tests/text_case_plugin.rs index ff54fcd5..72c962f2 100644 --- a/tests/text_case_plugin.rs +++ b/tests/text_case_plugin.rs @@ -45,4 +45,3 @@ fn converts_specific_cases() { assert_eq!(bin.len(), 1); assert_eq!(bin[0].label, "01000001"); } - diff --git a/tests/timer_plugin.rs b/tests/timer_plugin.rs index 376b4e7d..e272ee6d 100644 --- a/tests/timer_plugin.rs +++ b/tests/timer_plugin.rs @@ -52,18 +52,21 @@ fn search_cancel_lists_timers() { // manually insert an active timer { let mut list = ACTIVE_TIMERS.lock().unwrap(); - list.insert(1, TimerEntry { - id: 1, - label: "test".into(), - deadline: Instant::now() + Duration::from_secs(10), - persist: false, - end_ts: 0, - start_ts: 0, - paused: false, - remaining: Duration::from_secs(10), - generation: 0, - sound: "None".into(), - }); + list.insert( + 1, + TimerEntry { + id: 1, + label: "test".into(), + deadline: Instant::now() + Duration::from_secs(10), + persist: false, + end_ts: 0, + start_ts: 0, + paused: false, + remaining: Duration::from_secs(10), + generation: 0, + sound: "None".into(), + }, + ); } let plugin = TimerPlugin; let results = plugin.search("timer cancel"); @@ -79,18 +82,21 @@ fn search_list_lists_timers() { let _lock = TEST_MUTEX.lock().unwrap(); { let mut list = ACTIVE_TIMERS.lock().unwrap(); - list.insert(2, TimerEntry { - id: 2, - label: "demo".into(), - deadline: Instant::now() + Duration::from_secs(20), - persist: false, - end_ts: 0, - start_ts: 0, - paused: false, - remaining: Duration::from_secs(20), - generation: 0, - sound: "None".into(), - }); + list.insert( + 2, + TimerEntry { + id: 2, + label: "demo".into(), + deadline: Instant::now() + Duration::from_secs(20), + persist: false, + end_ts: 0, + start_ts: 0, + paused: false, + remaining: Duration::from_secs(20), + generation: 0, + sound: "None".into(), + }, + ); } let plugin = TimerPlugin; let results = plugin.search("timer list"); @@ -103,18 +109,21 @@ fn search_rm_lists_timers() { let _lock = TEST_MUTEX.lock().unwrap(); { let mut list = ACTIVE_TIMERS.lock().unwrap(); - list.insert(3, TimerEntry { - id: 3, - label: "remove".into(), - deadline: Instant::now() + Duration::from_secs(30), - persist: false, - end_ts: 0, - start_ts: 0, - paused: false, - remaining: Duration::from_secs(30), - generation: 0, - sound: "None".into(), - }); + list.insert( + 3, + TimerEntry { + id: 3, + label: "remove".into(), + deadline: Instant::now() + Duration::from_secs(30), + persist: false, + end_ts: 0, + start_ts: 0, + paused: false, + remaining: Duration::from_secs(30), + generation: 0, + sound: "None".into(), + }, + ); } let plugin = TimerPlugin; let results = plugin.search("timer rm"); @@ -138,18 +147,21 @@ fn search_pause_lists_running_timers() { let _lock = TEST_MUTEX.lock().unwrap(); { let mut list = ACTIVE_TIMERS.lock().unwrap(); - list.insert(11, TimerEntry { - id: 11, - label: "run".into(), - deadline: Instant::now() + Duration::from_secs(5), - persist: false, - end_ts: 0, - start_ts: 0, - paused: false, - remaining: Duration::from_secs(5), - generation: 0, - sound: "None".into(), - }); + list.insert( + 11, + TimerEntry { + id: 11, + label: "run".into(), + deadline: Instant::now() + Duration::from_secs(5), + persist: false, + end_ts: 0, + start_ts: 0, + paused: false, + remaining: Duration::from_secs(5), + generation: 0, + sound: "None".into(), + }, + ); } let plugin = TimerPlugin; let results = plugin.search("timer pause"); @@ -171,18 +183,21 @@ fn search_resume_lists_paused_timers() { let _lock = TEST_MUTEX.lock().unwrap(); { let mut list = ACTIVE_TIMERS.lock().unwrap(); - list.insert(12, TimerEntry { - id: 12, - label: "pause".into(), - deadline: Instant::now() + Duration::from_secs(5), - persist: false, - end_ts: 0, - start_ts: 0, - paused: true, - remaining: Duration::from_secs(5), - generation: 0, - sound: "None".into(), - }); + list.insert( + 12, + TimerEntry { + id: 12, + label: "pause".into(), + deadline: Instant::now() + Duration::from_secs(5), + persist: false, + end_ts: 0, + start_ts: 0, + paused: true, + remaining: Duration::from_secs(5), + generation: 0, + sound: "None".into(), + }, + ); } let plugin = TimerPlugin; let results = plugin.search("timer resume"); @@ -255,8 +270,8 @@ fn search_timer_hms_format() { #[test] fn format_ts_invalid_timestamp() { - use multi_launcher::plugins::timer::format_ts; use chrono::{Local, TimeZone}; + use multi_launcher::plugins::timer::format_ts; let invalid_ts = 10_000_000_000_000u64; let expected = Local .timestamp_opt(0, 0) @@ -276,7 +291,13 @@ fn pause_resume_does_not_grow_heap() { ACTIVE_TIMERS.lock().unwrap().clear(); start_timer(Duration::from_secs(3600), "None".into()); - let id = ACTIVE_TIMERS.lock().unwrap().keys().cloned().next().unwrap(); + let id = ACTIVE_TIMERS + .lock() + .unwrap() + .keys() + .cloned() + .next() + .unwrap(); assert_eq!(heap_len(), 1); for _ in 0..5 { @@ -292,8 +313,8 @@ fn pause_resume_does_not_grow_heap() { #[test] fn parse_hhmm_with_day_offset() { + use chrono::{Duration as ChronoDuration, Local}; use multi_launcher::plugins::timer::parse_hhmm; - use chrono::{Local, Duration as ChronoDuration}; let (h, m, date) = parse_hhmm("2d 07:30").unwrap(); assert_eq!((h, m), (7, 30)); let expected = Local::now().date_naive() + ChronoDuration::days(2); @@ -302,8 +323,8 @@ fn parse_hhmm_with_day_offset() { #[test] fn parse_hhmm_with_absolute_date() { - use multi_launcher::plugins::timer::parse_hhmm; use chrono::NaiveDate; + use multi_launcher::plugins::timer::parse_hhmm; let (h, m, date) = parse_hhmm("2024-01-31 05:15").unwrap(); assert_eq!((h, m), (5, 15)); assert_eq!(date.unwrap(), NaiveDate::from_ymd_opt(2024, 1, 31).unwrap()); @@ -318,10 +339,8 @@ fn parse_hhmm_large_day_offset() { #[test] fn start_alarm_multi_day() { - use multi_launcher::plugins::timer::{ - cancel_timer, start_alarm_named, ACTIVE_TIMERS, - }; - use chrono::{Local, Duration as ChronoDuration, Timelike}; + use chrono::{Duration as ChronoDuration, Local, Timelike}; + use multi_launcher::plugins::timer::{cancel_timer, start_alarm_named, ACTIVE_TIMERS}; let _lock = TEST_MUTEX.lock().unwrap(); ACTIVE_TIMERS.lock().unwrap().clear(); @@ -335,10 +354,16 @@ fn start_alarm_multi_day() { let list = ACTIVE_TIMERS.lock().unwrap(); assert_eq!(list.len(), 1); let t = list.values().next().unwrap(); - (t.id, t.deadline.saturating_duration_since(std::time::Instant::now()).as_secs()) + ( + t.id, + t.deadline + .saturating_duration_since(std::time::Instant::now()) + .as_secs(), + ) }; - let expected = (date.and_hms_opt(hour, minute, 0).unwrap() - now.naive_local()).num_seconds() as u64; + let expected = + (date.and_hms_opt(hour, minute, 0).unwrap() - now.naive_local()).num_seconds() as u64; assert!((remaining as i64 - expected as i64).abs() <= 2); cancel_timer(id); } diff --git a/tests/timestamp_plugin.rs b/tests/timestamp_plugin.rs index 2f067041..2d3a8642 100644 --- a/tests/timestamp_plugin.rs +++ b/tests/timestamp_plugin.rs @@ -1,6 +1,6 @@ +use chrono::{Local, TimeZone}; use multi_launcher::plugin::Plugin; use multi_launcher::plugins::timestamp::TimestampPlugin; -use chrono::{Local, TimeZone}; #[test] fn unix_to_date() { @@ -21,9 +21,7 @@ fn unix_to_date() { fn date_to_unix() { let plugin = TimestampPlugin; let query = "ts 2024-05-01 12:00"; - let dt = Local - .with_ymd_and_hms(2024, 5, 1, 12, 0, 0) - .unwrap(); + let dt = Local.with_ymd_and_hms(2024, 5, 1, 12, 0, 0).unwrap(); let ts = dt.timestamp().to_string(); let results = plugin.search(query); assert_eq!(results.len(), 1); @@ -61,9 +59,7 @@ fn time_with_ms_to_ms() { #[test] fn unix_ms_to_date() { let plugin = TimestampPlugin; - let dt = Local - .with_ymd_and_hms(2024, 5, 1, 12, 0, 0) - .unwrap(); + let dt = Local.with_ymd_and_hms(2024, 5, 1, 12, 0, 0).unwrap(); let ts_ms = dt.timestamp_millis(); let query = format!("ts {ts_ms}"); let results = plugin.search(&query); diff --git a/tests/tmp_rm_refresh.rs b/tests/tmp_rm_refresh.rs index ef103e6f..822492ca 100644 --- a/tests/tmp_rm_refresh.rs +++ b/tests/tmp_rm_refresh.rs @@ -1,7 +1,11 @@ use eframe::egui; use multi_launcher::{ - actions::Action, gui::LauncherApp, launcher::launch_action, plugin::PluginManager, - plugins::tempfile::{clear_files, create_file}, settings::Settings, + actions::Action, + gui::LauncherApp, + launcher::launch_action, + plugin::PluginManager, + plugins::tempfile::{clear_files, create_file}, + settings::Settings, }; use once_cell::sync::Lazy; use std::sync::{atomic::AtomicBool, Arc, Mutex}; @@ -75,9 +79,6 @@ fn tmp_rm_refreshes_results() { app.search(); } - assert!(!app - .results - .iter() - .any(|a| a.action == remove_action)); + assert!(!app.results.iter().any(|a| a.action == remove_action)); assert!(!file.exists()); } diff --git a/tests/todo_dialog.rs b/tests/todo_dialog.rs index 5c0ba168..69fe53fc 100644 --- a/tests/todo_dialog.rs +++ b/tests/todo_dialog.rs @@ -4,8 +4,18 @@ use multi_launcher::plugins::todo::TodoEntry; #[test] fn filter_by_text() { let entries = vec![ - TodoEntry { text: "alpha".into(), done: false, priority: 0, tags: vec![] }, - TodoEntry { text: "beta".into(), done: false, priority: 0, tags: vec!["x".into()] }, + TodoEntry { + text: "alpha".into(), + done: false, + priority: 0, + tags: vec![], + }, + TodoEntry { + text: "beta".into(), + done: false, + priority: 0, + tags: vec!["x".into()], + }, ]; let idx = TodoDialog::filtered_indices(&entries, "beta"); assert_eq!(idx, vec![1]); @@ -14,8 +24,18 @@ fn filter_by_text() { #[test] fn filter_by_tag() { let entries = vec![ - TodoEntry { text: "alpha".into(), done: false, priority: 0, tags: vec!["rs3".into()] }, - TodoEntry { text: "beta".into(), done: false, priority: 0, tags: vec!["other".into()] }, + TodoEntry { + text: "alpha".into(), + done: false, + priority: 0, + tags: vec!["rs3".into()], + }, + TodoEntry { + text: "beta".into(), + done: false, + priority: 0, + tags: vec!["other".into()], + }, ]; let idx = TodoDialog::filtered_indices(&entries, "#rs3"); assert_eq!(idx, vec![0]); @@ -24,8 +44,18 @@ fn filter_by_tag() { #[test] fn empty_filter_returns_all() { let entries = vec![ - TodoEntry { text: "one".into(), done: false, priority: 0, tags: vec![] }, - TodoEntry { text: "two".into(), done: false, priority: 0, tags: vec![] }, + TodoEntry { + text: "one".into(), + done: false, + priority: 0, + tags: vec![], + }, + TodoEntry { + text: "two".into(), + done: false, + priority: 0, + tags: vec![], + }, ]; let idx = TodoDialog::filtered_indices(&entries, ""); assert_eq!(idx, vec![0, 1]); @@ -64,4 +94,3 @@ fn add_todo_ignores_trailing_comma() { ] ); } - diff --git a/tests/trigger_visibility.rs b/tests/trigger_visibility.rs index f8f33a4b..2b7f6f5f 100644 --- a/tests/trigger_visibility.rs +++ b/tests/trigger_visibility.rs @@ -1,7 +1,10 @@ +use eframe::egui; use multi_launcher::hotkey::{Hotkey, HotkeyTrigger}; use multi_launcher::visibility::handle_visibility_trigger; -use std::sync::{Arc, Mutex, atomic::{AtomicBool, Ordering}}; -use eframe::egui; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, +}; #[path = "mock_ctx.rs"] mod mock_ctx; diff --git a/tests/usage.rs b/tests/usage.rs index b80db39b..ce35db3b 100644 --- a/tests/usage.rs +++ b/tests/usage.rs @@ -1,4 +1,4 @@ -use multi_launcher::usage::{save_usage, load_usage}; +use multi_launcher::usage::{load_usage, save_usage}; use std::collections::HashMap; use tempfile::tempdir; diff --git a/tests/weather_plugin.rs b/tests/weather_plugin.rs index cf35dac3..f5a4115e 100644 --- a/tests/weather_plugin.rs +++ b/tests/weather_plugin.rs @@ -6,5 +6,8 @@ fn search_returns_action() { let plugin = WeatherPlugin; let results = plugin.search("weather Berlin"); assert_eq!(results.len(), 1); - assert_eq!(results[0].action, "https://www.weather.com/weather/today/l/Berlin"); + assert_eq!( + results[0].action, + "https://www.weather.com/weather/today/l/Berlin" + ); } diff --git a/tests/wikipedia_plugin.rs b/tests/wikipedia_plugin.rs index f104222b..cd711563 100644 --- a/tests/wikipedia_plugin.rs +++ b/tests/wikipedia_plugin.rs @@ -6,5 +6,8 @@ fn search_returns_action() { let plugin = WikipediaPlugin; let results = plugin.search("wiki space"); assert_eq!(results.len(), 1); - assert_eq!(results[0].action, "https://en.wikipedia.org/wiki/Special:Search?search=space"); + assert_eq!( + results[0].action, + "https://en.wikipedia.org/wiki/Special:Search?search=space" + ); } diff --git a/tests/window_manager.rs b/tests/window_manager.rs index ae6b177c..47bbc8ca 100644 --- a/tests/window_manager.rs +++ b/tests/window_manager.rs @@ -1,9 +1,6 @@ use multi_launcher::window_manager::{ - set_mock_mouse_position, - clear_mock_mouse_position, - current_mouse_position, - virtual_key_from_string, - MOCK_MOUSE_LOCK, + clear_mock_mouse_position, current_mouse_position, set_mock_mouse_position, + virtual_key_from_string, MOCK_MOUSE_LOCK, }; #[test] @@ -34,4 +31,3 @@ fn virtual_key_from_string_cases() { assert_eq!(virtual_key_from_string(input), expected, "input: {input}"); } } - diff --git a/tests/windows_plugin.rs b/tests/windows_plugin.rs index d937b425..9d75ce49 100644 --- a/tests/windows_plugin.rs +++ b/tests/windows_plugin.rs @@ -5,6 +5,10 @@ use multi_launcher::plugins::windows::WindowsPlugin; fn search_lists_windows() { let plugin = WindowsPlugin; let results = plugin.search("win"); - assert!(results.iter().any(|a| a.action.starts_with("window:switch:"))); - assert!(results.iter().any(|a| a.action.starts_with("window:close:"))); + assert!(results + .iter() + .any(|a| a.action.starts_with("window:switch:"))); + assert!(results + .iter() + .any(|a| a.action.starts_with("window:close:"))); } From dd010e7dcab53e00ec926b96e77a90e8f2fe638e Mon Sep 17 00:00:00 2001 From: multiplex55 <6619098+multiplex55@users.noreply.github.com> Date: Wed, 31 Dec 2025 20:50:37 -0500 Subject: [PATCH 3/3] Fix newline scanning without reverse iterators --- src/gui/note_panel.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/gui/note_panel.rs b/src/gui/note_panel.rs index 8b7fe65c..9692f289 100644 --- a/src/gui/note_panel.rs +++ b/src/gui/note_panel.rs @@ -1119,12 +1119,16 @@ fn byte_to_char_index(text: &str, byte_idx: usize) -> usize { } fn prev_newline_char_idx(text: &str, before: usize) -> Option { - text.chars() - .take(before) - .enumerate() - .rev() - .find(|(_, c)| *c == '\n') - .map(|(i, _)| i) + let mut last_newline = None; + for (i, c) in text.chars().enumerate() { + if i >= before { + break; + } + if c == '\n' { + last_newline = Some(i); + } + } + last_newline } fn next_newline_char_idx(text: &str, from: usize) -> Option {