Skip to content

Commit 7ba8a5a

Browse files
committed
fix(completion): dotnu completion refactor, fixes path with space
1 parent 9ebb02d commit 7ba8a5a

File tree

5 files changed

+83
-158
lines changed

5 files changed

+83
-158
lines changed

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

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,8 @@ impl NuCompleter {
461461
Argument::Positional(expr) => {
462462
let command_head = working_set.get_decl(call.decl_id).name();
463463
positional_arg_indices.push(arg_idx);
464-
self.argument_completion_helper(
464+
let mut need_fallback = suggestions.is_empty();
465+
let results = self.argument_completion_helper(
465466
PositionalArguments {
466467
command_head,
467468
positional_arg_indices,
@@ -470,8 +471,13 @@ impl NuCompleter {
470471
},
471472
pos,
472473
&ctx,
473-
suggestions.is_empty(),
474-
)
474+
&mut need_fallback,
475+
);
476+
// for those arguments that don't need fallback, return early
477+
if !need_fallback && suggestions.is_empty() {
478+
return results;
479+
}
480+
results
475481
}
476482
_ => vec![],
477483
},
@@ -602,7 +608,7 @@ impl NuCompleter {
602608
argument_info: PositionalArguments,
603609
pos: usize,
604610
ctx: &Context,
605-
need_fallback: bool,
611+
need_fallback: &mut bool,
606612
) -> Vec<SemanticSuggestion> {
607613
let PositionalArguments {
608614
command_head,
@@ -614,8 +620,10 @@ impl NuCompleter {
614620
match command_head {
615621
// complete module file/directory
616622
"use" | "export use" | "overlay use" | "source-env"
617-
if positional_arg_indices.len() == 1 =>
623+
if positional_arg_indices.len() <= 1 =>
618624
{
625+
*need_fallback = false;
626+
619627
return self.process_completion(
620628
&mut DotNuCompletion {
621629
std_virtual_path: command_head != "source-env",
@@ -626,6 +634,8 @@ impl NuCompleter {
626634
// NOTE: if module file already specified,
627635
// should parse it to get modules/commands/consts to complete
628636
"use" | "export use" => {
637+
*need_fallback = false;
638+
629639
let Some(Argument::Positional(Expression {
630640
expr: Expr::String(module_name),
631641
span,
@@ -690,6 +700,8 @@ impl NuCompleter {
690700
}
691701
}
692702
"which" => {
703+
*need_fallback = false;
704+
693705
let mut completer = CommandCompletion {
694706
internals: true,
695707
externals: true,
@@ -705,7 +717,7 @@ impl NuCompleter {
705717
Expr::Directory(_, _) => self.process_completion(&mut DirectoryCompletion, ctx),
706718
Expr::Filepath(_, _) | Expr::GlobPattern(_, _) => file_completion_helper(),
707719
// fallback to file completion if necessary
708-
_ if need_fallback => file_completion_helper(),
720+
_ if *need_fallback => file_completion_helper(),
709721
_ => vec![],
710722
}
711723
}

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

Lines changed: 48 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
use crate::completions::{
22
Completer, CompletionOptions, SemanticSuggestion, SuggestionKind,
3-
completion_common::{FileSuggestion, surround_remove},
4-
completion_options::NuMatcher,
5-
file_path_completion,
3+
completion_common::FileSuggestion, completion_options::NuMatcher,
64
};
75
use nu_path::expand_tilde;
86
use nu_protocol::{
97
Span,
108
engine::{Stack, StateWorkingSet, VirtualPath},
119
};
1210
use reedline::Suggestion;
13-
use std::{
14-
collections::HashSet,
15-
path::{MAIN_SEPARATOR_STR, PathBuf, is_separator},
16-
};
11+
use std::collections::HashSet;
12+
13+
use super::completion_common::{complete_item, surround_remove};
1714

1815
pub struct DotNuCompletion {
1916
/// e.g. use std/a<tab>
@@ -30,30 +27,6 @@ impl Completer for DotNuCompletion {
3027
offset: usize,
3128
options: &CompletionOptions,
3229
) -> Vec<SemanticSuggestion> {
33-
let prefix_str = prefix.as_ref();
34-
let start_with_backquote = prefix_str.starts_with('`');
35-
let end_with_backquote = prefix_str.ends_with('`');
36-
let prefix_str = prefix_str.replace('`', "");
37-
// e.g. `./`, `..\`, `/`
38-
let not_lib_dirs = prefix_str
39-
.chars()
40-
.find(|c| *c != '.')
41-
.is_some_and(is_separator);
42-
let mut search_dirs: Vec<PathBuf> = vec![];
43-
44-
let (base, partial) = if let Some((parent, remain)) = prefix_str.rsplit_once(is_separator) {
45-
// If prefix_str is only a word we want to search in the current dir.
46-
// "/xx" should be split to "/" and "xx".
47-
if parent.is_empty() {
48-
(MAIN_SEPARATOR_STR, remain)
49-
} else {
50-
(parent, remain)
51-
}
52-
} else {
53-
(".", prefix_str.as_str())
54-
};
55-
let base_dir = base.replace(is_separator, MAIN_SEPARATOR_STR);
56-
5730
// Fetch the lib dirs
5831
// NOTE: 2 ways to setup `NU_LIB_DIRS`
5932
// 1. `const NU_LIB_DIRS = [paths]`, equal to `nu -I paths`
@@ -62,7 +35,7 @@ impl Completer for DotNuCompletion {
6235
.find_variable(b"$NU_LIB_DIRS")
6336
.and_then(|vid| working_set.get_variable(vid).const_val.as_ref());
6437
let env_lib_dirs = working_set.get_env_var("NU_LIB_DIRS");
65-
let lib_dirs: HashSet<PathBuf> = [const_lib_dirs, env_lib_dirs]
38+
let mut search_dirs = [const_lib_dirs, env_lib_dirs]
6639
.into_iter()
6740
.flatten()
6841
.flat_map(|lib_dirs| {
@@ -72,45 +45,18 @@ impl Completer for DotNuCompletion {
7245
.flat_map(|it| it.iter().filter_map(|x| x.to_path().ok()))
7346
.map(expand_tilde)
7447
})
75-
.collect();
48+
.collect::<HashSet<_>>();
7649

77-
// Check if the base_dir is a folder
78-
let cwd = working_set.permanent_state.cwd(None);
79-
if base_dir != "." {
80-
let expanded_base_dir = expand_tilde(&base_dir);
81-
let is_base_dir_relative = expanded_base_dir.is_relative();
82-
// Search in base_dir as well as lib_dirs.
83-
// After expanded, base_dir can be a relative path or absolute path.
84-
// If relative, we join "current working dir" with it to get subdirectory and add to search_dirs.
85-
// If absolute, we add it to search_dirs.
86-
if let Ok(mut cwd) = cwd {
87-
if is_base_dir_relative {
88-
cwd.push(&base_dir);
89-
search_dirs.push(cwd.into_std_path_buf());
90-
} else {
91-
search_dirs.push(expanded_base_dir);
92-
}
93-
}
94-
if !not_lib_dirs {
95-
search_dirs.extend(lib_dirs.into_iter().map(|mut dir| {
96-
dir.push(&base_dir);
97-
dir
98-
}));
99-
}
100-
} else {
101-
if let Ok(cwd) = cwd {
102-
search_dirs.push(cwd.into_std_path_buf());
103-
}
104-
if !not_lib_dirs {
105-
search_dirs.extend(lib_dirs);
106-
}
50+
if let Ok(cwd) = working_set.permanent_state.cwd(None) {
51+
search_dirs.insert(cwd.into_std_path_buf());
10752
}
10853

10954
// Fetch the files filtering the ones that ends with .nu
11055
// and transform them into suggestions
111-
let mut completions = file_path_completion(
56+
let mut completions = complete_item(
57+
false,
11258
span,
113-
partial,
59+
prefix.as_ref(),
11460
&search_dirs
11561
.iter()
11662
.filter_map(|d| d.to_str())
@@ -121,13 +67,32 @@ impl Completer for DotNuCompletion {
12167
);
12268

12369
if self.std_virtual_path {
124-
let mut matcher = NuMatcher::new(partial, options);
125-
let base_dir = surround_remove(&base_dir);
126-
if base_dir == "." {
127-
let surround_prefix = partial
128-
.chars()
129-
.take_while(|c| "`'\"".contains(*c))
130-
.collect::<String>();
70+
let surround_prefix = prefix
71+
.as_ref()
72+
.chars()
73+
.take_while(|c| "`'\"".contains(*c))
74+
.collect::<String>();
75+
let mut matcher = NuMatcher::new(&prefix, options);
76+
// Where we have '/' in the prefix, e.g. use std/l
77+
if let Some((base_dir, _)) = prefix.as_ref().rsplit_once("/") {
78+
let base_dir = surround_remove(base_dir);
79+
if let Some(VirtualPath::Dir(sub_paths)) = working_set.find_virtual_path(&base_dir)
80+
{
81+
for sub_vp_id in sub_paths {
82+
let (path, sub_vp) = working_set.get_virtual_path(*sub_vp_id);
83+
let path = format!("{surround_prefix}{path}");
84+
matcher.add(
85+
path.clone(),
86+
FileSuggestion {
87+
path,
88+
span,
89+
style: None,
90+
is_dir: matches!(sub_vp, VirtualPath::Dir(_)),
91+
},
92+
);
93+
}
94+
}
95+
} else {
13196
for path in ["std", "std-rfc"] {
13297
let path = format!("{surround_prefix}{path}");
13398
matcher.add(
@@ -140,25 +105,6 @@ impl Completer for DotNuCompletion {
140105
},
141106
);
142107
}
143-
} else if let Some(VirtualPath::Dir(sub_paths)) =
144-
working_set.find_virtual_path(&base_dir)
145-
{
146-
for sub_vp_id in sub_paths {
147-
let (path, sub_vp) = working_set.get_virtual_path(*sub_vp_id);
148-
let path = path
149-
.strip_prefix(&format!("{base_dir}/"))
150-
.unwrap_or(path)
151-
.to_string();
152-
matcher.add(
153-
path.clone(),
154-
FileSuggestion {
155-
path,
156-
span,
157-
style: None,
158-
is_dir: matches!(sub_vp, VirtualPath::Dir(_)),
159-
},
160-
);
161-
}
162108
}
163109
completions.extend(matcher.results());
164110
}
@@ -171,38 +117,18 @@ impl Completer for DotNuCompletion {
171117
let path = it.path.trim_end_matches('`');
172118
path.ends_with(".nu") || it.is_dir
173119
})
174-
.map(|x| {
175-
let append_whitespace = !x.is_dir && (!start_with_backquote || end_with_backquote);
176-
// Re-calculate the span to replace
177-
let mut span_offset = 0;
178-
let mut value = x.path.to_string();
179-
// Complete only the last path component
180-
if base_dir == MAIN_SEPARATOR_STR {
181-
span_offset = base_dir.len()
182-
} else if base_dir != "." {
183-
span_offset = base_dir.len() + 1
184-
}
185-
// Retain only one '`'
186-
if start_with_backquote {
187-
value = value.trim_start_matches('`').to_string();
188-
span_offset += 1;
189-
}
190-
// Add the backquote back
191-
if end_with_backquote && !value.ends_with('`') {
192-
value.push('`');
193-
}
194-
let end = x.span.end - offset;
195-
let start = std::cmp::min(end, x.span.start - offset + span_offset);
196-
SemanticSuggestion {
197-
suggestion: Suggestion {
198-
value,
199-
style: x.style,
200-
span: reedline::Span { start, end },
201-
append_whitespace,
202-
..Suggestion::default()
120+
.map(|x| SemanticSuggestion {
121+
suggestion: Suggestion {
122+
value: x.path.to_string(),
123+
style: x.style,
124+
span: reedline::Span {
125+
start: x.span.start - offset,
126+
end: x.span.end - offset,
203127
},
204-
kind: Some(SuggestionKind::Module),
205-
}
128+
append_whitespace: !x.is_dir,
129+
..Suggestion::default()
130+
},
131+
kind: Some(SuggestionKind::Module),
206132
})
207133
.collect::<Vec<_>>()
208134
}

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

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ use crate::completions::{
44
};
55
use nu_protocol::{
66
Span,
7-
engine::{EngineState, Stack, StateWorkingSet},
7+
engine::{Stack, StateWorkingSet},
88
};
99
use reedline::Suggestion;
1010
use std::path::Path;
1111

12-
use super::{SemanticSuggestion, SuggestionKind, completion_common::FileSuggestion};
12+
use super::{SemanticSuggestion, SuggestionKind};
1313

1414
pub struct FileCompletion;
1515

@@ -84,14 +84,3 @@ impl Completer for FileCompletion {
8484
non_hidden
8585
}
8686
}
87-
88-
pub fn file_path_completion(
89-
span: nu_protocol::Span,
90-
partial: &str,
91-
cwds: &[impl AsRef<str>],
92-
options: &CompletionOptions,
93-
engine_state: &EngineState,
94-
stack: &Stack,
95-
) -> Vec<FileSuggestion> {
96-
complete_item(false, span, partial, cwds, options, engine_state, stack)
97-
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub use custom_completions::CustomCompletion;
2525
pub use directory_completions::DirectoryCompletion;
2626
pub use dotnu_completions::DotNuCompletion;
2727
pub use exportable_completions::ExportableCompletion;
28-
pub use file_completions::{FileCompletion, file_path_completion};
28+
pub use file_completions::FileCompletion;
2929
pub use flag_completions::FlagCompletion;
3030
pub use operator_completions::OperatorCompletion;
3131
pub use static_completions::StaticCompletion;

0 commit comments

Comments
 (0)