Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 52 additions & 6 deletions src/gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ use std::sync::{
};
use std::time::Instant;

const SUBCOMMANDS: &[&str] = &[
"add", "rm", "list", "clear", "open", "new", "alias", "set", "pause", "resume", "cancel",
"edit", "ma",
];

fn scale_ui<R>(ui: &mut egui::Ui, scale: f32, add_contents: impl FnOnce(&mut egui::Ui) -> R) -> R {
ui.scope(|ui| {
if (scale - 1.0).abs() > f32::EPSILON {
Expand Down Expand Up @@ -454,9 +459,26 @@ impl LauncherApp {
Some(&filter),
self.enabled_capabilities.as_ref(),
);
let query_term = trimmed_lc.splitn(2, ' ').nth(1).unwrap_or("").to_string();
for a in plugin_results {
if self.fuzzy_weight <= 0.0 {
res.push((a, 0.0));
if query_term.is_empty() {
res.push((a, 0.0));
} else {
let alias_match = self
.folder_aliases
.get(&a.action)
.or_else(|| self.bookmark_aliases.get(&a.action))
.and_then(|v| v.as_ref())
.map(|s| s.to_lowercase().contains(&query_term))
.unwrap_or(false);
let label_match = a.label.to_lowercase().contains(&query_term);
let desc_match = a.desc.to_lowercase().contains(&query_term);
if label_match || desc_match || alias_match {
let score = if alias_match { 1.0 } else { 0.0 };
res.push((a, score));
}
}
} else {
let score = if self.query.is_empty() {
0.0
Expand Down Expand Up @@ -504,9 +526,35 @@ impl LauncherApp {
self.enabled_plugins.as_ref(),
self.enabled_capabilities.as_ref(),
);
let tail = trimmed_lc.splitn(2, " ").nth(1).unwrap_or("");
let mut query_term = tail.splitn(3, " ").nth(1).unwrap_or("").to_string();
if query_term.is_empty() {
let parts: Vec<&str> = tail.split_whitespace().collect();
if parts.len() == 1 && !SUBCOMMANDS.contains(&parts[0]) {
query_term = parts[0].to_string();
} else if parts.len() > 1 {
query_term = parts[1..].join(" ");
}
}
for a in plugin_results {
if self.fuzzy_weight <= 0.0 {
res.push((a, 0.0));
if query_term.is_empty() {
res.push((a, 0.0));
} else {
let alias_match = self
.folder_aliases
.get(&a.action)
.or_else(|| self.bookmark_aliases.get(&a.action))
.and_then(|v| v.as_ref())
.map(|s| s.to_lowercase().contains(&query_term))
.unwrap_or(false);
let label_match = a.label.to_lowercase().contains(&query_term);
let desc_match = a.desc.to_lowercase().contains(&query_term);
if label_match || desc_match || alias_match {
let score = if alias_match { 1.0 } else { 0.0 };
res.push((a, score));
}
}
} else {
let score = if self.query.is_empty() {
0.0
Expand Down Expand Up @@ -772,10 +820,8 @@ impl eframe::App for LauncherApp {
}

scale_ui(ui, self.query_scale, |ui| {
let input = ui.add(
egui::TextEdit::singleline(&mut self.query)
.desired_width(f32::INFINITY),
);
let input = ui
.add(egui::TextEdit::singleline(&mut self.query).desired_width(f32::INFINITY));
if just_became_visible || self.focus_query {
input.request_focus();
self.focus_query = false;
Expand Down
95 changes: 95 additions & 0 deletions tests/plugin_exact_match.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use eframe::egui;
use multi_launcher::gui::LauncherApp;
use multi_launcher::plugin::PluginManager;
use multi_launcher::plugins::bookmarks::{save_bookmarks, BookmarkEntry, BOOKMARKS_FILE};
use multi_launcher::plugins::snippets::{save_snippets, SnippetEntry, SNIPPETS_FILE};
use multi_launcher::settings::Settings;
use once_cell::sync::Lazy;
use std::sync::{atomic::AtomicBool, Arc, Mutex};
use tempfile::tempdir;

static TEST_MUTEX: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));

fn new_app(ctx: &egui::Context, settings: Settings) -> LauncherApp {
let custom_len = 0;
let mut plugins = PluginManager::new();
let dirs: Vec<String> = Vec::new();
plugins.reload_from_dirs(&dirs, Settings::default().clipboard_limit, false);
LauncherApp::new(
ctx,
Vec::new(),
custom_len,
plugins,
"actions.json".into(),
"settings.json".into(),
settings,
None,
None,
None,
None,
Arc::new(AtomicBool::new(false)),
Arc::new(AtomicBool::new(false)),
Arc::new(AtomicBool::new(false)),
)
}

#[test]
fn plugin_query_is_exact_when_fuzzy_disabled() {
let _lock = TEST_MUTEX.lock().unwrap();
let dir = tempdir().unwrap();
std::env::set_current_dir(dir.path()).unwrap();

let entries = vec![BookmarkEntry {
url: "https://example.com".into(),
alias: Some("foobar".into()),
}];
save_bookmarks(BOOKMARKS_FILE, &entries).unwrap();

let mut settings = Settings::default();
settings.fuzzy_weight = 0.0;
let ctx = egui::Context::default();
let mut app = new_app(&ctx, settings);

app.query = "bm foobar".into();
app.search();
assert_eq!(app.results.len(), 1);

app.query = "bm fbr".into();
app.search();
assert_eq!(app.results.len(), 0);
}

#[test]
fn plugin_command_unfiltered_when_no_query() {
let _lock = TEST_MUTEX.lock().unwrap();
let dir = tempdir().unwrap();
std::env::set_current_dir(dir.path()).unwrap();
let entries = vec![BookmarkEntry {
url: "https://example.com".into(),
alias: None,
}];
save_bookmarks(BOOKMARKS_FILE, &entries).unwrap();
let mut settings = Settings::default();
settings.fuzzy_weight = 0.0;
let ctx = egui::Context::default();
let mut app = new_app(&ctx, settings);
app.query = "bm list".into();
app.search();
assert_eq!(app.results.len(), 1);
}

#[test]
fn snippet_edit_command_unfiltered() {
let _lock = TEST_MUTEX.lock().unwrap();
let dir = tempdir().unwrap();
std::env::set_current_dir(dir.path()).unwrap();
let entries = vec![SnippetEntry { alias: "foo".into(), text: "bar".into() }];
save_snippets(SNIPPETS_FILE, &entries).unwrap();
let mut settings = Settings::default();
settings.fuzzy_weight = 0.0;
let ctx = egui::Context::default();
let mut app = new_app(&ctx, settings);
app.query = "cs edit".into();
app.search();
assert_eq!(app.results.len(), 1);
}