mirror of https://github.com/helix-editor/helix
Improve auto completion for shell commands (#12883)
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>pull/13432/head
parent
448e3c2ef5
commit
99b57181d5
|
@ -6350,7 +6350,7 @@ fn shell_prompt(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBeha
|
||||||
cx,
|
cx,
|
||||||
prompt,
|
prompt,
|
||||||
Some('|'),
|
Some('|'),
|
||||||
ui::completers::filename,
|
ui::completers::shell,
|
||||||
move |cx, input: &str, event: PromptEvent| {
|
move |cx, input: &str, event: PromptEvent| {
|
||||||
if event != PromptEvent::Validate {
|
if event != PromptEvent::Validate {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -2566,6 +2566,9 @@ fn noop(_cx: &mut compositor::Context, _args: Args, _event: PromptEvent) -> anyh
|
||||||
Ok(())
|
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.
|
/// This command handles all of its input as-is with no quoting or flags.
|
||||||
const SHELL_SIGNATURE: Signature = Signature {
|
const SHELL_SIGNATURE: Signature = Signature {
|
||||||
positionals: (1, Some(2)),
|
positionals: (1, Some(2)),
|
||||||
|
@ -2574,10 +2577,10 @@ const SHELL_SIGNATURE: Signature = Signature {
|
||||||
};
|
};
|
||||||
|
|
||||||
const SHELL_COMPLETER: CommandCompleter = CommandCompleter::positional(&[
|
const SHELL_COMPLETER: CommandCompleter = CommandCompleter::positional(&[
|
||||||
// Command name (TODO: consider a command completer - Kakoune has prior art)
|
// Command name
|
||||||
completers::none,
|
completers::program,
|
||||||
// Shell argument(s)
|
// Shell argument(s)
|
||||||
completers::filename,
|
completers::repeating_filenames,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
|
pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
|
||||||
|
|
|
@ -371,6 +371,7 @@ fn directory_content(path: &Path) -> Result<Vec<(PathBuf, bool)>, std::io::Error
|
||||||
pub mod completers {
|
pub mod completers {
|
||||||
use super::Utf8PathBuf;
|
use super::Utf8PathBuf;
|
||||||
use crate::ui::prompt::Completion;
|
use crate::ui::prompt::Completion;
|
||||||
|
use helix_core::command_line::{self, Tokenizer};
|
||||||
use helix_core::fuzzy::fuzzy_match;
|
use helix_core::fuzzy::fuzzy_match;
|
||||||
use helix_core::syntax::LanguageServerFeature;
|
use helix_core::syntax::LanguageServerFeature;
|
||||||
use helix_view::document::SCRATCH_BUFFER_NAME;
|
use helix_view::document::SCRATCH_BUFFER_NAME;
|
||||||
|
@ -378,6 +379,7 @@ pub mod completers {
|
||||||
use helix_view::{editor::Config, Editor};
|
use helix_view::{editor::Config, Editor};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
use tui::text::Span;
|
use tui::text::Span;
|
||||||
|
|
||||||
pub type Completer = fn(&Editor, &str) -> Vec<Completion>;
|
pub type Completer = fn(&Editor, &str) -> Vec<Completion>;
|
||||||
|
@ -677,4 +679,63 @@ pub mod completers {
|
||||||
.map(|(name, _)| ((0..), name.into()))
|
.map(|(name, _)| ((0..), name.into()))
|
||||||
.collect()
|
.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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue