Skip to content

Commit 6e88b3f

Browse files
authored
refactor(completion): expression based variable/cell_path completion (nushell#15033)
# Description fixes nushell#14643 , as well as some nested cell path cases: ```nushell let foo = {a: [1 {a: 1}]} $foo.a.1.#<tab> const bar = {a: 1, b: 2} $bar.#<tab> ``` So my plan of the refactoring process is that: 1. gradually move those rules of flattened shapes into expression match branches, until they are gone 2. keep each PR focused, easier to review and track. # User-Facing Changes # Tests + Formatting +2 # After Submitting
1 parent 7208133 commit 6e88b3f

File tree

5 files changed

+243
-282
lines changed

5 files changed

+243
-282
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use crate::completions::{Completer, CompletionOptions, SemanticSuggestion, SuggestionKind};
2+
use nu_engine::{column::get_columns, eval_variable};
3+
use nu_protocol::{
4+
ast::{Expr, FullCellPath, PathMember},
5+
engine::{Stack, StateWorkingSet},
6+
eval_const::eval_constant,
7+
Span, Value,
8+
};
9+
use reedline::Suggestion;
10+
11+
use super::completion_options::NuMatcher;
12+
13+
pub struct CellPathCompletion<'a> {
14+
pub full_cell_path: &'a FullCellPath,
15+
}
16+
17+
impl Completer for CellPathCompletion<'_> {
18+
fn fetch(
19+
&mut self,
20+
working_set: &StateWorkingSet,
21+
stack: &Stack,
22+
_prefix: &[u8],
23+
_span: Span,
24+
offset: usize,
25+
_pos: usize,
26+
options: &CompletionOptions,
27+
) -> Vec<SemanticSuggestion> {
28+
// empty tail is already handled as variable names completion
29+
let Some((prefix_member, path_members)) = self.full_cell_path.tail.split_last() else {
30+
return vec![];
31+
};
32+
let (mut prefix_str, span) = match prefix_member {
33+
PathMember::String { val, span, .. } => (val.clone(), span),
34+
PathMember::Int { val, span, .. } => (val.to_string(), span),
35+
};
36+
// strip the placeholder
37+
prefix_str.pop();
38+
let true_end = std::cmp::max(span.start, span.end - 1);
39+
let span = Span::new(span.start, true_end);
40+
let current_span = reedline::Span {
41+
start: span.start - offset,
42+
end: true_end - offset,
43+
};
44+
45+
let mut matcher = NuMatcher::new(prefix_str, options.clone());
46+
47+
// evaluate the head expression to get its value
48+
let value = if let Expr::Var(var_id) = self.full_cell_path.head.expr {
49+
working_set
50+
.get_variable(var_id)
51+
.const_val
52+
.to_owned()
53+
.or_else(|| eval_variable(working_set.permanent_state, stack, var_id, span).ok())
54+
} else {
55+
eval_constant(working_set, &self.full_cell_path.head).ok()
56+
}
57+
.unwrap_or_default();
58+
59+
for suggestion in nested_suggestions(&value, path_members, current_span) {
60+
matcher.add_semantic_suggestion(suggestion);
61+
}
62+
matcher.results()
63+
}
64+
}
65+
66+
// Find recursively the values for cell_path
67+
fn nested_suggestions(
68+
val: &Value,
69+
path_members: &[PathMember],
70+
current_span: reedline::Span,
71+
) -> Vec<SemanticSuggestion> {
72+
let value = val
73+
.clone()
74+
.follow_cell_path(path_members, false)
75+
.unwrap_or_default();
76+
77+
let kind = SuggestionKind::Type(value.get_type());
78+
let str_to_suggestion = |s: String| SemanticSuggestion {
79+
suggestion: Suggestion {
80+
value: s,
81+
span: current_span,
82+
..Suggestion::default()
83+
},
84+
kind: Some(kind.to_owned()),
85+
};
86+
match value {
87+
Value::Record { val, .. } => val
88+
.columns()
89+
.map(|s| str_to_suggestion(s.to_string()))
90+
.collect(),
91+
Value::List { vals, .. } => get_columns(vals.as_slice())
92+
.into_iter()
93+
.map(str_to_suggestion)
94+
.collect(),
95+
_ => vec![],
96+
}
97+
}

