diff --git a/crates/nu-cli/src/completions/completer.rs b/crates/nu-cli/src/completions/completer.rs index ff2a7a151f..94275fb774 100644 --- a/crates/nu-cli/src/completions/completer.rs +++ b/crates/nu-cli/src/completions/completer.rs @@ -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 = 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, Vec>)> { + // 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![]; + 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> = variables_found.into_iter().skip(1).collect(); + + Some((var, sublevels)) +} diff --git a/crates/nu-cli/src/completions/variable_completions.rs b/crates/nu-cli/src/completions/variable_completions.rs index a0c5858211..ee66b7fe42 100644 --- a/crates/nu-cli/src/completions/variable_completions.rs +++ b/crates/nu-cli/src/completions/variable_completions.rs @@ -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, stack: Stack, - previous_expr: Vec, + var_context: (Vec, Vec>), // tuple with $var and the sublevels (.b.c.d) } impl VariableCompletion { - pub fn new(engine_state: Arc, stack: Stack, previous_expr: Vec) -> Self { + pub fn new( + engine_state: Arc, + stack: Stack, + var_context: (Vec, Vec>), + ) -> Self { Self { engine_state, stack, - previous_expr, + var_context, } } } @@ -34,38 +38,83 @@ impl Completer for VariableCompletion { ) -> (Vec, 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. 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. + 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 to complete $env) for builtin in builtins { - // Variable completion (e.g: $en 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>) -> 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 +}