diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 44a151487..47b046c9d 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -371,7 +371,7 @@ fn directory_content(path: &Path) -> Result, std::io::Error pub mod completers { use super::Utf8PathBuf; use crate::ui::prompt::Completion; - use helix_core::command_line::{self, Token, Tokenizer}; + 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; @@ -379,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; @@ -680,27 +681,24 @@ pub mod completers { } 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. + static PROGRAMS_IN_PATH: Lazy> = Lazy::new(|| { + // Go through the entire PATH and read all files into a set. let Some(path) = std::env::var_os("PATH") else { - return Vec::new(); + return Default::default(); }; - let mut vec = std::env::split_paths(&path) - .flat_map(|s| { - std::fs::read_dir(s) - .map_or_else(|_| vec![], |res| res.into_iter().collect::>()) + 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 + } }) - .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 + .collect() }); fuzzy_match(input, PROGRAMS_IN_PATH.iter(), false) @@ -709,22 +707,15 @@ pub mod completers { .collect() } - fn get_last_argument(input: &str) -> Option { - let tokenizer = Tokenizer::new(input, false); - let last = tokenizer.last()?; - - Some(last.unwrap()) - } - /// 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 { - let Some(token) = get_last_argument(input) else { - return Vec::new(); + let token = match Tokenizer::new(input, false).last() { + Some(token) => token.unwrap(), + None => return filename(editor, input), }; let offset = token.content_start; - // 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; @@ -733,20 +724,16 @@ pub mod completers { } pub fn shell(editor: &Editor, input: &str) -> Vec { - let (_, _, complete_command) = command_line::split(input); + let (command, args, complete_command) = command_line::split(input); if complete_command { - return program(editor, input); + return program(editor, command); } - let Some(token) = get_last_argument(input) else { - return Vec::new(); - }; - - let mut completions = repeating_filenames(editor, &token.content); - + let mut completions = repeating_filenames(editor, args); for completion in completions.iter_mut() { - completion.0.start += token.content_start; + // + 1 for separator between `command` and `args` + completion.0.start += command.len() + 1; } completions