Add completions.sort option

This commit is contained in:
ysthakur 2024-07-05 23:40:04 -04:00
parent de2b752771
commit b5a02de35a
12 changed files with 91 additions and 67 deletions

View File

@ -1,5 +1,5 @@
use crate::{
completions::{Completer, CompletionOptions, MatchAlgorithm, SortBy},
completions::{Completer, CompletionOptions, MatchAlgorithm},
SuggestionKind,
};
use nu_parser::FlatShape;
@ -198,11 +198,7 @@ impl Completer for CommandCompletion {
};
if !subcommands.is_empty() {
return sort_suggestions(
&String::from_utf8_lossy(&prefix),
subcommands,
SortBy::LevenshteinDistance,
);
return sort_suggestions(&String::from_utf8_lossy(&prefix), subcommands, options);
}
let config = working_set.get_config();
@ -227,11 +223,7 @@ impl Completer for CommandCompletion {
vec![]
};
sort_suggestions(
&String::from_utf8_lossy(&prefix),
commands,
SortBy::LevenshteinDistance,
)
sort_suggestions(&String::from_utf8_lossy(&prefix), commands, options)
}
}

View File

@ -48,6 +48,7 @@ impl NuCompleter {
let options = CompletionOptions {
case_sensitive: config.case_sensitive_completions,
match_algorithm: config.completion_algorithm.into(),
sort: config.completion_sort,
..Default::default()
};

View File

@ -2,19 +2,20 @@ use crate::{
completions::{matches, CompletionOptions},
SemanticSuggestion,
};
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
use nu_ansi_term::Style;
use nu_engine::env_to_string;
use nu_path::{expand_to_real_path, home_dir};
use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet},
levenshtein_distance, Span,
CompletionSort, Span,
};
use nu_utils::get_ls_colors;
use std::path::{
is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR,
};
use super::SortBy;
use super::MatchAlgorithm;
#[derive(Clone, Default)]
pub struct PathBuiltFromString {
@ -64,7 +65,7 @@ fn complete_rec(
}
let prefix = partial.first().unwrap_or(&"");
let sorted_entries = sort_completions(prefix, entries, SortBy::Ascending, |(entry, _)| entry);
let sorted_entries = sort_completions(prefix, entries, options, |(entry, _)| entry);
for (entry_name, built) in sorted_entries {
match partial.split_first() {
@ -273,9 +274,9 @@ pub fn adjust_if_intermediate(
pub fn sort_suggestions(
prefix: &str,
items: Vec<SemanticSuggestion>,
sort_by: SortBy,
options: &CompletionOptions,
) -> Vec<SemanticSuggestion> {
sort_completions(prefix, items, sort_by, |it| &it.suggestion.value)
sort_completions(prefix, items, options, |it| &it.suggestion.value)
}
/// # Arguments
@ -283,23 +284,22 @@ pub fn sort_suggestions(
pub fn sort_completions<T>(
prefix: &str,
mut items: Vec<T>,
sort_by: SortBy,
options: &CompletionOptions,
get_value: fn(&T) -> &str,
) -> Vec<T> {
// Sort items
match sort_by {
SortBy::LevenshteinDistance => {
items.sort_by(|a, b| {
let a_distance = levenshtein_distance(prefix, get_value(a));
let b_distance = levenshtein_distance(prefix, get_value(b));
a_distance.cmp(&b_distance)
});
}
SortBy::Ascending => {
items.sort_by(|a, b| get_value(a).cmp(get_value(b)));
}
SortBy::None => {}
};
if options.sort == CompletionSort::Default && options.match_algorithm == MatchAlgorithm::Fuzzy {
items.sort_by(|a, b| {
let matcher = SkimMatcherV2::default();
let a_str = get_value(a);
let b_str = get_value(b);
let a_score = matcher.fuzzy_match(a_str, prefix).unwrap_or_default();
let b_score = matcher.fuzzy_match(b_str, prefix).unwrap_or_default();
b_score.cmp(&a_score).then(a_str.cmp(b_str))
});
} else {
items.sort_by(|a, b| get_value(a).cmp(get_value(b)));
}
items
}

View File

@ -1,17 +1,10 @@
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
use nu_parser::trim_quotes_str;
use nu_protocol::CompletionAlgorithm;
use nu_protocol::{CompletionAlgorithm, CompletionSort};
use std::fmt::Display;
#[derive(Copy, Clone)]
pub enum SortBy {
LevenshteinDistance,
Ascending,
None,
}
/// Describes how suggestions should be matched.
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum MatchAlgorithm {
/// Only show suggestions which begin with the given input
///
@ -96,6 +89,7 @@ pub struct CompletionOptions {
pub case_sensitive: bool,
pub positional: bool,
pub match_algorithm: MatchAlgorithm,
pub sort: CompletionSort,
}
impl Default for CompletionOptions {
@ -104,6 +98,7 @@ impl Default for CompletionOptions {
case_sensitive: true,
positional: true,
match_algorithm: MatchAlgorithm::Prefix,
sort: Default::default(),
}
}
}

View File

@ -1,13 +1,13 @@
use crate::completions::{
completer::map_value_completions, Completer, CompletionOptions, MatchAlgorithm,
SemanticSuggestion, SortBy,
SemanticSuggestion,
};
use nu_engine::eval_call;
use nu_protocol::{
ast::{Argument, Call, Expr, Expression},
debugger::WithoutDebug,
engine::{Stack, StateWorkingSet},
PipelineData, Span, Type, Value,
CompletionSort, PipelineData, Span, Type, Value,
};
use nu_utils::IgnoreCaseExt;
use std::collections::HashMap;
@ -18,7 +18,6 @@ pub struct CustomCompletion {
stack: Stack,
decl_id: usize,
line: String,
sort_by: SortBy,
}
impl CustomCompletion {
@ -27,7 +26,6 @@ impl CustomCompletion {
stack,
decl_id,
line,
sort_by: SortBy::None,
}
}
}
@ -93,10 +91,6 @@ impl Completer for CustomCompletion {
.and_then(|val| val.as_bool().ok())
.unwrap_or(false);
if should_sort {
self.sort_by = SortBy::Ascending;
}
custom_completion_options = Some(CompletionOptions {
case_sensitive: options
.get("case_sensitive")
@ -114,6 +108,11 @@ impl Completer for CustomCompletion {
.unwrap_or(MatchAlgorithm::Prefix),
None => completion_options.match_algorithm,
},
sort: if should_sort {
CompletionSort::Alpha
} else {
CompletionSort::Default
},
});
}
@ -124,12 +123,11 @@ impl Completer for CustomCompletion {
})
.unwrap_or_default();
let suggestions = if let Some(custom_completion_options) = custom_completion_options {
filter(&prefix, suggestions, &custom_completion_options)
} else {
filter(&prefix, suggestions, completion_options)
};
sort_suggestions(&String::from_utf8_lossy(&prefix), suggestions, self.sort_by)
let options = custom_completion_options
.as_ref()
.unwrap_or(completion_options);
let suggestions = filter(&prefix, suggestions, completion_options);
sort_suggestions(&String::from_utf8_lossy(&prefix), suggestions, options)
}
}

View File

@ -6,7 +6,7 @@ use nu_protocol::{
use reedline::Suggestion;
use std::path::{is_separator, Path, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR};
use super::{completion_common::sort_suggestions, SemanticSuggestion, SortBy};
use super::{completion_common::sort_suggestions, SemanticSuggestion};
#[derive(Clone, Default)]
pub struct DotNuCompletion {}
@ -131,6 +131,6 @@ impl Completer for DotNuCompletion {
})
.collect();
sort_suggestions(&prefix_str, output, SortBy::Ascending)
sort_suggestions(&prefix_str, output, options)
}
}

