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:
Richard 2022-04-23 17:01:19 +02:00 committed by GitHub
parent 667eb27d1b
commit e6a70f9846
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 151 additions and 64 deletions

View file

@ -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 {

View file

@ -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 {

View file

@ -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);

View file

@ -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]));
}
}

View file

@ -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()

View file

@ -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) {

View file

@ -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

View file

@ -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())
} }

View file

@ -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()),

View file

@ -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;

View file

@ -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,

View file

@ -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()
} }