mirror of
https://github.com/danbulant/nushell
synced 2026-06-12 03:01:04 +00:00
On Windows, we used the `is-exeuctable` crate but on Unix, we duplicated the check that it did, with one difference: We also looked at whether or not it was a symlink. The `is-executable` crate uses `std::fs::metadata` which follows symlinks, so this scenario should never occur here, as it will return the metadata for the target file. Using the `is-executable` crate on both Unix and Windows lets us make it non-optional. This lets us remove the `executable-support` feature. (It is worth noting that this code didn't compile on Windows when the `executable-support` feature was not specified.) Right now, there is an alternate code path for `target_arch` being `wasm32`. This isn't exactly correct as it should probably handle something different for when the `target_os` is `wasi`.
91 lines
3 KiB
Rust
91 lines
3 KiB
Rust
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<Context> Completer<Context> for CommandCompleter
|
|
where
|
|
Context: CompletionContext,
|
|
{
|
|
fn complete(&self, ctx: &Context, partial: &str, matcher: &dyn Matcher) -> Vec<Suggestion> {
|
|
let registry = ctx.signature_registry();
|
|
let mut commands: IndexSet<String> = 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.into_iter());
|
|
|
|
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<IndexSet<String>> {
|
|
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<String> = 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)
|
|
}
|