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 { if pos >= flat.0.start && pos < flat.0.end {
// Context variables // Context variables
let mut is_variable_completion = false; 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 // Create a new span
let new_span = Span { let new_span = Span {
@ -84,23 +85,9 @@ impl NuCompleter {
let mut prefix = working_set.get_span_contents(flat.0).to_vec(); let mut prefix = working_set.get_span_contents(flat.0).to_vec();
prefix.remove(pos - flat.0.start); prefix.remove(pos - flat.0.start);
// Try to get the previous expression // Check if has most left variable
if flat_idx > 0 { if most_left_var.is_some() {
match flattened.get(flat_idx - 1) { is_variable_completion = true;
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 => {}
}
} }
// Variables completion // Variables completion
@ -108,7 +95,7 @@ impl NuCompleter {
let mut completer = VariableCompletion::new( let mut completer = VariableCompletion::new(
self.engine_state.clone(), self.engine_state.clone(),
self.stack.clone(), self.stack.clone(),
previous_expr, most_left_var.unwrap_or((vec![], vec![])),
); );
return self.process_completion( return self.process_completion(
@ -200,3 +187,53 @@ impl ReedlineCompleter for NuCompleter {
self.completion_helper(line, pos) 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 crate::completions::{Completer, CompletionOptions};
use nu_protocol::{ use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet}, engine::{EngineState, Stack, StateWorkingSet},
Span, Span, Value,
}; };
use reedline::Suggestion; use reedline::Suggestion;
use std::sync::Arc; use std::sync::Arc;
@ -10,15 +10,19 @@ use std::sync::Arc;
pub struct VariableCompletion { pub struct VariableCompletion {
engine_state: Arc<EngineState>, engine_state: Arc<EngineState>,
stack: Stack, stack: Stack,
previous_expr: Vec<u8>, var_context: (Vec<u8>, Vec<Vec<u8>>), // tuple with $var and the sublevels (.b.c.d)
} }
impl VariableCompletion { 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 { Self {
engine_state, engine_state,
stack, stack,
previous_expr, var_context,
} }
} }
} }
@ -34,38 +38,83 @@ impl Completer for VariableCompletion {
) -> (Vec<Suggestion>, CompletionOptions) { ) -> (Vec<Suggestion>, CompletionOptions) {
let mut output = vec![]; let mut output = vec![];
let builtins = ["$nu", "$in", "$config", "$env", "$nothing"]; 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("") .unwrap_or("")
.to_lowercase(); .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) // Completions for the given variable
if !self.previous_expr.is_empty() && previous_expr_str.as_str() == "$env" { if !var_str.is_empty() {
for env_var in self.stack.get_env_vars(&self.engine_state) { // Completion for $env.<tab>
output.push(Suggestion { if var_str.as_str() == "$env" {
value: env_var.0, for env_var in self.stack.get_env_vars(&self.engine_state) {
description: None, output.push(Suggestion {
extra: None, value: env_var.0,
span: reedline::Span { description: None,
start: span.start - offset, extra: None,
end: span.end - offset, 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 { for builtin in builtins {
// Variable completion (e.g: $en<tab> to complete $env)
if builtin.as_bytes().starts_with(&prefix) { if builtin.as_bytes().starts_with(&prefix) {
output.push(Suggestion { output.push(Suggestion {
value: builtin.to_string(), value: builtin.to_string(),
description: None, description: None,
extra: None, extra: None,
span: reedline::Span { span: current_span,
start: span.start - offset,
end: span.end - offset,
},
}); });
} }
} }
@ -78,10 +127,7 @@ impl Completer for VariableCompletion {
value: String::from_utf8_lossy(v.0).to_string(), value: String::from_utf8_lossy(v.0).to_string(),
description: None, description: None,
extra: None, extra: None,
span: reedline::Span { span: current_span,
start: span.start - offset,
end: span.end - offset,
},
}); });
} }
} }
@ -95,10 +141,7 @@ impl Completer for VariableCompletion {
value: String::from_utf8_lossy(v.0).to_string(), value: String::from_utf8_lossy(v.0).to_string(),
description: None, description: None,
extra: None, extra: None,
span: reedline::Span { span: current_span,
start: span.start - offset,
end: span.end - offset,
},
}); });
} }
} }
@ -109,3 +152,32 @@ impl Completer for VariableCompletion {
(output, CompletionOptions::default()) (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
}