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