Skip to content

Commit 644da8b

Browse files
authored
feat(completion): include already parsed module names in dotnu completion (#17026)
Closes #17021 @ysthakur, the name "dotnu" seems abused after this change? ## Release notes summary - What our users need to know Added module names to completion results of `use <tab>` ## Tasks after submitting
1 parent ce99af9 commit 644da8b

2 files changed

Lines changed: 122 additions & 76 deletions

File tree

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

Lines changed: 98 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use nu_protocol::{
88
engine::{Stack, StateWorkingSet, VirtualPath},
99
};
1010
use reedline::Suggestion;
11-
use std::collections::HashSet;
11+
use std::collections::{HashMap, HashSet};
1212

1313
use super::completion_common::{complete_item, surround_remove};
1414

@@ -27,6 +27,76 @@ impl Completer for DotNuCompletion {
2727
offset: usize,
2828
options: &CompletionOptions,
2929
) -> Vec<SemanticSuggestion> {
30+
let reedline_span = reedline::Span {
31+
start: span.start - offset,
32+
end: span.end - offset,
33+
};
34+
// Modules that are already loaded go first
35+
let mut matcher = NuMatcher::new(&prefix, options, true);
36+
let mut modules_map = HashMap::new();
37+
// TODO: inline-defined modules, e.g. `module foo {}; use foo<tab>` ?
38+
for overlay_frame in working_set.permanent_state.active_overlays(&[]) {
39+
modules_map.extend(&overlay_frame.modules);
40+
}
41+
42+
for (module_name_bytes, module_id) in modules_map.into_iter() {
43+
let value = String::from_utf8_lossy(module_name_bytes).to_string();
44+
let description = working_set.get_module_comments(*module_id).map(|spans| {
45+
spans
46+
.iter()
47+
.map(|sp| String::from_utf8_lossy(working_set.get_span_contents(*sp)).into())
48+
.collect::<Vec<String>>()
49+
.join("\n")
50+
});
51+
52+
matcher.add_semantic_suggestion(SemanticSuggestion {
53+
suggestion: Suggestion {
54+
value,
55+
description,
56+
span: reedline_span,
57+
append_whitespace: true,
58+
..Suggestion::default()
59+
},
60+
kind: Some(SuggestionKind::Module),
61+
});
62+
}
63+
64+
// Add std virtual paths first
65+
if self.std_virtual_path {
66+
// Where we have '/' in the prefix, e.g. use std/l
67+
if let Some((base_dir, _)) = prefix.as_ref().rsplit_once("/") {
68+
let base_dir = surround_remove(base_dir);
69+
if let Some(VirtualPath::Dir(sub_paths)) = working_set.find_virtual_path(&base_dir)
70+
{
71+
for sub_vp_id in sub_paths {
72+
let (path, sub_vp) = working_set.get_virtual_path(*sub_vp_id);
73+
matcher.add_semantic_suggestion(SemanticSuggestion {
74+
suggestion: Suggestion {
75+
value: path.into(),
76+
span: reedline_span,
77+
append_whitespace: !matches!(sub_vp, VirtualPath::Dir(_)),
78+
..Suggestion::default()
79+
},
80+
kind: Some(SuggestionKind::Module),
81+
});
82+
}
83+
}
84+
} else {
85+
for path in ["std", "std-rfc"] {
86+
matcher.add_semantic_suggestion(SemanticSuggestion {
87+
suggestion: Suggestion {
88+
value: path.into(),
89+
span: reedline_span,
90+
..Suggestion::default()
91+
},
92+
kind: Some(SuggestionKind::Module),
93+
});
94+
}
95+
}
96+
}
97+
98+
let mut all_results = matcher.suggestion_results();
99+
30100
// Fetch the lib dirs
31101
// NOTE: 2 ways to setup `NU_LIB_DIRS`
32102
// 1. `const NU_LIB_DIRS = [paths]`, equal to `nu -I paths`
@@ -51,53 +121,8 @@ impl Completer for DotNuCompletion {
51121
search_dirs.insert(cwd.into_std_path_buf());
52122
}
53123

54-
let mut completions = Vec::new();
55-
56-
// Add std virtual paths first
57-
if self.std_virtual_path {
58-
let mut matcher = NuMatcher::new(&prefix, options, true);
59-
// Where we have '/' in the prefix, e.g. use std/l
60-
if let Some((base_dir, _)) = prefix.as_ref().rsplit_once("/") {
61-
let base_dir = surround_remove(base_dir);
62-
if let Some(VirtualPath::Dir(sub_paths)) = working_set.find_virtual_path(&base_dir)
63-
{
64-
for sub_vp_id in sub_paths {
65-
let (path, sub_vp) = working_set.get_virtual_path(*sub_vp_id);
66-
matcher.add(
67-
path.clone(),
68-
FileSuggestion {
69-
path: path.into(),
70-
span,
71-
style: None,
72-
is_dir: matches!(sub_vp, VirtualPath::Dir(_)),
73-
},
74-
);
75-
}
76-
}
77-
} else {
78-
for path in ["std", "std-rfc"] {
79-
matcher.add(
80-
path,
81-
FileSuggestion {
82-
path: path.into(),
83-
span,
84-
style: None,
85-
is_dir: true,
86-
},
87-
);
88-
}
89-
}
90-
completions.extend(
91-
matcher
92-
.results()
93-
.into_iter()
94-
.map(|(s, _)| s)
95-
.collect::<Vec<_>>(),
96-
);
97-
}
98-
99124
// Fetch the files
100-
completions.extend(complete_item(
125+
let module_file_results = complete_item(
101126
false,
102127
span,
103128
prefix.as_ref(),
@@ -108,34 +133,33 @@ impl Completer for DotNuCompletion {
108133
options,
109134
working_set.permanent_state,
110135
stack,
111-
));
136+
);
112137

113-
let into_suggestion = |x: &FileSuggestion| SemanticSuggestion {
114-
suggestion: Suggestion {
115-
value: x.path.to_string(),
116-
style: x.style,
117-
span: reedline::Span {
118-
start: x.span.start - offset,
119-
end: x.span.end - offset,
120-
},
121-
append_whitespace: !x.is_dir,
122-
..Suggestion::default()
123-
},
124-
kind: Some(SuggestionKind::Module),
125-
};
138+
all_results.extend(
139+
// Put files atop
140+
module_file_results
141+
.iter()
142+
// filtering the files that ends with .nu
143+
.filter(|it| {
144+
// for paths with spaces in them
145+
let path = it.path.trim_end_matches('`');
146+
path.ends_with(".nu")
147+
})
148+
// or directories
149+
.chain(module_file_results.iter().filter(|it| it.is_dir))
150+
.map(|x: &FileSuggestion| SemanticSuggestion {
151+
suggestion: Suggestion {
152+
value: x.path.to_string(),
153+
style: x.style,
154+
span: reedline_span,
155+
append_whitespace: !x.is_dir,
156+
..Suggestion::default()
157+
},
158+
kind: Some(SuggestionKind::Module),
159+
})
160+
.collect::<Vec<_>>(),
161+
);
126162

127-
// Put files atop
128-
completions
129-
.iter()
130-
// filtering the files that ends with .nu
131-
.filter(|it| {
132-
// for paths with spaces in them
133-
let path = it.path.trim_end_matches('`');
134-
path.ends_with(".nu")
135-
})
136-
// or directories
137-
.chain(completions.iter().filter(|it| it.is_dir))
138-
.map(into_suggestion)
139-
.collect::<Vec<_>>()
163+
all_results
140164
}
141165
}

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -690,8 +690,8 @@ fn dotnu_completions() {
690690
match_suggestions(&expected, &suggestions);
691691

692692
// Test use completion
693-
expected.insert(7, "std-rfc");
694-
expected.insert(7, "std");
693+
expected.insert(0, "std-rfc");
694+
expected.insert(0, "std");
695695
let completion_str = "use ";
696696
let suggestions = completer.complete(completion_str, completion_str.len());
697697

@@ -723,6 +723,28 @@ fn dotnu_completions() {
723723
match_dir_content_for_dotnu(dir_content, &suggestions);
724724
}
725725

726+
// https://github.com/nushell/nushell/issues/17021
727+
#[test]
728+
fn module_name_completions() {
729+
let (_, _, mut engine, mut stack) = new_dotnu_engine();
730+
let code = r#"module "🤔🐘" {
731+
# module comment
732+
# another comment
733+
}"#;
734+
assert!(support::merge_input(code.as_bytes(), &mut engine, &mut stack).is_ok());
735+
736+
let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack));
737+
let completion_str = "use 🤔";
738+
739+
let suggestions = completer.complete(completion_str, completion_str.len());
740+
match_suggestions(&vec!["🤔🐘"], &suggestions);
741+
742+
assert_eq!(
743+
suggestions[0].description,
744+
Some("# module comment\n# another comment".into())
745+
);
746+
}
747+
726748
#[test]
727749
fn dotnu_stdlib_completions() {
728750
let (_, _, mut engine, stack) = new_dotnu_engine();

0 commit comments

Comments
 (0)