Make trait item completions work in a bare impl and score them
This commit is contained in:
parent
cdeb6140bf
commit
22b13c8bff
@ -36,11 +36,13 @@ use ide_db::{path_transform::PathTransform, traits::get_missing_assoc_items, Sym
|
||||
use syntax::{
|
||||
ast::{self, edit_in_place::AttrsOwnerEdit},
|
||||
display::function_declaration,
|
||||
AstNode, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, T,
|
||||
AstNode, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, T,
|
||||
};
|
||||
use text_edit::TextEdit;
|
||||
|
||||
use crate::{CompletionContext, CompletionItem, CompletionItemKind, Completions};
|
||||
use crate::{
|
||||
CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance, Completions,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
enum ImplCompletionKind {
|
||||
@ -51,22 +53,22 @@ enum ImplCompletionKind {
|
||||
}
|
||||
|
||||
pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
if let Some((kind, trigger, impl_def)) = completion_match(ctx.token.clone()) {
|
||||
if let Some((kind, replacement_range, impl_def)) = completion_match(ctx) {
|
||||
if let Some(hir_impl) = ctx.sema.to_def(&impl_def) {
|
||||
get_missing_assoc_items(&ctx.sema, &impl_def).into_iter().for_each(|item| {
|
||||
match (item, kind) {
|
||||
(
|
||||
hir::AssocItem::Function(fn_item),
|
||||
ImplCompletionKind::All | ImplCompletionKind::Fn,
|
||||
) => add_function_impl(acc, ctx, &trigger, fn_item, hir_impl),
|
||||
) => add_function_impl(acc, ctx, replacement_range, fn_item, hir_impl),
|
||||
(
|
||||
hir::AssocItem::TypeAlias(type_item),
|
||||
ImplCompletionKind::All | ImplCompletionKind::TypeAlias,
|
||||
) => add_type_alias_impl(acc, ctx, &trigger, type_item),
|
||||
) => add_type_alias_impl(acc, ctx, replacement_range, type_item),
|
||||
(
|
||||
hir::AssocItem::Const(const_item),
|
||||
ImplCompletionKind::All | ImplCompletionKind::Const,
|
||||
) => add_const_impl(acc, ctx, &trigger, const_item, hir_impl),
|
||||
) => add_const_impl(acc, ctx, replacement_range, const_item, hir_impl),
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
@ -74,61 +76,79 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext
|
||||
}
|
||||
}
|
||||
|
||||
fn completion_match(mut token: SyntaxToken) -> Option<(ImplCompletionKind, SyntaxNode, ast::Impl)> {
|
||||
fn completion_match(ctx: &CompletionContext) -> Option<(ImplCompletionKind, TextRange, ast::Impl)> {
|
||||
let token = ctx.token.clone();
|
||||
|
||||
// For keyword without name like `impl .. { fn $0 }`, the current position is inside
|
||||
// the whitespace token, which is outside `FN` syntax node.
|
||||
// We need to follow the previous token in this case.
|
||||
let mut token_before_ws = token.clone();
|
||||
if token.kind() == SyntaxKind::WHITESPACE {
|
||||
token = token.prev_token()?;
|
||||
token_before_ws = token.prev_token()?;
|
||||
}
|
||||
|
||||
let parent_kind = token.parent().map_or(SyntaxKind::EOF, |it| it.kind());
|
||||
let impl_item_offset = match token.kind() {
|
||||
// `impl .. { const $0 }`
|
||||
// ERROR 0
|
||||
// CONST_KW <- *
|
||||
T![const] => 0,
|
||||
// `impl .. { fn/type $0 }`
|
||||
// FN/TYPE_ALIAS 0
|
||||
// FN_KW <- *
|
||||
T![fn] | T![type] => 0,
|
||||
// `impl .. { fn/type/const foo$0 }`
|
||||
// FN/TYPE_ALIAS/CONST 1
|
||||
// NAME 0
|
||||
// IDENT <- *
|
||||
SyntaxKind::IDENT if parent_kind == SyntaxKind::NAME => 1,
|
||||
// `impl .. { foo$0 }`
|
||||
// MACRO_CALL 3
|
||||
// PATH 2
|
||||
// PATH_SEGMENT 1
|
||||
// NAME_REF 0
|
||||
// IDENT <- *
|
||||
SyntaxKind::IDENT if parent_kind == SyntaxKind::NAME_REF => 3,
|
||||
_ => return None,
|
||||
};
|
||||
let parent_kind = token_before_ws.parent().map_or(SyntaxKind::EOF, |it| it.kind());
|
||||
if token.parent().map(|n| n.kind()) == Some(SyntaxKind::ASSOC_ITEM_LIST)
|
||||
&& matches!(
|
||||
token_before_ws.kind(),
|
||||
SyntaxKind::SEMICOLON | SyntaxKind::R_CURLY | SyntaxKind::L_CURLY
|
||||
)
|
||||
{
|
||||
let impl_def = ast::Impl::cast(token.parent()?.parent()?)?;
|
||||
let kind = ImplCompletionKind::All;
|
||||
let replacement_range = TextRange::empty(ctx.position.offset);
|
||||
Some((kind, replacement_range, impl_def))
|
||||
} else {
|
||||
let impl_item_offset = match token_before_ws.kind() {
|
||||
// `impl .. { const $0 }`
|
||||
// ERROR 0
|
||||
// CONST_KW <- *
|
||||
T![const] => 0,
|
||||
// `impl .. { fn/type $0 }`
|
||||
// FN/TYPE_ALIAS 0
|
||||
// FN_KW <- *
|
||||
T![fn] | T![type] => 0,
|
||||
// `impl .. { fn/type/const foo$0 }`
|
||||
// FN/TYPE_ALIAS/CONST 1
|
||||
// NAME 0
|
||||
// IDENT <- *
|
||||
SyntaxKind::IDENT if parent_kind == SyntaxKind::NAME => 1,
|
||||
// `impl .. { foo$0 }`
|
||||
// MACRO_CALL 3
|
||||
// PATH 2
|
||||
// PATH_SEGMENT 1
|
||||
// NAME_REF 0
|
||||
// IDENT <- *
|
||||
SyntaxKind::IDENT if parent_kind == SyntaxKind::NAME_REF => 3,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let impl_item = token.ancestors().nth(impl_item_offset)?;
|
||||
// Must directly belong to an impl block.
|
||||
// IMPL
|
||||
// ASSOC_ITEM_LIST
|
||||
// <item>
|
||||
let impl_def = ast::Impl::cast(impl_item.parent()?.parent()?)?;
|
||||
let kind = match impl_item.kind() {
|
||||
// `impl ... { const $0 fn/type/const }`
|
||||
_ if token.kind() == T![const] => ImplCompletionKind::Const,
|
||||
SyntaxKind::CONST | SyntaxKind::ERROR => ImplCompletionKind::Const,
|
||||
SyntaxKind::TYPE_ALIAS => ImplCompletionKind::TypeAlias,
|
||||
SyntaxKind::FN => ImplCompletionKind::Fn,
|
||||
SyntaxKind::MACRO_CALL => ImplCompletionKind::All,
|
||||
_ => return None,
|
||||
};
|
||||
Some((kind, impl_item, impl_def))
|
||||
let impl_item = token_before_ws.ancestors().nth(impl_item_offset)?;
|
||||
// Must directly belong to an impl block.
|
||||
// IMPL
|
||||
// ASSOC_ITEM_LIST
|
||||
// <item>
|
||||
let impl_def = ast::Impl::cast(impl_item.parent()?.parent()?)?;
|
||||
let kind = match impl_item.kind() {
|
||||
// `impl ... { const $0 fn/type/const }`
|
||||
_ if token_before_ws.kind() == T![const] => ImplCompletionKind::Const,
|
||||
SyntaxKind::CONST | SyntaxKind::ERROR => ImplCompletionKind::Const,
|
||||
SyntaxKind::TYPE_ALIAS => ImplCompletionKind::TypeAlias,
|
||||
SyntaxKind::FN => ImplCompletionKind::Fn,
|
||||
SyntaxKind::MACRO_CALL => ImplCompletionKind::All,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let replacement_range = replacement_range(ctx, &impl_item);
|
||||
|
||||
Some((kind, replacement_range, impl_def))
|
||||
}
|
||||
}
|
||||
|
||||
fn add_function_impl(
|
||||
acc: &mut Completions,
|
||||
ctx: &CompletionContext,
|
||||
fn_def_node: &SyntaxNode,
|
||||
replacement_range: TextRange,
|
||||
func: hir::Function,
|
||||
impl_def: hir::Impl,
|
||||
) {
|
||||
@ -146,9 +166,10 @@ fn add_function_impl(
|
||||
CompletionItemKind::SymbolKind(SymbolKind::Function)
|
||||
};
|
||||
|
||||
let range = replacement_range(ctx, fn_def_node);
|
||||
let mut item = CompletionItem::new(completion_kind, range, label);
|
||||
item.lookup_by(fn_name).set_documentation(func.docs(ctx.db));
|
||||
let mut item = CompletionItem::new(completion_kind, replacement_range, label);
|
||||
item.lookup_by(fn_name)
|
||||
.set_documentation(func.docs(ctx.db))
|
||||
.set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
|
||||
|
||||
if let Some(source) = ctx.sema.source(func) {
|
||||
let assoc_item = ast::AssocItem::Fn(source.value);
|
||||
@ -162,11 +183,11 @@ fn add_function_impl(
|
||||
match ctx.config.snippet_cap {
|
||||
Some(cap) => {
|
||||
let snippet = format!("{} {{\n $0\n}}", function_decl);
|
||||
item.snippet_edit(cap, TextEdit::replace(range, snippet));
|
||||
item.snippet_edit(cap, TextEdit::replace(replacement_range, snippet));
|
||||
}
|
||||
None => {
|
||||
let header = format!("{} {{", function_decl);
|
||||
item.text_edit(TextEdit::replace(range, header));
|
||||
item.text_edit(TextEdit::replace(replacement_range, header));
|
||||
}
|
||||
};
|
||||
item.add_to(acc);
|
||||
@ -201,25 +222,26 @@ fn get_transformed_assoc_item(
|
||||
fn add_type_alias_impl(
|
||||
acc: &mut Completions,
|
||||
ctx: &CompletionContext,
|
||||
type_def_node: &SyntaxNode,
|
||||
replacement_range: TextRange,
|
||||
type_alias: hir::TypeAlias,
|
||||
) {
|
||||
let alias_name = type_alias.name(ctx.db).to_smol_str();
|
||||
|
||||
let label = format!("type {} =", alias_name);
|
||||
let snippet = format!("type {} = ", alias_name);
|
||||
|
||||
let range = replacement_range(ctx, type_def_node);
|
||||
let mut item = CompletionItem::new(SymbolKind::TypeAlias, range, &snippet);
|
||||
item.text_edit(TextEdit::replace(range, snippet))
|
||||
let mut item = CompletionItem::new(SymbolKind::TypeAlias, replacement_range, label);
|
||||
item.text_edit(TextEdit::replace(replacement_range, snippet))
|
||||
.lookup_by(alias_name)
|
||||
.set_documentation(type_alias.docs(ctx.db));
|
||||
.set_documentation(type_alias.docs(ctx.db))
|
||||
.set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
|
||||
item.add_to(acc);
|
||||
}
|
||||
|
||||
fn add_const_impl(
|
||||
acc: &mut Completions,
|
||||
ctx: &CompletionContext,
|
||||
const_def_node: &SyntaxNode,
|
||||
replacement_range: TextRange,
|
||||
const_: hir::Const,
|
||||
impl_def: hir::Impl,
|
||||
) {
|
||||
@ -234,13 +256,17 @@ fn add_const_impl(
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let snippet = make_const_compl_syntax(&transformed_const);
|
||||
let label = make_const_compl_syntax(&transformed_const);
|
||||
let snippet = format!("{} ", label);
|
||||
|
||||
let range = replacement_range(ctx, const_def_node);
|
||||
let mut item = CompletionItem::new(SymbolKind::Const, range, &snippet);
|
||||
item.text_edit(TextEdit::replace(range, snippet))
|
||||
let mut item = CompletionItem::new(SymbolKind::Const, replacement_range, label);
|
||||
item.text_edit(TextEdit::replace(replacement_range, snippet))
|
||||
.lookup_by(const_name)
|
||||
.set_documentation(const_.docs(ctx.db));
|
||||
.set_documentation(const_.docs(ctx.db))
|
||||
.set_relevance(CompletionRelevance {
|
||||
is_item_from_trait: true,
|
||||
..Default::default()
|
||||
});
|
||||
item.add_to(acc);
|
||||
}
|
||||
}
|
||||
@ -267,7 +293,7 @@ fn make_const_compl_syntax(const_: &ast::Const) -> String {
|
||||
|
||||
let syntax = const_.syntax().text().slice(range).to_string();
|
||||
|
||||
format!("{} = ", syntax.trim_end())
|
||||
format!("{} =", syntax.trim_end())
|
||||
}
|
||||
|
||||
fn replacement_range(ctx: &CompletionContext, item: &SyntaxNode) -> TextRange {
|
||||
@ -987,4 +1013,38 @@ where Self: SomeTrait<u32> {
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_directly_in_impl() {
|
||||
check(
|
||||
r#"
|
||||
trait Tr {
|
||||
fn required();
|
||||
}
|
||||
|
||||
impl Tr for () {
|
||||
$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn fn required()
|
||||
"#]],
|
||||
);
|
||||
check(
|
||||
r#"
|
||||
trait Tr {
|
||||
fn provided() {}
|
||||
fn required();
|
||||
}
|
||||
|
||||
impl Tr for () {
|
||||
fn provided() {}
|
||||
$0
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fn fn required()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -132,6 +132,8 @@ pub struct CompletionRelevance {
|
||||
/// }
|
||||
/// ```
|
||||
pub is_local: bool,
|
||||
/// This is set when trait items are completed in an impl of that trait.
|
||||
pub is_item_from_trait: bool,
|
||||
/// Set for method completions of the `core::ops` and `core::cmp` family.
|
||||
pub is_op_method: bool,
|
||||
/// Set for item completions that are private but in the workspace.
|
||||
@ -197,6 +199,7 @@ impl CompletionRelevance {
|
||||
exact_name_match,
|
||||
type_match,
|
||||
is_local,
|
||||
is_item_from_trait,
|
||||
is_op_method,
|
||||
is_private_editable,
|
||||
postfix_match,
|
||||
@ -228,6 +231,9 @@ impl CompletionRelevance {
|
||||
if is_local {
|
||||
score += 1;
|
||||
}
|
||||
if is_item_from_trait {
|
||||
score += 1;
|
||||
}
|
||||
if is_definite {
|
||||
score += 10;
|
||||
}
|
||||
|
@ -624,6 +624,7 @@ fn main() { let _: m::Spam = S$0 }
|
||||
Exact,
|
||||
),
|
||||
is_local: false,
|
||||
is_item_from_trait: false,
|
||||
is_op_method: false,
|
||||
is_private_editable: false,
|
||||
postfix_match: None,
|
||||
@ -646,6 +647,7 @@ fn main() { let _: m::Spam = S$0 }
|
||||
Exact,
|
||||
),
|
||||
is_local: false,
|
||||
is_item_from_trait: false,
|
||||
is_op_method: false,
|
||||
is_private_editable: false,
|
||||
postfix_match: None,
|
||||
@ -734,6 +736,7 @@ fn foo() { A { the$0 } }
|
||||
CouldUnify,
|
||||
),
|
||||
is_local: false,
|
||||
is_item_from_trait: false,
|
||||
is_op_method: false,
|
||||
is_private_editable: false,
|
||||
postfix_match: None,
|
||||
|
@ -241,11 +241,14 @@ impl Test for () {
|
||||
kw fn
|
||||
kw const
|
||||
kw type
|
||||
ta type Type1 =
|
||||
ct const CONST1: () =
|
||||
fn fn function1()
|
||||
kw self
|
||||
kw super
|
||||
kw crate
|
||||
md module
|
||||
ma makro!(…) macro_rules! makro
|
||||
ma makro!(…) macro_rules! makro
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user