Fix alias completion crash (#5814)

* Solve crash - commit 1

* commit 2 with issue

* Fix corner case

* Unit tests

* Fix windows tests
This commit is contained in:
Kangaxx-0 2022-06-17 05:50:10 -07:00 committed by GitHub
parent 8d7bb9147e
commit 5f0ad1d6ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 90 additions and 18 deletions

View file

@ -68,14 +68,10 @@ impl NuCompleter {
for pipeline in output.pipelines.into_iter() {
for expr in pipeline.expressions {
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
let span_offset: usize = alias_offset.iter().sum();
for (flat_idx, flat) in flattened.iter().enumerate() {
let alias = if alias_offset.is_empty() {
0
} else {
alias_offset[flat_idx]
};
if pos >= flat.0.start - alias && pos < flat.0.end - alias {
if pos + span_offset >= flat.0.start && pos + span_offset < flat.0.end {
// Context variables
let most_left_var =
most_left_variable(flat_idx, &working_set, flattened.clone());
@ -84,18 +80,18 @@ impl NuCompleter {
let new_span = if flat_idx == 0 {
Span {
start: flat.0.start,
end: flat.0.end - 1 - alias,
end: flat.0.end - 1 - span_offset,
}
} else {
Span {
start: flat.0.start - alias,
end: flat.0.end - 1 - alias,
start: flat.0.start - span_offset,
end: flat.0.end - 1 - span_offset,
}
};
// Parses the prefix
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
prefix.remove(pos - (flat.0.start - alias));
prefix.remove(pos - (flat.0.start - span_offset));
// Variables completion
if prefix.starts_with(b"$") || most_left_var.is_some() {
@ -259,7 +255,7 @@ impl ReedlineCompleter for NuCompleter {
}
}
type MatchedAlias<'a> = Vec<(&'a [u8], &'a [u8])>;
type MatchedAlias = Vec<(Vec<u8>, Vec<u8>)>;
// Handler the completion when giving lines contains at least one alias. (e.g: `g checkout`)
// that `g` is an alias of `git`
@ -278,6 +274,13 @@ fn try_find_alias(line: &[u8], working_set: &StateWorkingSet) -> (Vec<u8>, Vec<u
lens -= 1;
}
}
if !line.is_empty() {
let last = line.last().expect("input is empty");
if last == &b' ' {
output.push(b' ');
}
}
} else {
output = line.to_vec();
}
@ -285,7 +288,7 @@ fn try_find_alias(line: &[u8], working_set: &StateWorkingSet) -> (Vec<u8>, Vec<u
(output, alias_offset)
}
fn search_alias<'a>(input: &'a [u8], working_set: &'a StateWorkingSet) -> Option<MatchedAlias<'a>> {
fn search_alias(input: &[u8], working_set: &StateWorkingSet) -> Option<MatchedAlias> {
let mut vec_names = vec![];
let mut vec_alias = vec![];
let mut pos = 0;
@ -293,27 +296,31 @@ fn search_alias<'a>(input: &'a [u8], working_set: &'a StateWorkingSet) -> Option
for (index, character) in input.iter().enumerate() {
if *character == b' ' {
let range = &input[pos..index];
vec_names.push(range);
vec_names.push(range.to_owned());
pos = index + 1;
}
}
// Push the rest to names vector.
if pos < input.len() {
vec_names.push(&input[pos..]);
vec_names.push((&input[pos..]).to_owned());
}
for name in &vec_names {
if let Some(alias_id) = working_set.find_alias(name) {
if let Some(alias_id) = working_set.find_alias(&name[..]) {
let alias_span = working_set.get_alias(alias_id);
let mut span_vec = vec![];
is_alias = true;
for alias in alias_span {
let name = working_set.get_span_contents(*alias);
if !name.is_empty() {
vec_alias.push(name);
span_vec.push(name);
}
}
// Join span of vector together for complex alias, e.g: `f` is an alias for `git remote -v`
let full_aliases = span_vec.join(&[b' '][..]);
vec_alias.push(full_aliases);
} else {
vec_alias.push(name);
vec_alias.push(name.to_owned());
}
}

View file

@ -0,0 +1,65 @@
pub mod support;
use nu_cli::NuCompleter;
use reedline::Completer;
use support::{match_suggestions, new_engine};
#[test]
fn alias_of_command_and_flags() {
let (dir, _, mut engine, mut stack) = new_engine();
// Create an alias
let alias = r#"alias ll = ls -l"#;
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir.clone()).is_ok());
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let suggestions = completer.complete("ll t", 4);
#[cfg(windows)]
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
#[cfg(not(windows))]
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
match_suggestions(expected_paths, suggestions)
}
#[test]
fn alias_of_basic_command() {
let (dir, _, mut engine, mut stack) = new_engine();
// Create an alias
let alias = r#"alias ll = ls "#;
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir.clone()).is_ok());
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let suggestions = completer.complete("ll t", 4);
#[cfg(windows)]
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
#[cfg(not(windows))]
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
match_suggestions(expected_paths, suggestions)
}
#[test]
fn alias_of_another_alias() {
let (dir, _, mut engine, mut stack) = new_engine();
// Create an alias
let alias = r#"alias ll = ls -la"#;
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir.clone()).is_ok());
// Create the second alias
let alias = r#"alias lf = ll -f"#;
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir.clone()).is_ok());
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let suggestions = completer.complete("lf t", 4);
#[cfg(windows)]
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
#[cfg(not(windows))]
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
match_suggestions(expected_paths, suggestions)
}

View file

@ -14,7 +14,7 @@ fn variables_completions() {
def my-command [animal: string@animals] { print $animal }"#;
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
// Instatiate a new completer
// Instantiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for $nu