From d08e254d169deceda9c55cac75990a0e318fa93a Mon Sep 17 00:00:00 2001 From: sigoden Date: Thu, 30 Nov 2023 06:17:06 +0800 Subject: [PATCH] Fix spans passed to external_completer (#11008) close #10973 --- crates/nu-cli/src/completions/completer.rs | 18 +++++--- crates/nu-cli/tests/completions.rs | 51 ++++++++++++++++++++++ 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/crates/nu-cli/src/completions/completer.rs b/crates/nu-cli/src/completions/completer.rs index 5a15543141..b6a15ff0a2 100644 --- a/crates/nu-cli/src/completions/completer.rs +++ b/crates/nu-cli/src/completions/completer.rs @@ -143,18 +143,24 @@ impl NuCompleter { let current_span = working_set.get_span_contents(flat.0).to_vec(); let current_span_str = String::from_utf8_lossy(¤t_span); + let is_last_span = pos >= flat.0.start && pos < flat.0.end; + // Skip the last 'a' as span item - if flat_idx == flattened.len() - 1 { - let mut chars = current_span_str.chars(); - chars.next_back(); - let current_span_str = chars.as_str().to_owned(); - spans.push(current_span_str.to_string()); + if is_last_span { + let offset = pos - flat.0.start; + if offset == 0 { + spans.push(String::new()) + } else { + let mut current_span_str = current_span_str.to_string(); + current_span_str.remove(offset); + spans.push(current_span_str); + } } else { spans.push(current_span_str.to_string()); } // Complete based on the last span - if pos >= flat.0.start && pos < flat.0.end { + if is_last_span { // Context variables let most_left_var = most_left_variable(flat_idx, &working_set, flattened.clone()); diff --git a/crates/nu-cli/tests/completions.rs b/crates/nu-cli/tests/completions.rs index e6528031fc..e5db82e4ea 100644 --- a/crates/nu-cli/tests/completions.rs +++ b/crates/nu-cli/tests/completions.rs @@ -59,6 +59,29 @@ fn extern_completer() -> NuCompleter { NuCompleter::new(std::sync::Arc::new(engine), stack) } +#[fixture] +fn custom_completer() -> NuCompleter { + // Create a new engine + let (dir, _, mut engine, mut stack) = new_engine(); + + // Add record value as example + let record = r#" + let external_completer = {|spans| + $spans + } + + $env.config.completions.external = { + enable: true + max_results: 100 + completer: $external_completer + } + "#; + assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok()); + + // Instantiate a new completer + NuCompleter::new(std::sync::Arc::new(engine), stack) +} + #[test] fn variables_dollar_sign_with_varialblecompletion() { let (_, _, engine, stack) = new_engine(); @@ -938,6 +961,34 @@ fn extern_complete_flags(mut extern_completer: NuCompleter) { match_suggestions(expected, suggestions); } +#[rstest] +fn custom_completer_triggers_cursor_before_word(mut custom_completer: NuCompleter) { + let suggestions = custom_completer.complete("cmd foo bar", 8); + let expected: Vec = vec!["cmd".into(), "foo".into(), "".into()]; + match_suggestions(expected, suggestions); +} + +#[rstest] +fn custom_completer_triggers_cursor_on_word_left_boundary(mut custom_completer: NuCompleter) { + let suggestions = custom_completer.complete("cmd foo bar", 8); + let expected: Vec = vec!["cmd".into(), "foo".into(), "".into()]; + match_suggestions(expected, suggestions); +} + +#[rstest] +fn custom_completer_triggers_cursor_next_to_word(mut custom_completer: NuCompleter) { + let suggestions = custom_completer.complete("cmd foo bar", 11); + let expected: Vec = vec!["cmd".into(), "foo".into(), "bar".into()]; + match_suggestions(expected, suggestions); +} + +#[rstest] +fn custom_completer_triggers_cursor_after_word(mut custom_completer: NuCompleter) { + let suggestions = custom_completer.complete("cmd foo bar ", 12); + let expected: Vec = vec!["cmd".into(), "foo".into(), "bar".into(), "".into()]; + match_suggestions(expected, suggestions); +} + #[ignore = "was reverted, still needs fixing"] #[rstest] fn alias_offset_bug_7648() {