Andrés N. Robalino 03c9eaf005
Variable completions. (#3666)
In Nu we have variables (E.g. $var-name) and these contain `Value` types.
This means we can bind to variables any structured data and column path syntax
(E.g. `$variable.path.to`) allows flexibility for "querying" said structures.

Here we offer completions for these. For example, in a Nushell session the
variable `$nu` contains environment values among other things. If we wanted to
see in the screen some environment variable (say the var `SHELL`) we do:

```
> echo $nu.env.SHELL
```

with completions we can now do: `echo $nu.env.S[\TAB]` and we get suggestions
that start at the column path `$nu.env` with vars starting with the letter `S`
in this case `SHELL` appears in the suggestions.
2021-06-23 19:21:39 +12:00

108 lines
3.4 KiB
Rust

use super::matchers::Matcher;
use crate::{Completer, CompletionContext, Suggestion};
use indexmap::set::IndexSet;
#[cfg(feature = "is_executable")]
#[allow(unused)]
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(windows)]
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().
path.is_executable()
}
#[cfg(target_arch = "wasm32")]
fn is_executable(_path: &Path) -> bool {
false
}
#[cfg(unix)]
fn is_executable(path: &Path) -> bool {
use std::os::unix::fs::PermissionsExt;
if let Ok(metadata) = path.metadata() {
let filetype = metadata.file_type();
let permissions = metadata.permissions();
// The file is executable if it is a directory or a symlink and the permissions are set for
// owner, group, or other
(filetype.is_file() || filetype.is_symlink()) && (permissions.mode() & 0o111 != 0)
} else {
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)
}