aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-12-08 13:10:28 +0000
committerGitHub <[email protected]>2020-12-08 13:10:28 +0000
commit4d4f11925f793c45560c45c088d4b3139c2c171c (patch)
treef8c5e3c14a0bb55d4b435b8389bccf305975d39a /crates
parent021e97ea03cf67ad7785ab39580e04bc69506b8c (diff)
parentbf24cb3e8db94a84fb4a24c407797ab6ff5ee109 (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')
-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
16 files changed, 560 insertions, 116 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)