mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 06:35:56 +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
@ -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;
|
||||
|
31
crates/nu-protocol/src/registry.rs
Normal file
31
crates/nu-protocol/src/registry.rs
Normal 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()
|
||||
}
|
||||
}
|
@ -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(¤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::<i64>() {
|
||||
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::<i64>() {
|
||||
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<String>) -> (SpannedExpression, Option<ParseError>) {
|
||||
let mut delimiter = '.';
|
||||
let mut inside_delimiter = false;
|
||||
|
@ -17,7 +17,7 @@ pub struct ValueResource {
|
||||
|
||||
impl ValueResource {}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ValueStructure {
|
||||
pub resources: Vec<ValueResource>,
|
||||
}
|
||||
|
Reference in New Issue
Block a user