From 2edddf16d0a750b7ca1d2ff8e911a24904cff986 Mon Sep 17 00:00:00 2001 From: Rolo Date: Mon, 6 Jan 2025 19:40:56 -0800 Subject: [PATCH] refactor: transition to a Vec wrapper for Args --- helix-term/src/commands/typed.rs | 215 +++++++++++++------------------ 1 file changed, 89 insertions(+), 126 deletions(-) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 62c9bc02a8a74..7502e16c042f3 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -392,36 +392,32 @@ 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: Args, event: PromptEvent) -> anyhow::Result<()> { if event != PromptEvent::Validate { return Ok(()); } - write_impl(cx, args.next().as_deref(), false) + write_impl(cx, args.first(), false) } -fn force_write( - cx: &mut compositor::Context, - mut args: Args, - event: PromptEvent, -) -> anyhow::Result<()> { +fn force_write(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> { if event != PromptEvent::Validate { return Ok(()); } - write_impl(cx, args.next().as_deref(), true) + write_impl(cx, args.first(), true) } fn write_buffer_close( cx: &mut compositor::Context, - mut args: Args, + args: Args, event: PromptEvent, ) -> anyhow::Result<()> { if event != PromptEvent::Validate { return Ok(()); } - write_impl(cx, args.next().as_deref(), 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) @@ -429,14 +425,14 @@ fn write_buffer_close( fn force_write_buffer_close( cx: &mut compositor::Context, - mut args: Args, + args: Args, event: PromptEvent, ) -> anyhow::Result<()> { if event != PromptEvent::Validate { return Ok(()); } - write_impl(cx, args.next().as_deref(), 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) @@ -469,7 +465,7 @@ fn format(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyh fn set_indent_style( cx: &mut compositor::Context, - mut args: Args, + args: Args, event: PromptEvent, ) -> anyhow::Result<()> { if event != PromptEvent::Validate { @@ -490,7 +486,7 @@ fn set_indent_style( } // Attempt to parse argument as an indent style. - let style = match args.next().as_deref() { + let style = match args.first() { Some(arg) if "tabs".starts_with(&arg.to_lowercase()) => Some(Tabs), Some("0") => Some(Tabs), Some(arg) => arg @@ -511,7 +507,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: Args, event: PromptEvent, ) -> anyhow::Result<()> { if event != PromptEvent::Validate { @@ -542,7 +538,7 @@ fn set_line_ending( } let arg = args - .next() + .first() .context("argument missing")? .to_ascii_lowercase(); @@ -613,30 +609,26 @@ fn later(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow Ok(()) } -fn write_quit( - cx: &mut compositor::Context, - mut args: Args, - event: PromptEvent, -) -> anyhow::Result<()> { +fn write_quit(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> { if event != PromptEvent::Validate { return Ok(()); } - write_impl(cx, args.next().as_deref(), false)?; + write_impl(cx, args.first(), false)?; cx.block_try_flush_writes()?; quit(cx, Args::empty(), event) } fn force_write_quit( cx: &mut compositor::Context, - mut args: Args, + args: Args, event: PromptEvent, ) -> anyhow::Result<()> { if event != PromptEvent::Validate { return Ok(()); } - write_impl(cx, args.next().as_deref(), true)?; + write_impl(cx, args.first(), true)?; cx.block_try_flush_writes()?; force_quit(cx, Args::empty(), event) } @@ -822,13 +814,13 @@ fn force_quit_all( 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: Args, event: PromptEvent) -> anyhow::Result<()> { if event != PromptEvent::Validate { return Ok(()); } let exit_code = args - .next() + .first() .and_then(|code| code.parse::().ok()) .unwrap_or(1); @@ -836,17 +828,13 @@ fn cquit(cx: &mut compositor::Context, mut args: Args, event: PromptEvent) -> an quit_all_impl(cx, false) } -fn force_cquit( - cx: &mut compositor::Context, - mut args: Args, - event: PromptEvent, -) -> anyhow::Result<()> { +fn force_cquit(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> { if event != PromptEvent::Validate { return Ok(()); } let exit_code = args - .next() + .first() .and_then(|code| code.parse::().ok()) .unwrap_or(1); cx.editor.exit_code = exit_code; @@ -854,7 +842,7 @@ 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: Args, event: PromptEvent) -> anyhow::Result<()> { let true_color = cx.editor.config.load().true_color || crate::true_color(); match event { PromptEvent::Abort => { @@ -864,7 +852,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().as_deref() { + } 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"); @@ -874,7 +862,7 @@ fn theme(cx: &mut compositor::Context, mut args: Args, event: PromptEvent) -> an }; } PromptEvent::Validate => { - if let Some(theme_name) = args.next().as_deref() { + if let Some(theme_name) = args.first() { let theme = cx .editor .theme_loader @@ -1040,16 +1028,14 @@ fn show_clipboard_provider( fn change_current_directory( cx: &mut compositor::Context, - mut args: Args, + args: Args, event: PromptEvent, ) -> anyhow::Result<()> { if event != PromptEvent::Validate { return Ok(()); } - let path = args.next(); - - let dir = match path.as_deref() { + let dir = match args.first() { Some("-") => cx .editor .get_last_cwd() @@ -1092,7 +1078,7 @@ fn show_current_directory( /// Sets the [`Document`]'s encoding.. fn set_encoding( cx: &mut compositor::Context, - mut args: Args, + args: Args, event: PromptEvent, ) -> anyhow::Result<()> { if event != PromptEvent::Validate { @@ -1100,7 +1086,7 @@ fn set_encoding( } let doc = doc_mut!(cx.editor); - if let Some(label) = args.next().as_deref() { + if let Some(label) = args.first() { doc.set_encoding(label) } else { let encoding = doc.encoding().name().to_owned(); @@ -1662,37 +1648,30 @@ fn debug_eval(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> a Ok(()) } -fn debug_start( - cx: &mut compositor::Context, - mut args: Args, - event: PromptEvent, -) -> anyhow::Result<()> { +fn debug_start(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> { if event != PromptEvent::Validate { return Ok(()); } - dap_start_impl( - cx, - args.next().as_deref(), - None, - Some(args.map(Into::into).collect()), - ) + + let name = args.first(); + let params = args.iter().cloned().collect(); + + dap_start_impl(cx, name, None, Some(params)) } fn debug_remote( cx: &mut compositor::Context, - mut args: Args, + args: Args, 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().as_deref(), - address, - Some(args.map(Into::into).collect()), - ) + + let address = args.first().map(|addr| addr.parse()).transpose()?; + let params = args.iter().cloned().collect(); + + dap_start_impl(cx, args.get(1), address, Some(params)) } fn tutor(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyhow::Result<()> { @@ -1717,17 +1696,14 @@ fn abort_goto_line_number_preview(cx: &mut compositor::Context) { } } -fn update_goto_line_number_preview( - cx: &mut compositor::Context, - mut args: Args, -) -> anyhow::Result<()> { +fn update_goto_line_number_preview(cx: &mut compositor::Context, args: Args) -> anyhow::Result<()> { cx.editor.last_selection.get_or_insert_with(|| { let (view, doc) = current!(cx.editor); doc.selection(view.id).clone() }); let scrolloff = cx.editor.config().scrolloff; - let line = args.next().unwrap().parse::()?; + let line = args[0].parse::()?; goto_line_without_jumplist(cx.editor, NonZeroUsize::new(line)); let (view, doc) = current!(cx.editor); @@ -1772,20 +1748,16 @@ 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, - event: PromptEvent, -) -> anyhow::Result<()> { +fn get_option(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> { if event != PromptEvent::Validate { return Ok(()); } - if args.arg_count() != 1 { + if args.count() != 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()); @@ -1798,20 +1770,18 @@ fn get_option( /// Change config at runtime. Access nested values by dot syntax, for /// example to disable smart case search, use `:set search.smart-case false`. -fn set_option( - cx: &mut compositor::Context, - mut args: Args, - event: PromptEvent, -) -> anyhow::Result<()> { +fn set_option(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> { if event != PromptEvent::Validate { return Ok(()); } - let Some(key) = args.next().map(|arg| arg.to_lowercase()) else { + let mut parser = args.raw_parser(); + + let Some(key) = parser.next().map(|arg| arg.to_lowercase()) else { anyhow::bail!("Bad arguments. Usage: `:set key field`, didn't provide `key`"); }; - let field = args.rest(); + let field = parser.rest(); if field.is_empty() { anyhow::bail!("Bad arguments. Usage: `:set key field`, didn't provide `field`"); @@ -1854,7 +1824,7 @@ fn set_option( /// - `:toggle line-number relative absolute` (string) fn toggle_option( cx: &mut compositor::Context, - mut args: Args, + args: Args, event: PromptEvent, ) -> anyhow::Result<()> { if event != PromptEvent::Validate { @@ -1864,7 +1834,8 @@ 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 pointer = format!("/{}", key.replace('.', "/")); @@ -1875,7 +1846,7 @@ fn toggle_option( *value = match value { Value::Bool(ref value) => { ensure!( - args.next().is_none(), + args.get(1).is_none(), "Bad arguments. For boolean configurations use: `:toggle key`" ); Value::Bool(!value) @@ -1883,37 +1854,43 @@ fn toggle_option( Value::String(ref value) => { ensure!( // key + arguments - args.arg_count() >= 3, + args.count() >= 3, "Bad arguments. For string configurations use: `:toggle key val1 val2 ...`", ); Value::String( - args.clone() + args[1..] + .iter() .skip_while(|e| e.as_ref() != value) .nth(1) - .unwrap_or_else(|| args.next().unwrap()) + .map(|option| option.as_ref()) + .unwrap_or_else(|| args.get(1).unwrap()) .to_string(), ) } Value::Number(ref value) => { ensure!( // key + arguments - args.arg_count() >= 3, + args.count() >= 3, "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.iter() + .skip_while(|e| e.as_ref() != value) .nth(1) - .unwrap_or_else(|| args.next().unwrap()) + .map(|option| option.as_ref()) + .unwrap_or_else(|| args.get(1).unwrap()) .parse()?, ) } Value::Array(value) => { - let mut lists = serde_json::Deserializer::from_str(args.rest()).into_iter::(); + let mut parser = args.raw_parser(); + parser.next(); + + let mut lists = serde_json::Deserializer::from_str(parser.rest()).into_iter::(); let (Some(first), Some(second)) = (lists.next().transpose()?, lists.next().transpose()?) @@ -1953,11 +1930,7 @@ fn toggle_option( } /// Change the language of the current buffer at runtime. -fn language( - cx: &mut compositor::Context, - mut args: Args, - event: PromptEvent, -) -> anyhow::Result<()> { +fn language(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> { if event != PromptEvent::Validate { return Ok(()); } @@ -1969,14 +1942,13 @@ fn language( return Ok(()); } - if args.arg_count() != 1 { + if args.count() != 1 { anyhow::bail!("Bad arguments. Usage: `:set-language language`"); } let doc = doc_mut!(cx.editor); - let arg = args.next(); - let language_id = arg.as_deref().unwrap(); + let language_id = &args[0]; if language_id == DEFAULT_LANGUAGE_NAME { doc.set_language(None, None); } else { @@ -2043,7 +2015,7 @@ 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: Args, event: PromptEvent) -> anyhow::Result<()> { if event != PromptEvent::Validate { return Ok(()); } @@ -2057,7 +2029,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::()) .transpose()? .or_else(|| doc.language_config().and_then(|config| config.text_width)) @@ -2308,7 +2280,7 @@ fn reset_diff_change( fn clear_register( cx: &mut compositor::Context, - mut args: Args, + args: Args, event: PromptEvent, ) -> anyhow::Result<()> { if event != PromptEvent::Validate { @@ -2316,7 +2288,7 @@ fn clear_register( } ensure!( - args.arg_count() <= 1, + args.count() <= 1, ":clear-register takes at most 1 argument" ); @@ -2326,7 +2298,7 @@ fn clear_register( return Ok(()); } - let register = args.next().unwrap(); + let register = args.first().unwrap(); ensure!( register.chars().count() == 1, @@ -2362,24 +2334,19 @@ fn redraw(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyh Ok(()) } -fn move_buffer( - cx: &mut compositor::Context, - mut args: Args, - event: PromptEvent, -) -> anyhow::Result<()> { +fn move_buffer(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> { if event != PromptEvent::Validate { return Ok(()); } - ensure!(args.arg_count() == 1, format!(":move takes one argument")); + ensure!(args.count() == 1, format!(":move takes one argument")); let old_path = doc!(cx.editor) .path() .context("Scratch buffer cannot be moved. Use :write instead")? .clone(); - let path = args.next(); - let new_path = path.as_deref().unwrap(); + let new_path = &args[0]; if let Err(err) = cx.editor.move_path(&old_path, new_path.as_ref()) { bail!("Could not move file: {err}"); @@ -2389,14 +2356,14 @@ fn move_buffer( fn yank_diagnostic( cx: &mut compositor::Context, - mut args: Args, + args: Args, 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() @@ -2427,7 +2394,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: Args, event: PromptEvent) -> anyhow::Result<()> { if event != PromptEvent::Validate { return Ok(()); } @@ -2436,11 +2403,9 @@ 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.count() == 1, "only the file name is expected"); - let path = args.next(); - let filename = path.as_deref().unwrap(); - let path = helix_stdx::path::expand_tilde(Path::new(filename)); + let path = helix_stdx::path::expand_tilde(Path::new(args.first().unwrap())); ensure!( path.exists() && path.is_file(), @@ -3195,9 +3160,10 @@ pub(super) fn command_mode(cx: &mut Context) { |editor: &Editor, input: &str| { let shellwords = Shellwords::from(input); let command = shellwords.command(); + let args = shellwords.args(); if command.is_empty() - || (shellwords.args().next().is_none() && !shellwords.ends_with_whitespace()) + || (shellwords.args().first().is_none() && !shellwords.ends_with_whitespace()) { fuzzy_match( input, @@ -3210,16 +3176,13 @@ pub(super) fn command_mode(cx: &mut Context) { } else { // Otherwise, use the command's completer and the last shellword // as completion input. - let (len, word) = shellwords - .args() - .last() - .map_or((0, Cow::default()), |last| (last.len(), last)); + let (word, len) = 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.as_ref()) + completer(editor, word) .into_iter() .map(|(range, mut file)| { file.content = shellwords::escape(file.content); @@ -3252,7 +3215,7 @@ pub(super) fn command_mode(cx: &mut Context) { // Handle typable commands if let Some(cmd) = typed::TYPABLE_COMMAND_MAP.get(command) { - if let Err(err) = cmd.ensure_params_within_range(args.arg_count()) { + if let Err(err) = cmd.ensure_params_within_range(args.count()) { cx.editor.set_error(err.to_string()); return; } @@ -3289,7 +3252,7 @@ pub(super) fn command_mode(cx: &mut Context) { fn argument_number_of(shellwords: &Shellwords) -> usize { shellwords .args() - .arg_count() + .count() .saturating_sub(1 - usize::from(shellwords.ends_with_whitespace())) }