View File

@ -1,6 +1,4 @@
use crate::completions::{
completion_common::sort_suggestions, Completer, CompletionOptions, SortBy,
};
use crate::completions::{completion_common::sort_suggestions, Completer, CompletionOptions};
use nu_protocol::{
ast::{Expr, Expression},
engine::{Stack, StateWorkingSet},
@ -92,7 +90,7 @@ impl Completer for FlagCompletion {
}
}
return sort_suggestions(&String::from_utf8_lossy(&prefix), output, SortBy::Ascending);
return sort_suggestions(&String::from_utf8_lossy(&prefix), output, options);
}
vec![]

View File

@ -13,7 +13,7 @@ mod variable_completions;
pub use base::{Completer, SemanticSuggestion, SuggestionKind};
pub use command_completions::CommandCompletion;
pub use completer::NuCompleter;
pub use completion_options::{CompletionOptions, MatchAlgorithm, SortBy};
pub use completion_options::{CompletionOptions, MatchAlgorithm};
pub use custom_completions::CustomCompletion;
pub use directory_completions::DirectoryCompletion;
pub use dotnu_completions::DotNuCompletion;

View File

@ -9,7 +9,7 @@ use nu_protocol::{
use reedline::Suggestion;
use std::str;
use super::{completion_common::sort_suggestions, SortBy};
use super::completion_common::sort_suggestions;
#[derive(Clone)]
pub struct VariableCompletion {
@ -72,7 +72,7 @@ impl Completer for VariableCompletion {
}
}
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
return sort_suggestions(&prefix_str, output, options);
}
} else {
// No nesting provided, return all env vars
@ -96,7 +96,7 @@ impl Completer for VariableCompletion {
}
}
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
return sort_suggestions(&prefix_str, output, options);
}
}
@ -120,7 +120,7 @@ impl Completer for VariableCompletion {
}
}
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
return sort_suggestions(&prefix_str, output, options);
}
}
@ -142,7 +142,7 @@ impl Completer for VariableCompletion {
}
}
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
return sort_suggestions(&prefix_str, output, options);
}
}
}
@ -229,7 +229,7 @@ impl Completer for VariableCompletion {
}
}
output = sort_suggestions(&prefix_str, output, SortBy::Ascending);
output = sort_suggestions(&prefix_str, output, options);
output.dedup(); // TODO: Removes only consecutive duplicates, is it intended?

