diff options
29 files changed, 702 insertions, 331 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/hir/src/attrs.rs b/crates/hir/src/attrs.rs index c3e820d89..1f2ee2580 100644 --- a/crates/hir/src/attrs.rs +++ b/crates/hir/src/attrs.rs | |||
@@ -1,6 +1,9 @@ | |||
1 | //! Attributes & documentation for hir types. | 1 | //! Attributes & documentation for hir types. |
2 | use hir_def::{ | 2 | use hir_def::{ |
3 | attr::Attrs, docs::Documentation, path::ModPath, resolver::HasResolver, AttrDefId, ModuleDefId, | 3 | attr::{Attrs, Documentation}, |
4 | path::ModPath, | ||
5 | resolver::HasResolver, | ||
6 | AttrDefId, ModuleDefId, | ||
4 | }; | 7 | }; |
5 | use hir_expand::hygiene::Hygiene; | 8 | use hir_expand::hygiene::Hygiene; |
6 | use hir_ty::db::HirDatabase; | 9 | use hir_ty::db::HirDatabase; |
@@ -38,7 +41,7 @@ macro_rules! impl_has_attrs { | |||
38 | } | 41 | } |
39 | fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> { | 42 | fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> { |
40 | let def = AttrDefId::$def_id(self.into()); | 43 | let def = AttrDefId::$def_id(self.into()); |
41 | db.documentation(def) | 44 | db.attrs(def).docs() |
42 | } | 45 | } |
43 | fn resolve_doc_path(self, db: &dyn HirDatabase, link: &str, ns: Option<Namespace>) -> Option<ModuleDef> { | 46 | fn resolve_doc_path(self, db: &dyn HirDatabase, link: &str, ns: Option<Namespace>) -> Option<ModuleDef> { |
44 | let def = AttrDefId::$def_id(self.into()); | 47 | let def = AttrDefId::$def_id(self.into()); |
diff --git a/crates/hir/src/db.rs b/crates/hir/src/db.rs index 8c767b249..8d949b264 100644 --- a/crates/hir/src/db.rs +++ b/crates/hir/src/db.rs | |||
@@ -2,12 +2,12 @@ | |||
2 | 2 | ||
3 | pub use hir_def::db::{ | 3 | pub use hir_def::db::{ |
4 | AttrsQuery, BodyQuery, BodyWithSourceMapQuery, ConstDataQuery, CrateDefMapQueryQuery, | 4 | AttrsQuery, BodyQuery, BodyWithSourceMapQuery, ConstDataQuery, CrateDefMapQueryQuery, |
5 | CrateLangItemsQuery, DefDatabase, DefDatabaseStorage, DocumentationQuery, EnumDataQuery, | 5 | CrateLangItemsQuery, DefDatabase, DefDatabaseStorage, EnumDataQuery, ExprScopesQuery, |
6 | ExprScopesQuery, FunctionDataQuery, GenericParamsQuery, ImplDataQuery, ImportMapQuery, | 6 | FunctionDataQuery, GenericParamsQuery, ImplDataQuery, ImportMapQuery, InternConstQuery, |
7 | InternConstQuery, InternDatabase, InternDatabaseStorage, InternEnumQuery, InternFunctionQuery, | 7 | InternDatabase, InternDatabaseStorage, InternEnumQuery, InternFunctionQuery, InternImplQuery, |
8 | InternImplQuery, InternStaticQuery, InternStructQuery, InternTraitQuery, InternTypeAliasQuery, | 8 | InternStaticQuery, InternStructQuery, InternTraitQuery, InternTypeAliasQuery, InternUnionQuery, |
9 | InternUnionQuery, ItemTreeQuery, LangItemQuery, ModuleLangItemsQuery, StaticDataQuery, | 9 | ItemTreeQuery, LangItemQuery, ModuleLangItemsQuery, StaticDataQuery, StructDataQuery, |
10 | StructDataQuery, TraitDataQuery, TypeAliasDataQuery, UnionDataQuery, | 10 | TraitDataQuery, TypeAliasDataQuery, UnionDataQuery, |
11 | }; | 11 | }; |
12 | pub use hir_expand::db::{ | 12 | pub use hir_expand::db::{ |
13 | AstDatabase, AstDatabaseStorage, AstIdMapQuery, InternEagerExpansionQuery, InternMacroQuery, | 13 | AstDatabase, AstDatabaseStorage, AstIdMapQuery, InternEagerExpansionQuery, InternMacroQuery, |
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 93bdb4472..c7c7377d7 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs | |||
@@ -44,10 +44,9 @@ pub use crate::{ | |||
44 | 44 | ||
45 | pub use hir_def::{ | 45 | pub use hir_def::{ |
46 | adt::StructKind, | 46 | adt::StructKind, |
47 | attr::Attrs, | 47 | attr::{Attrs, Documentation}, |
48 | body::scope::ExprScopes, | 48 | body::scope::ExprScopes, |
49 | builtin_type::BuiltinType, | 49 | builtin_type::BuiltinType, |
50 | docs::Documentation, | ||
51 | find_path::PrefixKind, | 50 | find_path::PrefixKind, |
52 | import_map, | 51 | import_map, |
53 | item_scope::ItemInNs, | 52 | item_scope::ItemInNs, |
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs index b2ce7ca3c..12f4b02e2 100644 --- a/crates/hir_def/src/attr.rs +++ b/crates/hir_def/src/attr.rs | |||
@@ -5,10 +5,11 @@ use std::{ops, sync::Arc}; | |||
5 | use cfg::{CfgExpr, CfgOptions}; | 5 | use cfg::{CfgExpr, CfgOptions}; |
6 | use either::Either; | 6 | use either::Either; |
7 | use hir_expand::{hygiene::Hygiene, AstId, InFile}; | 7 | use hir_expand::{hygiene::Hygiene, AstId, InFile}; |
8 | use itertools::Itertools; | ||
8 | use mbe::ast_to_token_tree; | 9 | use mbe::ast_to_token_tree; |
9 | use syntax::{ | 10 | use syntax::{ |
10 | ast::{self, AstNode, AttrsOwner}, | 11 | ast::{self, AstNode, AttrsOwner}, |
11 | SmolStr, | 12 | AstToken, SmolStr, |
12 | }; | 13 | }; |
13 | use tt::Subtree; | 14 | use tt::Subtree; |
14 | 15 | ||
@@ -21,6 +22,22 @@ use crate::{ | |||
21 | AdtId, AttrDefId, Lookup, | 22 | AdtId, AttrDefId, Lookup, |
22 | }; | 23 | }; |
23 | 24 | ||
25 | /// Holds documentation | ||
26 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
27 | pub struct Documentation(String); | ||
28 | |||
29 | impl Documentation { | ||
30 | pub fn as_str(&self) -> &str { | ||
31 | &self.0 | ||
32 | } | ||
33 | } | ||
34 | |||
35 | impl Into<String> for Documentation { | ||
36 | fn into(self) -> String { | ||
37 | self.0 | ||
38 | } | ||
39 | } | ||
40 | |||
24 | #[derive(Default, Debug, Clone, PartialEq, Eq)] | 41 | #[derive(Default, Debug, Clone, PartialEq, Eq)] |
25 | pub struct Attrs { | 42 | pub struct Attrs { |
26 | entries: Option<Arc<[Attr]>>, | 43 | entries: Option<Arc<[Attr]>>, |
@@ -93,18 +110,25 @@ impl Attrs { | |||
93 | } | 110 | } |
94 | 111 | ||
95 | pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Attrs { | 112 | pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Attrs { |
96 | let docs = ast::CommentIter::from_syntax_node(owner.syntax()).doc_comment_text().map( | 113 | let docs = ast::CommentIter::from_syntax_node(owner.syntax()).map(|docs_text| { |
97 | |docs_text| Attr { | 114 | ( |
98 | input: Some(AttrInput::Literal(SmolStr::new(docs_text))), | 115 | docs_text.syntax().text_range().start(), |
99 | path: ModPath::from(hir_expand::name!(doc)), | 116 | docs_text.doc_comment().map(|doc| Attr { |
100 | }, | 117 | input: Some(AttrInput::Literal(SmolStr::new(doc))), |
101 | ); | 118 | path: ModPath::from(hir_expand::name!(doc)), |
102 | let mut attrs = owner.attrs().peekable(); | 119 | }), |
103 | let entries = if attrs.peek().is_none() { | 120 | ) |
121 | }); | ||
122 | let attrs = owner | ||
123 | .attrs() | ||
124 | .map(|attr| (attr.syntax().text_range().start(), Attr::from_src(attr, hygiene))); | ||
125 | // sort here by syntax node offset because the source can have doc attributes and doc strings be interleaved | ||
126 | let attrs: Vec<_> = docs.chain(attrs).sorted_by_key(|&(offset, _)| offset).collect(); | ||
127 | let entries = if attrs.is_empty() { | ||
104 | // Avoid heap allocation | 128 | // Avoid heap allocation |
105 | None | 129 | None |
106 | } else { | 130 | } else { |
107 | Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).chain(docs).collect()) | 131 | Some(attrs.into_iter().flat_map(|(_, attr)| attr).collect()) |
108 | }; | 132 | }; |
109 | Attrs { entries } | 133 | Attrs { entries } |
110 | } | 134 | } |
@@ -140,6 +164,24 @@ impl Attrs { | |||
140 | Some(cfg) => cfg_options.check(&cfg) != Some(false), | 164 | Some(cfg) => cfg_options.check(&cfg) != Some(false), |
141 | } | 165 | } |
142 | } | 166 | } |
167 | |||
168 | pub fn docs(&self) -> Option<Documentation> { | ||
169 | let docs = self | ||
170 | .by_key("doc") | ||
171 | .attrs() | ||
172 | .flat_map(|attr| match attr.input.as_ref()? { | ||
173 | AttrInput::Literal(s) => Some(s), | ||
174 | AttrInput::TokenTree(_) => None, | ||
175 | }) | ||
176 | .intersperse(&SmolStr::new_inline("\n")) | ||
177 | .map(|it| it.as_str()) | ||
178 | .collect::<String>(); | ||
179 | if docs.is_empty() { | ||
180 | None | ||
181 | } else { | ||
182 | Some(Documentation(docs.into())) | ||
183 | } | ||
184 | } | ||
143 | } | 185 | } |
144 | 186 | ||
145 | #[derive(Debug, Clone, PartialEq, Eq)] | 187 | #[derive(Debug, Clone, PartialEq, Eq)] |
@@ -160,8 +202,10 @@ impl Attr { | |||
160 | fn from_src(ast: ast::Attr, hygiene: &Hygiene) -> Option<Attr> { | 202 | fn from_src(ast: ast::Attr, hygiene: &Hygiene) -> Option<Attr> { |
161 | let path = ModPath::from_src(ast.path()?, hygiene)?; | 203 | let path = ModPath::from_src(ast.path()?, hygiene)?; |
162 | let input = if let Some(lit) = ast.literal() { | 204 | let input = if let Some(lit) = ast.literal() { |
163 | // FIXME: escape? raw string? | 205 | let value = match lit.kind() { |
164 | let value = lit.syntax().first_token()?.text().trim_matches('"').into(); | 206 | ast::LiteralKind::String(string) => string.value()?.into(), |
207 | _ => lit.syntax().first_token()?.text().trim_matches('"').into(), | ||
208 | }; | ||
165 | Some(AttrInput::Literal(value)) | 209 | Some(AttrInput::Literal(value)) |
166 | } else if let Some(tt) = ast.token_tree() { | 210 | } else if let Some(tt) = ast.token_tree() { |
167 | Some(AttrInput::TokenTree(ast_to_token_tree(&tt)?.0)) | 211 | Some(AttrInput::TokenTree(ast_to_token_tree(&tt)?.0)) |
diff --git a/crates/hir_def/src/db.rs b/crates/hir_def/src/db.rs index 6d694de11..7f250da33 100644 --- a/crates/hir_def/src/db.rs +++ b/crates/hir_def/src/db.rs | |||
@@ -10,7 +10,6 @@ use crate::{ | |||
10 | attr::Attrs, | 10 | attr::Attrs, |
11 | body::{scope::ExprScopes, Body, BodySourceMap}, | 11 | body::{scope::ExprScopes, Body, BodySourceMap}, |
12 | data::{ConstData, FunctionData, ImplData, StaticData, TraitData, TypeAliasData}, | 12 | data::{ConstData, FunctionData, ImplData, StaticData, TraitData, TypeAliasData}, |
13 | docs::Documentation, | ||
14 | generics::GenericParams, | 13 | generics::GenericParams, |
15 | import_map::ImportMap, | 14 | import_map::ImportMap, |
16 | item_tree::ItemTree, | 15 | item_tree::ItemTree, |
@@ -105,11 +104,6 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> { | |||
105 | #[salsa::invoke(LangItems::lang_item_query)] | 104 | #[salsa::invoke(LangItems::lang_item_query)] |
106 | fn lang_item(&self, start_crate: CrateId, item: SmolStr) -> Option<LangItemTarget>; | 105 | fn lang_item(&self, start_crate: CrateId, item: SmolStr) -> Option<LangItemTarget>; |
107 | 106 | ||
108 | // FIXME(https://github.com/rust-analyzer/rust-analyzer/issues/2148#issuecomment-550519102) | ||
109 | // Remove this query completely, in favor of `Attrs::docs` method | ||
110 | #[salsa::invoke(Documentation::documentation_query)] | ||
111 | fn documentation(&self, def: AttrDefId) -> Option<Documentation>; | ||
112 | |||
113 | #[salsa::invoke(ImportMap::import_map_query)] | 107 | #[salsa::invoke(ImportMap::import_map_query)] |
114 | fn import_map(&self, krate: CrateId) -> Arc<ImportMap>; | 108 | fn import_map(&self, krate: CrateId) -> Arc<ImportMap>; |
115 | } | 109 | } |
diff --git a/crates/hir_def/src/docs.rs b/crates/hir_def/src/docs.rs deleted file mode 100644 index 3e59a8f47..000000000 --- a/crates/hir_def/src/docs.rs +++ /dev/null | |||
@@ -1,121 +0,0 @@ | |||
1 | //! Defines hir documentation. | ||
2 | //! | ||
3 | //! This really shouldn't exist, instead, we should deshugar doc comments into attributes, see | ||
4 | //! https://github.com/rust-analyzer/rust-analyzer/issues/2148#issuecomment-550519102 | ||
5 | |||
6 | use std::sync::Arc; | ||
7 | |||
8 | use either::Either; | ||
9 | use itertools::Itertools; | ||
10 | use syntax::{ast, SmolStr}; | ||
11 | |||
12 | use crate::{ | ||
13 | db::DefDatabase, | ||
14 | src::{HasChildSource, HasSource}, | ||
15 | AdtId, AttrDefId, Lookup, | ||
16 | }; | ||
17 | |||
18 | /// Holds documentation | ||
19 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
20 | pub struct Documentation(Arc<str>); | ||
21 | |||
22 | impl Into<String> for Documentation { | ||
23 | fn into(self) -> String { | ||
24 | self.as_str().to_owned() | ||
25 | } | ||
26 | } | ||
27 | |||
28 | impl Documentation { | ||
29 | fn new(s: &str) -> Documentation { | ||
30 | Documentation(s.into()) | ||
31 | } | ||
32 | |||
33 | pub fn from_ast<N>(node: &N) -> Option<Documentation> | ||
34 | where | ||
35 | N: ast::DocCommentsOwner + ast::AttrsOwner, | ||
36 | { | ||
37 | docs_from_ast(node) | ||
38 | } | ||
39 | |||
40 | pub fn as_str(&self) -> &str { | ||
41 | &*self.0 | ||
42 | } | ||
43 | |||
44 | pub(crate) fn documentation_query( | ||
45 | db: &dyn DefDatabase, | ||
46 | def: AttrDefId, | ||
47 | ) -> Option<Documentation> { | ||
48 | match def { | ||
49 | AttrDefId::ModuleId(module) => { | ||
50 | let def_map = db.crate_def_map(module.krate); | ||
51 | let src = def_map[module.local_id].declaration_source(db)?; | ||
52 | docs_from_ast(&src.value) | ||
53 | } | ||
54 | AttrDefId::FieldId(it) => { | ||
55 | let src = it.parent.child_source(db); | ||
56 | match &src.value[it.local_id] { | ||
57 | Either::Left(_tuple) => None, | ||
58 | Either::Right(record) => docs_from_ast(record), | ||
59 | } | ||
60 | } | ||
61 | AttrDefId::AdtId(it) => match it { | ||
62 | AdtId::StructId(it) => docs_from_ast(&it.lookup(db).source(db).value), | ||
63 | AdtId::EnumId(it) => docs_from_ast(&it.lookup(db).source(db).value), | ||
64 | AdtId::UnionId(it) => docs_from_ast(&it.lookup(db).source(db).value), | ||
65 | }, | ||
66 | AttrDefId::EnumVariantId(it) => { | ||
67 | let src = it.parent.child_source(db); | ||
68 | docs_from_ast(&src.value[it.local_id]) | ||
69 | } | ||
70 | AttrDefId::TraitId(it) => docs_from_ast(&it.lookup(db).source(db).value), | ||
71 | AttrDefId::MacroDefId(it) => docs_from_ast(&it.ast_id?.to_node(db.upcast())), | ||
72 | AttrDefId::ConstId(it) => docs_from_ast(&it.lookup(db).source(db).value), | ||
73 | AttrDefId::StaticId(it) => docs_from_ast(&it.lookup(db).source(db).value), | ||
74 | AttrDefId::FunctionId(it) => docs_from_ast(&it.lookup(db).source(db).value), | ||
75 | AttrDefId::TypeAliasId(it) => docs_from_ast(&it.lookup(db).source(db).value), | ||
76 | AttrDefId::ImplId(_) => None, | ||
77 | } | ||
78 | } | ||
79 | } | ||
80 | |||
81 | pub(crate) fn docs_from_ast<N>(node: &N) -> Option<Documentation> | ||
82 | where | ||
83 | N: ast::DocCommentsOwner + ast::AttrsOwner, | ||
84 | { | ||
85 | let doc_comment_text = node.doc_comment_text(); | ||
86 | let doc_attr_text = expand_doc_attrs(node); | ||
87 | let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text); | ||
88 | docs.map(|it| Documentation::new(&it)) | ||
89 | } | ||
90 | |||
91 | fn merge_doc_comments_and_attrs( | ||
92 | doc_comment_text: Option<String>, | ||
93 | doc_attr_text: Option<String>, | ||
94 | ) -> Option<String> { | ||
95 | match (doc_comment_text, doc_attr_text) { | ||
96 | (Some(mut comment_text), Some(attr_text)) => { | ||
97 | comment_text.push_str("\n"); | ||
98 | comment_text.push_str(&attr_text); | ||
99 | Some(comment_text) | ||
100 | } | ||
101 | (Some(comment_text), None) => Some(comment_text), | ||
102 | (None, Some(attr_text)) => Some(attr_text), | ||
103 | (None, None) => None, | ||
104 | } | ||
105 | } | ||
106 | |||
107 | fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option<String> { | ||
108 | let mut docs = String::new(); | ||
109 | owner | ||
110 | .attrs() | ||
111 | .filter_map(|attr| attr.as_simple_key_value().filter(|(key, _)| key == "doc")) | ||
112 | .map(|(_, value)| value) | ||
113 | .intersperse(SmolStr::new_inline("\n")) | ||
114 | // No FromIterator<SmolStr> for String | ||
115 | .for_each(|s| docs.push_str(s.as_str())); | ||
116 | if docs.is_empty() { | ||
117 | None | ||
118 | } else { | ||
119 | Some(docs) | ||
120 | } | ||
121 | } | ||
diff --git a/crates/hir_def/src/lib.rs b/crates/hir_def/src/lib.rs index b41c5acb2..02ed30e4d 100644 --- a/crates/hir_def/src/lib.rs +++ b/crates/hir_def/src/lib.rs | |||
@@ -31,7 +31,6 @@ pub mod adt; | |||
31 | pub mod data; | 31 | pub mod data; |
32 | pub mod generics; | 32 | pub mod generics; |
33 | pub mod lang_item; | 33 | pub mod lang_item; |
34 | pub mod docs; | ||
35 | 34 | ||
36 | pub mod expr; | 35 | pub mod expr; |
37 | pub mod body; | 36 | pub mod body; |
diff --git a/crates/hir_def/src/nameres/tests/mod_resolution.rs b/crates/hir_def/src/nameres/tests/mod_resolution.rs index ef6f85e15..e80b593aa 100644 --- a/crates/hir_def/src/nameres/tests/mod_resolution.rs +++ b/crates/hir_def/src/nameres/tests/mod_resolution.rs | |||
@@ -372,7 +372,7 @@ fn module_resolution_explicit_path_mod_rs_with_win_separator() { | |||
372 | check( | 372 | check( |
373 | r#" | 373 | r#" |
374 | //- /main.rs | 374 | //- /main.rs |
375 | #[path = "module\bar\mod.rs"] | 375 | #[path = r"module\bar\mod.rs"] |
376 | mod foo; | 376 | mod foo; |
377 | 377 | ||
378 | //- /module/bar/mod.rs | 378 | //- /module/bar/mod.rs |
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index dc9621f46..1b6ff6d21 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use hir::{ | 1 | use hir::{ |
2 | Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, | 2 | Adt, AsAssocItem, AssocItemContainer, FieldSource, HasAttrs, HasSource, HirDisplay, Module, |
3 | Module, ModuleDef, ModuleSource, Semantics, | 3 | ModuleDef, ModuleSource, Semantics, |
4 | }; | 4 | }; |
5 | use ide_db::base_db::SourceDatabase; | 5 | use ide_db::base_db::SourceDatabase; |
6 | use ide_db::{ | 6 | use ide_db::{ |
@@ -319,31 +319,27 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> { | |||
319 | let mod_path = definition_mod_path(db, &def); | 319 | let mod_path = definition_mod_path(db, &def); |
320 | return match def { | 320 | return match def { |
321 | Definition::Macro(it) => { | 321 | Definition::Macro(it) => { |
322 | let src = it.source(db); | 322 | let label = macro_label(&it.source(db).value); |
323 | let docs = Documentation::from_ast(&src.value).map(Into::into); | 323 | from_def_source_labeled(db, it, Some(label), mod_path) |
324 | hover_markup(docs, Some(macro_label(&src.value)), mod_path) | ||
325 | } | 324 | } |
326 | Definition::Field(it) => { | 325 | Definition::Field(def) => { |
327 | let src = it.source(db); | 326 | let src = def.source(db).value; |
328 | match src.value { | 327 | if let FieldSource::Named(it) = src { |
329 | FieldSource::Named(it) => { | 328 | from_def_source_labeled(db, def, it.short_label(), mod_path) |
330 | let docs = Documentation::from_ast(&it).map(Into::into); | 329 | } else { |
331 | hover_markup(docs, it.short_label(), mod_path) | 330 | None |
332 | } | ||
333 | _ => None, | ||
334 | } | 331 | } |
335 | } | 332 | } |
336 | Definition::ModuleDef(it) => match it { | 333 | Definition::ModuleDef(it) => match it { |
337 | ModuleDef::Module(it) => match it.definition_source(db).value { | 334 | ModuleDef::Module(it) => from_def_source_labeled( |
338 | ModuleSource::Module(it) => { | 335 | db, |
339 | let docs = Documentation::from_ast(&it).map(Into::into); | 336 | it, |
340 | hover_markup(docs, it.short_label(), mod_path) | 337 | match it.definition_source(db).value { |
341 | } | 338 | ModuleSource::Module(it) => it.short_label(), |
342 | ModuleSource::SourceFile(it) => { | 339 | ModuleSource::SourceFile(it) => it.short_label(), |
343 | let docs = Documentation::from_ast(&it).map(Into::into); | 340 | }, |
344 | hover_markup(docs, it.short_label(), mod_path) | 341 | mod_path, |
345 | } | 342 | ), |
346 | }, | ||
347 | ModuleDef::Function(it) => from_def_source(db, it, mod_path), | 343 | ModuleDef::Function(it) => from_def_source(db, it, mod_path), |
348 | ModuleDef::Adt(Adt::Struct(it)) => from_def_source(db, it, mod_path), | 344 | ModuleDef::Adt(Adt::Struct(it)) => from_def_source(db, it, mod_path), |
349 | ModuleDef::Adt(Adt::Union(it)) => from_def_source(db, it, mod_path), | 345 | ModuleDef::Adt(Adt::Union(it)) => from_def_source(db, it, mod_path), |
@@ -371,12 +367,24 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> { | |||
371 | 367 | ||
372 | fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<Markup> | 368 | fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<Markup> |
373 | where | 369 | where |
374 | D: HasSource<Ast = A>, | 370 | D: HasSource<Ast = A> + HasAttrs + Copy, |
375 | A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel + ast::AttrsOwner, | 371 | A: ShortLabel, |
372 | { | ||
373 | let short_label = def.source(db).value.short_label(); | ||
374 | from_def_source_labeled(db, def, short_label, mod_path) | ||
375 | } | ||
376 | |||
377 | fn from_def_source_labeled<D>( | ||
378 | db: &RootDatabase, | ||
379 | def: D, | ||
380 | short_label: Option<String>, | ||
381 | mod_path: Option<String>, | ||
382 | ) -> Option<Markup> | ||
383 | where | ||
384 | D: HasAttrs, | ||
376 | { | 385 | { |
377 | let src = def.source(db); | 386 | let docs = def.attrs(db).docs().map(Into::into); |
378 | let docs = Documentation::from_ast(&src.value).map(Into::into); | 387 | hover_markup(docs, short_label, mod_path) |
379 | hover_markup(docs, src.value.short_label(), mod_path) | ||
380 | } | 388 | } |
381 | } | 389 | } |
382 | 390 | ||
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/apply_change.rs b/crates/ide_db/src/apply_change.rs index 987191fe3..e2251f2b7 100644 --- a/crates/ide_db/src/apply_change.rs +++ b/crates/ide_db/src/apply_change.rs | |||
@@ -166,7 +166,6 @@ impl RootDatabase { | |||
166 | hir::db::ModuleLangItemsQuery | 166 | hir::db::ModuleLangItemsQuery |
167 | hir::db::CrateLangItemsQuery | 167 | hir::db::CrateLangItemsQuery |
168 | hir::db::LangItemQuery | 168 | hir::db::LangItemQuery |
169 | hir::db::DocumentationQuery | ||
170 | hir::db::ImportMapQuery | 169 | hir::db::ImportMapQuery |
171 | 170 | ||
172 | // HirDatabase | 171 | // HirDatabase |
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 6250171ba..94e2bfa1b 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, |
@@ -1530,3 +1616,30 @@ fn should_skip_target(runnable: &Runnable, cargo_spec: Option<&CargoTargetSpec>) | |||
1530 | _ => false, | 1616 | _ => false, |
1531 | } | 1617 | } |
1532 | } | 1618 | } |
1619 | |||
1620 | #[derive(Debug, Serialize, Deserialize)] | ||
1621 | struct CompletionResolveData { | ||
1622 | position: lsp_types::TextDocumentPositionParams, | ||
1623 | full_import_path: String, | ||
1624 | imported_name: String, | ||
1625 | } | ||
1626 | |||
1627 | fn fill_resolve_data( | ||
1628 | resolve_data: &mut Option<serde_json::Value>, | ||
1629 | item: &ide::CompletionItem, | ||
1630 | position: &TextDocumentPositionParams, | ||
1631 | ) -> Option<()> { | ||
1632 | let import_edit = item.import_to_add()?; | ||
1633 | let full_import_path = import_edit.import_path.to_string(); | ||
1634 | let imported_name = import_edit.import_path.segments.clone().pop()?.to_string(); | ||
1635 | |||
1636 | *resolve_data = Some( | ||
1637 | to_value(CompletionResolveData { | ||
1638 | position: position.to_owned(), | ||
1639 | full_import_path, | ||
1640 | imported_name, | ||
1641 | }) | ||
1642 | .unwrap(), | ||
1643 | ); | ||
1644 | Some(()) | ||
1645 | } | ||
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) |
diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs index fa40e64e8..a10b14778 100644 --- a/crates/syntax/src/ast/token_ext.rs +++ b/crates/syntax/src/ast/token_ext.rs | |||
@@ -24,6 +24,28 @@ impl ast::Comment { | |||
24 | .unwrap(); | 24 | .unwrap(); |
25 | prefix | 25 | prefix |
26 | } | 26 | } |
27 | |||
28 | /// Returns the textual content of a doc comment block as a single string. | ||
29 | /// That is, strips leading `///` (+ optional 1 character of whitespace), | ||
30 | /// trailing `*/`, trailing whitespace and then joins the lines. | ||
31 | pub fn doc_comment(&self) -> Option<&str> { | ||
32 | let kind = self.kind(); | ||
33 | match kind { | ||
34 | CommentKind { shape, doc: Some(_) } => { | ||
35 | let prefix = kind.prefix(); | ||
36 | let text = &self.text().as_str()[prefix.len()..]; | ||
37 | let ws = text.chars().next().filter(|c| c.is_whitespace()); | ||
38 | let text = ws.map_or(text, |ws| &text[ws.len_utf8()..]); | ||
39 | match shape { | ||
40 | CommentShape::Block if text.ends_with("*/") => { | ||
41 | Some(&text[..text.len() - "*/".len()]) | ||
42 | } | ||
43 | _ => Some(text), | ||
44 | } | ||
45 | } | ||
46 | _ => None, | ||
47 | } | ||
48 | } | ||
27 | } | 49 | } |
28 | 50 | ||
29 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | 51 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] |
@@ -73,6 +95,11 @@ impl CommentKind { | |||
73 | .unwrap(); | 95 | .unwrap(); |
74 | kind | 96 | kind |
75 | } | 97 | } |
98 | |||
99 | fn prefix(&self) -> &'static str { | ||
100 | let &(prefix, _) = CommentKind::BY_PREFIX.iter().find(|(_, kind)| kind == self).unwrap(); | ||
101 | prefix | ||
102 | } | ||
76 | } | 103 | } |
77 | 104 | ||
78 | impl ast::Whitespace { | 105 | impl ast::Whitespace { |
diff --git a/crates/syntax/src/ast/traits.rs b/crates/syntax/src/ast/traits.rs index 0bdc22d95..13a769d51 100644 --- a/crates/syntax/src/ast/traits.rs +++ b/crates/syntax/src/ast/traits.rs | |||
@@ -91,40 +91,12 @@ impl CommentIter { | |||
91 | /// That is, strips leading `///` (+ optional 1 character of whitespace), | 91 | /// That is, strips leading `///` (+ optional 1 character of whitespace), |
92 | /// trailing `*/`, trailing whitespace and then joins the lines. | 92 | /// trailing `*/`, trailing whitespace and then joins the lines. |
93 | pub fn doc_comment_text(self) -> Option<String> { | 93 | pub fn doc_comment_text(self) -> Option<String> { |
94 | let mut has_comments = false; | 94 | let docs = |
95 | let docs = self | 95 | self.filter_map(|comment| comment.doc_comment().map(ToOwned::to_owned)).join("\n"); |
96 | .filter(|comment| comment.kind().doc.is_some()) | 96 | if docs.is_empty() { |
97 | .map(|comment| { | ||
98 | has_comments = true; | ||
99 | let prefix_len = comment.prefix().len(); | ||
100 | |||
101 | let line: &str = comment.text().as_str(); | ||
102 | |||
103 | // Determine if the prefix or prefix + 1 char is stripped | ||
104 | let pos = | ||
105 | if let Some(ws) = line.chars().nth(prefix_len).filter(|c| c.is_whitespace()) { | ||
106 | prefix_len + ws.len_utf8() | ||
107 | } else { | ||
108 | prefix_len | ||
109 | }; | ||
110 | |||
111 | let end = if comment.kind().shape.is_block() && line.ends_with("*/") { | ||
112 | line.len() - 2 | ||
113 | } else { | ||
114 | line.len() | ||
115 | }; | ||
116 | |||
117 | // Note that we do not trim the end of the line here | ||
118 | // since whitespace can have special meaning at the end | ||
119 | // of a line in markdown. | ||
120 | line[pos..end].to_owned() | ||
121 | }) | ||
122 | .join("\n"); | ||
123 | |||
124 | if has_comments { | ||
125 | Some(docs) | ||
126 | } else { | ||
127 | None | 97 | None |
98 | } else { | ||
99 | Some(docs) | ||
128 | } | 100 | } |
129 | } | 101 | } |
130 | } | 102 | } |
diff --git a/editors/code/package.json b/editors/code/package.json index af228f983..dbde37005 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -460,10 +460,13 @@ | |||
460 | "default": true, | 460 | "default": true, |
461 | "markdownDescription": "Whether to show postfix snippets like `dbg`, `if`, `not`, etc." | 461 | "markdownDescription": "Whether to show postfix snippets like `dbg`, `if`, `not`, etc." |
462 | }, | 462 | }, |
463 | "rust-analyzer.completion.enableExperimental": { | 463 | "rust-analyzer.completion.autoimport.enable": { |
464 | "type": "boolean", | 464 | "type": "boolean", |
465 | "default": true, | 465 | "default": true, |
466 | "markdownDescription": "Display additional completions with potential false positives and performance issues" | 466 | "markdownDescription": [ |
467 | "Toggles the additional completions that automatically add imports when completed.", | ||
468 | "Note that your client have to specify the `additionalTextEdits` LSP client capability to truly have this feature enabled" | ||
469 | ] | ||
467 | }, | 470 | }, |
468 | "rust-analyzer.callInfo.full": { | 471 | "rust-analyzer.callInfo.full": { |
469 | "type": "boolean", | 472 | "type": "boolean", |