mirror of
https://github.com/nushell/nushell.git
synced 2025-08-18 06:40:16 +02:00
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.
This commit is contained in:
committed by
GitHub
parent
2b021472d6
commit
03c9eaf005
@@ -10,6 +10,7 @@ version = "0.33.0"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { version="0.33.0", path="../nu-engine" }
|
||||
nu-data = { version="0.33.0", path="../nu-data" }
|
||||
nu-errors = { version="0.33.0", path="../nu-errors" }
|
||||
nu-parser = { version="0.33.0", path="../nu-parser" }
|
||||
@@ -19,5 +20,8 @@ nu-source = { version="0.33.0", path="../nu-source" }
|
||||
nu-test-support = { version="0.33.0", path="../nu-test-support" }
|
||||
|
||||
dirs-next = "2.0.0"
|
||||
indexmap = { version="1.6.1", features=["serde-1"] }
|
||||
indexmap = { version = "1.6.1", features = ["serde-1"] }
|
||||
is_executable = { version="1.0.1", optional=true }
|
||||
|
||||
[dev-dependencies]
|
||||
parking_lot = "0.11.1"
|
||||
|
@@ -16,7 +16,7 @@ where
|
||||
{
|
||||
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.get_names());
|
||||
let mut commands: IndexSet<String> = IndexSet::from_iter(registry.names());
|
||||
|
||||
// Command suggestions can come from three possible sets:
|
||||
// 1. internal command names,
|
||||
|
@@ -8,6 +8,7 @@ use crate::flag::FlagCompleter;
|
||||
use crate::matchers;
|
||||
use crate::matchers::Matcher;
|
||||
use crate::path::{PathCompleter, PathSuggestion};
|
||||
use crate::variable::VariableCompleter;
|
||||
use crate::{Completer, CompletionContext, Suggestion};
|
||||
|
||||
pub struct NuCompleter {}
|
||||
@@ -26,7 +27,7 @@ impl NuCompleter {
|
||||
let tokens = nu_parser::lex(line, 0).0;
|
||||
|
||||
let locations = Some(nu_parser::parse_block(tokens).0)
|
||||
.map(|block| nu_parser::classify_block(&block, context.signature_registry()))
|
||||
.map(|block| nu_parser::classify_block(&block, context.scope()))
|
||||
.map(|(block, _)| engine::completion_location(line, &block, pos))
|
||||
.unwrap_or_default();
|
||||
|
||||
@@ -121,7 +122,10 @@ impl NuCompleter {
|
||||
.collect()
|
||||
}
|
||||
|
||||
LocationType::Variable => Vec::new(),
|
||||
LocationType::Variable => {
|
||||
let variable_completer = VariableCompleter;
|
||||
variable_completer.complete(context, &partial, matcher.to_owned())
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
@@ -301,10 +301,6 @@ mod tests {
|
||||
}
|
||||
|
||||
impl ParserScope for VecRegistry {
|
||||
fn get_names(&self) -> Vec<String> {
|
||||
self.0.iter().cloned().map(|s| s.name).collect()
|
||||
}
|
||||
|
||||
fn has_signature(&self, name: &str) -> bool {
|
||||
self.0.iter().any(|v| v.name == name)
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ where
|
||||
Context: CompletionContext,
|
||||
{
|
||||
fn complete(&self, ctx: &Context, partial: &str, matcher: &dyn Matcher) -> Vec<Suggestion> {
|
||||
if let Some(sig) = ctx.signature_registry().get_signature(&self.cmd) {
|
||||
if let Some(sig) = ctx.signature_registry().get(&self.cmd) {
|
||||
let mut suggestions = Vec::new();
|
||||
for (name, (named_type, _desc)) in sig.named.iter() {
|
||||
suggestions.push(format!("--{}", name));
|
||||
|
@@ -4,6 +4,10 @@ pub(crate) mod engine;
|
||||
pub(crate) mod flag;
|
||||
pub(crate) mod matchers;
|
||||
pub(crate) mod path;
|
||||
pub(crate) mod variable;
|
||||
|
||||
use nu_engine::EvaluationContext;
|
||||
use nu_protocol::{SignatureRegistry, VariableRegistry};
|
||||
|
||||
use matchers::Matcher;
|
||||
|
||||
@@ -15,8 +19,20 @@ pub struct Suggestion {
|
||||
pub replacement: String,
|
||||
}
|
||||
|
||||
impl Suggestion {
|
||||
fn new(display: impl Into<String>, replacement: impl Into<String>) -> Self {
|
||||
Self {
|
||||
display: display.into(),
|
||||
replacement: replacement.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CompletionContext {
|
||||
fn signature_registry(&self) -> &dyn nu_parser::ParserScope;
|
||||
fn signature_registry(&self) -> &dyn SignatureRegistry;
|
||||
fn scope(&self) -> &dyn nu_parser::ParserScope;
|
||||
fn source(&self) -> &EvaluationContext;
|
||||
fn variable_registry(&self) -> &dyn VariableRegistry;
|
||||
}
|
||||
|
||||
pub trait Completer<Context: CompletionContext> {
|
||||
|
182
crates/nu-completion/src/variable.rs
Normal file
182
crates/nu-completion/src/variable.rs
Normal file
@@ -0,0 +1,182 @@
|
||||
use nu_engine::value_shell::ValueShell;
|
||||
use nu_protocol::ColumnPath;
|
||||
use nu_source::SpannedItem;
|
||||
|
||||
use super::matchers::Matcher;
|
||||
use crate::{Completer, CompletionContext, Suggestion};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn build_path(head: &str, members: &Path, entry: &str) -> String {
|
||||
let mut full_path = head.to_string();
|
||||
full_path.push_str(
|
||||
&members
|
||||
.join(entry)
|
||||
.display()
|
||||
.to_string()
|
||||
.replace(std::path::MAIN_SEPARATOR, "."),
|
||||
);
|
||||
full_path
|
||||
}
|
||||
|
||||
fn collect_entries(value_fs: &ValueShell, head: &str, path: &Path) -> Vec<String> {
|
||||
value_fs
|
||||
.members_under(&path)
|
||||
.iter()
|
||||
.flat_map(|entry| {
|
||||
entry
|
||||
.row_entries()
|
||||
.map(|(entry_name, _)| build_path(&head, &path, entry_name))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub struct VariableCompleter;
|
||||
|
||||
impl<Context> Completer<Context> for VariableCompleter
|
||||
where
|
||||
Context: CompletionContext,
|
||||
{
|
||||
fn complete(&self, ctx: &Context, partial: &str, matcher: &dyn Matcher) -> Vec<Suggestion> {
|
||||
let registry = ctx.variable_registry();
|
||||
let variables_available = registry.variables();
|
||||
let partial_column_path = ColumnPath::with_head(&partial.to_string().spanned_unknown());
|
||||
|
||||
partial_column_path
|
||||
.map(|(head, members)| {
|
||||
variables_available
|
||||
.iter()
|
||||
.filter(|candidate| matcher.matches(&head, candidate))
|
||||
.into_iter()
|
||||
.filter_map(|candidate| {
|
||||
if !partial.ends_with('.') && members.is_empty() {
|
||||
Some(vec![candidate.to_string()])
|
||||
} else {
|
||||
let value = registry.get_variable(&candidate[..].spanned_unknown());
|
||||
let path = PathBuf::from(members.path());
|
||||
|
||||
value.map(|candidate| {
|
||||
let fs = ValueShell::new(candidate);
|
||||
|
||||
fs.find(&path)
|
||||
.map(|fs| collect_entries(fs, &head, &path))
|
||||
.or_else(|| {
|
||||
path.parent().map(|parent| {
|
||||
fs.find(parent)
|
||||
.map(|fs| collect_entries(fs, &head, &parent))
|
||||
.unwrap_or_default()
|
||||
})
|
||||
})
|
||||
.unwrap_or_default()
|
||||
})
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.filter_map(|candidate| {
|
||||
if matcher.matches(&partial, &candidate) {
|
||||
Some(Suggestion::new(&candidate, &candidate))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Completer, Suggestion as S, VariableCompleter};
|
||||
use crate::matchers::case_insensitive::Matcher as CaseInsensitiveMatcher;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use nu_engine::{
|
||||
evaluation_context::EngineState, ConfigHolder, EvaluationContext, FakeHost, Host, Scope,
|
||||
ShellManager,
|
||||
};
|
||||
use nu_protocol::{SignatureRegistry, VariableRegistry};
|
||||
use parking_lot::Mutex;
|
||||
use std::ffi::OsString;
|
||||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
|
||||
struct CompletionContext<'a>(&'a EvaluationContext);
|
||||
|
||||
impl<'a> crate::CompletionContext for CompletionContext<'a> {
|
||||
fn signature_registry(&self) -> &dyn SignatureRegistry {
|
||||
&self.0.scope
|
||||
}
|
||||
|
||||
fn source(&self) -> &nu_engine::EvaluationContext {
|
||||
&self.0
|
||||
}
|
||||
|
||||
fn scope(&self) -> &dyn nu_parser::ParserScope {
|
||||
&self.0.scope
|
||||
}
|
||||
|
||||
fn variable_registry(&self) -> &dyn VariableRegistry {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
fn create_context_with_host(host: Box<dyn Host>) -> EvaluationContext {
|
||||
let scope = Scope::new();
|
||||
let env_vars = host.vars().iter().cloned().collect::<IndexMap<_, _>>();
|
||||
scope.add_env(env_vars);
|
||||
|
||||
EvaluationContext {
|
||||
scope,
|
||||
engine_state: Arc::new(EngineState {
|
||||
host: Arc::new(parking_lot::Mutex::new(host)),
|
||||
current_errors: Arc::new(Mutex::new(vec![])),
|
||||
ctrl_c: Arc::new(AtomicBool::new(false)),
|
||||
configs: Arc::new(Mutex::new(ConfigHolder::new())),
|
||||
shell_manager: ShellManager::basic(),
|
||||
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_envs(host: &mut FakeHost, values: Vec<(&str, &str)>) {
|
||||
values.iter().for_each(|(key, value)| {
|
||||
host.env_set(OsString::from(key), OsString::from(value));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn structure() {
|
||||
let mut host = nu_engine::FakeHost::new();
|
||||
set_envs(&mut host, vec![("COMPLETER", "VARIABLE"), ("SHELL", "NU")]);
|
||||
let context = create_context_with_host(Box::new(host));
|
||||
|
||||
assert_eq!(
|
||||
VariableCompleter {}.complete(
|
||||
&CompletionContext(&context),
|
||||
"$nu.env.",
|
||||
&CaseInsensitiveMatcher
|
||||
),
|
||||
vec![
|
||||
S::new("$nu.env.COMPLETER", "$nu.env.COMPLETER"),
|
||||
S::new("$nu.env.SHELL", "$nu.env.SHELL")
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
VariableCompleter {}.complete(
|
||||
&CompletionContext(&context),
|
||||
"$nu.env.CO",
|
||||
&CaseInsensitiveMatcher
|
||||
),
|
||||
vec![S::new("$nu.env.COMPLETER", "$nu.env.COMPLETER"),]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
VariableCompleter {}.complete(
|
||||
&CompletionContext(&context),
|
||||
"$nu.en",
|
||||
&CaseInsensitiveMatcher
|
||||
),
|
||||
vec![S::new("$nu.env", "$nu.env"),]
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user