diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-12-08 13:10:28 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-12-08 13:10:28 +0000 |
commit | 4d4f11925f793c45560c45c088d4b3139c2c171c (patch) | |
tree | f8c5e3c14a0bb55d4b435b8389bccf305975d39a /crates/completion/src | |
parent | 021e97ea03cf67ad7785ab39580e04bc69506b8c (diff) | |
parent | bf24cb3e8db94a84fb4a24c407797ab6ff5ee109 (diff) |
Merge #6706
6706: Move import text edit calculation into a completion resolve request r=matklad a=SomeoneToIgnore
Part of https://github.com/rust-analyzer/rust-analyzer/issues/6612 (presumably fixing it)
Part of https://github.com/rust-analyzer/rust-analyzer/issues/6366 (does not cover all possible resolve capabilities we can do)
Closes https://github.com/rust-analyzer/rust-analyzer/issues/6594
Further improves imports on completion performance by deferring the computations for import inserts.
To use the new mode, you have to have the experimental completions enabled and use the LSP 3.16-compliant client that reports `additionalTextEdits` in its `CompletionItemCapabilityResolveSupport` field in the client capabilities.
rust-analyzer VSCode extension does this already hence picks up the changes completely.
Performance implications are descrbed in: https://github.com/rust-analyzer/rust-analyzer/issues/6633#issuecomment-737295182
Co-authored-by: Kirill Bulatov <[email protected]>
Diffstat (limited to 'crates/completion/src')
-rw-r--r-- | crates/completion/src/completions/unqualified_path.rs | 92 | ||||
-rw-r--r-- | crates/completion/src/config.rs | 26 | ||||
-rw-r--r-- | crates/completion/src/item.rs | 64 | ||||
-rw-r--r-- | crates/completion/src/lib.rs | 47 | ||||
-rw-r--r-- | crates/completion/src/render.rs | 38 | ||||
-rw-r--r-- | crates/completion/src/render/enum_variant.rs | 6 | ||||
-rw-r--r-- | crates/completion/src/render/function.rs | 6 | ||||
-rw-r--r-- | crates/completion/src/render/macro_.rs | 6 | ||||
-rw-r--r-- | crates/completion/src/test_utils.rs | 11 |
9 files changed, 203 insertions, 93 deletions
diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs index 81691cd7f..4e4e2b36f 100644 --- a/crates/completion/src/completions/unqualified_path.rs +++ b/crates/completion/src/completions/unqualified_path.rs | |||
@@ -9,7 +9,7 @@ use test_utils::mark; | |||
9 | 9 | ||
10 | use crate::{ | 10 | use crate::{ |
11 | render::{render_resolution_with_import, RenderContext}, | 11 | render::{render_resolution_with_import, RenderContext}, |
12 | CompletionContext, Completions, | 12 | CompletionContext, Completions, ImportEdit, |
13 | }; | 13 | }; |
14 | 14 | ||
15 | pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { | 15 | pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { |
@@ -44,7 +44,7 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC | |||
44 | acc.add_resolution(ctx, name.to_string(), &res) | 44 | acc.add_resolution(ctx, name.to_string(), &res) |
45 | }); | 45 | }); |
46 | 46 | ||
47 | if ctx.config.enable_experimental_completions { | 47 | if ctx.config.enable_autoimport_completions && ctx.config.resolve_additional_edits_lazily() { |
48 | fuzzy_completion(acc, ctx).unwrap_or_default() | 48 | fuzzy_completion(acc, ctx).unwrap_or_default() |
49 | } | 49 | } |
50 | } | 50 | } |
@@ -73,19 +73,64 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T | |||
73 | } | 73 | } |
74 | } | 74 | } |
75 | 75 | ||
76 | // Feature: Fuzzy Completion and Autoimports | ||
77 | // | ||
78 | // When completing names in the current scope, proposes additional imports from other modules or crates, | ||
79 | // if they can be qualified in the scope and their name contains all symbols from the completion input | ||
80 | // (case-insensitive, in any order or places). | ||
81 | // | ||
82 | // ``` | ||
83 | // fn main() { | ||
84 | // pda<|> | ||
85 | // } | ||
86 | // # pub mod std { pub mod marker { pub struct PhantomData { } } } | ||
87 | // ``` | ||
88 | // -> | ||
89 | // ``` | ||
90 | // use std::marker::PhantomData; | ||
91 | // | ||
92 | // fn main() { | ||
93 | // PhantomData | ||
94 | // } | ||
95 | // # pub mod std { pub mod marker { pub struct PhantomData { } } } | ||
96 | // ``` | ||
97 | // | ||
98 | // .Fuzzy search details | ||
99 | // | ||
100 | // To avoid an excessive amount of the results returned, completion input is checked for inclusion in the identifiers only | ||
101 | // (i.e. in `HashMap` in the `std::collections::HashMap` path), also not in the module indentifiers. | ||
102 | // | ||
103 | // .Merge Behaviour | ||
104 | // | ||
105 | // It is possible to configure how use-trees are merged with the `importMergeBehaviour` setting. | ||
106 | // Mimics the corresponding behaviour of the `Auto Import` feature. | ||
107 | // | ||
108 | // .LSP and performance implications | ||
109 | // | ||
110 | // The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits` | ||
111 | // (case sensitive) resolve client capability in its client capabilities. | ||
112 | // This way the server is able to defer the costly computations, doing them for a selected completion item only. | ||
113 | // For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones, | ||
114 | // which might be slow ergo the feature is automatically disabled. | ||
115 | // | ||
116 | // .Feature toggle | ||
117 | // | ||
118 | // The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.enableAutoimportCompletions` flag. | ||
119 | // Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding | ||
120 | // capability enabled. | ||
76 | fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | 121 | fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { |
77 | let _p = profile::span("fuzzy_completion"); | 122 | let _p = profile::span("fuzzy_completion"); |
123 | let potential_import_name = ctx.token.to_string(); | ||
124 | |||
78 | let current_module = ctx.scope.module()?; | 125 | let current_module = ctx.scope.module()?; |
79 | let anchor = ctx.name_ref_syntax.as_ref()?; | 126 | let anchor = ctx.name_ref_syntax.as_ref()?; |
80 | let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; | 127 | let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; |
81 | 128 | ||
82 | let potential_import_name = ctx.token.to_string(); | ||
83 | |||
84 | let possible_imports = imports_locator::find_similar_imports( | 129 | let possible_imports = imports_locator::find_similar_imports( |
85 | &ctx.sema, | 130 | &ctx.sema, |
86 | ctx.krate?, | 131 | ctx.krate?, |
132 | Some(100), | ||
87 | &potential_import_name, | 133 | &potential_import_name, |
88 | 50, | ||
89 | true, | 134 | true, |
90 | ) | 135 | ) |
91 | .filter_map(|import_candidate| { | 136 | .filter_map(|import_candidate| { |
@@ -99,13 +144,14 @@ fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<() | |||
99 | }) | 144 | }) |
100 | }) | 145 | }) |
101 | .filter(|(mod_path, _)| mod_path.len() > 1) | 146 | .filter(|(mod_path, _)| mod_path.len() > 1) |
102 | .take(20) | ||
103 | .filter_map(|(import_path, definition)| { | 147 | .filter_map(|(import_path, definition)| { |
104 | render_resolution_with_import( | 148 | render_resolution_with_import( |
105 | RenderContext::new(ctx), | 149 | RenderContext::new(ctx), |
106 | import_path.clone(), | 150 | ImportEdit { |
107 | import_scope.clone(), | 151 | import_path: import_path.clone(), |
108 | ctx.config.merge, | 152 | import_scope: import_scope.clone(), |
153 | merge_behaviour: ctx.config.merge, | ||
154 | }, | ||
109 | &definition, | 155 | &definition, |
110 | ) | 156 | ) |
111 | }); | 157 | }); |
@@ -120,8 +166,8 @@ mod tests { | |||
120 | use test_utils::mark; | 166 | use test_utils::mark; |
121 | 167 | ||
122 | use crate::{ | 168 | use crate::{ |
123 | test_utils::{check_edit, completion_list}, | 169 | test_utils::{check_edit, check_edit_with_config, completion_list}, |
124 | CompletionKind, | 170 | CompletionConfig, CompletionKind, |
125 | }; | 171 | }; |
126 | 172 | ||
127 | fn check(ra_fixture: &str, expect: Expect) { | 173 | fn check(ra_fixture: &str, expect: Expect) { |
@@ -730,7 +776,13 @@ impl My<|> | |||
730 | 776 | ||
731 | #[test] | 777 | #[test] |
732 | fn function_fuzzy_completion() { | 778 | fn function_fuzzy_completion() { |
733 | check_edit( | 779 | let mut completion_config = CompletionConfig::default(); |
780 | completion_config | ||
781 | .active_resolve_capabilities | ||
782 | .insert(crate::CompletionResolveCapability::AdditionalTextEdits); | ||
783 | |||
784 | check_edit_with_config( | ||
785 | completion_config, | ||
734 | "stdin", | 786 | "stdin", |
735 | r#" | 787 | r#" |
736 | //- /lib.rs crate:dep | 788 | //- /lib.rs crate:dep |
@@ -755,7 +807,13 @@ fn main() { | |||
755 | 807 | ||
756 | #[test] | 808 | #[test] |
757 | fn macro_fuzzy_completion() { | 809 | fn macro_fuzzy_completion() { |
758 | check_edit( | 810 | let mut completion_config = CompletionConfig::default(); |
811 | completion_config | ||
812 | .active_resolve_capabilities | ||
813 | .insert(crate::CompletionResolveCapability::AdditionalTextEdits); | ||
814 | |||
815 | check_edit_with_config( | ||
816 | completion_config, | ||
759 | "macro_with_curlies!", | 817 | "macro_with_curlies!", |
760 | r#" | 818 | r#" |
761 | //- /lib.rs crate:dep | 819 | //- /lib.rs crate:dep |
@@ -782,7 +840,13 @@ fn main() { | |||
782 | 840 | ||
783 | #[test] | 841 | #[test] |
784 | fn struct_fuzzy_completion() { | 842 | fn struct_fuzzy_completion() { |
785 | check_edit( | 843 | let mut completion_config = CompletionConfig::default(); |
844 | completion_config | ||
845 | .active_resolve_capabilities | ||
846 | .insert(crate::CompletionResolveCapability::AdditionalTextEdits); | ||
847 | |||
848 | check_edit_with_config( | ||
849 | completion_config, | ||
786 | "ThirdStruct", | 850 | "ThirdStruct", |
787 | r#" | 851 | r#" |
788 | //- /lib.rs crate:dep | 852 | //- /lib.rs crate:dep |
diff --git a/crates/completion/src/config.rs b/crates/completion/src/config.rs index 654a76f7b..5175b9d69 100644 --- a/crates/completion/src/config.rs +++ b/crates/completion/src/config.rs | |||
@@ -5,21 +5,42 @@ | |||
5 | //! completions if we are allowed to. | 5 | //! completions if we are allowed to. |
6 | 6 | ||
7 | use ide_db::helpers::insert_use::MergeBehaviour; | 7 | use ide_db::helpers::insert_use::MergeBehaviour; |
8 | use rustc_hash::FxHashSet; | ||
8 | 9 | ||
9 | #[derive(Clone, Debug, PartialEq, Eq)] | 10 | #[derive(Clone, Debug, PartialEq, Eq)] |
10 | pub struct CompletionConfig { | 11 | pub struct CompletionConfig { |
11 | pub enable_postfix_completions: bool, | 12 | pub enable_postfix_completions: bool, |
12 | pub enable_experimental_completions: bool, | 13 | pub enable_autoimport_completions: bool, |
13 | pub add_call_parenthesis: bool, | 14 | pub add_call_parenthesis: bool, |
14 | pub add_call_argument_snippets: bool, | 15 | pub add_call_argument_snippets: bool, |
15 | pub snippet_cap: Option<SnippetCap>, | 16 | pub snippet_cap: Option<SnippetCap>, |
16 | pub merge: Option<MergeBehaviour>, | 17 | pub merge: Option<MergeBehaviour>, |
18 | /// A set of capabilities, enabled on the client and supported on the server. | ||
19 | pub active_resolve_capabilities: FxHashSet<CompletionResolveCapability>, | ||
20 | } | ||
21 | |||
22 | /// A resolve capability, supported on the server. | ||
23 | /// If the client registers any completion resolve capabilities, | ||
24 | /// the server is able to render completion items' corresponding fields later, | ||
25 | /// not during an initial completion item request. | ||
26 | /// See https://github.com/rust-analyzer/rust-analyzer/issues/6366 for more details. | ||
27 | #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] | ||
28 | pub enum CompletionResolveCapability { | ||
29 | Documentation, | ||
30 | Detail, | ||
31 | AdditionalTextEdits, | ||
17 | } | 32 | } |
18 | 33 | ||
19 | impl CompletionConfig { | 34 | impl CompletionConfig { |
20 | pub fn allow_snippets(&mut self, yes: bool) { | 35 | pub fn allow_snippets(&mut self, yes: bool) { |
21 | self.snippet_cap = if yes { Some(SnippetCap { _private: () }) } else { None } | 36 | self.snippet_cap = if yes { Some(SnippetCap { _private: () }) } else { None } |
22 | } | 37 | } |
38 | |||
39 | /// Whether the completions' additional edits are calculated when sending an initional completions list | ||
40 | /// or later, in a separate resolve request. | ||
41 | pub fn resolve_additional_edits_lazily(&self) -> bool { | ||
42 | self.active_resolve_capabilities.contains(&CompletionResolveCapability::AdditionalTextEdits) | ||
43 | } | ||
23 | } | 44 | } |
24 | 45 | ||
25 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | 46 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
@@ -31,11 +52,12 @@ impl Default for CompletionConfig { | |||
31 | fn default() -> Self { | 52 | fn default() -> Self { |
32 | CompletionConfig { | 53 | CompletionConfig { |
33 | enable_postfix_completions: true, | 54 | enable_postfix_completions: true, |
34 | enable_experimental_completions: true, | 55 | enable_autoimport_completions: true, |
35 | add_call_parenthesis: true, | 56 | add_call_parenthesis: true, |
36 | add_call_argument_snippets: true, | 57 | add_call_argument_snippets: true, |
37 | snippet_cap: Some(SnippetCap { _private: () }), | 58 | snippet_cap: Some(SnippetCap { _private: () }), |
38 | merge: Some(MergeBehaviour::Full), | 59 | merge: Some(MergeBehaviour::Full), |
60 | active_resolve_capabilities: FxHashSet::default(), | ||
39 | } | 61 | } |
40 | } | 62 | } |
41 | } | 63 | } |
diff --git a/crates/completion/src/item.rs b/crates/completion/src/item.rs index e85549fef..bd94402d7 100644 --- a/crates/completion/src/item.rs +++ b/crates/completion/src/item.rs | |||
@@ -15,6 +15,7 @@ use crate::config::SnippetCap; | |||
15 | /// `CompletionItem` describes a single completion variant in the editor pop-up. | 15 | /// `CompletionItem` describes a single completion variant in the editor pop-up. |
16 | /// It is basically a POD with various properties. To construct a | 16 | /// It is basically a POD with various properties. To construct a |
17 | /// `CompletionItem`, use `new` method and the `Builder` struct. | 17 | /// `CompletionItem`, use `new` method and the `Builder` struct. |
18 | #[derive(Clone)] | ||
18 | pub struct CompletionItem { | 19 | pub struct CompletionItem { |
19 | /// Used only internally in tests, to check only specific kind of | 20 | /// Used only internally in tests, to check only specific kind of |
20 | /// completion (postfix, keyword, reference, etc). | 21 | /// completion (postfix, keyword, reference, etc). |
@@ -65,6 +66,9 @@ pub struct CompletionItem { | |||
65 | /// Indicates that a reference or mutable reference to this variable is a | 66 | /// Indicates that a reference or mutable reference to this variable is a |
66 | /// possible match. | 67 | /// possible match. |
67 | ref_match: Option<(Mutability, CompletionScore)>, | 68 | ref_match: Option<(Mutability, CompletionScore)>, |
69 | |||
70 | /// The import data to add to completion's edits. | ||
71 | import_to_add: Option<ImportEdit>, | ||
68 | } | 72 | } |
69 | 73 | ||
70 | // We use custom debug for CompletionItem to make snapshot tests more readable. | 74 | // We use custom debug for CompletionItem to make snapshot tests more readable. |
@@ -256,14 +260,37 @@ impl CompletionItem { | |||
256 | pub fn ref_match(&self) -> Option<(Mutability, CompletionScore)> { | 260 | pub fn ref_match(&self) -> Option<(Mutability, CompletionScore)> { |
257 | self.ref_match | 261 | self.ref_match |
258 | } | 262 | } |
263 | |||
264 | pub fn import_to_add(&self) -> Option<&ImportEdit> { | ||
265 | self.import_to_add.as_ref() | ||
266 | } | ||
259 | } | 267 | } |
260 | 268 | ||
261 | /// An extra import to add after the completion is applied. | 269 | /// An extra import to add after the completion is applied. |
262 | #[derive(Clone)] | 270 | #[derive(Debug, Clone)] |
263 | pub(crate) struct ImportToAdd { | 271 | pub struct ImportEdit { |
264 | pub(crate) import_path: ModPath, | 272 | pub import_path: ModPath, |
265 | pub(crate) import_scope: ImportScope, | 273 | pub import_scope: ImportScope, |
266 | pub(crate) merge_behaviour: Option<MergeBehaviour>, | 274 | pub merge_behaviour: Option<MergeBehaviour>, |
275 | } | ||
276 | |||
277 | impl ImportEdit { | ||
278 | /// Attempts to insert the import to the given scope, producing a text edit. | ||
279 | /// May return no edit in edge cases, such as scope already containing the import. | ||
280 | pub fn to_text_edit(&self) -> Option<TextEdit> { | ||
281 | let _p = profile::span("ImportEdit::to_text_edit"); | ||
282 | |||
283 | let rewriter = insert_use::insert_use( | ||
284 | &self.import_scope, | ||
285 | mod_path_to_ast(&self.import_path), | ||
286 | self.merge_behaviour, | ||
287 | ); | ||
288 | let old_ast = rewriter.rewrite_root()?; | ||
289 | let mut import_insert = TextEdit::builder(); | ||
290 | algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert); | ||
291 | |||
292 | Some(import_insert.finish()) | ||
293 | } | ||
267 | } | 294 | } |
268 | 295 | ||
269 | /// A helper to make `CompletionItem`s. | 296 | /// A helper to make `CompletionItem`s. |
@@ -272,7 +299,7 @@ pub(crate) struct ImportToAdd { | |||
272 | pub(crate) struct Builder { | 299 | pub(crate) struct Builder { |
273 | source_range: TextRange, | 300 | source_range: TextRange, |
274 | completion_kind: CompletionKind, | 301 | completion_kind: CompletionKind, |
275 | import_to_add: Option<ImportToAdd>, | 302 | import_to_add: Option<ImportEdit>, |
276 | label: String, | 303 | label: String, |
277 | insert_text: Option<String>, | 304 | insert_text: Option<String>, |
278 | insert_text_format: InsertTextFormat, | 305 | insert_text_format: InsertTextFormat, |
@@ -294,11 +321,9 @@ impl Builder { | |||
294 | let mut label = self.label; | 321 | let mut label = self.label; |
295 | let mut lookup = self.lookup; | 322 | let mut lookup = self.lookup; |
296 | let mut insert_text = self.insert_text; | 323 | let mut insert_text = self.insert_text; |
297 | let mut text_edits = TextEdit::builder(); | ||
298 | 324 | ||
299 | if let Some(import_data) = self.import_to_add { | 325 | if let Some(import_to_add) = self.import_to_add.as_ref() { |
300 | let import = mod_path_to_ast(&import_data.import_path); | 326 | let mut import_path_without_last_segment = import_to_add.import_path.to_owned(); |
301 | let mut import_path_without_last_segment = import_data.import_path; | ||
302 | let _ = import_path_without_last_segment.segments.pop(); | 327 | let _ = import_path_without_last_segment.segments.pop(); |
303 | 328 | ||
304 | if !import_path_without_last_segment.segments.is_empty() { | 329 | if !import_path_without_last_segment.segments.is_empty() { |
@@ -310,32 +335,20 @@ impl Builder { | |||
310 | } | 335 | } |
311 | label = format!("{}::{}", import_path_without_last_segment, label); | 336 | label = format!("{}::{}", import_path_without_last_segment, label); |
312 | } | 337 | } |
313 | |||
314 | let rewriter = insert_use::insert_use( | ||
315 | &import_data.import_scope, | ||
316 | import, | ||
317 | import_data.merge_behaviour, | ||
318 | ); | ||
319 | if let Some(old_ast) = rewriter.rewrite_root() { | ||
320 | algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut text_edits); | ||
321 | } | ||
322 | } | 338 | } |
323 | 339 | ||
324 | let original_edit = match self.text_edit { | 340 | let text_edit = match self.text_edit { |
325 | Some(it) => it, | 341 | Some(it) => it, |
326 | None => { | 342 | None => { |
327 | TextEdit::replace(self.source_range, insert_text.unwrap_or_else(|| label.clone())) | 343 | TextEdit::replace(self.source_range, insert_text.unwrap_or_else(|| label.clone())) |
328 | } | 344 | } |
329 | }; | 345 | }; |
330 | 346 | ||
331 | let mut resulting_edit = text_edits.finish(); | ||
332 | resulting_edit.union(original_edit).expect("Failed to unite text edits"); | ||
333 | |||
334 | CompletionItem { | 347 | CompletionItem { |
335 | source_range: self.source_range, | 348 | source_range: self.source_range, |
336 | label, | 349 | label, |
337 | insert_text_format: self.insert_text_format, | 350 | insert_text_format: self.insert_text_format, |
338 | text_edit: resulting_edit, | 351 | text_edit, |
339 | detail: self.detail, | 352 | detail: self.detail, |
340 | documentation: self.documentation, | 353 | documentation: self.documentation, |
341 | lookup, | 354 | lookup, |
@@ -345,6 +358,7 @@ impl Builder { | |||
345 | trigger_call_info: self.trigger_call_info.unwrap_or(false), | 358 | trigger_call_info: self.trigger_call_info.unwrap_or(false), |
346 | score: self.score, | 359 | score: self.score, |
347 | ref_match: self.ref_match, | 360 | ref_match: self.ref_match, |
361 | import_to_add: self.import_to_add, | ||
348 | } | 362 | } |
349 | } | 363 | } |
350 | pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { | 364 | pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { |
@@ -407,7 +421,7 @@ impl Builder { | |||
407 | self.trigger_call_info = Some(true); | 421 | self.trigger_call_info = Some(true); |
408 | self | 422 | self |
409 | } | 423 | } |
410 | pub(crate) fn add_import(mut self, import_to_add: Option<ImportToAdd>) -> Builder { | 424 | pub(crate) fn add_import(mut self, import_to_add: Option<ImportEdit>) -> Builder { |
411 | self.import_to_add = import_to_add; | 425 | self.import_to_add = import_to_add; |
412 | self | 426 | self |
413 | } | 427 | } |
diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs index 1ec2e9be7..f60f87243 100644 --- a/crates/completion/src/lib.rs +++ b/crates/completion/src/lib.rs | |||
@@ -11,14 +11,17 @@ mod render; | |||
11 | 11 | ||
12 | mod completions; | 12 | mod completions; |
13 | 13 | ||
14 | use ide_db::base_db::FilePosition; | 14 | use ide_db::{ |
15 | use ide_db::RootDatabase; | 15 | base_db::FilePosition, helpers::insert_use::ImportScope, imports_locator, RootDatabase, |
16 | }; | ||
17 | use syntax::AstNode; | ||
18 | use text_edit::TextEdit; | ||
16 | 19 | ||
17 | use crate::{completions::Completions, context::CompletionContext, item::CompletionKind}; | 20 | use crate::{completions::Completions, context::CompletionContext, item::CompletionKind}; |
18 | 21 | ||
19 | pub use crate::{ | 22 | pub use crate::{ |
20 | config::CompletionConfig, | 23 | config::{CompletionConfig, CompletionResolveCapability}, |
21 | item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat}, | 24 | item::{CompletionItem, CompletionItemKind, CompletionScore, ImportEdit, InsertTextFormat}, |
22 | }; | 25 | }; |
23 | 26 | ||
24 | //FIXME: split the following feature into fine-grained features. | 27 | //FIXME: split the following feature into fine-grained features. |
@@ -70,12 +73,9 @@ pub use crate::{ | |||
70 | // } | 73 | // } |
71 | // ``` | 74 | // ``` |
72 | // | 75 | // |
73 | // And experimental completions, enabled with the `rust-analyzer.completion.enableExperimental` setting. | 76 | // And the auto import completions, enabled with the `rust-analyzer.completion.autoimport.enable` setting and the corresponding LSP client capabilities. |
74 | // This flag enables or disables: | 77 | // Those are the additional completion options with automatic `use` import and options from all project importable items, |
75 | // | 78 | // fuzzy matched agains the completion imput. |
76 | // - Auto import: additional completion options with automatic `use` import and options from all project importable items, matched for the input | ||
77 | // | ||
78 | // Experimental completions might cause issues with performance and completion list look. | ||
79 | 79 | ||
80 | /// Main entry point for completion. We run completion as a two-phase process. | 80 | /// Main entry point for completion. We run completion as a two-phase process. |
81 | /// | 81 | /// |
@@ -131,6 +131,33 @@ pub fn completions( | |||
131 | Some(acc) | 131 | Some(acc) |
132 | } | 132 | } |
133 | 133 | ||
134 | /// Resolves additional completion data at the position given. | ||
135 | pub fn resolve_completion_edits( | ||
136 | db: &RootDatabase, | ||
137 | config: &CompletionConfig, | ||
138 | position: FilePosition, | ||
139 | full_import_path: &str, | ||
140 | imported_name: &str, | ||
141 | ) -> Option<Vec<TextEdit>> { | ||
142 | let ctx = CompletionContext::new(db, position, config)?; | ||
143 | let anchor = ctx.name_ref_syntax.as_ref()?; | ||
144 | let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; | ||
145 | |||
146 | let current_module = ctx.sema.scope(anchor.syntax()).module()?; | ||
147 | let current_crate = current_module.krate(); | ||
148 | |||
149 | let import_path = imports_locator::find_exact_imports(&ctx.sema, current_crate, imported_name) | ||
150 | .filter_map(|candidate| { | ||
151 | let item: hir::ItemInNs = candidate.either(Into::into, Into::into); | ||
152 | current_module.find_use_path(db, item) | ||
153 | }) | ||
154 | .find(|mod_path| mod_path.to_string() == full_import_path)?; | ||
155 | |||
156 | ImportEdit { import_path, import_scope, merge_behaviour: config.merge } | ||
157 | .to_text_edit() | ||
158 | .map(|edit| vec![edit]) | ||
159 | } | ||
160 | |||
134 | #[cfg(test)] | 161 | #[cfg(test)] |
135 | mod tests { | 162 | mod tests { |
136 | use crate::config::CompletionConfig; | 163 | use crate::config::CompletionConfig; |
diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs index 504757a6a..b940388df 100644 --- a/crates/completion/src/render.rs +++ b/crates/completion/src/render.rs | |||
@@ -9,14 +9,13 @@ pub(crate) mod type_alias; | |||
9 | 9 | ||
10 | mod builder_ext; | 10 | mod builder_ext; |
11 | 11 | ||
12 | use hir::{Documentation, HasAttrs, HirDisplay, ModPath, Mutability, ScopeDef, Type}; | 12 | use hir::{Documentation, HasAttrs, HirDisplay, Mutability, ScopeDef, Type}; |
13 | use ide_db::helpers::insert_use::{ImportScope, MergeBehaviour}; | ||
14 | use ide_db::RootDatabase; | 13 | use ide_db::RootDatabase; |
15 | use syntax::TextRange; | 14 | use syntax::TextRange; |
16 | use test_utils::mark; | 15 | use test_utils::mark; |
17 | 16 | ||
18 | use crate::{ | 17 | use crate::{ |
19 | config::SnippetCap, item::ImportToAdd, CompletionContext, CompletionItem, CompletionItemKind, | 18 | config::SnippetCap, item::ImportEdit, CompletionContext, CompletionItem, CompletionItemKind, |
20 | CompletionKind, CompletionScore, | 19 | CompletionKind, CompletionScore, |
21 | }; | 20 | }; |
22 | 21 | ||
@@ -48,15 +47,12 @@ pub(crate) fn render_resolution<'a>( | |||
48 | 47 | ||
49 | pub(crate) fn render_resolution_with_import<'a>( | 48 | pub(crate) fn render_resolution_with_import<'a>( |
50 | ctx: RenderContext<'a>, | 49 | ctx: RenderContext<'a>, |
51 | import_path: ModPath, | 50 | import_edit: ImportEdit, |
52 | import_scope: ImportScope, | ||
53 | merge_behaviour: Option<MergeBehaviour>, | ||
54 | resolution: &ScopeDef, | 51 | resolution: &ScopeDef, |
55 | ) -> Option<CompletionItem> { | 52 | ) -> Option<CompletionItem> { |
56 | let local_name = import_path.segments.last()?.to_string(); | ||
57 | Render::new(ctx).render_resolution( | 53 | Render::new(ctx).render_resolution( |
58 | local_name, | 54 | import_edit.import_path.segments.last()?.to_string(), |
59 | Some(ImportToAdd { import_path, import_scope, merge_behaviour }), | 55 | Some(import_edit), |
60 | resolution, | 56 | resolution, |
61 | ) | 57 | ) |
62 | } | 58 | } |
@@ -147,7 +143,7 @@ impl<'a> Render<'a> { | |||
147 | fn render_resolution( | 143 | fn render_resolution( |
148 | self, | 144 | self, |
149 | local_name: String, | 145 | local_name: String, |
150 | import_to_add: Option<ImportToAdd>, | 146 | import_to_add: Option<ImportEdit>, |
151 | resolution: &ScopeDef, | 147 | resolution: &ScopeDef, |
152 | ) -> Option<CompletionItem> { | 148 | ) -> Option<CompletionItem> { |
153 | let _p = profile::span("render_resolution"); | 149 | let _p = profile::span("render_resolution"); |
@@ -451,28 +447,6 @@ fn main() { let _: m::Spam = S<|> } | |||
451 | kind: Module, | 447 | kind: Module, |
452 | }, | 448 | }, |
453 | CompletionItem { | 449 | CompletionItem { |
454 | label: "m::Spam", | ||
455 | source_range: 75..76, | ||
456 | text_edit: TextEdit { | ||
457 | indels: [ | ||
458 | Indel { | ||
459 | insert: "use m::Spam;", | ||
460 | delete: 0..0, | ||
461 | }, | ||
462 | Indel { | ||
463 | insert: "\n\n", | ||
464 | delete: 0..0, | ||
465 | }, | ||
466 | Indel { | ||
467 | insert: "Spam", | ||
468 | delete: 75..76, | ||
469 | }, | ||
470 | ], | ||
471 | }, | ||
472 | kind: Enum, | ||
473 | lookup: "Spam", | ||
474 | }, | ||
475 | CompletionItem { | ||
476 | label: "m::Spam::Foo", | 450 | label: "m::Spam::Foo", |
477 | source_range: 75..76, | 451 | source_range: 75..76, |
478 | delete: 75..76, | 452 | delete: 75..76, |
diff --git a/crates/completion/src/render/enum_variant.rs b/crates/completion/src/render/enum_variant.rs index f4bd02f25..8e0fea6c0 100644 --- a/crates/completion/src/render/enum_variant.rs +++ b/crates/completion/src/render/enum_variant.rs | |||
@@ -5,13 +5,13 @@ use itertools::Itertools; | |||
5 | use test_utils::mark; | 5 | use test_utils::mark; |
6 | 6 | ||
7 | use crate::{ | 7 | use crate::{ |
8 | item::{CompletionItem, CompletionItemKind, CompletionKind, ImportToAdd}, | 8 | item::{CompletionItem, CompletionItemKind, CompletionKind, ImportEdit}, |
9 | render::{builder_ext::Params, RenderContext}, | 9 | render::{builder_ext::Params, RenderContext}, |
10 | }; | 10 | }; |
11 | 11 | ||
12 | pub(crate) fn render_enum_variant<'a>( | 12 | pub(crate) fn render_enum_variant<'a>( |
13 | ctx: RenderContext<'a>, | 13 | ctx: RenderContext<'a>, |
14 | import_to_add: Option<ImportToAdd>, | 14 | import_to_add: Option<ImportEdit>, |
15 | local_name: Option<String>, | 15 | local_name: Option<String>, |
16 | variant: hir::EnumVariant, | 16 | variant: hir::EnumVariant, |
17 | path: Option<ModPath>, | 17 | path: Option<ModPath>, |
@@ -62,7 +62,7 @@ impl<'a> EnumVariantRender<'a> { | |||
62 | } | 62 | } |
63 | } | 63 | } |
64 | 64 | ||
65 | fn render(self, import_to_add: Option<ImportToAdd>) -> CompletionItem { | 65 | fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem { |
66 | let mut builder = CompletionItem::new( | 66 | let mut builder = CompletionItem::new( |
67 | CompletionKind::Reference, | 67 | CompletionKind::Reference, |
68 | self.ctx.source_range(), | 68 | self.ctx.source_range(), |
diff --git a/crates/completion/src/render/function.rs b/crates/completion/src/render/function.rs index 00e3eb203..d16005249 100644 --- a/crates/completion/src/render/function.rs +++ b/crates/completion/src/render/function.rs | |||
@@ -5,13 +5,13 @@ use syntax::{ast::Fn, display::function_declaration}; | |||
5 | use test_utils::mark; | 5 | use test_utils::mark; |
6 | 6 | ||
7 | use crate::{ | 7 | use crate::{ |
8 | item::{CompletionItem, CompletionItemKind, CompletionKind, ImportToAdd}, | 8 | item::{CompletionItem, CompletionItemKind, CompletionKind, ImportEdit}, |
9 | render::{builder_ext::Params, RenderContext}, | 9 | render::{builder_ext::Params, RenderContext}, |
10 | }; | 10 | }; |
11 | 11 | ||
12 | pub(crate) fn render_fn<'a>( | 12 | pub(crate) fn render_fn<'a>( |
13 | ctx: RenderContext<'a>, | 13 | ctx: RenderContext<'a>, |
14 | import_to_add: Option<ImportToAdd>, | 14 | import_to_add: Option<ImportEdit>, |
15 | local_name: Option<String>, | 15 | local_name: Option<String>, |
16 | fn_: hir::Function, | 16 | fn_: hir::Function, |
17 | ) -> CompletionItem { | 17 | ) -> CompletionItem { |
@@ -39,7 +39,7 @@ impl<'a> FunctionRender<'a> { | |||
39 | FunctionRender { ctx, name, func: fn_, ast_node } | 39 | FunctionRender { ctx, name, func: fn_, ast_node } |
40 | } | 40 | } |
41 | 41 | ||
42 | fn render(self, import_to_add: Option<ImportToAdd>) -> CompletionItem { | 42 | fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem { |
43 | let params = self.params(); | 43 | let params = self.params(); |
44 | CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), self.name.clone()) | 44 | CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), self.name.clone()) |
45 | .kind(self.kind()) | 45 | .kind(self.kind()) |
diff --git a/crates/completion/src/render/macro_.rs b/crates/completion/src/render/macro_.rs index b4ab32c6e..eb3209bee 100644 --- a/crates/completion/src/render/macro_.rs +++ b/crates/completion/src/render/macro_.rs | |||
@@ -5,13 +5,13 @@ use syntax::display::macro_label; | |||
5 | use test_utils::mark; | 5 | use test_utils::mark; |
6 | 6 | ||
7 | use crate::{ | 7 | use crate::{ |
8 | item::{CompletionItem, CompletionItemKind, CompletionKind, ImportToAdd}, | 8 | item::{CompletionItem, CompletionItemKind, CompletionKind, ImportEdit}, |
9 | render::RenderContext, | 9 | render::RenderContext, |
10 | }; | 10 | }; |
11 | 11 | ||
12 | pub(crate) fn render_macro<'a>( | 12 | pub(crate) fn render_macro<'a>( |
13 | ctx: RenderContext<'a>, | 13 | ctx: RenderContext<'a>, |
14 | import_to_add: Option<ImportToAdd>, | 14 | import_to_add: Option<ImportEdit>, |
15 | name: String, | 15 | name: String, |
16 | macro_: hir::MacroDef, | 16 | macro_: hir::MacroDef, |
17 | ) -> Option<CompletionItem> { | 17 | ) -> Option<CompletionItem> { |
@@ -38,7 +38,7 @@ impl<'a> MacroRender<'a> { | |||
38 | MacroRender { ctx, name, macro_, docs, bra, ket } | 38 | MacroRender { ctx, name, macro_, docs, bra, ket } |
39 | } | 39 | } |
40 | 40 | ||
41 | fn render(&self, import_to_add: Option<ImportToAdd>) -> Option<CompletionItem> { | 41 | fn render(&self, import_to_add: Option<ImportEdit>) -> Option<CompletionItem> { |
42 | // FIXME: Currently proc-macro do not have ast-node, | 42 | // FIXME: Currently proc-macro do not have ast-node, |
43 | // such that it does not have source | 43 | // such that it does not have source |
44 | if self.macro_.is_proc_macro() { | 44 | if self.macro_.is_proc_macro() { |
diff --git a/crates/completion/src/test_utils.rs b/crates/completion/src/test_utils.rs index 4c1b1a839..25f5f4924 100644 --- a/crates/completion/src/test_utils.rs +++ b/crates/completion/src/test_utils.rs | |||
@@ -96,7 +96,16 @@ pub(crate) fn check_edit_with_config( | |||
96 | .collect_tuple() | 96 | .collect_tuple() |
97 | .unwrap_or_else(|| panic!("can't find {:?} completion in {:#?}", what, completions)); | 97 | .unwrap_or_else(|| panic!("can't find {:?} completion in {:#?}", what, completions)); |
98 | let mut actual = db.file_text(position.file_id).to_string(); | 98 | let mut actual = db.file_text(position.file_id).to_string(); |
99 | completion.text_edit().apply(&mut actual); | 99 | |
100 | let mut combined_edit = completion.text_edit().to_owned(); | ||
101 | if let Some(import_text_edit) = completion.import_to_add().and_then(|edit| edit.to_text_edit()) | ||
102 | { | ||
103 | combined_edit.union(import_text_edit).expect( | ||
104 | "Failed to apply completion resolve changes: change ranges overlap, but should not", | ||
105 | ) | ||
106 | } | ||
107 | |||
108 | combined_edit.apply(&mut actual); | ||
100 | assert_eq_text!(&ra_fixture_after, &actual) | 109 | assert_eq_text!(&ra_fixture_after, &actual) |
101 | } | 110 | } |
102 | 111 | ||