diff --git a/crates/nu-cli/src/completions/completer.rs b/crates/nu-cli/src/completions/completer.rs index f0cb6f3fda..71125e04e9 100644 --- a/crates/nu-cli/src/completions/completer.rs +++ b/crates/nu-cli/src/completions/completer.rs @@ -2,6 +2,7 @@ use crate::completions::{ CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion, DotNuCompletion, FileCompletion, FlagCompletion, OperatorCompletion, VariableCompletion, }; +use log::debug; use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style}; use nu_engine::eval_block; use nu_parser::{flatten_pipeline_element, parse, FlatShape}; @@ -52,6 +53,11 @@ impl NuCompleter { ..Default::default() }; + debug!( + "process_completion: prefix: {}, new_span: {new_span:?}, offset: {offset}, pos: {pos}", + String::from_utf8_lossy(prefix) + ); + completer.fetch( working_set, &self.stack, @@ -134,9 +140,29 @@ impl NuCompleter { let config = self.engine_state.get_config(); - let output = parse(&mut working_set, Some("completer"), line.as_bytes(), false); + let outermost_block = parse(&mut working_set, Some("completer"), line.as_bytes(), false); - for pipeline in &output.pipelines { + // Try to get the innermost block parsed (by span) so that we consider the correct context/scope. + let target_block = working_set + .delta + .blocks + .iter() + .filter_map(|block| match block.span { + Some(span) if span.contains(pos) => Some((block, span)), + _ => None, + }) + .reduce(|prev, cur| { + // |(block, span), (block, span)| + match cur.1.start.cmp(&prev.1.start) { + core::cmp::Ordering::Greater => cur, + core::cmp::Ordering::Equal if cur.1.end < prev.1.end => cur, + _ => prev, + } + }) + .map(|(block, _)| block) + .unwrap_or(&outermost_block); + + for pipeline in &target_block.pipelines { for pipeline_element in &pipeline.elements { let flattened = flatten_pipeline_element(&working_set, pipeline_element); let mut spans: Vec = vec![]; @@ -146,10 +172,10 @@ impl NuCompleter { .first() .filter(|content| content.as_str() == "sudo" || content.as_str() == "doas") .is_some(); - // Read the current spam to string - let current_span = working_set.get_span_contents(flat.0).to_vec(); - let current_span_str = String::from_utf8_lossy(¤t_span); + // Read the current span to string + let current_span = working_set.get_span_contents(flat.0); + let current_span_str = String::from_utf8_lossy(current_span); let is_last_span = pos >= flat.0.start && pos < flat.0.end; // Skip the last 'a' as span item @@ -176,9 +202,8 @@ impl NuCompleter { let new_span = Span::new(flat.0.start, flat.0.end - 1); // Parses the prefix. Completion should look up to the cursor position, not after. - let mut prefix = working_set.get_span_contents(flat.0); let index = pos - flat.0.start; - prefix = &prefix[..index]; + let prefix = ¤t_span[..index]; // Variables completion if prefix.starts_with(b"$") || most_left_var.is_some() { diff --git a/crates/nu-cli/tests/completions/mod.rs b/crates/nu-cli/tests/completions/mod.rs index b9d9fdb573..fa868a1d11 100644 --- a/crates/nu-cli/tests/completions/mod.rs +++ b/crates/nu-cli/tests/completions/mod.rs @@ -1862,3 +1862,31 @@ fn alias_offset_bug_7754() { // This crashes before PR #7756 let _suggestions = completer.complete("ll -a | c", 9); } + +#[rstest] +fn nested_block(mut completer: NuCompleter) { + let expected: Vec = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()]; + + let suggestions = completer.complete("somecmd | lines | each { tst - }", 30); + match_suggestions(&expected, &suggestions); + + let suggestions = completer.complete("somecmd | lines | each { tst -}", 30); + match_suggestions(&expected, &suggestions); +} + +#[rstest] +fn incomplete_nested_block(mut completer: NuCompleter) { + let suggestions = completer.complete("somecmd | lines | each { tst -", 30); + let expected: Vec = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()]; + match_suggestions(&expected, &suggestions); +} + +#[rstest] +fn deeply_nested_block(mut completer: NuCompleter) { + let suggestions = completer.complete( + "somecmd | lines | each { print ([each (print) (tst -)]) }", + 52, + ); + let expected: Vec = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()]; + match_suggestions(&expected, &suggestions); +}