ide_completion/completions/
mod_.rs1use std::iter;
4
5use hir::Module;
6use ide_db::{
7 FxHashSet, RootDatabase, SymbolKind,
8 base_db::{SourceDatabase, VfsPath},
9};
10use syntax::{AstNode, SyntaxKind, ast};
11
12use crate::{CompletionItem, Completions, context::CompletionContext};
13
14pub(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 = tracing::info_span!("completion::complete_mod").entered();
25
26 let mut current_module = ctx.module;
27 if ctx.original_token.kind() == SyntaxKind::IDENT
30 && 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 let module_definition_file =
44 current_module.definition_source_file_id(ctx.db).original_file(ctx.db).file_id(ctx.db);
45 let source_root_id = ctx.db.file_source_root(module_definition_file).source_root_id(ctx.db);
46 let source_root = ctx.db.source_root(source_root_id).source_root(ctx.db);
47 let crate_roots = base_db::all_crates(ctx.db)
48 .iter()
49 .map(|krate| krate.root_file_id(ctx.db).file_id(ctx.db))
50 .collect::<FxHashSet<_>>();
51
52 let directory_to_look_for_submodules = directory_to_look_for_submodules(
53 current_module,
54 ctx.db,
55 crate_roots.contains(&module_definition_file),
56 source_root.path_for_file(&module_definition_file)?,
57 )?;
58
59 let existing_mod_declarations = current_module
60 .children(ctx.db)
61 .filter_map(|module| Some(module.name(ctx.db)?.as_str().to_owned()))
62 .filter(|module| module != ctx.original_token.text())
63 .collect::<FxHashSet<_>>();
64
65 let module_declaration_file =
66 current_module.declaration_source_range(ctx.db).map(|module_declaration_source_file| {
67 module_declaration_source_file.file_id.original_file(ctx.db)
68 });
69
70 source_root
71 .iter()
72 .filter(|&submodule_candidate_file| submodule_candidate_file != module_definition_file)
73 .filter(|&submodule_candidate_file| {
74 module_declaration_file.is_none_or(|it| it.file_id(ctx.db) != submodule_candidate_file)
75 })
76 .filter_map(|submodule_file| {
77 let submodule_path = source_root.path_for_file(&submodule_file)?;
78 let directory_with_submodule = submodule_path.parent()?;
79 let (name, ext) = submodule_path.name_and_extension()?;
80 if ext != Some("rs") || crate_roots.contains(&submodule_file) {
81 return None;
82 }
83 match name {
84 "mod" => {
85 if directory_with_submodule.parent()? == directory_to_look_for_submodules {
86 match directory_with_submodule.name_and_extension()? {
87 (directory_name, None) => Some(directory_name.to_owned()),
88 _ => None,
89 }
90 } else {
91 None
92 }
93 }
94 file_name if directory_with_submodule == directory_to_look_for_submodules => {
95 Some(file_name.to_owned())
96 }
97 _ => None,
98 }
99 })
100 .filter(|name| !existing_mod_declarations.contains(name))
101 .for_each(|name| {
102 let mut item =
103 CompletionItem::new(SymbolKind::Module, ctx.source_range(), &name, ctx.edition);
104 let mut insert_text = name.clone();
105 if mod_under_caret.semicolon_token().is_none() {
106 item.lookup_by(&name).label(format!("{name};"));
107 insert_text.push(';');
108 }
109 if syntax::utils::is_raw_identifier(&name, ctx.edition) {
110 insert_text.insert_str(0, "r#");
111 }
112 item.insert_text(insert_text);
113 item.add_to(acc, ctx.db)
114 });
115
116 Some(())
117}
118
119fn directory_to_look_for_submodules(
120 module: Module,
121 db: &RootDatabase,
122 is_root: bool,
123 module_file_path: &VfsPath,
124) -> Option<VfsPath> {
125 let directory_with_module_path = module_file_path.parent()?;
126 let (name, ext) = module_file_path.name_and_extension()?;
127 if ext != Some("rs") {
128 return None;
129 }
130 let base_directory = match name {
131 "mod" => Some(directory_with_module_path),
132 _ if is_root => Some(directory_with_module_path),
133 regular_rust_file_name => {
134 if matches!(
135 (
136 directory_with_module_path
137 .parent()
138 .as_ref()
139 .and_then(|path| path.name_and_extension()),
140 directory_with_module_path.name_and_extension(),
141 ),
142 (Some(("src", None)), Some(("bin", None)))
143 ) {
144 Some(directory_with_module_path)
146 } else {
147 directory_with_module_path.join(regular_rust_file_name)
148 }
149 }
150 }?;
151
152 module_chain_to_containing_module_file(module, db)
153 .into_iter()
154 .filter_map(|module| module.name(db))
155 .try_fold(base_directory, |path, name| path.join(name.as_str()))
156}
157
158fn module_chain_to_containing_module_file(
159 current_module: Module,
160 db: &RootDatabase,
161) -> Vec<Module> {
162 let mut path =
163 iter::successors(Some(current_module), |current_module| current_module.parent(db))
164 .take_while(|current_module| current_module.is_inline(db))
165 .collect::<Vec<_>>();
166 path.reverse();
167 path
168}
169
170#[cfg(test)]
171mod tests {
172 use expect_test::expect;
173
174 use crate::tests::{check, check_edit};
175
176 #[test]
177 fn lib_module_completion() {
178 check(
179 r#"
180//- /lib.rs
181mod $0
182//- /foo.rs
183fn foo() {}
184//- /foo/ignored_foo.rs
185fn ignored_foo() {}
186//- /bar/mod.rs
187fn bar() {}
188//- /bar/ignored_bar.rs
189fn ignored_bar() {}
190"#,
191 expect![[r#"
192 md bar;
193 md foo;
194 "#]],
195 );
196 }
197
198 #[test]
199 fn no_module_completion_with_module_body() {
200 check(
201 r#"
202//- /lib.rs
203mod $0 {
204
205}
206//- /foo.rs
207fn foo() {}
208"#,
209 expect![[r#""#]],
210 );
211 }
212
213 #[test]
214 fn exclude_exists_module_declarations() {
215 check(
216 r#"
217//- /lib.rs
218mod foo;
219mod r#struct;
220mod $0
221//- /foo.rs
222//- /bar.rs
223//- /struct.rs
224"#,
225 expect![[r#"
226 md bar;
227 "#]],
228 );
229 }
230
231 #[test]
232 fn exclude_crate_roots() {
233 check(
234 r#"
235//- /lib.rs crate:lib
236mod $0
237//- /main.rs crate:main
238//- /sub.rs
239"#,
240 expect![[r#"
241 md sub;
242 "#]],
243 );
244 check(
245 r#"
246//- /main.rs crate:main
247mod $0
248//- /lib.rs crate:lib
249//- /sub.rs
250"#,
251 expect![[r#"
252 md sub;
253 "#]],
254 );
255 }
256
257 #[test]
258 fn contained_submodule_name_same_as_root() {
259 check(
260 r#"
261//- /lib.rs
262mod sub {
263 mod $0
264}
265//- /sub/lib.rs
266//- /sub/main.rs
267"#,
268 expect![[r#"
269 md lib;
270 md main;
271 "#]],
272 );
273 }
274
275 #[test]
276 fn inside_name_same_as_root() {
277 check(
278 r#"
279//- /foo/lib.rs
280mod $0
281//- /foo/lib/loader.rs
282//- /lib.rs
283mod foo {
284 mod lib;
285}
286"#,
287 expect![[r#"
288 md loader;
289 "#]],
290 );
291 }
292
293 #[test]
294 fn main_module_completion() {
295 check(
296 r#"
297//- /main.rs
298mod $0
299//- /foo.rs
300fn foo() {}
301//- /foo/ignored_foo.rs
302fn ignored_foo() {}
303//- /bar/mod.rs
304fn bar() {}
305//- /bar/ignored_bar.rs
306fn ignored_bar() {}
307"#,
308 expect![[r#"
309 md bar;
310 md foo;
311 "#]],
312 );
313 }
314
315 #[test]
316 fn main_test_module_completion() {
317 check(
318 r#"
319//- /main.rs
320mod tests {
321 mod $0;
322}
323//- /tests/foo.rs
324fn foo() {}
325"#,
326 expect![[r#"
327 md foo
328 "#]],
329 );
330 }
331
332 #[test]
333 fn directly_nested_module_completion() {
334 check(
335 r#"
336//- /lib.rs
337mod foo;
338//- /foo.rs
339mod $0;
340//- /foo/bar.rs
341fn bar() {}
342//- /foo/bar/ignored_bar.rs
343fn ignored_bar() {}
344//- /foo/baz/mod.rs
345fn baz() {}
346//- /foo/moar/ignored_moar.rs
347fn ignored_moar() {}
348"#,
349 expect![[r#"
350 md bar
351 md baz
352 "#]],
353 );
354 }
355
356 #[test]
357 fn nested_in_source_module_completion() {
358 check(
359 r#"
360//- /lib.rs
361mod foo;
362//- /foo.rs
363mod bar {
364 mod $0
365}
366//- /foo/bar/baz.rs
367fn baz() {}
368"#,
369 expect![[r#"
370 md baz;
371 "#]],
372 );
373 }
374
375 #[test]
403 fn already_declared_bin_module_completion_omitted() {
404 check(
405 r#"
406//- /src/bin.rs crate:main
407fn main() {}
408//- /src/bin/foo.rs
409mod $0
410//- /src/bin/bar.rs
411mod foo;
412fn bar() {}
413//- /src/bin/bar/bar_ignored.rs
414fn bar_ignored() {}
415"#,
416 expect![[r#""#]],
417 );
418 }
419
420 #[test]
421 fn name_partially_typed() {
422 check(
423 r#"
424//- /lib.rs
425mod f$0
426//- /foo.rs
427fn foo() {}
428//- /foo/ignored_foo.rs
429fn ignored_foo() {}
430//- /bar/mod.rs
431fn bar() {}
432//- /bar/ignored_bar.rs
433fn ignored_bar() {}
434"#,
435 expect![[r#"
436 md bar;
437 md foo;
438 "#]],
439 );
440 }
441
442 #[test]
443 fn semi_colon_completion() {
444 check(
445 r#"
446//- /lib.rs
447mod foo;
448//- /foo.rs
449mod bar {
450 mod baz$0
451}
452//- /foo/bar/baz.rs
453fn baz() {}
454"#,
455 expect![[r#"
456 md baz;
457 "#]],
458 );
459 check(
460 r#"
461//- /lib.rs
462mod foo;
463//- /foo.rs
464mod bar {
465 mod struct$0
466}
467//- /foo/bar/struct.rs
468fn baz() {}
469"#,
470 expect![[r#"
471 md struct;
472 "#]],
473 );
474 }
475
476 #[test]
477 fn insert_escaped_name() {
478 check(
479 r#"
480//- /lib.rs
481mod s$0
482//- /struct.rs
483fn baz() {}
484//- /normal.rs
485fn baz() {}
486"#,
487 expect![[r#"
488 md normal;
489 md struct;
490 "#]],
491 );
492 check_edit(
493 "normal",
494 r#"
495//- /lib.rs
496mod s$0
497//- /struct.rs
498fn baz() {}
499//- /normal.rs
500fn baz() {}
501"#,
502 r#"
503mod normal;
504"#,
505 );
506 check_edit(
507 "struct",
508 r#"
509//- /lib.rs
510mod s$0
511//- /struct.rs
512fn baz() {}
513//- /normal.rs
514fn baz() {}
515"#,
516 r#"
517mod r#struct;
518"#,
519 );
520 }
521}