@@ -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+
100111pub 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