Skip to content

Commit 416fd86

Browse files
committed
fix(completion): file completion for redirection target
1 parent 5c251ea commit 416fd86

File tree

2 files changed

+67
-4
lines changed

2 files changed

+67
-4
lines changed

crates/nu-cli/src/completions/completer.rs

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
88
use nu_parser::{parse, parse_module_file_or_dir};
99
use nu_protocol::{
1010
CommandWideCompleter, Completion, Span, Type, Value,
11-
ast::{Argument, Block, Expr, Expression, FindMapResult, ListItem, Traverse},
11+
ast::{
12+
Argument, Block, Expr, Expression, FindMapResult, ListItem, PipelineRedirection,
13+
RedirectionTarget, Traverse,
14+
},
1215
engine::{EngineState, Stack, StateWorkingSet},
1316
};
1417
use reedline::{Completer as ReedlineCompleter, Suggestion};
@@ -78,6 +81,24 @@ fn find_pipeline_element_by_position<'a>(
7881
}
7982
}
8083

84+
/// For redirection target completion
85+
/// https://github.com/nushell/nushell/issues/16827
86+
fn redirection_target_expression<'a>(
87+
target: &'a RedirectionTarget,
88+
pos: usize,
89+
) -> Option<&'a Expression> {
90+
let expr = target.expr();
91+
expr.and_then(|expression| {
92+
if let Expr::String(_) = expression.expr
93+
&& expression.span.contains(pos)
94+
{
95+
expr
96+
} else {
97+
None
98+
}
99+
})
100+
}
101+
81102
/// Before completion, an additional character `a` is added to the source as a placeholder for correct parsing results.
82103
/// This function helps to strip it
83104
fn strip_placeholder_if_any<'a>(
@@ -218,9 +239,26 @@ impl NuCompleter {
218239
if !extra_placeholder {
219240
pos_to_search = pos_to_search.saturating_sub(1);
220241
}
221-
let Some(element_expression) = block.find_map(working_set, &|expr: &Expression| {
222-
find_pipeline_element_by_position(expr, working_set, pos_to_search)
223-
}) else {
242+
let Some(element_expression) = block
243+
.find_map(working_set, &|expr: &Expression| {
244+
find_pipeline_element_by_position(expr, working_set, pos_to_search)
245+
})
246+
.or_else(|| {
247+
block.pipelines.iter().find_map(|pipeline| {
248+
pipeline.elements.iter().find_map(|element| {
249+
element.redirection.as_ref().and_then(|redir| match redir {
250+
PipelineRedirection::Single { target, .. } => {
251+
redirection_target_expression(target, pos_to_search)
252+
}
253+
PipelineRedirection::Separate { out, err } => {
254+
redirection_target_expression(out, pos_to_search)
255+
.or_else(|| redirection_target_expression(err, pos_to_search))
256+
}
257+
})
258+
})
259+
})
260+
})
261+
else {
224262
return vec![];
225263
};
226264

@@ -259,6 +297,16 @@ impl NuCompleter {
259297
let mut suggestions: Vec<SemanticSuggestion> = vec![];
260298

261299
match &element_expression.expr {
300+
// WARN: Expr::String should only match redirection targets.
301+
// We choose to handle it explicitly here because of a legacy issue: fallback file
302+
// completion doesn't work well with filepath with spaces
303+
// https://github.com/nushell/nushell/issues/16712
304+
Expr::String(_) => {
305+
let span = element_expression.span;
306+
let (new_span, prefix) = strip_placeholder_if_any(working_set, &span, strip);
307+
let ctx = Context::new(working_set, new_span, prefix, offset);
308+
return self.process_completion(&mut FileCompletion, &ctx);
309+
}
262310
Expr::Var(_) => {
263311
return self.variable_names_completion_helper(
264312
working_set,

crates/nu-cli/tests/completions/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2498,6 +2498,21 @@ fn filecompletions_triggers_after_cursor() {
24982498
match_suggestions(&expected_paths, &suggestions);
24992499
}
25002500

2501+
#[test]
2502+
fn filecompletions_for_redirection_target() {
2503+
let (_, _, engine, stack) = new_engine_helper(fs::fixtures().join("external_completions"));
2504+
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
2505+
2506+
let expected = vec!["`dir with space/bar baz`", "`dir with space/foo`"];
2507+
let command = "echo 'foo' o+e> `dir with space/`";
2508+
let suggestions = completer.complete(command, command.len());
2509+
match_suggestions(&expected, &suggestions);
2510+
2511+
let command = "echo 'foo' o> foo e> `dir with space/`";
2512+
let suggestions = completer.complete(command, command.len());
2513+
match_suggestions(&expected, &suggestions);
2514+
}
2515+
25012516
#[rstest]
25022517
fn extern_custom_completion_positional(mut extern_completer: NuCompleter) {
25032518
let suggestions = extern_completer.complete("spam ", 5);

0 commit comments

Comments
 (0)