diff options
author | Kirill Bulatov <[email protected]> | 2021-01-16 17:33:36 +0000 |
---|---|---|
committer | Kirill Bulatov <[email protected]> | 2021-01-16 17:33:36 +0000 |
commit | 6742f38e49d001359a7a9911becc0fcae4c67910 (patch) | |
tree | 807a038d74e345668b8f5025bc58a44b11503a33 /crates/completion/src/completions/flyimport.rs | |
parent | 3782c78d7558633be5483a04aa1c098fe76100b9 (diff) |
Share import_assets and related entities
Diffstat (limited to 'crates/completion/src/completions/flyimport.rs')
-rw-r--r-- | crates/completion/src/completions/flyimport.rs | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/crates/completion/src/completions/flyimport.rs b/crates/completion/src/completions/flyimport.rs new file mode 100644 index 000000000..54f8e6d24 --- /dev/null +++ b/crates/completion/src/completions/flyimport.rs | |||
@@ -0,0 +1,291 @@ | |||
1 | //! Feature: completion with imports-on-the-fly | ||
2 | //! | ||
3 | //! When completing names in the current scope, proposes additional imports from other modules or crates, | ||
4 | //! if they can be qualified in the scope and their name contains all symbols from the completion input | ||
5 | //! (case-insensitive, in any order or places). | ||
6 | //! | ||
7 | //! ``` | ||
8 | //! fn main() { | ||
9 | //! pda$0 | ||
10 | //! } | ||
11 | //! # pub mod std { pub mod marker { pub struct PhantomData { } } } | ||
12 | //! ``` | ||
13 | //! -> | ||
14 | //! ``` | ||
15 | //! use std::marker::PhantomData; | ||
16 | //! | ||
17 | //! fn main() { | ||
18 | //! PhantomData | ||
19 | //! } | ||
20 | //! # pub mod std { pub mod marker { pub struct PhantomData { } } } | ||
21 | //! ``` | ||
22 | //! | ||
23 | //! .Fuzzy search details | ||
24 | //! | ||
25 | //! To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only | ||
26 | //! (i.e. in `HashMap` in the `std::collections::HashMap` path). | ||
27 | //! For the same reasons, avoids searching for any imports for inputs with their length less that 2 symbols. | ||
28 | //! | ||
29 | //! .Import configuration | ||
30 | //! | ||
31 | //! It is possible to configure how use-trees are merged with the `importMergeBehavior` setting. | ||
32 | //! Mimics the corresponding behavior of the `Auto Import` feature. | ||
33 | //! | ||
34 | //! .LSP and performance implications | ||
35 | //! | ||
36 | //! The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits` | ||
37 | //! (case sensitive) resolve client capability in its client capabilities. | ||
38 | //! This way the server is able to defer the costly computations, doing them for a selected completion item only. | ||
39 | //! For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones, | ||
40 | //! which might be slow ergo the feature is automatically disabled. | ||
41 | //! | ||
42 | //! .Feature toggle | ||
43 | //! | ||
44 | //! The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.enableAutoimportCompletions` flag. | ||
45 | //! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding | ||
46 | //! capability enabled. | ||
47 | |||
48 | use either::Either; | ||
49 | use hir::{ModPath, ScopeDef}; | ||
50 | use ide_db::{helpers::insert_use::ImportScope, imports_locator}; | ||
51 | use syntax::AstNode; | ||
52 | use test_utils::mark; | ||
53 | |||
54 | use crate::{ | ||
55 | context::CompletionContext, | ||
56 | render::{render_resolution_with_import, RenderContext}, | ||
57 | ImportEdit, | ||
58 | }; | ||
59 | |||
60 | use super::Completions; | ||
61 | |||
62 | pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | ||
63 | if !ctx.config.enable_autoimport_completions { | ||
64 | return None; | ||
65 | } | ||
66 | if ctx.attribute_under_caret.is_some() || ctx.mod_declaration_under_caret.is_some() { | ||
67 | return None; | ||
68 | } | ||
69 | let potential_import_name = ctx.token.to_string(); | ||
70 | if potential_import_name.len() < 2 { | ||
71 | return None; | ||
72 | } | ||
73 | let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.to_string()); | ||
74 | |||
75 | let current_module = ctx.scope.module()?; | ||
76 | let anchor = ctx.name_ref_syntax.as_ref()?; | ||
77 | let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; | ||
78 | |||
79 | let user_input_lowercased = potential_import_name.to_lowercase(); | ||
80 | let mut all_mod_paths = imports_locator::find_similar_imports( | ||
81 | &ctx.sema, | ||
82 | ctx.krate?, | ||
83 | Some(40), | ||
84 | potential_import_name, | ||
85 | true, | ||
86 | true, | ||
87 | ) | ||
88 | .filter_map(|import_candidate| { | ||
89 | Some(match import_candidate { | ||
90 | Either::Left(module_def) => { | ||
91 | (current_module.find_use_path(ctx.db, module_def)?, ScopeDef::ModuleDef(module_def)) | ||
92 | } | ||
93 | Either::Right(macro_def) => { | ||
94 | (current_module.find_use_path(ctx.db, macro_def)?, ScopeDef::MacroDef(macro_def)) | ||
95 | } | ||
96 | }) | ||
97 | }) | ||
98 | .filter(|(mod_path, _)| mod_path.len() > 1) | ||
99 | .collect::<Vec<_>>(); | ||
100 | |||
101 | all_mod_paths.sort_by_cached_key(|(mod_path, _)| { | ||
102 | compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased) | ||
103 | }); | ||
104 | |||
105 | acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| { | ||
106 | render_resolution_with_import( | ||
107 | RenderContext::new(ctx), | ||
108 | ImportEdit { import_path, import_scope: import_scope.clone() }, | ||
109 | &definition, | ||
110 | ) | ||
111 | })); | ||
112 | Some(()) | ||
113 | } | ||
114 | |||
115 | fn compute_fuzzy_completion_order_key( | ||
116 | proposed_mod_path: &ModPath, | ||
117 | user_input_lowercased: &str, | ||
118 | ) -> usize { | ||
119 | mark::hit!(certain_fuzzy_order_test); | ||
120 | let proposed_import_name = match proposed_mod_path.segments.last() { | ||
121 | Some(name) => name.to_string().to_lowercase(), | ||
122 | None => return usize::MAX, | ||
123 | }; | ||
124 | match proposed_import_name.match_indices(user_input_lowercased).next() { | ||
125 | Some((first_matching_index, _)) => first_matching_index, | ||
126 | None => usize::MAX, | ||
127 | } | ||
128 | } | ||
129 | |||
130 | #[cfg(test)] | ||
131 | mod tests { | ||
132 | use expect_test::{expect, Expect}; | ||
133 | use test_utils::mark; | ||
134 | |||
135 | use crate::{ | ||
136 | item::CompletionKind, | ||
137 | test_utils::{check_edit, completion_list}, | ||
138 | }; | ||
139 | |||
140 | fn check(ra_fixture: &str, expect: Expect) { | ||
141 | let actual = completion_list(ra_fixture, CompletionKind::Magic); | ||
142 | expect.assert_eq(&actual); | ||
143 | } | ||
144 | |||
145 | #[test] | ||
146 | fn function_fuzzy_completion() { | ||
147 | check_edit( | ||
148 | "stdin", | ||
149 | r#" | ||
150 | //- /lib.rs crate:dep | ||
151 | pub mod io { | ||
152 | pub fn stdin() {} | ||
153 | }; | ||
154 | |||
155 | //- /main.rs crate:main deps:dep | ||
156 | fn main() { | ||
157 | stdi$0 | ||
158 | } | ||
159 | "#, | ||
160 | r#" | ||
161 | use dep::io::stdin; | ||
162 | |||
163 | fn main() { | ||
164 | stdin()$0 | ||
165 | } | ||
166 | "#, | ||
167 | ); | ||
168 | } | ||
169 | |||
170 | #[test] | ||
171 | fn macro_fuzzy_completion() { | ||
172 | check_edit( | ||
173 | "macro_with_curlies!", | ||
174 | r#" | ||
175 | //- /lib.rs crate:dep | ||
176 | /// Please call me as macro_with_curlies! {} | ||
177 | #[macro_export] | ||
178 | macro_rules! macro_with_curlies { | ||
179 | () => {} | ||
180 | } | ||
181 | |||
182 | //- /main.rs crate:main deps:dep | ||
183 | fn main() { | ||
184 | curli$0 | ||
185 | } | ||
186 | "#, | ||
187 | r#" | ||
188 | use dep::macro_with_curlies; | ||
189 | |||
190 | fn main() { | ||
191 | macro_with_curlies! {$0} | ||
192 | } | ||
193 | "#, | ||
194 | ); | ||
195 | } | ||
196 | |||
197 | #[test] | ||
198 | fn struct_fuzzy_completion() { | ||
199 | check_edit( | ||
200 | "ThirdStruct", | ||
201 | r#" | ||
202 | //- /lib.rs crate:dep | ||
203 | pub struct FirstStruct; | ||
204 | pub mod some_module { | ||
205 | pub struct SecondStruct; | ||
206 | pub struct ThirdStruct; | ||
207 | } | ||
208 | |||
209 | //- /main.rs crate:main deps:dep | ||
210 | use dep::{FirstStruct, some_module::SecondStruct}; | ||
211 | |||
212 | fn main() { | ||
213 | this$0 | ||
214 | } | ||
215 | "#, | ||
216 | r#" | ||
217 | use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}}; | ||
218 | |||
219 | fn main() { | ||
220 | ThirdStruct | ||
221 | } | ||
222 | "#, | ||
223 | ); | ||
224 | } | ||
225 | |||
226 | #[test] | ||
227 | fn fuzzy_completions_come_in_specific_order() { | ||
228 | mark::check!(certain_fuzzy_order_test); | ||
229 | check( | ||
230 | r#" | ||
231 | //- /lib.rs crate:dep | ||
232 | pub struct FirstStruct; | ||
233 | pub mod some_module { | ||
234 | // already imported, omitted | ||
235 | pub struct SecondStruct; | ||
236 | // does not contain all letters from the query, omitted | ||
237 | pub struct UnrelatedOne; | ||
238 | // contains all letters from the query, but not in sequence, displayed last | ||
239 | pub struct ThiiiiiirdStruct; | ||
240 | // contains all letters from the query, but not in the beginning, displayed second | ||
241 | pub struct AfterThirdStruct; | ||
242 | // contains all letters from the query in the begginning, displayed first | ||
243 | pub struct ThirdStruct; | ||
244 | } | ||
245 | |||
246 | //- /main.rs crate:main deps:dep | ||
247 | use dep::{FirstStruct, some_module::SecondStruct}; | ||
248 | |||
249 | fn main() { | ||
250 | hir$0 | ||
251 | } | ||
252 | "#, | ||
253 | expect![[r#" | ||
254 | st dep::some_module::ThirdStruct | ||
255 | st dep::some_module::AfterThirdStruct | ||
256 | st dep::some_module::ThiiiiiirdStruct | ||
257 | "#]], | ||
258 | ); | ||
259 | } | ||
260 | |||
261 | #[test] | ||
262 | fn does_not_propose_names_in_scope() { | ||
263 | check( | ||
264 | r#" | ||
265 | //- /lib.rs crate:dep | ||
266 | pub mod test_mod { | ||
267 | pub trait TestTrait { | ||
268 | const SPECIAL_CONST: u8; | ||
269 | type HumbleType; | ||
270 | fn weird_function(); | ||
271 | fn random_method(&self); | ||
272 | } | ||
273 | pub struct TestStruct {} | ||
274 | impl TestTrait for TestStruct { | ||
275 | const SPECIAL_CONST: u8 = 42; | ||
276 | type HumbleType = (); | ||
277 | fn weird_function() {} | ||
278 | fn random_method(&self) {} | ||
279 | } | ||
280 | } | ||
281 | |||
282 | //- /main.rs crate:main deps:dep | ||
283 | use dep::test_mod::TestStruct; | ||
284 | fn main() { | ||
285 | TestSt$0 | ||
286 | } | ||
287 | "#, | ||
288 | expect![[r#""#]], | ||
289 | ); | ||
290 | } | ||
291 | } | ||