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