fix(completion): invalid prefix for external path argument with spaces (#15998)

Fixes the default behavior of #15790 

# Description

As for the mentioned carapace version: `cat ~"/Downloads/Obsidian
Vault/"`, the problem lies in the unexpanded home directory `~`. Either
we encourage users to manually expand that in
`$env.config.completions.external.completer` or open an issue on the
carapace project.

# User-Facing Changes

bug fix

# Tests + Formatting

Adjusted

# After Submitting
This commit is contained in:
zc he
2025-06-21 09:33:01 +08:00
committed by GitHub
parent c3079a14d9
commit 760c9ef2e9
4 changed files with 32 additions and 4 deletions

View File

@ -466,6 +466,14 @@ impl NuCompleter {
return suggestions; return suggestions;
} }
} }
// for external path arguments with spaces, please check issue #15790
if suggestions.is_empty() {
let (new_span, prefix) =
strip_placeholder_if_any(working_set, &span, strip);
let ctx = Context::new(working_set, new_span, prefix, offset);
suggestions.extend(self.process_completion(&mut FileCompletion, &ctx));
return suggestions;
}
break; break;
} }
} }

View File

@ -9,14 +9,16 @@ use std::{
use nu_cli::NuCompleter; use nu_cli::NuCompleter;
use nu_engine::eval_block; use nu_engine::eval_block;
use nu_parser::parse; use nu_parser::parse;
use nu_path::expand_tilde; use nu_path::{AbsolutePathBuf, expand_tilde};
use nu_protocol::{Config, PipelineData, debugger::WithoutDebug, engine::StateWorkingSet}; use nu_protocol::{Config, PipelineData, debugger::WithoutDebug, engine::StateWorkingSet};
use nu_std::load_standard_library; use nu_std::load_standard_library;
use nu_test_support::fs;
use reedline::{Completer, Suggestion}; use reedline::{Completer, Suggestion};
use rstest::{fixture, rstest}; use rstest::{fixture, rstest};
use support::{ use support::{
completions_helpers::{ completions_helpers::{
new_dotnu_engine, new_external_engine, new_partial_engine, new_quote_engine, new_dotnu_engine, new_engine_helper, new_external_engine, new_partial_engine,
new_quote_engine,
}, },
file, folder, match_suggestions, match_suggestions_by_string, new_engine, file, folder, match_suggestions, match_suggestions_by_string, new_engine,
}; };
@ -716,6 +718,16 @@ fn external_completer_fallback() {
let expected = [folder("test_a"), file("test_a_symlink"), folder("test_b")]; let expected = [folder("test_a"), file("test_a_symlink"), folder("test_b")];
let suggestions = run_external_completion(block, input); let suggestions = run_external_completion(block, input);
match_suggestions_by_string(&expected, &suggestions); match_suggestions_by_string(&expected, &suggestions);
// issue #15790
let input = "foo `dir with space/`";
let expected = vec!["`dir with space/bar baz`", "`dir with space/foo`"];
let suggestions = run_external_completion_within_pwd(
block,
input,
fs::fixtures().join("external_completions"),
);
match_suggestions(&expected, &suggestions);
} }
/// Fallback to external completions for flags of `sudo` /// Fallback to external completions for flags of `sudo`
@ -2103,11 +2115,15 @@ fn alias_of_another_alias() {
match_suggestions(&expected_paths, &suggestions) match_suggestions(&expected_paths, &suggestions)
} }
fn run_external_completion(completer: &str, input: &str) -> Vec<Suggestion> { fn run_external_completion_within_pwd(
completer: &str,
input: &str,
pwd: AbsolutePathBuf,
) -> Vec<Suggestion> {
let completer = format!("$env.config.completions.external.completer = {completer}"); let completer = format!("$env.config.completions.external.completer = {completer}");
// Create a new engine // Create a new engine
let (_, _, mut engine_state, mut stack) = new_engine(); let (_, _, mut engine_state, mut stack) = new_engine_helper(pwd);
let (block, delta) = { let (block, delta) = {
let mut working_set = StateWorkingSet::new(&engine_state); let mut working_set = StateWorkingSet::new(&engine_state);
let block = parse(&mut working_set, None, completer.as_bytes(), false); let block = parse(&mut working_set, None, completer.as_bytes(), false);
@ -2131,6 +2147,10 @@ fn run_external_completion(completer: &str, input: &str) -> Vec<Suggestion> {
completer.complete(input, input.len()) completer.complete(input, input.len())
} }
fn run_external_completion(completer: &str, input: &str) -> Vec<Suggestion> {
run_external_completion_within_pwd(completer, input, fs::fixtures().join("completions"))
}
#[test] #[test]
fn unknown_command_completion() { fn unknown_command_completion() {
let (_, _, engine, stack) = new_engine(); let (_, _, engine, stack) = new_engine();

View File