From 5f0ad1d6ad18d3e066406b4bd752cedaf61de0c1 Mon Sep 17 00:00:00 2001 From: Kangaxx-0 <85712372+Kangaxx-0@users.noreply.github.com> Date: Fri, 17 Jun 2022 05:50:10 -0700 Subject: [PATCH] Fix alias completion crash (#5814) * Solve crash - commit 1 * commit 2 with issue * Fix corner case * Unit tests * Fix windows tests --- crates/nu-cli/src/completions/completer.rs | 41 ++++++++------ crates/nu-cli/tests/alias.rs | 65 ++++++++++++++++++++++ crates/nu-cli/tests/custom_completions.rs | 2 +- 3 files changed, 90 insertions(+), 18 deletions(-) create mode 100644 crates/nu-cli/tests/alias.rs diff --git a/crates/nu-cli/src/completions/completer.rs b/crates/nu-cli/src/completions/completer.rs index f08dccb4e9..5d73cef7c2 100644 --- a/crates/nu-cli/src/completions/completer.rs +++ b/crates/nu-cli/src/completions/completer.rs @@ -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, Vec)>; // 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, Vec (Vec, Vec(input: &'a [u8], working_set: &'a StateWorkingSet) -> Option> { +fn search_alias(input: &[u8], working_set: &StateWorkingSet) -> Option { 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()); } } diff --git a/crates/nu-cli/tests/alias.rs b/crates/nu-cli/tests/alias.rs new file mode 100644 index 0000000000..a112aae32c --- /dev/null +++ b/crates/nu-cli/tests/alias.rs @@ -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 = vec!["test_a\\".to_string(), "test_b\\".to_string()]; + #[cfg(not(windows))] + let expected_paths: Vec = 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 = vec!["test_a\\".to_string(), "test_b\\".to_string()]; + #[cfg(not(windows))] + let expected_paths: Vec = 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 = vec!["test_a\\".to_string(), "test_b\\".to_string()]; + #[cfg(not(windows))] + let expected_paths: Vec = vec!["test_a/".to_string(), "test_b/".to_string()]; + + match_suggestions(expected_paths, suggestions) +} diff --git a/crates/nu-cli/tests/custom_completions.rs b/crates/nu-cli/tests/custom_completions.rs index 1a74e0af47..fb6dc63625 100644 --- a/crates/nu-cli/tests/custom_completions.rs +++ b/crates/nu-cli/tests/custom_completions.rs @@ -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