diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-01-16 17:52:29 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2021-01-16 17:52:29 +0000 |
commit | 9a349f280ff1c6d0b57df80aa3d6720474e4b00a (patch) | |
tree | 23bbb365f31438949358a576bc9467fa70fa874e | |
parent | 3782c78d7558633be5483a04aa1c098fe76100b9 (diff) | |
parent | 497fc232e7d90d8d39c7a13742dd85d758dc2f72 (diff) |
Merge #7295
7295: Share import_assets and related entities r=matklad a=SomeoneToIgnore
Part of https://github.com/rust-analyzer/rust-analyzer/pull/7293
Addresses https://github.com/rust-analyzer/rust-analyzer/pull/7293#issuecomment-761569558
Prepares `import_assets` and related to be used later for the trait fuzzy importing.
Also moves fuzzy imports into a separate completion module and renames them, as suggested in https://github.com/rust-analyzer/rust-analyzer/pull/7293#discussion_r558896685
Co-authored-by: Kirill Bulatov <[email protected]>
21 files changed, 421 insertions, 337 deletions
diff --git a/crates/assists/src/assist_config.rs b/crates/assists/src/assist_config.rs index 4fe8ea761..9cabf037c 100644 --- a/crates/assists/src/assist_config.rs +++ b/crates/assists/src/assist_config.rs | |||
@@ -4,7 +4,7 @@ | |||
4 | //! module, and we use to statically check that we only produce snippet | 4 | //! module, and we use to statically check that we only produce snippet |
5 | //! assists if we are allowed to. | 5 | //! assists if we are allowed to. |
6 | 6 | ||
7 | use ide_db::helpers::{insert_use::MergeBehavior, SnippetCap}; | 7 | use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap}; |
8 | 8 | ||
9 | use crate::AssistKind; | 9 | use crate::AssistKind; |
10 | 10 | ||
@@ -14,9 +14,3 @@ pub struct AssistConfig { | |||
14 | pub allowed: Option<Vec<AssistKind>>, | 14 | pub allowed: Option<Vec<AssistKind>>, |
15 | pub insert_use: InsertUseConfig, | 15 | pub insert_use: InsertUseConfig, |
16 | } | 16 | } |
17 | |||
18 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
19 | pub struct InsertUseConfig { | ||
20 | pub merge: Option<MergeBehavior>, | ||
21 | pub prefix_kind: hir::PrefixKind, | ||
22 | } | ||
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs index 55620f0f3..4e2a4fcd9 100644 --- a/crates/assists/src/handlers/auto_import.rs +++ b/crates/assists/src/handlers/auto_import.rs | |||
@@ -1,13 +1,11 @@ | |||
1 | use ide_db::helpers::{ | 1 | use ide_db::helpers::{ |
2 | import_assets::{ImportAssets, ImportCandidate}, | ||
2 | insert_use::{insert_use, ImportScope}, | 3 | insert_use::{insert_use, ImportScope}, |
3 | mod_path_to_ast, | 4 | mod_path_to_ast, |
4 | }; | 5 | }; |
5 | use syntax::ast; | 6 | use syntax::ast; |
6 | 7 | ||
7 | use crate::{ | 8 | use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; |
8 | utils::import_assets::{ImportAssets, ImportCandidate}, | ||
9 | AssistContext, AssistId, AssistKind, Assists, GroupLabel, | ||
10 | }; | ||
11 | 9 | ||
12 | // Feature: Auto Import | 10 | // Feature: Auto Import |
13 | // | 11 | // |
@@ -121,8 +119,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
121 | 119 | ||
122 | fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel { | 120 | fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel { |
123 | let name = match import_candidate { | 121 | let name = match import_candidate { |
124 | ImportCandidate::UnqualifiedName(candidate) | 122 | ImportCandidate::Path(candidate) => format!("Import {}", &candidate.name), |
125 | | ImportCandidate::QualifierStart(candidate) => format!("Import {}", &candidate.name), | ||
126 | ImportCandidate::TraitAssocItem(candidate) => { | 123 | ImportCandidate::TraitAssocItem(candidate) => { |
127 | format!("Import a trait for item {}", &candidate.name) | 124 | format!("Import a trait for item {}", &candidate.name) |
128 | } | 125 | } |
diff --git a/crates/assists/src/handlers/qualify_path.rs b/crates/assists/src/handlers/qualify_path.rs index f7fbf37f4..a7d9fd4dc 100644 --- a/crates/assists/src/handlers/qualify_path.rs +++ b/crates/assists/src/handlers/qualify_path.rs | |||
@@ -1,7 +1,10 @@ | |||
1 | use std::iter; | 1 | use std::iter; |
2 | 2 | ||
3 | use hir::AsName; | 3 | use hir::AsName; |
4 | use ide_db::helpers::mod_path_to_ast; | 4 | use ide_db::helpers::{ |
5 | import_assets::{ImportAssets, ImportCandidate}, | ||
6 | mod_path_to_ast, | ||
7 | }; | ||
5 | use ide_db::RootDatabase; | 8 | use ide_db::RootDatabase; |
6 | use syntax::{ | 9 | use syntax::{ |
7 | ast, | 10 | ast, |
@@ -12,7 +15,6 @@ use test_utils::mark; | |||
12 | 15 | ||
13 | use crate::{ | 16 | use crate::{ |
14 | assist_context::{AssistContext, Assists}, | 17 | assist_context::{AssistContext, Assists}, |
15 | utils::import_assets::{ImportAssets, ImportCandidate}, | ||
16 | AssistId, AssistKind, GroupLabel, | 18 | AssistId, AssistKind, GroupLabel, |
17 | }; | 19 | }; |
18 | 20 | ||
@@ -53,17 +55,18 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
53 | let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range; | 55 | let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range; |
54 | 56 | ||
55 | let qualify_candidate = match candidate { | 57 | let qualify_candidate = match candidate { |
56 | ImportCandidate::QualifierStart(_) => { | 58 | ImportCandidate::Path(candidate) => { |
57 | mark::hit!(qualify_path_qualifier_start); | 59 | if candidate.qualifier.is_some() { |
58 | let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; | 60 | mark::hit!(qualify_path_qualifier_start); |
59 | let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?); | 61 | let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; |
60 | QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list()) | 62 | let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?); |
61 | } | 63 | QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list()) |
62 | ImportCandidate::UnqualifiedName(_) => { | 64 | } else { |
63 | mark::hit!(qualify_path_unqualified_name); | 65 | mark::hit!(qualify_path_unqualified_name); |
64 | let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; | 66 | let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; |
65 | let generics = path.segment()?.generic_arg_list(); | 67 | let generics = path.segment()?.generic_arg_list(); |
66 | QualifyCandidate::UnqualifiedName(generics) | 68 | QualifyCandidate::UnqualifiedName(generics) |
69 | } | ||
67 | } | 70 | } |
68 | ImportCandidate::TraitAssocItem(_) => { | 71 | ImportCandidate::TraitAssocItem(_) => { |
69 | mark::hit!(qualify_path_trait_assoc_item); | 72 | mark::hit!(qualify_path_trait_assoc_item); |
@@ -186,7 +189,7 @@ fn item_as_trait(item: hir::ItemInNs) -> Option<hir::Trait> { | |||
186 | 189 | ||
187 | fn group_label(candidate: &ImportCandidate) -> GroupLabel { | 190 | fn group_label(candidate: &ImportCandidate) -> GroupLabel { |
188 | let name = match candidate { | 191 | let name = match candidate { |
189 | ImportCandidate::UnqualifiedName(it) | ImportCandidate::QualifierStart(it) => &it.name, | 192 | ImportCandidate::Path(it) => &it.name, |
190 | ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name, | 193 | ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name, |
191 | }; | 194 | }; |
192 | GroupLabel(format!("Qualify {}", name)) | 195 | GroupLabel(format!("Qualify {}", name)) |
@@ -194,8 +197,13 @@ fn group_label(candidate: &ImportCandidate) -> GroupLabel { | |||
194 | 197 | ||
195 | fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String { | 198 | fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String { |
196 | match candidate { | 199 | match candidate { |
197 | ImportCandidate::UnqualifiedName(_) => format!("Qualify as `{}`", &import), | 200 | ImportCandidate::Path(candidate) => { |
198 | ImportCandidate::QualifierStart(_) => format!("Qualify with `{}`", &import), | 201 | if candidate.qualifier.is_some() { |
202 | format!("Qualify with `{}`", &import) | ||
203 | } else { | ||
204 | format!("Qualify as `{}`", &import) | ||
205 | } | ||
206 | } | ||
199 | ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", &import), | 207 | ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", &import), |
200 | ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", &import), | 208 | ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", &import), |
201 | } | 209 | } |
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs index 3d7971806..14178a651 100644 --- a/crates/assists/src/lib.rs +++ b/crates/assists/src/lib.rs | |||
@@ -24,7 +24,7 @@ use syntax::TextRange; | |||
24 | 24 | ||
25 | pub(crate) use crate::assist_context::{AssistContext, Assists}; | 25 | pub(crate) use crate::assist_context::{AssistContext, Assists}; |
26 | 26 | ||
27 | pub use assist_config::{AssistConfig, InsertUseConfig}; | 27 | pub use assist_config::AssistConfig; |
28 | 28 | ||
29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
30 | pub enum AssistKind { | 30 | pub enum AssistKind { |
diff --git a/crates/assists/src/tests.rs b/crates/assists/src/tests.rs index 71431b406..32bd8698b 100644 --- a/crates/assists/src/tests.rs +++ b/crates/assists/src/tests.rs | |||
@@ -3,16 +3,17 @@ mod generated; | |||
3 | use hir::Semantics; | 3 | use hir::Semantics; |
4 | use ide_db::{ | 4 | use ide_db::{ |
5 | base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}, | 5 | base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}, |
6 | helpers::{insert_use::MergeBehavior, SnippetCap}, | 6 | helpers::{ |
7 | insert_use::{InsertUseConfig, MergeBehavior}, | ||
8 | SnippetCap, | ||
9 | }, | ||
7 | source_change::FileSystemEdit, | 10 | source_change::FileSystemEdit, |
8 | RootDatabase, | 11 | RootDatabase, |
9 | }; | 12 | }; |
10 | use syntax::TextRange; | 13 | use syntax::TextRange; |
11 | use test_utils::{assert_eq_text, extract_offset, extract_range}; | 14 | use test_utils::{assert_eq_text, extract_offset, extract_range}; |
12 | 15 | ||
13 | use crate::{ | 16 | use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists}; |
14 | handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists, InsertUseConfig, | ||
15 | }; | ||
16 | use stdx::{format_to, trim_indent}; | 17 | use stdx::{format_to, trim_indent}; |
17 | 18 | ||
18 | pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig { | 19 | pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig { |
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs index 9ea96eb73..fc9f83bab 100644 --- a/crates/assists/src/utils.rs +++ b/crates/assists/src/utils.rs | |||
@@ -1,5 +1,4 @@ | |||
1 | //! Assorted functions shared by several assists. | 1 | //! Assorted functions shared by several assists. |
2 | pub(crate) mod import_assets; | ||
3 | 2 | ||
4 | use std::ops; | 3 | use std::ops; |
5 | 4 | ||
diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index 00c9e76f0..c3ce6e51d 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs | |||
@@ -13,6 +13,7 @@ pub(crate) mod postfix; | |||
13 | pub(crate) mod macro_in_item_position; | 13 | pub(crate) mod macro_in_item_position; |
14 | pub(crate) mod trait_impl; | 14 | pub(crate) mod trait_impl; |
15 | pub(crate) mod mod_; | 15 | pub(crate) mod mod_; |
16 | pub(crate) mod flyimport; | ||
16 | 17 | ||
17 | use hir::{ModPath, ScopeDef, Type}; | 18 | use hir::{ModPath, ScopeDef, Type}; |
18 | 19 | ||
diff --git a/crates/completion/src/completions/flyimport.rs b/crates/completion/src/completions/flyimport.rs new file mode 100644 index 000000000..222809638 --- /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 | } | ||
diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs index 53e1391f3..ac5596ca4 100644 --- a/crates/completion/src/completions/unqualified_path.rs +++ b/crates/completion/src/completions/unqualified_path.rs | |||
@@ -2,17 +2,11 @@ | |||
2 | 2 | ||
3 | use std::iter; | 3 | use std::iter; |
4 | 4 | ||
5 | use either::Either; | 5 | use hir::{Adt, ModuleDef, ScopeDef, Type}; |
6 | use hir::{Adt, ModPath, ModuleDef, ScopeDef, Type}; | ||
7 | use ide_db::helpers::insert_use::ImportScope; | ||
8 | use ide_db::imports_locator; | ||
9 | use syntax::AstNode; | 6 | use syntax::AstNode; |
10 | use test_utils::mark; | 7 | use test_utils::mark; |
11 | 8 | ||
12 | use crate::{ | 9 | use crate::{CompletionContext, Completions}; |
13 | render::{render_resolution_with_import, RenderContext}, | ||
14 | CompletionContext, Completions, ImportEdit, | ||
15 | }; | ||
16 | 10 | ||
17 | pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { | 11 | pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { |
18 | if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { | 12 | if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) { |
@@ -45,10 +39,6 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC | |||
45 | } | 39 | } |
46 | acc.add_resolution(ctx, name.to_string(), &res) | 40 | acc.add_resolution(ctx, name.to_string(), &res) |
47 | }); | 41 | }); |
48 | |||
49 | if ctx.config.enable_autoimport_completions { | ||
50 | fuzzy_completion(acc, ctx); | ||
51 | } | ||
52 | } | 42 | } |
53 | 43 | ||
54 | fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) { | 44 | fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) { |
@@ -77,124 +67,13 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T | |||
77 | } | 67 | } |
78 | } | 68 | } |
79 | 69 | ||
80 | // Feature: Fuzzy Completion and Autoimports | ||
81 | // | ||
82 | // When completing names in the current scope, proposes additional imports from other modules or crates, | ||
83 | // if they can be qualified in the scope and their name contains all symbols from the completion input | ||
84 | // (case-insensitive, in any order or places). | ||
85 | // | ||
86 | // ``` | ||
87 | // fn main() { | ||
88 | // pda$0 | ||
89 | // } | ||
90 | // # pub mod std { pub mod marker { pub struct PhantomData { } } } | ||
91 | // ``` | ||
92 | // -> | ||
93 | // ``` | ||
94 | // use std::marker::PhantomData; | ||
95 | // | ||
96 | // fn main() { | ||
97 | // PhantomData | ||
98 | // } | ||
99 | // # pub mod std { pub mod marker { pub struct PhantomData { } } } | ||
100 | // ``` | ||
101 | // | ||
102 | // .Fuzzy search details | ||
103 | // | ||
104 | // To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only | ||
105 | // (i.e. in `HashMap` in the `std::collections::HashMap` path). | ||
106 | // For the same reasons, avoids searching for any imports for inputs with their length less that 2 symbols. | ||
107 | // | ||
108 | // .Merge Behavior | ||
109 | // | ||
110 | // It is possible to configure how use-trees are merged with the `importMergeBehavior` setting. | ||
111 | // Mimics the corresponding behavior of the `Auto Import` feature. | ||
112 | // | ||
113 | // .LSP and performance implications | ||
114 | // | ||
115 | // The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits` | ||
116 | // (case sensitive) resolve client capability in its client capabilities. | ||
117 | // This way the server is able to defer the costly computations, doing them for a selected completion item only. | ||
118 | // For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones, | ||
119 | // which might be slow ergo the feature is automatically disabled. | ||
120 | // | ||
121 | // .Feature toggle | ||
122 | // | ||
123 | // The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.enableAutoimportCompletions` flag. | ||
124 | // Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding | ||
125 | // capability enabled. | ||
126 | fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | ||
127 | let potential_import_name = ctx.token.to_string(); | ||
128 | let _p = profile::span("fuzzy_completion").detail(|| potential_import_name.clone()); | ||
129 | |||
130 | if potential_import_name.len() < 2 { | ||
131 | return None; | ||
132 | } | ||
133 | |||
134 | let current_module = ctx.scope.module()?; | ||
135 | let anchor = ctx.name_ref_syntax.as_ref()?; | ||
136 | let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; | ||
137 | |||
138 | let user_input_lowercased = potential_import_name.to_lowercase(); | ||
139 | let mut all_mod_paths = imports_locator::find_similar_imports( | ||
140 | &ctx.sema, | ||
141 | ctx.krate?, | ||
142 | Some(40), | ||
143 | potential_import_name, | ||
144 | true, | ||
145 | true, | ||
146 | ) | ||
147 | .filter_map(|import_candidate| { | ||
148 | Some(match import_candidate { | ||
149 | Either::Left(module_def) => { | ||
150 | (current_module.find_use_path(ctx.db, module_def)?, ScopeDef::ModuleDef(module_def)) | ||
151 | } | ||
152 | Either::Right(macro_def) => { | ||
153 | (current_module.find_use_path(ctx.db, macro_def)?, ScopeDef::MacroDef(macro_def)) | ||
154 | } | ||
155 | }) | ||
156 | }) | ||
157 | .filter(|(mod_path, _)| mod_path.len() > 1) | ||
158 | .collect::<Vec<_>>(); | ||
159 | |||
160 | all_mod_paths.sort_by_cached_key(|(mod_path, _)| { | ||
161 | compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased) | ||
162 | }); | ||
163 | |||
164 | acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| { | ||
165 | render_resolution_with_import( | ||
166 | RenderContext::new(ctx), | ||
167 | ImportEdit { import_path, import_scope: import_scope.clone() }, | ||
168 | &definition, | ||
169 | ) | ||
170 | })); | ||
171 | Some(()) | ||
172 | } | ||
173 | |||
174 | fn compute_fuzzy_completion_order_key( | ||
175 | proposed_mod_path: &ModPath, | ||
176 | user_input_lowercased: &str, | ||
177 | ) -> usize { | ||
178 | mark::hit!(certain_fuzzy_order_test); | ||
179 | let proposed_import_name = match proposed_mod_path.segments.last() { | ||
180 | Some(name) => name.to_string().to_lowercase(), | ||
181 | None => return usize::MAX, | ||
182 | }; | ||
183 | match proposed_import_name.match_indices(user_input_lowercased).next() { | ||
184 | Some((first_matching_index, _)) => first_matching_index, | ||
185 | None => usize::MAX, | ||
186 | } | ||
187 | } | ||
188 | |||
189 | #[cfg(test)] | 70 | #[cfg(test)] |
190 | mod tests { | 71 | mod tests { |
191 | use expect_test::{expect, Expect}; | 72 | use expect_test::{expect, Expect}; |
192 | use test_utils::mark; | 73 | use test_utils::mark; |
193 | 74 | ||
194 | use crate::{ | 75 | use crate::{ |
195 | test_utils::{ | 76 | test_utils::{check_edit, completion_list_with_config, TEST_CONFIG}, |
196 | check_edit, check_edit_with_config, completion_list_with_config, TEST_CONFIG, | ||
197 | }, | ||
198 | CompletionConfig, CompletionKind, | 77 | CompletionConfig, CompletionKind, |
199 | }; | 78 | }; |
200 | 79 | ||
@@ -855,128 +734,4 @@ impl My$0 | |||
855 | "#]], | 734 | "#]], |
856 | ) | 735 | ) |
857 | } | 736 | } |
858 | |||
859 | #[test] | ||
860 | fn function_fuzzy_completion() { | ||
861 | check_edit_with_config( | ||
862 | TEST_CONFIG, | ||
863 | "stdin", | ||
864 | r#" | ||
865 | //- /lib.rs crate:dep | ||
866 | pub mod io { | ||
867 | pub fn stdin() {} | ||
868 | }; | ||
869 | |||
870 | //- /main.rs crate:main deps:dep | ||
871 | fn main() { | ||
872 | stdi$0 | ||
873 | } | ||
874 | "#, | ||
875 | r#" | ||
876 | use dep::io::stdin; | ||
877 | |||
878 | fn main() { | ||
879 | stdin()$0 | ||
880 | } | ||
881 | "#, | ||
882 | ); | ||
883 | } | ||
884 | |||
885 | #[test] | ||
886 | fn macro_fuzzy_completion() { | ||
887 | check_edit_with_config( | ||
888 | TEST_CONFIG, | ||
889 | "macro_with_curlies!", | ||
890 | r#" | ||
891 | //- /lib.rs crate:dep | ||
892 | /// Please call me as macro_with_curlies! {} | ||
893 | #[macro_export] | ||
894 | macro_rules! macro_with_curlies { | ||
895 | () => {} | ||
896 | } | ||
897 | |||
898 | //- /main.rs crate:main deps:dep | ||
899 | fn main() { | ||
900 | curli$0 | ||
901 | } | ||
902 | "#, | ||
903 | r#" | ||
904 | use dep::macro_with_curlies; | ||
905 | |||
906 | fn main() { | ||
907 | macro_with_curlies! {$0} | ||
908 | } | ||
909 | "#, | ||
910 | ); | ||
911 | } | ||
912 | |||
913 | #[test] | ||
914 | fn struct_fuzzy_completion() { | ||
915 | check_edit_with_config( | ||
916 | TEST_CONFIG, | ||
917 | "ThirdStruct", | ||
918 | r#" | ||
919 | //- /lib.rs crate:dep | ||
920 | pub struct FirstStruct; | ||
921 | pub mod some_module { | ||
922 | pub struct SecondStruct; | ||
923 | pub struct ThirdStruct; | ||
924 | } | ||
925 | |||
926 | //- /main.rs crate:main deps:dep | ||
927 | use dep::{FirstStruct, some_module::SecondStruct}; | ||
928 | |||
929 | fn main() { | ||
930 | this$0 | ||
931 | } | ||
932 | "#, | ||
933 | r#" | ||
934 | use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}}; | ||
935 | |||
936 | fn main() { | ||
937 | ThirdStruct | ||
938 | } | ||
939 | "#, | ||
940 | ); | ||
941 | } | ||
942 | |||
943 | #[test] | ||
944 | fn fuzzy_completions_come_in_specific_order() { | ||
945 | mark::check!(certain_fuzzy_order_test); | ||
946 | check_with_config( | ||
947 | TEST_CONFIG, | ||
948 | r#" | ||
949 | //- /lib.rs crate:dep | ||
950 | pub struct FirstStruct; | ||
951 | pub mod some_module { | ||
952 | // already imported, omitted | ||
953 | pub struct SecondStruct; | ||
954 | // does not contain all letters from the query, omitted | ||
955 | pub struct UnrelatedOne; | ||
956 | // contains all letters from the query, but not in sequence, displayed last | ||
957 | pub struct ThiiiiiirdStruct; | ||
958 | // contains all letters from the query, but not in the beginning, displayed second | ||
959 | pub struct AfterThirdStruct; | ||
960 | // contains all letters from the query in the begginning, displayed first | ||
961 | pub struct ThirdStruct; | ||
962 | } | ||
963 | |||
964 | //- /main.rs crate:main deps:dep | ||
965 | use dep::{FirstStruct, some_module::SecondStruct}; | ||
966 | |||
967 | fn main() { | ||
968 | hir$0 | ||
969 | } | ||
970 | "#, | ||
971 | expect![[r#" | ||
972 | fn main() fn main() | ||
973 | st SecondStruct | ||
974 | st FirstStruct | ||
975 | md dep | ||
976 | st dep::some_module::ThirdStruct | ||
977 | st dep::some_module::AfterThirdStruct | ||
978 | st dep::some_module::ThiiiiiirdStruct | ||
979 | "#]], | ||
980 | ); | ||
981 | } | ||
982 | } | 737 | } |
diff --git a/crates/completion/src/config.rs b/crates/completion/src/config.rs index b4439b7d1..58fc700f3 100644 --- a/crates/completion/src/config.rs +++ b/crates/completion/src/config.rs | |||
@@ -4,7 +4,7 @@ | |||
4 | //! module, and we use to statically check that we only produce snippet | 4 | //! module, and we use to statically check that we only produce snippet |
5 | //! completions if we are allowed to. | 5 | //! completions if we are allowed to. |
6 | 6 | ||
7 | use ide_db::helpers::{insert_use::MergeBehavior, SnippetCap}; | 7 | use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap}; |
8 | 8 | ||
9 | #[derive(Clone, Debug, PartialEq, Eq)] | 9 | #[derive(Clone, Debug, PartialEq, Eq)] |
10 | pub struct CompletionConfig { | 10 | pub struct CompletionConfig { |
@@ -13,5 +13,5 @@ pub struct CompletionConfig { | |||
13 | pub add_call_parenthesis: bool, | 13 | pub add_call_parenthesis: bool, |
14 | pub add_call_argument_snippets: bool, | 14 | pub add_call_argument_snippets: bool, |
15 | pub snippet_cap: Option<SnippetCap>, | 15 | pub snippet_cap: Option<SnippetCap>, |
16 | pub merge: Option<MergeBehavior>, | 16 | pub insert_use: InsertUseConfig, |
17 | } | 17 | } |
diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs index 6cba88a6b..ee1b822e7 100644 --- a/crates/completion/src/lib.rs +++ b/crates/completion/src/lib.rs | |||
@@ -127,6 +127,7 @@ pub fn completions( | |||
127 | completions::macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); | 127 | completions::macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); |
128 | completions::trait_impl::complete_trait_impl(&mut acc, &ctx); | 128 | completions::trait_impl::complete_trait_impl(&mut acc, &ctx); |
129 | completions::mod_::complete_mod(&mut acc, &ctx); | 129 | completions::mod_::complete_mod(&mut acc, &ctx); |
130 | completions::flyimport::import_on_the_fly(&mut acc, &ctx); | ||
130 | 131 | ||
131 | Some(acc) | 132 | Some(acc) |
132 | } | 133 | } |
@@ -153,7 +154,9 @@ pub fn resolve_completion_edits( | |||
153 | }) | 154 | }) |
154 | .find(|mod_path| mod_path.to_string() == full_import_path)?; | 155 | .find(|mod_path| mod_path.to_string() == full_import_path)?; |
155 | 156 | ||
156 | ImportEdit { import_path, import_scope }.to_text_edit(config.merge).map(|edit| vec![edit]) | 157 | ImportEdit { import_path, import_scope } |
158 | .to_text_edit(config.insert_use.merge) | ||
159 | .map(|edit| vec![edit]) | ||
157 | } | 160 | } |
158 | 161 | ||
159 | #[cfg(test)] | 162 | #[cfg(test)] |
diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs index e93c59f71..820dd01d1 100644 --- a/crates/completion/src/render.rs +++ b/crates/completion/src/render.rs | |||
@@ -51,11 +51,16 @@ pub(crate) fn render_resolution_with_import<'a>( | |||
51 | import_edit: ImportEdit, | 51 | import_edit: ImportEdit, |
52 | resolution: &ScopeDef, | 52 | resolution: &ScopeDef, |
53 | ) -> Option<CompletionItem> { | 53 | ) -> Option<CompletionItem> { |
54 | Render::new(ctx).render_resolution( | 54 | Render::new(ctx) |
55 | import_edit.import_path.segments.last()?.to_string(), | 55 | .render_resolution( |
56 | Some(import_edit), | 56 | import_edit.import_path.segments.last()?.to_string(), |
57 | resolution, | 57 | Some(import_edit), |
58 | ) | 58 | resolution, |
59 | ) | ||
60 | .map(|mut item| { | ||
61 | item.completion_kind = CompletionKind::Magic; | ||
62 | item | ||
63 | }) | ||
59 | } | 64 | } |
60 | 65 | ||
61 | /// Interface for data and methods required for items rendering. | 66 | /// Interface for data and methods required for items rendering. |
diff --git a/crates/completion/src/test_utils.rs b/crates/completion/src/test_utils.rs index 3f4b9d4ac..6ea6da989 100644 --- a/crates/completion/src/test_utils.rs +++ b/crates/completion/src/test_utils.rs | |||
@@ -1,9 +1,12 @@ | |||
1 | //! Runs completion for testing purposes. | 1 | //! Runs completion for testing purposes. |
2 | 2 | ||
3 | use hir::Semantics; | 3 | use hir::{PrefixKind, Semantics}; |
4 | use ide_db::{ | 4 | use ide_db::{ |
5 | base_db::{fixture::ChangeFixture, FileLoader, FilePosition}, | 5 | base_db::{fixture::ChangeFixture, FileLoader, FilePosition}, |
6 | helpers::{insert_use::MergeBehavior, SnippetCap}, | 6 | helpers::{ |
7 | insert_use::{InsertUseConfig, MergeBehavior}, | ||
8 | SnippetCap, | ||
9 | }, | ||
7 | RootDatabase, | 10 | RootDatabase, |
8 | }; | 11 | }; |
9 | use itertools::Itertools; | 12 | use itertools::Itertools; |
@@ -19,7 +22,10 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig { | |||
19 | add_call_parenthesis: true, | 22 | add_call_parenthesis: true, |
20 | add_call_argument_snippets: true, | 23 | add_call_argument_snippets: true, |
21 | snippet_cap: SnippetCap::new(true), | 24 | snippet_cap: SnippetCap::new(true), |
22 | merge: Some(MergeBehavior::Full), | 25 | insert_use: InsertUseConfig { |
26 | merge: Some(MergeBehavior::Full), | ||
27 | prefix_kind: PrefixKind::Plain, | ||
28 | }, | ||
23 | }; | 29 | }; |
24 | 30 | ||
25 | /// Creates analysis from a multi-file fixture, returns positions marked with $0. | 31 | /// Creates analysis from a multi-file fixture, returns positions marked with $0. |
@@ -110,7 +116,7 @@ pub(crate) fn check_edit_with_config( | |||
110 | 116 | ||
111 | let mut combined_edit = completion.text_edit().to_owned(); | 117 | let mut combined_edit = completion.text_edit().to_owned(); |
112 | if let Some(import_text_edit) = | 118 | if let Some(import_text_edit) = |
113 | completion.import_to_add().and_then(|edit| edit.to_text_edit(config.merge)) | 119 | completion.import_to_add().and_then(|edit| edit.to_text_edit(config.insert_use.merge)) |
114 | { | 120 | { |
115 | combined_edit.union(import_text_edit).expect( | 121 | combined_edit.union(import_text_edit).expect( |
116 | "Failed to apply completion resolve changes: change ranges overlap, but should not", | 122 | "Failed to apply completion resolve changes: change ranges overlap, but should not", |
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index afd552008..f8d69382e 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -80,7 +80,7 @@ pub use crate::{ | |||
80 | HlRange, | 80 | HlRange, |
81 | }, | 81 | }, |
82 | }; | 82 | }; |
83 | pub use assists::{Assist, AssistConfig, AssistId, AssistKind, InsertUseConfig}; | 83 | pub use assists::{Assist, AssistConfig, AssistId, AssistKind}; |
84 | pub use completion::{ | 84 | pub use completion::{ |
85 | CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, ImportEdit, | 85 | CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, ImportEdit, |
86 | InsertTextFormat, | 86 | InsertTextFormat, |
diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs index c6763ae36..0dcc4dd29 100644 --- a/crates/ide_db/src/helpers.rs +++ b/crates/ide_db/src/helpers.rs | |||
@@ -1,5 +1,6 @@ | |||
1 | //! A module with ide helpers for high-level ide features. | 1 | //! A module with ide helpers for high-level ide features. |
2 | pub mod insert_use; | 2 | pub mod insert_use; |
3 | pub mod import_assets; | ||
3 | 4 | ||
4 | use hir::{Crate, Enum, Module, ScopeDef, Semantics, Trait}; | 5 | use hir::{Crate, Enum, Module, ScopeDef, Semantics, Trait}; |
5 | use syntax::ast::{self, make}; | 6 | use syntax::ast::{self, make}; |
diff --git a/crates/assists/src/utils/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs index 4ce82c1ba..edc3da318 100644 --- a/crates/assists/src/utils/import_assets.rs +++ b/crates/ide_db/src/helpers/import_assets.rs | |||
@@ -1,19 +1,17 @@ | |||
1 | //! Look up accessible paths for items. | 1 | //! Look up accessible paths for items. |
2 | use either::Either; | 2 | use either::Either; |
3 | use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics}; | 3 | use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics}; |
4 | use ide_db::{imports_locator, RootDatabase}; | ||
5 | use rustc_hash::FxHashSet; | 4 | use rustc_hash::FxHashSet; |
6 | use syntax::{ast, AstNode, SyntaxNode}; | 5 | use syntax::{ast, AstNode, SyntaxNode}; |
7 | 6 | ||
8 | use crate::assist_config::InsertUseConfig; | 7 | use crate::{imports_locator, RootDatabase}; |
8 | |||
9 | use super::insert_use::InsertUseConfig; | ||
9 | 10 | ||
10 | #[derive(Debug)] | 11 | #[derive(Debug)] |
11 | pub(crate) enum ImportCandidate { | 12 | pub enum ImportCandidate { |
12 | /// Simple name like 'HashMap' | 13 | // A path, qualified (`std::collections::HashMap`) or not (`HashMap`). |
13 | UnqualifiedName(PathImportCandidate), | 14 | Path(PathImportCandidate), |
14 | /// First part of the qualified name. | ||
15 | /// For 'std::collections::HashMap', that will be 'std'. | ||
16 | QualifierStart(PathImportCandidate), | ||
17 | /// A trait associated function (with no self parameter) or associated constant. | 15 | /// A trait associated function (with no self parameter) or associated constant. |
18 | /// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type | 16 | /// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type |
19 | /// and `name` is the `test_function` | 17 | /// and `name` is the `test_function` |
@@ -25,25 +23,26 @@ pub(crate) enum ImportCandidate { | |||
25 | } | 23 | } |
26 | 24 | ||
27 | #[derive(Debug)] | 25 | #[derive(Debug)] |
28 | pub(crate) struct TraitImportCandidate { | 26 | pub struct TraitImportCandidate { |
29 | pub(crate) ty: hir::Type, | 27 | pub ty: hir::Type, |
30 | pub(crate) name: ast::NameRef, | 28 | pub name: ast::NameRef, |
31 | } | 29 | } |
32 | 30 | ||
33 | #[derive(Debug)] | 31 | #[derive(Debug)] |
34 | pub(crate) struct PathImportCandidate { | 32 | pub struct PathImportCandidate { |
35 | pub(crate) name: ast::NameRef, | 33 | pub qualifier: Option<ast::Path>, |
34 | pub name: ast::NameRef, | ||
36 | } | 35 | } |
37 | 36 | ||
38 | #[derive(Debug)] | 37 | #[derive(Debug)] |
39 | pub(crate) struct ImportAssets { | 38 | pub struct ImportAssets { |
40 | import_candidate: ImportCandidate, | 39 | import_candidate: ImportCandidate, |
41 | module_with_name_to_import: hir::Module, | 40 | module_with_name_to_import: hir::Module, |
42 | syntax_under_caret: SyntaxNode, | 41 | syntax_under_caret: SyntaxNode, |
43 | } | 42 | } |
44 | 43 | ||
45 | impl ImportAssets { | 44 | impl ImportAssets { |
46 | pub(crate) fn for_method_call( | 45 | pub fn for_method_call( |
47 | method_call: ast::MethodCallExpr, | 46 | method_call: ast::MethodCallExpr, |
48 | sema: &Semantics<RootDatabase>, | 47 | sema: &Semantics<RootDatabase>, |
49 | ) -> Option<Self> { | 48 | ) -> Option<Self> { |
@@ -56,7 +55,7 @@ impl ImportAssets { | |||
56 | }) | 55 | }) |
57 | } | 56 | } |
58 | 57 | ||
59 | pub(crate) fn for_regular_path( | 58 | pub fn for_regular_path( |
60 | path_under_caret: ast::Path, | 59 | path_under_caret: ast::Path, |
61 | sema: &Semantics<RootDatabase>, | 60 | sema: &Semantics<RootDatabase>, |
62 | ) -> Option<Self> { | 61 | ) -> Option<Self> { |
@@ -73,24 +72,23 @@ impl ImportAssets { | |||
73 | }) | 72 | }) |
74 | } | 73 | } |
75 | 74 | ||
76 | pub(crate) fn syntax_under_caret(&self) -> &SyntaxNode { | 75 | pub fn syntax_under_caret(&self) -> &SyntaxNode { |
77 | &self.syntax_under_caret | 76 | &self.syntax_under_caret |
78 | } | 77 | } |
79 | 78 | ||
80 | pub(crate) fn import_candidate(&self) -> &ImportCandidate { | 79 | pub fn import_candidate(&self) -> &ImportCandidate { |
81 | &self.import_candidate | 80 | &self.import_candidate |
82 | } | 81 | } |
83 | 82 | ||
84 | fn get_search_query(&self) -> &str { | 83 | fn get_search_query(&self) -> &str { |
85 | match &self.import_candidate { | 84 | match &self.import_candidate { |
86 | ImportCandidate::UnqualifiedName(candidate) | 85 | ImportCandidate::Path(candidate) => candidate.name.text(), |
87 | | ImportCandidate::QualifierStart(candidate) => candidate.name.text(), | ||
88 | ImportCandidate::TraitAssocItem(candidate) | 86 | ImportCandidate::TraitAssocItem(candidate) |
89 | | ImportCandidate::TraitMethod(candidate) => candidate.name.text(), | 87 | | ImportCandidate::TraitMethod(candidate) => candidate.name.text(), |
90 | } | 88 | } |
91 | } | 89 | } |
92 | 90 | ||
93 | pub(crate) fn search_for_imports( | 91 | pub fn search_for_imports( |
94 | &self, | 92 | &self, |
95 | sema: &Semantics<RootDatabase>, | 93 | sema: &Semantics<RootDatabase>, |
96 | config: &InsertUseConfig, | 94 | config: &InsertUseConfig, |
@@ -101,7 +99,7 @@ impl ImportAssets { | |||
101 | 99 | ||
102 | /// This may return non-absolute paths if a part of the returned path is already imported into scope. | 100 | /// This may return non-absolute paths if a part of the returned path is already imported into scope. |
103 | #[allow(dead_code)] | 101 | #[allow(dead_code)] |
104 | pub(crate) fn search_for_relative_paths( | 102 | pub fn search_for_relative_paths( |
105 | &self, | 103 | &self, |
106 | sema: &Semantics<RootDatabase>, | 104 | sema: &Semantics<RootDatabase>, |
107 | ) -> Vec<(hir::ModPath, hir::ItemInNs)> { | 105 | ) -> Vec<(hir::ModPath, hir::ItemInNs)> { |
@@ -253,10 +251,14 @@ impl ImportCandidate { | |||
253 | _ => return None, | 251 | _ => return None, |
254 | } | 252 | } |
255 | } else { | 253 | } else { |
256 | ImportCandidate::QualifierStart(PathImportCandidate { name: qualifier_start }) | 254 | ImportCandidate::Path(PathImportCandidate { |
255 | qualifier: Some(qualifier), | ||
256 | name: qualifier_start, | ||
257 | }) | ||
257 | } | 258 | } |
258 | } else { | 259 | } else { |
259 | ImportCandidate::UnqualifiedName(PathImportCandidate { | 260 | ImportCandidate::Path(PathImportCandidate { |
261 | qualifier: None, | ||
260 | name: segment.syntax().descendants().find_map(ast::NameRef::cast)?, | 262 | name: segment.syntax().descendants().find_map(ast::NameRef::cast)?, |
261 | }) | 263 | }) |
262 | }; | 264 | }; |
diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs index d2f9f5d25..877d4f1c7 100644 --- a/crates/ide_db/src/helpers/insert_use.rs +++ b/crates/ide_db/src/helpers/insert_use.rs | |||
@@ -15,6 +15,12 @@ use syntax::{ | |||
15 | }; | 15 | }; |
16 | use test_utils::mark; | 16 | use test_utils::mark; |
17 | 17 | ||
18 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
19 | pub struct InsertUseConfig { | ||
20 | pub merge: Option<MergeBehavior>, | ||
21 | pub prefix_kind: hir::PrefixKind, | ||
22 | } | ||
23 | |||
18 | #[derive(Debug, Clone)] | 24 | #[derive(Debug, Clone)] |
19 | pub enum ImportScope { | 25 | pub enum ImportScope { |
20 | File(ast::SourceFile), | 26 | File(ast::SourceFile), |
diff --git a/crates/ide_db/src/imports_locator.rs b/crates/ide_db/src/imports_locator.rs index e9f23adf8..d111fba92 100644 --- a/crates/ide_db/src/imports_locator.rs +++ b/crates/ide_db/src/imports_locator.rs | |||
@@ -12,6 +12,8 @@ use crate::{ | |||
12 | use either::Either; | 12 | use either::Either; |
13 | use rustc_hash::FxHashSet; | 13 | use rustc_hash::FxHashSet; |
14 | 14 | ||
15 | const QUERY_SEARCH_LIMIT: usize = 40; | ||
16 | |||
15 | pub fn find_exact_imports<'a>( | 17 | pub fn find_exact_imports<'a>( |
16 | sema: &Semantics<'a, RootDatabase>, | 18 | sema: &Semantics<'a, RootDatabase>, |
17 | krate: Crate, | 19 | krate: Crate, |
@@ -24,11 +26,11 @@ pub fn find_exact_imports<'a>( | |||
24 | { | 26 | { |
25 | let mut local_query = symbol_index::Query::new(name_to_import.clone()); | 27 | let mut local_query = symbol_index::Query::new(name_to_import.clone()); |
26 | local_query.exact(); | 28 | local_query.exact(); |
27 | local_query.limit(40); | 29 | local_query.limit(QUERY_SEARCH_LIMIT); |
28 | local_query | 30 | local_query |
29 | }, | 31 | }, |
30 | import_map::Query::new(name_to_import) | 32 | import_map::Query::new(name_to_import) |
31 | .limit(40) | 33 | .limit(QUERY_SEARCH_LIMIT) |
32 | .name_only() | 34 | .name_only() |
33 | .search_mode(import_map::SearchMode::Equals) | 35 | .search_mode(import_map::SearchMode::Equals) |
34 | .case_sensitive(), | 36 | .case_sensitive(), |
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs index 7d3fda7a8..a02c8327f 100644 --- a/crates/rust-analyzer/src/cli/analysis_bench.rs +++ b/crates/rust-analyzer/src/cli/analysis_bench.rs | |||
@@ -3,6 +3,7 @@ | |||
3 | use std::{env, path::PathBuf, str::FromStr, sync::Arc, time::Instant}; | 3 | use std::{env, path::PathBuf, str::FromStr, sync::Arc, time::Instant}; |
4 | 4 | ||
5 | use anyhow::{bail, format_err, Result}; | 5 | use anyhow::{bail, format_err, Result}; |
6 | use hir::PrefixKind; | ||
6 | use ide::{ | 7 | use ide::{ |
7 | Analysis, AnalysisHost, Change, CompletionConfig, DiagnosticsConfig, FilePosition, LineCol, | 8 | Analysis, AnalysisHost, Change, CompletionConfig, DiagnosticsConfig, FilePosition, LineCol, |
8 | }; | 9 | }; |
@@ -11,7 +12,7 @@ use ide_db::{ | |||
11 | salsa::{Database, Durability}, | 12 | salsa::{Database, Durability}, |
12 | FileId, | 13 | FileId, |
13 | }, | 14 | }, |
14 | helpers::SnippetCap, | 15 | helpers::{insert_use::InsertUseConfig, SnippetCap}, |
15 | }; | 16 | }; |
16 | use vfs::AbsPathBuf; | 17 | use vfs::AbsPathBuf; |
17 | 18 | ||
@@ -96,7 +97,7 @@ impl BenchCmd { | |||
96 | add_call_parenthesis: true, | 97 | add_call_parenthesis: true, |
97 | add_call_argument_snippets: true, | 98 | add_call_argument_snippets: true, |
98 | snippet_cap: SnippetCap::new(true), | 99 | snippet_cap: SnippetCap::new(true), |
99 | merge: None, | 100 | insert_use: InsertUseConfig { merge: None, prefix_kind: PrefixKind::Plain }, |
100 | }; | 101 | }; |
101 | let res = do_work(&mut host, file_id, |analysis| { | 102 | let res = do_work(&mut host, file_id, |analysis| { |
102 | analysis.completions(&options, file_position) | 103 | analysis.completions(&options, file_position) |
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 27b92a5a9..ce9655818 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs | |||
@@ -11,11 +11,11 @@ use std::{convert::TryFrom, ffi::OsString, iter, path::PathBuf}; | |||
11 | 11 | ||
12 | use flycheck::FlycheckConfig; | 12 | use flycheck::FlycheckConfig; |
13 | use hir::PrefixKind; | 13 | use hir::PrefixKind; |
14 | use ide::{ | 14 | use ide::{AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig}; |
15 | AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig, | 15 | use ide_db::helpers::{ |
16 | InsertUseConfig, | 16 | insert_use::{InsertUseConfig, MergeBehavior}, |
17 | SnippetCap, | ||
17 | }; | 18 | }; |
18 | use ide_db::helpers::{insert_use::MergeBehavior, SnippetCap}; | ||
19 | use itertools::Itertools; | 19 | use itertools::Itertools; |
20 | use lsp_types::{ClientCapabilities, MarkupKind}; | 20 | use lsp_types::{ClientCapabilities, MarkupKind}; |
21 | use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest}; | 21 | use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest}; |
@@ -542,11 +542,18 @@ impl Config { | |||
542 | max_length: self.data.inlayHints_maxLength, | 542 | max_length: self.data.inlayHints_maxLength, |
543 | } | 543 | } |
544 | } | 544 | } |
545 | fn merge_behavior(&self) -> Option<MergeBehavior> { | 545 | fn insert_use_config(&self) -> InsertUseConfig { |
546 | match self.data.assist_importMergeBehavior { | 546 | InsertUseConfig { |
547 | MergeBehaviorDef::None => None, | 547 | merge: match self.data.assist_importMergeBehavior { |
548 | MergeBehaviorDef::Full => Some(MergeBehavior::Full), | 548 | MergeBehaviorDef::None => None, |
549 | MergeBehaviorDef::Last => Some(MergeBehavior::Last), | 549 | MergeBehaviorDef::Full => Some(MergeBehavior::Full), |
550 | MergeBehaviorDef::Last => Some(MergeBehavior::Last), | ||
551 | }, | ||
552 | prefix_kind: match self.data.assist_importPrefix { | ||
553 | ImportPrefixDef::Plain => PrefixKind::Plain, | ||
554 | ImportPrefixDef::ByCrate => PrefixKind::ByCrate, | ||
555 | ImportPrefixDef::BySelf => PrefixKind::BySelf, | ||
556 | }, | ||
550 | } | 557 | } |
551 | } | 558 | } |
552 | pub fn completion(&self) -> CompletionConfig { | 559 | pub fn completion(&self) -> CompletionConfig { |
@@ -556,7 +563,7 @@ impl Config { | |||
556 | && completion_item_edit_resolve(&self.caps), | 563 | && completion_item_edit_resolve(&self.caps), |
557 | add_call_parenthesis: self.data.completion_addCallParenthesis, | 564 | add_call_parenthesis: self.data.completion_addCallParenthesis, |
558 | add_call_argument_snippets: self.data.completion_addCallArgumentSnippets, | 565 | add_call_argument_snippets: self.data.completion_addCallArgumentSnippets, |
559 | merge: self.merge_behavior(), | 566 | insert_use: self.insert_use_config(), |
560 | snippet_cap: SnippetCap::new(try_or!( | 567 | snippet_cap: SnippetCap::new(try_or!( |
561 | self.caps | 568 | self.caps |
562 | .text_document | 569 | .text_document |
@@ -575,7 +582,11 @@ impl Config { | |||
575 | snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")), | 582 | snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")), |
576 | allowed: None, | 583 | allowed: None, |
577 | insert_use: InsertUseConfig { | 584 | insert_use: InsertUseConfig { |
578 | merge: self.merge_behavior(), | 585 | merge: match self.data.assist_importMergeBehavior { |
586 | MergeBehaviorDef::None => None, | ||
587 | MergeBehaviorDef::Full => Some(MergeBehavior::Full), | ||
588 | MergeBehaviorDef::Last => Some(MergeBehavior::Last), | ||
589 | }, | ||
579 | prefix_kind: match self.data.assist_importPrefix { | 590 | prefix_kind: match self.data.assist_importPrefix { |
580 | ImportPrefixDef::Plain => PrefixKind::Plain, | 591 | ImportPrefixDef::Plain => PrefixKind::Plain, |
581 | ImportPrefixDef::ByCrate => PrefixKind::ByCrate, | 592 | ImportPrefixDef::ByCrate => PrefixKind::ByCrate, |
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index dc67d19a7..1ff2d3fea 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -861,8 +861,9 @@ pub(crate) fn rename_error(err: RenameError) -> crate::LspError { | |||
861 | 861 | ||
862 | #[cfg(test)] | 862 | #[cfg(test)] |
863 | mod tests { | 863 | mod tests { |
864 | use hir::PrefixKind; | ||
864 | use ide::Analysis; | 865 | use ide::Analysis; |
865 | use ide_db::helpers::SnippetCap; | 866 | use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap}; |
866 | 867 | ||
867 | use super::*; | 868 | use super::*; |
868 | 869 | ||
@@ -887,7 +888,7 @@ mod tests { | |||
887 | add_call_parenthesis: true, | 888 | add_call_parenthesis: true, |
888 | add_call_argument_snippets: true, | 889 | add_call_argument_snippets: true, |
889 | snippet_cap: SnippetCap::new(true), | 890 | snippet_cap: SnippetCap::new(true), |
890 | merge: None, | 891 | insert_use: InsertUseConfig { merge: None, prefix_kind: PrefixKind::Plain }, |
891 | }, | 892 | }, |
892 | ide_db::base_db::FilePosition { file_id, offset }, | 893 | ide_db::base_db::FilePosition { file_id, offset }, |
893 | ) | 894 | ) |