use glua_code_analysis::{
EmmyrcFilenameConvention, LuaSemanticDeclId, LuaType, ModuleInfo, check_export_visibility,
};
use glua_parser::{LuaAstNode, LuaNameExpr};
use lsp_types::{CompletionItem, Position};
use crate::{
handlers::{
command::make_auto_require,
completion::{
add_completions::get_completion_kind, completion_builder::CompletionBuilder,
completion_data::CompletionData,
},
},
util::{file_name_convert, module_name_convert},
};
pub fn add_completion(builder: &mut CompletionBuilder) -> Option<()> {
if builder.is_cancelled() {
return None;
}
let enable = builder.semantic_model.get_emmyrc().completion.auto_require;
if !enable {
return None;
}
let name_expr = LuaNameExpr::cast(builder.trigger_token.parent()?)?;
let prefix = name_expr.get_name_text()?.to_lowercase();
let emmyrc = builder.semantic_model.get_emmyrc();
let file_conversion = emmyrc.completion.auto_require_naming_convention;
let version_number = emmyrc.runtime.version.to_lua_version_number();
let file_id = builder.semantic_model.get_file_id();
let module_index = builder.semantic_model.get_db().get_module_index();
let module_infos = module_index.get_module_infos();
let range = builder.trigger_token.text_range();
let document = builder.semantic_model.get_document();
let lsp_position = document.to_lsp_range(range)?.start;
let mut completions = Vec::new();
for module_info in module_infos {
if module_info.is_visible(&version_number)
&& module_info.file_id != file_id
&& module_info.export_type.is_some()
&& !module_index.is_std(&module_info.file_id)
{
add_module_completion_item(
builder,
&prefix,
module_info,
file_conversion,
lsp_position,
&mut completions,
);
}
}
for completion in completions {
builder.add_completion_item(completion);
}
Some(())
}
fn add_module_completion_item(
builder: &CompletionBuilder,
prefix: &str,
module_info: &ModuleInfo,
file_conversion: EmmyrcFilenameConvention,
position: Position,
completions: &mut Vec<CompletionItem>,
) -> Option<()> {
if !check_export_visibility(&builder.semantic_model, module_info).unwrap_or(false) {
return None;
}
let completion_name = module_name_convert(module_info, file_conversion);
if !completion_name.to_lowercase().starts_with(prefix) {
add_completion_item_by_type(
builder,
prefix,
module_info,
file_conversion,
position,
completions,
);
return None;
}
if builder.env_duplicate_name.contains(&completion_name) {
return None;
}
let data = if let Some(property_id) = &module_info.semantic_id {
CompletionData::from_property_owner_id(builder, property_id.clone(), None)
} else {
None
};
let completion_item = CompletionItem {
label: completion_name.clone(),
kind: Some(lsp_types::CompletionItemKind::MODULE),
filter_text: Some(format!(
"{} {}",
completion_name, module_info.full_module_name
)),
label_details: Some(lsp_types::CompletionItemLabelDetails {
detail: Some(format!(" (in {})", module_info.full_module_name)),
..Default::default()
}),
command: Some(make_auto_require(
"",
builder.semantic_model.get_file_id(),
module_info.file_id,
position,
completion_name,
None,
)),
data,
..Default::default()
};
completions.push(completion_item);
Some(())
}
fn add_completion_item_by_type(
builder: &CompletionBuilder,
prefix: &str,
module_info: &ModuleInfo,
file_conversion: EmmyrcFilenameConvention,
position: Position,
completions: &mut Vec<CompletionItem>,
) -> Option<()> {
module_info.get_export(builder.semantic_model.get_db())?;
if let Some(export_type) = &module_info.export_type {
match export_type {
LuaType::TableConst(_) | LuaType::Def(_) => {
let member_infos = builder.semantic_model.get_member_infos(export_type)?;
for member_info in member_infos {
let key_name = file_name_convert(
&member_info.key.to_path(),
&member_info.typ,
file_conversion,
);
match member_info.typ {
LuaType::Def(_) => {}
LuaType::Signature(_) => {}
LuaType::DocFunction(_) => {}
LuaType::Ref(_) => {
let Some(LuaSemanticDeclId::Member(member_id)) =
member_info.property_owner_id.as_ref()
else {
continue;
};
let Some(property) = builder
.semantic_model
.get_db()
.get_property_index()
.get_property(&LuaSemanticDeclId::Member(member_id.clone()))
else {
continue;
};
if property.export().is_none() {
continue;
}
}
_ => {
continue;
}
}
if key_name.to_lowercase().starts_with(prefix) {
if builder.env_duplicate_name.contains(&key_name) {
continue;
}
let data = if let Some(property_owner_id) = &member_info.property_owner_id {
let is_visible = builder.semantic_model.is_semantic_visible(
builder.trigger_token.clone(),
property_owner_id.clone(),
);
if !is_visible {
continue;
}
CompletionData::from_property_owner_id(
builder,
property_owner_id.clone(),
None,
)
} else {
None
};
let completion_item = CompletionItem {
label: key_name.clone(),
kind: Some(get_completion_kind(&member_info.typ)),
filter_text: Some(format!(
"{} {} {}",
key_name,
module_info.full_module_name,
member_info.key.to_path()
)),
label_details: Some(lsp_types::CompletionItemLabelDetails {
detail: Some(format!(" (in {})", module_info.full_module_name)),
..Default::default()
}),
command: Some(make_auto_require(
"",
builder.semantic_model.get_file_id(),
module_info.file_id,
position,
key_name,
Some(member_info.key.to_path().to_string()),
)),
data,
..Default::default()
};
completions.push(completion_item);
}
}
}
LuaType::Signature(_) => {
let semantic_id = module_info.semantic_id.as_ref()?;
if let LuaSemanticDeclId::LuaDecl(decl_id) = semantic_id {
let decl = builder
.semantic_model
.get_db()
.get_decl_index()
.get_decl(&decl_id)?;
let name = decl.get_name();
if name.to_lowercase().starts_with(prefix) {
if builder.env_duplicate_name.contains(name) {
return None;
}
let completion_item = CompletionItem {
label: name.to_string(),
kind: Some(get_completion_kind(&export_type)),
filter_text: Some(format!("{} {}", name, module_info.full_module_name)),
label_details: Some(lsp_types::CompletionItemLabelDetails {
detail: Some(format!(" (in {})", module_info.full_module_name)),
..Default::default()
}),
command: Some(make_auto_require(
"",
builder.semantic_model.get_file_id(),
module_info.file_id,
position,
name.to_string(),
None,
)),
..Default::default()
};
completions.push(completion_item);
}
}
}
_ => {}
}
}
Some(())
}