From 13f2048ffb0402d54f7dd95867820ca725056bf3 Mon Sep 17 00:00:00 2001 From: Genna Wingert Date: Thu, 3 Mar 2022 15:45:35 +0100 Subject: [PATCH] Add completion options for custom completions (#4674) * Add completion options for custom completions * Make clippy happy * Refactor options for clarity * Make return type of filtering explicit --- crates/nu-cli/src/completions.rs | 182 +++++++++++++++++++++++-------- 1 file changed, 139 insertions(+), 43 deletions(-) diff --git a/crates/nu-cli/src/completions.rs b/crates/nu-cli/src/completions.rs index b711e7a43..a5044c0a2 100644 --- a/crates/nu-cli/src/completions.rs +++ b/crates/nu-cli/src/completions.rs @@ -9,6 +9,22 @@ use reedline::Completer; const SEP: char = std::path::MAIN_SEPARATOR; +pub struct CompletionOptions { + case_sensitive: bool, + positional: bool, + sort: bool, +} + +impl Default for CompletionOptions { + fn default() -> Self { + Self { + case_sensitive: true, + positional: true, + sort: true, + } + } +} + #[derive(Clone)] pub struct NuCompleter { engine_state: EngineState, @@ -192,12 +208,10 @@ impl NuCompleter { prefix.remove(pos - flat.0.start); if prefix.starts_with(b"$") { - return self.complete_variables( - &working_set, - &prefix, - new_span, - offset, - ); + let mut output = + self.complete_variables(&working_set, &prefix, new_span, offset); + output.sort_by(|a, b| a.1.cmp(&b.1)); + return output; } if prefix.starts_with(b"-") { // this might be a flag, let's see @@ -240,6 +254,7 @@ impl NuCompleter { )); } } + output.sort_by(|a, b| a.1.cmp(&b.1)); return output; } } @@ -279,29 +294,110 @@ impl NuCompleter { true, ); - let v: Vec<_> = match result { - Ok(pd) => pd - .into_iter() - .filter_map(move |x| { - let s = x.as_string(); + fn map_completions<'a>( + list: impl Iterator, + new_span: Span, + offset: usize, + ) -> Vec<(reedline::Span, String)> { + list.filter_map(move |x| { + let s = x.as_string(); - match s { - Ok(s) => Some(( - reedline::Span { - start: new_span.start - offset, - end: new_span.end - offset, - }, - s, - )), - Err(_) => None, + match s { + Ok(s) => Some(( + reedline::Span { + start: new_span.start - offset, + end: new_span.end - offset, + }, + s, + )), + Err(_) => None, + } + }) + .collect() + } + + let (completions, options) = match result { + Ok(pd) => { + let value = pd.into_value(new_span); + match &value { + Value::Record { .. } => { + let completions = value + .get_data_by_key("completions") + .and_then(|val| { + val.as_list().ok().map(|it| { + map_completions( + it.iter(), + new_span, + offset, + ) + }) + }) + .unwrap_or_default(); + let options = value.get_data_by_key("options"); + + let options = + if let Some(Value::Record { .. }) = &options { + let options = options.unwrap_or_default(); + CompletionOptions { + case_sensitive: options + .get_data_by_key("case_sensitive") + .and_then(|val| val.as_bool().ok()) + .unwrap_or(true), + positional: options + .get_data_by_key("positional") + .and_then(|val| val.as_bool().ok()) + .unwrap_or(true), + sort: options + .get_data_by_key("sort") + .and_then(|val| val.as_bool().ok()) + .unwrap_or(true), + } + } else { + CompletionOptions::default() + }; + + (completions, options) } - }) - .filter(|x| x.1.as_bytes().starts_with(&prefix)) - .collect(), - _ => vec![], + Value::List { vals, .. } => { + let completions = + map_completions(vals.iter(), new_span, offset); + (completions, CompletionOptions::default()) + } + _ => (vec![], CompletionOptions::default()), + } + } + _ => (vec![], CompletionOptions::default()), }; - return v; + let mut completions: Vec<(reedline::Span, String)> = completions + .into_iter() + .filter(|it| { + // Minimise clones for new functionality + match (options.case_sensitive, options.positional) { + (true, true) => it.1.as_bytes().starts_with(&prefix), + (true, false) => it.1.contains( + std::str::from_utf8(&prefix).unwrap_or(""), + ), + (false, positional) => { + let value = it.1.to_lowercase(); + let prefix = std::str::from_utf8(&prefix) + .unwrap_or("") + .to_lowercase(); + if positional { + value.starts_with(&prefix) + } else { + value.contains(&prefix) + } + } + } + }) + .collect(); + + if options.sort { + completions.sort_by(|a, b| a.1.cmp(&b.1)); + } + + return completions; } FlatShape::Filepath | FlatShape::GlobPattern => { let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") { @@ -313,18 +409,21 @@ impl NuCompleter { "".to_string() }; let prefix = String::from_utf8_lossy(&prefix).to_string(); - return file_path_completion(new_span, &prefix, &cwd) - .into_iter() - .map(move |x| { - ( - reedline::Span { - start: x.0.start - offset, - end: x.0.end - offset, - }, - x.1, - ) - }) - .collect(); + let mut output: Vec<_> = + file_path_completion(new_span, &prefix, &cwd) + .into_iter() + .map(move |x| { + ( + reedline::Span { + start: x.0.start - offset, + end: x.0.end - offset, + }, + x.1, + ) + }) + .collect(); + output.sort_by(|a, b| a.1.cmp(&b.1)); + return output; } flat_shape => { let last = flattened @@ -394,7 +493,7 @@ impl NuCompleter { }; // let prefix = working_set.get_span_contents(flat.0); let prefix = String::from_utf8_lossy(&prefix).to_string(); - let output = file_path_completion(new_span, &prefix, &cwd) + let mut output = file_path_completion(new_span, &prefix, &cwd) .into_iter() .map(move |x| { if flat_idx == 0 { @@ -437,6 +536,7 @@ impl NuCompleter { .chain(commands.into_iter()) .collect::>(); //output.dedup_by(|a, b| a.1 == b.1); + output.sort_by(|a, b| a.1.cmp(&b.1)); return output; } @@ -452,11 +552,7 @@ impl NuCompleter { impl Completer for NuCompleter { fn complete(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> { - let mut output = self.completion_helper(line, pos); - - output.sort_by(|a, b| a.1.cmp(&b.1)); - - output + self.completion_helper(line, pos) } }