From 03c9eaf005a828381665a67e09bf09b2d8dbea89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Wed, 23 Jun 2021 02:21:39 -0500 Subject: [PATCH] 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. --- Cargo.lock | 2 + crates/nu-cli/src/shell.rs | 21 +- crates/nu-completion/Cargo.toml | 6 +- crates/nu-completion/src/command.rs | 2 +- crates/nu-completion/src/completer.rs | 8 +- crates/nu-completion/src/engine.rs | 4 - crates/nu-completion/src/flag.rs | 2 +- crates/nu-completion/src/lib.rs | 18 +- crates/nu-completion/src/variable.rs | 182 ++++++++++++++++++ crates/nu-engine/src/env/basic_host.rs | 2 +- crates/nu-engine/src/env/host.rs | 6 +- crates/nu-engine/src/evaluate/evaluator.rs | 85 ++++---- crates/nu-engine/src/evaluate/mod.rs | 2 +- crates/nu-engine/src/evaluate/scope.rs | 27 ++- crates/nu-engine/src/evaluate/variables.rs | 11 +- crates/nu-engine/src/evaluation_context.rs | 35 +++- crates/nu-engine/src/lib.rs | 6 +- crates/nu-engine/src/shell/mod.rs | 2 +- crates/nu-engine/src/shell/value_shell.rs | 23 +-- crates/nu-parser/src/lib.rs | 2 - crates/nu-parser/src/scope.rs | 2 - crates/nu-parser/src/signature.rs | 48 ----- crates/nu-protocol/src/lib.rs | 2 + crates/nu-protocol/src/registry.rs | 31 +++ crates/nu-protocol/src/value/column_path.rs | 149 ++++++++++++++ .../nu-protocol/src/value/value_structure.rs | 2 +- 26 files changed, 546 insertions(+), 134 deletions(-) create mode 100644 crates/nu-completion/src/variable.rs delete mode 100644 crates/nu-parser/src/signature.rs create mode 100644 crates/nu-protocol/src/registry.rs diff --git a/Cargo.lock b/Cargo.lock index 1a77393923..7079d22bb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3509,12 +3509,14 @@ dependencies = [ "indexmap", "is_executable", "nu-data", + "nu-engine", "nu-errors", "nu-parser", "nu-path", "nu-protocol", "nu-source", "nu-test-support", + "parking_lot 0.11.1", ] [[package]] diff --git a/crates/nu-cli/src/shell.rs b/crates/nu-cli/src/shell.rs index 1bdd633001..998b94a2b0 100644 --- a/crates/nu-cli/src/shell.rs +++ b/crates/nu-cli/src/shell.rs @@ -27,12 +27,31 @@ impl Helper { } } +use nu_protocol::{SignatureRegistry, VariableRegistry}; struct CompletionContext<'a>(&'a EvaluationContext); impl<'a> nu_completion::CompletionContext for CompletionContext<'a> { - fn signature_registry(&self) -> &dyn nu_parser::ParserScope { + fn signature_registry(&self) -> &dyn SignatureRegistry { &self.0.scope } + + fn scope(&self) -> &dyn nu_parser::ParserScope { + &self.0.scope + } + + fn source(&self) -> &EvaluationContext { + self.as_ref() + } + + fn variable_registry(&self) -> &dyn VariableRegistry { + self.0 + } +} + +impl<'a> AsRef for CompletionContext<'a> { + fn as_ref(&self) -> &EvaluationContext { + self.0 + } } pub struct CompletionSuggestion(nu_completion::Suggestion); diff --git a/crates/nu-completion/Cargo.toml b/crates/nu-completion/Cargo.toml index 8912206b25..04d4a772f9 100644 --- a/crates/nu-completion/Cargo.toml +++ b/crates/nu-completion/Cargo.toml @@ -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" diff --git a/crates/nu-completion/src/command.rs b/crates/nu-completion/src/command.rs index 452664e88b..bf04f64508 100644 --- a/crates/nu-completion/src/command.rs +++ b/crates/nu-completion/src/command.rs @@ -16,7 +16,7 @@ where { fn complete(&self, ctx: &Context, partial: &str, matcher: &dyn Matcher) -> Vec { let registry = ctx.signature_registry(); - let mut commands: IndexSet = IndexSet::from_iter(registry.get_names()); + let mut commands: IndexSet = IndexSet::from_iter(registry.names()); // Command suggestions can come from three possible sets: // 1. internal command names, diff --git a/crates/nu-completion/src/completer.rs b/crates/nu-completion/src/completer.rs index 90182c557d..21240c57fd 100644 --- a/crates/nu-completion/src/completer.rs +++ b/crates/nu-completion/src/completer.rs @@ -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(); diff --git a/crates/nu-completion/src/engine.rs b/crates/nu-completion/src/engine.rs index 69eb72f23f..fba8465968 100644 --- a/crates/nu-completion/src/engine.rs +++ b/crates/nu-completion/src/engine.rs @@ -301,10 +301,6 @@ mod tests { } impl ParserScope for VecRegistry { - fn get_names(&self) -> Vec { - self.0.iter().cloned().map(|s| s.name).collect() - } - fn has_signature(&self, name: &str) -> bool { self.0.iter().any(|v| v.name == name) } diff --git a/crates/nu-completion/src/flag.rs b/crates/nu-completion/src/flag.rs index 1458b724c2..fe4b23e6b5 100644 --- a/crates/nu-completion/src/flag.rs +++ b/crates/nu-completion/src/flag.rs @@ -10,7 +10,7 @@ where Context: CompletionContext, { fn complete(&self, ctx: &Context, partial: &str, matcher: &dyn Matcher) -> Vec { - 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)); diff --git a/crates/nu-completion/src/lib.rs b/crates/nu-completion/src/lib.rs index 590c978e78..4774f933fd 100644 --- a/crates/nu-completion/src/lib.rs +++ b/crates/nu-completion/src/lib.rs @@ -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, replacement: impl Into) -> 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 { diff --git a/crates/nu-completion/src/variable.rs b/crates/nu-completion/src/variable.rs new file mode 100644 index 0000000000..4bc56542a3 --- /dev/null +++ b/crates/nu-completion/src/variable.rs @@ -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 { + 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 Completer for VariableCompleter +where + Context: CompletionContext, +{ + fn complete(&self, ctx: &Context, partial: &str, matcher: &dyn Matcher) -> Vec { + 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) -> EvaluationContext { + let scope = Scope::new(); + let env_vars = host.vars().iter().cloned().collect::>(); + 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"),] + ); + } +} diff --git a/crates/nu-engine/src/env/basic_host.rs b/crates/nu-engine/src/env/basic_host.rs index 4f7953bd88..177558d34c 100644 --- a/crates/nu-engine/src/env/basic_host.rs +++ b/crates/nu-engine/src/env/basic_host.rs @@ -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::>() diff --git a/crates/nu-engine/src/env/host.rs b/crates/nu-engine/src/env/host.rs index 2c6b03d509..0bc4ba7108 100644 --- a/crates/nu-engine/src/env/host.rs +++ b/crates/nu-engine/src/env/host.rs @@ -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; fn env_set(&mut self, k: OsString, v: OsString); fn env_rm(&mut self, k: OsString); @@ -41,7 +41,7 @@ impl Host for Box { (**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())) diff --git a/crates/nu-engine/src/evaluate/evaluator.rs b/crates/nu-engine/src/evaluate/evaluator.rs index 2c9e0d641c..13cae7ec54 100644 --- a/crates/nu-engine/src/evaluate/evaluator.rs +++ b/crates/nu-engine/src/evaluate/evaluator.rs @@ -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 { - 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 { + 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, +) -> Result { + 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(), )), }, } diff --git a/crates/nu-engine/src/evaluate/mod.rs b/crates/nu-engine/src/evaluate/mod.rs index e383e4fc59..e4109e507a 100644 --- a/crates/nu-engine/src/evaluate/mod.rs +++ b/crates/nu-engine/src/evaluate/mod.rs @@ -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; diff --git a/crates/nu-engine/src/evaluate/scope.rs b/crates/nu-engine/src/evaluate/scope.rs index 98904eb8cc..7293a06cd8 100644 --- a/crates/nu-engine/src/evaluate/scope.rs +++ b/crates/nu-engine/src/evaluate/scope.rs @@ -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 { 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 { + self.get_vars().iter().map(|(k, _)| k.to_string()).collect() + } + pub fn get_vars(&self) -> IndexMap { //FIXME: should this be an iterator? let mut output: IndexMap = IndexMap::new(); @@ -327,12 +332,26 @@ impl Scope { } } -impl ParserScope for Scope { - fn get_names(&self) -> Vec { +impl SignatureRegistry for Scope { + fn names(&self) -> Vec { self.get_command_names() } - fn get_signature(&self, name: &str) -> Option { + fn has(&self, name: &str) -> bool { + self.get_signature(name).is_some() + } + + fn get(&self, name: &str) -> Option { + self.get_signature(name) + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +impl ParserScope for Scope { + fn get_signature(&self, name: &str) -> Option { self.get_command(name).map(|x| x.signature()) } diff --git a/crates/nu-engine/src/evaluate/variables.rs b/crates/nu-engine/src/evaluate/variables.rs index fb17269d2e..44de76cc38 100644 --- a/crates/nu-engine/src/evaluate/variables.rs +++ b/crates/nu-engine/src/evaluate/variables.rs @@ -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, - ctx: &EvaluationContext, -) -> Result { +pub fn nu(scope: &Scope, ctx: &EvaluationContext) -> Result { 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>>, commands: &IndexMap, variables: &IndexMap, - tag: impl Into, ) -> Result { - let tag = tag.into(); + let tag = Tag::unknown(); let mut scope_dict = TaggedDictBuilder::new(&tag); diff --git a/crates/nu-engine/src/evaluation_context.rs b/crates/nu-engine/src/evaluation_context.rs index 8594a3bcdf..5553b077aa 100644 --- a/crates/nu-engine/src/evaluation_context.rs +++ b/crates/nu-engine/src/evaluation_context.rs @@ -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, + pub engine_state: Arc, } 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::>(); 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 { + 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 { + Variable::list() + .into_iter() + .chain(self.scope.get_variable_names().into_iter()) + .unique() + .collect() + } +} diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index 45452b0821..b84309845d 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -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}; diff --git a/crates/nu-engine/src/shell/mod.rs b/crates/nu-engine/src/shell/mod.rs index 98c11c77d5..5d0859fb11 100644 --- a/crates/nu-engine/src/shell/mod.rs +++ b/crates/nu-engine/src/shell/mod.rs @@ -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; diff --git a/crates/nu-engine/src/shell/value_shell.rs b/crates/nu-engine/src/shell/value_shell.rs index af5c65cc66..bf83f64f1e 100644 --- a/crates/nu-engine/src/shell/value_shell.rs +++ b/crates/nu-engine/src/shell/value_shell.rs @@ -38,7 +38,17 @@ impl ValueShell { } } - fn members_under(&self, path: &Path) -> VecDeque { + 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 { 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 { - 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", diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index 4b8abb8b7b..7952c3d6b1 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -9,11 +9,9 @@ mod lex; mod parse; mod scope; mod shapes; -mod signature; pub use lex::lexer::{lex, parse_block}; pub use lex::tokens::{LiteBlock, LiteCommand, LiteGroup, LitePipeline}; pub use parse::{classify_block, garbage, parse, parse_full_column_path, parse_math_expression}; pub use scope::ParserScope; pub use shapes::shapes; -pub use signature::{Signature, SignatureRegistry}; diff --git a/crates/nu-parser/src/scope.rs b/crates/nu-parser/src/scope.rs index cc8a4564ab..a112e163ca 100644 --- a/crates/nu-parser/src/scope.rs +++ b/crates/nu-parser/src/scope.rs @@ -3,8 +3,6 @@ use nu_source::Spanned; use std::{fmt::Debug, sync::Arc}; pub trait ParserScope: Debug { - fn get_names(&self) -> Vec; - fn get_signature(&self, name: &str) -> Option; fn has_signature(&self, name: &str) -> bool; diff --git a/crates/nu-parser/src/signature.rs b/crates/nu-parser/src/signature.rs deleted file mode 100644 index 5fcc74060e..0000000000 --- a/crates/nu-parser/src/signature.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::fmt::Debug; - -use nu_source::{DebugDocBuilder, HasSpan, PrettyDebugWithSource, Span}; - -pub trait SignatureRegistry: Debug { - fn has(&self, name: &str) -> bool; - fn get(&self, name: &str) -> Option; - fn clone_box(&self) -> Box; -} - -impl SignatureRegistry for Box { - fn has(&self, name: &str) -> bool { - (&**self).has(name) - } - fn get(&self, name: &str) -> Option { - (&**self).get(name) - } - fn clone_box(&self) -> Box { - (&**self).clone_box() - } -} - -#[derive(Debug, Clone)] -pub struct Signature { - pub(crate) unspanned: nu_protocol::Signature, - span: Span, -} - -impl Signature { - pub fn new(unspanned: nu_protocol::Signature, span: impl Into) -> Signature { - Signature { - unspanned, - span: span.into(), - } - } -} - -impl HasSpan for Signature { - fn span(&self) -> Span { - self.span - } -} - -impl PrettyDebugWithSource for Signature { - fn pretty_debug(&self, source: &str) -> DebugDocBuilder { - self.unspanned.pretty_debug(source) - } -} diff --git a/crates/nu-protocol/src/lib.rs b/crates/nu-protocol/src/lib.rs index 2e3ae0be60..e9d7346359 100644 --- a/crates/nu-protocol/src/lib.rs +++ b/crates/nu-protocol/src/lib.rs @@ -5,6 +5,7 @@ mod call_info; pub mod config_path; pub mod hir; mod maybe_owned; +mod registry; mod return_value; mod signature; mod syntax_shape; @@ -18,6 +19,7 @@ pub mod dataframe; pub use crate::call_info::{CallInfo, EvaluatedArgs}; pub use crate::config_path::ConfigPath; pub use crate::maybe_owned::MaybeOwned; +pub use crate::registry::{SignatureRegistry, VariableRegistry}; pub use crate::return_value::{CommandAction, ReturnSuccess, ReturnValue}; pub use crate::signature::{NamedType, PositionalType, Signature}; pub use crate::syntax_shape::SyntaxShape; diff --git a/crates/nu-protocol/src/registry.rs b/crates/nu-protocol/src/registry.rs new file mode 100644 index 0000000000..f264d69431 --- /dev/null +++ b/crates/nu-protocol/src/registry.rs @@ -0,0 +1,31 @@ +use crate::{Signature, Value}; +use nu_source::Spanned; +use std::fmt::Debug; + +pub trait VariableRegistry { + fn get_variable(&self, name: &Spanned<&str>) -> Option; + fn variables(&self) -> Vec; +} + +pub trait SignatureRegistry: Debug { + fn names(&self) -> Vec; + fn has(&self, name: &str) -> bool; + fn get(&self, name: &str) -> Option; + fn clone_box(&self) -> Box; +} + +impl SignatureRegistry for Box { + fn names(&self) -> Vec { + (&**self).names() + } + + fn has(&self, name: &str) -> bool { + (&**self).has(name) + } + fn get(&self, name: &str) -> Option { + (&**self).get(name) + } + fn clone_box(&self) -> Box { + (&**self).clone_box() + } +} diff --git a/crates/nu-protocol/src/value/column_path.rs b/crates/nu-protocol/src/value/column_path.rs index 5032c37bac..a352dad955 100644 --- a/crates/nu-protocol/src/value/column_path.rs +++ b/crates/nu-protocol/src/value/column_path.rs @@ -61,6 +61,10 @@ impl ColumnPath { self.members.iter() } + pub fn is_empty(&self) -> bool { + self.members.is_empty() + } + /// Returns the last member and a slice of the remaining members pub fn split_last(&self) -> Option<(&PathMember, &[PathMember])> { self.members.split_last() @@ -71,6 +75,23 @@ impl ColumnPath { self.iter().last() } + pub fn path(&self) -> String { + let sep = std::path::MAIN_SEPARATOR; + let mut members = self.iter(); + let mut f = String::from(sep); + + if let Some(member) = members.next() { + f.push_str(&member.as_string()); + } + + for member in members { + f.push(sep); + f.push_str(&member.as_string()); + } + + f + } + pub fn build(text: &Spanned) -> ColumnPath { if let ( SpannedExpression { @@ -87,6 +108,38 @@ impl ColumnPath { ColumnPath { members: vec![] } } } + + pub fn with_head(text: &Spanned) -> Option<(String, ColumnPath)> { + match parse_full_column_path(text) { + ( + SpannedExpression { + expr: Expression::FullColumnPath(path), + .. + }, + _, + ) => { + if let crate::hir::FullColumnPath { + head: + SpannedExpression { + expr: Expression::Variable(name, _), + span: _, + }, + tail, + } = *path + { + Some(( + name, + ColumnPath { + members: tail.to_vec(), + }, + )) + } else { + None + } + } + _ => None, + } + } } impl PrettyDebug for ColumnPath { @@ -136,6 +189,102 @@ impl PathMember { } } +fn parse_full_column_path( + raw_column_path: &Spanned, +) -> (SpannedExpression, Option) { + let mut inside_delimiter = vec![]; + let mut output = vec![]; + let mut current_part = String::new(); + let mut start_index = 0; + let mut last_index = 0; + let error = None; + + let mut head = None; + + for (idx, c) in raw_column_path.item.char_indices() { + last_index = idx; + if c == '(' { + inside_delimiter.push(')'); + } else if let Some(delimiter) = inside_delimiter.last() { + if c == *delimiter { + inside_delimiter.pop(); + } + } else if c == '\'' || c == '"' { + inside_delimiter.push(c); + } else if c == '.' { + let part_span = Span::new( + raw_column_path.span.start() + start_index, + raw_column_path.span.start() + idx, + ); + + if head.is_none() && current_part.starts_with('$') { + // We have the variable head + head = Some(Expression::variable(current_part.clone(), part_span)) + } else if let Ok(row_number) = current_part.parse::() { + output.push(UnspannedPathMember::Int(row_number).into_path_member(part_span)); + } else { + let current_part = trim_quotes(¤t_part); + output.push( + UnspannedPathMember::String(current_part.clone()).into_path_member(part_span), + ); + } + current_part.clear(); + // Note: I believe this is safe because of the delimiter we're using, + // but if we get fancy with Unicode we'll need to change this. + start_index = idx + '.'.len_utf8(); + continue; + } + current_part.push(c); + } + + if !current_part.is_empty() { + let part_span = Span::new( + raw_column_path.span.start() + start_index, + raw_column_path.span.start() + last_index + 1, + ); + + if head.is_none() { + if current_part.starts_with('$') { + head = Some(Expression::variable(current_part, raw_column_path.span)); + } else if let Ok(row_number) = current_part.parse::() { + output.push(UnspannedPathMember::Int(row_number).into_path_member(part_span)); + } else { + let current_part = trim_quotes(¤t_part); + output.push(UnspannedPathMember::String(current_part).into_path_member(part_span)); + } + } else if let Ok(row_number) = current_part.parse::() { + output.push(UnspannedPathMember::Int(row_number).into_path_member(part_span)); + } else { + let current_part = trim_quotes(¤t_part); + output.push(UnspannedPathMember::String(current_part).into_path_member(part_span)); + } + } + + if let Some(head) = head { + ( + SpannedExpression::new( + Expression::path(SpannedExpression::new(head, raw_column_path.span), output), + raw_column_path.span, + ), + error, + ) + } else { + ( + SpannedExpression::new( + Expression::path( + SpannedExpression::new( + Expression::variable("$it".into(), raw_column_path.span), + raw_column_path.span, + ), + output, + ), + raw_column_path.span, + ), + error, + ) + } +} + fn parse(raw_column_path: &Spanned) -> (SpannedExpression, Option) { let mut delimiter = '.'; let mut inside_delimiter = false; diff --git a/crates/nu-protocol/src/value/value_structure.rs b/crates/nu-protocol/src/value/value_structure.rs index 65ee41b057..d57e75615d 100644 --- a/crates/nu-protocol/src/value/value_structure.rs +++ b/crates/nu-protocol/src/value/value_structure.rs @@ -17,7 +17,7 @@ pub struct ValueResource { impl ValueResource {} -#[derive(Default)] +#[derive(Default, Debug)] pub struct ValueStructure { pub resources: Vec, }