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