use super::matchers::Matcher; use crate::{Completer, CompletionContext, Suggestion}; use indexmap::set::IndexSet; #[cfg(not(target_arch = "wasm32"))] use is_executable::IsExecutable; use nu_test_support::NATIVE_PATH_ENV_VAR; use std::iter::FromIterator; use std::path::Path; pub struct CommandCompleter; impl Completer for CommandCompleter where Context: CompletionContext, { fn complete(&self, ctx: &Context, partial: &str, matcher: &dyn Matcher) -> Vec { let registry = ctx.signature_registry(); let mut commands: IndexSet = IndexSet::from_iter(registry.names()); // Command suggestions can come from three possible sets: // 1. internal command names, // 2. external command names relative to PATH env var, and // 3. any other executable (that matches what's been typed so far). let path_executables = find_path_executables().unwrap_or_default(); // TODO quote these, if necessary commands.extend(path_executables); let mut suggestions: Vec<_> = commands .into_iter() .filter(|v| matcher.matches(partial, v)) .map(|v| Suggestion { replacement: v.clone(), display: v, }) .collect(); if !partial.is_empty() { let path_completer = crate::path::PathCompleter; let path_results = path_completer.path_suggestions(partial, matcher); let iter = path_results.into_iter().filter_map(|path_suggestion| { let path = path_suggestion.path; if path.is_dir() || is_executable(&path) { Some(path_suggestion.suggestion) } else { None } }); suggestions.extend(iter); } suggestions } } #[cfg(not(target_arch = "wasm32"))] fn is_executable(path: &Path) -> bool { // This call to a crate essentially checks the PATHEXT on Windows and does some // low level WinAPI calls to determine if the file is executable. It seems quite // a bit faster than calling path.metadata(). // On Unix, this checks the file metadata. The underlying code traverses symlinks. path.is_executable() } #[cfg(target_arch = "wasm32")] fn is_executable(_path: &Path) -> bool { false } // TODO cache these, but watch for changes to PATH fn find_path_executables() -> Option> { let path_var = std::env::var_os(NATIVE_PATH_ENV_VAR)?; let paths: Vec<_> = std::env::split_paths(&path_var).collect(); let mut executables: IndexSet = IndexSet::new(); for path in paths { if let Ok(mut contents) = std::fs::read_dir(path) { while let Some(Ok(item)) = contents.next() { if is_executable(&item.path()) { if let Ok(name) = item.file_name().into_string() { executables.insert(name); } } } } } Some(executables) }