Feature/refactor completion options (#5228)

* Copy completion filter to custom completions

* Remove filter function from completer

This function was a no-op for FileCompletion and CommandCompletion.
Flag- and VariableCompletion just filters with `starts_with` which
happens in both completers anyway and should therefore also be a no-op.
The remaining use case in CustomCompletion was moved into the
CustomCompletion source file.

Filtering should probably happen immediately while fetching completions
to avoid unnecessary memory allocations.

* Add get_sort_by() to Completer trait

* Remove CompletionOptions from Completer::fetch()

* Fix clippy lints

* Apply Completer changes to DotNuCompletion
This commit is contained in:
Richard 2022-04-19 20:59:10 +02:00 committed by GitHub
parent ae674bfaec
commit 0de289f6b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 80 additions and 118 deletions

View File

@ -1,4 +1,4 @@
use crate::completions::{CompletionOptions, SortBy}; use crate::completions::SortBy;
use nu_protocol::{engine::StateWorkingSet, levenshtein_distance, Span}; use nu_protocol::{engine::StateWorkingSet, levenshtein_distance, Span};
use reedline::Suggestion; use reedline::Suggestion;
@ -12,50 +12,18 @@ pub trait Completer {
span: Span, span: Span,
offset: usize, offset: usize,
pos: usize, pos: usize,
) -> (Vec<Suggestion>, CompletionOptions); ) -> Vec<Suggestion>;
// Filter results using the completion options fn get_sort_by(&self) -> SortBy {
fn filter( SortBy::Ascending
&self,
prefix: Vec<u8>,
items: Vec<Suggestion>,
options: CompletionOptions,
) -> Vec<Suggestion> {
items
.into_iter()
.filter(|it| {
// Minimise clones for new functionality
match (options.case_sensitive, options.positional) {
(true, true) => it.value.as_bytes().starts_with(&prefix),
(true, false) => it
.value
.contains(std::str::from_utf8(&prefix).unwrap_or("")),
(false, positional) => {
let value = it.value.to_lowercase();
let prefix = std::str::from_utf8(&prefix).unwrap_or("").to_lowercase();
if positional {
value.starts_with(&prefix)
} else {
value.contains(&prefix)
}
}
}
})
.collect()
} }
// Sort results using the completion options fn sort(&self, items: Vec<Suggestion>, prefix: Vec<u8>) -> Vec<Suggestion> {
fn sort(
&self,
items: Vec<Suggestion>,
prefix: Vec<u8>,
options: CompletionOptions,
) -> Vec<Suggestion> {
let prefix_str = String::from_utf8_lossy(&prefix).to_string(); let prefix_str = String::from_utf8_lossy(&prefix).to_string();
let mut filtered_items = items; let mut filtered_items = items;
// Sort items // Sort items
match options.sort_by { match self.get_sort_by() {
SortBy::LevenshteinDistance => { SortBy::LevenshteinDistance => {
filtered_items.sort_by(|a, b| { filtered_items.sort_by(|a, b| {
let a_distance = levenshtein_distance(&prefix_str, &a.value); let a_distance = levenshtein_distance(&prefix_str, &a.value);

View File

@ -1,6 +1,4 @@
use crate::completions::{ use crate::completions::{file_completions::file_path_completion, Completer, SortBy};
file_completions::file_path_completion, Completer, CompletionOptions, SortBy,
};
use nu_parser::{trim_quotes, FlatShape}; use nu_parser::{trim_quotes, FlatShape};
use nu_protocol::{ use nu_protocol::{
engine::{EngineState, StateWorkingSet}, engine::{EngineState, StateWorkingSet},
@ -154,7 +152,7 @@ impl Completer for CommandCompletion {
span: Span, span: Span,
offset: usize, offset: usize,
pos: usize, pos: usize,
) -> (Vec<Suggestion>, CompletionOptions) { ) -> Vec<Suggestion> {
let last = self let last = self
.flattened .flattened
.iter() .iter()
@ -172,9 +170,6 @@ impl Completer for CommandCompletion {
}) })
.last(); .last();
// Options
let options = CompletionOptions::new(true, true, SortBy::LevenshteinDistance);
// The last item here would be the earliest shape that could possible by part of this subcommand // The last item here would be the earliest shape that could possible by part of this subcommand
let subcommands = if let Some(last) = last { let subcommands = if let Some(last) = last {
self.complete_commands( self.complete_commands(
@ -191,7 +186,7 @@ impl Completer for CommandCompletion {
}; };
if !subcommands.is_empty() { if !subcommands.is_empty() {
return (subcommands, options); return subcommands;
} }
let commands = if matches!(self.flat_shape, nu_parser::FlatShape::External) let commands = if matches!(self.flat_shape, nu_parser::FlatShape::External)
@ -225,7 +220,8 @@ impl Completer for CommandCompletion {
}; };
// let prefix = working_set.get_span_contents(flat.0); // let prefix = working_set.get_span_contents(flat.0);
let prefix = String::from_utf8_lossy(&prefix).to_string(); let prefix = String::from_utf8_lossy(&prefix).to_string();
let output = file_path_completion(span, &prefix, &cwd)
file_path_completion(span, &prefix, &cwd)
.into_iter() .into_iter()
.map(move |x| { .map(move |x| {
if self.flat_idx == 0 { if self.flat_idx == 0 {
@ -262,13 +258,10 @@ impl Completer for CommandCompletion {
}) })
.chain(subcommands.into_iter()) .chain(subcommands.into_iter())
.chain(commands.into_iter()) .chain(commands.into_iter())
.collect::<Vec<_>>(); .collect::<Vec<_>>()
(output, options)
} }
// Replace base filter with no filter once all the results are already based in the current path fn get_sort_by(&self) -> SortBy {
fn filter(&self, _: Vec<u8>, items: Vec<Suggestion>, _: CompletionOptions) -> Vec<Suggestion> { SortBy::LevenshteinDistance
items
} }
} }

View File

@ -36,14 +36,10 @@ impl NuCompleter {
pos: usize, pos: usize,
) -> Vec<Suggestion> { ) -> Vec<Suggestion> {
// Fetch // Fetch
let (mut suggestions, options) = let mut suggestions = completer.fetch(working_set, prefix.clone(), new_span, offset, pos);
completer.fetch(working_set, prefix.clone(), new_span, offset, pos);
// Filter
suggestions = completer.filter(prefix.clone(), suggestions, options.clone());
// Sort // Sort
suggestions = completer.sort(suggestions, prefix, options); suggestions = completer.sort(suggestions, prefix);
suggestions suggestions
} }

View File

@ -1,4 +1,4 @@
#[derive(Clone)] #[derive(Copy, Clone)]
pub enum SortBy { pub enum SortBy {
LevenshteinDistance, LevenshteinDistance,
Ascending, Ascending,
@ -12,16 +12,6 @@ pub struct CompletionOptions {
pub sort_by: SortBy, pub sort_by: SortBy,
} }
impl CompletionOptions {
pub fn new(case_sensitive: bool, positional: bool, sort_by: SortBy) -> Self {
Self {
case_sensitive,
positional,
sort_by,
}
}
}
impl Default for CompletionOptions { impl Default for CompletionOptions {
fn default() -> Self { fn default() -> Self {
Self { Self {

View File

@ -13,6 +13,7 @@ pub struct CustomCompletion {
stack: Stack, stack: Stack,
decl_id: usize, decl_id: usize,
line: String, line: String,
sort_by: SortBy,
} }
impl CustomCompletion { impl CustomCompletion {
@ -22,6 +23,7 @@ impl CustomCompletion {
stack, stack,
decl_id, decl_id,
line, line,
sort_by: SortBy::None,
} }
} }
@ -55,11 +57,11 @@ impl Completer for CustomCompletion {
fn fetch( fn fetch(
&mut self, &mut self,
_: &StateWorkingSet, _: &StateWorkingSet,
_: Vec<u8>, prefix: Vec<u8>,
span: Span, span: Span,
offset: usize, offset: usize,
pos: usize, pos: usize,
) -> (Vec<Suggestion>, CompletionOptions) { ) -> Vec<Suggestion> {
// Line position // Line position
let line_pos = pos - offset; let line_pos = pos - offset;
@ -113,6 +115,10 @@ impl Completer for CustomCompletion {
.and_then(|val| val.as_bool().ok()) .and_then(|val| val.as_bool().ok())
.unwrap_or(false); .unwrap_or(false);
if should_sort {
self.sort_by = SortBy::Ascending;
}
CompletionOptions { CompletionOptions {
case_sensitive: options case_sensitive: options
.get_data_by_key("case_sensitive") .get_data_by_key("case_sensitive")
@ -144,6 +150,32 @@ impl Completer for CustomCompletion {
_ => (vec![], CompletionOptions::default()), _ => (vec![], CompletionOptions::default()),
}; };
(suggestions, options) filter(&prefix, suggestions, options)
}
fn get_sort_by(&self) -> SortBy {
self.sort_by
} }
} }
fn filter(prefix: &[u8], items: Vec<Suggestion>, options: CompletionOptions) -> Vec<Suggestion> {
items
.into_iter()
.filter(|it| {
// Minimise clones for new functionality
match (options.case_sensitive, options.positional) {
(true, true) => it.value.as_bytes().starts_with(prefix),
(true, false) => it.value.contains(std::str::from_utf8(prefix).unwrap_or("")),
(false, positional) => {
let value = it.value.to_lowercase();
let prefix = std::str::from_utf8(prefix).unwrap_or("").to_lowercase();
if positional {
value.starts_with(&prefix)
} else {
value.contains(&prefix)
}
}
}
})
.collect()
}

View File

@ -1,6 +1,4 @@
use crate::completions::{ use crate::completions::{file_path_completion, partial_from, Completer, SortBy};
file_path_completion, partial_from, Completer, CompletionOptions, SortBy,
};
use nu_protocol::{ use nu_protocol::{
engine::{EngineState, StateWorkingSet}, engine::{EngineState, StateWorkingSet},
Span, Span,
@ -21,11 +19,6 @@ impl DotNuCompletion {
} }
impl Completer for DotNuCompletion { impl Completer for DotNuCompletion {
// Replace base filter with no filter once all the results are already filtered
fn filter(&self, _: Vec<u8>, items: Vec<Suggestion>, _: CompletionOptions) -> Vec<Suggestion> {
items
}
fn fetch( fn fetch(
&mut self, &mut self,
_: &StateWorkingSet, _: &StateWorkingSet,
@ -33,7 +26,7 @@ impl Completer for DotNuCompletion {
span: Span, span: Span,
offset: usize, offset: usize,
_: usize, _: usize,
) -> (Vec<Suggestion>, CompletionOptions) { ) -> Vec<Suggestion> {
let prefix_str = String::from_utf8_lossy(&prefix).to_string(); let prefix_str = String::from_utf8_lossy(&prefix).to_string();
let mut search_dirs: Vec<String> = vec![]; let mut search_dirs: Vec<String> = vec![];
let (base_dir, mut partial) = partial_from(&prefix_str); let (base_dir, mut partial) = partial_from(&prefix_str);
@ -118,9 +111,10 @@ impl Completer for DotNuCompletion {
}) })
.collect(); .collect();
// Options output
let options = CompletionOptions::new(false, true, SortBy::LevenshteinDistance); }
(output, options) fn get_sort_by(&self) -> SortBy {
SortBy::LevenshteinDistance
} }
} }

View File

@ -1,4 +1,4 @@
use crate::completions::{Completer, CompletionOptions}; use crate::completions::Completer;
use nu_protocol::{ use nu_protocol::{
engine::{EngineState, StateWorkingSet}, engine::{EngineState, StateWorkingSet},
levenshtein_distance, Span, levenshtein_distance, Span,
@ -28,7 +28,7 @@ impl Completer for FileCompletion {
span: Span, span: Span,
offset: usize, offset: usize,
_: usize, _: usize,
) -> (Vec<Suggestion>, CompletionOptions) { ) -> Vec<Suggestion> {
let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") { let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") {
match d.as_string() { match d.as_string() {
Ok(s) => s, Ok(s) => s,
@ -51,19 +51,11 @@ impl Completer for FileCompletion {
}) })
.collect(); .collect();
// Options output
let options = CompletionOptions::default();
(output, options)
} }
// Sort results prioritizing the non hidden folders // Sort results prioritizing the non hidden folders
fn sort( fn sort(&self, items: Vec<Suggestion>, prefix: Vec<u8>) -> Vec<Suggestion> {
&self,
items: Vec<Suggestion>,
prefix: Vec<u8>,
_: CompletionOptions, // Ignore the given options, once it's a custom sorting
) -> Vec<Suggestion> {
let prefix_str = String::from_utf8_lossy(&prefix).to_string(); let prefix_str = String::from_utf8_lossy(&prefix).to_string();
// Sort items // Sort items
@ -97,11 +89,6 @@ impl Completer for FileCompletion {
non_hidden non_hidden
} }
// Replace base filter with no filter once all the results are already based in the current path
fn filter(&self, _: Vec<u8>, items: Vec<Suggestion>, _: CompletionOptions) -> Vec<Suggestion> {
items
}
} }
pub fn partial_from(input: &str) -> (String, String) { pub fn partial_from(input: &str) -> (String, String) {

View File

@ -1,4 +1,4 @@
use crate::completions::{Completer, CompletionOptions}; use crate::completions::Completer;
use nu_protocol::{ use nu_protocol::{
ast::{Expr, Expression}, ast::{Expr, Expression},
engine::StateWorkingSet, engine::StateWorkingSet,
@ -26,7 +26,7 @@ impl Completer for FlagCompletion {
span: Span, span: Span,
offset: usize, offset: usize,
_: usize, _: usize,
) -> (Vec<Suggestion>, CompletionOptions) { ) -> Vec<Suggestion> {
// Check if it's a flag // Check if it's a flag
if let Expr::Call(call) = &self.expression.expr { if let Expr::Call(call) = &self.expression.expr {
let decl = working_set.get_decl(call.decl_id); let decl = working_set.get_decl(call.decl_id);
@ -73,9 +73,9 @@ impl Completer for FlagCompletion {
} }
} }
return (output, CompletionOptions::default()); return output;
} }
(vec![], CompletionOptions::default()) vec![]
} }
} }

View File

@ -1,4 +1,4 @@
use crate::completions::{Completer, CompletionOptions}; use crate::completions::Completer;
use nu_protocol::{ use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet}, engine::{EngineState, Stack, StateWorkingSet},
Span, Value, Span, Value,
@ -35,7 +35,7 @@ impl Completer for VariableCompletion {
span: Span, span: Span,
offset: usize, offset: usize,
_: usize, _: usize,
) -> (Vec<Suggestion>, CompletionOptions) { ) -> Vec<Suggestion> {
let mut output = vec![]; let mut output = vec![];
let builtins = ["$nu", "$in", "$config", "$env", "$nothing"]; let builtins = ["$nu", "$in", "$config", "$env", "$nothing"];
let var_str = std::str::from_utf8(&self.var_context.0) let var_str = std::str::from_utf8(&self.var_context.0)
@ -52,6 +52,7 @@ impl Completer for VariableCompletion {
// Completion for $env.<tab> // Completion for $env.<tab>
if var_str.as_str() == "$env" { if var_str.as_str() == "$env" {
for env_var in self.stack.get_env_vars(&self.engine_state) { for env_var in self.stack.get_env_vars(&self.engine_state) {
if env_var.0.as_bytes().starts_with(&prefix) {
output.push(Suggestion { output.push(Suggestion {
value: env_var.0, value: env_var.0,
description: None, description: None,
@ -59,8 +60,9 @@ impl Completer for VariableCompletion {
span: current_span, span: current_span,
}); });
} }
}
return (output, CompletionOptions::default()); return output;
} }
// Completion other variable types // Completion other variable types
@ -96,11 +98,11 @@ impl Completer for VariableCompletion {
}); });
} }
return (output, CompletionOptions::default()); return output;
} }
_ => { _ => {
return (output, CompletionOptions::default()); return output;
} }
} }
} }
@ -149,7 +151,7 @@ impl Completer for VariableCompletion {
output.dedup(); output.dedup();
(output, CompletionOptions::default()) output
} }
} }