Unnamed repository; edit this file 'description' to name the repository.
Improve auto completion for shell commands (#12883)
Co-authored-by: Michael Davis <[email protected]>
Sumandora 10 months ago
parent 448e3c2 · commit 99b5718
-rw-r--r--helix-term/src/commands.rs2
-rw-r--r--helix-term/src/commands/typed.rs9
-rw-r--r--helix-term/src/ui/mod.rs61
3 files changed, 68 insertions, 4 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index f80c277b..2669d8dd 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -6350,7 +6350,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 5274c280..8b58c7e0 100644
--- a/helix-term/src/commands/typed.rs
+++ b/helix-term/src/commands/typed.rs
@@ -2566,6 +2566,9 @@ fn noop(_cx: &mut compositor::Context, _args: Args, _event: PromptEvent) -> anyh
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_SIGNATURE: Signature = Signature {
};
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] = &[
diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs
index a76adbe2..47b046c9 100644
--- a/helix-term/src/ui/mod.rs
+++ b/helix-term/src/ui/mod.rs
@@ -371,6 +371,7 @@ fn directory_content(path: &Path) -> Result<Vec<(PathBuf, bool)>, std::io::Error
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 @@ pub mod completers {
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 @@ pub mod completers {
.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
+ }
}