mirror of
https://github.com/rust-lang/rust.git
synced 2025-06-04 19:29:07 +00:00
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 <ark.email@gmail.com>
This commit is contained in:
commit
8b3c851dd3
@ -246,6 +246,19 @@ impl<'a> CompletionContext<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn active_name_and_type(&self) -> Option<(String, Type)> {
|
||||||
|
if let Some(record_field) = &self.record_field_syntax {
|
||||||
|
mark::hit!(record_field_type_match);
|
||||||
|
let (struct_field, _local) = self.sema.resolve_record_field(record_field)?;
|
||||||
|
Some((struct_field.name(self.db).to_string(), struct_field.signature_ty(self.db)))
|
||||||
|
} else if let Some(active_parameter) = &self.active_parameter {
|
||||||
|
mark::hit!(active_param_type_match);
|
||||||
|
Some((active_parameter.name.clone(), active_parameter.ty.clone()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) {
|
fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) {
|
||||||
let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap();
|
let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap();
|
||||||
let syntax_element = NodeOrToken::Token(fake_ident_token);
|
let syntax_element = NodeOrToken::Token(fake_ident_token);
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use hir::Documentation;
|
use hir::{Documentation, Mutability};
|
||||||
use syntax::TextRange;
|
use syntax::TextRange;
|
||||||
use text_edit::TextEdit;
|
use text_edit::TextEdit;
|
||||||
|
|
||||||
@ -56,6 +56,10 @@ pub struct CompletionItem {
|
|||||||
|
|
||||||
/// Score is useful to pre select or display in better order completion items
|
/// Score is useful to pre select or display in better order completion items
|
||||||
score: Option<CompletionScore>,
|
score: Option<CompletionScore>,
|
||||||
|
|
||||||
|
/// Indicates that a reference or mutable reference to this variable is a
|
||||||
|
/// possible match.
|
||||||
|
ref_match: Option<(Mutability, CompletionScore)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We use custom debug for CompletionItem to make snapshot tests more readable.
|
// We use custom debug for CompletionItem to make snapshot tests more readable.
|
||||||
@ -194,6 +198,7 @@ impl CompletionItem {
|
|||||||
deprecated: None,
|
deprecated: None,
|
||||||
trigger_call_info: None,
|
trigger_call_info: None,
|
||||||
score: None,
|
score: None,
|
||||||
|
ref_match: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// What user sees in pop-up in the UI.
|
/// What user sees in pop-up in the UI.
|
||||||
@ -240,10 +245,15 @@ impl CompletionItem {
|
|||||||
pub fn trigger_call_info(&self) -> bool {
|
pub fn trigger_call_info(&self) -> bool {
|
||||||
self.trigger_call_info
|
self.trigger_call_info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ref_match(&self) -> Option<(Mutability, CompletionScore)> {
|
||||||
|
self.ref_match
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A helper to make `CompletionItem`s.
|
/// A helper to make `CompletionItem`s.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
#[derive(Clone)]
|
||||||
pub(crate) struct Builder {
|
pub(crate) struct Builder {
|
||||||
source_range: TextRange,
|
source_range: TextRange,
|
||||||
completion_kind: CompletionKind,
|
completion_kind: CompletionKind,
|
||||||
@ -258,6 +268,7 @@ pub(crate) struct Builder {
|
|||||||
deprecated: Option<bool>,
|
deprecated: Option<bool>,
|
||||||
trigger_call_info: Option<bool>,
|
trigger_call_info: Option<bool>,
|
||||||
score: Option<CompletionScore>,
|
score: Option<CompletionScore>,
|
||||||
|
ref_match: Option<(Mutability, CompletionScore)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Builder {
|
impl Builder {
|
||||||
@ -288,6 +299,7 @@ impl Builder {
|
|||||||
deprecated: self.deprecated.unwrap_or(false),
|
deprecated: self.deprecated.unwrap_or(false),
|
||||||
trigger_call_info: self.trigger_call_info.unwrap_or(false),
|
trigger_call_info: self.trigger_call_info.unwrap_or(false),
|
||||||
score: self.score,
|
score: self.score,
|
||||||
|
ref_match: self.ref_match,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
|
pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
|
||||||
@ -350,6 +362,13 @@ impl Builder {
|
|||||||
self.trigger_call_info = Some(true);
|
self.trigger_call_info = Some(true);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
pub(crate) fn set_ref_match(
|
||||||
|
mut self,
|
||||||
|
ref_match: Option<(Mutability, CompletionScore)>,
|
||||||
|
) -> Builder {
|
||||||
|
self.ref_match = ref_match;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Into<CompletionItem> for Builder {
|
impl<'a> Into<CompletionItem> for Builder {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! This modules takes care of rendering various definitions as completion items.
|
//! This modules takes care of rendering various definitions as completion items.
|
||||||
//! It also handles scoring (sorting) completions.
|
//! It also handles scoring (sorting) completions.
|
||||||
|
|
||||||
use hir::{HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type};
|
use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, StructKind, Type};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use syntax::{ast::NameOwner, display::*};
|
use syntax::{ast::NameOwner, display::*};
|
||||||
use test_utils::mark;
|
use test_utils::mark;
|
||||||
@ -107,9 +107,16 @@ impl Completions {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut ref_match = None;
|
||||||
if let ScopeDef::Local(local) = resolution {
|
if let ScopeDef::Local(local) = resolution {
|
||||||
if let Some(score) = compute_score(ctx, &local.ty(ctx.db), &local_name) {
|
if let Some((active_name, active_type)) = ctx.active_name_and_type() {
|
||||||
completion_item = completion_item.set_score(score);
|
let ty = local.ty(ctx.db);
|
||||||
|
if let Some(score) =
|
||||||
|
compute_score_from_active(&active_type, &active_name, &ty, &local_name)
|
||||||
|
{
|
||||||
|
completion_item = completion_item.set_score(score);
|
||||||
|
}
|
||||||
|
ref_match = refed_type_matches(&active_type, &active_name, &ty, &local_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +138,7 @@ impl Completions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
completion_item.kind(kind).set_documentation(docs).add_to(self)
|
completion_item.kind(kind).set_documentation(docs).set_ref_match(ref_match).add_to(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_macro(
|
pub(crate) fn add_macro(
|
||||||
@ -342,25 +349,15 @@ impl Completions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn compute_score(
|
fn compute_score_from_active(
|
||||||
ctx: &CompletionContext,
|
active_type: &Type,
|
||||||
|
active_name: &str,
|
||||||
ty: &Type,
|
ty: &Type,
|
||||||
name: &str,
|
name: &str,
|
||||||
) -> Option<CompletionScore> {
|
) -> Option<CompletionScore> {
|
||||||
let (active_name, active_type) = if let Some(record_field) = &ctx.record_field_syntax {
|
|
||||||
mark::hit!(record_field_type_match);
|
|
||||||
let (struct_field, _local) = ctx.sema.resolve_record_field(record_field)?;
|
|
||||||
(struct_field.name(ctx.db).to_string(), struct_field.signature_ty(ctx.db))
|
|
||||||
} else if let Some(active_parameter) = &ctx.active_parameter {
|
|
||||||
mark::hit!(active_param_type_match);
|
|
||||||
(active_parameter.name.clone(), active_parameter.ty.clone())
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Compute score
|
// Compute score
|
||||||
// For the same type
|
// For the same type
|
||||||
if &active_type != ty {
|
if active_type != ty {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,6 +370,24 @@ pub(crate) fn compute_score(
|
|||||||
|
|
||||||
Some(res)
|
Some(res)
|
||||||
}
|
}
|
||||||
|
fn refed_type_matches(
|
||||||
|
active_type: &Type,
|
||||||
|
active_name: &str,
|
||||||
|
ty: &Type,
|
||||||
|
name: &str,
|
||||||
|
) -> Option<(Mutability, CompletionScore)> {
|
||||||
|
let derefed_active = active_type.remove_ref()?;
|
||||||
|
let score = compute_score_from_active(&derefed_active, &active_name, &ty, &name)?;
|
||||||
|
Some((
|
||||||
|
if active_type.is_mutable_reference() { Mutability::Mut } else { Mutability::Shared },
|
||||||
|
score,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_score(ctx: &CompletionContext, ty: &Type, name: &str) -> Option<CompletionScore> {
|
||||||
|
let (active_name, active_type) = ctx.active_name_and_type()?;
|
||||||
|
compute_score_from_active(&active_type, &active_name, ty, name)
|
||||||
|
}
|
||||||
|
|
||||||
enum Params {
|
enum Params {
|
||||||
Named(Vec<String>),
|
Named(Vec<String>),
|
||||||
|
@ -570,7 +570,7 @@ pub(crate) fn handle_completion(
|
|||||||
let line_endings = snap.file_line_endings(position.file_id);
|
let line_endings = snap.file_line_endings(position.file_id);
|
||||||
let items: Vec<CompletionItem> = items
|
let items: Vec<CompletionItem> = items
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|item| to_proto::completion_item(&line_index, line_endings, item))
|
.flat_map(|item| to_proto::completion_item(&line_index, line_endings, item))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(Some(items.into()))
|
Ok(Some(items.into()))
|
||||||
|
@ -160,7 +160,13 @@ pub(crate) fn completion_item(
|
|||||||
line_index: &LineIndex,
|
line_index: &LineIndex,
|
||||||
line_endings: LineEndings,
|
line_endings: LineEndings,
|
||||||
completion_item: CompletionItem,
|
completion_item: CompletionItem,
|
||||||
) -> lsp_types::CompletionItem {
|
) -> Vec<lsp_types::CompletionItem> {
|
||||||
|
fn set_score(res: &mut lsp_types::CompletionItem, label: &str) {
|
||||||
|
res.preselect = Some(true);
|
||||||
|
// HACK: sort preselect items first
|
||||||
|
res.sort_text = Some(format!(" {}", label));
|
||||||
|
}
|
||||||
|
|
||||||
let mut additional_text_edits = Vec::new();
|
let mut additional_text_edits = Vec::new();
|
||||||
let mut text_edit = None;
|
let mut text_edit = None;
|
||||||
// LSP does not allow arbitrary edits in completion, so we have to do a
|
// LSP does not allow arbitrary edits in completion, so we have to do a
|
||||||
@ -200,9 +206,7 @@ pub(crate) fn completion_item(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if completion_item.score().is_some() {
|
if completion_item.score().is_some() {
|
||||||
res.preselect = Some(true);
|
set_score(&mut res, completion_item.label());
|
||||||
// HACK: sort preselect items first
|
|
||||||
res.sort_text = Some(format!(" {}", completion_item.label()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if completion_item.deprecated() {
|
if completion_item.deprecated() {
|
||||||
@ -217,9 +221,22 @@ pub(crate) fn completion_item(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
res.insert_text_format = Some(insert_text_format(completion_item.insert_text_format()));
|
let mut all_results = match completion_item.ref_match() {
|
||||||
|
Some(ref_match) => {
|
||||||
|
let mut refed = res.clone();
|
||||||
|
let (mutability, _score) = ref_match;
|
||||||
|
let label = format!("&{}{}", mutability.as_keyword_for_ref(), refed.label);
|
||||||
|
set_score(&mut refed, &label);
|
||||||
|
refed.label = label;
|
||||||
|
vec![res, refed]
|
||||||
|
}
|
||||||
|
None => vec![res],
|
||||||
|
};
|
||||||
|
|
||||||
res
|
for mut r in all_results.iter_mut() {
|
||||||
|
r.insert_text_format = Some(insert_text_format(completion_item.insert_text_format()));
|
||||||
|
}
|
||||||
|
all_results
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn signature_help(
|
pub(crate) fn signature_help(
|
||||||
@ -775,6 +792,48 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_completion_with_ref() {
|
||||||
|
let fixture = r#"
|
||||||
|
struct Foo;
|
||||||
|
fn foo(arg: &Foo) {}
|
||||||
|
fn main() {
|
||||||
|
let arg = Foo;
|
||||||
|
foo(<|>)
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let (offset, text) = test_utils::extract_offset(fixture);
|
||||||
|
let line_index = LineIndex::new(&text);
|
||||||
|
let (analysis, file_id) = Analysis::from_single_file(text);
|
||||||
|
let completions: Vec<(String, Option<String>)> = analysis
|
||||||
|
.completions(
|
||||||
|
&ide::CompletionConfig::default(),
|
||||||
|
base_db::FilePosition { file_id, offset },
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|c| c.label().ends_with("arg"))
|
||||||
|
.map(|c| completion_item(&line_index, LineEndings::Unix, c))
|
||||||
|
.flat_map(|comps| comps.into_iter().map(|c| (c.label, c.sort_text)))
|
||||||
|
.collect();
|
||||||
|
expect_test::expect![[r#"
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"arg",
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"&arg",
|
||||||
|
Some(
|
||||||
|
" &arg",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
"#]]
|
||||||
|
.assert_debug_eq(&completions);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn conv_fold_line_folding_only_fixup() {
|
fn conv_fold_line_folding_only_fixup() {
|
||||||
let text = r#"mod a;
|
let text = r#"mod a;
|
||||||
|
Loading…
Reference in New Issue
Block a user