diff options
Diffstat (limited to 'crates/completion')
-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 | ||