crates/nu-cli/src/completions/completer.rs

Lines changed: 69 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::completions::{
2-
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
3-
DotNuCompletion, FileCompletion, FlagCompletion, OperatorCompletion, VariableCompletion,
2+
CellPathCompletion, CommandCompletion, Completer, CompletionOptions, CustomCompletion,
3+
DirectoryCompletion, DotNuCompletion, FileCompletion, FlagCompletion, OperatorCompletion,
4+
VariableCompletion,
45
};
56
use log::debug;
67
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
@@ -17,6 +18,10 @@ use std::{str, sync::Arc};
1718

1819
use super::base::{SemanticSuggestion, SuggestionKind};
1920

21+
/// Used as the function `f` in find_map Traverse
22+
///
23+
/// returns the inner-most pipeline_element of interest
24+
/// i.e. the one that contains given position and needs completion
2025
fn find_pipeline_element_by_position<'a>(
2126
expr: &'a Expression,
2227
working_set: &'a StateWorkingSet,
@@ -62,6 +67,15 @@ fn find_pipeline_element_by_position<'a>(
6267
}
6368
}
6469

70+
/// Before completion, an additional character `a` is added to the source as a placeholder for correct parsing results.
71+
/// This function helps to strip it
72+
fn strip_placeholder<'a>(working_set: &'a StateWorkingSet, span: &Span) -> (Span, &'a [u8]) {
73+
let new_end = std::cmp::max(span.end - 1, span.start);
74+
let new_span = Span::new(span.start, new_end);
75+
let prefix = working_set.get_span_contents(new_span);
76+
(new_span, prefix)
77+
}
78+
6579
#[derive(Clone)]
6680
pub struct NuCompleter {
6781
engine_state: Arc<EngineState>,
@@ -80,6 +94,28 @@ impl NuCompleter {
8094
self.completion_helper(line, pos)
8195
}
8296

97+
fn variable_names_completion_helper(
98+
&self,
99+
working_set: &StateWorkingSet,
100+
span: Span,
101+
offset: usize,
102+
) -> Vec<SemanticSuggestion> {
103+
let (new_span, prefix) = strip_placeholder(working_set, &span);
104+
if !prefix.starts_with(b"$") {
105+
return vec![];
106+
}
107+
let mut variable_names_completer = VariableCompletion {};
108+
self.process_completion(
109+
&mut variable_names_completer,
110+
working_set,
111+
prefix,
112+
new_span,
113+
offset,
114+
// pos is not required
115+
0,
116+
)
117+
}
118+
83119
// Process the completion for a given completer
84120
fn process_completion<T: Completer>(
85121
&self,
@@ -193,6 +229,37 @@ impl NuCompleter {
193229
return vec![];
194230
};
195231

232+
match &element_expression.expr {
233+
Expr::Var(_) => {
234+
return self.variable_names_completion_helper(
235+
&working_set,
236+
element_expression.span,
237+
fake_offset,
238+
);
239+
}
240+
Expr::FullCellPath(full_cell_path) => {
241+
// e.g. `$e<tab>` parsed as FullCellPath
242+
if full_cell_path.tail.is_empty() {
243+
return self.variable_names_completion_helper(
244+
&working_set,
245+
element_expression.span,
246+
fake_offset,
247+
);
248+
} else {
249+
let mut cell_path_completer = CellPathCompletion { full_cell_path };
250+
return self.process_completion(
251+
&mut cell_path_completer,
252+
&working_set,
253+
&[],
254+
element_expression.span,
255+
fake_offset,
256+
pos,
257+
);
258+
}
259+
}
260+
_ => (),
261+
}
262+
196263
let flattened = flatten_expression(&working_set, element_expression);
197264
let mut spans: Vec<String> = vec![];
198265

@@ -223,46 +290,13 @@ impl NuCompleter {
223290

224291
// Complete based on the last span
225292
if is_last_span {
226-
// Context variables
227-
let most_left_var = most_left_variable(flat_idx, &working_set, flattened.clone());
228-
229293
// Create a new span
230294
let new_span = Span::new(span.start, span.end - 1);
231295

232296
// Parses the prefix. Completion should look up to the cursor position, not after.
233297
let index = pos - span.start;
234298
let prefix = &current_span[..index];
235299

236-
// Variables completion
237-
if prefix.starts_with(b"$") || most_left_var.is_some() {
238-
let mut variable_names_completer =
239-
VariableCompletion::new(most_left_var.unwrap_or((vec![], vec![])));
240-
241-
let mut variable_completions = self.process_completion(
242-
&mut variable_names_completer,
243-
&working_set,
244-
prefix,
245-
new_span,
246-
fake_offset,
247-
pos,
248-
);
249-
250-
let mut variable_operations_completer =
251-
OperatorCompletion::new(element_expression.clone());
252-
253-
let mut variable_operations_completions = self.process_completion(
254-
&mut variable_operations_completer,
255-
&working_set,
256-
prefix,
257-
new_span,
258-
fake_offset,
259-
pos,
260-
);
261-
262-
variable_completions.append(&mut variable_operations_completions);
263-
return variable_completions;
264-
}
265-
266300
// Flags completion
267301
if prefix.starts_with(b"-") {
268302
// Try to complete flag internally
@@ -474,56 +508,6 @@ impl ReedlineCompleter for NuCompleter {
474508
}
475509
}
476510

477-
// reads the most left variable returning it's name (e.g: $myvar)
478-
// and the depth (a.b.c)
479-
fn most_left_variable(
480-
idx: usize,
481-
working_set: &StateWorkingSet<'_>,
482-
flattened: Vec<(Span, FlatShape)>,
483-
) -> Option<(Vec<u8>, Vec<Vec<u8>>)> {
484-
// Reverse items to read the list backwards and truncate
485-
// because the only items that matters are the ones before the current index
486-
let mut rev = flattened;
487-
rev.truncate(idx);
488-
rev = rev.into_iter().rev().collect();
489-
490-
// Store the variables and sub levels found and reverse to correct order
491-
let mut variables_found: Vec<Vec<u8>> = vec![];
492-
let mut found_var = false;
493-
for item in rev.clone() {
494-
let result = working_set.get_span_contents(item.0).to_vec();
495-
496-
match item.1 {
497-
FlatShape::Variable(_) => {
498-
variables_found.push(result);
499-
found_var = true;
500-
501-
break;
502-
}
503-
FlatShape::String => {
504-
variables_found.push(result);
505-
}
506-
_ => {
507-
break;
508-
}
509-
}
510-
}
511-
512-
// If most left var was not found
513-
if !found_var {
514-
return None;
515-
}
516-
517-
// Reverse the order back
518-
variables_found = variables_found.into_iter().rev().collect();
519-
520-
// Extract the variable and the sublevels
521-
let var = variables_found.first().unwrap_or(&vec![]).to_vec();
522-
let sublevels: Vec<Vec<u8>> = variables_found.into_iter().skip(1).collect();
523-
524-
Some((var, sublevels))
525-
}
526-
527511
pub fn map_value_completions<'a>(
528512
list: impl Iterator<Item = &'a Value>,
529513
span: Span,

crates/nu-cli/src/completions/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod base;
2+
mod cell_path_completions;
23
mod command_completions;
34
mod completer;
45
mod completion_common;
@@ -12,6 +13,7 @@ mod operator_completions;
1213
mod variable_completions;
1314

1415
pub use base::{Completer, SemanticSuggestion, SuggestionKind};
16+
pub use cell_path_completions::CellPathCompletion;
1517
pub use command_completions::CommandCompletion;
1618
pub use completer::NuCompleter;
1719
pub use completion_options::{CompletionOptions, MatchAlgorithm};

0 commit comments

Comments
 (0)