Mercurial > forks > helix
changeset 6749:4d779359617c
Improve auto completion for shell commands (#12883)
Co-authored-by: Michael Davis <[email protected]>
author | Sumandora <johannesmiesenhardt@gmail.com> |
---|---|
date | Sun, 27 Apr 2025 21:04:56 +0200 |
parents | f1e113eff6fb |
children | 34defb465c67 |
files | helix-term/src/commands.rs helix-term/src/commands/typed.rs helix-term/src/ui/mod.rs |
diffstat | 3 files changed, 68 insertions(+), 4 deletions(-) [+] |
line wrap: on
line diff
--- a/helix-term/src/commands.rs Sun Apr 27 20:23:47 2025 +0200 +++ b/helix-term/src/commands.rs Sun Apr 27 21:04:56 2025 +0200 @@ -6350,7 +6350,7 @@ cx, prompt, Some('|'), - ui::completers::filename, + ui::completers::shell, move |cx, input: &str, event: PromptEvent| { if event != PromptEvent::Validate { return;
--- a/helix-term/src/commands/typed.rs Sun Apr 27 20:23:47 2025 +0200 +++ b/helix-term/src/commands/typed.rs Sun Apr 27 21:04:56 2025 +0200 @@ -2566,6 +2566,9 @@ Ok(()) } +// TODO: SHELL_SIGNATURE should specify var args for arguments, so that just completers::filename can be used, +// but Signature does not yet allow for var args. + /// This command handles all of its input as-is with no quoting or flags. const SHELL_SIGNATURE: Signature = Signature { positionals: (1, Some(2)), @@ -2574,10 +2577,10 @@ }; const SHELL_COMPLETER: CommandCompleter = CommandCompleter::positional(&[ - // Command name (TODO: consider a command completer - Kakoune has prior art) - completers::none, + // Command name + completers::program, // Shell argument(s) - completers::filename, + completers::repeating_filenames, ]); pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
--- a/helix-term/src/ui/mod.rs Sun Apr 27 20:23:47 2025 +0200 +++ b/helix-term/src/ui/mod.rs Sun Apr 27 21:04:56 2025 +0200 @@ -371,6 +371,7 @@ pub mod completers { use super::Utf8PathBuf; use crate::ui::prompt::Completion; + use helix_core::command_line::{self, Tokenizer}; use helix_core::fuzzy::fuzzy_match; use helix_core::syntax::LanguageServerFeature; use helix_view::document::SCRATCH_BUFFER_NAME; @@ -378,6 +379,7 @@ use helix_view::{editor::Config, Editor}; use once_cell::sync::Lazy; use std::borrow::Cow; + use std::collections::BTreeSet; use tui::text::Span; pub type Completer = fn(&Editor, &str) -> Vec<Completion>; @@ -677,4 +679,63 @@ .map(|(name, _)| ((0..), name.into())) .collect() } + + pub fn program(_editor: &Editor, input: &str) -> Vec<Completion> { + static PROGRAMS_IN_PATH: Lazy<BTreeSet<String>> = Lazy::new(|| { + // Go through the entire PATH and read all files into a set. + let Some(path) = std::env::var_os("PATH") else { + return Default::default(); + }; + + std::env::split_paths(&path) + .filter_map(|path| std::fs::read_dir(path).ok()) + .flatten() + .filter_map(|res| { + let entry = res.ok()?; + if entry.metadata().ok()?.is_file() { + entry.file_name().into_string().ok() + } else { + None + } + }) + .collect() + }); + + fuzzy_match(input, PROGRAMS_IN_PATH.iter(), false) + .into_iter() + .map(|(name, _)| ((0..), name.clone().into())) + .collect() + } + + /// This expects input to be a raw string of arguments, because this is what Signature's raw_after does. + pub fn repeating_filenames(editor: &Editor, input: &str) -> Vec<Completion> { + let token = match Tokenizer::new(input, false).last() { + Some(token) => token.unwrap(), + None => return filename(editor, input), + }; + + let offset = token.content_start; + + let mut completions = filename(editor, &input[offset..]); + for completion in completions.iter_mut() { + completion.0.start += offset; + } + completions + } + + pub fn shell(editor: &Editor, input: &str) -> Vec<Completion> { + let (command, args, complete_command) = command_line::split(input); + + if complete_command { + return program(editor, command); + } + + let mut completions = repeating_filenames(editor, args); + for completion in completions.iter_mut() { + // + 1 for separator between `command` and `args` + completion.0.start += command.len() + 1; + } + + completions + } }