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

@ -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;

View File

@ -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<Value>;
fn variables(&self) -> Vec<String>;
}
pub trait SignatureRegistry: Debug {
fn names(&self) -> Vec<String>;
fn has(&self, name: &str) -> bool;
fn get(&self, name: &str) -> Option<Signature>;
fn clone_box(&self) -> Box<dyn SignatureRegistry>;
}
impl SignatureRegistry for Box<dyn SignatureRegistry> {
fn names(&self) -> Vec<String> {
(&**self).names()
}
fn has(&self, name: &str) -> bool {
(&**self).has(name)
}
fn get(&self, name: &str) -> Option<Signature> {
(&**self).get(name)
}
fn clone_box(&self) -> Box<dyn SignatureRegistry> {
(&**self).clone_box()
}
}

View File

@ -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<String>) -> ColumnPath {
if let (
SpannedExpression {
@ -87,6 +108,38 @@ impl ColumnPath {
ColumnPath { members: vec![] }
}
}
pub fn with_head(text: &Spanned<String>) -> 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<String>,
) -> (SpannedExpression, Option<ParseError>) {
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::<i64>() {
output.push(UnspannedPathMember::Int(row_number).into_path_member(part_span));
} else {
let current_part = trim_quotes(&current_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::<i64>() {
output.push(UnspannedPathMember::Int(row_number).into_path_member(part_span));
} else {
let current_part = trim_quotes(&current_part);
output.push(UnspannedPathMember::String(current_part).into_path_member(part_span));
}
} else if let Ok(row_number) = current_part.parse::<i64>() {
output.push(UnspannedPathMember::Int(row_number).into_path_member(part_span));
} else {
let current_part = trim_quotes(&current_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<String>) -> (SpannedExpression, Option<ParseError>) {
let mut delimiter = '.';
let mut inside_delimiter = false;

View File

@ -17,7 +17,7 @@ pub struct ValueResource {
impl ValueResource {}
#[derive(Default)]
#[derive(Default, Debug)]
pub struct ValueStructure {
pub resources: Vec<ValueResource>,
}