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