use crate::db::AnalysisDatabase;
use crate::FilePosition;
use mun_hir::semantics::{Semantics, SemanticsScope};
use mun_hir::AstDatabase;
use mun_syntax::{ast, utils::find_node_at_offset, AstNode, SyntaxNode, TextRange, TextSize};
use ra_ap_text_edit::Indel;
pub(super) struct CompletionContext<'a> {
pub sema: Semantics<'a>,
pub scope: SemanticsScope<'a>,
pub db: &'a AnalysisDatabase,
pub is_trivial_path: bool,
pub is_param: bool,
pub is_path_type: bool,
pub dot_receiver: Option<ast::Expr>,
}
impl<'a> CompletionContext<'a> {
pub fn new(db: &'a AnalysisDatabase, position: FilePosition) -> Option<Self> {
let sema = Semantics::new(db);
let original_file = sema.parse(position.file_id);
let file_with_fake_ident = {
let parse = db.parse(position.file_id);
let edit = Indel::insert(position.offset, String::from("intellijRulezz"));
parse.reparse(&edit).tree()
};
let token = original_file
.syntax()
.token_at_offset(position.offset)
.left_biased()?;
let scope = sema.scope_at_offset(&token.parent()?, position.offset);
let mut context = Self {
sema,
scope,
db,
is_trivial_path: false,
is_param: false,
is_path_type: false,
dot_receiver: None,
};
context.fill(
&original_file.syntax().clone(),
file_with_fake_ident.syntax().clone(),
position.offset,
);
Some(context)
}
fn fill(
&mut self,
original_file: &SyntaxNode,
file_with_fake_ident: SyntaxNode,
offset: TextSize,
) {
if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) {
if is_node::<ast::Param>(name_ref.syntax()) {
self.is_param = true;
return;
}
self.classify_name_ref(original_file, name_ref);
}
}
fn classify_name_ref(&mut self, original_file: &SyntaxNode, name_ref: ast::NameRef) {
let parent = match name_ref.syntax().parent() {
Some(it) => it,
None => return,
};
if let Some(segment) = ast::PathSegment::cast(parent.clone()) {
let path = segment.parent_path();
self.is_path_type = path
.syntax()
.parent()
.and_then(ast::PathType::cast)
.is_some();
if let Some(segment) = path.segment() {
if segment.has_colon_colon() {
return;
}
}
self.is_trivial_path = true;
}
if let Some(field_expr) = ast::FieldExpr::cast(parent) {
self.dot_receiver = field_expr
.expr()
.map(|e| e.syntax().text_range())
.and_then(|r| find_node_with_range(original_file, r));
}
}
}
fn is_node<N: AstNode>(node: &SyntaxNode) -> bool {
match node.ancestors().find_map(N::cast) {
None => false,
Some(n) => n.syntax().text_range() == node.text_range(),
}
}
fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> {
syntax.covering_element(range).ancestors().find_map(N::cast)
}