]> git.lizzy.rs Git - rust.git/blob - crates/ide-completion/src/completions/mod_.rs
fix: add semicolon completion to mod
[rust.git] / crates / ide-completion / src / completions / mod_.rs
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;`
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)?.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)
103         });
104
105     Some(())
106 }
107
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
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
165     fn check(ra_fixture: &str, expect: Expect) {
166         let actual = completion_list(ra_fixture);
167         expect.assert_eq(&actual);
168     }
169
170     #[test]
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]
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]
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]
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]
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]
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]
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]
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]
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 }