diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 2e15dcdcc..4faf63153 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -6315,7 +6315,7 @@ fn shell_prompt(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBeha cx, prompt, Some('|'), - ui::completers::filename, + ui::completers::shell, move |cx, input: &str, event: PromptEvent| { if event != PromptEvent::Validate { return; diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index e1c09a04d..916cad76a 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -68,6 +68,13 @@ impl CommandCompleter { var_args: completer, } } + + const fn hybrid(completers: &'static [Completer], fallback: Completer) -> Self { + Self { + positional_args: completers, + var_args: fallback, + } + } } fn quit(cx: &mut compositor::Context, _args: Args, event: PromptEvent) -> anyhow::Result<()> { diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index a76adbe21..753feaadc 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -372,6 +372,7 @@ pub mod completers { use super::Utf8PathBuf; use crate::ui::prompt::Completion; use helix_core::fuzzy::fuzzy_match; + use helix_core::shellwords::Shellwords; use helix_core::syntax::LanguageServerFeature; use helix_view::document::SCRATCH_BUFFER_NAME; use helix_view::theme; @@ -677,4 +678,48 @@ pub mod completers { .map(|(name, _)| ((0..), name.into())) .collect() } + + pub fn program(_editor: &Editor, input: &str) -> Vec { + static PROGRAMS_IN_PATH: Lazy> = Lazy::new(|| { + // Go through the entire PATH and read all files into a vec. + let mut vec = std::env::var("PATH") + .unwrap_or("".to_owned()) + .split(":") + .flat_map(|s| { + std::fs::read_dir(s) + .map_or_else(|_| vec![], |res| res.into_iter().collect::>()) + }) + .filter_map(|it| it.ok()) + .map(|f| f.path()) + .filter(|p| !p.is_dir()) + .filter_map(|p| p.file_name().and_then(|s| s.to_str().map(|s| s.to_owned()))) + .collect::>(); + + // Paths can share programs like /bin and /usr/bin sometimes contain the same. + vec.dedup(); + + vec + }); + + fuzzy_match(input, PROGRAMS_IN_PATH.iter(), false) + .into_iter() + .map(|(name, _)| ((0..), name.clone().into())) + .collect() + } + + pub fn shell(editor: &Editor, input: &str) -> Vec { + let shellwords = Shellwords::from(input); + let words = shellwords.words(); + + if words.len() > 1 || shellwords.ends_with_whitespace() { + let offset = words[0].len() + 1; + // Theoretically one could now parse bash completion scripts, but filename should suffice for now. + let mut completions = filename(editor, &input[offset..]); + for completion in completions.iter_mut() { + completion.0.start += offset; + } + return completions; + } + program(editor, input) + } }