diff --git a/crates/nu-cli/src/completions/completer.rs b/crates/nu-cli/src/completions/completer.rs index 079425e2a7..2845445b44 100644 --- a/crates/nu-cli/src/completions/completer.rs +++ b/crates/nu-cli/src/completions/completer.rs @@ -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()), diff --git a/crates/nu-cli/src/completions/mod.rs b/crates/nu-cli/src/completions/mod.rs index ed1510cdfb..3acb8e9bf3 100644 --- a/crates/nu-cli/src/completions/mod.rs +++ b/crates/nu-cli/src/completions/mod.rs @@ -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; diff --git a/crates/nu-cli/src/completions/operator_completions.rs b/crates/nu-cli/src/completions/operator_completions.rs new file mode 100644 index 0000000000..84c1feb67b --- /dev/null +++ b/crates/nu-cli/src/completions/operator_completions.rs @@ -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, + span: Span, + offset: usize, + _pos: usize, + _options: &CompletionOptions, + ) -> Vec { + //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, + 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![], + } +}