View File

@ -35,6 +35,35 @@ impl ReconstructVal for CompletionAlgorithm {
}
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default, PartialEq)]
pub enum CompletionSort {
#[default]
Default,
Alpha,
}
impl FromStr for CompletionSort {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"default" => Ok(Self::Default),
"alpha" => Ok(Self::Alpha),
_ => Err("expected either 'default' or 'alpha'"),
}
}
}
impl ReconstructVal for CompletionSort {
fn reconstruct_value(&self, span: Span) -> Value {
let str = match self {
Self::Default => "default",
Self::Alpha => "alpha",
};
Value::string(str, span)
}
}
pub(super) fn reconstruct_external_completer(config: &Config, span: Span) -> Value {
if let Some(closure) = config.external_completer.as_ref() {
Value::closure(closure.clone(), span)

View File

@ -10,7 +10,7 @@ use crate::{record, ShellError, Span, Value};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub use self::completer::CompletionAlgorithm;
pub use self::completer::{CompletionAlgorithm, CompletionSort};
pub use self::helper::extract_value;
pub use self::hooks::Hooks;
pub use self::output::ErrorStyle;
@ -68,6 +68,7 @@ pub struct Config {
pub quick_completions: bool,
pub partial_completions: bool,
pub completion_algorithm: CompletionAlgorithm,
pub completion_sort: CompletionSort,
pub edit_mode: EditBindings,
pub history: HistoryConfig,
pub keybindings: Vec<ParsedKeybinding>,
@ -140,6 +141,7 @@ impl Default for Config {
quick_completions: true,
partial_completions: true,
completion_algorithm: CompletionAlgorithm::default(),
completion_sort: CompletionSort::default(),
enable_external_completion: true,
max_external_completion_results: 100,
recursion_limit: 50,
@ -340,6 +342,13 @@ impl Value {
"case_sensitive" => {
process_bool_config(value, &mut errors, &mut config.case_sensitive_completions);
}
"sort" => {
process_string_enum(
&mut config.completion_sort,
&[key, key2],
value,
&mut errors);
}
"external" => {
if let Value::Record { val, .. } = value {
val.to_mut().retain_mut(|key3, value|
@ -400,6 +409,7 @@ impl Value {
"partial" => Value::bool(config.partial_completions, span),
"algorithm" => config.completion_algorithm.reconstruct_value(span),
"case_sensitive" => Value::bool(config.case_sensitive_completions, span),
"sort" => config.completion_sort.reconstruct_value(span),
"external" => reconstruct_external(&config, span),
"use_ls_colors" => Value::bool(config.use_ls_colors_completions, span),
},

View File

@ -205,6 +205,7 @@ $env.config = {
quick: true # set this to false to prevent auto-selecting completions when only one remains
partial: true # set this to false to prevent partial filling of the prompt
algorithm: "prefix" # prefix or fuzzy
sort: "default" # default (choose based on algorithm) or alpha (always sort alphabetically)
external: {
enable: true # set to false to prevent nushell looking into $env.PATH to find more suggestions, `false` recommended for WSL users as this look up may be very slow
max_results: 100 # setting it lower can improve completion performance at the cost of omitting some options