aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-10-22 21:46:24 +0100
committerGitHub <[email protected]>2020-10-22 21:46:24 +0100
commit8b3c851dd37f39f79e7e8807378f45fdde7f6411 (patch)
treed6c20cd94e6291d6e4c3b8f58a17817d61463d79
parentedf46a13a6a28093985d2d934ef97570947b9494 (diff)
parent3dbbcfca67ed09322227f2190b5364754a29a216 (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.rs13
-rw-r--r--crates/completion/src/completion_item.rs21
-rw-r--r--crates/completion/src/presentation.rs51
-rw-r--r--crates/rust-analyzer/src/handlers.rs2
-rw-r--r--crates/rust-analyzer/src/to_proto.rs71
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
3use std::fmt; 3use std::fmt;
4 4
5use hir::Documentation; 5use hir::{Documentation, Mutability};
6use syntax::TextRange; 6use syntax::TextRange;
7use text_edit::TextEdit; 7use 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)]
247pub(crate) struct Builder { 257pub(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
263impl Builder { 274impl 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
355impl<'a> Into<CompletionItem> for Builder { 374impl<'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
4use hir::{HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type}; 4use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, StructKind, Type};
5use itertools::Itertools; 5use itertools::Itertools;
6use syntax::{ast::NameOwner, display::*}; 6use syntax::{ast::NameOwner, display::*};
7use test_utils::mark; 7use 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
345pub(crate) fn compute_score( 352fn 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}
373fn 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
387fn 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
377enum Params { 392enum 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
225pub(crate) fn signature_help( 242pub(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;
781mod b; 840mod b;