mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-02 14:51:48 +00:00
Reorder CompletionContext functions
This commit is contained in:
parent
791a2afbf9
commit
e329b7742b
1 changed files with 293 additions and 289 deletions
|
@ -120,153 +120,6 @@ pub(crate) struct CompletionContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CompletionContext<'a> {
|
impl<'a> CompletionContext<'a> {
|
||||||
pub(super) fn new(
|
|
||||||
db: &'a RootDatabase,
|
|
||||||
position: FilePosition,
|
|
||||||
config: &'a CompletionConfig,
|
|
||||||
) -> Option<CompletionContext<'a>> {
|
|
||||||
let sema = Semantics::new(db);
|
|
||||||
|
|
||||||
let original_file = sema.parse(position.file_id);
|
|
||||||
|
|
||||||
// Insert a fake ident to get a valid parse tree. We will use this file
|
|
||||||
// to determine context, though the original_file will be used for
|
|
||||||
// actual completion.
|
|
||||||
let file_with_fake_ident = {
|
|
||||||
let parse = db.parse(position.file_id);
|
|
||||||
let edit = Indel::insert(position.offset, "intellijRulezz".to_string());
|
|
||||||
parse.reparse(&edit).tree()
|
|
||||||
};
|
|
||||||
let fake_ident_token =
|
|
||||||
file_with_fake_ident.syntax().token_at_offset(position.offset).right_biased().unwrap();
|
|
||||||
|
|
||||||
let original_token =
|
|
||||||
original_file.syntax().token_at_offset(position.offset).left_biased()?;
|
|
||||||
let token = sema.descend_into_macros_single(original_token.clone());
|
|
||||||
let scope = sema.scope_at_offset(&token, position.offset);
|
|
||||||
let krate = scope.krate();
|
|
||||||
let mut locals = vec![];
|
|
||||||
scope.process_all_names(&mut |name, scope| {
|
|
||||||
if let ScopeDef::Local(local) = scope {
|
|
||||||
locals.push((name, local));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let mut ctx = CompletionContext {
|
|
||||||
sema,
|
|
||||||
scope,
|
|
||||||
db,
|
|
||||||
config,
|
|
||||||
position,
|
|
||||||
original_token,
|
|
||||||
token,
|
|
||||||
krate,
|
|
||||||
expected_name: None,
|
|
||||||
expected_type: None,
|
|
||||||
function_def: None,
|
|
||||||
impl_def: None,
|
|
||||||
name_syntax: None,
|
|
||||||
lifetime_ctx: None,
|
|
||||||
pattern_ctx: None,
|
|
||||||
completion_location: None,
|
|
||||||
prev_sibling: None,
|
|
||||||
attribute_under_caret: None,
|
|
||||||
previous_token: None,
|
|
||||||
path_context: None,
|
|
||||||
locals,
|
|
||||||
incomplete_let: false,
|
|
||||||
no_completion_required: false,
|
|
||||||
};
|
|
||||||
ctx.expand_and_fill(
|
|
||||||
original_file.syntax().clone(),
|
|
||||||
file_with_fake_ident.syntax().clone(),
|
|
||||||
position.offset,
|
|
||||||
fake_ident_token,
|
|
||||||
);
|
|
||||||
Some(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Do the attribute expansion at the current cursor position for both original file and fake file
|
|
||||||
/// as long as possible. As soon as one of the two expansions fail we stop to stay in sync.
|
|
||||||
fn expand_and_fill(
|
|
||||||
&mut self,
|
|
||||||
mut original_file: SyntaxNode,
|
|
||||||
mut speculative_file: SyntaxNode,
|
|
||||||
mut offset: TextSize,
|
|
||||||
mut fake_ident_token: SyntaxToken,
|
|
||||||
) {
|
|
||||||
loop {
|
|
||||||
// Expand attributes
|
|
||||||
if let (Some(actual_item), Some(item_with_fake_ident)) = (
|
|
||||||
find_node_at_offset::<ast::Item>(&original_file, offset),
|
|
||||||
find_node_at_offset::<ast::Item>(&speculative_file, offset),
|
|
||||||
) {
|
|
||||||
match (
|
|
||||||
self.sema.expand_attr_macro(&actual_item),
|
|
||||||
self.sema.speculative_expand_attr_macro(
|
|
||||||
&actual_item,
|
|
||||||
&item_with_fake_ident,
|
|
||||||
fake_ident_token.clone(),
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
(Some(actual_expansion), Some(speculative_expansion)) => {
|
|
||||||
let new_offset = speculative_expansion.1.text_range().start();
|
|
||||||
if new_offset > actual_expansion.text_range().end() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
original_file = actual_expansion;
|
|
||||||
speculative_file = speculative_expansion.0;
|
|
||||||
fake_ident_token = speculative_expansion.1;
|
|
||||||
offset = new_offset;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
(None, None) => (),
|
|
||||||
_ => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expand fn-like macro calls
|
|
||||||
if let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
|
|
||||||
find_node_at_offset::<ast::MacroCall>(&original_file, offset),
|
|
||||||
find_node_at_offset::<ast::MacroCall>(&speculative_file, offset),
|
|
||||||
) {
|
|
||||||
let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text());
|
|
||||||
let mac_call_path1 =
|
|
||||||
macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text());
|
|
||||||
if mac_call_path0 != mac_call_path1 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let speculative_args = match macro_call_with_fake_ident.token_tree() {
|
|
||||||
Some(tt) => tt,
|
|
||||||
None => break,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let (Some(actual_expansion), Some(speculative_expansion)) = (
|
|
||||||
self.sema.expand(&actual_macro_call),
|
|
||||||
self.sema.speculative_expand(
|
|
||||||
&actual_macro_call,
|
|
||||||
&speculative_args,
|
|
||||||
fake_ident_token,
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
let new_offset = speculative_expansion.1.text_range().start();
|
|
||||||
if new_offset > actual_expansion.text_range().end() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
original_file = actual_expansion;
|
|
||||||
speculative_file = speculative_expansion.0;
|
|
||||||
fake_ident_token = speculative_expansion.1;
|
|
||||||
offset = new_offset;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.fill(&original_file, speculative_file, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks whether completions in that particular case don't make much sense.
|
/// Checks whether completions in that particular case don't make much sense.
|
||||||
/// Examples:
|
/// Examples:
|
||||||
/// - `fn $0` -- we expect function name, it's unlikely that "hint" will be helpful.
|
/// - `fn $0` -- we expect function name, it's unlikely that "hint" will be helpful.
|
||||||
|
@ -491,6 +344,156 @@ impl<'a> CompletionContext<'a> {
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompletionContext construction
|
||||||
|
impl<'a> CompletionContext<'a> {
|
||||||
|
pub(super) fn new(
|
||||||
|
db: &'a RootDatabase,
|
||||||
|
position: FilePosition,
|
||||||
|
config: &'a CompletionConfig,
|
||||||
|
) -> Option<CompletionContext<'a>> {
|
||||||
|
let sema = Semantics::new(db);
|
||||||
|
|
||||||
|
let original_file = sema.parse(position.file_id);
|
||||||
|
|
||||||
|
// Insert a fake ident to get a valid parse tree. We will use this file
|
||||||
|
// to determine context, though the original_file will be used for
|
||||||
|
// actual completion.
|
||||||
|
let file_with_fake_ident = {
|
||||||
|
let parse = db.parse(position.file_id);
|
||||||
|
let edit = Indel::insert(position.offset, "intellijRulezz".to_string());
|
||||||
|
parse.reparse(&edit).tree()
|
||||||
|
};
|
||||||
|
let fake_ident_token =
|
||||||
|
file_with_fake_ident.syntax().token_at_offset(position.offset).right_biased().unwrap();
|
||||||
|
|
||||||
|
let original_token =
|
||||||
|
original_file.syntax().token_at_offset(position.offset).left_biased()?;
|
||||||
|
let token = sema.descend_into_macros_single(original_token.clone());
|
||||||
|
let scope = sema.scope_at_offset(&token, position.offset);
|
||||||
|
let krate = scope.krate();
|
||||||
|
let mut locals = vec![];
|
||||||
|
scope.process_all_names(&mut |name, scope| {
|
||||||
|
if let ScopeDef::Local(local) = scope {
|
||||||
|
locals.push((name, local));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let mut ctx = CompletionContext {
|
||||||
|
sema,
|
||||||
|
scope,
|
||||||
|
db,
|
||||||
|
config,
|
||||||
|
position,
|
||||||
|
original_token,
|
||||||
|
token,
|
||||||
|
krate,
|
||||||
|
expected_name: None,
|
||||||
|
expected_type: None,
|
||||||
|
function_def: None,
|
||||||
|
impl_def: None,
|
||||||
|
name_syntax: None,
|
||||||
|
lifetime_ctx: None,
|
||||||
|
pattern_ctx: None,
|
||||||
|
completion_location: None,
|
||||||
|
prev_sibling: None,
|
||||||
|
attribute_under_caret: None,
|
||||||
|
previous_token: None,
|
||||||
|
path_context: None,
|
||||||
|
locals,
|
||||||
|
incomplete_let: false,
|
||||||
|
no_completion_required: false,
|
||||||
|
};
|
||||||
|
ctx.expand_and_fill(
|
||||||
|
original_file.syntax().clone(),
|
||||||
|
file_with_fake_ident.syntax().clone(),
|
||||||
|
position.offset,
|
||||||
|
fake_ident_token,
|
||||||
|
);
|
||||||
|
Some(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Do the attribute expansion at the current cursor position for both original file and fake file
|
||||||
|
/// as long as possible. As soon as one of the two expansions fail we stop to stay in sync.
|
||||||
|
fn expand_and_fill(
|
||||||
|
&mut self,
|
||||||
|
mut original_file: SyntaxNode,
|
||||||
|
mut speculative_file: SyntaxNode,
|
||||||
|
mut offset: TextSize,
|
||||||
|
mut fake_ident_token: SyntaxToken,
|
||||||
|
) {
|
||||||
|
loop {
|
||||||
|
// Expand attributes
|
||||||
|
if let (Some(actual_item), Some(item_with_fake_ident)) = (
|
||||||
|
find_node_at_offset::<ast::Item>(&original_file, offset),
|
||||||
|
find_node_at_offset::<ast::Item>(&speculative_file, offset),
|
||||||
|
) {
|
||||||
|
match (
|
||||||
|
self.sema.expand_attr_macro(&actual_item),
|
||||||
|
self.sema.speculative_expand_attr_macro(
|
||||||
|
&actual_item,
|
||||||
|
&item_with_fake_ident,
|
||||||
|
fake_ident_token.clone(),
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
(Some(actual_expansion), Some(speculative_expansion)) => {
|
||||||
|
let new_offset = speculative_expansion.1.text_range().start();
|
||||||
|
if new_offset > actual_expansion.text_range().end() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
original_file = actual_expansion;
|
||||||
|
speculative_file = speculative_expansion.0;
|
||||||
|
fake_ident_token = speculative_expansion.1;
|
||||||
|
offset = new_offset;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
(None, None) => (),
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand fn-like macro calls
|
||||||
|
if let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
|
||||||
|
find_node_at_offset::<ast::MacroCall>(&original_file, offset),
|
||||||
|
find_node_at_offset::<ast::MacroCall>(&speculative_file, offset),
|
||||||
|
) {
|
||||||
|
let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text());
|
||||||
|
let mac_call_path1 =
|
||||||
|
macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text());
|
||||||
|
if mac_call_path0 != mac_call_path1 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let speculative_args = match macro_call_with_fake_ident.token_tree() {
|
||||||
|
Some(tt) => tt,
|
||||||
|
None => break,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let (Some(actual_expansion), Some(speculative_expansion)) = (
|
||||||
|
self.sema.expand(&actual_macro_call),
|
||||||
|
self.sema.speculative_expand(
|
||||||
|
&actual_macro_call,
|
||||||
|
&speculative_args,
|
||||||
|
fake_ident_token,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
let new_offset = speculative_expansion.1.text_range().start();
|
||||||
|
if new_offset > actual_expansion.text_range().end() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
original_file = actual_expansion;
|
||||||
|
speculative_file = speculative_expansion.0;
|
||||||
|
fake_ident_token = speculative_expansion.1;
|
||||||
|
offset = new_offset;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.fill(&original_file, speculative_file, offset);
|
||||||
|
}
|
||||||
|
|
||||||
fn expected_type_and_name(&self) -> (Option<Type>, Option<NameOrNameRef>) {
|
fn expected_type_and_name(&self) -> (Option<Type>, Option<NameOrNameRef>) {
|
||||||
let mut node = match self.token.parent() {
|
let mut node = match self.token.parent() {
|
||||||
|
@ -658,51 +661,53 @@ impl<'a> CompletionContext<'a> {
|
||||||
.find_map(ast::Fn::cast);
|
.find_map(ast::Fn::cast);
|
||||||
match name_like {
|
match name_like {
|
||||||
ast::NameLike::Lifetime(lifetime) => {
|
ast::NameLike::Lifetime(lifetime) => {
|
||||||
self.classify_lifetime(original_file, lifetime, offset);
|
self.lifetime_ctx =
|
||||||
|
Self::classify_lifetime(&self.sema, original_file, lifetime, offset);
|
||||||
}
|
}
|
||||||
ast::NameLike::NameRef(name_ref) => {
|
ast::NameLike::NameRef(name_ref) => {
|
||||||
self.classify_name_ref(original_file, name_ref);
|
self.path_context = Self::classify_name_ref(&self.sema, original_file, name_ref);
|
||||||
}
|
}
|
||||||
ast::NameLike::Name(name) => {
|
ast::NameLike::Name(name) => {
|
||||||
self.classify_name(name);
|
self.pattern_ctx = Self::classify_name(&self.sema, name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn classify_lifetime(
|
fn classify_lifetime(
|
||||||
&mut self,
|
sema: &Semantics<RootDatabase>,
|
||||||
original_file: &SyntaxNode,
|
original_file: &SyntaxNode,
|
||||||
lifetime: ast::Lifetime,
|
lifetime: ast::Lifetime,
|
||||||
offset: TextSize,
|
offset: TextSize,
|
||||||
) {
|
) -> Option<LifetimeContext> {
|
||||||
if let Some(parent) = lifetime.syntax().parent() {
|
let parent = lifetime.syntax().parent()?;
|
||||||
if parent.kind() == ERROR {
|
if parent.kind() == ERROR {
|
||||||
return;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.lifetime_ctx = Some(match_ast! {
|
Some(match_ast! {
|
||||||
match parent {
|
match parent {
|
||||||
ast::LifetimeParam(_it) => LifetimeContext::LifetimeParam(self.sema.find_node_at_offset_with_macros(original_file, offset)),
|
ast::LifetimeParam(_it) => LifetimeContext::LifetimeParam(sema.find_node_at_offset_with_macros(original_file, offset)),
|
||||||
ast::BreakExpr(_it) => LifetimeContext::LabelRef,
|
ast::BreakExpr(_it) => LifetimeContext::LabelRef,
|
||||||
ast::ContinueExpr(_it) => LifetimeContext::LabelRef,
|
ast::ContinueExpr(_it) => LifetimeContext::LabelRef,
|
||||||
ast::Label(_it) => LifetimeContext::LabelDef,
|
ast::Label(_it) => LifetimeContext::LabelDef,
|
||||||
_ => LifetimeContext::Lifetime,
|
_ => LifetimeContext::Lifetime,
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn classify_name(&mut self, name: ast::Name) {
|
fn classify_name(_sema: &Semantics<RootDatabase>, name: ast::Name) -> Option<PatternContext> {
|
||||||
if let Some(bind_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) {
|
let bind_pat = name.syntax().parent().and_then(ast::IdentPat::cast)?;
|
||||||
let is_name_in_field_pat = bind_pat
|
let is_name_in_field_pat = bind_pat
|
||||||
.syntax()
|
.syntax()
|
||||||
.parent()
|
.parent()
|
||||||
.and_then(ast::RecordPatField::cast)
|
.and_then(ast::RecordPatField::cast)
|
||||||
.map_or(false, |pat_field| pat_field.name_ref().is_none());
|
.map_or(false, |pat_field| pat_field.name_ref().is_none());
|
||||||
if is_name_in_field_pat {
|
if is_name_in_field_pat {
|
||||||
return;
|
return None;
|
||||||
|
}
|
||||||
|
if !bind_pat.is_simple_ident() {
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
if bind_pat.is_simple_ident() {
|
|
||||||
let mut is_param = None;
|
let mut is_param = None;
|
||||||
let refutability = bind_pat
|
let refutability = bind_pat
|
||||||
.syntax()
|
.syntax()
|
||||||
|
@ -734,19 +739,18 @@ impl<'a> CompletionContext<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
self.pattern_ctx = Some(PatternContext { refutability, is_param });
|
Some(PatternContext { refutability, is_param })
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn classify_name_ref(&mut self, original_file: &SyntaxNode, name_ref: ast::NameRef) {
|
fn classify_name_ref(
|
||||||
let parent = match name_ref.syntax().parent() {
|
_sema: &Semantics<RootDatabase>,
|
||||||
Some(it) => it,
|
original_file: &SyntaxNode,
|
||||||
None => return,
|
name_ref: ast::NameRef,
|
||||||
};
|
) -> Option<PathCompletionContext> {
|
||||||
|
let parent = name_ref.syntax().parent()?;
|
||||||
|
let segment = ast::PathSegment::cast(parent)?;
|
||||||
|
|
||||||
if let Some(segment) = ast::PathSegment::cast(parent) {
|
let mut path_ctx = PathCompletionContext {
|
||||||
let path_ctx = self.path_context.get_or_insert(PathCompletionContext {
|
|
||||||
call_kind: None,
|
call_kind: None,
|
||||||
is_trivial_path: false,
|
is_trivial_path: false,
|
||||||
qualifier: None,
|
qualifier: None,
|
||||||
|
@ -755,7 +759,7 @@ impl<'a> CompletionContext<'a> {
|
||||||
in_loop_body: false,
|
in_loop_body: false,
|
||||||
use_tree_parent: false,
|
use_tree_parent: false,
|
||||||
kind: None,
|
kind: None,
|
||||||
});
|
};
|
||||||
path_ctx.in_loop_body = is_in_loop_body(name_ref.syntax());
|
path_ctx.in_loop_body = is_in_loop_body(name_ref.syntax());
|
||||||
let path = segment.parent_path();
|
let path = segment.parent_path();
|
||||||
|
|
||||||
|
@ -792,12 +796,12 @@ impl<'a> CompletionContext<'a> {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.map(|it| it.parent_path());
|
.map(|it| it.parent_path());
|
||||||
return;
|
return Some(path_ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(segment) = path.segment() {
|
if let Some(segment) = path.segment() {
|
||||||
if segment.coloncolon_token().is_some() {
|
if segment.coloncolon_token().is_some() {
|
||||||
return;
|
return Some(path_ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -821,7 +825,7 @@ impl<'a> CompletionContext<'a> {
|
||||||
None
|
None
|
||||||
})
|
})
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
}
|
Some(path_ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue