Merge #3954
3954: Improve autocompletion by looking on the type and name r=matklad a=bnjjj This tweet (https://twitter.com/tjholowaychuk/status/1248918374731714560) gaves me the idea to implement that in rust-analyzer. Basically for this first example I made some examples when we are in a function call definition. I look on the parameter list to prioritize autocompletions for the same types and if it's the same type + the same name then it's displayed first in the completion list. So here is a draft, first step to open a discussion and know what you think about the implementation. It works (cf tests) but maybe I can make a better implementation at some places. Be careful the code needs some refactoring to be better and concise. PS: It was lot of fun writing this haha Co-authored-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com>
This commit is contained in:
commit
e833e03783
9 changed files with 412 additions and 40 deletions
|
|
@ -29,7 +29,7 @@ use crate::{
|
|||
};
|
||||
|
||||
pub use crate::completion::completion_item::{
|
||||
CompletionItem, CompletionItemKind, InsertTextFormat,
|
||||
CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@
|
|||
|
||||
use hir::{HasVisibility, Type};
|
||||
|
||||
use crate::completion::completion_item::CompletionKind;
|
||||
use crate::{
|
||||
completion::{completion_context::CompletionContext, completion_item::Completions},
|
||||
completion::{
|
||||
completion_context::CompletionContext,
|
||||
completion_item::{CompletionKind, Completions},
|
||||
},
|
||||
CompletionItem,
|
||||
};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
|
@ -103,6 +105,237 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_struct_field_completion_in_func_call() {
|
||||
assert_debug_snapshot!(
|
||||
do_ref_completion(
|
||||
r"
|
||||
struct A { another_field: i64, the_field: u32, my_string: String }
|
||||
fn test(my_param: u32) -> u32 { my_param }
|
||||
fn foo(a: A) {
|
||||
test(a.<|>)
|
||||
}
|
||||
",
|
||||
),
|
||||
@r###"
|
||||
[
|
||||
CompletionItem {
|
||||
label: "another_field",
|
||||
source_range: [201; 201),
|
||||
delete: [201; 201),
|
||||
insert: "another_field",
|
||||
kind: Field,
|
||||
detail: "i64",
|
||||
},
|
||||
CompletionItem {
|
||||
label: "my_string",
|
||||
source_range: [201; 201),
|
||||
delete: [201; 201),
|
||||
insert: "my_string",
|
||||
kind: Field,
|
||||
detail: "{unknown}",
|
||||
},
|
||||
CompletionItem {
|
||||
label: "the_field",
|
||||
source_range: [201; 201),
|
||||
delete: [201; 201),
|
||||
insert: "the_field",
|
||||
kind: Field,
|
||||
detail: "u32",
|
||||
score: TypeMatch,
|
||||
},
|
||||
]
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_struct_field_completion_in_func_call_with_type_and_name() {
|
||||
assert_debug_snapshot!(
|
||||
do_ref_completion(
|
||||
r"
|
||||
struct A { another_field: i64, another_good_type: u32, the_field: u32 }
|
||||
fn test(the_field: u32) -> u32 { the_field }
|
||||
fn foo(a: A) {
|
||||
test(a.<|>)
|
||||
}
|
||||
",
|
||||
),
|
||||
@r###"
|
||||
[
|
||||
CompletionItem {
|
||||
label: "another_field",
|
||||
source_range: [208; 208),
|
||||
delete: [208; 208),
|
||||
insert: "another_field",
|
||||
kind: Field,
|
||||
detail: "i64",
|
||||
},
|
||||
CompletionItem {
|
||||
label: "another_good_type",
|
||||
source_range: [208; 208),
|
||||
delete: [208; 208),
|
||||
insert: "another_good_type",
|
||||
kind: Field,
|
||||
detail: "u32",
|
||||
score: TypeMatch,
|
||||
},
|
||||
CompletionItem {
|
||||
label: "the_field",
|
||||
source_range: [208; 208),
|
||||
delete: [208; 208),
|
||||
insert: "the_field",
|
||||
kind: Field,
|
||||
detail: "u32",
|
||||
score: TypeAndNameMatch,
|
||||
},
|
||||
]
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_struct_field_completion_in_record_lit() {
|
||||
assert_debug_snapshot!(
|
||||
do_ref_completion(
|
||||
r"
|
||||
struct A { another_field: i64, another_good_type: u32, the_field: u32 }
|
||||
struct B { my_string: String, my_vec: Vec<u32>, the_field: u32 }
|
||||
fn foo(a: A) {
|
||||
let b = B {
|
||||
the_field: a.<|>
|
||||
};
|
||||
}
|
||||
",
|
||||
),
|
||||
@r###"
|
||||
[
|
||||
CompletionItem {
|
||||
label: "another_field",
|
||||
source_range: [270; 270),
|
||||
delete: [270; 270),
|
||||
insert: "another_field",
|
||||
kind: Field,
|
||||
detail: "i64",
|
||||
},
|
||||
CompletionItem {
|
||||
label: "another_good_type",
|
||||
source_range: [270; 270),
|
||||
delete: [270; 270),
|
||||
insert: "another_good_type",
|
||||
kind: Field,
|
||||
detail: "u32",
|
||||
score: TypeMatch,
|
||||
},
|
||||
CompletionItem {
|
||||
label: "the_field",
|
||||
source_range: [270; 270),
|
||||
delete: [270; 270),
|
||||
insert: "the_field",
|
||||
kind: Field,
|
||||
detail: "u32",
|
||||
score: TypeAndNameMatch,
|
||||
},
|
||||
]
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_struct_field_completion_in_record_lit_and_fn_call() {
|
||||
assert_debug_snapshot!(
|
||||
do_ref_completion(
|
||||
r"
|
||||
struct A { another_field: i64, another_good_type: u32, the_field: u32 }
|
||||
struct B { my_string: String, my_vec: Vec<u32>, the_field: u32 }
|
||||
fn test(the_field: i64) -> i64 { the_field }
|
||||
fn foo(a: A) {
|
||||
let b = B {
|
||||
the_field: test(a.<|>)
|
||||
};
|
||||
}
|
||||
",
|
||||
),
|
||||
@r###"
|
||||
[
|
||||
CompletionItem {
|
||||
label: "another_field",
|
||||
source_range: [336; 336),
|
||||
delete: [336; 336),
|
||||
insert: "another_field",
|
||||
kind: Field,
|
||||
detail: "i64",
|
||||
score: TypeMatch,
|
||||
},
|
||||
CompletionItem {
|
||||
label: "another_good_type",
|
||||
source_range: [336; 336),
|
||||
delete: [336; 336),
|
||||
insert: "another_good_type",
|
||||
kind: Field,
|
||||
detail: "u32",
|
||||
},
|
||||
CompletionItem {
|
||||
label: "the_field",
|
||||
source_range: [336; 336),
|
||||
delete: [336; 336),
|
||||
insert: "the_field",
|
||||
kind: Field,
|
||||
detail: "u32",
|
||||
},
|
||||
]
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_struct_field_completion_in_fn_call_and_record_lit() {
|
||||
assert_debug_snapshot!(
|
||||
do_ref_completion(
|
||||
r"
|
||||
struct A { another_field: i64, another_good_type: u32, the_field: u32 }
|
||||
struct B { my_string: String, my_vec: Vec<u32>, the_field: u32 }
|
||||
fn test(the_field: i64) -> i64 { the_field }
|
||||
fn foo(a: A) {
|
||||
test(B {
|
||||
the_field: a.<|>
|
||||
});
|
||||
}
|
||||
",
|
||||
),
|
||||
@r###"
|
||||
[
|
||||
CompletionItem {
|
||||
label: "another_field",
|
||||
source_range: [328; 328),
|
||||
delete: [328; 328),
|
||||
insert: "another_field",
|
||||
kind: Field,
|
||||
detail: "i64",
|
||||
},
|
||||
CompletionItem {
|
||||
label: "another_good_type",
|
||||
source_range: [328; 328),
|
||||
delete: [328; 328),
|
||||
insert: "another_good_type",
|
||||
kind: Field,
|
||||
detail: "u32",
|
||||
score: TypeMatch,
|
||||
},
|
||||
CompletionItem {
|
||||
label: "the_field",
|
||||
source_range: [328; 328),
|
||||
delete: [328; 328),
|
||||
insert: "the_field",
|
||||
kind: Field,
|
||||
detail: "u32",
|
||||
score: TypeAndNameMatch,
|
||||
},
|
||||
]
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_struct_field_completion_self() {
|
||||
assert_debug_snapshot!(
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ pub(crate) struct CompletionContext<'a> {
|
|||
pub(super) db: &'a RootDatabase,
|
||||
pub(super) config: &'a CompletionConfig,
|
||||
pub(super) offset: TextUnit,
|
||||
pub(super) file_position: FilePosition,
|
||||
/// The token before the cursor, in the original file.
|
||||
pub(super) original_token: SyntaxToken,
|
||||
/// The token before the cursor, in the macro-expanded file.
|
||||
|
|
@ -31,6 +32,7 @@ pub(crate) struct CompletionContext<'a> {
|
|||
pub(super) use_item_syntax: Option<ast::UseItem>,
|
||||
pub(super) record_lit_syntax: Option<ast::RecordLit>,
|
||||
pub(super) record_pat_syntax: Option<ast::RecordPat>,
|
||||
pub(super) record_field_syntax: Option<ast::RecordField>,
|
||||
pub(super) impl_def: Option<ast::ImplDef>,
|
||||
pub(super) is_param: bool,
|
||||
/// If a name-binding or reference to a const in a pattern.
|
||||
|
|
@ -88,12 +90,14 @@ impl<'a> CompletionContext<'a> {
|
|||
original_token,
|
||||
token,
|
||||
offset: position.offset,
|
||||
file_position: position,
|
||||
krate,
|
||||
name_ref_syntax: None,
|
||||
function_syntax: None,
|
||||
use_item_syntax: None,
|
||||
record_lit_syntax: None,
|
||||
record_pat_syntax: None,
|
||||
record_field_syntax: None,
|
||||
impl_def: None,
|
||||
is_param: false,
|
||||
is_pat_binding_or_const: false,
|
||||
|
|
@ -279,6 +283,14 @@ impl<'a> CompletionContext<'a> {
|
|||
.take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
|
||||
.find_map(ast::FnDef::cast);
|
||||
|
||||
self.record_field_syntax = self
|
||||
.sema
|
||||
.ancestors_with_macros(self.token.parent())
|
||||
.take_while(|it| {
|
||||
it.kind() != SOURCE_FILE && it.kind() != MODULE && it.kind() != CALL_EXPR
|
||||
})
|
||||
.find_map(ast::RecordField::cast);
|
||||
|
||||
let parent = match name_ref.syntax().parent() {
|
||||
Some(it) => it,
|
||||
None => return,
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@ pub struct CompletionItem {
|
|||
/// If completing a function call, ask the editor to show parameter popup
|
||||
/// after completion.
|
||||
trigger_call_info: bool,
|
||||
|
||||
/// Score is usefull to pre select or display in better order completion items
|
||||
score: Option<CompletionScore>,
|
||||
}
|
||||
|
||||
// We use custom debug for CompletionItem to make `insta`'s diffs more readable.
|
||||
|
|
@ -80,6 +83,9 @@ impl fmt::Debug for CompletionItem {
|
|||
if self.deprecated {
|
||||
s.field("deprecated", &true);
|
||||
}
|
||||
if let Some(score) = &self.score {
|
||||
s.field("score", score);
|
||||
}
|
||||
if self.trigger_call_info {
|
||||
s.field("trigger_call_info", &true);
|
||||
}
|
||||
|
|
@ -147,6 +153,7 @@ impl CompletionItem {
|
|||
text_edit: None,
|
||||
deprecated: None,
|
||||
trigger_call_info: None,
|
||||
score: None,
|
||||
}
|
||||
}
|
||||
/// What user sees in pop-up in the UI.
|
||||
|
|
@ -186,6 +193,14 @@ impl CompletionItem {
|
|||
self.deprecated
|
||||
}
|
||||
|
||||
pub fn score(&self) -> Option<CompletionScore> {
|
||||
self.score.clone()
|
||||
}
|
||||
|
||||
pub fn set_score(&mut self, score: CompletionScore) {
|
||||
self.score = Some(score);
|
||||
}
|
||||
|
||||
pub fn trigger_call_info(&self) -> bool {
|
||||
self.trigger_call_info
|
||||
}
|
||||
|
|
@ -206,6 +221,7 @@ pub(crate) struct Builder {
|
|||
text_edit: Option<TextEdit>,
|
||||
deprecated: Option<bool>,
|
||||
trigger_call_info: Option<bool>,
|
||||
score: Option<CompletionScore>,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
|
|
@ -235,6 +251,7 @@ impl Builder {
|
|||
completion_kind: self.completion_kind,
|
||||
deprecated: self.deprecated.unwrap_or(false),
|
||||
trigger_call_info: self.trigger_call_info.unwrap_or(false),
|
||||
score: self.score,
|
||||
}
|
||||
}
|
||||
pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
|
||||
|
|
@ -285,6 +302,11 @@ impl Builder {
|
|||
self.deprecated = Some(deprecated);
|
||||
self
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub(crate) fn set_score(mut self, score: CompletionScore) -> Builder {
|
||||
self.score = Some(score);
|
||||
self
|
||||
}
|
||||
pub(crate) fn trigger_call_info(mut self) -> Builder {
|
||||
self.trigger_call_info = Some(true);
|
||||
self
|
||||
|
|
@ -297,6 +319,14 @@ impl<'a> Into<CompletionItem> for Builder {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CompletionScore {
|
||||
/// If only type match
|
||||
TypeMatch,
|
||||
/// If type and name match
|
||||
TypeAndNameMatch,
|
||||
}
|
||||
|
||||
/// Represents an in-progress set of completions being built.
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct Completions {
|
||||
|
|
|
|||
|
|
@ -6,12 +6,13 @@ use stdx::SepBy;
|
|||
use test_utils::tested_by;
|
||||
|
||||
use crate::{
|
||||
call_info::call_info,
|
||||
completion::{
|
||||
completion_item::Builder, CompletionContext, CompletionItem, CompletionItemKind,
|
||||
CompletionKind, Completions,
|
||||
},
|
||||
display::{const_label, macro_label, type_label, FunctionSignature},
|
||||
RootDatabase,
|
||||
CompletionScore, RootDatabase,
|
||||
};
|
||||
|
||||
impl Completions {
|
||||
|
|
@ -22,7 +23,7 @@ impl Completions {
|
|||
ty: &Type,
|
||||
) {
|
||||
let is_deprecated = is_deprecated(field, ctx.db);
|
||||
CompletionItem::new(
|
||||
let mut completion_item = CompletionItem::new(
|
||||
CompletionKind::Reference,
|
||||
ctx.source_range(),
|
||||
field.name(ctx.db).to_string(),
|
||||
|
|
@ -31,7 +32,11 @@ impl Completions {
|
|||
.detail(ty.display(ctx.db).to_string())
|
||||
.set_documentation(field.docs(ctx.db))
|
||||
.set_deprecated(is_deprecated)
|
||||
.add_to(self);
|
||||
.build();
|
||||
|
||||
compute_score(&mut completion_item, ctx);
|
||||
|
||||
self.add(completion_item);
|
||||
}
|
||||
|
||||
pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) {
|
||||
|
|
@ -300,6 +305,42 @@ impl Completions {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn compute_score(completion_item: &mut CompletionItem, ctx: &CompletionContext) {
|
||||
let (active_name, active_type) = if let Some(record_field) = &ctx.record_field_syntax {
|
||||
if let Some((struct_field, _)) = ctx.sema.resolve_record_field(record_field) {
|
||||
(
|
||||
struct_field.name(ctx.db).to_string(),
|
||||
struct_field.signature_ty(ctx.db).display(ctx.db).to_string(),
|
||||
)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if let Some(call_info) = call_info(ctx.db, ctx.file_position) {
|
||||
if call_info.active_parameter_type().is_some()
|
||||
&& call_info.active_parameter_name().is_some()
|
||||
{
|
||||
(call_info.active_parameter_name().unwrap(), call_info.active_parameter_type().unwrap())
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Compute score
|
||||
// For the same type
|
||||
if let Some(a_parameter_type) = completion_item.detail() {
|
||||
if &active_type == a_parameter_type {
|
||||
// If same type + same name then go top position
|
||||
if active_name == completion_item.label() {
|
||||
completion_item.set_score(CompletionScore::TypeAndNameMatch);
|
||||
} else {
|
||||
completion_item.set_score(CompletionScore::TypeMatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Params {
|
||||
Named(Vec<String>),
|
||||
Anonymous(usize),
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ pub struct FunctionSignature {
|
|||
pub parameters: Vec<String>,
|
||||
/// Parameter names of the function
|
||||
pub parameter_names: Vec<String>,
|
||||
/// Parameter types of the function
|
||||
pub parameter_types: Vec<String>,
|
||||
/// Optional return type
|
||||
pub ret_type: Option<String>,
|
||||
/// Where predicates
|
||||
|
|
@ -62,14 +64,20 @@ impl FunctionSignature {
|
|||
return None;
|
||||
};
|
||||
|
||||
let params = st
|
||||
.fields(db)
|
||||
.into_iter()
|
||||
.map(|field: hir::StructField| {
|
||||
let ty = field.signature_ty(db);
|
||||
format!("{}", ty.display(db))
|
||||
})
|
||||
.collect();
|
||||
let mut params = vec![];
|
||||
let mut parameter_types = vec![];
|
||||
for field in st.fields(db).into_iter() {
|
||||
let ty = field.signature_ty(db);
|
||||
let raw_param = format!("{}", ty.display(db));
|
||||
|
||||
if let Some(param_type) = raw_param.split(':').nth(1) {
|
||||
parameter_types.push(param_type[1..].to_string());
|
||||
} else {
|
||||
// useful when you have tuple struct
|
||||
parameter_types.push(raw_param.clone());
|
||||
}
|
||||
params.push(raw_param);
|
||||
}
|
||||
|
||||
Some(
|
||||
FunctionSignature {
|
||||
|
|
@ -79,6 +87,7 @@ impl FunctionSignature {
|
|||
ret_type: node.name().map(|n| n.text().to_string()),
|
||||
parameters: params,
|
||||
parameter_names: vec![],
|
||||
parameter_types,
|
||||
generic_parameters: generic_parameters(&node),
|
||||
where_predicates: where_predicates(&node),
|
||||
doc: None,
|
||||
|
|
@ -99,15 +108,21 @@ impl FunctionSignature {
|
|||
|
||||
let name = format!("{}::{}", parent_name, variant.name(db));
|
||||
|
||||
let params = variant
|
||||
.fields(db)
|
||||
.into_iter()
|
||||
.map(|field: hir::StructField| {
|
||||
let name = field.name(db);
|
||||
let ty = field.signature_ty(db);
|
||||
format!("{}: {}", name, ty.display(db))
|
||||
})
|
||||
.collect();
|
||||
let mut params = vec![];
|
||||
let mut parameter_types = vec![];
|
||||
for field in variant.fields(db).into_iter() {
|
||||
let ty = field.signature_ty(db);
|
||||
let raw_param = format!("{}", ty.display(db));
|
||||
if let Some(param_type) = raw_param.split(':').nth(1) {
|
||||
parameter_types.push(param_type[1..].to_string());
|
||||
} else {
|
||||
// The unwrap_or_else is useful when you have tuple
|
||||
parameter_types.push(raw_param);
|
||||
}
|
||||
let name = field.name(db);
|
||||
|
||||
params.push(format!("{}: {}", name, ty.display(db)));
|
||||
}
|
||||
|
||||
Some(
|
||||
FunctionSignature {
|
||||
|
|
@ -117,6 +132,7 @@ impl FunctionSignature {
|
|||
ret_type: None,
|
||||
parameters: params,
|
||||
parameter_names: vec![],
|
||||
parameter_types,
|
||||
generic_parameters: vec![],
|
||||
where_predicates: vec![],
|
||||
doc: None,
|
||||
|
|
@ -139,6 +155,7 @@ impl FunctionSignature {
|
|||
ret_type: None,
|
||||
parameters: params,
|
||||
parameter_names: vec![],
|
||||
parameter_types: vec![],
|
||||
generic_parameters: vec![],
|
||||
where_predicates: vec![],
|
||||
doc: None,
|
||||
|
|
@ -151,18 +168,27 @@ impl FunctionSignature {
|
|||
|
||||
impl From<&'_ ast::FnDef> for FunctionSignature {
|
||||
fn from(node: &ast::FnDef) -> FunctionSignature {
|
||||
fn param_list(node: &ast::FnDef) -> (bool, Vec<String>) {
|
||||
fn param_list(node: &ast::FnDef) -> (bool, Vec<String>, Vec<String>) {
|
||||
let mut res = vec![];
|
||||
let mut res_types = vec![];
|
||||
let mut has_self_param = false;
|
||||
if let Some(param_list) = node.param_list() {
|
||||
if let Some(self_param) = param_list.self_param() {
|
||||
has_self_param = true;
|
||||
res.push(self_param.syntax().text().to_string())
|
||||
let raw_param = self_param.syntax().text().to_string();
|
||||
|
||||
res_types.push(
|
||||
raw_param.split(':').nth(1).unwrap_or_else(|| " Self")[1..].to_string(),
|
||||
);
|
||||
res.push(raw_param);
|
||||
}
|
||||
|
||||
res.extend(param_list.params().map(|param| param.syntax().text().to_string()));
|
||||
res_types.extend(param_list.params().map(|param| {
|
||||
param.syntax().text().to_string().split(':').nth(1).unwrap()[1..].to_string()
|
||||
}));
|
||||
}
|
||||
(has_self_param, res)
|
||||
(has_self_param, res, res_types)
|
||||
}
|
||||
|
||||
fn param_name_list(node: &ast::FnDef) -> Vec<String> {
|
||||
|
|
@ -192,7 +218,7 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
|
|||
res
|
||||
}
|
||||
|
||||
let (has_self_param, parameters) = param_list(node);
|
||||
let (has_self_param, parameters, parameter_types) = param_list(node);
|
||||
|
||||
FunctionSignature {
|
||||
kind: CallableKind::Function,
|
||||
|
|
@ -204,6 +230,7 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
|
|||
.map(|n| n.syntax().text().to_string()),
|
||||
parameters,
|
||||
parameter_names: param_name_list(node),
|
||||
parameter_types,
|
||||
generic_parameters: generic_parameters(node),
|
||||
where_predicates: where_predicates(node),
|
||||
// docs are processed separately
|
||||
|
|
|
|||
|
|
@ -67,7 +67,9 @@ use crate::display::ToNav;
|
|||
pub use crate::{
|
||||
assists::{Assist, AssistId},
|
||||
call_hierarchy::CallItem,
|
||||
completion::{CompletionConfig, CompletionItem, CompletionItemKind, InsertTextFormat},
|
||||
completion::{
|
||||
CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat,
|
||||
},
|
||||
diagnostics::Severity,
|
||||
display::{file_structure, FunctionSignature, NavigationTarget, StructureNode},
|
||||
expand_macro::ExpandedMacro,
|
||||
|
|
@ -127,6 +129,21 @@ pub struct CallInfo {
|
|||
pub active_parameter: Option<usize>,
|
||||
}
|
||||
|
||||
impl CallInfo {
|
||||
pub fn active_parameter_type(&self) -> Option<String> {
|
||||
if let Some(id) = self.active_parameter {
|
||||
return self.signature.parameter_types.get(id).map(|param_ty| param_ty.clone());
|
||||
}
|
||||
None
|
||||
}
|
||||
pub fn active_parameter_name(&self) -> Option<String> {
|
||||
if let Some(id) = self.active_parameter {
|
||||
return self.signature.parameter_names.get(id).map(|param_ty| param_ty.clone());
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// `AnalysisHost` stores the current state of the world.
|
||||
#[derive(Debug)]
|
||||
pub struct AnalysisHost {
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ use lsp_types::{
|
|||
TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier, WorkspaceEdit,
|
||||
};
|
||||
use ra_ide::{
|
||||
translate_offset_with_edit, CompletionItem, CompletionItemKind, FileId, FilePosition,
|
||||
FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HighlightModifier, HighlightTag,
|
||||
InlayHint, InlayKind, InsertTextFormat, LineCol, LineIndex, NavigationTarget, RangeInfo,
|
||||
ReferenceAccess, Severity, SourceChange, SourceFileEdit,
|
||||
translate_offset_with_edit, CompletionItem, CompletionItemKind, CompletionScore, FileId,
|
||||
FilePosition, FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HighlightModifier,
|
||||
HighlightTag, InlayHint, InlayKind, InsertTextFormat, LineCol, LineIndex, NavigationTarget,
|
||||
RangeInfo, ReferenceAccess, Severity, SourceChange, SourceFileEdit,
|
||||
};
|
||||
use ra_syntax::{SyntaxKind, TextRange, TextUnit};
|
||||
use ra_text_edit::{AtomTextEdit, TextEdit};
|
||||
|
|
@ -114,10 +114,10 @@ impl Conv for Severity {
|
|||
}
|
||||
}
|
||||
|
||||
impl ConvWith<(&LineIndex, LineEndings)> for CompletionItem {
|
||||
impl ConvWith<(&LineIndex, LineEndings, &mut usize)> for CompletionItem {
|
||||
type Output = ::lsp_types::CompletionItem;
|
||||
|
||||
fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> ::lsp_types::CompletionItem {
|
||||
fn conv_with(self, ctx: (&LineIndex, LineEndings, &mut usize)) -> ::lsp_types::CompletionItem {
|
||||
let mut additional_text_edits = Vec::new();
|
||||
let mut text_edit = None;
|
||||
// LSP does not allow arbitrary edits in completion, so we have to do a
|
||||
|
|
@ -125,7 +125,7 @@ impl ConvWith<(&LineIndex, LineEndings)> for CompletionItem {
|
|||
for atom_edit in self.text_edit().as_atoms() {
|
||||
if self.source_range().is_subrange(&atom_edit.delete) {
|
||||
text_edit = Some(if atom_edit.delete == self.source_range() {
|
||||
atom_edit.conv_with(ctx)
|
||||
atom_edit.conv_with((ctx.0, ctx.1))
|
||||
} else {
|
||||
assert!(self.source_range().end() == atom_edit.delete.end());
|
||||
let range1 =
|
||||
|
|
@ -133,12 +133,12 @@ impl ConvWith<(&LineIndex, LineEndings)> for CompletionItem {
|
|||
let range2 = self.source_range();
|
||||
let edit1 = AtomTextEdit::replace(range1, String::new());
|
||||
let edit2 = AtomTextEdit::replace(range2, atom_edit.insert.clone());
|
||||
additional_text_edits.push(edit1.conv_with(ctx));
|
||||
edit2.conv_with(ctx)
|
||||
additional_text_edits.push(edit1.conv_with((ctx.0, ctx.1)));
|
||||
edit2.conv_with((ctx.0, ctx.1))
|
||||
})
|
||||
} else {
|
||||
assert!(self.source_range().intersection(&atom_edit.delete).is_none());
|
||||
additional_text_edits.push(atom_edit.conv_with(ctx));
|
||||
additional_text_edits.push(atom_edit.conv_with((ctx.0, ctx.1)));
|
||||
}
|
||||
}
|
||||
let text_edit = text_edit.unwrap();
|
||||
|
|
@ -165,6 +165,15 @@ impl ConvWith<(&LineIndex, LineEndings)> for CompletionItem {
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
if let Some(score) = self.score() {
|
||||
match score {
|
||||
CompletionScore::TypeAndNameMatch => res.preselect = Some(true),
|
||||
CompletionScore::TypeMatch => {}
|
||||
}
|
||||
res.sort_text = Some(format!("{:02}", *ctx.2));
|
||||
*ctx.2 += 1;
|
||||
}
|
||||
|
||||
if self.deprecated() {
|
||||
res.tags = Some(vec![lsp_types::CompletionItemTag::Deprecated])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -423,8 +423,11 @@ pub fn handle_completion(
|
|||
};
|
||||
let line_index = world.analysis().file_line_index(position.file_id)?;
|
||||
let line_endings = world.file_line_endings(position.file_id);
|
||||
let items: Vec<CompletionItem> =
|
||||
items.into_iter().map(|item| item.conv_with((&line_index, line_endings))).collect();
|
||||
let mut count_sort_text_item = 0usize;
|
||||
let items: Vec<CompletionItem> = items
|
||||
.into_iter()
|
||||
.map(|item| item.conv_with((&line_index, line_endings, &mut count_sort_text_item)))
|
||||
.collect();
|
||||
|
||||
Ok(Some(items.into()))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue