mirror of
https://github.com/nushell/nushell
synced 2024-12-28 14:03:09 +00:00
Add MatchAlgorithm for completion suggestions (#5244)
* Pass completion options to each fetch() call * Add MatchAlgorithm to CompletionOptions * Add unit test for MatchAlgorithm * Pass completion options to directory completer
This commit is contained in:
parent
667eb27d1b
commit
e6a70f9846
12 changed files with 151 additions and 64 deletions
|
@ -1,4 +1,4 @@
|
||||||
use crate::completions::SortBy;
|
use crate::completions::{CompletionOptions, SortBy};
|
||||||
use nu_protocol::{engine::StateWorkingSet, levenshtein_distance, Span};
|
use nu_protocol::{engine::StateWorkingSet, levenshtein_distance, Span};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ pub trait Completer {
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
pos: usize,
|
pos: usize,
|
||||||
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion>;
|
) -> Vec<Suggestion>;
|
||||||
|
|
||||||
fn get_sort_by(&self) -> SortBy {
|
fn get_sort_by(&self) -> SortBy {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use crate::completions::{file_completions::file_path_completion, Completer, SortBy};
|
use crate::completions::{
|
||||||
|
file_completions::file_path_completion, Completer, CompletionOptions, MatchAlgorithm, 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},
|
||||||
|
@ -30,7 +32,11 @@ impl CommandCompletion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn external_command_completion(&self, prefix: &str) -> Vec<String> {
|
fn external_command_completion(
|
||||||
|
&self,
|
||||||
|
prefix: &str,
|
||||||
|
match_algorithm: MatchAlgorithm,
|
||||||
|
) -> Vec<String> {
|
||||||
let mut executables = vec![];
|
let mut executables = vec![];
|
||||||
|
|
||||||
let paths = self.engine_state.env_vars.get("PATH");
|
let paths = self.engine_state.env_vars.get("PATH");
|
||||||
|
@ -51,7 +57,8 @@ impl CommandCompletion {
|
||||||
) && matches!(
|
) && matches!(
|
||||||
item.path()
|
item.path()
|
||||||
.file_name()
|
.file_name()
|
||||||
.map(|x| x.to_string_lossy().starts_with(prefix)),
|
.map(|x| match_algorithm
|
||||||
|
.matches_str(&x.to_string_lossy(), prefix)),
|
||||||
Some(true)
|
Some(true)
|
||||||
) && is_executable::is_executable(&item.path())
|
) && is_executable::is_executable(&item.path())
|
||||||
{
|
{
|
||||||
|
@ -74,11 +81,14 @@ impl CommandCompletion {
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
find_externals: bool,
|
find_externals: bool,
|
||||||
|
match_algorithm: MatchAlgorithm,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
let prefix = working_set.get_span_contents(span);
|
let partial = working_set.get_span_contents(span);
|
||||||
|
|
||||||
|
let filter_predicate = |command: &[u8]| match_algorithm.matches_u8(command, partial);
|
||||||
|
|
||||||
let results = working_set
|
let results = working_set
|
||||||
.find_commands_by_prefix(prefix)
|
.find_commands_by_predicate(filter_predicate)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |x| Suggestion {
|
.map(move |x| Suggestion {
|
||||||
value: String::from_utf8_lossy(&x.0).to_string(),
|
value: String::from_utf8_lossy(&x.0).to_string(),
|
||||||
|
@ -90,12 +100,29 @@ impl CommandCompletion {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let results_aliases =
|
let results_aliases = working_set
|
||||||
working_set
|
.find_aliases_by_predicate(filter_predicate)
|
||||||
.find_aliases_by_prefix(prefix)
|
.into_iter()
|
||||||
|
.map(move |x| Suggestion {
|
||||||
|
value: String::from_utf8_lossy(&x).to_string(),
|
||||||
|
description: None,
|
||||||
|
extra: None,
|
||||||
|
span: reedline::Span {
|
||||||
|
start: span.start - offset,
|
||||||
|
end: span.end - offset,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut results = results.chain(results_aliases).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let partial = working_set.get_span_contents(span);
|
||||||
|
let partial = String::from_utf8_lossy(partial).to_string();
|
||||||
|
let results = if find_externals {
|
||||||
|
let results_external = self
|
||||||
|
.external_command_completion(&partial, match_algorithm)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |x| Suggestion {
|
.map(move |x| Suggestion {
|
||||||
value: String::from_utf8_lossy(&x).to_string(),
|
value: x,
|
||||||
description: None,
|
description: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
|
@ -104,24 +131,6 @@ impl CommandCompletion {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut results = results.chain(results_aliases).collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let prefix = working_set.get_span_contents(span);
|
|
||||||
let prefix = String::from_utf8_lossy(prefix).to_string();
|
|
||||||
let results = if find_externals {
|
|
||||||
let results_external =
|
|
||||||
self.external_command_completion(&prefix)
|
|
||||||
.into_iter()
|
|
||||||
.map(move |x| Suggestion {
|
|
||||||
value: x,
|
|
||||||
description: None,
|
|
||||||
extra: None,
|
|
||||||
span: reedline::Span {
|
|
||||||
start: span.start - offset,
|
|
||||||
end: span.end - offset,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
for external in results_external {
|
for external in results_external {
|
||||||
if results.contains(&external) {
|
if results.contains(&external) {
|
||||||
results.push(Suggestion {
|
results.push(Suggestion {
|
||||||
|
@ -152,6 +161,7 @@ impl Completer for CommandCompletion {
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
pos: usize,
|
pos: usize,
|
||||||
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
let last = self
|
let last = self
|
||||||
.flattened
|
.flattened
|
||||||
|
@ -180,6 +190,7 @@ impl Completer for CommandCompletion {
|
||||||
},
|
},
|
||||||
offset,
|
offset,
|
||||||
false,
|
false,
|
||||||
|
options.match_algorithm,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
|
@ -194,7 +205,7 @@ impl Completer for CommandCompletion {
|
||||||
|| ((span.end - span.start) == 0)
|
|| ((span.end - span.start) == 0)
|
||||||
{
|
{
|
||||||
// we're in a gap or at a command
|
// we're in a gap or at a command
|
||||||
self.complete_commands(working_set, span, offset, true)
|
self.complete_commands(working_set, span, offset, true, options.match_algorithm)
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
};
|
};
|
||||||
|
@ -221,7 +232,7 @@ 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();
|
||||||
|
|
||||||
file_path_completion(span, &prefix, &cwd)
|
file_path_completion(span, &prefix, &cwd, options.match_algorithm)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |x| {
|
.map(move |x| {
|
||||||
if self.flat_idx == 0 {
|
if self.flat_idx == 0 {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::completions::{
|
use crate::completions::{
|
||||||
CommandCompletion, Completer, CustomCompletion, DirectoryCompletion, DotNuCompletion,
|
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
|
||||||
FileCompletion, FlagCompletion, VariableCompletion,
|
DotNuCompletion, FileCompletion, FlagCompletion, VariableCompletion,
|
||||||
};
|
};
|
||||||
use nu_parser::{flatten_expression, parse, FlatShape};
|
use nu_parser::{flatten_expression, parse, FlatShape};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
|
@ -35,8 +35,11 @@ impl NuCompleter {
|
||||||
offset: usize,
|
offset: usize,
|
||||||
pos: usize,
|
pos: usize,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
|
let options = CompletionOptions::default();
|
||||||
|
|
||||||
// Fetch
|
// Fetch
|
||||||
let mut suggestions = completer.fetch(working_set, prefix.clone(), new_span, offset, pos);
|
let mut suggestions =
|
||||||
|
completer.fetch(working_set, prefix.clone(), new_span, offset, pos, &options);
|
||||||
|
|
||||||
// Sort
|
// Sort
|
||||||
suggestions = completer.sort(suggestions, prefix);
|
suggestions = completer.sort(suggestions, prefix);
|
||||||
|
|
|
@ -5,11 +5,38 @@ pub enum SortBy {
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Describes how suggestions should be matched.
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum MatchAlgorithm {
|
||||||
|
/// Only show suggestions which begin with the given input
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// "git switch" is matched by "git sw"
|
||||||
|
Prefix,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MatchAlgorithm {
|
||||||
|
/// Returns whether the `needle` search text matches the given `haystack`.
|
||||||
|
pub fn matches_str(&self, haystack: &str, needle: &str) -> bool {
|
||||||
|
match *self {
|
||||||
|
MatchAlgorithm::Prefix => haystack.starts_with(needle),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the `needle` search text matches the given `haystack`.
|
||||||
|
pub fn matches_u8(&self, haystack: &[u8], needle: &[u8]) -> bool {
|
||||||
|
match *self {
|
||||||
|
MatchAlgorithm::Prefix => haystack.starts_with(needle),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CompletionOptions {
|
pub struct CompletionOptions {
|
||||||
pub case_sensitive: bool,
|
pub case_sensitive: bool,
|
||||||
pub positional: bool,
|
pub positional: bool,
|
||||||
pub sort_by: SortBy,
|
pub sort_by: SortBy,
|
||||||
|
pub match_algorithm: MatchAlgorithm,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CompletionOptions {
|
impl Default for CompletionOptions {
|
||||||
|
@ -18,6 +45,25 @@ impl Default for CompletionOptions {
|
||||||
case_sensitive: true,
|
case_sensitive: true,
|
||||||
positional: true,
|
positional: true,
|
||||||
sort_by: SortBy::Ascending,
|
sort_by: SortBy::Ascending,
|
||||||
|
match_algorithm: MatchAlgorithm::Prefix,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::MatchAlgorithm;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn match_algorithm_prefix() {
|
||||||
|
let algorithm = MatchAlgorithm::Prefix;
|
||||||
|
|
||||||
|
assert!(algorithm.matches_str("example text", ""));
|
||||||
|
assert!(algorithm.matches_str("example text", "examp"));
|
||||||
|
assert!(!algorithm.matches_str("example text", "text"));
|
||||||
|
|
||||||
|
assert!(algorithm.matches_u8(&[1, 2, 3], &[]));
|
||||||
|
assert!(algorithm.matches_u8(&[1, 2, 3], &[1, 2]));
|
||||||
|
assert!(!algorithm.matches_u8(&[1, 2, 3], &[2, 3]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::completions::{Completer, CompletionOptions, SortBy};
|
use crate::completions::{Completer, CompletionOptions, MatchAlgorithm, SortBy};
|
||||||
use nu_engine::eval_call;
|
use nu_engine::eval_call;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Argument, Call, Expr, Expression},
|
ast::{Argument, Call, Expr, Expression},
|
||||||
|
@ -97,6 +97,7 @@ impl Completer for CustomCompletion {
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
pos: usize,
|
pos: usize,
|
||||||
|
_options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
// Line position
|
// Line position
|
||||||
let line_pos = pos - offset;
|
let line_pos = pos - offset;
|
||||||
|
@ -169,6 +170,7 @@ impl Completer for CustomCompletion {
|
||||||
} else {
|
} else {
|
||||||
SortBy::None
|
SortBy::None
|
||||||
},
|
},
|
||||||
|
match_algorithm: MatchAlgorithm::Prefix,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
CompletionOptions::default()
|
CompletionOptions::default()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::completions::{file_path_completion, Completer};
|
use crate::completions::{file_path_completion, Completer, CompletionOptions};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::{EngineState, StateWorkingSet},
|
||||||
levenshtein_distance, Span,
|
levenshtein_distance, Span,
|
||||||
|
@ -28,6 +28,7 @@ impl Completer for DirectoryCompletion {
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_: usize,
|
_: usize,
|
||||||
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> 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() {
|
||||||
|
@ -37,10 +38,10 @@ impl Completer for DirectoryCompletion {
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
"".to_string()
|
||||||
};
|
};
|
||||||
let prefix = String::from_utf8_lossy(&prefix).to_string();
|
let partial = String::from_utf8_lossy(&prefix).to_string();
|
||||||
|
|
||||||
// Filter only the folders
|
// Filter only the folders
|
||||||
let output: Vec<_> = file_path_completion(span, &prefix, &cwd)
|
let output: Vec<_> = file_path_completion(span, &partial, &cwd, options.match_algorithm)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(move |x| {
|
.filter_map(move |x| {
|
||||||
if x.1.ends_with(SEP) {
|
if x.1.ends_with(SEP) {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use crate::completions::{file_path_completion, partial_from, Completer, SortBy};
|
use crate::completions::{
|
||||||
|
file_path_completion, partial_from, Completer, CompletionOptions, SortBy,
|
||||||
|
};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::{EngineState, StateWorkingSet},
|
||||||
Span,
|
Span,
|
||||||
|
@ -26,6 +28,7 @@ impl Completer for DotNuCompletion {
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_: usize,
|
_: usize,
|
||||||
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> 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![];
|
||||||
|
@ -88,7 +91,7 @@ impl Completer for DotNuCompletion {
|
||||||
let output: Vec<Suggestion> = search_dirs
|
let output: Vec<Suggestion> = search_dirs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|it| {
|
.flat_map(|it| {
|
||||||
file_path_completion(span, &partial, &it)
|
file_path_completion(span, &partial, &it, options.match_algorithm)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|it| {
|
.filter(|it| {
|
||||||
// Different base dir, so we list the .nu files or folders
|
// Different base dir, so we list the .nu files or folders
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::completions::Completer;
|
use crate::completions::{Completer, CompletionOptions, MatchAlgorithm};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::{EngineState, StateWorkingSet},
|
||||||
levenshtein_distance, Span,
|
levenshtein_distance, Span,
|
||||||
|
@ -28,6 +28,7 @@ impl Completer for FileCompletion {
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_: usize,
|
_: usize,
|
||||||
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> 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() {
|
||||||
|
@ -38,7 +39,7 @@ impl Completer for FileCompletion {
|
||||||
"".to_string()
|
"".to_string()
|
||||||
};
|
};
|
||||||
let prefix = String::from_utf8_lossy(&prefix).to_string();
|
let prefix = String::from_utf8_lossy(&prefix).to_string();
|
||||||
let output: Vec<_> = file_path_completion(span, &prefix, &cwd)
|
let output: Vec<_> = file_path_completion(span, &prefix, &cwd, options.match_algorithm)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |x| Suggestion {
|
.map(move |x| Suggestion {
|
||||||
value: x.1,
|
value: x.1,
|
||||||
|
@ -110,6 +111,7 @@ pub fn file_path_completion(
|
||||||
span: nu_protocol::Span,
|
span: nu_protocol::Span,
|
||||||
partial: &str,
|
partial: &str,
|
||||||
cwd: &str,
|
cwd: &str,
|
||||||
|
match_algorithm: MatchAlgorithm,
|
||||||
) -> Vec<(nu_protocol::Span, String)> {
|
) -> Vec<(nu_protocol::Span, String)> {
|
||||||
let (base_dir_name, partial) = partial_from(partial);
|
let (base_dir_name, partial) = partial_from(partial);
|
||||||
|
|
||||||
|
@ -125,7 +127,7 @@ pub fn file_path_completion(
|
||||||
.filter_map(|entry| {
|
.filter_map(|entry| {
|
||||||
entry.ok().and_then(|entry| {
|
entry.ok().and_then(|entry| {
|
||||||
let mut file_name = entry.file_name().to_string_lossy().into_owned();
|
let mut file_name = entry.file_name().to_string_lossy().into_owned();
|
||||||
if matches(&partial, &file_name) {
|
if matches(&partial, &file_name, match_algorithm) {
|
||||||
let mut path = format!("{}{}", base_dir_name, file_name);
|
let mut path = format!("{}{}", base_dir_name, file_name);
|
||||||
if entry.path().is_dir() {
|
if entry.path().is_dir() {
|
||||||
path.push(SEP);
|
path.push(SEP);
|
||||||
|
@ -153,7 +155,6 @@ pub fn file_path_completion(
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn matches(partial: &str, from: &str) -> bool {
|
pub fn matches(partial: &str, from: &str, match_algorithm: MatchAlgorithm) -> bool {
|
||||||
from.to_ascii_lowercase()
|
match_algorithm.matches_str(&from.to_ascii_lowercase(), &partial.to_ascii_lowercase())
|
||||||
.starts_with(&partial.to_ascii_lowercase())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::completions::Completer;
|
use crate::completions::{Completer, CompletionOptions};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Expr, Expression},
|
ast::{Expr, Expression},
|
||||||
engine::StateWorkingSet,
|
engine::StateWorkingSet,
|
||||||
|
@ -26,6 +26,7 @@ impl Completer for FlagCompletion {
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_: usize,
|
_: usize,
|
||||||
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> 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 {
|
||||||
|
@ -40,7 +41,8 @@ impl Completer for FlagCompletion {
|
||||||
let mut named = vec![0; short.len_utf8()];
|
let mut named = vec![0; short.len_utf8()];
|
||||||
short.encode_utf8(&mut named);
|
short.encode_utf8(&mut named);
|
||||||
named.insert(0, b'-');
|
named.insert(0, b'-');
|
||||||
if named.starts_with(&prefix) {
|
|
||||||
|
if options.match_algorithm.matches_u8(&named, &prefix) {
|
||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: String::from_utf8_lossy(&named).to_string(),
|
value: String::from_utf8_lossy(&named).to_string(),
|
||||||
description: Some(flag_desc.to_string()),
|
description: Some(flag_desc.to_string()),
|
||||||
|
@ -60,7 +62,8 @@ impl Completer for FlagCompletion {
|
||||||
let mut named = named.long.as_bytes().to_vec();
|
let mut named = named.long.as_bytes().to_vec();
|
||||||
named.insert(0, b'-');
|
named.insert(0, b'-');
|
||||||
named.insert(0, b'-');
|
named.insert(0, b'-');
|
||||||
if named.starts_with(&prefix) {
|
|
||||||
|
if options.match_algorithm.matches_u8(&named, &prefix) {
|
||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: String::from_utf8_lossy(&named).to_string(),
|
value: String::from_utf8_lossy(&named).to_string(),
|
||||||
description: Some(flag_desc.to_string()),
|
description: Some(flag_desc.to_string()),
|
||||||
|
|
|
@ -12,7 +12,7 @@ mod variable_completions;
|
||||||
pub use base::Completer;
|
pub use base::Completer;
|
||||||
pub use command_completions::CommandCompletion;
|
pub use command_completions::CommandCompletion;
|
||||||
pub use completer::NuCompleter;
|
pub use completer::NuCompleter;
|
||||||
pub use completion_options::{CompletionOptions, SortBy};
|
pub use completion_options::{CompletionOptions, MatchAlgorithm, SortBy};
|
||||||
pub use custom_completions::CustomCompletion;
|
pub use custom_completions::CustomCompletion;
|
||||||
pub use directory_completions::DirectoryCompletion;
|
pub use directory_completions::DirectoryCompletion;
|
||||||
pub use dotnu_completions::DotNuCompletion;
|
pub use dotnu_completions::DotNuCompletion;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::completions::Completer;
|
use crate::completions::{Completer, CompletionOptions};
|
||||||
use nu_engine::eval_variable;
|
use nu_engine::eval_variable;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
|
@ -37,6 +37,7 @@ impl Completer for VariableCompletion {
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_: usize,
|
_: usize,
|
||||||
|
options: &CompletionOptions,
|
||||||
) -> Vec<Suggestion> {
|
) -> Vec<Suggestion> {
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
let builtins = ["$nu", "$in", "$config", "$env", "$nothing"];
|
let builtins = ["$nu", "$in", "$config", "$env", "$nothing"];
|
||||||
|
@ -54,7 +55,10 @@ 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) {
|
if options
|
||||||
|
.match_algorithm
|
||||||
|
.matches_u8(env_var.0.as_bytes(), &prefix)
|
||||||
|
{
|
||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: env_var.0,
|
value: env_var.0,
|
||||||
description: None,
|
description: None,
|
||||||
|
@ -155,7 +159,10 @@ impl Completer for VariableCompletion {
|
||||||
|
|
||||||
// Variable completion (e.g: $en<tab> to complete $env)
|
// Variable completion (e.g: $en<tab> to complete $env)
|
||||||
for builtin in builtins {
|
for builtin in builtins {
|
||||||
if builtin.as_bytes().starts_with(&prefix) {
|
if options
|
||||||
|
.match_algorithm
|
||||||
|
.matches_u8(builtin.as_bytes(), &prefix)
|
||||||
|
{
|
||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: builtin.to_string(),
|
value: builtin.to_string(),
|
||||||
description: None,
|
description: None,
|
||||||
|
@ -168,7 +175,7 @@ impl Completer for VariableCompletion {
|
||||||
// Working set scope vars
|
// Working set scope vars
|
||||||
for scope in &working_set.delta.scope {
|
for scope in &working_set.delta.scope {
|
||||||
for v in &scope.vars {
|
for v in &scope.vars {
|
||||||
if v.0.starts_with(&prefix) {
|
if options.match_algorithm.matches_u8(v.0, &prefix) {
|
||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: String::from_utf8_lossy(v.0).to_string(),
|
value: String::from_utf8_lossy(v.0).to_string(),
|
||||||
description: None,
|
description: None,
|
||||||
|
@ -182,7 +189,7 @@ impl Completer for VariableCompletion {
|
||||||
// Permanent state vars
|
// Permanent state vars
|
||||||
for scope in &self.engine_state.scope {
|
for scope in &self.engine_state.scope {
|
||||||
for v in &scope.vars {
|
for v in &scope.vars {
|
||||||
if v.0.starts_with(&prefix) {
|
if options.match_algorithm.matches_u8(v.0, &prefix) {
|
||||||
output.push(Suggestion {
|
output.push(Suggestion {
|
||||||
value: String::from_utf8_lossy(v.0).to_string(),
|
value: String::from_utf8_lossy(v.0).to_string(),
|
||||||
description: None,
|
description: None,
|
||||||
|
|
|
@ -444,12 +444,15 @@ impl EngineState {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_commands_by_prefix(&self, name: &[u8]) -> Vec<(Vec<u8>, Option<String>)> {
|
pub fn find_commands_by_predicate(
|
||||||
|
&self,
|
||||||
|
predicate: impl Fn(&[u8]) -> bool,
|
||||||
|
) -> Vec<(Vec<u8>, Option<String>)> {
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
|
|
||||||
for scope in self.scope.iter().rev() {
|
for scope in self.scope.iter().rev() {
|
||||||
for decl in &scope.decls {
|
for decl in &scope.decls {
|
||||||
if decl.0.starts_with(name) {
|
if predicate(decl.0) {
|
||||||
let command = self.get_decl(*decl.1);
|
let command = self.get_decl(*decl.1);
|
||||||
output.push((decl.0.clone(), Some(command.usage().to_string())));
|
output.push((decl.0.clone(), Some(command.usage().to_string())));
|
||||||
}
|
}
|
||||||
|
@ -459,12 +462,12 @@ impl EngineState {
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_aliases_by_prefix(&self, name: &[u8]) -> Vec<Vec<u8>> {
|
pub fn find_aliases_by_predicate(&self, predicate: impl Fn(&[u8]) -> bool) -> Vec<Vec<u8>> {
|
||||||
self.scope
|
self.scope
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.flat_map(|scope| &scope.aliases)
|
.flat_map(|scope| &scope.aliases)
|
||||||
.filter(|decl| decl.0.starts_with(name))
|
.filter(|decl| predicate(decl.0))
|
||||||
.map(|decl| decl.0.clone())
|
.map(|decl| decl.0.clone())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -1315,34 +1318,40 @@ impl<'a> StateWorkingSet<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_commands_by_prefix(&self, name: &[u8]) -> Vec<(Vec<u8>, Option<String>)> {
|
pub fn find_commands_by_predicate(
|
||||||
|
&self,
|
||||||
|
predicate: impl Fn(&[u8]) -> bool,
|
||||||
|
) -> Vec<(Vec<u8>, Option<String>)> {
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
|
|
||||||
for scope in self.delta.scope.iter().rev() {
|
for scope in self.delta.scope.iter().rev() {
|
||||||
for decl in &scope.decls {
|
for decl in &scope.decls {
|
||||||
if decl.0.starts_with(name) {
|
if predicate(decl.0) {
|
||||||
let command = self.get_decl(*decl.1);
|
let command = self.get_decl(*decl.1);
|
||||||
output.push((decl.0.clone(), Some(command.usage().to_string())));
|
output.push((decl.0.clone(), Some(command.usage().to_string())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut permanent = self.permanent_state.find_commands_by_prefix(name);
|
let mut permanent = self.permanent_state.find_commands_by_predicate(predicate);
|
||||||
|
|
||||||
output.append(&mut permanent);
|
output.append(&mut permanent);
|
||||||
|
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_aliases_by_prefix(&self, name: &[u8]) -> Vec<Vec<u8>> {
|
pub fn find_aliases_by_predicate(
|
||||||
|
&self,
|
||||||
|
predicate: impl Fn(&[u8]) -> bool + Copy,
|
||||||
|
) -> Vec<Vec<u8>> {
|
||||||
self.delta
|
self.delta
|
||||||
.scope
|
.scope
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.flat_map(|scope| &scope.aliases)
|
.flat_map(|scope| &scope.aliases)
|
||||||
.filter(|decl| decl.0.starts_with(name))
|
.filter(|decl| predicate(decl.0))
|
||||||
.map(|decl| decl.0.clone())
|
.map(|decl| decl.0.clone())
|
||||||
.chain(self.permanent_state.find_aliases_by_prefix(name))
|
.chain(self.permanent_state.find_aliases_by_predicate(predicate))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue