1 //! Completes mod declarations.
2
3 use std::iter;
4
5 use hir::{Module, ModuleSource};
6 use ide_db::{
7 base_db::{SourceDatabaseExt, VfsPath},
8 FxHashSet, RootDatabase, SymbolKind,
9 };
10 use syntax::{ast, AstNode, SyntaxKind};
11
12 use crate::{context::CompletionContext, CompletionItem, Completions};
13
14 /// Complete mod declaration, i.e. `mod $0;`
complete_mod( acc: &mut Completions, ctx: &CompletionContext<'_>, mod_under_caret: &ast::Module, ) -> Option<()>15 pub(crate) fn complete_mod(
16 acc: &mut Completions,
17 ctx: &CompletionContext<'_>,
18 mod_under_caret: &ast::Module,
19 ) -> Option<()> {
20 if mod_under_caret.item_list().is_some() {
21 return None;
22 }
23
24 let _p = profile::span("completion::complete_mod");
25
26 let mut current_module = ctx.module;
27 // For `mod $0`, `ctx.module` is its parent, but for `mod f$0`, it's `mod f` itself, but we're
28 // interested in its parent.
29 if ctx.original_token.kind() == SyntaxKind::IDENT {
30 if let Some(module) =
31 ctx.original_token.parent_ancestors().nth(1).and_then(ast::Module::cast)
32 {
33 match ctx.sema.to_def(&module) {
34 Some(module) if module == current_module => {
35 if let Some(parent) = current_module.parent(ctx.db) {
36 current_module = parent;
37 }
38 }
39 _ => {}
40 }
41 }
42 }
43
44 let module_definition_file =
45 current_module.definition_source(ctx.db).file_id.original_file(ctx.db);
46 let source_root = ctx.db.source_root(ctx.db.file_source_root(module_definition_file));
47 let directory_to_look_for_submodules = directory_to_look_for_submodules(
48 current_module,
49 ctx.db,
50 source_root.path_for_file(&module_definition_file)?,
51 )?;
52
53 let existing_mod_declarations = current_module
54 .children(ctx.db)
55 .filter_map(|module| Some(module.name(ctx.db)?.display(ctx.db).to_string()))
56 .filter(|module| module != ctx.original_token.text())
57 .collect::<FxHashSet<_>>();
58
59 let module_declaration_file =
60 current_module.declaration_source(ctx.db).map(|module_declaration_source_file| {
61 module_declaration_source_file.file_id.original_file(ctx.db)
62 });
63
64 source_root
65 .iter()
66 .filter(|submodule_candidate_file| submodule_candidate_file != &module_definition_file)
67 .filter(|submodule_candidate_file| {
68 Some(submodule_candidate_file) != module_declaration_file.as_ref()
69 })
70 .filter_map(|submodule_file| {
71 let submodule_path = source_root.path_for_file(&submodule_file)?;
72 let directory_with_submodule = submodule_path.parent()?;
73 let (name, ext) = submodule_path.name_and_extension()?;
74 if ext != Some("rs") {
75 return None;
76 }
77 match name {
78 "lib" | "main" => None,
79 "mod" => {
80 if directory_with_submodule.parent()? == directory_to_look_for_submodules {
81 match directory_with_submodule.name_and_extension()? {
82 (directory_name, None) => Some(directory_name.to_owned()),
83 _ => None,
84 }
85 } else {
86 None
87 }
88 }
89 file_name if directory_with_submodule == directory_to_look_for_submodules => {
90 Some(file_name.to_owned())
91 }
92 _ => None,
93 }
94 })
95 .filter(|name| !existing_mod_declarations.contains(name))
96 .for_each(|submodule_name| {
97 let mut label = submodule_name;
98 if mod_under_caret.semicolon_token().is_none() {
99 label.push(';');
100 }
101 let item = CompletionItem::new(SymbolKind::Module, ctx.source_range(), &label);
102 item.add_to(acc, ctx.db)
103 });
104
105 Some(())
106 }
107
directory_to_look_for_submodules( module: Module, db: &RootDatabase, module_file_path: &VfsPath, ) -> Option<VfsPath>108 fn directory_to_look_for_submodules(
109 module: Module,
110 db: &RootDatabase,
111 module_file_path: &VfsPath,
112 ) -> Option<VfsPath> {
113 let directory_with_module_path = module_file_path.parent()?;
114 let (name, ext) = module_file_path.name_and_extension()?;
115 if ext != Some("rs") {
116 return None;
117 }
118 let base_directory = match name {
119 "mod" | "lib" | "main" => Some(directory_with_module_path),
120 regular_rust_file_name => {
121 if matches!(
122 (
123 directory_with_module_path
124 .parent()
125 .as_ref()
126 .and_then(|path| path.name_and_extension()),
127 directory_with_module_path.name_and_extension(),
128 ),
129 (Some(("src", None)), Some(("bin", None)))
130 ) {
131 // files in /src/bin/ can import each other directly
132 Some(directory_with_module_path)
133 } else {
134 directory_with_module_path.join(regular_rust_file_name)
135 }
136 }
137 }?;
138
139 module_chain_to_containing_module_file(module, db)
140 .into_iter()
141 .filter_map(|module| module.name(db))
142 .try_fold(base_directory, |path, name| path.join(&name.to_smol_str()))
143 }
144
module_chain_to_containing_module_file( current_module: Module, db: &RootDatabase, ) -> Vec<Module>145 fn module_chain_to_containing_module_file(
146 current_module: Module,
147 db: &RootDatabase,
148 ) -> Vec<Module> {
149 let mut path =
150 iter::successors(Some(current_module), |current_module| current_module.parent(db))
151 .take_while(|current_module| {
152 matches!(current_module.definition_source(db).value, ModuleSource::Module(_))
153 })
154 .collect::<Vec<_>>();
155 path.reverse();
156 path
157 }
158
159 #[cfg(test)]
160 mod tests {
161 use expect_test::{expect, Expect};
162
163 use crate::tests::completion_list;
164
check(ra_fixture: &str, expect: Expect)165 fn check(ra_fixture: &str, expect: Expect) {
166 let actual = completion_list(ra_fixture);
167 expect.assert_eq(&actual);
168 }
169
170 #[test]
lib_module_completion()171 fn lib_module_completion() {
172 check(
173 r#"
174 //- /lib.rs
175 mod $0
176 //- /foo.rs
177 fn foo() {}
178 //- /foo/ignored_foo.rs
179 fn ignored_foo() {}
180 //- /bar/mod.rs
181 fn bar() {}
182 //- /bar/ignored_bar.rs
183 fn ignored_bar() {}
184 "#,
185 expect![[r#"
186 md bar;
187 md foo;
188 "#]],
189 );
190 }
191
192 #[test]
no_module_completion_with_module_body()193 fn no_module_completion_with_module_body() {
194 check(
195 r#"
196 //- /lib.rs
197 mod $0 {
198
199 }
200 //- /foo.rs
201 fn foo() {}
202 "#,
203 expect![[r#""#]],
204 );
205 }
206
207 #[test]
main_module_completion()208 fn main_module_completion() {
209 check(
210 r#"
211 //- /main.rs
212 mod $0
213 //- /foo.rs
214 fn foo() {}
215 //- /foo/ignored_foo.rs
216 fn ignored_foo() {}
217 //- /bar/mod.rs
218 fn bar() {}
219 //- /bar/ignored_bar.rs
220 fn ignored_bar() {}
221 "#,
222 expect![[r#"
223 md bar;
224 md foo;
225 "#]],
226 );
227 }
228
229 #[test]
main_test_module_completion()230 fn main_test_module_completion() {
231 check(
232 r#"
233 //- /main.rs
234 mod tests {
235 mod $0;
236 }
237 //- /tests/foo.rs
238 fn foo() {}
239 "#,
240 expect![[r#"
241 md foo
242 "#]],
243 );
244 }
245
246 #[test]
directly_nested_module_completion()247 fn directly_nested_module_completion() {
248 check(
249 r#"
250 //- /lib.rs
251 mod foo;
252 //- /foo.rs
253 mod $0;
254 //- /foo/bar.rs
255 fn bar() {}
256 //- /foo/bar/ignored_bar.rs
257 fn ignored_bar() {}
258 //- /foo/baz/mod.rs
259 fn baz() {}
260 //- /foo/moar/ignored_moar.rs
261 fn ignored_moar() {}
262 "#,
263 expect![[r#"
264 md bar
265 md baz
266 "#]],
267 );
268 }
269
270 #[test]
nested_in_source_module_completion()271 fn nested_in_source_module_completion() {
272 check(
273 r#"
274 //- /lib.rs
275 mod foo;
276 //- /foo.rs
277 mod bar {
278 mod $0
279 }
280 //- /foo/bar/baz.rs
281 fn baz() {}
282 "#,
283 expect![[r#"
284 md baz;
285 "#]],
286 );
287 }
288
289 // FIXME binary modules are not supported in tests properly
290 // Binary modules are a bit special, they allow importing the modules from `/src/bin`
291 // and that's why are good to test two things:
292 // * no cycles are allowed in mod declarations
293 // * no modules from the parent directory are proposed
294 // Unfortunately, binary modules support is in cargo not rustc,
295 // hence the test does not work now
296 //
297 // #[test]
298 // fn regular_bin_module_completion() {
299 // check(
300 // r#"
301 // //- /src/bin.rs
302 // fn main() {}
303 // //- /src/bin/foo.rs
304 // mod $0
305 // //- /src/bin/bar.rs
306 // fn bar() {}
307 // //- /src/bin/bar/bar_ignored.rs
308 // fn bar_ignored() {}
309 // "#,
310 // expect![[r#"
311 // md bar;
312 // "#]],foo
313 // );
314 // }
315
316 #[test]
already_declared_bin_module_completion_omitted()317 fn already_declared_bin_module_completion_omitted() {
318 check(
319 r#"
320 //- /src/bin.rs crate:main
321 fn main() {}
322 //- /src/bin/foo.rs
323 mod $0
324 //- /src/bin/bar.rs
325 mod foo;
326 fn bar() {}
327 //- /src/bin/bar/bar_ignored.rs
328 fn bar_ignored() {}
329 "#,
330 expect![[r#""#]],
331 );
332 }
333
334 #[test]
name_partially_typed()335 fn name_partially_typed() {
336 check(
337 r#"
338 //- /lib.rs
339 mod f$0
340 //- /foo.rs
341 fn foo() {}
342 //- /foo/ignored_foo.rs
343 fn ignored_foo() {}
344 //- /bar/mod.rs
345 fn bar() {}
346 //- /bar/ignored_bar.rs
347 fn ignored_bar() {}
348 "#,
349 expect![[r#"
350 md bar;
351 md foo;
352 "#]],
353 );
354 }
355
356 #[test]
semi_colon_completion()357 fn semi_colon_completion() {
358 check(
359 r#"
360 //- /lib.rs
361 mod foo;
362 //- /foo.rs
363 mod bar {
364 mod baz$0
365 }
366 //- /foo/bar/baz.rs
367 fn baz() {}
368 "#,
369 expect![[r#"
370 md baz;
371 "#]],
372 );
373 }
374 }
375