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:
Andrés N. Robalino
2021-06-23 02:21:39 -05:00
committed by GitHub
parent 2b021472d6
commit 03c9eaf005
26 changed files with 546 additions and 134 deletions

View File

@ -37,7 +37,7 @@ impl Host for BasicHost {
}
#[allow(unused_variables)]
fn vars(&mut self) -> Vec<(String, String)> {
fn vars(&self) -> Vec<(String, String)> {
#[cfg(not(target_arch = "wasm32"))]
{
std::env::vars().collect::<Vec<_>>()

View File

@ -11,7 +11,7 @@ pub trait Host: Debug + Send {
fn stderr(&mut self, out: &str);
fn print_err(&mut self, err: ShellError, source: &Text);
fn vars(&mut self) -> Vec<(String, String)>;
fn vars(&self) -> Vec<(String, String)>;
fn env_get(&mut self, key: OsString) -> Option<OsString>;
fn env_set(&mut self, k: OsString, v: OsString);
fn env_rm(&mut self, k: OsString);
@ -41,7 +41,7 @@ impl Host for Box<dyn Host> {
(**self).print_err(err, source)
}
fn vars(&mut self) -> Vec<(String, String)> {
fn vars(&self) -> Vec<(String, String)> {
(**self).vars()
}
@ -104,7 +104,7 @@ impl Host for FakeHost {
BasicHost {}.print_err(err, source);
}
fn vars(&mut self) -> Vec<(String, String)> {
fn vars(&self) -> Vec<(String, String)> {
self.env_vars
.iter()
.map(|(k, v)| (k.clone(), v.clone()))

View File

@ -35,7 +35,7 @@ pub fn evaluate_baseline_expr(
Expression::Synthetic(hir::Synthetic::String(s)) => {
Ok(UntaggedValue::string(s).into_untagged_value())
}
Expression::Variable(var, s) => evaluate_reference(var, ctx, *s),
expr @ Expression::Variable(_, _) => evaluate_reference(&Variable::from(expr), ctx, span),
Expression::Command => unimplemented!(),
Expression::Subexpression(block) => evaluate_subexpression(block, ctx),
Expression::ExternalCommand(_) => unimplemented!(),
@ -236,45 +236,64 @@ fn evaluate_literal(literal: &hir::Literal, span: Span) -> Value {
}
}
fn evaluate_reference(
name: &str,
ctx: &EvaluationContext,
span: Span,
) -> Result<Value, ShellError> {
match name {
"$nu" => crate::evaluate::variables::nu(&ctx.scope, span, ctx),
pub enum Variable<'a> {
Nu,
Scope,
True,
False,
Nothing,
Other(&'a str),
}
"$scope" => crate::evaluate::variables::scope(
impl<'a> Variable<'a> {
pub fn list() -> Vec<String> {
vec![
String::from("$nu"),
String::from("$scope"),
String::from("$true"),
String::from("$false"),
String::from("$nothing"),
]
}
}
impl<'a> From<&'a Expression> for Variable<'a> {
fn from(expr: &'a Expression) -> Self {
match &expr {
Expression::Variable(name, _) => match name.as_str() {
"$nu" => Self::Nu,
"$scope" => Self::Scope,
"$true" => Self::True,
"$false" => Self::False,
"$nothing" => Self::Nothing,
_ => Self::Other(&name),
},
_ => unreachable!(),
}
}
}
pub fn evaluate_reference(
variable: &Variable,
ctx: &EvaluationContext,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
match variable {
Variable::Nu => crate::evaluate::variables::nu(&ctx.scope, ctx),
Variable::Scope => crate::evaluate::variables::scope(
&ctx.scope.get_aliases(),
&ctx.scope.get_commands(),
&ctx.scope.get_vars(),
span,
),
"$true" => Ok(Value {
value: UntaggedValue::boolean(true),
tag: span.into(),
}),
"$false" => Ok(Value {
value: UntaggedValue::boolean(false),
tag: span.into(),
}),
"$nothing" => Ok(Value {
value: UntaggedValue::nothing(),
tag: span.into(),
}),
x => match ctx.scope.get_var(x) {
Some(mut v) => {
v.tag.span = span;
Ok(v)
}
Variable::True => Ok(UntaggedValue::boolean(true).into_untagged_value()),
Variable::False => Ok(UntaggedValue::boolean(false).into_untagged_value()),
Variable::Nothing => Ok(UntaggedValue::nothing().into_untagged_value()),
Variable::Other(name) => match ctx.scope.get_var(name) {
Some(v) => Ok(v),
None => Err(ShellError::labeled_error(
"Variable not in scope",
format!("unknown variable: {}", x),
span,
format!("unknown variable: {}", name),
tag.into(),
)),
},
}

View File

@ -1,6 +1,6 @@
pub(crate) mod block;
pub(crate) mod evaluate_args;
pub(crate) mod evaluator;
pub mod evaluator;
pub(crate) mod expr;
pub(crate) mod internal;
pub(crate) mod operator;

View File

@ -2,7 +2,7 @@ use crate::whole_stream_command::{whole_stream_command, Command};
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_parser::ParserScope;
use nu_protocol::{hir::Block, Signature, Value};
use nu_protocol::{hir::Block, Signature, SignatureRegistry, Value};
use nu_source::Spanned;
use std::sync::Arc;
@ -23,6 +23,7 @@ impl Scope {
frames: Arc::new(parking_lot::Mutex::new(vec![ScopeFrame::new()])),
}
}
pub fn get_command(&self, name: &str) -> Option<Command> {
for frame in self.frames.lock().iter().rev() {
if let Some(command) = frame.get_command(name) {
@ -64,6 +65,10 @@ impl Scope {
output.sorted_by(|k1, _v1, k2, _v2| k1.cmp(k2)).collect()
}
pub fn get_variable_names(&self) -> Vec<String> {
self.get_vars().iter().map(|(k, _)| k.to_string()).collect()
}
pub fn get_vars(&self) -> IndexMap<String, Value> {
//FIXME: should this be an iterator?
let mut output: IndexMap<String, Value> = IndexMap::new();
@ -327,12 +332,26 @@ impl Scope {
}
}
impl ParserScope for Scope {
fn get_names(&self) -> Vec<String> {
impl SignatureRegistry for Scope {
fn names(&self) -> Vec<String> {
self.get_command_names()
}
fn get_signature(&self, name: &str) -> Option<nu_protocol::Signature> {
fn has(&self, name: &str) -> bool {
self.get_signature(name).is_some()
}
fn get(&self, name: &str) -> Option<Signature> {
self.get_signature(name)
}
fn clone_box(&self) -> Box<dyn SignatureRegistry> {
Box::new(self.clone())
}
}
impl ParserScope for Scope {
fn get_signature(&self, name: &str) -> Option<Signature> {
self.get_command(name).map(|x| x.signature())
}

View File

@ -5,13 +5,9 @@ use nu_errors::ShellError;
use nu_protocol::{Dictionary, ShellTypeName, Signature, TaggedDictBuilder, UntaggedValue, Value};
use nu_source::{Spanned, Tag};
pub fn nu(
scope: &Scope,
tag: impl Into<Tag>,
ctx: &EvaluationContext,
) -> Result<Value, ShellError> {
pub fn nu(scope: &Scope, ctx: &EvaluationContext) -> Result<Value, ShellError> {
let env = &scope.get_env_vars();
let tag = tag.into();
let tag = Tag::unknown();
let mut nu_dict = TaggedDictBuilder::new(&tag);
@ -99,9 +95,8 @@ pub fn scope(
aliases: &IndexMap<String, Vec<Spanned<String>>>,
commands: &IndexMap<String, Signature>,
variables: &IndexMap<String, Value>,
tag: impl Into<Tag>,
) -> Result<Value, ShellError> {
let tag = tag.into();
let tag = Tag::unknown();
let mut scope_dict = TaggedDictBuilder::new(&tag);

View File

@ -1,3 +1,4 @@
use crate::evaluate::evaluator::Variable;
use crate::evaluate::scope::{Scope, ScopeFrame};
use crate::shell::palette::ThemedPalette;
use crate::shell::shell_manager::ShellManager;
@ -5,14 +6,17 @@ use crate::whole_stream_command::Command;
use crate::{call_info::UnevaluatedCallInfo, config_holder::ConfigHolder};
use crate::{command_args::CommandArgs, script};
use crate::{env::basic_host::BasicHost, Host};
use indexmap::IndexMap;
use log::trace;
use nu_data::config::{self, Conf, NuConfig};
use nu_errors::ShellError;
use nu_protocol::{hir, ConfigPath};
use nu_protocol::{hir, ConfigPath, VariableRegistry};
use nu_source::Spanned;
use nu_source::{Span, Tag};
use nu_stream::InputStream;
use nu_test_support::NATIVE_PATH_ENV_VAR;
use indexmap::IndexMap;
use log::trace;
use parking_lot::Mutex;
use std::fs::File;
use std::io::BufReader;
@ -33,7 +37,7 @@ pub struct EngineState {
#[derive(Clone, Default)]
pub struct EvaluationContext {
pub scope: Scope,
engine_state: Arc<EngineState>,
pub engine_state: Arc<EngineState>,
}
impl EvaluationContext {
@ -61,7 +65,7 @@ impl EvaluationContext {
pub fn basic() -> EvaluationContext {
let scope = Scope::new();
let mut host = BasicHost {};
let host = BasicHost {};
let env_vars = host.vars().iter().cloned().collect::<IndexMap<_, _>>();
scope.add_env(env_vars);
@ -395,3 +399,24 @@ impl EvaluationContext {
}
}
}
use itertools::Itertools;
impl VariableRegistry for EvaluationContext {
fn get_variable(&self, name: &Spanned<&str>) -> Option<nu_protocol::Value> {
let span = name.span;
let name = nu_protocol::hir::Expression::variable(name.item.to_string(), name.span);
let var = Variable::from(&name);
crate::evaluate::evaluator::evaluate_reference(&var, self, span).ok()
}
fn variables(&self) -> Vec<String> {
Variable::list()
.into_iter()
.chain(self.scope.get_variable_names().into_iter())
.unique()
.collect()
}
}

View File

@ -4,7 +4,7 @@ mod config_holder;
pub mod documentation;
mod env;
mod evaluate;
mod evaluation_context;
pub mod evaluation_context;
mod example;
pub mod filesystem;
mod from_value;
@ -23,8 +23,8 @@ pub use crate::documentation::{generate_docs, get_brief_help, get_documentation,
pub use crate::env::host::FakeHost;
pub use crate::env::host::Host;
pub use crate::evaluate::block::run_block;
pub use crate::evaluate::evaluator::evaluate_baseline_expr;
pub use crate::evaluate::scope::Scope;
pub use crate::evaluate::{evaluator, evaluator::evaluate_baseline_expr};
pub use crate::evaluation_context::EvaluationContext;
pub use crate::example::Example;
pub use crate::filesystem::dir_info::{DirBuilder, DirInfo, FileInfo};
@ -35,5 +35,5 @@ pub use crate::print::maybe_print_errors;
pub use crate::shell::painter::Painter;
pub use crate::shell::palette::{DefaultPalette, Palette};
pub use crate::shell::shell_manager::ShellManager;
pub use crate::shell::value_shell::ValueShell;
pub use crate::shell::value_shell;
pub use crate::whole_stream_command::{whole_stream_command, Command, WholeStreamCommand};

View File

@ -14,7 +14,7 @@ pub(crate) mod painter;
pub(crate) mod palette;
pub(crate) mod shell_args;
pub(crate) mod shell_manager;
pub(crate) mod value_shell;
pub mod value_shell;
pub trait Shell: std::fmt::Debug {
fn is_interactive(&self) -> bool;

View File

@ -38,7 +38,17 @@ impl ValueShell {
}
}
fn members_under(&self, path: &Path) -> VecDeque<Value> {
pub fn find(&self, path: &Path) -> Option<&Self> {
let mut value_system = ValueStructure::new();
if value_system.walk_decorate(&self.value).is_ok() {
value_system.exists(&path).then(|| self)
} else {
None
}
}
pub fn members_under(&self, path: &Path) -> VecDeque<Value> {
let mut shell_entries = VecDeque::new();
let full_path = path.to_path_buf();
let mut viewed = self.value.clone();
@ -72,12 +82,6 @@ impl ValueShell {
shell_entries
}
// TODO make use of this in the new completion engine
#[allow(dead_code)]
fn members(&self) -> VecDeque<Value> {
self.members_under(Path::new("."))
}
}
impl Shell for ValueShell {
@ -106,10 +110,7 @@ impl Shell for ValueShell {
full_path.push(value.as_ref());
}
let mut value_system = ValueStructure::new();
value_system.walk_decorate(&self.value)?;
if !value_system.exists(&full_path) {
if self.find(&full_path).is_none() {
if let Some(target) = &path {
return Err(ShellError::labeled_error(
"Can not list entries inside",