Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/commands/typed.rs')
-rw-r--r--helix-term/src/commands/typed.rs715
1 files changed, 413 insertions, 302 deletions
diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs
index ffe58adf..078bb800 100644
--- a/helix-term/src/commands/typed.rs
+++ b/helix-term/src/commands/typed.rs
@@ -13,7 +13,6 @@ use helix_stdx::path::home_dir;
use helix_view::document::{read_to_string, DEFAULT_LANGUAGE_NAME};
use helix_view::editor::{CloseError, ConfigEvent};
use serde_json::Value;
-use shellwords::Args;
use ui::completers::{self, Completer};
#[derive(Clone)]
@@ -22,17 +21,17 @@ pub struct TypableCommand {
pub aliases: &'static [&'static str],
pub doc: &'static str,
// params, flags, helper, completer
- pub fun: fn(&mut compositor::Context, Args, PromptEvent) -> anyhow::Result<()>,
+ pub fun: fn(&mut compositor::Context, &[Cow<str>], PromptEvent) -> anyhow::Result<()>,
/// What completion methods, if any, does this command have?
pub signature: CommandSignature,
}
impl TypableCommand {
fn completer_for_argument_number(&self, n: usize) -> &Completer {
- self.signature
- .positional_args
- .get(n)
- .unwrap_or(&self.signature.var_args)
+ match self.signature.positional_args.get(n) {
+ Some(completer) => completer,
+ _ => &self.signature.var_args,
+ }
}
}
@@ -68,7 +67,7 @@ impl CommandSignature {
}
}
-fn quit(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn quit(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) -> anyhow::Result<()> {
log::debug!("quitting...");
if event != PromptEvent::Validate {
@@ -79,7 +78,7 @@ fn quit(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow:
// last view and we have unsaved changes
if cx.editor.tree.views().count() == 1 {
- buffers_remaining_impl(cx.editor)?;
+ buffers_remaining_impl(cx.editor)?
}
cx.block_try_flush_writes()?;
@@ -88,7 +87,11 @@ fn quit(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow:
Ok(())
}
-fn force_quit(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn force_quit(
+ cx: &mut compositor::Context,
+ args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
@@ -101,13 +104,12 @@ fn force_quit(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> a
Ok(())
}
-fn open(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn open(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
- ensure!(!args.is_empty(), ":open needs at least one argument");
-
+ ensure!(!args.is_empty(), "wrong argument count");
for arg in args {
let (path, pos) = args::parse_file(arg);
let path = helix_stdx::path::expand_tilde(path);
@@ -173,7 +175,7 @@ fn buffer_close_by_ids_impl(
Ok(())
}
-fn buffer_gather_paths_impl(editor: &mut Editor, args: Args) -> Vec<DocumentId> {
+fn buffer_gather_paths_impl(editor: &mut Editor, args: &[Cow<str>]) -> Vec<DocumentId> {
// No arguments implies current document
if args.is_empty() {
let doc_id = view!(editor).doc;
@@ -184,7 +186,7 @@ fn buffer_gather_paths_impl(editor: &mut Editor, args: Args) -> Vec<DocumentId>
let mut document_ids = vec![];
for arg in args {
let doc_id = editor.documents().find_map(|doc| {
- let arg_path = Some(Path::new(arg));
+ let arg_path = Some(Path::new(arg.as_ref()));
if doc.path().map(|p| p.as_path()) == arg_path || doc.relative_path() == arg_path {
Some(doc.id())
} else {
@@ -210,7 +212,7 @@ fn buffer_gather_paths_impl(editor: &mut Editor, args: Args) -> Vec<DocumentId>
fn buffer_close(
cx: &mut compositor::Context,
- args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -223,7 +225,7 @@ fn buffer_close(
fn force_buffer_close(
cx: &mut compositor::Context,
- args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -245,7 +247,7 @@ fn buffer_gather_others_impl(editor: &mut Editor) -> Vec<DocumentId> {
fn buffer_close_others(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -258,7 +260,7 @@ fn buffer_close_others(
fn force_buffer_close_others(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -275,7 +277,7 @@ fn buffer_gather_all_impl(editor: &mut Editor) -> Vec<DocumentId> {
fn buffer_close_all(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -288,7 +290,7 @@ fn buffer_close_all(
fn force_buffer_close_all(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -301,7 +303,7 @@ fn force_buffer_close_all(
fn buffer_next(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -314,7 +316,7 @@ fn buffer_next(
fn buffer_previous(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -325,10 +327,15 @@ fn buffer_previous(
Ok(())
}
-fn write_impl(cx: &mut compositor::Context, path: Option<&str>, force: bool) -> anyhow::Result<()> {
+fn write_impl(
+ cx: &mut compositor::Context,
+ path: Option<&Cow<str>>,
+ force: bool,
+) -> anyhow::Result<()> {
let config = cx.editor.config();
let jobs = &mut cx.jobs;
let (view, doc) = current!(cx.editor);
+ let path = path.map(AsRef::as_ref);
if config.insert_final_newline {
insert_final_newline(doc, view.id);
@@ -370,36 +377,40 @@ fn insert_final_newline(doc: &mut Document, view_id: ViewId) {
}
}
-fn write(cx: &mut compositor::Context, mut args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn write(
+ cx: &mut compositor::Context,
+ args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
- write_impl(cx, args.next(), false)
+ write_impl(cx, args.first(), false)
}
fn force_write(
cx: &mut compositor::Context,
- mut args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
- write_impl(cx, args.next(), true)
+ write_impl(cx, args.first(), true)
}
fn write_buffer_close(
cx: &mut compositor::Context,
- mut args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
- write_impl(cx, args.next(), false)?;
+ write_impl(cx, args.first(), false)?;
let document_ids = buffer_gather_paths_impl(cx.editor, args);
buffer_close_by_ids_impl(cx, &document_ids, false)
@@ -407,20 +418,24 @@ fn write_buffer_close(
fn force_write_buffer_close(
cx: &mut compositor::Context,
- mut args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
- write_impl(cx, args.next(), true)?;
+ write_impl(cx, args.first(), true)?;
let document_ids = buffer_gather_paths_impl(cx.editor, args);
buffer_close_by_ids_impl(cx, &document_ids, false)
}
-fn new_file(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn new_file(
+ cx: &mut compositor::Context,
+ _args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
@@ -430,7 +445,11 @@ fn new_file(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> an
Ok(())
}
-fn format(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn format(
+ cx: &mut compositor::Context,
+ _args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
@@ -447,7 +466,7 @@ fn format(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyh
fn set_indent_style(
cx: &mut compositor::Context,
- mut args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -468,9 +487,9 @@ fn set_indent_style(
}
// Attempt to parse argument as an indent style.
- let style = match args.next() {
+ let style = match args.first() {
Some(arg) if "tabs".starts_with(&arg.to_lowercase()) => Some(Tabs),
- Some("0") => Some(Tabs),
+ Some(Cow::Borrowed("0")) => Some(Tabs),
Some(arg) => arg
.parse::<u8>()
.ok()
@@ -489,7 +508,7 @@ fn set_indent_style(
/// Sets or reports the current document's line ending setting.
fn set_line_ending(
cx: &mut compositor::Context,
- mut args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -520,7 +539,7 @@ fn set_line_ending(
}
let arg = args
- .next()
+ .first()
.context("argument missing")?
.to_ascii_lowercase();
@@ -559,12 +578,16 @@ fn set_line_ending(
Ok(())
}
-fn earlier(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn earlier(
+ cx: &mut compositor::Context,
+ args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
- let uk = args.raw().parse::<UndoKind>().map_err(|s| anyhow!(s))?;
+ let uk = args.join(" ").parse::<UndoKind>().map_err(|s| anyhow!(s))?;
let (view, doc) = current!(cx.editor);
let success = doc.earlier(view, uk);
@@ -575,13 +598,16 @@ fn earlier(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyh
Ok(())
}
-fn later(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn later(
+ cx: &mut compositor::Context,
+ args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
- let uk = args.raw().parse::<UndoKind>().map_err(|s| anyhow!(s))?;
-
+ let uk = args.join(" ").parse::<UndoKind>().map_err(|s| anyhow!(s))?;
let (view, doc) = current!(cx.editor);
let success = doc.later(view, uk);
if !success {
@@ -593,30 +619,30 @@ fn later(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow
fn write_quit(
cx: &mut compositor::Context,
- mut args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
- write_impl(cx, args.next(), false)?;
+ write_impl(cx, args.first(), false)?;
cx.block_try_flush_writes()?;
- quit(cx, Args::empty(), event)
+ quit(cx, &[], event)
}
fn force_write_quit(
cx: &mut compositor::Context,
- mut args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
- write_impl(cx, args.next(), true)?;
+ write_impl(cx, args.first(), true)?;
cx.block_try_flush_writes()?;
- force_quit(cx, Args::empty(), event)
+ force_quit(cx, &[], event)
}
/// Results in an error if there are modified buffers remaining and sets editor
@@ -723,7 +749,11 @@ pub fn write_all_impl(
Ok(())
}
-fn write_all(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn write_all(
+ cx: &mut compositor::Context,
+ _args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
@@ -733,7 +763,7 @@ fn write_all(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> a
fn force_write_all(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -745,7 +775,7 @@ fn force_write_all(
fn write_all_quit(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -757,7 +787,7 @@ fn write_all_quit(
fn force_write_all_quit(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -782,31 +812,41 @@ fn quit_all_impl(cx: &mut compositor::Context, force: bool) -> anyhow::Result<()
Ok(())
}
-fn quit_all(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn quit_all(
+ cx: &mut compositor::Context,
+ _args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
quit_all_impl(cx, false)
}
fn force_quit_all(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
quit_all_impl(cx, true)
}
-fn cquit(cx: &mut compositor::Context, mut args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn cquit(
+ cx: &mut compositor::Context,
+ args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let exit_code = args
- .next()
+ .first()
.and_then(|code| code.parse::<i32>().ok())
.unwrap_or(1);
@@ -816,7 +856,7 @@ fn cquit(cx: &mut compositor::Context, mut args: Args, event: PromptEvent) -> an
fn force_cquit(
cx: &mut compositor::Context,
- mut args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -824,7 +864,7 @@ fn force_cquit(
}
let exit_code = args
- .next()
+ .first()
.and_then(|code| code.parse::<i32>().ok())
.unwrap_or(1);
cx.editor.exit_code = exit_code;
@@ -832,7 +872,11 @@ fn force_cquit(
quit_all_impl(cx, true)
}
-fn theme(cx: &mut compositor::Context, mut args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn theme(
+ cx: &mut compositor::Context,
+ args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
let true_color = cx.editor.config.load().true_color || crate::true_color();
match event {
PromptEvent::Abort => {
@@ -842,7 +886,7 @@ fn theme(cx: &mut compositor::Context, mut args: Args, event: PromptEvent) -> an
if args.is_empty() {
// Ensures that a preview theme gets cleaned up if the user backspaces until the prompt is empty.
cx.editor.unset_theme_preview();
- } else if let Some(theme_name) = args.next() {
+ } else if let Some(theme_name) = args.first() {
if let Ok(theme) = cx.editor.theme_loader.load(theme_name) {
if !(true_color || theme.is_16_color()) {
bail!("Unsupported theme: theme requires true color support");
@@ -852,7 +896,7 @@ fn theme(cx: &mut compositor::Context, mut args: Args, event: PromptEvent) -> an
};
}
PromptEvent::Validate => {
- if let Some(theme_name) = args.next() {
+ if let Some(theme_name) = args.first() {
let theme = cx
.editor
.theme_loader
@@ -875,142 +919,168 @@ fn theme(cx: &mut compositor::Context, mut args: Args, event: PromptEvent) -> an
fn yank_main_selection_to_clipboard(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
yank_primary_selection_impl(cx.editor, '+');
Ok(())
}
-fn yank_joined(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn yank_joined(
+ cx: &mut compositor::Context,
+ args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
+ ensure!(args.len() <= 1, ":yank-join takes at most 1 argument");
+
+ let doc = doc!(cx.editor);
+ let default_sep = Cow::Borrowed(doc.line_ending.as_str());
+ let separator = args.first().unwrap_or(&default_sep);
let register = cx.editor.selected_register.unwrap_or('"');
- yank_joined_impl(cx.editor, args.raw(), register);
+ yank_joined_impl(cx.editor, separator, register);
Ok(())
}
fn yank_joined_to_clipboard(
cx: &mut compositor::Context,
- args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
- yank_joined_impl(cx.editor, args.raw(), '+');
+
+ let doc = doc!(cx.editor);
+ let default_sep = Cow::Borrowed(doc.line_ending.as_str());
+ let separator = args.first().unwrap_or(&default_sep);
+ yank_joined_impl(cx.editor, separator, '+');
Ok(())
}
fn yank_main_selection_to_primary_clipboard(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
yank_primary_selection_impl(cx.editor, '*');
Ok(())
}
fn yank_joined_to_primary_clipboard(
cx: &mut compositor::Context,
- args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
- yank_joined_impl(cx.editor, args.raw(), '*');
+ let doc = doc!(cx.editor);
+ let default_sep = Cow::Borrowed(doc.line_ending.as_str());
+ let separator = args.first().unwrap_or(&default_sep);
+ yank_joined_impl(cx.editor, separator, '*');
Ok(())
}
fn paste_clipboard_after(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
paste(cx.editor, '+', Paste::After, 1);
Ok(())
}
fn paste_clipboard_before(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
paste(cx.editor, '+', Paste::Before, 1);
Ok(())
}
fn paste_primary_clipboard_after(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
paste(cx.editor, '*', Paste::After, 1);
Ok(())
}
fn paste_primary_clipboard_before(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
paste(cx.editor, '*', Paste::Before, 1);
Ok(())
}
fn replace_selections_with_clipboard(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
replace_with_yanked_impl(cx.editor, '+', 1);
Ok(())
}
fn replace_selections_with_primary_clipboard(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
replace_with_yanked_impl(cx.editor, '*', 1);
Ok(())
}
fn show_clipboard_provider(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
cx.editor
.set_status(cx.editor.registers.clipboard_provider_name());
Ok(())
@@ -1018,14 +1088,14 @@ fn show_clipboard_provider(
fn change_current_directory(
cx: &mut compositor::Context,
- mut args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
- let dir = match args.next() {
+ let dir = match args.first().map(AsRef::as_ref) {
Some("-") => cx
.editor
.get_last_cwd()
@@ -1047,7 +1117,7 @@ fn change_current_directory(
fn show_current_directory(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -1060,7 +1130,7 @@ fn show_current_directory(
if cwd.exists() {
cx.editor.set_status(message);
} else {
- cx.editor.set_error(format!("{message} (deleted)"));
+ cx.editor.set_error(format!("{} (deleted)", message));
}
Ok(())
}
@@ -1068,7 +1138,7 @@ fn show_current_directory(
/// Sets the [`Document`]'s encoding..
fn set_encoding(
cx: &mut compositor::Context,
- mut args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -1076,7 +1146,7 @@ fn set_encoding(
}
let doc = doc_mut!(cx.editor);
- if let Some(label) = args.next() {
+ if let Some(label) = args.first() {
doc.set_encoding(label)
} else {
let encoding = doc.encoding().name().to_owned();
@@ -1088,7 +1158,7 @@ fn set_encoding(
/// Shows info about the character under the primary cursor.
fn get_character_info(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -1211,7 +1281,11 @@ fn get_character_info(
}
/// Reload the [`Document`] from its source file.
-fn reload(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn reload(
+ cx: &mut compositor::Context,
+ _args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
@@ -1230,7 +1304,11 @@ fn reload(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyh
Ok(())
}
-fn reload_all(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn reload_all(
+ cx: &mut compositor::Context,
+ _args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
@@ -1286,7 +1364,11 @@ fn reload_all(cx: &mut compositor::Context, _args: Args, event: PromptEvent) ->
}
/// Update the [`Document`] if it has been modified.
-fn update(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn update(
+ cx: &mut compositor::Context,
+ args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
@@ -1301,7 +1383,7 @@ fn update(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyho
fn lsp_workspace_command(
cx: &mut compositor::Context,
- args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -1357,8 +1439,7 @@ fn lsp_workspace_command(
};
cx.jobs.callback(callback);
} else {
- let command = args.raw().to_string();
-
+ let command = args.join(" ");
let matches: Vec<_> = ls_id_commands
.filter(|(_ls_id, c)| *c == &command)
.collect();
@@ -1392,7 +1473,7 @@ fn lsp_workspace_command(
fn lsp_restart(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -1438,7 +1519,11 @@ fn lsp_restart(
Ok(())
}
-fn lsp_stop(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn lsp_stop(
+ cx: &mut compositor::Context,
+ _args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
@@ -1465,7 +1550,7 @@ fn lsp_stop(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> an
fn tree_sitter_scopes(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -1498,7 +1583,7 @@ fn tree_sitter_scopes(
fn tree_sitter_highlight_name(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
fn find_highlight_at_cursor(
@@ -1571,50 +1656,81 @@ fn tree_sitter_highlight_name(
Ok(())
}
-fn vsplit(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn vsplit(
+ cx: &mut compositor::Context,
+ args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
- } else if args.is_empty() {
+ }
+
+ if args.is_empty() {
split(cx.editor, Action::VerticalSplit);
} else {
for arg in args {
- cx.editor.open(&PathBuf::from(arg), Action::VerticalSplit)?;
+ cx.editor
+ .open(&PathBuf::from(arg.as_ref()), Action::VerticalSplit)?;
}
}
+
Ok(())
}
-fn hsplit(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn hsplit(
+ cx: &mut compositor::Context,
+ args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
- } else if args.is_empty() {
+ }
+
+ if args.is_empty() {
split(cx.editor, Action::HorizontalSplit);
} else {
for arg in args {
cx.editor
- .open(&PathBuf::from(arg), Action::HorizontalSplit)?;
+ .open(&PathBuf::from(arg.as_ref()), Action::HorizontalSplit)?;
}
}
+
Ok(())
}
-fn vsplit_new(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn vsplit_new(
+ cx: &mut compositor::Context,
+ _args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
cx.editor.new_file(Action::VerticalSplit);
+
Ok(())
}
-fn hsplit_new(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn hsplit_new(
+ cx: &mut compositor::Context,
+ _args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
cx.editor.new_file(Action::HorizontalSplit);
+
Ok(())
}
-fn debug_eval(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn debug_eval(
+ cx: &mut compositor::Context,
+ args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
@@ -1628,10 +1744,9 @@ fn debug_eval(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> a
};
// TODO: support no frame_id
- let frame_id = debugger.stack_frames[&thread_id][frame].id;
- let expression = args.raw().to_string();
- let response = helix_lsp::block_on(debugger.eval(expression, Some(frame_id)))?;
+ let frame_id = debugger.stack_frames[&thread_id][frame].id;
+ let response = helix_lsp::block_on(debugger.eval(args.join(" "), Some(frame_id)))?;
cx.editor.set_status(response.result);
}
Ok(())
@@ -1639,33 +1754,47 @@ fn debug_eval(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> a
fn debug_start(
cx: &mut compositor::Context,
- mut args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
- dap_start_impl(cx, args.next(), None, Some(args.map(Into::into).collect()))
+
+ let mut args = args.to_owned();
+ let name = match args.len() {
+ 0 => None,
+ _ => Some(args.remove(0)),
+ };
+ dap_start_impl(cx, name.as_deref(), None, Some(args))
}
fn debug_remote(
cx: &mut compositor::Context,
- mut args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
- let address = args.next().map(|addr| addr.parse()).transpose()?;
- dap_start_impl(
- cx,
- args.next(),
- address,
- Some(args.map(Into::into).collect()),
- )
+
+ let mut args = args.to_owned();
+ let address = match args.len() {
+ 0 => None,
+ _ => Some(args.remove(0).parse()?),
+ };
+ let name = match args.len() {
+ 0 => None,
+ _ => Some(args.remove(0)),
+ };
+ dap_start_impl(cx, name.as_deref(), address, Some(args))
}
-fn tutor(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn tutor(
+ cx: &mut compositor::Context,
+ _args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
@@ -1689,7 +1818,7 @@ fn abort_goto_line_number_preview(cx: &mut compositor::Context) {
fn update_goto_line_number_preview(
cx: &mut compositor::Context,
- mut args: Args,
+ args: &[Cow<str>],
) -> anyhow::Result<()> {
cx.editor.last_selection.get_or_insert_with(|| {
let (view, doc) = current!(cx.editor);
@@ -1697,7 +1826,7 @@ fn update_goto_line_number_preview(
});
let scrolloff = cx.editor.config().scrolloff;
- let line = args.next().unwrap().parse::<usize>()?;
+ let line = args[0].parse::<usize>()?;
goto_line_without_jumplist(cx.editor, NonZeroUsize::new(line));
let (view, doc) = current!(cx.editor);
@@ -1708,7 +1837,7 @@ fn update_goto_line_number_preview(
pub(super) fn goto_line_number(
cx: &mut compositor::Context,
- args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
match event {
@@ -1744,18 +1873,18 @@ pub(super) fn goto_line_number(
// Fetch the current value of a config option and output as status.
fn get_option(
cx: &mut compositor::Context,
- mut args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
- if args.arg_count() != 1 {
+ if args.len() != 1 {
anyhow::bail!("Bad arguments. Usage: `:get key`");
}
- let key = args.next().unwrap().to_lowercase();
+ let key = &args[0].to_lowercase();
let key_error = || anyhow::anyhow!("Unknown key `{}`", key);
let config = serde_json::json!(cx.editor.config().deref());
@@ -1770,61 +1899,46 @@ fn get_option(
/// example to disable smart case search, use `:set search.smart-case false`.
fn set_option(
cx: &mut compositor::Context,
- mut args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
- let Some(key) = args.next().map(|arg| arg.to_lowercase()) else {
- anyhow::bail!("Bad arguments. Usage: `:set key field`, didn't provide `key`");
- };
-
- let field = args.rest();
-
- if field.is_empty() {
- anyhow::bail!("Bad arguments. Usage: `:set key field`, didn't provide `field`");
+ if args.len() != 2 {
+ anyhow::bail!("Bad arguments. Usage: `:set key field`");
}
+ let (key, arg) = (&args[0].to_lowercase(), &args[1]);
+
+ let key_error = || anyhow::anyhow!("Unknown key `{}`", key);
+ let field_error = |_| anyhow::anyhow!("Could not parse field `{}`", arg);
- let mut config = serde_json::json!(&*cx.editor.config());
+ let mut config = serde_json::json!(&cx.editor.config().deref());
let pointer = format!("/{}", key.replace('.', "/"));
- let value = config
- .pointer_mut(&pointer)
- .ok_or_else(|| anyhow::anyhow!("Unknown key `{key}`"))?;
+ let value = config.pointer_mut(&pointer).ok_or_else(key_error)?;
*value = if value.is_string() {
// JSON strings require quotes, so we can't .parse() directly
- Value::String(field.to_string())
+ Value::String(arg.to_string())
} else {
- field
- .parse()
- .map_err(|err| anyhow::anyhow!("Could not parse field `{field}`: {err}"))?
+ arg.parse().map_err(field_error)?
};
-
- let config = serde_json::from_value(config).expect(
- "`Config` was already deserialized, serialization is just a 'repacking' and should be valid",
- );
+ let config = serde_json::from_value(config).map_err(field_error)?;
cx.editor
.config_events
.0
.send(ConfigEvent::Update(config))?;
-
- cx.editor
- .set_status(format!("'{key}' is now set to {field}"));
-
Ok(())
}
/// Toggle boolean config option at runtime. Access nested values by dot
-/// syntax.
-/// Example:
-/// - `:toggle search.smart-case` (bool)
-/// - `:toggle line-number relative absolute` (string)
+/// syntax, for example to toggle smart case search, use `:toggle search.smart-
+/// case`.
fn toggle_option(
cx: &mut compositor::Context,
- mut args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -1834,98 +1948,73 @@ fn toggle_option(
if args.is_empty() {
anyhow::bail!("Bad arguments. Usage: `:toggle key [values]?`");
}
- let key = args.next().unwrap().to_lowercase();
+ let key = &args[0].to_lowercase();
- let mut config = serde_json::json!(&*cx.editor.config());
+ let key_error = || anyhow::anyhow!("Unknown key `{}`", key);
+
+ let mut config = serde_json::json!(&cx.editor.config().deref());
let pointer = format!("/{}", key.replace('.', "/"));
- let value = config
- .pointer_mut(&pointer)
- .ok_or_else(|| anyhow::anyhow!("Unknown key `{}`", key))?;
+ let value = config.pointer_mut(&pointer).ok_or_else(key_error)?;
*value = match value {
Value::Bool(ref value) => {
ensure!(
- args.next().is_none(),
+ args.len() == 1,
"Bad arguments. For boolean configurations use: `:toggle key`"
);
Value::Bool(!value)
}
Value::String(ref value) => {
ensure!(
- // key + arguments
- args.arg_count() >= 3,
+ args.len() > 2,
"Bad arguments. For string configurations use: `:toggle key val1 val2 ...`",
);
Value::String(
- args.clone()
+ args[1..]
+ .iter()
.skip_while(|e| *e != value)
.nth(1)
- .unwrap_or_else(|| args.nth(1).unwrap())
+ .unwrap_or_else(|| &args[1])
.to_string(),
)
}
Value::Number(ref value) => {
ensure!(
- // key + arguments
- args.arg_count() >= 3,
+ args.len() > 2,
"Bad arguments. For number configurations use: `:toggle key val1 val2 ...`",
);
- let value = value.to_string();
-
Value::Number(
- args.clone()
- .skip_while(|e| *e != value)
+ args[1..]
+ .iter()
+ .skip_while(|&e| value.to_string() != *e.to_string())
.nth(1)
- .unwrap_or_else(|| args.nth(1).unwrap())
+ .unwrap_or_else(|| &args[1])
.parse()?,
)
}
- Value::Array(value) => {
- let mut lists = serde_json::Deserializer::from_str(args.rest()).into_iter::<Value>();
-
- let (Some(first), Some(second)) =
- (lists.next().transpose()?, lists.next().transpose()?)
- else {
- anyhow::bail!(
- "Bad arguments. For list configurations use: `:toggle key [...] [...]`",
- )
- };
-
- match (&first, &second) {
- (Value::Array(list), Value::Array(_)) => {
- if list == value {
- second
- } else {
- first
- }
- }
- _ => anyhow::bail!("values must be lists"),
- }
- }
- Value::Null | Value::Object(_) => {
+ Value::Null | Value::Object(_) | Value::Array(_) => {
anyhow::bail!("Configuration {key} does not support toggle yet")
}
};
let status = format!("'{key}' is now set to {value}");
- let config = serde_json::from_value(config)?;
+ let config = serde_json::from_value(config)
+ .map_err(|err| anyhow::anyhow!("Cannot parse `{:?}`, {}", &args, err))?;
cx.editor
.config_events
.0
.send(ConfigEvent::Update(config))?;
-
cx.editor.set_status(status);
-
Ok(())
}
/// Change the language of the current buffer at runtime.
fn language(
cx: &mut compositor::Context,
- mut args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -1934,22 +2023,21 @@ fn language(
if args.is_empty() {
let doc = doc!(cx.editor);
- let language = doc.language_name().unwrap_or(DEFAULT_LANGUAGE_NAME);
+ let language = &doc.language_name().unwrap_or(DEFAULT_LANGUAGE_NAME);
cx.editor.set_status(language.to_string());
return Ok(());
}
- if args.arg_count() != 1 {
+ if args.len() != 1 {
anyhow::bail!("Bad arguments. Usage: `:set-language language`");
}
let doc = doc_mut!(cx.editor);
- let language_id = args.next().unwrap();
- if language_id == DEFAULT_LANGUAGE_NAME {
- doc.set_language(None, None);
+ if args[0] == DEFAULT_LANGUAGE_NAME {
+ doc.set_language(None, None)
} else {
- doc.set_language_by_language_id(language_id, cx.editor.syn_loader.clone())?;
+ doc.set_language_by_language_id(&args[0], cx.editor.syn_loader.clone())?;
}
doc.detect_indent_and_line_ending();
@@ -1962,25 +2050,31 @@ fn language(
Ok(())
}
-fn sort(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn sort(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
sort_impl(cx, args, false)
}
fn sort_reverse(
cx: &mut compositor::Context,
- args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
sort_impl(cx, args, true)
}
-fn sort_impl(cx: &mut compositor::Context, _args: Args, reverse: bool) -> anyhow::Result<()> {
+fn sort_impl(
+ cx: &mut compositor::Context,
+ _args: &[Cow<str>],
+ reverse: bool,
+) -> anyhow::Result<()> {
let scrolloff = cx.editor.config().scrolloff;
let (view, doc) = current!(cx.editor);
let text = doc.text().slice(..);
@@ -2012,7 +2106,11 @@ fn sort_impl(cx: &mut compositor::Context, _args: Args, reverse: bool) -> anyhow
Ok(())
}
-fn reflow(cx: &mut compositor::Context, mut args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn reflow(
+ cx: &mut compositor::Context,
+ args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
@@ -2026,7 +2124,7 @@ fn reflow(cx: &mut compositor::Context, mut args: Args, event: PromptEvent) -> a
// - The configured text-width for this language in languages.toml
// - The configured text-width in the config.toml
let text_width: usize = args
- .next()
+ .first()
.map(|num| num.parse::<usize>())
.transpose()?
.or_else(|| doc.language_config().and_then(|config| config.text_width))
@@ -2051,7 +2149,7 @@ fn reflow(cx: &mut compositor::Context, mut args: Args, event: PromptEvent) -> a
fn tree_sitter_subtree(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -2090,12 +2188,13 @@ fn tree_sitter_subtree(
fn open_config(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
cx.editor
.open(&helix_loader::config_file(), Action::Replace)?;
Ok(())
@@ -2103,28 +2202,34 @@ fn open_config(
fn open_workspace_config(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
cx.editor
.open(&helix_loader::workspace_config_file(), Action::Replace)?;
Ok(())
}
-fn open_log(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn open_log(
+ cx: &mut compositor::Context,
+ _args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
cx.editor.open(&helix_loader::log_file(), Action::Replace)?;
Ok(())
}
fn refresh_config(
cx: &mut compositor::Context,
- _args: Args,
+ _args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -2137,58 +2242,62 @@ fn refresh_config(
fn append_output(
cx: &mut compositor::Context,
- args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
ensure!(!args.is_empty(), "Shell command required");
- let cmd = helix_core::shellwords::unescape(args.raw());
- shell(cx, &cmd, &ShellBehavior::Append);
+ shell(cx, &args.join(" "), &ShellBehavior::Append);
Ok(())
}
fn insert_output(
cx: &mut compositor::Context,
- args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
ensure!(!args.is_empty(), "Shell command required");
- let cmd = helix_core::shellwords::unescape(args.raw());
- shell(cx, &cmd, &ShellBehavior::Insert);
+ shell(cx, &args.join(" "), &ShellBehavior::Insert);
Ok(())
}
-fn pipe_to(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn pipe_to(
+ cx: &mut compositor::Context,
+ args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
pipe_impl(cx, args, event, &ShellBehavior::Ignore)
}
-fn pipe(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn pipe(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) -> anyhow::Result<()> {
pipe_impl(cx, args, event, &ShellBehavior::Replace)
}
fn pipe_impl(
cx: &mut compositor::Context,
- args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
behavior: &ShellBehavior,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
+
ensure!(!args.is_empty(), "Shell command required");
- let cmd = helix_core::shellwords::unescape(args.raw());
- shell(cx, &cmd, behavior);
+ shell(cx, &args.join(" "), behavior);
Ok(())
}
fn run_shell_command(
cx: &mut compositor::Context,
- args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -2196,8 +2305,7 @@ fn run_shell_command(
}
let shell = cx.editor.config().shell.clone();
-
- let args = helix_core::shellwords::unescape(args.raw()).into_owned();
+ let args = args.join(" ");
let callback = async move {
let output = shell_impl_async(&shell, &args, None).await?;
@@ -2225,7 +2333,7 @@ fn run_shell_command(
fn reset_diff_change(
cx: &mut compositor::Context,
- args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
@@ -2233,8 +2341,10 @@ fn reset_diff_change(
}
ensure!(args.is_empty(), ":reset-diff-change takes no arguments");
- let scrolloff = cx.editor.config().scrolloff;
- let (view, doc) = current!(cx.editor);
+ let editor = &mut cx.editor;
+ let scrolloff = editor.config().scrolloff;
+
+ let (view, doc) = current!(editor);
let Some(handle) = doc.diff_handle() else {
bail!("Diff is not available in the current buffer")
};
@@ -2276,42 +2386,40 @@ fn reset_diff_change(
fn clear_register(
cx: &mut compositor::Context,
- mut args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
- ensure!(
- args.arg_count() <= 1,
- ":clear-register takes at most 1 argument"
- );
-
+ ensure!(args.len() <= 1, ":clear-register takes at most 1 argument");
if args.is_empty() {
cx.editor.registers.clear();
cx.editor.set_status("All registers cleared");
return Ok(());
}
- let register = args.next().unwrap();
-
ensure!(
- register.chars().count() == 1,
- format!("Invalid register {register}")
+ args[0].chars().count() == 1,
+ format!("Invalid register {}", args[0])
);
-
- let register = register.chars().next().unwrap_or_default();
+ let register = args[0].chars().next().unwrap_or_default();
if cx.editor.registers.remove(register) {
- cx.editor.set_status(format!("Register {register} cleared"));
+ cx.editor
+ .set_status(format!("Register {} cleared", register));
} else {
cx.editor
- .set_error(format!("Register {register} not found"));
+ .set_error(format!("Register {} not found", register));
}
Ok(())
}
-fn redraw(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn redraw(
+ cx: &mut compositor::Context,
+ _args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
@@ -2332,22 +2440,20 @@ fn redraw(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyh
fn move_buffer(
cx: &mut compositor::Context,
- mut args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
- ensure!(args.arg_count() == 1, format!(":move takes one argument"));
-
- let old_path = doc!(cx.editor)
+ ensure!(args.len() == 1, format!(":move takes one argument"));
+ let doc = doc!(cx.editor);
+ let old_path = doc
.path()
.context("Scratch buffer cannot be moved. Use :write instead")?
.clone();
-
- let new_path = args.next().unwrap();
-
+ let new_path = args.first().unwrap().to_string();
if let Err(err) = cx.editor.move_path(&old_path, new_path.as_ref()) {
bail!("Could not move file: {err}");
}
@@ -2356,14 +2462,14 @@ fn move_buffer(
fn yank_diagnostic(
cx: &mut compositor::Context,
- mut args: Args,
+ args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
- let reg = match args.next() {
+ let reg = match args.first() {
Some(s) => {
ensure!(s.chars().count() == 1, format!("Invalid register {s}"));
s.chars().next().unwrap()
@@ -2394,7 +2500,7 @@ fn yank_diagnostic(
Ok(())
}
-fn read(cx: &mut compositor::Context, mut args: Args, event: PromptEvent) -> anyhow::Result<()> {
+fn read(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
@@ -2403,10 +2509,10 @@ fn read(cx: &mut compositor::Context, mut args: Args, event: PromptEvent) -> any
let (view, doc) = current!(cx.editor);
ensure!(!args.is_empty(), "file name is expected");
- ensure!(args.arg_count() == 1, "only the file name is expected");
+ ensure!(args.len() == 1, "only the file name is expected");
- let filename = args.next().unwrap();
- let path = helix_stdx::path::expand_tilde(Path::new(filename));
+ let filename = args.first().unwrap();
+ let path = helix_stdx::path::expand_tilde(PathBuf::from(filename.to_string()));
ensure!(
path.exists() && path.is_file(),
@@ -3069,11 +3175,9 @@ pub(super) fn command_mode(cx: &mut Context) {
Some(':'),
|editor: &Editor, input: &str| {
let shellwords = Shellwords::from(input);
- let command = shellwords.command();
+ let words = shellwords.words();
- if command.is_empty()
- || (shellwords.args().next().is_none() && !shellwords.ends_with_whitespace())
- {
+ if words.is_empty() || (words.len() == 1 && !shellwords.ends_with_whitespace()) {
fuzzy_match(
input,
TYPABLE_COMMAND_LIST.iter().map(|command| command.name),
@@ -3085,61 +3189,67 @@ pub(super) fn command_mode(cx: &mut Context) {
} else {
// Otherwise, use the command's completer and the last shellword
// as completion input.
- let (word, len) = shellwords
- .args()
- .last()
- .map_or(("", 0), |last| (last, last.len()));
-
- TYPABLE_COMMAND_MAP
- .get(command)
- .map(|tc| tc.completer_for_argument_number(argument_number_of(&shellwords)))
- .map_or_else(Vec::new, |completer| {
- completer(editor, word)
- .into_iter()
- .map(|(range, mut file)| {
- file.content = shellwords::escape(file.content);
-
- // offset ranges to input
- let offset = input.len() - len;
- let range = (range.start + offset)..;
- (range, file)
- })
- .collect()
- })
+ let (word, word_len) = if words.len() == 1 || shellwords.ends_with_whitespace() {
+ (&Cow::Borrowed(""), 0)
+ } else {
+ (words.last().unwrap(), words.last().unwrap().len())
+ };
+
+ let argument_number = argument_number_of(&shellwords);
+
+ if let Some(completer) = TYPABLE_COMMAND_MAP
+ .get(&words[0] as &str)
+ .map(|tc| tc.completer_for_argument_number(argument_number))
+ {
+ completer(editor, word)
+ .into_iter()
+ .map(|(range, mut file)| {
+ file.content = shellwords::escape(file.content);
+
+ // offset ranges to input
+ let offset = input.len() - word_len;
+ let range = (range.start + offset)..;
+ (range, file)
+ })
+ .collect()
+ } else {
+ Vec::new()
+ }
}
}, // completion
move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
- let shellwords = Shellwords::from(input);
- let command = shellwords.command();
-
- if command.is_empty() {
+ let parts = input.split_whitespace().collect::<Vec<&str>>();
+ if parts.is_empty() {
return;
}
- // If input is `:NUMBER`, interpret as line number and go there.
- if command.parse::<usize>().is_ok() {
- if let Err(err) = typed::goto_line_number(cx, Args::from(command), event) {
- cx.editor.set_error(format!("{err}"));
+ // If command is numeric, interpret as line number and go there.
+ if parts.len() == 1 && parts[0].parse::<usize>().ok().is_some() {
+ if let Err(e) = typed::goto_line_number(cx, &[Cow::from(parts[0])], event) {
+ cx.editor.set_error(format!("{}", e));
}
return;
}
// Handle typable commands
- if let Some(cmd) = typed::TYPABLE_COMMAND_MAP.get(command) {
- if let Err(err) = (cmd.fun)(cx, shellwords.args(), event) {
- cx.editor.set_error(format!("{err}"));
+ if let Some(cmd) = typed::TYPABLE_COMMAND_MAP.get(parts[0]) {
+ let shellwords = Shellwords::from(input);
+ let args = shellwords.words();
+
+ if let Err(e) = (cmd.fun)(cx, &args[1..], event) {
+ cx.editor.set_error(format!("{}", e));
}
} else if event == PromptEvent::Validate {
- cx.editor.set_error(format!("no such command: '{command}'"));
+ cx.editor
+ .set_error(format!("no such command: '{}'", parts[0]));
}
},
);
-
prompt.doc_fn = Box::new(|input: &str| {
- let shellwords = Shellwords::from(input);
+ let part = input.split(' ').next().unwrap_or_default();
if let Some(typed::TypableCommand { doc, aliases, .. }) =
- typed::TYPABLE_COMMAND_MAP.get(shellwords.command())
+ typed::TYPABLE_COMMAND_MAP.get(part)
{
if aliases.is_empty() {
return Some((*doc).into());
@@ -3156,10 +3266,11 @@ pub(super) fn command_mode(cx: &mut Context) {
}
fn argument_number_of(shellwords: &Shellwords) -> usize {
- shellwords
- .args()
- .arg_count()
- .saturating_sub(1 - usize::from(shellwords.ends_with_whitespace()))
+ if shellwords.ends_with_whitespace() {
+ shellwords.words().len().saturating_sub(1)
+ } else {
+ shellwords.words().len().saturating_sub(2)
+ }
}
#[test]