use emmylua_code_analysis::{DbIndex, LuaMemberInfo, LuaSemanticDeclId, LuaType, LuaTypeDeclId};
use emmylua_parser::{LuaAstNode, LuaAstToken, LuaIndexExpr, LuaStringToken};
use crate::handlers::completion::{
add_completions::{add_member_completion, CompletionTriggerStatus},
completion_builder::CompletionBuilder,
};
pub fn add_completion(builder: &mut CompletionBuilder) -> Option<()> {
if builder.is_cancelled() {
return None;
}
let index_expr = LuaIndexExpr::cast(builder.trigger_token.parent()?)?;
let index_token = index_expr.get_index_token()?;
let completion_status = if index_token.is_dot() {
CompletionTriggerStatus::Dot
} else if index_token.is_colon() {
CompletionTriggerStatus::Colon
} else if LuaStringToken::can_cast(builder.trigger_token.kind().into()) {
CompletionTriggerStatus::InString
} else {
CompletionTriggerStatus::LeftBracket
};
let prefix_expr = index_expr.get_prefix_expr()?;
let prefix_type = builder.semantic_model.infer_expr(prefix_expr.into()).ok()?;
let member_info_map = builder.semantic_model.infer_member_map(&prefix_type)?;
for (_, member_infos) in member_info_map.iter() {
add_resolve_member_infos(builder, &member_infos, completion_status);
}
Some(())
}
fn add_resolve_member_infos(
builder: &mut CompletionBuilder,
member_infos: &Vec<LuaMemberInfo>,
completion_status: CompletionTriggerStatus,
) -> Option<()> {
if member_infos.len() == 1 {
let function_count = count_function_overloads(
builder.semantic_model.get_db(),
&member_infos.iter().map(|info| info).collect::<Vec<_>>(),
);
let member_info = &member_infos[0];
add_member_completion(
builder,
member_info.clone(),
completion_status,
function_count,
);
return Some(());
}
let mut resolve_state = MemberResolveState::All;
if builder
.semantic_model
.get_db()
.get_emmyrc()
.strict
.meta_override_file_define
{
for member_info in member_infos {
match member_info.feature {
Some(feature) => {
if feature.is_meta_decl() {
resolve_state = MemberResolveState::Meta;
break;
} else if feature.is_file_decl() {
resolve_state = MemberResolveState::FileDecl;
}
}
None => {}
}
}
}
let first_owner = get_owner_type_id(builder.semantic_model.get_db(), member_infos.first()?);
let member_infos: Vec<&LuaMemberInfo> = member_infos
.iter()
.filter(|member_info| {
get_owner_type_id(builder.semantic_model.get_db(), member_info) == first_owner
})
.collect();
let limit_doc_function = member_infos
.iter()
.all(|info| matches!(info.typ, LuaType::DocFunction(_)));
let function_count = count_function_overloads(builder.semantic_model.get_db(), &member_infos);
for member_info in member_infos {
match resolve_state {
MemberResolveState::All => {
add_member_completion(
builder,
member_info.clone(),
completion_status,
function_count,
);
if limit_doc_function {
break;
}
}
MemberResolveState::Meta => {
if let Some(feature) = member_info.feature {
if feature.is_meta_decl() {
add_member_completion(
builder,
member_info.clone(),
completion_status,
function_count,
);
if limit_doc_function {
break;
}
}
}
}
MemberResolveState::FileDecl => {
if let Some(feature) = member_info.feature {
if feature.is_file_decl() {
add_member_completion(
builder,
member_info.clone(),
completion_status,
function_count,
);
if limit_doc_function {
break;
}
}
}
}
}
}
Some(())
}
fn count_function_overloads(db: &DbIndex, member_infos: &Vec<&LuaMemberInfo>) -> Option<usize> {
let mut count = 0;
for member_info in member_infos {
match &member_info.typ {
LuaType::DocFunction(_) => {
count += 1;
}
LuaType::Signature(id) => {
count += 1;
if let Some(signature) = db.get_signature_index().get(&id) {
count += signature.overloads.len();
}
}
_ => {}
}
}
if count > 1 {
count -= 1;
}
if count == 0 {
None
} else {
Some(count)
}
}
enum MemberResolveState {
All,
Meta,
FileDecl,
}
fn get_owner_type_id(db: &DbIndex, info: &LuaMemberInfo) -> Option<LuaTypeDeclId> {
match &info.property_owner_id {
Some(LuaSemanticDeclId::Member(member_id)) => {
if let Some(owner) = db.get_member_index().get_current_owner(member_id) {
return owner.get_type_id().cloned();
}
None
}
_ => None,
}
}