mirror of
https://github.com/nushell/nushell
synced 2024-12-27 21:43:09 +00:00
nu-cli/completions: add completion for record vars (#5204)
This commit is contained in:
parent
2a3991cfdb
commit
13b371ab58
2 changed files with 159 additions and 50 deletions
|
@ -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))
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue