Merge #4700
4700: Add top level keywords completion r=matklad a=mcrakhman This fixes the following issue: https://github.com/rust-analyzer/rust-analyzer/issues/4566. Also added simple logic which filters the keywords which can be used with unsafe on the top level. Co-authored-by: Mikhail Rakhmanov <rakhmanov.m@gmail.com> Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
c87c4a0a40
@ -15,6 +15,7 @@ mod complete_unqualified_path;
|
||||
mod complete_postfix;
|
||||
mod complete_macro_in_item_position;
|
||||
mod complete_trait_impl;
|
||||
mod patterns;
|
||||
#[cfg(test)]
|
||||
mod test_utils;
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,12 +5,17 @@ use ra_db::SourceDatabase;
|
||||
use ra_ide_db::RootDatabase;
|
||||
use ra_syntax::{
|
||||
algo::{find_covering_element, find_node_at_offset},
|
||||
ast, match_ast, AstNode,
|
||||
ast, match_ast, AstNode, NodeOrToken,
|
||||
SyntaxKind::*,
|
||||
SyntaxNode, SyntaxToken, TextRange, TextSize,
|
||||
};
|
||||
use ra_text_edit::Indel;
|
||||
|
||||
use super::patterns::{
|
||||
has_bind_pat_parent, has_block_expr_parent, has_impl_as_prev_sibling, has_impl_parent,
|
||||
has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling,
|
||||
has_trait_parent, if_is_prev, is_in_loop_body, is_match_arm, unsafe_is_prev,
|
||||
};
|
||||
use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition};
|
||||
use test_utils::mark;
|
||||
|
||||
@ -60,6 +65,18 @@ pub(crate) struct CompletionContext<'a> {
|
||||
pub(super) is_path_type: bool,
|
||||
pub(super) has_type_args: bool,
|
||||
pub(super) attribute_under_caret: Option<ast::Attr>,
|
||||
pub(super) unsafe_is_prev: bool,
|
||||
pub(super) if_is_prev: bool,
|
||||
pub(super) block_expr_parent: bool,
|
||||
pub(super) bind_pat_parent: bool,
|
||||
pub(super) ref_pat_parent: bool,
|
||||
pub(super) in_loop_body: bool,
|
||||
pub(super) has_trait_parent: bool,
|
||||
pub(super) has_impl_parent: bool,
|
||||
pub(super) trait_as_prev_sibling: bool,
|
||||
pub(super) impl_as_prev_sibling: bool,
|
||||
pub(super) is_match_arm: bool,
|
||||
pub(super) has_item_list_or_source_file_parent: bool,
|
||||
}
|
||||
|
||||
impl<'a> CompletionContext<'a> {
|
||||
@ -118,6 +135,18 @@ impl<'a> CompletionContext<'a> {
|
||||
has_type_args: false,
|
||||
dot_receiver_is_ambiguous_float_literal: false,
|
||||
attribute_under_caret: None,
|
||||
unsafe_is_prev: false,
|
||||
in_loop_body: false,
|
||||
ref_pat_parent: false,
|
||||
bind_pat_parent: false,
|
||||
block_expr_parent: false,
|
||||
has_trait_parent: false,
|
||||
has_impl_parent: false,
|
||||
trait_as_prev_sibling: false,
|
||||
impl_as_prev_sibling: false,
|
||||
if_is_prev: false,
|
||||
is_match_arm: false,
|
||||
has_item_list_or_source_file_parent: false,
|
||||
};
|
||||
|
||||
let mut original_file = original_file.syntax().clone();
|
||||
@ -159,7 +188,7 @@ impl<'a> CompletionContext<'a> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.fill_keyword_patterns(&hypothetical_file, offset);
|
||||
ctx.fill(&original_file, hypothetical_file, offset);
|
||||
Some(ctx)
|
||||
}
|
||||
@ -188,6 +217,24 @@ impl<'a> CompletionContext<'a> {
|
||||
self.sema.scope_at_offset(&self.token.parent(), self.offset)
|
||||
}
|
||||
|
||||
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 syntax_element = NodeOrToken::Token(fake_ident_token.clone());
|
||||
self.block_expr_parent = has_block_expr_parent(syntax_element.clone());
|
||||
self.unsafe_is_prev = unsafe_is_prev(syntax_element.clone());
|
||||
self.if_is_prev = if_is_prev(syntax_element.clone());
|
||||
self.bind_pat_parent = has_bind_pat_parent(syntax_element.clone());
|
||||
self.ref_pat_parent = has_ref_parent(syntax_element.clone());
|
||||
self.in_loop_body = is_in_loop_body(syntax_element.clone());
|
||||
self.has_trait_parent = has_trait_parent(syntax_element.clone());
|
||||
self.has_impl_parent = has_impl_parent(syntax_element.clone());
|
||||
self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone());
|
||||
self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone());
|
||||
self.is_match_arm = is_match_arm(syntax_element.clone());
|
||||
self.has_item_list_or_source_file_parent =
|
||||
has_item_list_or_source_file_parent(syntax_element.clone());
|
||||
}
|
||||
|
||||
fn fill(
|
||||
&mut self,
|
||||
original_file: &SyntaxNode,
|
||||
|
@ -125,6 +125,32 @@ pub enum CompletionItemKind {
|
||||
Attribute,
|
||||
}
|
||||
|
||||
impl CompletionItemKind {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn tag(&self) -> &'static str {
|
||||
match self {
|
||||
CompletionItemKind::Snippet => "sn",
|
||||
CompletionItemKind::Keyword => "kw",
|
||||
CompletionItemKind::Module => "md",
|
||||
CompletionItemKind::Function => "fn",
|
||||
CompletionItemKind::BuiltinType => "bt",
|
||||
CompletionItemKind::Struct => "st",
|
||||
CompletionItemKind::Enum => "en",
|
||||
CompletionItemKind::EnumVariant => "ev",
|
||||
CompletionItemKind::Binding => "bn",
|
||||
CompletionItemKind::Field => "fd",
|
||||
CompletionItemKind::Static => "sc",
|
||||
CompletionItemKind::Const => "ct",
|
||||
CompletionItemKind::Trait => "tt",
|
||||
CompletionItemKind::TypeAlias => "ta",
|
||||
CompletionItemKind::Method => "me",
|
||||
CompletionItemKind::TypeParam => "tp",
|
||||
CompletionItemKind::Macro => "ma",
|
||||
CompletionItemKind::Attribute => "at",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub(crate) enum CompletionKind {
|
||||
/// Parser-based keyword completion.
|
||||
|
194
crates/ra_ide/src/completion/patterns.rs
Normal file
194
crates/ra_ide/src/completion/patterns.rs
Normal file
@ -0,0 +1,194 @@
|
||||
//! Patterns telling us certain facts about current syntax element, they are used in completion context
|
||||
|
||||
use ra_syntax::{
|
||||
algo::non_trivia_sibling,
|
||||
ast::{self, LoopBodyOwner},
|
||||
match_ast, AstNode, Direction, NodeOrToken, SyntaxElement,
|
||||
SyntaxKind::*,
|
||||
SyntaxNode, SyntaxToken,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::completion::test_utils::check_pattern_is_applicable;
|
||||
|
||||
pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool {
|
||||
not_same_range_ancestor(element)
|
||||
.filter(|it| it.kind() == ITEM_LIST)
|
||||
.and_then(|it| it.parent())
|
||||
.filter(|it| it.kind() == TRAIT_DEF)
|
||||
.is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_has_trait_parent() {
|
||||
check_pattern_is_applicable(r"trait A { f<|> }", has_trait_parent);
|
||||
}
|
||||
|
||||
pub(crate) fn has_impl_parent(element: SyntaxElement) -> bool {
|
||||
not_same_range_ancestor(element)
|
||||
.filter(|it| it.kind() == ITEM_LIST)
|
||||
.and_then(|it| it.parent())
|
||||
.filter(|it| it.kind() == IMPL_DEF)
|
||||
.is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_has_impl_parent() {
|
||||
check_pattern_is_applicable(r"impl A { f<|> }", has_impl_parent);
|
||||
}
|
||||
|
||||
pub(crate) fn has_block_expr_parent(element: SyntaxElement) -> bool {
|
||||
not_same_range_ancestor(element).filter(|it| it.kind() == BLOCK_EXPR).is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_has_block_expr_parent() {
|
||||
check_pattern_is_applicable(r"fn my_fn() { let a = 2; f<|> }", has_block_expr_parent);
|
||||
}
|
||||
|
||||
pub(crate) fn has_bind_pat_parent(element: SyntaxElement) -> bool {
|
||||
element.ancestors().find(|it| it.kind() == BIND_PAT).is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_has_bind_pat_parent() {
|
||||
check_pattern_is_applicable(r"fn my_fn(m<|>) {}", has_bind_pat_parent);
|
||||
check_pattern_is_applicable(r"fn my_fn() { let m<|> }", has_bind_pat_parent);
|
||||
}
|
||||
|
||||
pub(crate) fn has_ref_parent(element: SyntaxElement) -> bool {
|
||||
not_same_range_ancestor(element)
|
||||
.filter(|it| it.kind() == REF_PAT || it.kind() == REF_EXPR)
|
||||
.is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_has_ref_parent() {
|
||||
check_pattern_is_applicable(r"fn my_fn(&m<|>) {}", has_ref_parent);
|
||||
check_pattern_is_applicable(r"fn my() { let &m<|> }", has_ref_parent);
|
||||
}
|
||||
|
||||
pub(crate) fn has_item_list_or_source_file_parent(element: SyntaxElement) -> bool {
|
||||
let ancestor = not_same_range_ancestor(element);
|
||||
if !ancestor.is_some() {
|
||||
return true;
|
||||
}
|
||||
ancestor.filter(|it| it.kind() == SOURCE_FILE || it.kind() == ITEM_LIST).is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_has_item_list_or_source_file_parent() {
|
||||
check_pattern_is_applicable(r"i<|>", has_item_list_or_source_file_parent);
|
||||
check_pattern_is_applicable(r"impl { f<|> }", has_item_list_or_source_file_parent);
|
||||
}
|
||||
|
||||
pub(crate) fn is_match_arm(element: SyntaxElement) -> bool {
|
||||
not_same_range_ancestor(element.clone()).filter(|it| it.kind() == MATCH_ARM).is_some()
|
||||
&& previous_sibling_or_ancestor_sibling(element)
|
||||
.and_then(|it| it.into_token())
|
||||
.filter(|it| it.kind() == FAT_ARROW)
|
||||
.is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_is_match_arm() {
|
||||
check_pattern_is_applicable(r"fn my_fn() { match () { () => m<|> } }", is_match_arm);
|
||||
}
|
||||
|
||||
pub(crate) fn unsafe_is_prev(element: SyntaxElement) -> bool {
|
||||
element
|
||||
.into_token()
|
||||
.and_then(|it| previous_non_trivia_token(it))
|
||||
.filter(|it| it.kind() == UNSAFE_KW)
|
||||
.is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_unsafe_is_prev() {
|
||||
check_pattern_is_applicable(r"unsafe i<|>", unsafe_is_prev);
|
||||
}
|
||||
|
||||
pub(crate) fn if_is_prev(element: SyntaxElement) -> bool {
|
||||
element
|
||||
.into_token()
|
||||
.and_then(|it| previous_non_trivia_token(it))
|
||||
.filter(|it| it.kind() == IF_KW)
|
||||
.is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_if_is_prev() {
|
||||
check_pattern_is_applicable(r"if l<|>", if_is_prev);
|
||||
}
|
||||
|
||||
pub(crate) fn has_trait_as_prev_sibling(element: SyntaxElement) -> bool {
|
||||
previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == TRAIT_DEF).is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_has_trait_as_prev_sibling() {
|
||||
check_pattern_is_applicable(r"trait A w<|> {}", has_trait_as_prev_sibling);
|
||||
}
|
||||
|
||||
pub(crate) fn has_impl_as_prev_sibling(element: SyntaxElement) -> bool {
|
||||
previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == IMPL_DEF).is_some()
|
||||
}
|
||||
#[test]
|
||||
fn test_has_impl_as_prev_sibling() {
|
||||
check_pattern_is_applicable(r"impl A w<|> {}", has_impl_as_prev_sibling);
|
||||
}
|
||||
|
||||
pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool {
|
||||
let leaf = match element {
|
||||
NodeOrToken::Node(node) => node,
|
||||
NodeOrToken::Token(token) => token.parent(),
|
||||
};
|
||||
for node in leaf.ancestors() {
|
||||
if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
|
||||
break;
|
||||
}
|
||||
let loop_body = match_ast! {
|
||||
match node {
|
||||
ast::ForExpr(it) => it.loop_body(),
|
||||
ast::WhileExpr(it) => it.loop_body(),
|
||||
ast::LoopExpr(it) => it.loop_body(),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
if let Some(body) = loop_body {
|
||||
if body.syntax().text_range().contains_range(leaf.text_range()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn not_same_range_ancestor(element: SyntaxElement) -> Option<SyntaxNode> {
|
||||
element
|
||||
.ancestors()
|
||||
.take_while(|it| it.text_range() == element.text_range())
|
||||
.last()
|
||||
.and_then(|it| it.parent())
|
||||
}
|
||||
|
||||
fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
|
||||
let mut token = token.prev_token();
|
||||
while let Some(inner) = token.clone() {
|
||||
if !inner.kind().is_trivia() {
|
||||
return Some(inner);
|
||||
} else {
|
||||
token = inner.prev_token();
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option<SyntaxElement> {
|
||||
let token_sibling = non_trivia_sibling(element.clone(), Direction::Prev);
|
||||
if let Some(sibling) = token_sibling {
|
||||
Some(sibling)
|
||||
} else {
|
||||
// if not trying to find first ancestor which has such a sibling
|
||||
let node = match element {
|
||||
NodeOrToken::Node(node) => node,
|
||||
NodeOrToken::Token(token) => token.parent(),
|
||||
};
|
||||
let range = node.text_range();
|
||||
let top_node = node.ancestors().take_while(|it| it.text_range() == range).last()?;
|
||||
let prev_sibling_node = top_node.ancestors().find(|it| {
|
||||
non_trivia_sibling(NodeOrToken::Node(it.to_owned()), Direction::Prev).is_some()
|
||||
})?;
|
||||
non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev)
|
||||
}
|
||||
}
|
@ -5,25 +5,63 @@ use crate::{
|
||||
mock_analysis::{analysis_and_position, single_file_with_position},
|
||||
CompletionItem,
|
||||
};
|
||||
use hir::Semantics;
|
||||
use ra_syntax::{AstNode, NodeOrToken, SyntaxElement};
|
||||
|
||||
pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> {
|
||||
do_completion_with_options(code, kind, &CompletionConfig::default())
|
||||
}
|
||||
|
||||
pub(crate) fn completion_list(code: &str, kind: CompletionKind) -> String {
|
||||
completion_list_with_options(code, kind, &CompletionConfig::default())
|
||||
}
|
||||
|
||||
pub(crate) fn do_completion_with_options(
|
||||
code: &str,
|
||||
kind: CompletionKind,
|
||||
options: &CompletionConfig,
|
||||
) -> Vec<CompletionItem> {
|
||||
let mut kind_completions: Vec<CompletionItem> = get_all_completion_items(code, options)
|
||||
.into_iter()
|
||||
.filter(|c| c.completion_kind == kind)
|
||||
.collect();
|
||||
kind_completions.sort_by(|l, r| l.label().cmp(r.label()));
|
||||
kind_completions
|
||||
}
|
||||
|
||||
fn get_all_completion_items(code: &str, options: &CompletionConfig) -> Vec<CompletionItem> {
|
||||
let (analysis, position) = if code.contains("//-") {
|
||||
analysis_and_position(code)
|
||||
} else {
|
||||
single_file_with_position(code)
|
||||
};
|
||||
let completions = analysis.completions(options, position).unwrap().unwrap();
|
||||
let completion_items: Vec<CompletionItem> = completions.into();
|
||||
let mut kind_completions: Vec<CompletionItem> =
|
||||
completion_items.into_iter().filter(|c| c.completion_kind == kind).collect();
|
||||
analysis.completions(options, position).unwrap().unwrap().into()
|
||||
}
|
||||
|
||||
pub(crate) fn completion_list_with_options(
|
||||
code: &str,
|
||||
kind: CompletionKind,
|
||||
options: &CompletionConfig,
|
||||
) -> String {
|
||||
let mut kind_completions: Vec<CompletionItem> = get_all_completion_items(code, options)
|
||||
.into_iter()
|
||||
.filter(|c| c.completion_kind == kind)
|
||||
.collect();
|
||||
kind_completions.sort_by_key(|c| c.label().to_owned());
|
||||
kind_completions
|
||||
.into_iter()
|
||||
.map(|it| format!("{} {}\n", it.kind().unwrap().tag(), it.label()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn check_pattern_is_applicable(code: &str, check: fn(SyntaxElement) -> bool) {
|
||||
let (analysis, pos) = single_file_with_position(code);
|
||||
analysis
|
||||
.with_db(|db| {
|
||||
let sema = Semantics::new(db);
|
||||
let original_file = sema.parse(pos.file_id);
|
||||
let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap();
|
||||
assert!(check(NodeOrToken::Token(token)));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user