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);
45 let source_root_id =
46 ctx.db.file_source_root(module_definition_file.file_id(ctx.db)).source_root_id(ctx.db);
47 let source_root = ctx.db.source_root(source_root_id).source_root(ctx.db);
48
49 let directory_to_look_for_submodules = directory_to_look_for_submodules(
50 current_module,
51 ctx.db,
52 source_root.path_for_file(&module_definition_file.file_id(ctx.db))?,
53 )?;
54
55 let existing_mod_declarations = current_module
56 .children(ctx.db)
57 .filter_map(|module| Some(module.name(ctx.db)?.display(ctx.db, ctx.edition).to_string()))
58 .filter(|module| module != ctx.original_token.text())
59 .collect::<FxHashSet<_>>();
60
61 let module_declaration_file =
62 current_module.declaration_source_range(ctx.db).map(|module_declaration_source_file| {
63 module_declaration_source_file.file_id.original_file(ctx.db)
64 });
65
66 source_root
67 .iter()
68 .filter(|&submodule_candidate_file| {
69 submodule_candidate_file != module_definition_file.file_id(ctx.db)
70 })
71 .filter(|&submodule_candidate_file| {
72 module_declaration_file.is_none_or(|it| it.file_id(ctx.db) != submodule_candidate_file)
73 })
74 .filter_map(|submodule_file| {
75 let submodule_path = source_root.path_for_file(&submodule_file)?;
76 let directory_with_submodule = submodule_path.parent()?;
77 let (name, ext) = submodule_path.name_and_extension()?;
78 if ext != Some("rs") {
79 return None;
80 }
81 match name {
82 "lib" | "main" => None,
83 "mod" => {
84 if directory_with_submodule.parent()? == directory_to_look_for_submodules {
85 match directory_with_submodule.name_and_extension()? {
86 (directory_name, None) => Some(directory_name.to_owned()),
87 _ => None,
88 }
89 } else {
90 None
91 }
92 }
93 file_name if directory_with_submodule == directory_to_look_for_submodules => {
94 Some(file_name.to_owned())
95 }
96 _ => None,
97 }
98 })
99 .filter(|name| !existing_mod_declarations.contains(name))
100 .for_each(|submodule_name| {
101 let mut label = submodule_name;
102 if mod_under_caret.semicolon_token().is_none() {
103 label.push(';');
104 }
105 let item =
106 CompletionItem::new(SymbolKind::Module, ctx.source_range(), &label, ctx.edition);
107 item.add_to(acc, ctx.db)
108 });
109
110 Some(())
111}
112
113fn directory_to_look_for_submodules(
114 module: Module,
115 db: &RootDatabase,
116 module_file_path: &VfsPath,
117) -> Option<VfsPath> {
118 let directory_with_module_path = module_file_path.parent()?;
119 let (name, ext) = module_file_path.name_and_extension()?;
120 if ext != Some("rs") {
121 return None;
122 }
123 let base_directory = match name {
124 "mod" | "lib" | "main" => Some(directory_with_module_path),
125 regular_rust_file_name => {
126 if matches!(
127 (
128 directory_with_module_path
129 .parent()
130 .as_ref()
131 .and_then(|path| path.name_and_extension()),
132 directory_with_module_path.name_and_extension(),
133 ),
134 (Some(("src", None)), Some(("bin", None)))
135 ) {
136 Some(directory_with_module_path)
138 } else {
139 directory_with_module_path.join(regular_rust_file_name)
140 }
141 }
142 }?;
143
144 module_chain_to_containing_module_file(module, db)
145 .into_iter()
146 .filter_map(|module| module.name(db))
147 .try_fold(base_directory, |path, name| path.join(name.as_str()))
148}
149
150fn module_chain_to_containing_module_file(
151 current_module: Module,
152 db: &RootDatabase,
153) -> Vec<Module> {
154 let mut path =
155 iter::successors(Some(current_module), |current_module| current_module.parent(db))
156 .take_while(|current_module| current_module.is_inline(db))
157 .collect::<Vec<_>>();
158 path.reverse();
159 path
160}
161
162#[cfg(test)]
163mod tests {
164 use expect_test::expect;
165
166 use crate::tests::check;
167
168 #[test]
169 fn lib_module_completion() {
170 check(
171 r#"
172//- /lib.rs
173mod $0
174//- /foo.rs
175fn foo() {}
176//- /foo/ignored_foo.rs
177fn ignored_foo() {}
178//- /bar/mod.rs
179fn bar() {}
180//- /bar/ignored_bar.rs
181fn ignored_bar() {}
182"#,
183 expect![[r#"
184 md bar;
185 md foo;
186 "#]],
187 );
188 }
189
190 #[test]
191 fn no_module_completion_with_module_body() {
192 check(
193 r#"
194//- /lib.rs
195mod $0 {
196
197}
198//- /foo.rs
199fn foo() {}
200"#,
201 expect![[r#""#]],
202 );
203 }
204
205 #[test]
206 fn main_module_completion() {
207 check(
208 r#"
209//- /main.rs
210mod $0
211//- /foo.rs
212fn foo() {}
213//- /foo/ignored_foo.rs
214fn ignored_foo() {}
215//- /bar/mod.rs
216fn bar() {}
217//- /bar/ignored_bar.rs
218fn ignored_bar() {}
219"#,
220 expect![[r#"
221 md bar;
222 md foo;
223 "#]],
224 );
225 }
226
227 #[test]
228 fn main_test_module_completion() {
229 check(
230 r#"
231//- /main.rs
232mod tests {
233 mod $0;
234}
235//- /tests/foo.rs
236fn foo() {}
237"#,
238 expect![[r#"
239 md foo
240 "#]],
241 );
242 }
243
244 #[test]
245 fn directly_nested_module_completion() {
246 check(
247 r#"
248//- /lib.rs
249mod foo;
250//- /foo.rs
251mod $0;
252//- /foo/bar.rs
253fn bar() {}
254//- /foo/bar/ignored_bar.rs
255fn ignored_bar() {}
256//- /foo/baz/mod.rs
257fn baz() {}
258//- /foo/moar/ignored_moar.rs
259fn ignored_moar() {}
260"#,
261 expect![[r#"
262 md bar
263 md baz
264 "#]],
265 );
266 }
267
268 #[test]
269 fn nested_in_source_module_completion() {
270 check(
271 r#"
272//- /lib.rs
273mod foo;
274//- /foo.rs
275mod bar {
276 mod $0
277}
278//- /foo/bar/baz.rs
279fn baz() {}
280"#,
281 expect![[r#"
282 md baz;
283 "#]],
284 );
285 }
286
287 #[test]
315 fn already_declared_bin_module_completion_omitted() {
316 check(
317 r#"
318//- /src/bin.rs crate:main
319fn main() {}
320//- /src/bin/foo.rs
321mod $0
322//- /src/bin/bar.rs
323mod foo;
324fn bar() {}
325//- /src/bin/bar/bar_ignored.rs
326fn bar_ignored() {}
327"#,
328 expect![[r#""#]],
329 );
330 }
331
332 #[test]
333 fn name_partially_typed() {
334 check(
335 r#"
336//- /lib.rs
337mod f$0
338//- /foo.rs
339fn foo() {}
340//- /foo/ignored_foo.rs
341fn ignored_foo() {}
342//- /bar/mod.rs
343fn bar() {}
344//- /bar/ignored_bar.rs
345fn ignored_bar() {}
346"#,
347 expect![[r#"
348 md bar;
349 md foo;
350 "#]],
351 );
352 }
353
354 #[test]
355 fn semi_colon_completion() {
356 check(
357 r#"
358//- /lib.rs
359mod foo;
360//- /foo.rs
361mod bar {
362 mod baz$0
363}
364//- /foo/bar/baz.rs
365fn baz() {}
366"#,
367 expect![[r#"
368 md baz;
369 "#]],
370 );
371 }
372}