nu-cli/completions: add completion for record vars (#5204)

This commit is contained in:
Herlon Aguiar 2022-04-15 22:24:41 +02:00 committed by GitHub
parent 2a3991cfdb
commit 13b371ab58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 159 additions and 50 deletions

View File

@ -72,7 +72,8 @@ impl NuCompleter {
if pos >= flat.0.start && pos < flat.0.end {
// Context variables
let mut is_variable_completion = false;
let mut previous_expr: Vec<u8> = vec![];
let most_left_var =
most_left_variable(flat_idx, &working_set, flattened.clone());
// Create a new span
let new_span = Span {
@ -84,23 +85,9 @@ impl NuCompleter {
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
prefix.remove(pos - flat.0.start);
// Try to get the previous expression
if flat_idx > 0 {
match flattened.get(flat_idx - 1) {
Some(value) => {
let previous_prefix =
working_set.get_span_contents(value.0).to_vec();
// Update the previous expression
previous_expr = previous_prefix;
// Check if should match variable completion
if matches!(value.1, FlatShape::Variable) {
is_variable_completion = true;
}
}
None => {}
}
// Check if has most left variable
if most_left_var.is_some() {
is_variable_completion = true;
}
// Variables completion
@ -108,7 +95,7 @@ impl NuCompleter {
let mut completer = VariableCompletion::new(
self.engine_state.clone(),
self.stack.clone(),
previous_expr,
most_left_var.unwrap_or((vec![], vec![])),
);
return self.process_completion(
@ -200,3 +187,53 @@ impl ReedlineCompleter for NuCompleter {
self.completion_helper(line, pos)
}
}
// reads the most left variable returning it's name (e.g: $myvar)
// and the depth (a.b.c)
fn most_left_variable(
idx: usize,
working_set: &StateWorkingSet<'_>,
flattened: Vec<(Span, FlatShape)>,
) -> Option<(Vec<u8>, Vec<Vec<u8>>)> {
// Reverse items to read the list backwards and truncate
// because the only items that matters are the ones before the current index
let mut rev = flattened;
rev.truncate(idx);
rev = rev.into_iter().rev().collect();
// Store the variables and sub levels found and reverse to correct order
let mut variables_found: Vec<Vec<u8>> = vec![];
let mut found_var = false;
for item in rev.clone() {
let result = working_set.get_span_contents(item.0).to_vec();
match item.1 {
FlatShape::Variable => {
variables_found.push(result);
found_var = true;
break;
}
FlatShape::String => {
variables_found.push(result);
}
_ => {
break;
}
}
}
// If most left var was not found
if !found_var {
return None;
}
// Reverse the order back
variables_found = variables_found.into_iter().rev().collect();
// Extract the variable and the sublevels
let var = variables_found.first().unwrap_or(&vec![]).to_vec();
let sublevels: Vec<Vec<u8>> = variables_found.into_iter().skip(1).collect();
Some((var, sublevels))
}

View File

@ -1,7 +1,7 @@
use crate::completions::{Completer, CompletionOptions};
use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet},
Span,
Span, Value,
};
use reedline::Suggestion;
use std::sync::Arc;
@ -10,15 +10,19 @@ use std::sync::Arc;
pub struct VariableCompletion {
engine_state: Arc<EngineState>,
stack: Stack,
previous_expr: Vec<u8>,
var_context: (Vec<u8>, Vec<Vec<u8>>), // tuple with $var and the sublevels (.b.c.d)
}
impl VariableCompletion {
pub fn new(engine_state: Arc<EngineState>, stack: Stack, previous_expr: Vec<u8>) -> Self {
pub fn new(
engine_state: Arc<EngineState>,
stack: Stack,
var_context: (Vec<u8>, Vec<Vec<u8>>),
) -> Self {
Self {
engine_state,
stack,
previous_expr,
var_context,
}
}
}
@ -34,38 +38,83 @@ impl Completer for VariableCompletion {
) -> (Vec<Suggestion>, CompletionOptions) {
let mut output = vec![];
let builtins = ["$nu", "$in", "$config", "$env", "$nothing"];
let previous_expr_str = std::str::from_utf8(&self.previous_expr)
let var_str = std::str::from_utf8(&self.var_context.0)
.unwrap_or("")
.to_lowercase();
let var_id = working_set.find_variable(&self.var_context.0);
let current_span = reedline::Span {
start: span.start - offset,
end: span.end - offset,
};
// Completions for the given variable (e.g: $env.<tab> for completing $env.SOMETHING)
if !self.previous_expr.is_empty() && previous_expr_str.as_str() == "$env" {
for env_var in self.stack.get_env_vars(&self.engine_state) {
output.push(Suggestion {
value: env_var.0,
description: None,
extra: None,
span: reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
});
// Completions for the given variable
if !var_str.is_empty() {
// Completion for $env.<tab>
if var_str.as_str() == "$env" {
for env_var in self.stack.get_env_vars(&self.engine_state) {
output.push(Suggestion {
value: env_var.0,
description: None,
extra: None,
span: current_span,
});
}
return (output, CompletionOptions::default());
}
return (output, CompletionOptions::default());
// Completion other variable types
if let Some(var_id) = var_id {
// Extract the variable value from the stack
let var = self.stack.get_var(
var_id,
Span {
start: span.start,
end: span.end,
},
);
// If the value exists and it's of type Record
if let Ok(mut value) = var {
// Find recursively the values for sublevels
// if no sublevels are set it returns the current value
value = recursive_value(value, self.var_context.1.clone());
match value {
Value::Record {
cols,
vals: _,
span: _,
} => {
// Add all the columns as completion
for item in cols {
output.push(Suggestion {
value: item,
description: None,
extra: None,
span: current_span,
});
}
return (output, CompletionOptions::default());
}
_ => {
return (output, CompletionOptions::default());
}
}
}
}
}
// Variable completion (e.g: $en<tab> to complete $env)
for builtin in builtins {
// Variable completion (e.g: $en<tab> to complete $env)
if builtin.as_bytes().starts_with(&prefix) {
output.push(Suggestion {
value: builtin.to_string(),
description: None,
extra: None,
span: reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
span: current_span,
});
}
}
@ -78,10 +127,7 @@ impl Completer for VariableCompletion {
value: String::from_utf8_lossy(v.0).to_string(),
description: None,
extra: None,
span: reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
span: current_span,
});
}
}
@ -95,10 +141,7 @@ impl Completer for VariableCompletion {
value: String::from_utf8_lossy(v.0).to_string(),
description: None,
extra: None,
span: reedline::Span {
start: span.start - offset,
end: span.end - offset,
},
span: current_span,
});
}
}
@ -109,3 +152,32 @@ impl Completer for VariableCompletion {
(output, CompletionOptions::default())
}
}
fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
// Go to next sublevel
if let Some(next_sublevel) = sublevels.clone().into_iter().next() {
match val {
Value::Record {
cols,
vals,
span: _,
} => {
for item in cols.into_iter().zip(vals.into_iter()) {
// Check if index matches with sublevel
if item.0.as_bytes().to_vec() == next_sublevel {
// If matches try to fetch recursively the next
return recursive_value(item.1, sublevels.into_iter().skip(1).collect());
}
}
// Current sublevel value not found
return Value::Nothing {
span: Span { start: 0, end: 0 },
};
}
_ => return val,
}
}
val
}