diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-10-22 21:46:24 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-10-22 21:46:24 +0100 |
commit | 8b3c851dd37f39f79e7e8807378f45fdde7f6411 (patch) | |
tree | d6c20cd94e6291d6e4c3b8f58a17817d61463d79 | |
parent | edf46a13a6a28093985d2d934ef97570947b9494 (diff) | |
parent | 3dbbcfca67ed09322227f2190b5364754a29a216 (diff) |
Merge #6098
6098: Insert ref for completions r=adamrk a=adamrk
Follow up to https://github.com/rust-analyzer/rust-analyzer/pull/5846. When we have a local in scope which needs a ref or mutable ref to match the name and type of the active in the completion context then a new completion item with `&` or `&mut ` is inserted. E.g.
```rust
fn foo(arg: &i32){};
fn main() {
let arg = 1_i32;
foo(a<|>)
}
```
now offers `&arg` as a completion option with the highest score.
Co-authored-by: adamrk <[email protected]>
-rw-r--r-- | crates/completion/src/completion_context.rs | 13 | ||||
-rw-r--r-- | crates/completion/src/completion_item.rs | 21 | ||||
-rw-r--r-- | crates/completion/src/presentation.rs | 51 | ||||
-rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/to_proto.rs | 71 |
5 files changed, 132 insertions, 26 deletions
diff --git a/crates/completion/src/completion_context.rs b/crates/completion/src/completion_context.rs index dc4e136c6..e4f86d0e0 100644 --- a/crates/completion/src/completion_context.rs +++ b/crates/completion/src/completion_context.rs | |||
@@ -246,6 +246,19 @@ impl<'a> CompletionContext<'a> { | |||
246 | } | 246 | } |
247 | } | 247 | } |
248 | 248 | ||
249 | pub(crate) fn active_name_and_type(&self) -> Option<(String, Type)> { | ||
250 | if let Some(record_field) = &self.record_field_syntax { | ||
251 | mark::hit!(record_field_type_match); | ||
252 | let (struct_field, _local) = self.sema.resolve_record_field(record_field)?; | ||
253 | Some((struct_field.name(self.db).to_string(), struct_field.signature_ty(self.db))) | ||
254 | } else if let Some(active_parameter) = &self.active_parameter { | ||
255 | mark::hit!(active_param_type_match); | ||
256 | Some((active_parameter.name.clone(), active_parameter.ty.clone())) | ||
257 | } else { | ||
258 | None | ||
259 | } | ||
260 | } | ||
261 | |||
249 | fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) { | 262 | fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) { |
250 | let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); | 263 | let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); |
251 | let syntax_element = NodeOrToken::Token(fake_ident_token); | 264 | let syntax_element = NodeOrToken::Token(fake_ident_token); |
diff --git a/crates/completion/src/completion_item.rs b/crates/completion/src/completion_item.rs index f8be0ad2b..2e1ca0e59 100644 --- a/crates/completion/src/completion_item.rs +++ b/crates/completion/src/completion_item.rs | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | use std::fmt; | 3 | use std::fmt; |
4 | 4 | ||
5 | use hir::Documentation; | 5 | use hir::{Documentation, Mutability}; |
6 | use syntax::TextRange; | 6 | use syntax::TextRange; |
7 | use text_edit::TextEdit; | 7 | use text_edit::TextEdit; |
8 | 8 | ||
@@ -56,6 +56,10 @@ pub struct CompletionItem { | |||
56 | 56 | ||
57 | /// Score is useful to pre select or display in better order completion items | 57 | /// Score is useful to pre select or display in better order completion items |
58 | score: Option<CompletionScore>, | 58 | score: Option<CompletionScore>, |
59 | |||
60 | /// Indicates that a reference or mutable reference to this variable is a | ||
61 | /// possible match. | ||
62 | ref_match: Option<(Mutability, CompletionScore)>, | ||
59 | } | 63 | } |
60 | 64 | ||
61 | // We use custom debug for CompletionItem to make snapshot tests more readable. | 65 | // We use custom debug for CompletionItem to make snapshot tests more readable. |
@@ -194,6 +198,7 @@ impl CompletionItem { | |||
194 | deprecated: None, | 198 | deprecated: None, |
195 | trigger_call_info: None, | 199 | trigger_call_info: None, |
196 | score: None, | 200 | score: None, |
201 | ref_match: None, | ||
197 | } | 202 | } |
198 | } | 203 | } |
199 | /// What user sees in pop-up in the UI. | 204 | /// What user sees in pop-up in the UI. |
@@ -240,10 +245,15 @@ impl CompletionItem { | |||
240 | pub fn trigger_call_info(&self) -> bool { | 245 | pub fn trigger_call_info(&self) -> bool { |
241 | self.trigger_call_info | 246 | self.trigger_call_info |
242 | } | 247 | } |
248 | |||
249 | pub fn ref_match(&self) -> Option<(Mutability, CompletionScore)> { | ||
250 | self.ref_match | ||
251 | } | ||
243 | } | 252 | } |
244 | 253 | ||
245 | /// A helper to make `CompletionItem`s. | 254 | /// A helper to make `CompletionItem`s. |
246 | #[must_use] | 255 | #[must_use] |
256 | #[derive(Clone)] | ||
247 | pub(crate) struct Builder { | 257 | pub(crate) struct Builder { |
248 | source_range: TextRange, | 258 | source_range: TextRange, |
249 | completion_kind: CompletionKind, | 259 | completion_kind: CompletionKind, |
@@ -258,6 +268,7 @@ pub(crate) struct Builder { | |||
258 | deprecated: Option<bool>, | 268 | deprecated: Option<bool>, |
259 | trigger_call_info: Option<bool>, | 269 | trigger_call_info: Option<bool>, |
260 | score: Option<CompletionScore>, | 270 | score: Option<CompletionScore>, |
271 | ref_match: Option<(Mutability, CompletionScore)>, | ||
261 | } | 272 | } |
262 | 273 | ||
263 | impl Builder { | 274 | impl Builder { |
@@ -288,6 +299,7 @@ impl Builder { | |||
288 | deprecated: self.deprecated.unwrap_or(false), | 299 | deprecated: self.deprecated.unwrap_or(false), |
289 | trigger_call_info: self.trigger_call_info.unwrap_or(false), | 300 | trigger_call_info: self.trigger_call_info.unwrap_or(false), |
290 | score: self.score, | 301 | score: self.score, |
302 | ref_match: self.ref_match, | ||
291 | } | 303 | } |
292 | } | 304 | } |
293 | pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { | 305 | pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder { |
@@ -350,6 +362,13 @@ impl Builder { | |||
350 | self.trigger_call_info = Some(true); | 362 | self.trigger_call_info = Some(true); |
351 | self | 363 | self |
352 | } | 364 | } |
365 | pub(crate) fn set_ref_match( | ||
366 | mut self, | ||
367 | ref_match: Option<(Mutability, CompletionScore)>, | ||
368 | ) -> Builder { | ||
369 | self.ref_match = ref_match; | ||
370 | self | ||
371 | } | ||
353 | } | 372 | } |
354 | 373 | ||
355 | impl<'a> Into<CompletionItem> for Builder { | 374 | impl<'a> Into<CompletionItem> for Builder { |
diff --git a/crates/completion/src/presentation.rs b/crates/completion/src/presentation.rs index 0a0dc1ce5..2a19281cf 100644 --- a/crates/completion/src/presentation.rs +++ b/crates/completion/src/presentation.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | //! This modules takes care of rendering various definitions as completion items. | 1 | //! This modules takes care of rendering various definitions as completion items. |
2 | //! It also handles scoring (sorting) completions. | 2 | //! It also handles scoring (sorting) completions. |
3 | 3 | ||
4 | use hir::{HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type}; | 4 | use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, StructKind, Type}; |
5 | use itertools::Itertools; | 5 | use itertools::Itertools; |
6 | use syntax::{ast::NameOwner, display::*}; | 6 | use syntax::{ast::NameOwner, display::*}; |
7 | use test_utils::mark; | 7 | use test_utils::mark; |
@@ -107,9 +107,16 @@ impl Completions { | |||
107 | } | 107 | } |
108 | }; | 108 | }; |
109 | 109 | ||
110 | let mut ref_match = None; | ||
110 | if let ScopeDef::Local(local) = resolution { | 111 | if let ScopeDef::Local(local) = resolution { |
111 | if let Some(score) = compute_score(ctx, &local.ty(ctx.db), &local_name) { | 112 | if let Some((active_name, active_type)) = ctx.active_name_and_type() { |
112 | completion_item = completion_item.set_score(score); | 113 | let ty = local.ty(ctx.db); |
114 | if let Some(score) = | ||
115 | compute_score_from_active(&active_type, &active_name, &ty, &local_name) | ||
116 | { | ||
117 | completion_item = completion_item.set_score(score); | ||
118 | } | ||
119 | ref_match = refed_type_matches(&active_type, &active_name, &ty, &local_name); | ||
113 | } | 120 | } |
114 | } | 121 | } |
115 | 122 | ||
@@ -131,7 +138,7 @@ impl Completions { | |||
131 | } | 138 | } |
132 | } | 139 | } |
133 | 140 | ||
134 | completion_item.kind(kind).set_documentation(docs).add_to(self) | 141 | completion_item.kind(kind).set_documentation(docs).set_ref_match(ref_match).add_to(self) |
135 | } | 142 | } |
136 | 143 | ||
137 | pub(crate) fn add_macro( | 144 | pub(crate) fn add_macro( |
@@ -342,25 +349,15 @@ impl Completions { | |||
342 | } | 349 | } |
343 | } | 350 | } |
344 | 351 | ||
345 | pub(crate) fn compute_score( | 352 | fn compute_score_from_active( |
346 | ctx: &CompletionContext, | 353 | active_type: &Type, |
354 | active_name: &str, | ||
347 | ty: &Type, | 355 | ty: &Type, |
348 | name: &str, | 356 | name: &str, |
349 | ) -> Option<CompletionScore> { | 357 | ) -> Option<CompletionScore> { |
350 | let (active_name, active_type) = if let Some(record_field) = &ctx.record_field_syntax { | ||
351 | mark::hit!(record_field_type_match); | ||
352 | let (struct_field, _local) = ctx.sema.resolve_record_field(record_field)?; | ||
353 | (struct_field.name(ctx.db).to_string(), struct_field.signature_ty(ctx.db)) | ||
354 | } else if let Some(active_parameter) = &ctx.active_parameter { | ||
355 | mark::hit!(active_param_type_match); | ||
356 | (active_parameter.name.clone(), active_parameter.ty.clone()) | ||
357 | } else { | ||
358 | return None; | ||
359 | }; | ||
360 | |||
361 | // Compute score | 358 | // Compute score |
362 | // For the same type | 359 | // For the same type |
363 | if &active_type != ty { | 360 | if active_type != ty { |
364 | return None; | 361 | return None; |
365 | } | 362 | } |
366 | 363 | ||
@@ -373,6 +370,24 @@ pub(crate) fn compute_score( | |||
373 | 370 | ||
374 | Some(res) | 371 | Some(res) |
375 | } | 372 | } |
373 | fn refed_type_matches( | ||
374 | active_type: &Type, | ||
375 | active_name: &str, | ||
376 | ty: &Type, | ||
377 | name: &str, | ||
378 | ) -> Option<(Mutability, CompletionScore)> { | ||
379 | let derefed_active = active_type.remove_ref()?; | ||
380 | let score = compute_score_from_active(&derefed_active, &active_name, &ty, &name)?; | ||
381 | Some(( | ||
382 | if active_type.is_mutable_reference() { Mutability::Mut } else { Mutability::Shared }, | ||
383 | score, | ||
384 | )) | ||
385 | } | ||
386 | |||
387 | fn compute_score(ctx: &CompletionContext, ty: &Type, name: &str) -> Option<CompletionScore> { | ||
388 | let (active_name, active_type) = ctx.active_name_and_type()?; | ||
389 | compute_score_from_active(&active_type, &active_name, ty, name) | ||
390 | } | ||
376 | 391 | ||
377 | enum Params { | 392 | enum Params { |
378 | Named(Vec<String>), | 393 | Named(Vec<String>), |
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index f2d57f986..2680e5f08 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs | |||
@@ -570,7 +570,7 @@ pub(crate) fn handle_completion( | |||
570 | let line_endings = snap.file_line_endings(position.file_id); | 570 | let line_endings = snap.file_line_endings(position.file_id); |
571 | let items: Vec<CompletionItem> = items | 571 | let items: Vec<CompletionItem> = items |
572 | .into_iter() | 572 | .into_iter() |
573 | .map(|item| to_proto::completion_item(&line_index, line_endings, item)) | 573 | .flat_map(|item| to_proto::completion_item(&line_index, line_endings, item)) |
574 | .collect(); | 574 | .collect(); |
575 | 575 | ||
576 | Ok(Some(items.into())) | 576 | Ok(Some(items.into())) |
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index aeacde0f7..24ad49206 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -160,7 +160,13 @@ pub(crate) fn completion_item( | |||
160 | line_index: &LineIndex, | 160 | line_index: &LineIndex, |
161 | line_endings: LineEndings, | 161 | line_endings: LineEndings, |
162 | completion_item: CompletionItem, | 162 | completion_item: CompletionItem, |
163 | ) -> lsp_types::CompletionItem { | 163 | ) -> Vec<lsp_types::CompletionItem> { |
164 | fn set_score(res: &mut lsp_types::CompletionItem, label: &str) { | ||
165 | res.preselect = Some(true); | ||
166 | // HACK: sort preselect items first | ||
167 | res.sort_text = Some(format!(" {}", label)); | ||
168 | } | ||
169 | |||
164 | let mut additional_text_edits = Vec::new(); | 170 | let mut additional_text_edits = Vec::new(); |
165 | let mut text_edit = None; | 171 | let mut text_edit = None; |
166 | // LSP does not allow arbitrary edits in completion, so we have to do a | 172 | // LSP does not allow arbitrary edits in completion, so we have to do a |
@@ -200,9 +206,7 @@ pub(crate) fn completion_item( | |||
200 | }; | 206 | }; |
201 | 207 | ||
202 | if completion_item.score().is_some() { | 208 | if completion_item.score().is_some() { |
203 | res.preselect = Some(true); | 209 | set_score(&mut res, completion_item.label()); |
204 | // HACK: sort preselect items first | ||
205 | res.sort_text = Some(format!(" {}", completion_item.label())); | ||
206 | } | 210 | } |
207 | 211 | ||
208 | if completion_item.deprecated() { | 212 | if completion_item.deprecated() { |
@@ -217,9 +221,22 @@ pub(crate) fn completion_item( | |||
217 | }); | 221 | }); |
218 | } | 222 | } |
219 | 223 | ||
220 | res.insert_text_format = Some(insert_text_format(completion_item.insert_text_format())); | 224 | let mut all_results = match completion_item.ref_match() { |
225 | Some(ref_match) => { | ||
226 | let mut refed = res.clone(); | ||
227 | let (mutability, _score) = ref_match; | ||
228 | let label = format!("&{}{}", mutability.as_keyword_for_ref(), refed.label); | ||
229 | set_score(&mut refed, &label); | ||
230 | refed.label = label; | ||
231 | vec![res, refed] | ||
232 | } | ||
233 | None => vec![res], | ||
234 | }; | ||
221 | 235 | ||
222 | res | 236 | for mut r in all_results.iter_mut() { |
237 | r.insert_text_format = Some(insert_text_format(completion_item.insert_text_format())); | ||
238 | } | ||
239 | all_results | ||
223 | } | 240 | } |
224 | 241 | ||
225 | pub(crate) fn signature_help( | 242 | pub(crate) fn signature_help( |
@@ -776,6 +793,48 @@ mod tests { | |||
776 | use super::*; | 793 | use super::*; |
777 | 794 | ||
778 | #[test] | 795 | #[test] |
796 | fn test_completion_with_ref() { | ||
797 | let fixture = r#" | ||
798 | struct Foo; | ||
799 | fn foo(arg: &Foo) {} | ||
800 | fn main() { | ||
801 | let arg = Foo; | ||
802 | foo(<|>) | ||
803 | }"#; | ||
804 | |||
805 | let (offset, text) = test_utils::extract_offset(fixture); | ||
806 | let line_index = LineIndex::new(&text); | ||
807 | let (analysis, file_id) = Analysis::from_single_file(text); | ||
808 | let completions: Vec<(String, Option<String>)> = analysis | ||
809 | .completions( | ||
810 | &ide::CompletionConfig::default(), | ||
811 | base_db::FilePosition { file_id, offset }, | ||
812 | ) | ||
813 | .unwrap() | ||
814 | .unwrap() | ||
815 | .into_iter() | ||
816 | .filter(|c| c.label().ends_with("arg")) | ||
817 | .map(|c| completion_item(&line_index, LineEndings::Unix, c)) | ||
818 | .flat_map(|comps| comps.into_iter().map(|c| (c.label, c.sort_text))) | ||
819 | .collect(); | ||
820 | expect_test::expect![[r#" | ||
821 | [ | ||
822 | ( | ||
823 | "arg", | ||
824 | None, | ||
825 | ), | ||
826 | ( | ||
827 | "&arg", | ||
828 | Some( | ||
829 | " &arg", | ||
830 | ), | ||
831 | ), | ||
832 | ] | ||
833 | "#]] | ||
834 | .assert_debug_eq(&completions); | ||
835 | } | ||
836 | |||
837 | #[test] | ||
779 | fn conv_fold_line_folding_only_fixup() { | 838 | fn conv_fold_line_folding_only_fixup() { |
780 | let text = r#"mod a; | 839 | let text = r#"mod a; |
781 | mod b; | 840 | mod b; |