Merge 8bf907b7c2
into 948b90299d
This commit is contained in:
commit
19c4e77e18
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -2926,7 +2926,6 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"fancy-regex",
|
"fancy-regex",
|
||||||
"fuzzy-matcher",
|
|
||||||
"is_executable",
|
"is_executable",
|
||||||
"log",
|
"log",
|
||||||
"lscolors",
|
"lscolors",
|
||||||
|
@ -2943,6 +2942,7 @@ dependencies = [
|
||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
"nu-test-support",
|
"nu-test-support",
|
||||||
"nu-utils",
|
"nu-utils",
|
||||||
|
"nucleo-matcher",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"reedline",
|
"reedline",
|
||||||
|
@ -3549,6 +3549,16 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nucleo-matcher"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bf33f538733d1a5a3494b836ba913207f14d9d4a1d3cd67030c5061bdd2cac85"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num"
|
name = "num"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
|
|
|
@ -90,7 +90,6 @@ fancy-regex = "0.13"
|
||||||
filesize = "0.2"
|
filesize = "0.2"
|
||||||
filetime = "0.2"
|
filetime = "0.2"
|
||||||
fs_extra = "1.3"
|
fs_extra = "1.3"
|
||||||
fuzzy-matcher = "0.3"
|
|
||||||
hamcrest2 = "0.3"
|
hamcrest2 = "0.3"
|
||||||
heck = "0.5.0"
|
heck = "0.5.0"
|
||||||
human-date-parser = "0.1.1"
|
human-date-parser = "0.1.1"
|
||||||
|
@ -116,6 +115,7 @@ native-tls = "0.2"
|
||||||
nix = { version = "0.28", default-features = false }
|
nix = { version = "0.28", default-features = false }
|
||||||
notify-debouncer-full = { version = "0.3", default-features = false }
|
notify-debouncer-full = { version = "0.3", default-features = false }
|
||||||
nu-ansi-term = "0.50.0"
|
nu-ansi-term = "0.50.0"
|
||||||
|
nucleo-matcher = "0.3"
|
||||||
num-format = "0.4"
|
num-format = "0.4"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
omnipath = "0.1"
|
omnipath = "0.1"
|
||||||
|
|
|
@ -32,11 +32,11 @@ reedline = { workspace = true, features = ["bashisms", "sqlite"] }
|
||||||
chrono = { default-features = false, features = ["std"], workspace = true }
|
chrono = { default-features = false, features = ["std"], workspace = true }
|
||||||
crossterm = { workspace = true }
|
crossterm = { workspace = true }
|
||||||
fancy-regex = { workspace = true }
|
fancy-regex = { workspace = true }
|
||||||
fuzzy-matcher = { workspace = true }
|
|
||||||
is_executable = { workspace = true }
|
is_executable = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
miette = { workspace = true, features = ["fancy-no-backtrace"] }
|
miette = { workspace = true, features = ["fancy-no-backtrace"] }
|
||||||
lscolors = { workspace = true, default-features = false, features = ["nu-ansi-term"] }
|
lscolors = { workspace = true, default-features = false, features = ["nu-ansi-term"] }
|
||||||
|
nucleo-matcher = { workspace = true }
|
||||||
once_cell = { workspace = true }
|
once_cell = { workspace = true }
|
||||||
percent-encoding = { workspace = true }
|
percent-encoding = { workspace = true }
|
||||||
sysinfo = { workspace = true }
|
sysinfo = { workspace = true }
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
completions::{Completer, CompletionOptions, MatchAlgorithm, SortBy},
|
completions::{Completer, CompletionOptions, SortBy},
|
||||||
SuggestionKind,
|
SuggestionKind,
|
||||||
};
|
};
|
||||||
use nu_parser::FlatShape;
|
use nu_parser::FlatShape;
|
||||||
|
@ -9,7 +11,10 @@ use nu_protocol::{
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
|
|
||||||
use super::{completion_common::sort_suggestions, SemanticSuggestion};
|
use super::{
|
||||||
|
completion_options::{MatcherOptions, NuMatcher},
|
||||||
|
SemanticSuggestion,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct CommandCompletion {
|
pub struct CommandCompletion {
|
||||||
flattened: Vec<(Span, FlatShape)>,
|
flattened: Vec<(Span, FlatShape)>,
|
||||||
|
@ -33,10 +38,11 @@ impl CommandCompletion {
|
||||||
fn external_command_completion(
|
fn external_command_completion(
|
||||||
&self,
|
&self,
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
prefix: &str,
|
sugg_span: reedline::Span,
|
||||||
match_algorithm: MatchAlgorithm,
|
matched_internal: HashSet<String>,
|
||||||
) -> Vec<String> {
|
matcher: &mut NuMatcher<SemanticSuggestion>,
|
||||||
let mut executables = vec![];
|
) {
|
||||||
|
let mut executables = HashSet::new();
|
||||||
|
|
||||||
// os agnostic way to get the PATH env var
|
// os agnostic way to get the PATH env var
|
||||||
let paths = working_set.permanent_state.get_path_env_var();
|
let paths = working_set.permanent_state.get_path_env_var();
|
||||||
|
@ -48,27 +54,36 @@ impl CommandCompletion {
|
||||||
|
|
||||||
if let Ok(mut contents) = std::fs::read_dir(path.as_ref()) {
|
if let Ok(mut contents) = std::fs::read_dir(path.as_ref()) {
|
||||||
while let Some(Ok(item)) = contents.next() {
|
while let Some(Ok(item)) = contents.next() {
|
||||||
if working_set
|
if let Ok(name) = item.file_name().into_string() {
|
||||||
.permanent_state
|
if working_set
|
||||||
.config
|
.permanent_state
|
||||||
.max_external_completion_results
|
.config
|
||||||
> executables.len() as i64
|
.max_external_completion_results
|
||||||
&& !executables.contains(
|
> executables.len() as i64
|
||||||
&item
|
&& !executables.contains(&name)
|
||||||
.path()
|
&& is_executable::is_executable(item.path())
|
||||||
.file_name()
|
{
|
||||||
.map(|x| x.to_string_lossy().to_string())
|
executables.insert(name.clone());
|
||||||
.unwrap_or_default(),
|
let name = if matched_internal.contains(&name) {
|
||||||
)
|
format!("^{}", name)
|
||||||
&& matches!(
|
} else {
|
||||||
item.path().file_name().map(|x| match_algorithm
|
name.to_string()
|
||||||
.matches_str(&x.to_string_lossy(), prefix)),
|
};
|
||||||
Some(true)
|
matcher.add(
|
||||||
)
|
name.clone(),
|
||||||
&& is_executable::is_executable(item.path())
|
SemanticSuggestion {
|
||||||
{
|
suggestion: Suggestion {
|
||||||
if let Ok(name) = item.file_name().into_string() {
|
value: name,
|
||||||
executables.push(name);
|
description: None,
|
||||||
|
style: None,
|
||||||
|
extra: None,
|
||||||
|
span: sugg_span,
|
||||||
|
append_whitespace: true,
|
||||||
|
},
|
||||||
|
// TODO: is there a way to create a test?
|
||||||
|
kind: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,8 +91,6 @@ impl CommandCompletion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
executables
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete_commands(
|
fn complete_commands(
|
||||||
|
@ -86,73 +99,47 @@ impl CommandCompletion {
|
||||||
span: Span,
|
span: Span,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
find_externals: bool,
|
find_externals: bool,
|
||||||
match_algorithm: MatchAlgorithm,
|
options: MatcherOptions,
|
||||||
) -> Vec<SemanticSuggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
let partial = working_set.get_span_contents(span);
|
let partial = working_set.get_span_contents(span);
|
||||||
|
let partial = String::from_utf8_lossy(partial);
|
||||||
|
|
||||||
let filter_predicate = |command: &[u8]| match_algorithm.matches_u8(command, partial);
|
let sugg_span = reedline::Span::new(span.start - offset, span.end - offset);
|
||||||
|
|
||||||
let mut results = working_set
|
let mut matcher = NuMatcher::new(partial, options);
|
||||||
.find_commands_by_predicate(filter_predicate, true)
|
|
||||||
.into_iter()
|
let all_internal_commands = working_set.find_commands_by_predicate(|_| true, true);
|
||||||
.map(move |x| SemanticSuggestion {
|
|
||||||
|
let mut matched_internal = HashSet::new();
|
||||||
|
|
||||||
|
for (name, usage, typ) in all_internal_commands {
|
||||||
|
let name = String::from_utf8_lossy(&name);
|
||||||
|
let sugg = SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: String::from_utf8_lossy(&x.0).to_string(),
|
value: name.to_string(),
|
||||||
description: x.1,
|
description: usage,
|
||||||
style: None,
|
style: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: reedline::Span::new(span.start - offset, span.end - offset),
|
span: sugg_span,
|
||||||
append_whitespace: true,
|
append_whitespace: true,
|
||||||
},
|
},
|
||||||
kind: Some(SuggestionKind::Command(x.2)),
|
kind: Some(SuggestionKind::Command(typ)),
|
||||||
})
|
};
|
||||||
.collect::<Vec<_>>();
|
if matcher.add(&name, sugg) {
|
||||||
|
matched_internal.insert(name.to_string());
|
||||||
let partial = working_set.get_span_contents(span);
|
}
|
||||||
let partial = String::from_utf8_lossy(partial).to_string();
|
}
|
||||||
|
|
||||||
if find_externals {
|
if find_externals {
|
||||||
let results_external = self
|
self.external_command_completion(
|
||||||
.external_command_completion(working_set, &partial, match_algorithm)
|
working_set,
|
||||||
.into_iter()
|
sugg_span,
|
||||||
.map(move |x| SemanticSuggestion {
|
matched_internal,
|
||||||
suggestion: Suggestion {
|
&mut matcher,
|
||||||
value: x,
|
);
|
||||||
description: None,
|
|
||||||
style: None,
|
|
||||||
extra: None,
|
|
||||||
span: reedline::Span::new(span.start - offset, span.end - offset),
|
|
||||||
append_whitespace: true,
|
|
||||||
},
|
|
||||||
// TODO: is there a way to create a test?
|
|
||||||
kind: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
let results_strings: Vec<String> =
|
|
||||||
results.iter().map(|x| x.suggestion.value.clone()).collect();
|
|
||||||
|
|
||||||
for external in results_external {
|
|
||||||
if results_strings.contains(&external.suggestion.value) {
|
|
||||||
results.push(SemanticSuggestion {
|
|
||||||
suggestion: Suggestion {
|
|
||||||
value: format!("^{}", external.suggestion.value),
|
|
||||||
description: None,
|
|
||||||
style: None,
|
|
||||||
extra: None,
|
|
||||||
span: external.suggestion.span,
|
|
||||||
append_whitespace: true,
|
|
||||||
},
|
|
||||||
kind: external.kind,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
results.push(external)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
results
|
|
||||||
} else {
|
|
||||||
results
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
matcher.get_results()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,6 +154,8 @@ impl Completer for CommandCompletion {
|
||||||
pos: usize,
|
pos: usize,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<SemanticSuggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
|
let matcher_options = MatcherOptions::new(options).sort_by(self.get_sort_by());
|
||||||
|
|
||||||
let last = self
|
let last = self
|
||||||
.flattened
|
.flattened
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -191,7 +180,7 @@ impl Completer for CommandCompletion {
|
||||||
Span::new(last.0.start, pos),
|
Span::new(last.0.start, pos),
|
||||||
offset,
|
offset,
|
||||||
false,
|
false,
|
||||||
options.match_algorithm,
|
matcher_options.clone(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
|
@ -221,7 +210,7 @@ impl Completer for CommandCompletion {
|
||||||
span,
|
span,
|
||||||
offset,
|
offset,
|
||||||
config.enable_external_completion,
|
config.enable_external_completion,
|
||||||
options.match_algorithm,
|
matcher_options,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
use crate::{
|
|
||||||
completions::{matches, CompletionOptions},
|
|
||||||
SemanticSuggestion,
|
|
||||||
};
|
|
||||||
use nu_ansi_term::Style;
|
use nu_ansi_term::Style;
|
||||||
use nu_engine::env_to_string;
|
use nu_engine::env_to_string;
|
||||||
use nu_path::{expand_to_real_path, home_dir};
|
use nu_path::{expand_to_real_path, home_dir};
|
||||||
|
@ -14,75 +10,100 @@ use std::path::{
|
||||||
is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR,
|
is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::SortBy;
|
use super::completion_options::{MatcherOptions, NuMatcher};
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct PathBuiltFromString {
|
pub struct PathBuiltFromString {
|
||||||
|
cwd: PathBuf,
|
||||||
parts: Vec<String>,
|
parts: Vec<String>,
|
||||||
isdir: bool,
|
isdir: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Recursively find files matching the search string
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `partial` - Remaining components of the partial text the user's typed
|
||||||
|
/// * `built_paths` - Directories matching the previous components of `partial`
|
||||||
|
/// * `isdir` - Is the user looking for a directory? (true if partial text ended in a slash)
|
||||||
fn complete_rec(
|
fn complete_rec(
|
||||||
partial: &[&str],
|
partial: &[&str],
|
||||||
built: &PathBuiltFromString,
|
built_paths: &[PathBuiltFromString],
|
||||||
cwd: &Path,
|
options: MatcherOptions,
|
||||||
options: &CompletionOptions,
|
want_dir: bool,
|
||||||
dir: bool,
|
|
||||||
isdir: bool,
|
isdir: bool,
|
||||||
) -> Vec<PathBuiltFromString> {
|
) -> Vec<PathBuiltFromString> {
|
||||||
let mut completions = vec![];
|
|
||||||
|
|
||||||
if let Some((&base, rest)) = partial.split_first() {
|
if let Some((&base, rest)) = partial.split_first() {
|
||||||
if (base == "." || base == "..") && (isdir || !rest.is_empty()) {
|
if (base == "." || base == "..") && (isdir || !rest.is_empty()) {
|
||||||
let mut built = built.clone();
|
let builts: Vec<_> = built_paths
|
||||||
built.parts.push(base.to_string());
|
.iter()
|
||||||
built.isdir = true;
|
.map(|built| {
|
||||||
return complete_rec(rest, &built, cwd, options, dir, isdir);
|
let mut built = built.clone();
|
||||||
|
built.parts.push(base.to_string());
|
||||||
|
built.isdir = true;
|
||||||
|
built
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
return complete_rec(rest, &builts, options, want_dir, isdir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut built_path = cwd.to_path_buf();
|
let entries: Vec<_> = built_paths
|
||||||
for part in &built.parts {
|
.iter()
|
||||||
built_path.push(part);
|
.flat_map(|built| {
|
||||||
}
|
let mut built_path = built.cwd.clone();
|
||||||
|
for part in &built.parts {
|
||||||
|
built_path.push(part);
|
||||||
|
}
|
||||||
|
|
||||||
let Ok(result) = built_path.read_dir() else {
|
let Ok(result) = built_path.read_dir() else {
|
||||||
return completions;
|
return Vec::new();
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut entries = Vec::new();
|
result
|
||||||
for entry in result.filter_map(|e| e.ok()) {
|
.filter_map(|e| e.ok())
|
||||||
let entry_name = entry.file_name().to_string_lossy().into_owned();
|
.filter_map(|entry| {
|
||||||
let entry_isdir = entry.path().is_dir();
|
let entry_name = entry.file_name().to_string_lossy().into_owned();
|
||||||
let mut built = built.clone();
|
let entry_isdir = entry.path().is_dir();
|
||||||
built.parts.push(entry_name.clone());
|
let mut built = built.clone();
|
||||||
built.isdir = entry_isdir;
|
built.parts.push(entry_name.clone());
|
||||||
|
built.isdir = entry_isdir;
|
||||||
|
|
||||||
if !dir || entry_isdir {
|
if !want_dir || entry_isdir {
|
||||||
entries.push((entry_name, built));
|
Some((entry_name, built))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let prefix = partial.first().unwrap_or(&"");
|
|
||||||
let sorted_entries = sort_completions(prefix, entries, SortBy::Ascending, |(entry, _)| entry);
|
|
||||||
|
|
||||||
for (entry_name, built) in sorted_entries {
|
|
||||||
match partial.split_first() {
|
|
||||||
Some((base, rest)) => {
|
|
||||||
if matches(base, &entry_name, options) {
|
|
||||||
if !rest.is_empty() || isdir {
|
|
||||||
completions.extend(complete_rec(rest, &built, cwd, options, dir, isdir));
|
|
||||||
} else {
|
} else {
|
||||||
completions.push(built);
|
None
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
.collect()
|
||||||
None => {
|
})
|
||||||
completions.push(built);
|
.collect();
|
||||||
}
|
|
||||||
|
if let Some((base, rest)) = partial.split_first() {
|
||||||
|
let mut matcher = NuMatcher::new(base, options.clone());
|
||||||
|
|
||||||
|
for (entry_name, built) in entries {
|
||||||
|
matcher.add(entry_name, built);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let results = matcher.get_results();
|
||||||
|
|
||||||
|
if !rest.is_empty() || isdir {
|
||||||
|
results
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|built| complete_rec(rest, &[built], options.clone(), want_dir, isdir))
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
results
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We could directly return the entries, but then they wouldn't be sorted
|
||||||
|
let mut matcher = NuMatcher::new("", options.clone());
|
||||||
|
for (entry_name, built) in entries {
|
||||||
|
matcher.add(entry_name, built);
|
||||||
|
}
|
||||||
|
matcher.get_results()
|
||||||
}
|
}
|
||||||
completions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -93,7 +114,7 @@ enum OriginalCwd {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OriginalCwd {
|
impl OriginalCwd {
|
||||||
fn apply(&self, mut p: PathBuiltFromString) -> String {
|
fn apply(&self, p: &mut PathBuiltFromString) -> String {
|
||||||
match self {
|
match self {
|
||||||
Self::None => {}
|
Self::None => {}
|
||||||
Self::Home => p.parts.insert(0, "~".to_string()),
|
Self::Home => p.parts.insert(0, "~".to_string()),
|
||||||
|
@ -122,18 +143,22 @@ fn surround_remove(partial: &str) -> String {
|
||||||
partial.to_string()
|
partial.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Looks inside a set of directories (given by `cwds`) to find files matching
|
||||||
|
/// `partial` (text the user typed in)
|
||||||
|
///
|
||||||
|
/// Returns (span, cwd, path suggestion, style)
|
||||||
pub fn complete_item(
|
pub fn complete_item(
|
||||||
want_directory: bool,
|
want_directory: bool,
|
||||||
span: nu_protocol::Span,
|
span: nu_protocol::Span,
|
||||||
partial: &str,
|
partial: &str,
|
||||||
cwd: &str,
|
cwds: &[impl AsRef<str>],
|
||||||
options: &CompletionOptions,
|
options: MatcherOptions,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
) -> Vec<(nu_protocol::Span, String, Option<Style>)> {
|
) -> Vec<(nu_protocol::Span, PathBuf, String, Option<Style>)> {
|
||||||
let partial = surround_remove(partial);
|
let partial = surround_remove(partial);
|
||||||
let isdir = partial.ends_with(is_separator);
|
let isdir = partial.ends_with(is_separator);
|
||||||
let cwd_pathbuf = Path::new(cwd).to_path_buf();
|
let cwd_pathbufs: Vec<_> = cwds.iter().map(|cwd| PathBuf::from(cwd.as_ref())).collect();
|
||||||
let ls_colors = (engine_state.config.use_ls_colors_completions
|
let ls_colors = (engine_state.config.use_ls_colors_completions
|
||||||
&& engine_state.config.use_ansi_coloring)
|
&& engine_state.config.use_ansi_coloring)
|
||||||
.then(|| {
|
.then(|| {
|
||||||
|
@ -144,7 +169,7 @@ pub fn complete_item(
|
||||||
get_ls_colors(ls_colors_env_str)
|
get_ls_colors(ls_colors_env_str)
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut cwd = cwd_pathbuf.clone();
|
let mut cwds = cwd_pathbufs.clone();
|
||||||
let mut prefix_len = 0;
|
let mut prefix_len = 0;
|
||||||
let mut original_cwd = OriginalCwd::None;
|
let mut original_cwd = OriginalCwd::None;
|
||||||
|
|
||||||
|
@ -156,7 +181,7 @@ pub fn complete_item(
|
||||||
if let Some(Component::RootDir) = components.peek().cloned() {
|
if let Some(Component::RootDir) = components.peek().cloned() {
|
||||||
components.next();
|
components.next();
|
||||||
};
|
};
|
||||||
cwd = [c, Component::RootDir].iter().collect();
|
cwds = vec![[c, Component::RootDir].iter().collect()];
|
||||||
prefix_len = c.as_os_str().len();
|
prefix_len = c.as_os_str().len();
|
||||||
original_cwd = OriginalCwd::Prefix(c.as_os_str().to_string_lossy().into_owned());
|
original_cwd = OriginalCwd::Prefix(c.as_os_str().to_string_lossy().into_owned());
|
||||||
}
|
}
|
||||||
|
@ -164,13 +189,13 @@ pub fn complete_item(
|
||||||
components.next();
|
components.next();
|
||||||
// This is kind of a hack. When joining an empty string with the rest,
|
// This is kind of a hack. When joining an empty string with the rest,
|
||||||
// we add the slash automagically
|
// we add the slash automagically
|
||||||
cwd = PathBuf::from(c.as_os_str());
|
cwds = vec![PathBuf::from(c.as_os_str())];
|
||||||
prefix_len = 1;
|
prefix_len = 1;
|
||||||
original_cwd = OriginalCwd::Prefix(String::new());
|
original_cwd = OriginalCwd::Prefix(String::new());
|
||||||
}
|
}
|
||||||
Some(Component::Normal(home)) if home.to_string_lossy() == "~" => {
|
Some(Component::Normal(home)) if home.to_string_lossy() == "~" => {
|
||||||
components.next();
|
components.next();
|
||||||
cwd = home_dir().unwrap_or(cwd_pathbuf);
|
cwds = home_dir().map(|dir| vec![dir]).unwrap_or(cwd_pathbufs);
|
||||||
prefix_len = 1;
|
prefix_len = 1;
|
||||||
original_cwd = OriginalCwd::Home;
|
original_cwd = OriginalCwd::Home;
|
||||||
}
|
}
|
||||||
|
@ -187,15 +212,21 @@ pub fn complete_item(
|
||||||
|
|
||||||
complete_rec(
|
complete_rec(
|
||||||
partial.as_slice(),
|
partial.as_slice(),
|
||||||
&PathBuiltFromString::default(),
|
&cwds
|
||||||
&cwd,
|
.into_iter()
|
||||||
|
.map(|cwd| PathBuiltFromString {
|
||||||
|
cwd,
|
||||||
|
parts: Vec::new(),
|
||||||
|
isdir: false,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
options,
|
options,
|
||||||
want_directory,
|
want_directory,
|
||||||
isdir,
|
isdir,
|
||||||
)
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|p| {
|
.map(|mut p| {
|
||||||
let path = original_cwd.apply(p);
|
let path = original_cwd.apply(&mut p);
|
||||||
let style = ls_colors.as_ref().map(|lsc| {
|
let style = ls_colors.as_ref().map(|lsc| {
|
||||||
lsc.style_for_path_with_metadata(
|
lsc.style_for_path_with_metadata(
|
||||||
&path,
|
&path,
|
||||||
|
@ -206,7 +237,7 @@ pub fn complete_item(
|
||||||
.map(lscolors::Style::to_nu_ansi_term_style)
|
.map(lscolors::Style::to_nu_ansi_term_style)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
});
|
});
|
||||||
(span, escape_path(path, want_directory), style)
|
(span, p.cwd, escape_path(path, want_directory), style)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
|
||||||
use nu_parser::trim_quotes_str;
|
use nu_parser::trim_quotes_str;
|
||||||
use nu_protocol::CompletionAlgorithm;
|
use nu_protocol::{levenshtein_distance, CompletionAlgorithm};
|
||||||
use std::fmt::Display;
|
use nu_utils::IgnoreCaseExt;
|
||||||
|
use nucleo_matcher::{
|
||||||
|
pattern::{AtomKind, CaseMatching, Normalization, Pattern},
|
||||||
|
Config, Matcher, Utf32Str,
|
||||||
|
};
|
||||||
|
use std::{borrow::Cow, cmp::Ordering, fmt::Display, path::MAIN_SEPARATOR};
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum SortBy {
|
pub enum SortBy {
|
||||||
|
@ -26,33 +30,230 @@ pub enum MatchAlgorithm {
|
||||||
Fuzzy,
|
Fuzzy,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MatchAlgorithm {
|
pub struct NuMatcher<T> {
|
||||||
/// Returns whether the `needle` search text matches the given `haystack`.
|
options: MatcherOptions,
|
||||||
pub fn matches_str(&self, haystack: &str, needle: &str) -> bool {
|
needle: String,
|
||||||
let haystack = trim_quotes_str(haystack);
|
state: State<T>,
|
||||||
let needle = trim_quotes_str(needle);
|
}
|
||||||
match *self {
|
|
||||||
MatchAlgorithm::Prefix => haystack.starts_with(needle),
|
enum State<T> {
|
||||||
|
Prefix {
|
||||||
|
/// Holds (haystack, item)
|
||||||
|
items: Vec<(String, T)>,
|
||||||
|
},
|
||||||
|
Nucleo {
|
||||||
|
matcher: Matcher,
|
||||||
|
pat: Pattern,
|
||||||
|
/// Holds (score, haystack, item, indices of matches)
|
||||||
|
items: Vec<(u32, String, T, Vec<usize>)>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct MatcherOptions {
|
||||||
|
pub match_algorithm: MatchAlgorithm,
|
||||||
|
pub case_sensitive: bool,
|
||||||
|
/// How to sort results. [`SortBy::None`] by default.
|
||||||
|
pub sort_by: SortBy,
|
||||||
|
/// When fuzzy matching, this will configure Nucleo to reward file paths.
|
||||||
|
/// When sorting alphabetically, this will disregard trailing slashes.
|
||||||
|
/// False by default.
|
||||||
|
pub match_paths: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> NuMatcher<T> {
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `needle` - The text to search for
|
||||||
|
pub fn new(needle: impl AsRef<str>, options: MatcherOptions) -> NuMatcher<T> {
|
||||||
|
let orig_needle = trim_quotes_str(needle.as_ref());
|
||||||
|
let lowercase_needle = if options.case_sensitive {
|
||||||
|
orig_needle.to_owned()
|
||||||
|
} else {
|
||||||
|
orig_needle.to_folded_case()
|
||||||
|
};
|
||||||
|
match options.match_algorithm {
|
||||||
|
MatchAlgorithm::Prefix => NuMatcher {
|
||||||
|
options,
|
||||||
|
needle: lowercase_needle,
|
||||||
|
state: State::Prefix { items: Vec::new() },
|
||||||
|
},
|
||||||
MatchAlgorithm::Fuzzy => {
|
MatchAlgorithm::Fuzzy => {
|
||||||
let matcher = SkimMatcherV2::default();
|
let matcher = Matcher::new(if options.match_paths {
|
||||||
matcher.fuzzy_match(haystack, needle).is_some()
|
Config::DEFAULT.match_paths()
|
||||||
|
} else {
|
||||||
|
Config::DEFAULT
|
||||||
|
});
|
||||||
|
let pat = Pattern::new(
|
||||||
|
// Use the original needle even if case sensitive, because Nucleo handles that
|
||||||
|
orig_needle,
|
||||||
|
if options.case_sensitive {
|
||||||
|
CaseMatching::Respect
|
||||||
|
} else {
|
||||||
|
CaseMatching::Ignore
|
||||||
|
},
|
||||||
|
Normalization::Smart,
|
||||||
|
AtomKind::Fuzzy,
|
||||||
|
);
|
||||||
|
NuMatcher {
|
||||||
|
options,
|
||||||
|
// Use lowercase needle here for Levenshtein distance comparison
|
||||||
|
needle: lowercase_needle,
|
||||||
|
state: State::Nucleo {
|
||||||
|
matcher,
|
||||||
|
pat,
|
||||||
|
items: Vec::new(),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the `needle` search text matches the given `haystack`.
|
/// Add the given item if the given haystack matches.
|
||||||
pub fn matches_u8(&self, haystack: &[u8], needle: &[u8]) -> bool {
|
///
|
||||||
match *self {
|
/// Returns whether the item was added.
|
||||||
MatchAlgorithm::Prefix => haystack.starts_with(needle),
|
pub fn add(&mut self, haystack: impl AsRef<str>, item: T) -> bool {
|
||||||
MatchAlgorithm::Fuzzy => {
|
let haystack = haystack.as_ref();
|
||||||
let haystack_str = String::from_utf8_lossy(haystack);
|
|
||||||
let needle_str = String::from_utf8_lossy(needle);
|
|
||||||
|
|
||||||
let matcher = SkimMatcherV2::default();
|
match &mut self.state {
|
||||||
matcher.fuzzy_match(&haystack_str, &needle_str).is_some()
|
State::Prefix { items } => {
|
||||||
|
let haystack = trim_quotes_str(haystack).to_owned();
|
||||||
|
let haystack_lowercased = if self.options.case_sensitive {
|
||||||
|
Cow::Borrowed(&haystack)
|
||||||
|
} else {
|
||||||
|
Cow::Owned(haystack.to_folded_case())
|
||||||
|
};
|
||||||
|
if haystack_lowercased.starts_with(self.needle.as_str()) {
|
||||||
|
match self.options.sort_by {
|
||||||
|
SortBy::None => items.push((haystack, item)),
|
||||||
|
_ => {
|
||||||
|
let ind = match items.binary_search_by(|(other, _)| {
|
||||||
|
cmp(
|
||||||
|
&self.needle,
|
||||||
|
&self.options,
|
||||||
|
other.as_str(),
|
||||||
|
haystack.as_str(),
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(i) => i,
|
||||||
|
};
|
||||||
|
items.insert(ind, (haystack, item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
State::Nucleo {
|
||||||
|
matcher,
|
||||||
|
pat,
|
||||||
|
items,
|
||||||
|
} => {
|
||||||
|
let mut haystack_buf = Vec::new();
|
||||||
|
let haystack_utf32 = Utf32Str::new(trim_quotes_str(haystack), &mut haystack_buf);
|
||||||
|
// todo find out why nucleo uses u32 instead of usize for indices
|
||||||
|
let mut indices = Vec::new();
|
||||||
|
match pat.indices(haystack_utf32, matcher, &mut indices) {
|
||||||
|
Some(score) => {
|
||||||
|
indices.sort_unstable();
|
||||||
|
indices.dedup();
|
||||||
|
|
||||||
|
let match_record = (
|
||||||
|
score,
|
||||||
|
haystack.to_string(),
|
||||||
|
item,
|
||||||
|
indices.into_iter().map(|i| i as usize).collect(),
|
||||||
|
);
|
||||||
|
let ind =
|
||||||
|
match items.binary_search_by(|(other_score, other_haystack, _, _)| {
|
||||||
|
match self.options.sort_by {
|
||||||
|
SortBy::None => {
|
||||||
|
// Use alphabetical order if same score
|
||||||
|
score
|
||||||
|
.cmp(other_score)
|
||||||
|
.then(other_haystack.as_str().cmp(haystack))
|
||||||
|
}
|
||||||
|
_ => cmp(
|
||||||
|
&self.needle,
|
||||||
|
&self.options,
|
||||||
|
other_haystack.as_str(),
|
||||||
|
haystack,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(i) => i,
|
||||||
|
};
|
||||||
|
items.insert(ind, match_record);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get all the items that matched
|
||||||
|
pub fn get_results(self) -> Vec<T> {
|
||||||
|
let (results, _): (Vec<_>, Vec<_>) = self.get_results_with_inds().into_iter().unzip();
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all the items that matched, along with the indices in their haystacks that matched
|
||||||
|
pub fn get_results_with_inds(self) -> Vec<(T, Vec<usize>)> {
|
||||||
|
match self.state {
|
||||||
|
State::Prefix { items, .. } => items
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_, item)| (item, (0..self.needle.len()).collect()))
|
||||||
|
.collect(),
|
||||||
|
State::Nucleo { items, .. } => items
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_, _, items, indices)| (items, indices))
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmp(needle: &str, options: &MatcherOptions, a: &str, b: &str) -> Ordering {
|
||||||
|
let alpha_ordering = if options.match_paths {
|
||||||
|
a.trim_end_matches(MAIN_SEPARATOR)
|
||||||
|
.cmp(b.trim_end_matches(MAIN_SEPARATOR))
|
||||||
|
} else {
|
||||||
|
a.cmp(b)
|
||||||
|
};
|
||||||
|
match options.sort_by {
|
||||||
|
SortBy::LevenshteinDistance => {
|
||||||
|
let a_distance = levenshtein_distance(needle, a);
|
||||||
|
let b_distance = levenshtein_distance(needle, b);
|
||||||
|
a_distance.cmp(&b_distance).then(alpha_ordering)
|
||||||
|
}
|
||||||
|
SortBy::Ascending => alpha_ordering,
|
||||||
|
SortBy::None => Ordering::Less,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MatcherOptions {
|
||||||
|
pub fn new(completion_options: &CompletionOptions) -> Self {
|
||||||
|
MatcherOptions {
|
||||||
|
match_algorithm: completion_options.match_algorithm,
|
||||||
|
case_sensitive: completion_options.case_sensitive,
|
||||||
|
sort_by: SortBy::None,
|
||||||
|
match_paths: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sort_by(mut self, sort_by: SortBy) -> Self {
|
||||||
|
self.sort_by = sort_by;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn match_paths(mut self, match_paths: bool) -> Self {
|
||||||
|
self.match_paths = match_paths;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<CompletionAlgorithm> for MatchAlgorithm {
|
impl From<CompletionAlgorithm> for MatchAlgorithm {
|
||||||
|
@ -110,35 +311,54 @@ impl Default for CompletionOptions {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::MatchAlgorithm;
|
use rstest::rstest;
|
||||||
|
|
||||||
#[test]
|
use super::{CompletionOptions, MatchAlgorithm, MatcherOptions, NuMatcher};
|
||||||
fn match_algorithm_prefix() {
|
|
||||||
let algorithm = MatchAlgorithm::Prefix;
|
|
||||||
|
|
||||||
assert!(algorithm.matches_str("example text", ""));
|
#[rstest]
|
||||||
assert!(algorithm.matches_str("example text", "examp"));
|
#[case(MatchAlgorithm::Prefix, "example text", "", true)]
|
||||||
assert!(!algorithm.matches_str("example text", "text"));
|
#[case(MatchAlgorithm::Prefix, "example text", "examp", true)]
|
||||||
|
#[case(MatchAlgorithm::Prefix, "example text", "text", false)]
|
||||||
|
#[case(MatchAlgorithm::Fuzzy, "example text", "", true)]
|
||||||
|
#[case(MatchAlgorithm::Fuzzy, "example text", "examp", true)]
|
||||||
|
#[case(MatchAlgorithm::Fuzzy, "example text", "ext", true)]
|
||||||
|
#[case(MatchAlgorithm::Fuzzy, "example text", "mplxt", true)]
|
||||||
|
#[case(MatchAlgorithm::Fuzzy, "example text", "mpp", false)]
|
||||||
|
fn match_algorithm_simple(
|
||||||
|
#[case] match_algorithm: MatchAlgorithm,
|
||||||
|
#[case] haystack: &str,
|
||||||
|
#[case] needle: &str,
|
||||||
|
#[case] should_match: bool,
|
||||||
|
) {
|
||||||
|
let options = MatcherOptions::new(&CompletionOptions {
|
||||||
|
match_algorithm,
|
||||||
|
case_sensitive: true,
|
||||||
|
positional: false,
|
||||||
|
});
|
||||||
|
|
||||||
assert!(algorithm.matches_u8(&[1, 2, 3], &[]));
|
let mut matcher = NuMatcher::new(needle, options);
|
||||||
assert!(algorithm.matches_u8(&[1, 2, 3], &[1, 2]));
|
matcher.add(haystack, haystack);
|
||||||
assert!(!algorithm.matches_u8(&[1, 2, 3], &[2, 3]));
|
if should_match {
|
||||||
|
assert_eq!(vec![haystack], matcher.get_results());
|
||||||
|
} else {
|
||||||
|
assert_ne!(vec![haystack], matcher.get_results());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn match_algorithm_fuzzy() {
|
fn match_algorithm_fuzzy_sort_score() {
|
||||||
let algorithm = MatchAlgorithm::Fuzzy;
|
let options = MatcherOptions::new(&CompletionOptions {
|
||||||
|
match_algorithm: MatchAlgorithm::Fuzzy,
|
||||||
|
case_sensitive: true,
|
||||||
|
positional: false,
|
||||||
|
});
|
||||||
|
|
||||||
assert!(algorithm.matches_str("example text", ""));
|
// Taken from the nucleo-matcher crate's examples
|
||||||
assert!(algorithm.matches_str("example text", "examp"));
|
// todo more thorough tests
|
||||||
assert!(algorithm.matches_str("example text", "ext"));
|
let mut matcher = NuMatcher::new("foo bar", options);
|
||||||
assert!(algorithm.matches_str("example text", "mplxt"));
|
matcher.add("foo/bar", "foo/bar");
|
||||||
assert!(!algorithm.matches_str("example text", "mpp"));
|
matcher.add("bar/foo", "bar/foo");
|
||||||
|
matcher.add("foobar", "foobar");
|
||||||
assert!(algorithm.matches_u8(&[1, 2, 3], &[]));
|
assert_eq!(vec!["bar/foo", "foo/bar", "foobar"], matcher.get_results());
|
||||||
assert!(algorithm.matches_u8(&[1, 2, 3], &[1, 2]));
|
|
||||||
assert!(algorithm.matches_u8(&[1, 2, 3], &[2, 3]));
|
|
||||||
assert!(algorithm.matches_u8(&[1, 2, 3], &[1, 3]));
|
|
||||||
assert!(!algorithm.matches_u8(&[1, 2, 3], &[2, 2]));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,9 @@ use nu_protocol::{
|
||||||
engine::{Stack, StateWorkingSet},
|
engine::{Stack, StateWorkingSet},
|
||||||
PipelineData, Span, Type, Value,
|
PipelineData, Span, Type, Value,
|
||||||
};
|
};
|
||||||
use nu_utils::IgnoreCaseExt;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use super::completion_common::sort_suggestions;
|
use super::completion_options::{MatcherOptions, NuMatcher};
|
||||||
|
|
||||||
pub struct CustomCompletion {
|
pub struct CustomCompletion {
|
||||||
stack: Stack,
|
stack: Stack,
|
||||||
|
@ -124,42 +123,30 @@ impl Completer for CustomCompletion {
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let suggestions = if let Some(custom_completion_options) = custom_completion_options {
|
filter(
|
||||||
filter(&prefix, suggestions, &custom_completion_options)
|
&prefix,
|
||||||
} else {
|
suggestions,
|
||||||
filter(&prefix, suggestions, completion_options)
|
MatcherOptions::new(
|
||||||
};
|
custom_completion_options
|
||||||
sort_suggestions(&String::from_utf8_lossy(&prefix), suggestions, self.sort_by)
|
.as_ref()
|
||||||
|
.unwrap_or(completion_options),
|
||||||
|
)
|
||||||
|
.sort_by(self.sort_by),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn filter(
|
fn filter(
|
||||||
prefix: &[u8],
|
prefix: &[u8],
|
||||||
items: Vec<SemanticSuggestion>,
|
items: Vec<SemanticSuggestion>,
|
||||||
options: &CompletionOptions,
|
options: MatcherOptions,
|
||||||
) -> Vec<SemanticSuggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
items
|
let prefix = String::from_utf8_lossy(prefix);
|
||||||
.into_iter()
|
let mut matcher = NuMatcher::new(prefix, options);
|
||||||
.filter(|it| match options.match_algorithm {
|
|
||||||
MatchAlgorithm::Prefix => match (options.case_sensitive, options.positional) {
|
for it in items {
|
||||||
(true, true) => it.suggestion.value.as_bytes().starts_with(prefix),
|
matcher.add(it.suggestion.value.clone(), it);
|
||||||
(true, false) => it
|
}
|
||||||
.suggestion
|
|
||||||
.value
|
matcher.get_results()
|
||||||
.contains(std::str::from_utf8(prefix).unwrap_or("")),
|
|
||||||
(false, positional) => {
|
|
||||||
let value = it.suggestion.value.to_folded_case();
|
|
||||||
let prefix = std::str::from_utf8(prefix).unwrap_or("").to_folded_case();
|
|
||||||
if positional {
|
|
||||||
value.starts_with(&prefix)
|
|
||||||
} else {
|
|
||||||
value.contains(&prefix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
MatchAlgorithm::Fuzzy => options
|
|
||||||
.match_algorithm
|
|
||||||
.matches_u8(it.suggestion.value.as_bytes(), prefix),
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,9 @@ use nu_protocol::{
|
||||||
Span,
|
Span,
|
||||||
};
|
};
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use super::SemanticSuggestion;
|
use super::{completion_options::MatcherOptions, SemanticSuggestion};
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct DirectoryCompletion {}
|
pub struct DirectoryCompletion {}
|
||||||
|
@ -39,21 +39,23 @@ impl Completer for DirectoryCompletion {
|
||||||
let items: Vec<_> = directory_completion(
|
let items: Vec<_> = directory_completion(
|
||||||
span,
|
span,
|
||||||
&prefix,
|
&prefix,
|
||||||
&working_set.permanent_state.current_work_dir(),
|
&[&working_set.permanent_state.current_work_dir()],
|
||||||
options,
|
MatcherOptions::new(options)
|
||||||
|
.sort_by(self.get_sort_by())
|
||||||
|
.match_paths(true),
|
||||||
working_set.permanent_state,
|
working_set.permanent_state,
|
||||||
stack,
|
stack,
|
||||||
)
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |x| SemanticSuggestion {
|
.map(move |(span, _, path, style)| SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: x.1,
|
value: path,
|
||||||
description: None,
|
description: None,
|
||||||
style: x.2,
|
style,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: x.0.start - offset,
|
start: span.start - offset,
|
||||||
end: x.0.end - offset,
|
end: span.end - offset,
|
||||||
},
|
},
|
||||||
append_whitespace: false,
|
append_whitespace: false,
|
||||||
},
|
},
|
||||||
|
@ -90,10 +92,10 @@ impl Completer for DirectoryCompletion {
|
||||||
pub fn directory_completion(
|
pub fn directory_completion(
|
||||||
span: nu_protocol::Span,
|
span: nu_protocol::Span,
|
||||||
partial: &str,
|
partial: &str,
|
||||||
cwd: &str,
|
cwds: &[&str],
|
||||||
options: &CompletionOptions,
|
options: MatcherOptions,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
) -> Vec<(nu_protocol::Span, String, Option<Style>)> {
|
) -> Vec<(nu_protocol::Span, PathBuf, String, Option<Style>)> {
|
||||||
complete_item(true, span, partial, cwd, options, engine_state, stack)
|
complete_item(true, span, partial, cwds, options, engine_state, stack)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use nu_protocol::{
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::path::{is_separator, Path, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR};
|
use std::path::{is_separator, Path, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR};
|
||||||
|
|
||||||
use super::{completion_common::sort_suggestions, SemanticSuggestion, SortBy};
|
use super::{completion_options::MatcherOptions, SemanticSuggestion};
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct DotNuCompletion {}
|
pub struct DotNuCompletion {}
|
||||||
|
@ -87,50 +87,44 @@ impl Completer for DotNuCompletion {
|
||||||
|
|
||||||
// Fetch the files filtering the ones that ends with .nu
|
// Fetch the files filtering the ones that ends with .nu
|
||||||
// and transform them into suggestions
|
// and transform them into suggestions
|
||||||
let output: Vec<SemanticSuggestion> = search_dirs
|
let completions = file_path_completion(
|
||||||
|
span,
|
||||||
|
&partial,
|
||||||
|
&search_dirs,
|
||||||
|
MatcherOptions::new(options).sort_by(SortBy::LevenshteinDistance),
|
||||||
|
working_set.permanent_state,
|
||||||
|
stack,
|
||||||
|
);
|
||||||
|
completions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|search_dir| {
|
.filter(move |(_, search_dir, path, _)| {
|
||||||
let completions = file_path_completion(
|
// Different base dir, so we list the .nu files or folders
|
||||||
span,
|
if !is_current_folder {
|
||||||
&partial,
|
path.ends_with(".nu") || path.ends_with(SEP)
|
||||||
&search_dir,
|
} else {
|
||||||
options,
|
// Lib dirs, so we filter only the .nu files or directory modules
|
||||||
working_set.permanent_state,
|
if path.ends_with(SEP) {
|
||||||
stack,
|
Path::new(&search_dir).join(path).join("mod.nu").exists()
|
||||||
);
|
} else {
|
||||||
completions
|
path.ends_with(".nu")
|
||||||
.into_iter()
|
}
|
||||||
.filter(move |it| {
|
}
|
||||||
// Different base dir, so we list the .nu files or folders
|
|
||||||
if !is_current_folder {
|
|
||||||
it.1.ends_with(".nu") || it.1.ends_with(SEP)
|
|
||||||
} else {
|
|
||||||
// Lib dirs, so we filter only the .nu files or directory modules
|
|
||||||
if it.1.ends_with(SEP) {
|
|
||||||
Path::new(&search_dir).join(&it.1).join("mod.nu").exists()
|
|
||||||
} else {
|
|
||||||
it.1.ends_with(".nu")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map(move |x| SemanticSuggestion {
|
|
||||||
suggestion: Suggestion {
|
|
||||||
value: x.1,
|
|
||||||
description: None,
|
|
||||||
style: x.2,
|
|
||||||
extra: None,
|
|
||||||
span: reedline::Span {
|
|
||||||
start: x.0.start - offset,
|
|
||||||
end: x.0.end - offset,
|
|
||||||
},
|
|
||||||
append_whitespace: true,
|
|
||||||
},
|
|
||||||
// TODO????
|
|
||||||
kind: None,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.collect();
|
.map(move |(span, _, path, style)| SemanticSuggestion {
|
||||||
|
suggestion: Suggestion {
|
||||||
sort_suggestions(&prefix_str, output, SortBy::Ascending)
|
value: path,
|
||||||
|
description: None,
|
||||||
|
style,
|
||||||
|
extra: None,
|
||||||
|
span: reedline::Span {
|
||||||
|
start: span.start - offset,
|
||||||
|
end: span.end - offset,
|
||||||
|
},
|
||||||
|
append_whitespace: true,
|
||||||
|
},
|
||||||
|
// TODO????
|
||||||
|
kind: None,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,10 @@ use nu_protocol::{
|
||||||
engine::{EngineState, Stack, StateWorkingSet},
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
Span,
|
Span,
|
||||||
};
|
};
|
||||||
use nu_utils::IgnoreCaseExt;
|
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use super::SemanticSuggestion;
|
use super::{completion_options::MatcherOptions, SemanticSuggestion};
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct FileCompletion {}
|
pub struct FileCompletion {}
|
||||||
|
@ -44,21 +43,23 @@ impl Completer for FileCompletion {
|
||||||
readjusted,
|
readjusted,
|
||||||
span,
|
span,
|
||||||
&prefix,
|
&prefix,
|
||||||
&working_set.permanent_state.current_work_dir(),
|
&[&working_set.permanent_state.current_work_dir()],
|
||||||
options,
|
MatcherOptions::new(options)
|
||||||
|
.sort_by(self.get_sort_by())
|
||||||
|
.match_paths(true),
|
||||||
working_set.permanent_state,
|
working_set.permanent_state,
|
||||||
stack,
|
stack,
|
||||||
)
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |x| SemanticSuggestion {
|
.map(move |(span, _, path, style)| SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: x.1,
|
value: path,
|
||||||
description: None,
|
description: None,
|
||||||
style: x.2,
|
style,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: reedline::Span {
|
span: reedline::Span {
|
||||||
start: x.0.start - offset,
|
start: span.start - offset,
|
||||||
end: x.0.end - offset,
|
end: span.end - offset,
|
||||||
},
|
},
|
||||||
append_whitespace: false,
|
append_whitespace: false,
|
||||||
},
|
},
|
||||||
|
@ -97,21 +98,10 @@ impl Completer for FileCompletion {
|
||||||
pub fn file_path_completion(
|
pub fn file_path_completion(
|
||||||
span: nu_protocol::Span,
|
span: nu_protocol::Span,
|
||||||
partial: &str,
|
partial: &str,
|
||||||
cwd: &str,
|
cwds: &[impl AsRef<str>],
|
||||||
options: &CompletionOptions,
|
options: MatcherOptions,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
) -> Vec<(nu_protocol::Span, String, Option<Style>)> {
|
) -> Vec<(nu_protocol::Span, PathBuf, String, Option<Style>)> {
|
||||||
complete_item(false, span, partial, cwd, options, engine_state, stack)
|
complete_item(false, span, partial, cwds, options, engine_state, stack)
|
||||||
}
|
|
||||||
|
|
||||||
pub fn matches(partial: &str, from: &str, options: &CompletionOptions) -> bool {
|
|
||||||
// Check for case sensitive
|
|
||||||
if !options.case_sensitive {
|
|
||||||
return options
|
|
||||||
.match_algorithm
|
|
||||||
.matches_str(&from.to_folded_case(), &partial.to_folded_case());
|
|
||||||
}
|
|
||||||
|
|
||||||
options.match_algorithm.matches_str(from, partial)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::completions::{
|
use crate::completions::{
|
||||||
completion_common::sort_suggestions, Completer, CompletionOptions, SortBy,
|
completion_options::{MatcherOptions, NuMatcher},
|
||||||
|
Completer, CompletionOptions,
|
||||||
};
|
};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Expr, Expression},
|
ast::{Expr, Expression},
|
||||||
|
@ -37,19 +38,22 @@ impl Completer for FlagCompletion {
|
||||||
let decl = working_set.get_decl(call.decl_id);
|
let decl = working_set.get_decl(call.decl_id);
|
||||||
let sig = decl.signature();
|
let sig = decl.signature();
|
||||||
|
|
||||||
let mut output = vec![];
|
let prefix = String::from_utf8_lossy(&prefix);
|
||||||
|
let mut matcher = NuMatcher::new(
|
||||||
|
prefix,
|
||||||
|
MatcherOptions::new(options).sort_by(self.get_sort_by()),
|
||||||
|
);
|
||||||
|
|
||||||
for named in &sig.named {
|
for named in &sig.named {
|
||||||
let flag_desc = &named.desc;
|
let flag_desc = &named.desc;
|
||||||
if let Some(short) = named.short {
|
if let Some(short) = named.short {
|
||||||
let mut named = vec![0; short.len_utf8()];
|
let named = format!("-{}", short);
|
||||||
short.encode_utf8(&mut named);
|
|
||||||
named.insert(0, b'-');
|
|
||||||
|
|
||||||
if options.match_algorithm.matches_u8(&named, &prefix) {
|
matcher.add(
|
||||||
output.push(SemanticSuggestion {
|
named.clone(),
|
||||||
|
SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: String::from_utf8_lossy(&named).to_string(),
|
value: named,
|
||||||
description: Some(flag_desc.to_string()),
|
description: Some(flag_desc.to_string()),
|
||||||
style: None,
|
style: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
|
@ -61,22 +65,21 @@ impl Completer for FlagCompletion {
|
||||||
},
|
},
|
||||||
// TODO????
|
// TODO????
|
||||||
kind: None,
|
kind: None,
|
||||||
});
|
},
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if named.long.is_empty() {
|
if named.long.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut named = named.long.as_bytes().to_vec();
|
let named = format!("--{}", named.long);
|
||||||
named.insert(0, b'-');
|
|
||||||
named.insert(0, b'-');
|
|
||||||
|
|
||||||
if options.match_algorithm.matches_u8(&named, &prefix) {
|
matcher.add(
|
||||||
output.push(SemanticSuggestion {
|
named.clone(),
|
||||||
|
SemanticSuggestion {
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: String::from_utf8_lossy(&named).to_string(),
|
value: named,
|
||||||
description: Some(flag_desc.to_string()),
|
description: Some(flag_desc.to_string()),
|
||||||
style: None,
|
style: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
|
@ -88,11 +91,11 @@ impl Completer for FlagCompletion {
|
||||||
},
|
},
|
||||||
// TODO????
|
// TODO????
|
||||||
kind: None,
|
kind: None,
|
||||||
});
|
},
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sort_suggestions(&String::from_utf8_lossy(&prefix), output, SortBy::Ascending);
|
return matcher.get_results();
|
||||||
}
|
}
|
||||||
|
|
||||||
vec![]
|
vec![]
|
||||||
|
|
|
@ -17,6 +17,6 @@ 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;
|
||||||
pub use file_completions::{file_path_completion, matches, FileCompletion};
|
pub use file_completions::{file_path_completion, FileCompletion};
|
||||||
pub use flag_completions::FlagCompletion;
|
pub use flag_completions::FlagCompletion;
|
||||||
pub use variable_completions::VariableCompletion;
|
pub use variable_completions::VariableCompletion;
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
use crate::completions::{
|
use crate::completions::{Completer, CompletionOptions, SemanticSuggestion, SuggestionKind};
|
||||||
Completer, CompletionOptions, MatchAlgorithm, SemanticSuggestion, SuggestionKind,
|
|
||||||
};
|
|
||||||
use nu_engine::{column::get_columns, eval_variable};
|
use nu_engine::{column::get_columns, eval_variable};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{Stack, StateWorkingSet},
|
engine::{Stack, StateWorkingSet},
|
||||||
|
@ -9,7 +7,7 @@ use nu_protocol::{
|
||||||
use reedline::Suggestion;
|
use reedline::Suggestion;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
use super::{completion_common::sort_suggestions, SortBy};
|
use super::completion_options::{MatcherOptions, NuMatcher};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct VariableCompletion {
|
pub struct VariableCompletion {
|
||||||
|
@ -33,7 +31,6 @@ impl Completer for VariableCompletion {
|
||||||
_pos: usize,
|
_pos: usize,
|
||||||
options: &CompletionOptions,
|
options: &CompletionOptions,
|
||||||
) -> Vec<SemanticSuggestion> {
|
) -> Vec<SemanticSuggestion> {
|
||||||
let mut output = vec![];
|
|
||||||
let builtins = ["$nu", "$in", "$env"];
|
let builtins = ["$nu", "$in", "$env"];
|
||||||
let var_str = std::str::from_utf8(&self.var_context.0).unwrap_or("");
|
let var_str = std::str::from_utf8(&self.var_context.0).unwrap_or("");
|
||||||
let var_id = working_set.find_variable(&self.var_context.0);
|
let var_id = working_set.find_variable(&self.var_context.0);
|
||||||
|
@ -44,6 +41,12 @@ impl Completer for VariableCompletion {
|
||||||
let sublevels_count = self.var_context.1.len();
|
let sublevels_count = self.var_context.1.len();
|
||||||
let prefix_str = String::from_utf8_lossy(&prefix);
|
let prefix_str = String::from_utf8_lossy(&prefix);
|
||||||
|
|
||||||
|
let prefix = String::from_utf8_lossy(&prefix);
|
||||||
|
let mut matcher = NuMatcher::new(
|
||||||
|
prefix,
|
||||||
|
MatcherOptions::new(options).sort_by(SortBy::Ascending),
|
||||||
|
);
|
||||||
|
|
||||||
// Completions for the given variable
|
// Completions for the given variable
|
||||||
if !var_str.is_empty() {
|
if !var_str.is_empty() {
|
||||||
// Completion for $env.<tab>
|
// Completion for $env.<tab>
|
||||||
|
@ -62,41 +65,30 @@ impl Completer for VariableCompletion {
|
||||||
self.var_context.1.clone().into_iter().skip(1).collect();
|
self.var_context.1.clone().into_iter().skip(1).collect();
|
||||||
|
|
||||||
if let Some(val) = env_vars.get(&target_var_str) {
|
if let Some(val) = env_vars.get(&target_var_str) {
|
||||||
for suggestion in nested_suggestions(val, &nested_levels, current_span) {
|
for it in nested_suggestions(val, &nested_levels, current_span) {
|
||||||
if options.match_algorithm.matches_u8_insensitive(
|
matcher.add(it.suggestion.value.clone(), it);
|
||||||
options.case_sensitive,
|
|
||||||
suggestion.suggestion.value.as_bytes(),
|
|
||||||
&prefix,
|
|
||||||
) {
|
|
||||||
output.push(suggestion);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return matcher.get_results();
|
||||||
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No nesting provided, return all env vars
|
// No nesting provided, return all env vars
|
||||||
for env_var in env_vars {
|
for (var_name, value) in env_vars {
|
||||||
if options.match_algorithm.matches_u8_insensitive(
|
matcher.add(
|
||||||
options.case_sensitive,
|
var_name.clone(),
|
||||||
env_var.0.as_bytes(),
|
SemanticSuggestion {
|
||||||
&prefix,
|
|
||||||
) {
|
|
||||||
output.push(SemanticSuggestion {
|
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: env_var.0,
|
value: var_name,
|
||||||
description: None,
|
description: None,
|
||||||
style: None,
|
style: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
span: current_span,
|
span: current_span,
|
||||||
append_whitespace: false,
|
append_whitespace: false,
|
||||||
},
|
},
|
||||||
kind: Some(SuggestionKind::Type(env_var.1.get_type())),
|
kind: Some(SuggestionKind::Type(value.get_type())),
|
||||||
});
|
},
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
return matcher.get_results();
|
||||||
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,18 +101,10 @@ impl Completer for VariableCompletion {
|
||||||
nu_protocol::NU_VARIABLE_ID,
|
nu_protocol::NU_VARIABLE_ID,
|
||||||
nu_protocol::Span::new(current_span.start, current_span.end),
|
nu_protocol::Span::new(current_span.start, current_span.end),
|
||||||
) {
|
) {
|
||||||
for suggestion in nested_suggestions(&nuval, &self.var_context.1, current_span)
|
for it in nested_suggestions(&nuval, &self.var_context.1, current_span) {
|
||||||
{
|
matcher.add(it.suggestion.value.clone(), it);
|
||||||
if options.match_algorithm.matches_u8_insensitive(
|
|
||||||
options.case_sensitive,
|
|
||||||
suggestion.suggestion.value.as_bytes(),
|
|
||||||
&prefix,
|
|
||||||
) {
|
|
||||||
output.push(suggestion);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return matcher.get_results();
|
||||||
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,30 +115,19 @@ impl Completer for VariableCompletion {
|
||||||
|
|
||||||
// If the value exists and it's of type Record
|
// If the value exists and it's of type Record
|
||||||
if let Ok(value) = var {
|
if let Ok(value) = var {
|
||||||
for suggestion in nested_suggestions(&value, &self.var_context.1, current_span)
|
for it in nested_suggestions(&value, &self.var_context.1, current_span) {
|
||||||
{
|
matcher.add(it.suggestion.value.clone(), it);
|
||||||
if options.match_algorithm.matches_u8_insensitive(
|
|
||||||
options.case_sensitive,
|
|
||||||
suggestion.suggestion.value.as_bytes(),
|
|
||||||
&prefix,
|
|
||||||
) {
|
|
||||||
output.push(suggestion);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return matcher.get_results();
|
||||||
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 options.match_algorithm.matches_u8_insensitive(
|
matcher.add(
|
||||||
options.case_sensitive,
|
builtin,
|
||||||
builtin.as_bytes(),
|
SemanticSuggestion {
|
||||||
&prefix,
|
|
||||||
) {
|
|
||||||
output.push(SemanticSuggestion {
|
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: builtin.to_string(),
|
value: builtin.to_string(),
|
||||||
description: None,
|
description: None,
|
||||||
|
@ -165,8 +138,8 @@ impl Completer for VariableCompletion {
|
||||||
},
|
},
|
||||||
// TODO is there a way to get the VarId to get the type???
|
// TODO is there a way to get the VarId to get the type???
|
||||||
kind: None,
|
kind: None,
|
||||||
});
|
},
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: The following can be refactored (see find_commands_by_predicate() used in
|
// TODO: The following can be refactored (see find_commands_by_predicate() used in
|
||||||
|
@ -175,15 +148,13 @@ impl Completer for VariableCompletion {
|
||||||
// Working set scope vars
|
// Working set scope vars
|
||||||
for scope_frame in working_set.delta.scope.iter().rev() {
|
for scope_frame in working_set.delta.scope.iter().rev() {
|
||||||
for overlay_frame in scope_frame.active_overlays(&mut removed_overlays).rev() {
|
for overlay_frame in scope_frame.active_overlays(&mut removed_overlays).rev() {
|
||||||
for v in &overlay_frame.vars {
|
for (name, id) in &overlay_frame.vars {
|
||||||
if options.match_algorithm.matches_u8_insensitive(
|
let name = String::from_utf8_lossy(name);
|
||||||
options.case_sensitive,
|
matcher.add(
|
||||||
v.0,
|
name.clone(),
|
||||||
&prefix,
|
SemanticSuggestion {
|
||||||
) {
|
|
||||||
output.push(SemanticSuggestion {
|
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: String::from_utf8_lossy(v.0).to_string(),
|
value: name.to_string(),
|
||||||
description: None,
|
description: None,
|
||||||
style: None,
|
style: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
|
@ -191,10 +162,10 @@ impl Completer for VariableCompletion {
|
||||||
append_whitespace: false,
|
append_whitespace: false,
|
||||||
},
|
},
|
||||||
kind: Some(SuggestionKind::Type(
|
kind: Some(SuggestionKind::Type(
|
||||||
working_set.get_variable(*v.1).ty.clone(),
|
working_set.get_variable(*id).ty.clone(),
|
||||||
)),
|
)),
|
||||||
});
|
},
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -206,15 +177,13 @@ impl Completer for VariableCompletion {
|
||||||
.active_overlays(&removed_overlays)
|
.active_overlays(&removed_overlays)
|
||||||
.rev()
|
.rev()
|
||||||
{
|
{
|
||||||
for v in &overlay_frame.vars {
|
for (name, id) in &overlay_frame.vars {
|
||||||
if options.match_algorithm.matches_u8_insensitive(
|
let name = String::from_utf8_lossy(name);
|
||||||
options.case_sensitive,
|
matcher.add(
|
||||||
v.0,
|
name.clone(),
|
||||||
&prefix,
|
SemanticSuggestion {
|
||||||
) {
|
|
||||||
output.push(SemanticSuggestion {
|
|
||||||
suggestion: Suggestion {
|
suggestion: Suggestion {
|
||||||
value: String::from_utf8_lossy(v.0).to_string(),
|
value: name.to_string(),
|
||||||
description: None,
|
description: None,
|
||||||
style: None,
|
style: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
|
@ -222,16 +191,18 @@ impl Completer for VariableCompletion {
|
||||||
append_whitespace: false,
|
append_whitespace: false,
|
||||||
},
|
},
|
||||||
kind: Some(SuggestionKind::Type(
|
kind: Some(SuggestionKind::Type(
|
||||||
working_set.get_variable(*v.1).ty.clone(),
|
working_set.get_variable(*id).ty.clone(),
|
||||||
)),
|
)),
|
||||||
});
|
},
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
output.dedup(); // TODO: Removes only consecutive duplicates, is it intended?
|
let mut matches = matcher.get_results();
|
||||||
|
|
||||||
output
|
matches.dedup(); // TODO: Removes only consecutive duplicates, is it intended?
|
||||||
|
|
||||||
|
matches
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,13 +289,3 @@ fn recursive_value(val: &Value, sublevels: &[Vec<u8>]) -> Result<Value, Span> {
|
||||||
Ok(val.clone())
|
Ok(val.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MatchAlgorithm {
|
|
||||||
pub fn matches_u8_insensitive(&self, sensitive: bool, haystack: &[u8], needle: &[u8]) -> bool {
|
|
||||||
if sensitive {
|
|
||||||
self.matches_u8(haystack, needle)
|
|
||||||
} else {
|
|
||||||
self.matches_u8(&haystack.to_ascii_lowercase(), &needle.to_ascii_lowercase())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -158,6 +158,11 @@ fn variables_customcompletion_subcommands_with_customcompletion_2(
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dotnu_completions() {
|
fn dotnu_completions() {
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected = vec!["custom_completion.nu", "directory_completion\\"];
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected = vec!["custom_completion.nu", "directory_completion/"];
|
||||||
|
|
||||||
// Create a new engine
|
// Create a new engine
|
||||||
let (_, _, engine, stack) = new_dotnu_engine();
|
let (_, _, engine, stack) = new_dotnu_engine();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user