Improve auto completion for shell commands

This makes the very first argument of shell commands (like
run-shell-command or pipe) auto complete programs instead of files.
pull/12883/head
johannes 2025-02-15 21:14:37 +01:00
parent 0ee5850016
commit 28349fb8cf
3 changed files with 53 additions and 1 deletions

View File

@ -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;

View File

@ -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<()> {

View File

@ -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<Completion> {
static PROGRAMS_IN_PATH: Lazy<Vec<String>> = 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::<Vec<_>>())
})
.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::<Vec<_>>();
// 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<Completion> {
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)
}
}