Skip to content

Commit fa2579b

Browse files
authored
Merge pull request #807 from multiplex55/codex/update-safe-join-for-command-and-args
Safely normalize plugin query command/args joining for favorites
2 parents b44f52b + 0888a7b commit fa2579b

2 files changed

Lines changed: 81 additions & 9 deletions

File tree

src/gui/fav_dialog.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::gui::LauncherApp;
2-
use crate::plugins::fav::{load_favs, resolve_with_plugin, save_favs, FavEntry, FAV_FILE};
2+
use crate::plugins::fav::{
3+
join_command_args, load_favs, resolve_with_plugin, save_favs, FavEntry, FAV_FILE,
4+
};
35
use eframe::egui;
46

57
#[derive(Default)]
@@ -129,10 +131,7 @@ impl FavDialog {
129131
Some(self.args.clone())
130132
};
131133
if let Some(q) = cmd.strip_prefix("query:") {
132-
let mut q = q.to_string();
133-
if let Some(ref a) = args {
134-
q.push_str(a);
135-
}
134+
let q = join_command_args(q, args.as_deref());
136135
if let Some(res) = plugin.search(&q).into_iter().next()
137136
{
138137
cmd = res.action;

src/plugins/fav.rs

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,28 @@ pub fn resolve_with_plugin(
8686
command: &str,
8787
args: Option<&str>,
8888
) -> (String, Option<String>) {
89-
let mut query = command.to_string();
90-
if let Some(a) = args {
91-
query.push_str(a);
92-
}
89+
let query = join_command_args(command, args);
9390
if let Some(res) = plugin.search(&query).into_iter().next() {
9491
(res.action, res.args)
9592
} else {
9693
(command.to_string(), args.map(|s| s.to_string()))
9794
}
9895
}
9996

97+
pub fn join_command_args(command: &str, args: Option<&str>) -> String {
98+
let command = command.trim_end();
99+
let Some(args) = args else {
100+
return command.to_string();
101+
};
102+
103+
let args = args.trim_start();
104+
if args.is_empty() {
105+
command.to_string()
106+
} else {
107+
format!("{command} {args}")
108+
}
109+
}
110+
100111
pub fn run_fav(label: &str) -> anyhow::Result<()> {
101112
let list = load_favs(FAV_FILE).unwrap_or_default();
102113
if let Some(entry) = list.iter().find(|e| e.label.eq_ignore_ascii_case(label)) {
@@ -263,3 +274,65 @@ impl Plugin for FavPlugin {
263274
]
264275
}
265276
}
277+
278+
#[cfg(test)]
279+
mod tests {
280+
use super::{join_command_args, resolve_with_plugin};
281+
use crate::{actions::Action, plugin::Plugin};
282+
283+
struct TestPlugin;
284+
285+
impl Plugin for TestPlugin {
286+
fn search(&self, query: &str) -> Vec<Action> {
287+
vec![Action {
288+
label: query.to_string(),
289+
desc: String::new(),
290+
action: query.to_string(),
291+
args: None,
292+
}]
293+
}
294+
295+
fn name(&self) -> &str {
296+
"test"
297+
}
298+
299+
fn description(&self) -> &str {
300+
"test plugin"
301+
}
302+
303+
fn capabilities(&self) -> &[&str] {
304+
&["search"]
305+
}
306+
307+
fn commands(&self) -> Vec<Action> {
308+
Vec::new()
309+
}
310+
}
311+
312+
#[test]
313+
fn join_command_and_args_for_tokenized_query() {
314+
assert_eq!(join_command_args("todo", Some("list")), "todo list");
315+
}
316+
317+
#[test]
318+
fn join_keeps_command_when_args_empty() {
319+
assert_eq!(join_command_args("todo", None), "todo");
320+
assert_eq!(join_command_args("todo", Some(" ")), "todo");
321+
}
322+
323+
#[test]
324+
fn join_normalizes_whitespace_between_command_and_args() {
325+
assert_eq!(
326+
join_command_args("todo ", Some(" list now")),
327+
"todo list now"
328+
);
329+
}
330+
331+
#[test]
332+
fn resolve_with_plugin_uses_safe_joined_query() {
333+
let plugin = TestPlugin;
334+
let (action, args) = resolve_with_plugin(&plugin, "todo ", Some(" list"));
335+
assert_eq!(action, "todo list");
336+
assert!(args.is_none());
337+
}
338+
}

0 commit comments

Comments
 (0)