@@ -10,7 +10,7 @@ use helix_core::{
1010 movement::{self, Direction},
1111 object, pos_at_coords,
1212 regex::{self, Regex, RegexBuilder},
13− search, selection, surround, textobject,
13+ search, selection, shellwords, surround, textobject,
1414 unicode::width::UnicodeWidthChar,
1515 LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril,
1616 Transaction,
@@ -173,14 +173,14 @@ impl MappableCommand {
173173 pub fn execute(&self, cx: &mut Context) {
174174 match &self {
175175 MappableCommand::Typable { name, args, doc: _ } => {
176− let args: Vec<&str> = args.iter().map(|arg| arg.as_str()).collect();
176+ let args: Vec<Cow<str>> = args.iter().map(Cow::from).collect();
177177 if let Some(command) = cmd::TYPABLE_COMMAND_MAP.get(name.as_str()) {
178178 let mut cx = compositor::Context {
179179 editor: cx.editor,
180180 jobs: cx.jobs,
181181 scroll: None,
182182 };
183− if let Err(e) = (command.fun)(&mut cx, &args, PromptEvent::Validate) {
183+ if let Err(e) = (command.fun)(&mut cx, &args[..], PromptEvent::Validate) {
184184 cx.editor.set_error(format!("{}", e));
185185 }
186186 }
@@ -1963,13 +1963,13 @@ pub mod cmd {
19631963 pub aliases: &'static [&'static str],
19641964 pub doc: &'static str,
19651965 // params, flags, helper, completer
1966− pub fun: fn(&mut compositor::Context, &[&str], PromptEvent) -> anyhow::Result<()>,
1966+ pub fun: fn(&mut compositor::Context, &[Cow<str>], PromptEvent) -> anyhow::Result<()>,
19671967 pub completer: Option<Completer>,
19681968 }
19691969
19701970 fn quit(
19711971 cx: &mut compositor::Context,
1972− _args: &[&str],
1972+ _args: &[Cow<str>],
19731973 _event: PromptEvent,
19741974 ) -> anyhow::Result<()> {
19751975 // last view and we have unsaved changes
@@ -1984,7 +1984,7 @@ pub mod cmd {
19841984
19851985 fn force_quit(
19861986 cx: &mut compositor::Context,
1987− _args: &[&str],
1987+ _args: &[Cow<str>],
19881988 _event: PromptEvent,
19891989 ) -> anyhow::Result<()> {
19901990 cx.editor.close(view!(cx.editor).id);
@@ -1994,17 +1994,19 @@ pub mod cmd {
19941994
19951995 fn open(
19961996 cx: &mut compositor::Context,
1997− args: &[&str],
1997+ args: &[Cow<str>],
19981998 _event: PromptEvent,
19991999 ) -> anyhow::Result<()> {
2000− let path = args.get(0).context("wrong argument count")?;
2001− let _ = cx.editor.open(path.into(), Action::Replace)?;
2000+ ensure!(!args.is_empty(), "wrong argument count");
2001+ for arg in args {
2002+ let _ = cx.editor.open(arg.as_ref().into(), Action::Replace)?;
2003+ }
20022004 Ok(())
20032005 }
20042006
20052007 fn buffer_close(
20062008 cx: &mut compositor::Context,
2007− _args: &[&str],
2009+ _args: &[Cow<str>],
20082010 _event: PromptEvent,
20092011 ) -> anyhow::Result<()> {
20102012 let view = view!(cx.editor);
@@ -2015,7 +2017,7 @@ pub mod cmd {
20152017
20162018 fn force_buffer_close(
20172019 cx: &mut compositor::Context,
2018− _args: &[&str],
2020+ _args: &[Cow<str>],
20192021 _event: PromptEvent,
20202022 ) -> anyhow::Result<()> {
20212023 let view = view!(cx.editor);
@@ -2024,15 +2026,12 @@ pub mod cmd {
20242026 Ok(())
20252027 }
20262028
2027− fn write_impl<P: AsRef<Path>>(
2028− cx: &mut compositor::Context,
2029− path: Option<P>,
2030− ) -> anyhow::Result<()> {
2029+ fn write_impl(cx: &mut compositor::Context, path: Option<&Cow<str>>) -> anyhow::Result<()> {
20312030 let jobs = &mut cx.jobs;
20322031 let (_, doc) = current!(cx.editor);
20332032
20342033 if let Some(ref path) = path {
2035− doc.set_path(Some(path.as_ref()))
2034+ doc.set_path(Some(path.as_ref().as_ref()))
20362035 .context("invalid filepath")?;
20372036 }
20382037 if doc.path().is_none() {
@@ -2061,7 +2060,7 @@ pub mod cmd {
20612060
20622061 fn write(
20632062 cx: &mut compositor::Context,
2064− args: &[&str],
2063+ args: &[Cow<str>],
20652064 _event: PromptEvent,
20662065 ) -> anyhow::Result<()> {
20672066 write_impl(cx, args.first())
@@ -2069,7 +2068,7 @@ pub mod cmd {
20692068
20702069 fn new_file(
20712070 cx: &mut compositor::Context,
2072− _args: &[&str],
2071+ _args: &[Cow<str>],
20732072 _event: PromptEvent,
20742073 ) -> anyhow::Result<()> {
20752074 cx.editor.new_file(Action::Replace);
@@ -2079,7 +2078,7 @@ pub mod cmd {
20792078
20802079 fn format(
20812080 cx: &mut compositor::Context,
2082− _args: &[&str],
2081+ _args: &[Cow<str>],
20832082 _event: PromptEvent,
20842083 ) -> anyhow::Result<()> {
20852084 let (_, doc) = current!(cx.editor);
@@ -2094,7 +2093,7 @@ pub mod cmd {
20942093 }
20952094 fn set_indent_style(
20962095 cx: &mut compositor::Context,
2097− args: &[&str],
2096+ args: &[Cow<str>],
20982097 _event: PromptEvent,
20992098 ) -> anyhow::Result<()> {
21002099 use IndentStyle::*;
@@ -2114,7 +2113,7 @@ pub mod cmd {
21142113 // Attempt to parse argument as an indent style.
21152114 let style = match args.get(0) {
21162115 Some(arg) if "tabs".starts_with(&arg.to_lowercase()) => Some(Tabs),
2117− Some(&"0") => Some(Tabs),
2116+ Some(Cow::Borrowed("0")) => Some(Tabs),
21182117 Some(arg) => arg
21192118 .parse::<u8>()
21202119 .ok()
@@ -2133,7 +2132,7 @@ pub mod cmd {
21332132 /// Sets or reports the current document's line ending setting.
21342133 fn set_line_ending(
21352134 cx: &mut compositor::Context,
2136− args: &[&str],
2135+ args: &[Cow<str>],
21372136 _event: PromptEvent,
21382137 ) -> anyhow::Result<()> {
21392138 use LineEnding::*;
@@ -2177,7 +2176,7 @@ pub mod cmd {
21772176
21782177 fn earlier(
21792178 cx: &mut compositor::Context,
2180− args: &[&str],
2179+ args: &[Cow<str>],
21812180 _event: PromptEvent,
21822181 ) -> anyhow::Result<()> {
21832182 let uk = args.join(" ").parse::<UndoKind>().map_err(|s| anyhow!(s))?;
@@ -2193,7 +2192,7 @@ pub mod cmd {
21932192
21942193 fn later(
21952194 cx: &mut compositor::Context,
2196− args: &[&str],
2195+ args: &[Cow<str>],
21972196 _event: PromptEvent,
21982197 ) -> anyhow::Result<()> {
21992198 let uk = args.join(" ").parse::<UndoKind>().map_err(|s| anyhow!(s))?;
@@ -2208,7 +2207,7 @@ pub mod cmd {
22082207
22092208 fn write_quit(
22102209 cx: &mut compositor::Context,
2211− args: &[&str],
2210+ args: &[Cow<str>],
22122211 event: PromptEvent,
22132212 ) -> anyhow::Result<()> {
22142213 write_impl(cx, args.first())?;
@@ -2217,7 +2216,7 @@ pub mod cmd {
22172216
22182217 fn force_write_quit(
22192218 cx: &mut compositor::Context,
2220− args: &[&str],
2219+ args: &[Cow<str>],
22212220 event: PromptEvent,
22222221 ) -> anyhow::Result<()> {
22232222 write_impl(cx, args.first())?;
@@ -2248,7 +2247,7 @@ pub mod cmd {
22482247
22492248 fn write_all_impl(
22502249 cx: &mut compositor::Context,
2251− _args: &[&str],
2250+ _args: &[Cow<str>],
22522251 _event: PromptEvent,
22532252 quit: bool,
22542253 force: bool,
@@ -2284,7 +2283,7 @@ pub mod cmd {
22842283
22852284 fn write_all(
22862285 cx: &mut compositor::Context,
2287− args: &[&str],
2286+ args: &[Cow<str>],
22882287 event: PromptEvent,
22892288 ) -> anyhow::Result<()> {
22902289 write_all_impl(cx, args, event, false, false)
@@ -2292,7 +2291,7 @@ pub mod cmd {
22922291
22932292 fn write_all_quit(
22942293 cx: &mut compositor::Context,
2295− args: &[&str],
2294+ args: &[Cow<str>],
22962295 event: PromptEvent,
22972296 ) -> anyhow::Result<()> {
22982297 write_all_impl(cx, args, event, true, false)
@@ -2300,7 +2299,7 @@ pub mod cmd {
23002299
23012300 fn force_write_all_quit(
23022301 cx: &mut compositor::Context,
2303− args: &[&str],
2302+ args: &[Cow<str>],
23042303 event: PromptEvent,
23052304 ) -> anyhow::Result<()> {
23062305 write_all_impl(cx, args, event, true, true)
@@ -2308,7 +2307,7 @@ pub mod cmd {
23082307
23092308 fn quit_all_impl(
23102309 editor: &mut Editor,
2311− _args: &[&str],
2310+ _args: &[Cow<str>],
23122311 _event: PromptEvent,
23132312 force: bool,
23142313 ) -> anyhow::Result<()> {
@@ -2327,7 +2326,7 @@ pub mod cmd {
23272326
23282327 fn quit_all(
23292328 cx: &mut compositor::Context,
2330− args: &[&str],
2329+ args: &[Cow<str>],
23312330 event: PromptEvent,
23322331 ) -> anyhow::Result<()> {
23332332 quit_all_impl(cx.editor, args, event, false)
@@ -2335,7 +2334,7 @@ pub mod cmd {
23352334
23362335 fn force_quit_all(
23372336 cx: &mut compositor::Context,
2338− args: &[&str],
2337+ args: &[Cow<str>],
23392338 event: PromptEvent,
23402339 ) -> anyhow::Result<()> {
23412340 quit_all_impl(cx.editor, args, event, true)
@@ -2343,7 +2342,7 @@ pub mod cmd {
23432342
23442343 fn cquit(
23452344 cx: &mut compositor::Context,
2346− args: &[&str],
2345+ args: &[Cow<str>],
23472346 _event: PromptEvent,
23482347 ) -> anyhow::Result<()> {
23492348 let exit_code = args
@@ -2362,7 +2361,7 @@ pub mod cmd {
23622361
23632362 fn theme(
23642363 cx: &mut compositor::Context,
2365− args: &[&str],
2364+ args: &[Cow<str>],
23662365 _event: PromptEvent,
23672366 ) -> anyhow::Result<()> {
23682367 let theme = args.first().context("theme not provided")?;
@@ -2371,7 +2370,7 @@ pub mod cmd {
23712370
23722371 fn yank_main_selection_to_clipboard(
23732372 cx: &mut compositor::Context,
2374− _args: &[&str],
2373+ _args: &[Cow<str>],
23752374 _event: PromptEvent,
23762375 ) -> anyhow::Result<()> {
23772376 yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Clipboard)
@@ -2379,20 +2378,18 @@ pub mod cmd {
23792378
23802379 fn yank_joined_to_clipboard(
23812380 cx: &mut compositor::Context,
2382− args: &[&str],
2381+ args: &[Cow<str>],
23832382 _event: PromptEvent,
23842383 ) -> anyhow::Result<()> {
23852384 let (_, doc) = current!(cx.editor);
2386− let separator = args
2387− .first()
2388− .copied()
2389− .unwrap_or_else(|| doc.line_ending.as_str());
2385+ let default_sep = Cow::Borrowed(doc.line_ending.as_str());
2386+ let separator = args.first().unwrap_or(&default_sep);
23902387 yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Clipboard)
23912388 }
23922389
23932390 fn yank_main_selection_to_primary_clipboard(
23942391 cx: &mut compositor::Context,
2395− _args: &[&str],
2392+ _args: &[Cow<str>],
23962393 _event: PromptEvent,
23972394 ) -> anyhow::Result<()> {
23982395 yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection)
@@ -2400,20 +2397,18 @@ pub mod cmd {
24002397
24012398 fn yank_joined_to_primary_clipboard(
24022399 cx: &mut compositor::Context,
2403− args: &[&str],
2400+ args: &[Cow<str>],
24042401 _event: PromptEvent,
24052402 ) -> anyhow::Result<()> {
24062403 let (_, doc) = current!(cx.editor);
2407− let separator = args
2408− .first()
2409− .copied()
2410− .unwrap_or_else(|| doc.line_ending.as_str());
2404+ let default_sep = Cow::Borrowed(doc.line_ending.as_str());
2405+ let separator = args.first().unwrap_or(&default_sep);
24112406 yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Selection)
24122407 }
24132408
24142409 fn paste_clipboard_after(
24152410 cx: &mut compositor::Context,
2416− _args: &[&str],
2411+ _args: &[Cow<str>],
24172412 _event: PromptEvent,
24182413 ) -> anyhow::Result<()> {
24192414 paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Clipboard)
@@ -2421,7 +2416,7 @@ pub mod cmd {
24212416
24222417 fn paste_clipboard_before(
24232418 cx: &mut compositor::Context,
2424− _args: &[&str],
2419+ _args: &[Cow<str>],
24252420 _event: PromptEvent,
24262421 ) -> anyhow::Result<()> {
24272422 paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Clipboard)
@@ -2429,7 +2424,7 @@ pub mod cmd {
24292424
24302425 fn paste_primary_clipboard_after(
24312426 cx: &mut compositor::Context,
2432− _args: &[&str],
2427+ _args: &[Cow<str>],
24332428 _event: PromptEvent,
24342429 ) -> anyhow::Result<()> {
24352430 paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Selection)
@@ -2437,7 +2432,7 @@ pub mod cmd {
24372432
24382433 fn paste_primary_clipboard_before(
24392434 cx: &mut compositor::Context,
2440− _args: &[&str],
2435+ _args: &[Cow<str>],
24412436 _event: PromptEvent,
24422437 ) -> anyhow::Result<()> {
24432438 paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Selection)
@@ -2467,7 +2462,7 @@ pub mod cmd {
24672462
24682463 fn replace_selections_with_clipboard(
24692464 cx: &mut compositor::Context,
2470− _args: &[&str],
2465+ _args: &[Cow<str>],
24712466 _event: PromptEvent,
24722467 ) -> anyhow::Result<()> {
24732468 replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard)
@@ -2475,7 +2470,7 @@ pub mod cmd {
24752470
24762471 fn replace_selections_with_primary_clipboard(
24772472 cx: &mut compositor::Context,
2478− _args: &[&str],
2473+ _args: &[Cow<str>],
24792474 _event: PromptEvent,
24802475 ) -> anyhow::Result<()> {
24812476 replace_selections_with_clipboard_impl(cx, ClipboardType::Selection)
@@ -2483,7 +2478,7 @@ pub mod cmd {
24832478
24842479 fn show_clipboard_provider(
24852480 cx: &mut compositor::Context,
2486− _args: &[&str],
2481+ _args: &[Cow<str>],
24872482 _event: PromptEvent,
24882483 ) -> anyhow::Result<()> {
24892484 cx.editor
@@ -2493,12 +2488,13 @@ pub mod cmd {
24932488
24942489 fn change_current_directory(
24952490 cx: &mut compositor::Context,
2496− args: &[&str],
2491+ args: &[Cow<str>],
24972492 _event: PromptEvent,
24982493 ) -> anyhow::Result<()> {
24992494 let dir = helix_core::path::expand_tilde(
25002495 args.first()
25012496 .context("target directory not provided")?
2497+ .as_ref()
25022498 .as_ref(),
25032499 );
25042500
@@ -2516,7 +2512,7 @@ pub mod cmd {
25162512
25172513 fn show_current_directory(
25182514 cx: &mut compositor::Context,
2519− _args: &[&str],
2515+ _args: &[Cow<str>],
25202516 _event: PromptEvent,
25212517 ) -> anyhow::Result<()> {
25222518 let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
@@ -2528,7 +2524,7 @@ pub mod cmd {
25282524 /// Sets the [`Document`]'s encoding..
25292525 fn set_encoding(
25302526 cx: &mut compositor::Context,
2531− args: &[&str],
2527+ args: &[Cow<str>],
25322528 _event: PromptEvent,
25332529 ) -> anyhow::Result<()> {
25342530 let (_, doc) = current!(cx.editor);
@@ -2544,7 +2540,7 @@ pub mod cmd {
25442540 /// Reload the [`Document`] from its source file.
25452541 fn reload(
25462542 cx: &mut compositor::Context,
2547− _args: &[&str],
2543+ _args: &[Cow<str>],
25482544 _event: PromptEvent,
25492545 ) -> anyhow::Result<()> {
25502546 let (view, doc) = current!(cx.editor);
@@ -2553,7 +2549,7 @@ pub mod cmd {
25532549
25542550 fn tree_sitter_scopes(
25552551 cx: &mut compositor::Context,
2556− _args: &[&str],
2552+ _args: &[Cow<str>],
25572553 _event: PromptEvent,
25582554 ) -> anyhow::Result<()> {
25592555 let (view, doc) = current!(cx.editor);
@@ -2567,15 +2563,18 @@ pub mod cmd {
25672563
25682564 fn vsplit(
25692565 cx: &mut compositor::Context,
2570− args: &[&str],
2566+ args: &[Cow<str>],
25712567 _event: PromptEvent,
25722568 ) -> anyhow::Result<()> {
25732569 let id = view!(cx.editor).doc;
25742570
2575− if let Some(path) = args.get(0) {
2576− cx.editor.open(path.into(), Action::VerticalSplit)?;
2577− } else {
2571+ if args.is_empty() {
25782572 cx.editor.switch(id, Action::VerticalSplit);
2573+ } else {
2574+ for arg in args {
2575+ cx.editor
2576+ .open(PathBuf::from(arg.as_ref()), Action::VerticalSplit)?;
2577+ }
25792578 }
25802579
25812580 Ok(())
@@ -2583,15 +2582,18 @@ pub mod cmd {
25832582
25842583 fn hsplit(
25852584 cx: &mut compositor::Context,
2586− args: &[&str],
2585+ args: &[Cow<str>],
25872586 _event: PromptEvent,
25882587 ) -> anyhow::Result<()> {
25892588 let id = view!(cx.editor).doc;
25902589
2591− if let Some(path) = args.get(0) {
2592− cx.editor.open(path.into(), Action::HorizontalSplit)?;
2593− } else {
2590+ if args.is_empty() {
25942591 cx.editor.switch(id, Action::HorizontalSplit);
2592+ } else {
2593+ for arg in args {
2594+ cx.editor
2595+ .open(PathBuf::from(arg.as_ref()), Action::HorizontalSplit)?;
2596+ }
25952597 }
25962598
25972599 Ok(())
@@ -2599,7 +2601,7 @@ pub mod cmd {
25992601
26002602 fn tutor(
26012603 cx: &mut compositor::Context,
2602− _args: &[&str],
2604+ _args: &[Cow<str>],
26032605 _event: PromptEvent,
26042606 ) -> anyhow::Result<()> {
26052607 let path = helix_core::runtime_dir().join("tutor.txt");
@@ -2611,7 +2613,7 @@ pub mod cmd {
26112613
26122614 pub(super) fn goto_line_number(
26132615 cx: &mut compositor::Context,
2614− args: &[&str],
2616+ args: &[Cow<str>],
26152617 _event: PromptEvent,
26162618 ) -> anyhow::Result<()> {
26172619 ensure!(!args.is_empty(), "Line number required");
@@ -2980,7 +2982,7 @@ fn command_mode(cx: &mut Context) {
29802982
29812983 // If command is numeric, interpret as line number and go there.
29822984 if parts.len() == 1 && parts[0].parse::<usize>().ok().is_some() {
2983− if let Err(e) = cmd::goto_line_number(cx, &parts[0..], event) {
2985+ if let Err(e) = cmd::goto_line_number(cx, &[Cow::from(parts[0])], event) {
29842986 cx.editor.set_error(format!("{}", e));
29852987 }
29862988 return;
@@ -2988,7 +2990,8 @@ fn command_mode(cx: &mut Context) {
29882990
29892991 // Handle typable commands
29902992 if let Some(cmd) = cmd::TYPABLE_COMMAND_MAP.get(parts[0]) {
2991− if let Err(e) = (cmd.fun)(cx, &parts[1..], event) {
2993+ let args = shellwords::shellwords(input);
2994+ if let Err(e) = (cmd.fun)(cx, &args[1..], event) {
29922995 cx.editor.set_error(format!("{}", e));
29932996 }
29942997 } else {