aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/completion/src/completions/unqualified_path.rs92
-rw-r--r--crates/completion/src/config.rs26
-rw-r--r--crates/completion/src/item.rs64
-rw-r--r--crates/completion/src/lib.rs47
-rw-r--r--crates/completion/src/render.rs38
-rw-r--r--crates/completion/src/render/enum_variant.rs6
-rw-r--r--crates/completion/src/render/function.rs6
-rw-r--r--crates/completion/src/render/macro_.rs6
-rw-r--r--crates/completion/src/test_utils.rs11
-rw-r--r--crates/hir/src/attrs.rs7
-rw-r--r--crates/hir/src/db.rs12
-rw-r--r--crates/hir/src/lib.rs3
-rw-r--r--crates/hir_def/src/attr.rs68
-rw-r--r--crates/hir_def/src/db.rs6
-rw-r--r--crates/hir_def/src/docs.rs121
-rw-r--r--crates/hir_def/src/lib.rs1
-rw-r--r--crates/hir_def/src/nameres/tests/mod_resolution.rs2
-rw-r--r--crates/ide/src/hover.rs64
-rw-r--r--crates/ide/src/lib.rs24
-rw-r--r--crates/ide_db/src/apply_change.rs1
-rw-r--r--crates/ide_db/src/imports_locator.rs22
-rw-r--r--crates/rust-analyzer/src/caps.rs38
-rw-r--r--crates/rust-analyzer/src/config.rs10
-rw-r--r--crates/rust-analyzer/src/handlers.rs121
-rw-r--r--crates/rust-analyzer/src/lsp_utils.rs164
-rw-r--r--crates/rust-analyzer/src/main_loop.rs1
-rw-r--r--crates/syntax/src/ast/token_ext.rs27
-rw-r--r--crates/syntax/src/ast/traits.rs38
28 files changed, 697 insertions, 329 deletions
diff --git a/crates/completion/src/completions/unqualified_path.rs b/crates/completion/src/completions/unqualified_path.rs
index 81691cd7f..4e4e2b36f 100644
--- a/crates/completion/src/completions/unqualified_path.rs
+++ b/crates/completion/src/completions/unqualified_path.rs
@@ -9,7 +9,7 @@ use test_utils::mark;
9 9
10use crate::{ 10use crate::{
11 render::{render_resolution_with_import, RenderContext}, 11 render::{render_resolution_with_import, RenderContext},
12 CompletionContext, Completions, 12 CompletionContext, Completions, ImportEdit,
13}; 13};
14 14
15pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { 15pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
@@ -44,7 +44,7 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
44 acc.add_resolution(ctx, name.to_string(), &res) 44 acc.add_resolution(ctx, name.to_string(), &res)
45 }); 45 });
46 46
47 if ctx.config.enable_experimental_completions { 47 if ctx.config.enable_autoimport_completions && ctx.config.resolve_additional_edits_lazily() {
48 fuzzy_completion(acc, ctx).unwrap_or_default() 48 fuzzy_completion(acc, ctx).unwrap_or_default()
49 } 49 }
50} 50}
@@ -73,19 +73,64 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T
73 } 73 }
74} 74}
75 75
76// Feature: Fuzzy Completion and Autoimports
77//
78// When completing names in the current scope, proposes additional imports from other modules or crates,
79// if they can be qualified in the scope and their name contains all symbols from the completion input
80// (case-insensitive, in any order or places).
81//
82// ```
83// fn main() {
84// pda<|>
85// }
86// # pub mod std { pub mod marker { pub struct PhantomData { } } }
87// ```
88// ->
89// ```
90// use std::marker::PhantomData;
91//
92// fn main() {
93// PhantomData
94// }
95// # pub mod std { pub mod marker { pub struct PhantomData { } } }
96// ```
97//
98// .Fuzzy search details
99//
100// To avoid an excessive amount of the results returned, completion input is checked for inclusion in the identifiers only
101// (i.e. in `HashMap` in the `std::collections::HashMap` path), also not in the module indentifiers.
102//
103// .Merge Behaviour
104//
105// It is possible to configure how use-trees are merged with the `importMergeBehaviour` setting.
106// Mimics the corresponding behaviour of the `Auto Import` feature.
107//
108// .LSP and performance implications
109//
110// The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits`
111// (case sensitive) resolve client capability in its client capabilities.
112// This way the server is able to defer the costly computations, doing them for a selected completion item only.
113// For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones,
114// which might be slow ergo the feature is automatically disabled.
115//
116// .Feature toggle
117//
118// The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.enableAutoimportCompletions` flag.
119// Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding
120// capability enabled.
76fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { 121fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
77 let _p = profile::span("fuzzy_completion"); 122 let _p = profile::span("fuzzy_completion");
123 let potential_import_name = ctx.token.to_string();
124
78 let current_module = ctx.scope.module()?; 125 let current_module = ctx.scope.module()?;
79 let anchor = ctx.name_ref_syntax.as_ref()?; 126 let anchor = ctx.name_ref_syntax.as_ref()?;
80 let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?; 127 let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?;
81 128
82 let potential_import_name = ctx.token.to_string();
83
84 let possible_imports = imports_locator::find_similar_imports( 129 let possible_imports = imports_locator::find_similar_imports(
85 &ctx.sema, 130 &ctx.sema,
86 ctx.krate?, 131 ctx.krate?,
132 Some(100),
87 &potential_import_name, 133 &potential_import_name,
88 50,
89 true, 134 true,
90 ) 135 )
91 .filter_map(|import_candidate| { 136 .filter_map(|import_candidate| {
@@ -99,13 +144,14 @@ fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()
99 }) 144 })
100 }) 145 })
101 .filter(|(mod_path, _)| mod_path.len() > 1) 146 .filter(|(mod_path, _)| mod_path.len() > 1)
102 .take(20)
103 .filter_map(|(import_path, definition)| { 147 .filter_map(|(import_path, definition)| {
104 render_resolution_with_import( 148 render_resolution_with_import(
105 RenderContext::new(ctx), 149 RenderContext::new(ctx),
106 import_path.clone(), 150 ImportEdit {
107 import_scope.clone(), 151 import_path: import_path.clone(),
108 ctx.config.merge, 152 import_scope: import_scope.clone(),
153 merge_behaviour: ctx.config.merge,
154 },
109 &definition, 155 &definition,
110 ) 156 )
111 }); 157 });
@@ -120,8 +166,8 @@ mod tests {
120 use test_utils::mark; 166 use test_utils::mark;
121 167
122 use crate::{ 168 use crate::{
123 test_utils::{check_edit, completion_list}, 169 test_utils::{check_edit, check_edit_with_config, completion_list},
124 CompletionKind, 170 CompletionConfig, CompletionKind,
125 }; 171 };
126 172
127 fn check(ra_fixture: &str, expect: Expect) { 173 fn check(ra_fixture: &str, expect: Expect) {
@@ -730,7 +776,13 @@ impl My<|>
730 776
731 #[test] 777 #[test]
732 fn function_fuzzy_completion() { 778 fn function_fuzzy_completion() {
733 check_edit( 779 let mut completion_config = CompletionConfig::default();
780 completion_config
781 .active_resolve_capabilities
782 .insert(crate::CompletionResolveCapability::AdditionalTextEdits);
783
784 check_edit_with_config(
785 completion_config,
734 "stdin", 786 "stdin",
735 r#" 787 r#"
736//- /lib.rs crate:dep 788//- /lib.rs crate:dep
@@ -755,7 +807,13 @@ fn main() {
755 807
756 #[test] 808 #[test]
757 fn macro_fuzzy_completion() { 809 fn macro_fuzzy_completion() {
758 check_edit( 810 let mut completion_config = CompletionConfig::default();
811 completion_config
812 .active_resolve_capabilities
813 .insert(crate::CompletionResolveCapability::AdditionalTextEdits);
814
815 check_edit_with_config(
816 completion_config,
759 "macro_with_curlies!", 817 "macro_with_curlies!",
760 r#" 818 r#"
761//- /lib.rs crate:dep 819//- /lib.rs crate:dep
@@ -782,7 +840,13 @@ fn main() {
782 840
783 #[test] 841 #[test]
784 fn struct_fuzzy_completion() { 842 fn struct_fuzzy_completion() {
785 check_edit( 843 let mut completion_config = CompletionConfig::default();
844 completion_config
845 .active_resolve_capabilities
846 .insert(crate::CompletionResolveCapability::AdditionalTextEdits);
847
848 check_edit_with_config(
849 completion_config,
786 "ThirdStruct", 850 "ThirdStruct",
787 r#" 851 r#"
788//- /lib.rs crate:dep 852//- /lib.rs crate:dep
diff --git a/crates/completion/src/config.rs b/crates/completion/src/config.rs
index 654a76f7b..5175b9d69 100644
--- a/crates/completion/src/config.rs
+++ b/crates/completion/src/config.rs
@@ -5,21 +5,42 @@
5//! completions if we are allowed to. 5//! completions if we are allowed to.
6 6
7use ide_db::helpers::insert_use::MergeBehaviour; 7use ide_db::helpers::insert_use::MergeBehaviour;
8use rustc_hash::FxHashSet;
8 9
9#[derive(Clone, Debug, PartialEq, Eq)] 10#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct CompletionConfig { 11pub struct CompletionConfig {
11 pub enable_postfix_completions: bool, 12 pub enable_postfix_completions: bool,
12 pub enable_experimental_completions: bool, 13 pub enable_autoimport_completions: bool,
13 pub add_call_parenthesis: bool, 14 pub add_call_parenthesis: bool,
14 pub add_call_argument_snippets: bool, 15 pub add_call_argument_snippets: bool,
15 pub snippet_cap: Option<SnippetCap>, 16 pub snippet_cap: Option<SnippetCap>,
16 pub merge: Option<MergeBehaviour>, 17 pub merge: Option<MergeBehaviour>,
18 /// A set of capabilities, enabled on the client and supported on the server.
19 pub active_resolve_capabilities: FxHashSet<CompletionResolveCapability>,
20}
21
22/// A resolve capability, supported on the server.
23/// If the client registers any completion resolve capabilities,
24/// the server is able to render completion items' corresponding fields later,
25/// not during an initial completion item request.
26/// See https://github.com/rust-analyzer/rust-analyzer/issues/6366 for more details.
27#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
28pub enum CompletionResolveCapability {
29 Documentation,
30 Detail,
31 AdditionalTextEdits,
17} 32}
18 33
19impl CompletionConfig { 34impl CompletionConfig {
20 pub fn allow_snippets(&mut self, yes: bool) { 35 pub fn allow_snippets(&mut self, yes: bool) {
21 self.snippet_cap = if yes { Some(SnippetCap { _private: () }) } else { None } 36 self.snippet_cap = if yes { Some(SnippetCap { _private: () }) } else { None }
22 } 37 }
38
39 /// Whether the completions' additional edits are calculated when sending an initional completions list
40 /// or later, in a separate resolve request.
41 pub fn resolve_additional_edits_lazily(&self) -> bool {
42 self.active_resolve_capabilities.contains(&CompletionResolveCapability::AdditionalTextEdits)
43 }
23} 44}
24 45
25#[derive(Clone, Copy, Debug, PartialEq, Eq)] 46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -31,11 +52,12 @@ impl Default for CompletionConfig {
31 fn default() -> Self { 52 fn default() -> Self {
32 CompletionConfig { 53 CompletionConfig {
33 enable_postfix_completions: true, 54 enable_postfix_completions: true,
34 enable_experimental_completions: true, 55 enable_autoimport_completions: true,
35 add_call_parenthesis: true, 56 add_call_parenthesis: true,
36 add_call_argument_snippets: true, 57 add_call_argument_snippets: true,
37 snippet_cap: Some(SnippetCap { _private: () }), 58 snippet_cap: Some(SnippetCap { _private: () }),
38 merge: Some(MergeBehaviour::Full), 59 merge: Some(MergeBehaviour::Full),
60 active_resolve_capabilities: FxHashSet::default(),
39 } 61 }
40 } 62 }
41} 63}
diff --git a/crates/completion/src/item.rs b/crates/completion/src/item.rs
index e85549fef..bd94402d7 100644
--- a/crates/completion/src/item.rs
+++ b/crates/completion/src/item.rs
@@ -15,6 +15,7 @@ use crate::config::SnippetCap;
15/// `CompletionItem` describes a single completion variant in the editor pop-up. 15/// `CompletionItem` describes a single completion variant in the editor pop-up.
16/// It is basically a POD with various properties. To construct a 16/// It is basically a POD with various properties. To construct a
17/// `CompletionItem`, use `new` method and the `Builder` struct. 17/// `CompletionItem`, use `new` method and the `Builder` struct.
18#[derive(Clone)]
18pub struct CompletionItem { 19pub struct CompletionItem {
19 /// Used only internally in tests, to check only specific kind of 20 /// Used only internally in tests, to check only specific kind of
20 /// completion (postfix, keyword, reference, etc). 21 /// completion (postfix, keyword, reference, etc).
@@ -65,6 +66,9 @@ pub struct CompletionItem {
65 /// Indicates that a reference or mutable reference to this variable is a 66 /// Indicates that a reference or mutable reference to this variable is a
66 /// possible match. 67 /// possible match.
67 ref_match: Option<(Mutability, CompletionScore)>, 68 ref_match: Option<(Mutability, CompletionScore)>,
69
70 /// The import data to add to completion's edits.
71 import_to_add: Option<ImportEdit>,
68} 72}
69 73
70// We use custom debug for CompletionItem to make snapshot tests more readable. 74// We use custom debug for CompletionItem to make snapshot tests more readable.
@@ -256,14 +260,37 @@ impl CompletionItem {
256 pub fn ref_match(&self) -> Option<(Mutability, CompletionScore)> { 260 pub fn ref_match(&self) -> Option<(Mutability, CompletionScore)> {
257 self.ref_match 261 self.ref_match
258 } 262 }
263
264 pub fn import_to_add(&self) -> Option<&ImportEdit> {
265 self.import_to_add.as_ref()
266 }
259} 267}
260 268
261/// An extra import to add after the completion is applied. 269/// An extra import to add after the completion is applied.
262#[derive(Clone)] 270#[derive(Debug, Clone)]
263pub(crate) struct ImportToAdd { 271pub struct ImportEdit {
264 pub(crate) import_path: ModPath, 272 pub import_path: ModPath,
265 pub(crate) import_scope: ImportScope, 273 pub import_scope: ImportScope,
266 pub(crate) merge_behaviour: Option<MergeBehaviour>, 274 pub merge_behaviour: Option<MergeBehaviour>,
275}
276
277impl ImportEdit {
278 /// Attempts to insert the import to the given scope, producing a text edit.
279 /// May return no edit in edge cases, such as scope already containing the import.
280 pub fn to_text_edit(&self) -> Option<TextEdit> {
281 let _p = profile::span("ImportEdit::to_text_edit");
282
283 let rewriter = insert_use::insert_use(
284 &self.import_scope,
285 mod_path_to_ast(&self.import_path),
286 self.merge_behaviour,
287 );
288 let old_ast = rewriter.rewrite_root()?;
289 let mut import_insert = TextEdit::builder();
290 algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert);
291
292 Some(import_insert.finish())
293 }
267} 294}
268 295
269/// A helper to make `CompletionItem`s. 296/// A helper to make `CompletionItem`s.
@@ -272,7 +299,7 @@ pub(crate) struct ImportToAdd {
272pub(crate) struct Builder { 299pub(crate) struct Builder {
273 source_range: TextRange, 300 source_range: TextRange,
274 completion_kind: CompletionKind, 301 completion_kind: CompletionKind,
275 import_to_add: Option<ImportToAdd>, 302 import_to_add: Option<ImportEdit>,
276 label: String, 303 label: String,
277 insert_text: Option<String>, 304 insert_text: Option<String>,
278 insert_text_format: InsertTextFormat, 305 insert_text_format: InsertTextFormat,
@@ -294,11 +321,9 @@ impl Builder {
294 let mut label = self.label; 321 let mut label = self.label;
295 let mut lookup = self.lookup; 322 let mut lookup = self.lookup;
296 let mut insert_text = self.insert_text; 323 let mut insert_text = self.insert_text;
297 let mut text_edits = TextEdit::builder();
298 324
299 if let Some(import_data) = self.import_to_add { 325 if let Some(import_to_add) = self.import_to_add.as_ref() {
300 let import = mod_path_to_ast(&import_data.import_path); 326 let mut import_path_without_last_segment = import_to_add.import_path.to_owned();
301 let mut import_path_without_last_segment = import_data.import_path;
302 let _ = import_path_without_last_segment.segments.pop(); 327 let _ = import_path_without_last_segment.segments.pop();
303 328
304 if !import_path_without_last_segment.segments.is_empty() { 329 if !import_path_without_last_segment.segments.is_empty() {
@@ -310,32 +335,20 @@ impl Builder {
310 } 335 }
311 label = format!("{}::{}", import_path_without_last_segment, label); 336 label = format!("{}::{}", import_path_without_last_segment, label);
312 } 337 }
313
314 let rewriter = insert_use::insert_use(
315 &import_data.import_scope,
316 import,
317 import_data.merge_behaviour,
318 );
319 if let Some(old_ast) = rewriter.rewrite_root() {
320 algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut text_edits);
321 }
322 } 338 }
323 339
324 let original_edit = match self.text_edit { 340 let text_edit = match self.text_edit {
325 Some(it) => it, 341 Some(it) => it,
326 None => { 342 None => {
327 TextEdit::replace(self.source_range, insert_text.unwrap_or_else(|| label.clone())) 343 TextEdit::replace(self.source_range, insert_text.unwrap_or_else(|| label.clone()))
328 } 344 }
329 }; 345 };
330 346
331 let mut resulting_edit = text_edits.finish();
332 resulting_edit.union(original_edit).expect("Failed to unite text edits");
333
334 CompletionItem { 347 CompletionItem {
335 source_range: self.source_range, 348 source_range: self.source_range,
336 label, 349 label,
337 insert_text_format: self.insert_text_format, 350 insert_text_format: self.insert_text_format,
338 text_edit: resulting_edit, 351 text_edit,
339 detail: self.detail, 352 detail: self.detail,
340 documentation: self.documentation, 353 documentation: self.documentation,
341 lookup, 354 lookup,
@@ -345,6 +358,7 @@ impl Builder {
345 trigger_call_info: self.trigger_call_info.unwrap_or(false), 358 trigger_call_info: self.trigger_call_info.unwrap_or(false),
346 score: self.score, 359 score: self.score,
347 ref_match: self.ref_match, 360 ref_match: self.ref_match,
361 import_to_add: self.import_to_add,
348 } 362 }
349 } 363 }
350 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { 364 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
@@ -407,7 +421,7 @@ impl Builder {
407 self.trigger_call_info = Some(true); 421 self.trigger_call_info = Some(true);
408 self 422 self
409 } 423 }
410 pub(crate) fn add_import(mut self, import_to_add: Option<ImportToAdd>) -> Builder { 424 pub(crate) fn add_import(mut self, import_to_add: Option<ImportEdit>) -> Builder {
411 self.import_to_add = import_to_add; 425 self.import_to_add = import_to_add;
412 self 426 self
413 } 427 }
diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs
index 1ec2e9be7..f60f87243 100644
--- a/crates/completion/src/lib.rs
+++ b/crates/completion/src/lib.rs
@@ -11,14 +11,17 @@ mod render;
11 11
12mod completions; 12mod completions;
13 13
14use ide_db::base_db::FilePosition; 14use ide_db::{
15use ide_db::RootDatabase; 15 base_db::FilePosition, helpers::insert_use::ImportScope, imports_locator, RootDatabase,
16};
17use syntax::AstNode;
18use text_edit::TextEdit;
16 19
17use crate::{completions::Completions, context::CompletionContext, item::CompletionKind}; 20use crate::{completions::Completions, context::CompletionContext, item::CompletionKind};
18 21
19pub use crate::{ 22pub use crate::{
20 config::CompletionConfig, 23 config::{CompletionConfig, CompletionResolveCapability},
21 item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat}, 24 item::{CompletionItem, CompletionItemKind, CompletionScore, ImportEdit, InsertTextFormat},
22}; 25};
23 26
24//FIXME: split the following feature into fine-grained features. 27//FIXME: split the following feature into fine-grained features.
@@ -70,12 +73,9 @@ pub use crate::{
70// } 73// }
71// ``` 74// ```
72// 75//
73// And experimental completions, enabled with the `rust-analyzer.completion.enableExperimental` setting. 76// And the auto import completions, enabled with the `rust-analyzer.completion.autoimport.enable` setting and the corresponding LSP client capabilities.
74// This flag enables or disables: 77// Those are the additional completion options with automatic `use` import and options from all project importable items,
75// 78// fuzzy matched agains the completion imput.
76// - Auto import: additional completion options with automatic `use` import and options from all project importable items, matched for the input
77//
78// Experimental completions might cause issues with performance and completion list look.
79 79
80/// Main entry point for completion. We run completion as a two-phase process. 80/// Main entry point for completion. We run completion as a two-phase process.
81/// 81///
@@ -131,6 +131,33 @@ pub fn completions(
131 Some(acc) 131 Some(acc)
132} 132}
133 133
134/// Resolves additional completion data at the position given.
135pub fn resolve_completion_edits(
136 db: &RootDatabase,
137 config: &CompletionConfig,
138 position: FilePosition,
139 full_import_path: &str,
140 imported_name: &str,
141) -> Option<Vec<TextEdit>> {
142 let ctx = CompletionContext::new(db, position, config)?;
143 let anchor = ctx.name_ref_syntax.as_ref()?;
144 let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?;
145
146 let current_module = ctx.sema.scope(anchor.syntax()).module()?;
147 let current_crate = current_module.krate();
148
149 let import_path = imports_locator::find_exact_imports(&ctx.sema, current_crate, imported_name)
150 .filter_map(|candidate| {
151 let item: hir::ItemInNs = candidate.either(Into::into, Into::into);
152 current_module.find_use_path(db, item)
153 })
154 .find(|mod_path| mod_path.to_string() == full_import_path)?;
155
156 ImportEdit { import_path, import_scope, merge_behaviour: config.merge }
157 .to_text_edit()
158 .map(|edit| vec![edit])
159}
160
134#[cfg(test)] 161#[cfg(test)]
135mod tests { 162mod tests {
136 use crate::config::CompletionConfig; 163 use crate::config::CompletionConfig;
diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs
index 504757a6a..b940388df 100644
--- a/crates/completion/src/render.rs
+++ b/crates/completion/src/render.rs
@@ -9,14 +9,13 @@ pub(crate) mod type_alias;
9 9
10mod builder_ext; 10mod builder_ext;
11 11
12use hir::{Documentation, HasAttrs, HirDisplay, ModPath, Mutability, ScopeDef, Type}; 12use hir::{Documentation, HasAttrs, HirDisplay, Mutability, ScopeDef, Type};
13use ide_db::helpers::insert_use::{ImportScope, MergeBehaviour};
14use ide_db::RootDatabase; 13use ide_db::RootDatabase;
15use syntax::TextRange; 14use syntax::TextRange;
16use test_utils::mark; 15use test_utils::mark;
17 16
18use crate::{ 17use crate::{
19 config::SnippetCap, item::ImportToAdd, CompletionContext, CompletionItem, CompletionItemKind, 18 config::SnippetCap, item::ImportEdit, CompletionContext, CompletionItem, CompletionItemKind,
20 CompletionKind, CompletionScore, 19 CompletionKind, CompletionScore,
21}; 20};
22 21
@@ -48,15 +47,12 @@ pub(crate) fn render_resolution<'a>(
48 47
49pub(crate) fn render_resolution_with_import<'a>( 48pub(crate) fn render_resolution_with_import<'a>(
50 ctx: RenderContext<'a>, 49 ctx: RenderContext<'a>,
51 import_path: ModPath, 50 import_edit: ImportEdit,
52 import_scope: ImportScope,
53 merge_behaviour: Option<MergeBehaviour>,
54 resolution: &ScopeDef, 51 resolution: &ScopeDef,
55) -> Option<CompletionItem> { 52) -> Option<CompletionItem> {
56 let local_name = import_path.segments.last()?.to_string();
57 Render::new(ctx).render_resolution( 53 Render::new(ctx).render_resolution(
58 local_name, 54 import_edit.import_path.segments.last()?.to_string(),
59 Some(ImportToAdd { import_path, import_scope, merge_behaviour }), 55 Some(import_edit),
60 resolution, 56 resolution,
61 ) 57 )
62} 58}
@@ -147,7 +143,7 @@ impl<'a> Render<'a> {
147 fn render_resolution( 143 fn render_resolution(
148 self, 144 self,
149 local_name: String, 145 local_name: String,
150 import_to_add: Option<ImportToAdd>, 146 import_to_add: Option<ImportEdit>,
151 resolution: &ScopeDef, 147 resolution: &ScopeDef,
152 ) -> Option<CompletionItem> { 148 ) -> Option<CompletionItem> {
153 let _p = profile::span("render_resolution"); 149 let _p = profile::span("render_resolution");
@@ -451,28 +447,6 @@ fn main() { let _: m::Spam = S<|> }
451 kind: Module, 447 kind: Module,
452 }, 448 },
453 CompletionItem { 449 CompletionItem {
454 label: "m::Spam",
455 source_range: 75..76,
456 text_edit: TextEdit {
457 indels: [
458 Indel {
459 insert: "use m::Spam;",
460 delete: 0..0,
461 },
462 Indel {
463 insert: "\n\n",
464 delete: 0..0,
465 },
466 Indel {
467 insert: "Spam",
468 delete: 75..76,
469 },
470 ],
471 },
472 kind: Enum,
473 lookup: "Spam",
474 },
475 CompletionItem {
476 label: "m::Spam::Foo", 450 label: "m::Spam::Foo",
477 source_range: 75..76, 451 source_range: 75..76,
478 delete: 75..76, 452 delete: 75..76,
diff --git a/crates/completion/src/render/enum_variant.rs b/crates/completion/src/render/enum_variant.rs
index f4bd02f25..8e0fea6c0 100644
--- a/crates/completion/src/render/enum_variant.rs
+++ b/crates/completion/src/render/enum_variant.rs
@@ -5,13 +5,13 @@ use itertools::Itertools;
5use test_utils::mark; 5use test_utils::mark;
6 6
7use crate::{ 7use crate::{
8 item::{CompletionItem, CompletionItemKind, CompletionKind, ImportToAdd}, 8 item::{CompletionItem, CompletionItemKind, CompletionKind, ImportEdit},
9 render::{builder_ext::Params, RenderContext}, 9 render::{builder_ext::Params, RenderContext},
10}; 10};
11 11
12pub(crate) fn render_enum_variant<'a>( 12pub(crate) fn render_enum_variant<'a>(
13 ctx: RenderContext<'a>, 13 ctx: RenderContext<'a>,
14 import_to_add: Option<ImportToAdd>, 14 import_to_add: Option<ImportEdit>,
15 local_name: Option<String>, 15 local_name: Option<String>,
16 variant: hir::EnumVariant, 16 variant: hir::EnumVariant,
17 path: Option<ModPath>, 17 path: Option<ModPath>,
@@ -62,7 +62,7 @@ impl<'a> EnumVariantRender<'a> {
62 } 62 }
63 } 63 }
64 64
65 fn render(self, import_to_add: Option<ImportToAdd>) -> CompletionItem { 65 fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem {
66 let mut builder = CompletionItem::new( 66 let mut builder = CompletionItem::new(
67 CompletionKind::Reference, 67 CompletionKind::Reference,
68 self.ctx.source_range(), 68 self.ctx.source_range(),
diff --git a/crates/completion/src/render/function.rs b/crates/completion/src/render/function.rs
index 00e3eb203..d16005249 100644
--- a/crates/completion/src/render/function.rs
+++ b/crates/completion/src/render/function.rs
@@ -5,13 +5,13 @@ use syntax::{ast::Fn, display::function_declaration};
5use test_utils::mark; 5use test_utils::mark;
6 6
7use crate::{ 7use crate::{
8 item::{CompletionItem, CompletionItemKind, CompletionKind, ImportToAdd}, 8 item::{CompletionItem, CompletionItemKind, CompletionKind, ImportEdit},
9 render::{builder_ext::Params, RenderContext}, 9 render::{builder_ext::Params, RenderContext},
10}; 10};
11 11
12pub(crate) fn render_fn<'a>( 12pub(crate) fn render_fn<'a>(
13 ctx: RenderContext<'a>, 13 ctx: RenderContext<'a>,
14 import_to_add: Option<ImportToAdd>, 14 import_to_add: Option<ImportEdit>,
15 local_name: Option<String>, 15 local_name: Option<String>,
16 fn_: hir::Function, 16 fn_: hir::Function,
17) -> CompletionItem { 17) -> CompletionItem {
@@ -39,7 +39,7 @@ impl<'a> FunctionRender<'a> {
39 FunctionRender { ctx, name, func: fn_, ast_node } 39 FunctionRender { ctx, name, func: fn_, ast_node }
40 } 40 }
41 41
42 fn render(self, import_to_add: Option<ImportToAdd>) -> CompletionItem { 42 fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem {
43 let params = self.params(); 43 let params = self.params();
44 CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), self.name.clone()) 44 CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), self.name.clone())
45 .kind(self.kind()) 45 .kind(self.kind())
diff --git a/crates/completion/src/render/macro_.rs b/crates/completion/src/render/macro_.rs
index b4ab32c6e..eb3209bee 100644
--- a/crates/completion/src/render/macro_.rs
+++ b/crates/completion/src/render/macro_.rs
@@ -5,13 +5,13 @@ use syntax::display::macro_label;
5use test_utils::mark; 5use test_utils::mark;
6 6
7use crate::{ 7use crate::{
8 item::{CompletionItem, CompletionItemKind, CompletionKind, ImportToAdd}, 8 item::{CompletionItem, CompletionItemKind, CompletionKind, ImportEdit},
9 render::RenderContext, 9 render::RenderContext,
10}; 10};
11 11
12pub(crate) fn render_macro<'a>( 12pub(crate) fn render_macro<'a>(
13 ctx: RenderContext<'a>, 13 ctx: RenderContext<'a>,
14 import_to_add: Option<ImportToAdd>, 14 import_to_add: Option<ImportEdit>,
15 name: String, 15 name: String,
16 macro_: hir::MacroDef, 16 macro_: hir::MacroDef,
17) -> Option<CompletionItem> { 17) -> Option<CompletionItem> {
@@ -38,7 +38,7 @@ impl<'a> MacroRender<'a> {
38 MacroRender { ctx, name, macro_, docs, bra, ket } 38 MacroRender { ctx, name, macro_, docs, bra, ket }
39 } 39 }
40 40
41 fn render(&self, import_to_add: Option<ImportToAdd>) -> Option<CompletionItem> { 41 fn render(&self, import_to_add: Option<ImportEdit>) -> Option<CompletionItem> {
42 // FIXME: Currently proc-macro do not have ast-node, 42 // FIXME: Currently proc-macro do not have ast-node,
43 // such that it does not have source 43 // such that it does not have source
44 if self.macro_.is_proc_macro() { 44 if self.macro_.is_proc_macro() {
diff --git a/crates/completion/src/test_utils.rs b/crates/completion/src/test_utils.rs
index 4c1b1a839..25f5f4924 100644
--- a/crates/completion/src/test_utils.rs
+++ b/crates/completion/src/test_utils.rs
@@ -96,7 +96,16 @@ pub(crate) fn check_edit_with_config(
96 .collect_tuple() 96 .collect_tuple()
97 .unwrap_or_else(|| panic!("can't find {:?} completion in {:#?}", what, completions)); 97 .unwrap_or_else(|| panic!("can't find {:?} completion in {:#?}", what, completions));
98 let mut actual = db.file_text(position.file_id).to_string(); 98 let mut actual = db.file_text(position.file_id).to_string();
99 completion.text_edit().apply(&mut actual); 99
100 let mut combined_edit = completion.text_edit().to_owned();
101 if let Some(import_text_edit) = completion.import_to_add().and_then(|edit| edit.to_text_edit())
102 {
103 combined_edit.union(import_text_edit).expect(
104 "Failed to apply completion resolve changes: change ranges overlap, but should not",
105 )
106 }
107
108 combined_edit.apply(&mut actual);
100 assert_eq_text!(&ra_fixture_after, &actual) 109 assert_eq_text!(&ra_fixture_after, &actual)
101} 110}
102 111
diff --git a/crates/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.
2use hir_def::{ 2use 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};
5use hir_expand::hygiene::Hygiene; 8use hir_expand::hygiene::Hygiene;
6use hir_ty::db::HirDatabase; 9use 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
3pub use hir_def::db::{ 3pub 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};
12pub use hir_expand::db::{ 12pub 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
45pub use hir_def::{ 45pub 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};
5use cfg::{CfgExpr, CfgOptions}; 5use cfg::{CfgExpr, CfgOptions};
6use either::Either; 6use either::Either;
7use hir_expand::{hygiene::Hygiene, AstId, InFile}; 7use hir_expand::{hygiene::Hygiene, AstId, InFile};
8use itertools::Itertools;
8use mbe::ast_to_token_tree; 9use mbe::ast_to_token_tree;
9use syntax::{ 10use syntax::{
10 ast::{self, AstNode, AttrsOwner}, 11 ast::{self, AstNode, AttrsOwner},
11 SmolStr, 12 AstToken, SmolStr,
12}; 13};
13use tt::Subtree; 14use 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)]
27pub struct Documentation(String);
28
29impl Documentation {
30 pub fn as_str(&self) -> &str {
31 &self.0
32 }
33}
34
35impl 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)]
25pub struct Attrs { 42pub 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
6use std::sync::Arc;
7
8use either::Either;
9use itertools::Itertools;
10use syntax::{ast, SmolStr};
11
12use crate::{
13 db::DefDatabase,
14 src::{HasChildSource, HasSource},
15 AdtId, AttrDefId, Lookup,
16};
17
18/// Holds documentation
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct Documentation(Arc<str>);
21
22impl Into<String> for Documentation {
23 fn into(self) -> String {
24 self.as_str().to_owned()
25 }
26}
27
28impl 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
81pub(crate) fn docs_from_ast<N>(node: &N) -> Option<Documentation>
82where
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
91fn 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
107fn 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;
31pub mod data; 31pub mod data;
32pub mod generics; 32pub mod generics;
33pub mod lang_item; 33pub mod lang_item;
34pub mod docs;
35 34
36pub mod expr; 35pub mod expr;
37pub mod body; 36pub 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"]
376mod foo; 376mod 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 @@
1use hir::{ 1use 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};
5use ide_db::base_db::SourceDatabase; 5use ide_db::base_db::SourceDatabase;
6use ide_db::{ 6use 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};
82pub use completion::{ 82pub use completion::{
83 CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat, 83 CompletionConfig, CompletionItem, CompletionItemKind, CompletionResolveCapability,
84 CompletionScore, ImportEdit, InsertTextFormat,
84}; 85};
85pub use ide_db::{ 86pub use ide_db::{
86 call_info::CallInfo, 87 call_info::CallInfo,
@@ -468,6 +469,27 @@ impl Analysis {
468 self.with_db(|db| completion::completions(db, config, position).map(Into::into)) 469 self.with_db(|db| completion::completions(db, config, position).map(Into::into))
469 } 470 }
470 471
472 /// Resolves additional completion data at the position given.
473 pub fn resolve_completion_edits(
474 &self,
475 config: &CompletionConfig,
476 position: FilePosition,
477 full_import_path: &str,
478 imported_name: &str,
479 ) -> Cancelable<Vec<TextEdit>> {
480 Ok(self
481 .with_db(|db| {
482 completion::resolve_completion_edits(
483 db,
484 config,
485 position,
486 full_import_path,
487 imported_name,
488 )
489 })?
490 .unwrap_or_default())
491 }
492
471 /// Computes resolved assists with source changes for the given position. 493 /// Computes resolved assists with source changes for the given position.
472 pub fn resolved_assists( 494 pub fn resolved_assists(
473 &self, 495 &self,
diff --git a/crates/ide_db/src/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>(
34pub fn find_similar_imports<'a>( 34pub fn find_similar_imports<'a>(
35 sema: &Semantics<'a, RootDatabase>, 35 sema: &Semantics<'a, RootDatabase>,
36 krate: Crate, 36 krate: Crate,
37 limit: Option<usize>,
37 name_to_import: &str, 38 name_to_import: &str,
38 limit: usize,
39 ignore_modules: bool, 39 ignore_modules: bool,
40) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> { 40) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> {
41 let _p = profile::span("find_similar_imports"); 41 let _p = profile::span("find_similar_imports");
42 42
43 let mut external_query = import_map::Query::new(name_to_import).limit(limit); 43 let mut external_query = import_map::Query::new(name_to_import);
44 if ignore_modules { 44 if ignore_modules {
45 external_query = external_query.exclude_import_kind(import_map::ImportKind::Module); 45 external_query = external_query.exclude_import_kind(import_map::ImportKind::Module);
46 } 46 }
47 47
48 find_imports( 48 let mut local_query = symbol_index::Query::new(name_to_import.to_string());
49 sema, 49
50 krate, 50 if let Some(limit) = limit {
51 { 51 local_query.limit(limit);
52 let mut local_query = symbol_index::Query::new(name_to_import.to_string()); 52 external_query = external_query.limit(limit);
53 local_query.limit(limit); 53 }
54 local_query 54
55 }, 55 find_imports(sema, krate, local_query, external_query)
56 external_query,
57 )
58} 56}
59 57
60fn find_imports<'a>( 58fn find_imports<'a>(
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs
index c7203451c..5e4c22bc5 100644
--- a/crates/rust-analyzer/src/caps.rs
+++ b/crates/rust-analyzer/src/caps.rs
@@ -1,6 +1,7 @@
1//! Advertizes the capabilities of the LSP Server. 1//! Advertizes the capabilities of the LSP Server.
2use std::env; 2use std::env;
3 3
4use ide::CompletionResolveCapability;
4use lsp_types::{ 5use lsp_types::{
5 CallHierarchyServerCapability, ClientCapabilities, CodeActionKind, CodeActionOptions, 6 CallHierarchyServerCapability, ClientCapabilities, CodeActionKind, CodeActionOptions,
6 CodeActionProviderCapability, CodeLensOptions, CompletionOptions, 7 CodeActionProviderCapability, CodeLensOptions, CompletionOptions,
@@ -11,6 +12,7 @@ use lsp_types::{
11 TextDocumentSyncKind, TextDocumentSyncOptions, TypeDefinitionProviderCapability, 12 TextDocumentSyncKind, TextDocumentSyncOptions, TypeDefinitionProviderCapability,
12 WorkDoneProgressOptions, 13 WorkDoneProgressOptions,
13}; 14};
15use rustc_hash::FxHashSet;
14use serde_json::json; 16use serde_json::json;
15 17
16use crate::semantic_tokens; 18use crate::semantic_tokens;
@@ -30,7 +32,7 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti
30 })), 32 })),
31 hover_provider: Some(HoverProviderCapability::Simple(true)), 33 hover_provider: Some(HoverProviderCapability::Simple(true)),
32 completion_provider: Some(CompletionOptions { 34 completion_provider: Some(CompletionOptions {
33 resolve_provider: None, 35 resolve_provider: completions_resolve_provider(client_caps),
34 trigger_characters: Some(vec![":".to_string(), ".".to_string()]), 36 trigger_characters: Some(vec![":".to_string(), ".".to_string()]),
35 work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None }, 37 work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
36 }), 38 }),
@@ -93,6 +95,40 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti
93 } 95 }
94} 96}
95 97
98fn completions_resolve_provider(client_caps: &ClientCapabilities) -> Option<bool> {
99 if enabled_completions_resolve_capabilities(client_caps)?.is_empty() {
100 log::info!("No `additionalTextEdits` completion resolve capability was found in the client capabilities, autoimport completion is disabled");
101 None
102 } else {
103 Some(true)
104 }
105}
106
107/// Parses client capabilities and returns all completion resolve capabilities rust-analyzer supports.
108pub(crate) fn enabled_completions_resolve_capabilities(
109 caps: &ClientCapabilities,
110) -> Option<FxHashSet<CompletionResolveCapability>> {
111 Some(
112 caps.text_document
113 .as_ref()?
114 .completion
115 .as_ref()?
116 .completion_item
117 .as_ref()?
118 .resolve_support
119 .as_ref()?
120 .properties
121 .iter()
122 .filter_map(|cap_string| match cap_string.as_str() {
123 "additionalTextEdits" => Some(CompletionResolveCapability::AdditionalTextEdits),
124 "detail" => Some(CompletionResolveCapability::Detail),
125 "documentation" => Some(CompletionResolveCapability::Documentation),
126 _unsupported => None,
127 })
128 .collect(),
129 )
130}
131
96fn code_action_capabilities(client_caps: &ClientCapabilities) -> CodeActionProviderCapability { 132fn code_action_capabilities(client_caps: &ClientCapabilities) -> CodeActionProviderCapability {
97 client_caps 133 client_caps
98 .text_document 134 .text_document
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 59269a74b..5243b50c8 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -19,7 +19,7 @@ use rustc_hash::FxHashSet;
19use serde::Deserialize; 19use serde::Deserialize;
20use vfs::AbsPathBuf; 20use vfs::AbsPathBuf;
21 21
22use crate::diagnostics::DiagnosticsMapConfig; 22use crate::{caps::enabled_completions_resolve_capabilities, diagnostics::DiagnosticsMapConfig};
23 23
24#[derive(Debug, Clone)] 24#[derive(Debug, Clone)]
25pub struct Config { 25pub struct Config {
@@ -182,7 +182,7 @@ impl Config {
182 }, 182 },
183 completion: CompletionConfig { 183 completion: CompletionConfig {
184 enable_postfix_completions: true, 184 enable_postfix_completions: true,
185 enable_experimental_completions: true, 185 enable_autoimport_completions: true,
186 add_call_parenthesis: true, 186 add_call_parenthesis: true,
187 add_call_argument_snippets: true, 187 add_call_argument_snippets: true,
188 ..CompletionConfig::default() 188 ..CompletionConfig::default()
@@ -305,7 +305,7 @@ impl Config {
305 }; 305 };
306 306
307 self.completion.enable_postfix_completions = data.completion_postfix_enable; 307 self.completion.enable_postfix_completions = data.completion_postfix_enable;
308 self.completion.enable_experimental_completions = data.completion_enableExperimental; 308 self.completion.enable_autoimport_completions = data.completion_autoimport_enable;
309 self.completion.add_call_parenthesis = data.completion_addCallParenthesis; 309 self.completion.add_call_parenthesis = data.completion_addCallParenthesis;
310 self.completion.add_call_argument_snippets = data.completion_addCallArgumentSnippets; 310 self.completion.add_call_argument_snippets = data.completion_addCallArgumentSnippets;
311 self.completion.merge = self.assist.insert_use.merge; 311 self.completion.merge = self.assist.insert_use.merge;
@@ -388,6 +388,8 @@ impl Config {
388 } 388 }
389 389
390 self.completion.allow_snippets(false); 390 self.completion.allow_snippets(false);
391 self.completion.active_resolve_capabilities =
392 enabled_completions_resolve_capabilities(caps).unwrap_or_default();
391 if let Some(completion) = &doc_caps.completion { 393 if let Some(completion) = &doc_caps.completion {
392 if let Some(completion_item) = &completion.completion_item { 394 if let Some(completion_item) = &completion.completion_item {
393 if let Some(value) = completion_item.snippet_support { 395 if let Some(value) = completion_item.snippet_support {
@@ -506,7 +508,7 @@ config_data! {
506 completion_addCallArgumentSnippets: bool = true, 508 completion_addCallArgumentSnippets: bool = true,
507 completion_addCallParenthesis: bool = true, 509 completion_addCallParenthesis: bool = true,
508 completion_postfix_enable: bool = true, 510 completion_postfix_enable: bool = true,
509 completion_enableExperimental: bool = true, 511 completion_autoimport_enable: bool = true,
510 512
511 diagnostics_enable: bool = true, 513 diagnostics_enable: bool = true,
512 diagnostics_enableExperimental: bool = true, 514 diagnostics_enableExperimental: bool = true,
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 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
10use ide::{ 10use ide::{
11 FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, NavigationTarget, Query, 11 CompletionResolveCapability, FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData,
12 RangeInfo, Runnable, RunnableKind, SearchScope, TextEdit, 12 NavigationTarget, Query, RangeInfo, Runnable, RunnableKind, SearchScope, TextEdit,
13}; 13};
14use itertools::Itertools; 14use itertools::Itertools;
15use lsp_server::ErrorCode; 15use lsp_server::ErrorCode;
@@ -21,7 +21,7 @@ use lsp_types::{
21 HoverContents, Location, NumberOrString, Position, PrepareRenameResponse, Range, RenameParams, 21 HoverContents, Location, NumberOrString, Position, PrepareRenameResponse, Range, RenameParams,
22 SemanticTokensDeltaParams, SemanticTokensFullDeltaResult, SemanticTokensParams, 22 SemanticTokensDeltaParams, SemanticTokensFullDeltaResult, SemanticTokensParams,
23 SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, 23 SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation,
24 SymbolTag, TextDocumentIdentifier, Url, WorkspaceEdit, 24 SymbolTag, TextDocumentIdentifier, TextDocumentPositionParams, Url, WorkspaceEdit,
25}; 25};
26use project_model::TargetKind; 26use project_model::TargetKind;
27use serde::{Deserialize, Serialize}; 27use serde::{Deserialize, Serialize};
@@ -35,6 +35,7 @@ use crate::{
35 from_json, from_proto, 35 from_json, from_proto,
36 global_state::{GlobalState, GlobalStateSnapshot}, 36 global_state::{GlobalState, GlobalStateSnapshot},
37 lsp_ext::{self, InlayHint, InlayHintsParams}, 37 lsp_ext::{self, InlayHint, InlayHintsParams},
38 lsp_utils::all_edits_are_disjoint,
38 to_proto, LspError, Result, 39 to_proto, LspError, Result,
39}; 40};
40 41
@@ -539,6 +540,7 @@ pub(crate) fn handle_completion(
539 params: lsp_types::CompletionParams, 540 params: lsp_types::CompletionParams,
540) -> Result<Option<lsp_types::CompletionResponse>> { 541) -> Result<Option<lsp_types::CompletionResponse>> {
541 let _p = profile::span("handle_completion"); 542 let _p = profile::span("handle_completion");
543 let text_document_position = params.text_document_position.clone();
542 let position = from_proto::file_position(&snap, params.text_document_position)?; 544 let position = from_proto::file_position(&snap, params.text_document_position)?;
543 let completion_triggered_after_single_colon = { 545 let completion_triggered_after_single_colon = {
544 let mut res = false; 546 let mut res = false;
@@ -568,15 +570,99 @@ pub(crate) fn handle_completion(
568 }; 570 };
569 let line_index = snap.analysis.file_line_index(position.file_id)?; 571 let line_index = snap.analysis.file_line_index(position.file_id)?;
570 let line_endings = snap.file_line_endings(position.file_id); 572 let line_endings = snap.file_line_endings(position.file_id);
573
571 let items: Vec<CompletionItem> = items 574 let items: Vec<CompletionItem> = items
572 .into_iter() 575 .into_iter()
573 .flat_map(|item| to_proto::completion_item(&line_index, line_endings, item)) 576 .flat_map(|item| {
577 let mut new_completion_items =
578 to_proto::completion_item(&line_index, line_endings, item.clone());
579
580 if snap.config.completion.resolve_additional_edits_lazily() {
581 for new_item in &mut new_completion_items {
582 let _ = fill_resolve_data(&mut new_item.data, &item, &text_document_position)
583 .take();
584 }
585 }
586
587 new_completion_items
588 })
574 .collect(); 589 .collect();
575 590
576 let completion_list = lsp_types::CompletionList { is_incomplete: true, items }; 591 let completion_list = lsp_types::CompletionList { is_incomplete: true, items };
577 Ok(Some(completion_list.into())) 592 Ok(Some(completion_list.into()))
578} 593}
579 594
595pub(crate) fn handle_completion_resolve(
596 snap: GlobalStateSnapshot,
597 mut original_completion: CompletionItem,
598) -> Result<CompletionItem> {
599 let _p = profile::span("handle_completion_resolve");
600
601 if !all_edits_are_disjoint(&original_completion, &[]) {
602 return Err(LspError::new(
603 ErrorCode::InvalidParams as i32,
604 "Received a completion with overlapping edits, this is not LSP-compliant".into(),
605 )
606 .into());
607 }
608
609 // FIXME resolve the other capabilities also?
610 if !snap
611 .config
612 .completion
613 .active_resolve_capabilities
614 .contains(&CompletionResolveCapability::AdditionalTextEdits)
615 {
616 return Ok(original_completion);
617 }
618
619 let resolve_data = match original_completion
620 .data
621 .take()
622 .map(|data| serde_json::from_value::<CompletionResolveData>(data))
623 .transpose()?
624 {
625 Some(data) => data,
626 None => return Ok(original_completion),
627 };
628
629 let file_id = from_proto::file_id(&snap, &resolve_data.position.text_document.uri)?;
630 let line_index = snap.analysis.file_line_index(file_id)?;
631 let line_endings = snap.file_line_endings(file_id);
632 let offset = from_proto::offset(&line_index, resolve_data.position.position);
633
634 let additional_edits = snap
635 .analysis
636 .resolve_completion_edits(
637 &snap.config.completion,
638 FilePosition { file_id, offset },
639 &resolve_data.full_import_path,
640 &resolve_data.imported_name,
641 )?
642 .into_iter()
643 .flat_map(|edit| {
644 edit.into_iter().map(|indel| to_proto::text_edit(&line_index, line_endings, indel))
645 })
646 .collect_vec();
647
648 if !all_edits_are_disjoint(&original_completion, &additional_edits) {
649 return Err(LspError::new(
650 ErrorCode::InternalError as i32,
651 "Import edit overlaps with the original completion edits, this is not LSP-compliant"
652 .into(),
653 )
654 .into());
655 }
656
657 if let Some(original_additional_edits) = original_completion.additional_text_edits.as_mut() {
658 original_additional_edits.extend(additional_edits.into_iter())
659 } else {
660 original_completion.additional_text_edits = Some(additional_edits);
661 }
662
663 Ok(original_completion)
664}
665
580pub(crate) fn handle_folding_range( 666pub(crate) fn handle_folding_range(
581 snap: GlobalStateSnapshot, 667 snap: GlobalStateSnapshot,
582 params: FoldingRangeParams, 668 params: FoldingRangeParams,
@@ -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)]
1621struct CompletionResolveData {
1622 position: lsp_types::TextDocumentPositionParams,
1623 full_import_path: String,
1624 imported_name: String,
1625}
1626
1627fn 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.
134pub(crate) fn all_edits_are_disjoint(
135 completion: &lsp_types::CompletionItem,
136 additional_edits: &[lsp_types::TextEdit],
137) -> bool {
138 let mut edit_ranges = Vec::new();
139 match completion.text_edit.as_ref() {
140 Some(lsp_types::CompletionTextEdit::Edit(edit)) => {
141 edit_ranges.push(edit.range);
142 }
143 Some(lsp_types::CompletionTextEdit::InsertAndReplace(edit)) => {
144 edit_ranges.push(edit.insert);
145 edit_ranges.push(edit.replace);
146 }
147 None => {}
148 }
149 if let Some(additional_changes) = completion.additional_text_edits.as_ref() {
150 edit_ranges.extend(additional_changes.iter().map(|edit| edit.range));
151 };
152 edit_ranges.extend(additional_edits.iter().map(|edit| edit.range));
153 edit_ranges.sort_by_key(|range| (range.start, range.end));
154 edit_ranges
155 .iter()
156 .zip(edit_ranges.iter().skip(1))
157 .all(|(previous, next)| previous.end <= next.start)
158}
159
132#[cfg(test)] 160#[cfg(test)]
133mod tests { 161mod tests {
134 use lsp_types::{Position, Range, TextDocumentContentChangeEvent}; 162 use lsp_types::{
163 CompletionItem, CompletionTextEdit, InsertReplaceEdit, Position, Range,
164 TextDocumentContentChangeEvent,
165 };
135 166
136 use super::*; 167 use super::*;
137 168
@@ -197,4 +228,135 @@ mod tests {
197 apply_document_changes(&mut text, c![0, 1; 1, 0 => "È›\nc", 0, 2; 0, 2 => "c"]); 228 apply_document_changes(&mut text, c![0, 1; 1, 0 => "È›\nc", 0, 2; 0, 2 => "c"]);
198 assert_eq!(text, "ațc\ncb"); 229 assert_eq!(text, "ațc\ncb");
199 } 230 }
231
232 #[test]
233 fn empty_completion_disjoint_tests() {
234 let empty_completion =
235 CompletionItem::new_simple("label".to_string(), "detail".to_string());
236
237 let disjoint_edit_1 = lsp_types::TextEdit::new(
238 Range::new(Position::new(2, 2), Position::new(3, 3)),
239 "new_text".to_string(),
240 );
241 let disjoint_edit_2 = lsp_types::TextEdit::new(
242 Range::new(Position::new(3, 3), Position::new(4, 4)),
243 "new_text".to_string(),
244 );
245
246 let joint_edit = lsp_types::TextEdit::new(
247 Range::new(Position::new(1, 1), Position::new(5, 5)),
248 "new_text".to_string(),
249 );
250
251 assert!(
252 all_edits_are_disjoint(&empty_completion, &[]),
253 "Empty completion has all its edits disjoint"
254 );
255 assert!(
256 all_edits_are_disjoint(
257 &empty_completion,
258 &[disjoint_edit_1.clone(), disjoint_edit_2.clone()]
259 ),
260 "Empty completion is disjoint to whatever disjoint extra edits added"
261 );
262
263 assert!(
264 !all_edits_are_disjoint(
265 &empty_completion,
266 &[disjoint_edit_1, disjoint_edit_2, joint_edit]
267 ),
268 "Empty completion does not prevent joint extra edits from failing the validation"
269 );
270 }
271
272 #[test]
273 fn completion_with_joint_edits_disjoint_tests() {
274 let disjoint_edit = lsp_types::TextEdit::new(
275 Range::new(Position::new(1, 1), Position::new(2, 2)),
276 "new_text".to_string(),
277 );
278 let disjoint_edit_2 = lsp_types::TextEdit::new(
279 Range::new(Position::new(2, 2), Position::new(3, 3)),
280 "new_text".to_string(),
281 );
282 let joint_edit = lsp_types::TextEdit::new(
283 Range::new(Position::new(1, 1), Position::new(5, 5)),
284 "new_text".to_string(),
285 );
286
287 let mut completion_with_joint_edits =
288 CompletionItem::new_simple("label".to_string(), "detail".to_string());
289 completion_with_joint_edits.additional_text_edits =
290 Some(vec![disjoint_edit.clone(), joint_edit.clone()]);
291 assert!(
292 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
293 "Completion with disjoint edits fails the validaton even with empty extra edits"
294 );
295
296 completion_with_joint_edits.text_edit =
297 Some(CompletionTextEdit::Edit(disjoint_edit.clone()));
298 completion_with_joint_edits.additional_text_edits = Some(vec![joint_edit.clone()]);
299 assert!(
300 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
301 "Completion with disjoint edits fails the validaton even with empty extra edits"
302 );
303
304 completion_with_joint_edits.text_edit =
305 Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit {
306 new_text: "new_text".to_string(),
307 insert: disjoint_edit.range,
308 replace: joint_edit.range,
309 }));
310 completion_with_joint_edits.additional_text_edits = None;
311 assert!(
312 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
313 "Completion with disjoint edits fails the validaton even with empty extra edits"
314 );
315
316 completion_with_joint_edits.text_edit =
317 Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit {
318 new_text: "new_text".to_string(),
319 insert: disjoint_edit.range,
320 replace: disjoint_edit_2.range,
321 }));
322 completion_with_joint_edits.additional_text_edits = Some(vec![joint_edit]);
323 assert!(
324 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
325 "Completion with disjoint edits fails the validaton even with empty extra edits"
326 );
327 }
328
329 #[test]
330 fn completion_with_disjoint_edits_disjoint_tests() {
331 let disjoint_edit = lsp_types::TextEdit::new(
332 Range::new(Position::new(1, 1), Position::new(2, 2)),
333 "new_text".to_string(),
334 );
335 let disjoint_edit_2 = lsp_types::TextEdit::new(
336 Range::new(Position::new(2, 2), Position::new(3, 3)),
337 "new_text".to_string(),
338 );
339 let joint_edit = lsp_types::TextEdit::new(
340 Range::new(Position::new(1, 1), Position::new(5, 5)),
341 "new_text".to_string(),
342 );
343
344 let mut completion_with_disjoint_edits =
345 CompletionItem::new_simple("label".to_string(), "detail".to_string());
346 completion_with_disjoint_edits.text_edit = Some(CompletionTextEdit::Edit(disjoint_edit));
347 let completion_with_disjoint_edits = completion_with_disjoint_edits;
348
349 assert!(
350 all_edits_are_disjoint(&completion_with_disjoint_edits, &[]),
351 "Completion with disjoint edits is valid"
352 );
353 assert!(
354 !all_edits_are_disjoint(&completion_with_disjoint_edits, &[joint_edit.clone()]),
355 "Completion with disjoint edits and joint extra edit is invalid"
356 );
357 assert!(
358 all_edits_are_disjoint(&completion_with_disjoint_edits, &[disjoint_edit_2.clone()]),
359 "Completion with disjoint edits and joint extra edit is valid"
360 );
361 }
200} 362}
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 55d46b09e..95be2ebd3 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -454,6 +454,7 @@ impl GlobalState {
454 .on::<lsp_types::request::GotoImplementation>(handlers::handle_goto_implementation) 454 .on::<lsp_types::request::GotoImplementation>(handlers::handle_goto_implementation)
455 .on::<lsp_types::request::GotoTypeDefinition>(handlers::handle_goto_type_definition) 455 .on::<lsp_types::request::GotoTypeDefinition>(handlers::handle_goto_type_definition)
456 .on::<lsp_types::request::Completion>(handlers::handle_completion) 456 .on::<lsp_types::request::Completion>(handlers::handle_completion)
457 .on::<lsp_types::request::ResolveCompletionItem>(handlers::handle_completion_resolve)
457 .on::<lsp_types::request::CodeLensRequest>(handlers::handle_code_lens) 458 .on::<lsp_types::request::CodeLensRequest>(handlers::handle_code_lens)
458 .on::<lsp_types::request::CodeLensResolve>(handlers::handle_code_lens_resolve) 459 .on::<lsp_types::request::CodeLensResolve>(handlers::handle_code_lens_resolve)
459 .on::<lsp_types::request::FoldingRangeRequest>(handlers::handle_folding_range) 460 .on::<lsp_types::request::FoldingRangeRequest>(handlers::handle_folding_range)
diff --git a/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
78impl ast::Whitespace { 105impl 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}