Add operator completions (#13818)

# Description

This PR addresses #13676 . It adds completions for the operators listed
on https://www.nushell.sh/lang-guide/chapters/operators.html#operators
based on the type of the value before the cursor. Currently, values
created as the output of other operations will not have completions. For
example `(1 + 3)` will not have completions. When operators are
added/removed/updated the completions will have to be adjusted manually.

# User-Facing Changes
- Tab completions for operators

# Tests
Added unit tests to the new completion struct OperationCompletion for
int completions, float completions, and str completions
This commit is contained in:
Vivek Kethineni 2024-10-04 09:54:25 -05:00 committed by GitHub
parent 199aa2ad3a
commit baadaee016
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 202 additions and 4 deletions

View file

@ -1,6 +1,6 @@
use crate::completions::{
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
DotNuCompletion, FileCompletion, FlagCompletion, VariableCompletion,
DotNuCompletion, FileCompletion, FlagCompletion, OperatorCompletion, VariableCompletion,
};
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
use nu_engine::eval_block;
@ -176,17 +176,32 @@ impl NuCompleter {
// Variables completion
if prefix.starts_with(b"$") || most_left_var.is_some() {
let mut completer =
let mut variable_names_completer =
VariableCompletion::new(most_left_var.unwrap_or((vec![], vec![])));
return self.process_completion(
&mut completer,
let mut variable_completions = self.process_completion(
&mut variable_names_completer,
&working_set,
prefix.clone(),
new_span,
fake_offset,
pos,
);
let mut variable_operations_completer =
OperatorCompletion::new(pipeline_element.expr.clone());
let mut variable_operations_completions = self.process_completion(
&mut variable_operations_completer,
&working_set,
prefix,
new_span,
fake_offset,
pos,
);
variable_completions.append(&mut variable_operations_completions);
return variable_completions;
}
// Flags completion
@ -262,6 +277,26 @@ impl NuCompleter {
} else if prev_expr_str == b"ls" {
let mut completer = FileCompletion::new();
return self.process_completion(
&mut completer,
&working_set,
prefix,
new_span,
fake_offset,
pos,
);
} else if matches!(
previous_expr.1,
FlatShape::Float
| FlatShape::Int
| FlatShape::String
| FlatShape::List
| FlatShape::Bool
| FlatShape::Variable(_)
) {
let mut completer =
OperatorCompletion::new(pipeline_element.expr.clone());
return self.process_completion(
&mut completer,
&working_set,
@ -533,6 +568,11 @@ mod completer_tests {
let mut completer = NuCompleter::new(engine_state.into(), Arc::new(Stack::new()));
let dataset = [
("1 bit-sh", true, "b", vec!["bit-shl", "bit-shr"]),
("1.0 bit-sh", false, "b", vec![]),
("1 m", true, "m", vec!["mod"]),
("1.0 m", true, "m", vec!["mod"]),
("\"a\" s", true, "s", vec!["starts-with"]),
("sudo", false, "", Vec::new()),
("sudo l", true, "l", vec!["ls", "let", "lines", "loop"]),
(" sudo", false, "", Vec::new()),

View file

@ -8,6 +8,7 @@ mod directory_completions;
mod dotnu_completions;
mod file_completions;
mod flag_completions;
mod operator_completions;
mod variable_completions;
pub use base::{Completer, SemanticSuggestion, SuggestionKind};
@ -19,4 +20,5 @@ pub use directory_completions::DirectoryCompletion;
pub use dotnu_completions::DotNuCompletion;
pub use file_completions::{file_path_completion, matches, FileCompletion};
pub use flag_completions::FlagCompletion;
pub use operator_completions::OperatorCompletion;
pub use variable_completions::VariableCompletion;

View file

@ -0,0 +1,156 @@
use crate::completions::{
Completer, CompletionOptions, MatchAlgorithm, SemanticSuggestion, SuggestionKind,
};
use nu_protocol::{
ast::{Expr, Expression},
engine::{Stack, StateWorkingSet},
Span, Type,
};
use reedline::Suggestion;
#[derive(Clone)]
pub struct OperatorCompletion {
previous_expr: Expression,
}
impl OperatorCompletion {
pub fn new(previous_expr: Expression) -> Self {
OperatorCompletion { previous_expr }
}
}
impl Completer for OperatorCompletion {
fn fetch(
&mut self,
working_set: &StateWorkingSet,
_stack: &Stack,
_prefix: Vec<u8>,
span: Span,
offset: usize,
_pos: usize,
_options: &CompletionOptions,
) -> Vec<SemanticSuggestion> {
//Check if int, float, or string
let partial = std::str::from_utf8(working_set.get_span_contents(span)).unwrap_or("");
let op = match &self.previous_expr.expr {
Expr::BinaryOp(x, _, _) => &x.expr,
_ => {
return vec![];
}
};
let possible_operations = match op {
Expr::Int(_) => vec![
("+", "Plus / Addition"),
("-", "Minus / Subtraction"),
("*", "Multiply"),
("/", "Divide"),
("==", "Equal"),
("!=", "Not Equal"),
("//", "Floor Division"),
("<", "Less Than"),
(">", "Greater Than"),
("<=", "Less Than or Equal to"),
(">=", "Greater Than or Equal to"),
("mod", "Modulo"),
("**", "Pow"),
("bit-or", "bitwise or"),
("bit-xor", "bitwise exclusive or"),
("bit-and", "bitwise and"),
("bit-shl", "bitwise shift left"),
("bit-shr", "bitwise shift right"),
],
Expr::String(_) => vec![
("=~", "Regex Match / Contains"),
("!~", "Not Regex Match / Not Contains"),
("in", "In / Contains (doesn't use regex)"),
(
"++",
"Appends two lists, a list and a value, two strings, or two binary values",
),
("not-in", "Not In / Not Contains (doesn't use regex"),
("starts-with", "Starts With"),
("ends-with", "Ends With"),
],
Expr::Float(_) => vec![
("+", "Plus / Addition"),
("-", "Minus / Subtraction"),
("*", "Multiply"),
("/", "Divide"),
("==", "Equal"),
("!=", "Not Equal"),
("//", "Floor Division"),
("<", "Less Than"),
(">", "Greater Than"),
("<=", "Less Than or Equal to"),
(">=", "Greater Than or Equal to"),
("mod", "Modulo"),
("**", "Pow"),
],
Expr::Bool(_) => vec![
("and", "Checks if both values are true."),
("or", "Checks if either value is true."),
("xor", "Checks if one value is true and the other is false."),
("not", "Negates a value or expression."),
],
Expr::FullCellPath(path) => match path.head.expr {
Expr::List(_) => vec![(
"++",
"Appends two lists, a list and a value, two strings, or two binary values",
)],
Expr::Var(id) => get_variable_completions(id, working_set),
_ => vec![],
},
_ => vec![],
};
let match_algorithm = MatchAlgorithm::Prefix;
let input_fuzzy_search =
|(operator, _): &(&str, &str)| match_algorithm.matches_str(operator, partial);
possible_operations
.into_iter()
.filter(input_fuzzy_search)
.map(move |x| SemanticSuggestion {
suggestion: Suggestion {
value: x.0.to_string(),
description: Some(x.1.to_string()),
span: reedline::Span::new(span.start - offset, span.end - offset),
append_whitespace: true,
..Suggestion::default()
},
kind: Some(SuggestionKind::Command(
nu_protocol::engine::CommandType::Builtin,
)),
})
.collect()
}
}
pub fn get_variable_completions<'a>(
id: nu_protocol::Id<nu_protocol::marker::Var>,
working_set: &StateWorkingSet,
) -> Vec<(&'a str, &'a str)> {
let var = working_set.get_variable(id);
if !var.mutable {
return vec![];
}
match var.ty {
Type::List(_) | Type::String | Type::Binary => vec![
(
"++=",
"Appends a list, a value, a string, or a binary value to a variable.",
),
("=", "Assigns a value to a variable."),
],
Type::Int | Type::Float => vec![
("=", "Assigns a value to a variable."),
("+=", "Adds a value to a variable."),
("-=", "Subtracts a value from a variable."),
("*=", "Multiplies a variable by a value"),
("/=", "Divides a variable by a value."),
],
_ => vec![],
}
}