aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/completion/src/completions/unqualified_path.rs92
-rw-r--r--crates/completion/src/config.rs26
-rw-r--r--crates/completion/src/item.rs64
-rw-r--r--crates/completion/src/lib.rs47
-rw-r--r--crates/completion/src/render.rs38
-rw-r--r--crates/completion/src/render/enum_variant.rs6
-rw-r--r--crates/completion/src/render/function.rs6
-rw-r--r--crates/completion/src/render/macro_.rs6
-rw-r--r--crates/completion/src/test_utils.rs11
-rw-r--r--crates/ide/src/lib.rs24
-rw-r--r--crates/ide_db/src/imports_locator.rs22
-rw-r--r--crates/rust-analyzer/src/caps.rs38
-rw-r--r--crates/rust-analyzer/src/config.rs10
-rw-r--r--crates/rust-analyzer/src/handlers.rs121
-rw-r--r--crates/rust-analyzer/src/lsp_utils.rs164
-rw-r--r--crates/rust-analyzer/src/main_loop.rs1
-rw-r--r--editors/code/package.json7
17 files changed, 565 insertions, 118 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
10use crate::{ 10use 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
15pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { 15pub(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.
76fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { 121fn 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
7use ide_db::helpers::insert_use::MergeBehaviour; 7use ide_db::helpers::insert_use::MergeBehaviour;
8use rustc_hash::FxHashSet;
8 9
9#[derive(Clone, Debug, PartialEq, Eq)] 10#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct CompletionConfig { 11pub 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)]
28pub enum CompletionResolveCapability {
29 Documentation,
30 Detail,
31 AdditionalTextEdits,
17} 32}
18 33
19impl CompletionConfig { 34impl 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)]
18pub struct CompletionItem { 19pub 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)]
263pub(crate) struct ImportToAdd { 271pub 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
277impl 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 {
272pub(crate) struct Builder { 299pub(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
12mod completions; 12mod completions;
13 13
14use ide_db::base_db::FilePosition; 14use ide_db::{
15use ide_db::RootDatabase; 15 base_db::FilePosition, helpers::insert_use::ImportScope, imports_locator, RootDatabase,
16};
17use syntax::AstNode;
18use text_edit::TextEdit;
16 19
17use crate::{completions::Completions, context::CompletionContext, item::CompletionKind}; 20use crate::{completions::Completions, context::CompletionContext, item::CompletionKind};
18 21
19pub use crate::{ 22pub 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.
135pub 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)]
135mod tests { 162mod 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
10mod builder_ext; 10mod builder_ext;
11 11
12use hir::{Documentation, HasAttrs, HirDisplay, ModPath, Mutability, ScopeDef, Type}; 12use hir::{Documentation, HasAttrs, HirDisplay, Mutability, ScopeDef, Type};
13use ide_db::helpers::insert_use::{ImportScope, MergeBehaviour};
14use ide_db::RootDatabase; 13use ide_db::RootDatabase;
15use syntax::TextRange; 14use syntax::TextRange;
16use test_utils::mark; 15use test_utils::mark;
17 16
18use crate::{ 17use 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
49pub(crate) fn render_resolution_with_import<'a>( 48pub(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;
5use test_utils::mark; 5use test_utils::mark;
6 6
7use crate::{ 7use 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
12pub(crate) fn render_enum_variant<'a>( 12pub(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};
5use test_utils::mark; 5use test_utils::mark;
6 6
7use crate::{ 7use 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
12pub(crate) fn render_fn<'a>( 12pub(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;
5use test_utils::mark; 5use test_utils::mark;
6 6
7use crate::{ 7use crate::{
8 item::{CompletionItem, CompletionItemKind, CompletionKind, ImportToAdd}, 8 item::{CompletionItem, CompletionItemKind, CompletionKind, ImportEdit},
9 render::RenderContext, 9 render::RenderContext,
10}; 10};
11 11
12pub(crate) fn render_macro<'a>( 12pub(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
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 5244bdd61..71068cac2 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -80,7 +80,8 @@ pub use crate::{
80 }, 80 },
81}; 81};
82pub use completion::{ 82pub use completion::{
83 CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat, 83 CompletionConfig, CompletionItem, CompletionItemKind, CompletionResolveCapability,
84 CompletionScore, ImportEdit, InsertTextFormat,
84}; 85};
85pub use ide_db::{ 86pub use ide_db::{
86 call_info::CallInfo, 87 call_info::CallInfo,
@@ -468,6 +469,27 @@ impl Analysis {
468 self.with_db(|db| completion::completions(db, config, position).map(Into::into)) 469 self.with_db(|db| completion::completions(db, config, position).map(Into::into))
469 } 470 }
470 471
472 /// Resolves additional completion data at the position given.
473 pub fn resolve_completion_edits(
474 &self,
475 config: &CompletionConfig,
476 position: FilePosition,
477 full_import_path: &str,
478 imported_name: &str,
479 ) -> Cancelable<Vec<TextEdit>> {
480 Ok(self
481 .with_db(|db| {
482 completion::resolve_completion_edits(
483 db,
484 config,
485 position,
486 full_import_path,
487 imported_name,
488 )
489 })?
490 .unwrap_or_default())
491 }
492
471 /// Computes resolved assists with source changes for the given position. 493 /// Computes resolved assists with source changes for the given position.
472 pub fn resolved_assists( 494 pub fn resolved_assists(
473 &self, 495 &self,
diff --git a/crates/ide_db/src/imports_locator.rs b/crates/ide_db/src/imports_locator.rs
index 09046d3c3..b2980a5d6 100644
--- a/crates/ide_db/src/imports_locator.rs
+++ b/crates/ide_db/src/imports_locator.rs
@@ -34,27 +34,25 @@ pub fn find_exact_imports<'a>(
34pub fn find_similar_imports<'a>( 34pub fn find_similar_imports<'a>(
35 sema: &Semantics<'a, RootDatabase>, 35 sema: &Semantics<'a, RootDatabase>,
36 krate: Crate, 36 krate: Crate,
37 limit: Option<usize>,
37 name_to_import: &str, 38 name_to_import: &str,
38 limit: usize,
39 ignore_modules: bool, 39 ignore_modules: bool,
40) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> { 40) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> {
41 let _p = profile::span("find_similar_imports"); 41 let _p = profile::span("find_similar_imports");
42 42
43 let mut external_query = import_map::Query::new(name_to_import).limit(limit); 43 let mut external_query = import_map::Query::new(name_to_import);
44 if ignore_modules { 44 if ignore_modules {
45 external_query = external_query.exclude_import_kind(import_map::ImportKind::Module); 45 external_query = external_query.exclude_import_kind(import_map::ImportKind::Module);
46 } 46 }
47 47
48 find_imports( 48 let mut local_query = symbol_index::Query::new(name_to_import.to_string());
49 sema, 49
50 krate, 50 if let Some(limit) = limit {
51 { 51 local_query.limit(limit);
52 let mut local_query = symbol_index::Query::new(name_to_import.to_string()); 52 external_query = external_query.limit(limit);
53 local_query.limit(limit); 53 }
54 local_query 54
55 }, 55 find_imports(sema, krate, local_query, external_query)
56 external_query,
57 )
58} 56}
59 57
60fn find_imports<'a>( 58fn find_imports<'a>(
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs
index c7203451c..5e4c22bc5 100644
--- a/crates/rust-analyzer/src/caps.rs
+++ b/crates/rust-analyzer/src/caps.rs
@@ -1,6 +1,7 @@
1//! Advertizes the capabilities of the LSP Server. 1//! Advertizes the capabilities of the LSP Server.
2use std::env; 2use std::env;
3 3
4use ide::CompletionResolveCapability;
4use lsp_types::{ 5use lsp_types::{
5 CallHierarchyServerCapability, ClientCapabilities, CodeActionKind, CodeActionOptions, 6 CallHierarchyServerCapability, ClientCapabilities, CodeActionKind, CodeActionOptions,
6 CodeActionProviderCapability, CodeLensOptions, CompletionOptions, 7 CodeActionProviderCapability, CodeLensOptions, CompletionOptions,
@@ -11,6 +12,7 @@ use lsp_types::{
11 TextDocumentSyncKind, TextDocumentSyncOptions, TypeDefinitionProviderCapability, 12 TextDocumentSyncKind, TextDocumentSyncOptions, TypeDefinitionProviderCapability,
12 WorkDoneProgressOptions, 13 WorkDoneProgressOptions,
13}; 14};
15use rustc_hash::FxHashSet;
14use serde_json::json; 16use serde_json::json;
15 17
16use crate::semantic_tokens; 18use crate::semantic_tokens;
@@ -30,7 +32,7 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti
30 })), 32 })),
31 hover_provider: Some(HoverProviderCapability::Simple(true)), 33 hover_provider: Some(HoverProviderCapability::Simple(true)),
32 completion_provider: Some(CompletionOptions { 34 completion_provider: Some(CompletionOptions {
33 resolve_provider: None, 35 resolve_provider: completions_resolve_provider(client_caps),
34 trigger_characters: Some(vec![":".to_string(), ".".to_string()]), 36 trigger_characters: Some(vec![":".to_string(), ".".to_string()]),
35 work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None }, 37 work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
36 }), 38 }),
@@ -93,6 +95,40 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti
93 } 95 }
94} 96}
95 97
98fn completions_resolve_provider(client_caps: &ClientCapabilities) -> Option<bool> {
99 if enabled_completions_resolve_capabilities(client_caps)?.is_empty() {
100 log::info!("No `additionalTextEdits` completion resolve capability was found in the client capabilities, autoimport completion is disabled");
101 None
102 } else {
103 Some(true)
104 }
105}
106
107/// Parses client capabilities and returns all completion resolve capabilities rust-analyzer supports.
108pub(crate) fn enabled_completions_resolve_capabilities(
109 caps: &ClientCapabilities,
110) -> Option<FxHashSet<CompletionResolveCapability>> {
111 Some(
112 caps.text_document
113 .as_ref()?
114 .completion
115 .as_ref()?
116 .completion_item
117 .as_ref()?
118 .resolve_support
119 .as_ref()?
120 .properties
121 .iter()
122 .filter_map(|cap_string| match cap_string.as_str() {
123 "additionalTextEdits" => Some(CompletionResolveCapability::AdditionalTextEdits),
124 "detail" => Some(CompletionResolveCapability::Detail),
125 "documentation" => Some(CompletionResolveCapability::Documentation),
126 _unsupported => None,
127 })
128 .collect(),
129 )
130}
131
96fn code_action_capabilities(client_caps: &ClientCapabilities) -> CodeActionProviderCapability { 132fn code_action_capabilities(client_caps: &ClientCapabilities) -> CodeActionProviderCapability {
97 client_caps 133 client_caps
98 .text_document 134 .text_document
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 59269a74b..5243b50c8 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -19,7 +19,7 @@ use rustc_hash::FxHashSet;
19use serde::Deserialize; 19use serde::Deserialize;
20use vfs::AbsPathBuf; 20use vfs::AbsPathBuf;
21 21
22use crate::diagnostics::DiagnosticsMapConfig; 22use crate::{caps::enabled_completions_resolve_capabilities, diagnostics::DiagnosticsMapConfig};
23 23
24#[derive(Debug, Clone)] 24#[derive(Debug, Clone)]
25pub struct Config { 25pub struct Config {
@@ -182,7 +182,7 @@ impl Config {
182 }, 182 },
183 completion: CompletionConfig { 183 completion: CompletionConfig {
184 enable_postfix_completions: true, 184 enable_postfix_completions: true,
185 enable_experimental_completions: true, 185 enable_autoimport_completions: true,
186 add_call_parenthesis: true, 186 add_call_parenthesis: true,
187 add_call_argument_snippets: true, 187 add_call_argument_snippets: true,
188 ..CompletionConfig::default() 188 ..CompletionConfig::default()
@@ -305,7 +305,7 @@ impl Config {
305 }; 305 };
306 306
307 self.completion.enable_postfix_completions = data.completion_postfix_enable; 307 self.completion.enable_postfix_completions = data.completion_postfix_enable;
308 self.completion.enable_experimental_completions = data.completion_enableExperimental; 308 self.completion.enable_autoimport_completions = data.completion_autoimport_enable;
309 self.completion.add_call_parenthesis = data.completion_addCallParenthesis; 309 self.completion.add_call_parenthesis = data.completion_addCallParenthesis;
310 self.completion.add_call_argument_snippets = data.completion_addCallArgumentSnippets; 310 self.completion.add_call_argument_snippets = data.completion_addCallArgumentSnippets;
311 self.completion.merge = self.assist.insert_use.merge; 311 self.completion.merge = self.assist.insert_use.merge;
@@ -388,6 +388,8 @@ impl Config {
388 } 388 }
389 389
390 self.completion.allow_snippets(false); 390 self.completion.allow_snippets(false);
391 self.completion.active_resolve_capabilities =
392 enabled_completions_resolve_capabilities(caps).unwrap_or_default();
391 if let Some(completion) = &doc_caps.completion { 393 if let Some(completion) = &doc_caps.completion {
392 if let Some(completion_item) = &completion.completion_item { 394 if let Some(completion_item) = &completion.completion_item {
393 if let Some(value) = completion_item.snippet_support { 395 if let Some(value) = completion_item.snippet_support {
@@ -506,7 +508,7 @@ config_data! {
506 completion_addCallArgumentSnippets: bool = true, 508 completion_addCallArgumentSnippets: bool = true,
507 completion_addCallParenthesis: bool = true, 509 completion_addCallParenthesis: bool = true,
508 completion_postfix_enable: bool = true, 510 completion_postfix_enable: bool = true,
509 completion_enableExperimental: bool = true, 511 completion_autoimport_enable: bool = true,
510 512
511 diagnostics_enable: bool = true, 513 diagnostics_enable: bool = true,
512 diagnostics_enableExperimental: bool = true, 514 diagnostics_enableExperimental: bool = true,
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 1cf4139d2..89c7fd2c7 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -8,8 +8,8 @@ use std::{
8}; 8};
9 9
10use ide::{ 10use ide::{
11 FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, NavigationTarget, Query, 11 CompletionResolveCapability, FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData,
12 RangeInfo, Runnable, RunnableKind, SearchScope, TextEdit, 12 NavigationTarget, Query, RangeInfo, Runnable, RunnableKind, SearchScope, TextEdit,
13}; 13};
14use itertools::Itertools; 14use itertools::Itertools;
15use lsp_server::ErrorCode; 15use lsp_server::ErrorCode;
@@ -21,7 +21,7 @@ use lsp_types::{
21 HoverContents, Location, NumberOrString, Position, PrepareRenameResponse, Range, RenameParams, 21 HoverContents, Location, NumberOrString, Position, PrepareRenameResponse, Range, RenameParams,
22 SemanticTokensDeltaParams, SemanticTokensFullDeltaResult, SemanticTokensParams, 22 SemanticTokensDeltaParams, SemanticTokensFullDeltaResult, SemanticTokensParams,
23 SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, 23 SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation,
24 SymbolTag, TextDocumentIdentifier, Url, WorkspaceEdit, 24 SymbolTag, TextDocumentIdentifier, TextDocumentPositionParams, Url, WorkspaceEdit,
25}; 25};
26use project_model::TargetKind; 26use project_model::TargetKind;
27use serde::{Deserialize, Serialize}; 27use serde::{Deserialize, Serialize};
@@ -35,6 +35,7 @@ use crate::{
35 from_json, from_proto, 35 from_json, from_proto,
36 global_state::{GlobalState, GlobalStateSnapshot}, 36 global_state::{GlobalState, GlobalStateSnapshot},
37 lsp_ext::{self, InlayHint, InlayHintsParams}, 37 lsp_ext::{self, InlayHint, InlayHintsParams},
38 lsp_utils::all_edits_are_disjoint,
38 to_proto, LspError, Result, 39 to_proto, LspError, Result,
39}; 40};
40 41
@@ -539,6 +540,7 @@ pub(crate) fn handle_completion(
539 params: lsp_types::CompletionParams, 540 params: lsp_types::CompletionParams,
540) -> Result<Option<lsp_types::CompletionResponse>> { 541) -> Result<Option<lsp_types::CompletionResponse>> {
541 let _p = profile::span("handle_completion"); 542 let _p = profile::span("handle_completion");
543 let text_document_position = params.text_document_position.clone();
542 let position = from_proto::file_position(&snap, params.text_document_position)?; 544 let position = from_proto::file_position(&snap, params.text_document_position)?;
543 let completion_triggered_after_single_colon = { 545 let completion_triggered_after_single_colon = {
544 let mut res = false; 546 let mut res = false;
@@ -568,15 +570,99 @@ pub(crate) fn handle_completion(
568 }; 570 };
569 let line_index = snap.analysis.file_line_index(position.file_id)?; 571 let line_index = snap.analysis.file_line_index(position.file_id)?;
570 let line_endings = snap.file_line_endings(position.file_id); 572 let line_endings = snap.file_line_endings(position.file_id);
573
571 let items: Vec<CompletionItem> = items 574 let items: Vec<CompletionItem> = items
572 .into_iter() 575 .into_iter()
573 .flat_map(|item| to_proto::completion_item(&line_index, line_endings, item)) 576 .flat_map(|item| {
577 let mut new_completion_items =
578 to_proto::completion_item(&line_index, line_endings, item.clone());
579
580 if snap.config.completion.resolve_additional_edits_lazily() {
581 for new_item in &mut new_completion_items {
582 let _ = fill_resolve_data(&mut new_item.data, &item, &text_document_position)
583 .take();
584 }
585 }
586
587 new_completion_items
588 })
574 .collect(); 589 .collect();
575 590
576 let completion_list = lsp_types::CompletionList { is_incomplete: true, items }; 591 let completion_list = lsp_types::CompletionList { is_incomplete: true, items };
577 Ok(Some(completion_list.into())) 592 Ok(Some(completion_list.into()))
578} 593}
579 594
595pub(crate) fn handle_completion_resolve(
596 snap: GlobalStateSnapshot,
597 mut original_completion: CompletionItem,
598) -> Result<CompletionItem> {
599 let _p = profile::span("handle_completion_resolve");
600
601 if !all_edits_are_disjoint(&original_completion, &[]) {
602 return Err(LspError::new(
603 ErrorCode::InvalidParams as i32,
604 "Received a completion with overlapping edits, this is not LSP-compliant".into(),
605 )
606 .into());
607 }
608
609 // FIXME resolve the other capabilities also?
610 if !snap
611 .config
612 .completion
613 .active_resolve_capabilities
614 .contains(&CompletionResolveCapability::AdditionalTextEdits)
615 {
616 return Ok(original_completion);
617 }
618
619 let resolve_data = match original_completion
620 .data
621 .take()
622 .map(|data| serde_json::from_value::<CompletionResolveData>(data))
623 .transpose()?
624 {
625 Some(data) => data,
626 None => return Ok(original_completion),
627 };
628
629 let file_id = from_proto::file_id(&snap, &resolve_data.position.text_document.uri)?;
630 let line_index = snap.analysis.file_line_index(file_id)?;
631 let line_endings = snap.file_line_endings(file_id);
632 let offset = from_proto::offset(&line_index, resolve_data.position.position);
633
634 let additional_edits = snap
635 .analysis
636 .resolve_completion_edits(
637 &snap.config.completion,
638 FilePosition { file_id, offset },
639 &resolve_data.full_import_path,
640 &resolve_data.imported_name,
641 )?
642 .into_iter()
643 .flat_map(|edit| {
644 edit.into_iter().map(|indel| to_proto::text_edit(&line_index, line_endings, indel))
645 })
646 .collect_vec();
647
648 if !all_edits_are_disjoint(&original_completion, &additional_edits) {
649 return Err(LspError::new(
650 ErrorCode::InternalError as i32,
651 "Import edit overlaps with the original completion edits, this is not LSP-compliant"
652 .into(),
653 )
654 .into());
655 }
656
657 if let Some(original_additional_edits) = original_completion.additional_text_edits.as_mut() {
658 original_additional_edits.extend(additional_edits.into_iter())
659 } else {
660 original_completion.additional_text_edits = Some(additional_edits);
661 }
662
663 Ok(original_completion)
664}
665
580pub(crate) fn handle_folding_range( 666pub(crate) fn handle_folding_range(
581 snap: GlobalStateSnapshot, 667 snap: GlobalStateSnapshot,
582 params: FoldingRangeParams, 668 params: FoldingRangeParams,
@@ -1534,3 +1620,30 @@ fn should_skip_target(runnable: &Runnable, cargo_spec: Option<&CargoTargetSpec>)
1534 _ => false, 1620 _ => false,
1535 } 1621 }
1536} 1622}
1623
1624#[derive(Debug, Serialize, Deserialize)]
1625struct CompletionResolveData {
1626 position: lsp_types::TextDocumentPositionParams,
1627 full_import_path: String,
1628 imported_name: String,
1629}
1630
1631fn fill_resolve_data(
1632 resolve_data: &mut Option<serde_json::Value>,
1633 item: &ide::CompletionItem,
1634 position: &TextDocumentPositionParams,
1635) -> Option<()> {
1636 let import_edit = item.import_to_add()?;
1637 let full_import_path = import_edit.import_path.to_string();
1638 let imported_name = import_edit.import_path.segments.clone().pop()?.to_string();
1639
1640 *resolve_data = Some(
1641 to_value(CompletionResolveData {
1642 position: position.to_owned(),
1643 full_import_path,
1644 imported_name,
1645 })
1646 .unwrap(),
1647 );
1648 Some(())
1649}
diff --git a/crates/rust-analyzer/src/lsp_utils.rs b/crates/rust-analyzer/src/lsp_utils.rs
index 6427c7367..60c12e4e2 100644
--- a/crates/rust-analyzer/src/lsp_utils.rs
+++ b/crates/rust-analyzer/src/lsp_utils.rs
@@ -129,9 +129,40 @@ pub(crate) fn apply_document_changes(
129 } 129 }
130} 130}
131 131
132/// Checks that the edits inside the completion and the additional edits do not overlap.
133/// LSP explicitly forbits the additional edits to overlap both with the main edit and themselves.
134pub(crate) fn all_edits_are_disjoint(
135 completion: &lsp_types::CompletionItem,
136 additional_edits: &[lsp_types::TextEdit],
137) -> bool {
138 let mut edit_ranges = Vec::new();
139 match completion.text_edit.as_ref() {
140 Some(lsp_types::CompletionTextEdit::Edit(edit)) => {
141 edit_ranges.push(edit.range);
142 }
143 Some(lsp_types::CompletionTextEdit::InsertAndReplace(edit)) => {
144 edit_ranges.push(edit.insert);
145 edit_ranges.push(edit.replace);
146 }
147 None => {}
148 }
149 if let Some(additional_changes) = completion.additional_text_edits.as_ref() {
150 edit_ranges.extend(additional_changes.iter().map(|edit| edit.range));
151 };
152 edit_ranges.extend(additional_edits.iter().map(|edit| edit.range));
153 edit_ranges.sort_by_key(|range| (range.start, range.end));
154 edit_ranges
155 .iter()
156 .zip(edit_ranges.iter().skip(1))
157 .all(|(previous, next)| previous.end <= next.start)
158}
159
132#[cfg(test)] 160#[cfg(test)]
133mod tests { 161mod tests {
134 use lsp_types::{Position, Range, TextDocumentContentChangeEvent}; 162 use lsp_types::{
163 CompletionItem, CompletionTextEdit, InsertReplaceEdit, Position, Range,
164 TextDocumentContentChangeEvent,
165 };
135 166
136 use super::*; 167 use super::*;
137 168
@@ -197,4 +228,135 @@ mod tests {
197 apply_document_changes(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]); 228 apply_document_changes(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]);
198 assert_eq!(text, "ațc\ncb"); 229 assert_eq!(text, "ațc\ncb");
199 } 230 }
231
232 #[test]
233 fn empty_completion_disjoint_tests() {
234 let empty_completion =
235 CompletionItem::new_simple("label".to_string(), "detail".to_string());
236
237 let disjoint_edit_1 = lsp_types::TextEdit::new(
238 Range::new(Position::new(2, 2), Position::new(3, 3)),
239 "new_text".to_string(),
240 );
241 let disjoint_edit_2 = lsp_types::TextEdit::new(
242 Range::new(Position::new(3, 3), Position::new(4, 4)),
243 "new_text".to_string(),
244 );
245
246 let joint_edit = lsp_types::TextEdit::new(
247 Range::new(Position::new(1, 1), Position::new(5, 5)),
248 "new_text".to_string(),
249 );
250
251 assert!(
252 all_edits_are_disjoint(&empty_completion, &[]),
253 "Empty completion has all its edits disjoint"
254 );
255 assert!(
256 all_edits_are_disjoint(
257 &empty_completion,
258 &[disjoint_edit_1.clone(), disjoint_edit_2.clone()]
259 ),
260 "Empty completion is disjoint to whatever disjoint extra edits added"
261 );
262
263 assert!(
264 !all_edits_are_disjoint(
265 &empty_completion,
266 &[disjoint_edit_1, disjoint_edit_2, joint_edit]
267 ),
268 "Empty completion does not prevent joint extra edits from failing the validation"
269 );
270 }
271
272 #[test]
273 fn completion_with_joint_edits_disjoint_tests() {
274 let disjoint_edit = lsp_types::TextEdit::new(
275 Range::new(Position::new(1, 1), Position::new(2, 2)),
276 "new_text".to_string(),
277 );
278 let disjoint_edit_2 = lsp_types::TextEdit::new(
279 Range::new(Position::new(2, 2), Position::new(3, 3)),
280 "new_text".to_string(),
281 );
282 let joint_edit = lsp_types::TextEdit::new(
283 Range::new(Position::new(1, 1), Position::new(5, 5)),
284 "new_text".to_string(),
285 );
286
287 let mut completion_with_joint_edits =
288 CompletionItem::new_simple("label".to_string(), "detail".to_string());
289 completion_with_joint_edits.additional_text_edits =
290 Some(vec![disjoint_edit.clone(), joint_edit.clone()]);
291 assert!(
292 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
293 "Completion with disjoint edits fails the validaton even with empty extra edits"
294 );
295
296 completion_with_joint_edits.text_edit =
297 Some(CompletionTextEdit::Edit(disjoint_edit.clone()));
298 completion_with_joint_edits.additional_text_edits = Some(vec![joint_edit.clone()]);
299 assert!(
300 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
301 "Completion with disjoint edits fails the validaton even with empty extra edits"
302 );
303
304 completion_with_joint_edits.text_edit =
305 Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit {
306 new_text: "new_text".to_string(),
307 insert: disjoint_edit.range,
308 replace: joint_edit.range,
309 }));
310 completion_with_joint_edits.additional_text_edits = None;
311 assert!(
312 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
313 "Completion with disjoint edits fails the validaton even with empty extra edits"
314 );
315
316 completion_with_joint_edits.text_edit =
317 Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit {
318 new_text: "new_text".to_string(),
319 insert: disjoint_edit.range,
320 replace: disjoint_edit_2.range,
321 }));
322 completion_with_joint_edits.additional_text_edits = Some(vec![joint_edit]);
323 assert!(
324 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
325 "Completion with disjoint edits fails the validaton even with empty extra edits"
326 );
327 }
328
329 #[test]
330 fn completion_with_disjoint_edits_disjoint_tests() {
331 let disjoint_edit = lsp_types::TextEdit::new(
332 Range::new(Position::new(1, 1), Position::new(2, 2)),
333 "new_text".to_string(),
334 );
335 let disjoint_edit_2 = lsp_types::TextEdit::new(
336 Range::new(Position::new(2, 2), Position::new(3, 3)),
337 "new_text".to_string(),
338 );
339 let joint_edit = lsp_types::TextEdit::new(
340 Range::new(Position::new(1, 1), Position::new(5, 5)),
341 "new_text".to_string(),
342 );
343
344 let mut completion_with_disjoint_edits =
345 CompletionItem::new_simple("label".to_string(), "detail".to_string());
346 completion_with_disjoint_edits.text_edit = Some(CompletionTextEdit::Edit(disjoint_edit));
347 let completion_with_disjoint_edits = completion_with_disjoint_edits;
348
349 assert!(
350 all_edits_are_disjoint(&completion_with_disjoint_edits, &[]),
351 "Completion with disjoint edits is valid"
352 );
353 assert!(
354 !all_edits_are_disjoint(&completion_with_disjoint_edits, &[joint_edit.clone()]),
355 "Completion with disjoint edits and joint extra edit is invalid"
356 );
357 assert!(
358 all_edits_are_disjoint(&completion_with_disjoint_edits, &[disjoint_edit_2.clone()]),
359 "Completion with disjoint edits and joint extra edit is valid"
360 );
361 }
200} 362}
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 55d46b09e..95be2ebd3 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -454,6 +454,7 @@ impl GlobalState {
454 .on::<lsp_types::request::GotoImplementation>(handlers::handle_goto_implementation) 454 .on::<lsp_types::request::GotoImplementation>(handlers::handle_goto_implementation)
455 .on::<lsp_types::request::GotoTypeDefinition>(handlers::handle_goto_type_definition) 455 .on::<lsp_types::request::GotoTypeDefinition>(handlers::handle_goto_type_definition)
456 .on::<lsp_types::request::Completion>(handlers::handle_completion) 456 .on::<lsp_types::request::Completion>(handlers::handle_completion)
457 .on::<lsp_types::request::ResolveCompletionItem>(handlers::handle_completion_resolve)
457 .on::<lsp_types::request::CodeLensRequest>(handlers::handle_code_lens) 458 .on::<lsp_types::request::CodeLensRequest>(handlers::handle_code_lens)
458 .on::<lsp_types::request::CodeLensResolve>(handlers::handle_code_lens_resolve) 459 .on::<lsp_types::request::CodeLensResolve>(handlers::handle_code_lens_resolve)
459 .on::<lsp_types::request::FoldingRangeRequest>(handlers::handle_folding_range) 460 .on::<lsp_types::request::FoldingRangeRequest>(handlers::handle_folding_range)
diff --git a/editors/code/package.json b/editors/code/package.json
index af228f983..dbde37005 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -460,10 +460,13 @@
460 "default": true, 460 "default": true,
461 "markdownDescription": "Whether to show postfix snippets like `dbg`, `if`, `not`, etc." 461 "markdownDescription": "Whether to show postfix snippets like `dbg`, `if`, `not`, etc."
462 }, 462 },
463 "rust-analyzer.completion.enableExperimental": { 463 "rust-analyzer.completion.autoimport.enable": {
464 "type": "boolean", 464 "type": "boolean",
465 "default": true, 465 "default": true,
466 "markdownDescription": "Display additional completions with potential false positives and performance issues" 466 "markdownDescription": [
467 "Toggles the additional completions that automatically add imports when completed.",
468 "Note that your client have to specify the `additionalTextEdits` LSP client capability to truly have this feature enabled"
469 ]
467 }, 470 },
468 "rust-analyzer.callInfo.full": { 471 "rust-analyzer.callInfo.full": {
469 "type": "boolean", 472 "type": "boolean",