use std::collections::HashSet;
use emmylua_code_analysis::{
format_union_type, DbIndex, LuaDocReturnInfo, LuaFunctionType, LuaMember, LuaMemberKey,
LuaMemberOwner, LuaMultiLineUnion, LuaSemanticDeclId, LuaSignature, LuaSignatureId, LuaType,
LuaUnionType, RenderLevel, SemanticDeclLevel, SemanticModel,
};
use emmylua_code_analysis::humanize_type;
use emmylua_parser::{LuaAstNode, LuaIndexExpr, LuaSyntaxKind};
use super::hover_builder::HoverBuilder;
pub fn hover_const_type(db: &DbIndex, typ: &LuaType) -> String {
let const_value = humanize_type(db, typ, RenderLevel::Detailed);
match typ {
LuaType::IntegerConst(_) | LuaType::DocIntegerConst(_) => {
format!("integer = {}", const_value)
}
LuaType::FloatConst(_) => format!("number = {}", const_value),
LuaType::StringConst(_) | LuaType::DocStringConst(_) => format!("string = {}", const_value),
_ => const_value,
}
}
pub fn hover_function_type(
builder: &mut HoverBuilder,
db: &DbIndex,
typ: &LuaType,
function_member: Option<&LuaMember>,
func_name: &str,
is_local: bool,
) {
match typ {
LuaType::Function => builder.set_type_description(format!("function {}()", func_name)),
LuaType::DocFunction(lua_func) => builder.set_type_description(hover_doc_function_type(
builder,
db,
&lua_func,
function_member,
func_name,
)),
LuaType::Signature(signature_id) => {
let type_description = hover_signature_type(
builder,
db,
signature_id.clone(),
function_member,
func_name,
is_local,
false,
)
.unwrap_or_else(|| {
builder.signature_overload = None;
format!("function {}", func_name)
});
builder.set_type_description(type_description);
}
LuaType::Union(union) => {
hover_union_function_type(builder, db, union, function_member, func_name)
}
_ => builder.set_type_description(format!("function {}", func_name)),
}
}
fn hover_union_function_type(
builder: &mut HoverBuilder,
db: &DbIndex,
union: &LuaUnionType,
function_member: Option<&LuaMember>,
func_name: &str,
) {
if let Some(call) = builder.get_call_signature() {
builder.set_type_description(hover_doc_function_type(
builder,
db,
&call,
function_member,
func_name,
));
return;
}
let mut overloads = Vec::new();
let types = union.get_types();
for typ in types {
match typ {
LuaType::DocFunction(lua_func) => {
overloads.push(hover_doc_function_type(
builder,
db,
&lua_func,
function_member,
func_name,
));
}
LuaType::Signature(signature_id) => {
if let Some(type_description) = hover_signature_type(
builder,
db,
signature_id.clone(),
function_member,
func_name,
false,
true,
) {
overloads.push(type_description);
}
}
_ => {}
}
}
if let Some(type_description) = overloads.pop() {
builder.set_type_description(type_description);
for overload in overloads {
builder.add_signature_overload(overload);
}
}
}
fn hover_doc_function_type(
builder: &HoverBuilder,
db: &DbIndex,
lua_func: &LuaFunctionType,
owner_member: Option<&LuaMember>,
func_name: &str,
) -> String {
let async_label = if lua_func.is_async() { "async " } else { "" };
let mut type_label = "function ";
let full_name = if let Some(owner_member) = owner_member {
let global_name = infer_prefix_global_name(builder.semantic_model, owner_member);
let mut name = String::new();
let parent_owner = db
.get_member_index()
.get_current_owner(&owner_member.get_id());
if let Some(LuaMemberOwner::Type(ty)) = parent_owner {
if let Some(global_name) = global_name {
name.push_str(global_name);
} else {
name.push_str(ty.get_simple_name());
}
if owner_member.is_field() {
type_label = "(field) ";
}
}
if lua_func.is_colon_define() || lua_func.first_param_is_self() {
type_label = "(method) ";
name.push_str(":");
} else {
name.push_str(".");
}
if let LuaMemberKey::Name(n) = owner_member.get_key() {
name.push_str(n.as_str());
}
name
} else {
func_name.to_string()
};
let params = lua_func
.get_params()
.iter()
.enumerate()
.map(|(index, param)| {
let name = param.0.clone();
if index == 0 && param.1.is_some() && param.1.as_ref().unwrap().is_self_infer() {
"".to_string()
} else if let Some(ty) = ¶m.1 {
format!("{}: {}", name, humanize_type(db, ty, RenderLevel::Normal))
} else {
name.to_string()
}
})
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(", ");
let ret_detail = {
let ret_type = lua_func.get_ret();
match ret_type {
LuaType::Nil => "".to_string(),
_ => {
format!(" -> {}", humanize_type(db, ret_type, RenderLevel::Simple))
}
}
};
format_function_type(type_label, async_label, full_name, params, ret_detail)
}
fn hover_signature_type(
builder: &mut HoverBuilder,
db: &DbIndex,
signature_id: LuaSignatureId,
owner_member: Option<&LuaMember>,
func_name: &str,
is_local: bool,
is_form_union: bool,
) -> Option<String> {
let signature = db.get_signature_index().get(&signature_id)?;
if is_form_union && signature.param_docs.is_empty() {
return None;
}
let call_signature = builder.get_call_signature();
let mut type_label = "function ";
let full_name = if let Some(owner_member) = owner_member {
let global_name = infer_prefix_global_name(builder.semantic_model, owner_member);
let mut name = String::new();
let parent_owner = db
.get_member_index()
.get_current_owner(&owner_member.get_id());
if let Some(LuaMemberOwner::Type(ty)) = parent_owner {
if let Some(global_name) = global_name {
name.push_str(global_name);
} else {
name.push_str(ty.get_simple_name());
}
if signature.is_method() {
type_label = "(method) ";
name.push_str(":");
} else {
name.push_str(".");
}
}
if let LuaMemberKey::Name(n) = owner_member.get_key() {
name.push_str(n.as_str());
}
name
} else {
if is_local {
type_label = "local function ";
}
func_name.to_string()
};
let signature_info = {
let async_label = db
.get_signature_index()
.get(&signature_id)
.map(|signature| if signature.is_async { "async " } else { "" })
.unwrap_or("");
let params = signature
.get_type_params()
.iter()
.enumerate()
.map(|(index, param)| {
let name = param.0.clone();
if index == 0 && param.1.is_some() && param.1.as_ref().unwrap().is_self_infer() {
"".to_string()
} else if let Some(ty) = ¶m.1 {
format!("{}: {}", name, humanize_type(db, ty, RenderLevel::Simple))
} else {
name
}
})
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(", ");
let rets = build_signature_rets(builder, signature, builder.is_completion, None);
let result = format_function_type(type_label, async_label, full_name.clone(), params, rets);
if let Some(call_signature) = &call_signature {
if call_signature.get_params() == signature.get_type_params() {
builder.signature_overload = None;
return Some(result);
}
}
result
};
let overloads: Vec<String> = {
let mut overloads = Vec::new();
for (_, overload) in signature.overloads.iter().enumerate() {
let async_label = if overload.is_async() { "async " } else { "" };
let params = overload
.get_params()
.iter()
.enumerate()
.map(|(index, param)| {
let name = param.0.clone();
if index == 0 && param.1.is_some() && param.1.as_ref().unwrap().is_self_infer()
{
"".to_string()
} else if let Some(ty) = ¶m.1 {
format!("{}: {}", name, humanize_type(db, ty, RenderLevel::Simple))
} else {
name
}
})
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(", ");
let rets =
build_signature_rets(builder, signature, builder.is_completion, Some(overload));
let result =
format_function_type(type_label, async_label, full_name.clone(), params, rets);
if let Some(call_signature) = &call_signature {
if *call_signature == **overload {
builder.signature_overload = None;
return Some(result);
}
};
overloads.push(result);
}
overloads
};
for overload in overloads {
builder.add_signature_overload(overload);
}
Some(signature_info)
}
fn build_signature_rets(
builder: &mut HoverBuilder,
signature: &LuaSignature,
is_completion: bool,
overload: Option<&LuaFunctionType>,
) -> String {
let db = builder.semantic_model.get_db();
let mut result = String::new();
let overload_rets_string = if let Some(overload) = overload {
let ret_type = overload.get_ret();
match ret_type {
LuaType::Nil => "".to_string(),
_ => {
format!(" -> {}", humanize_type(db, ret_type, RenderLevel::Simple))
}
}
} else {
"".to_string()
};
if is_completion {
let rets = if !overload_rets_string.is_empty() {
overload_rets_string
} else {
let rets = &signature.return_docs;
if rets.is_empty() || signature.get_return_type().is_nil() {
"".to_string()
} else {
format!(
" -> {}",
rets.iter()
.enumerate()
.map(|(i, ret)| build_signature_ret_type(builder, ret, i))
.collect::<Vec<_>>()
.join(", ")
)
}
};
result.push_str(rets.as_str());
return result;
}
let rets = if !overload_rets_string.is_empty() {
overload_rets_string
} else {
let rets = &signature.return_docs;
if rets.is_empty() || signature.get_return_type().is_nil() {
"".to_string()
} else {
let mut rets_string_multiline = String::new();
rets_string_multiline.push_str("\n");
for (i, ret) in rets.iter().enumerate() {
let type_text = build_signature_ret_type(builder, ret, i);
let prefix = if i == 0 {
"-> ".to_string()
} else {
format!("{}. ", i + 1)
};
let name = ret.name.clone().unwrap_or_default();
rets_string_multiline.push_str(&format!(
" {}{}{}\n",
prefix,
if !name.is_empty() {
format!("{}: ", name)
} else {
"".to_string()
},
type_text,
));
}
rets_string_multiline
}
};
result.push_str(rets.as_str());
result
}
fn build_signature_ret_type(
builder: &mut HoverBuilder,
ret_info: &LuaDocReturnInfo,
i: usize,
) -> String {
let type_expansion_count = builder.get_type_expansion_count();
let type_text = hover_type(builder, &ret_info.type_ref, Some(RenderLevel::Simple));
if builder.get_type_expansion_count() > type_expansion_count {
if let Some(pop_type_expansion) =
builder.pop_type_expansion(type_expansion_count, builder.get_type_expansion_count())
{
let mut new_type_expansion = format!("return #{}", i + 1);
let mut seen = HashSet::new();
for type_expansion in pop_type_expansion {
for line in type_expansion.lines().skip(1) {
if seen.insert(line.to_string()) {
new_type_expansion.push('\n');
new_type_expansion.push_str(line);
}
}
}
builder.add_type_expansion(new_type_expansion);
}
};
type_text
}
fn format_function_type(
type_label: &str,
async_label: &str,
full_name: String,
params: String,
rets: String,
) -> String {
let prefix = if type_label.starts_with("function") {
format!("{}{}", async_label, type_label)
} else {
format!("{}{}", type_label, async_label)
};
format!("{}{}({}){}", prefix, full_name, params, rets)
}
pub fn hover_type(
builder: &mut HoverBuilder,
ty: &LuaType,
fallback_level: Option<RenderLevel>, ) -> String {
let db = builder.semantic_model.get_db();
match ty {
LuaType::Ref(type_decl_id) => {
if let Some(type_decl) = db.get_type_index().get_type_decl(type_decl_id) {
if let Some(LuaType::MultiLineUnion(multi_union)) =
type_decl.get_alias_origin(db, None)
{
return hover_multi_line_union_type(
builder,
db,
multi_union.as_ref(),
Some(type_decl.get_full_name()),
)
.unwrap_or_default();
}
}
humanize_type(db, ty, fallback_level.unwrap_or(RenderLevel::Simple))
}
LuaType::MultiLineUnion(multi_union) => {
hover_multi_line_union_type(builder, db, multi_union.as_ref(), None).unwrap_or_default()
}
LuaType::Union(union) => hover_union_type(builder, union, RenderLevel::Detailed),
_ => humanize_type(db, ty, fallback_level.unwrap_or(RenderLevel::Simple)),
}
}
fn hover_union_type(
builder: &mut HoverBuilder,
union: &LuaUnionType,
level: RenderLevel,
) -> String {
format_union_type(union, level, |ty, level| {
hover_type(builder, ty, Some(level))
})
}
fn hover_multi_line_union_type(
builder: &mut HoverBuilder,
db: &DbIndex,
multi_union: &LuaMultiLineUnion,
ty_name: Option<&str>,
) -> Option<String> {
let members = multi_union.get_unions();
let type_name = if ty_name.is_none() {
let members = multi_union.get_unions();
let type_str = members
.iter()
.take(10)
.map(|(ty, _)| humanize_type(db, ty, RenderLevel::Simple))
.collect::<Vec<_>>()
.join("|");
Some(format!("({})", type_str))
} else {
ty_name.map(|name| name.to_string())
};
let mut text = format!("{}:\n", type_name.clone().unwrap_or_default());
for (typ, description) in members {
let type_humanize_text = humanize_type(db, &typ, RenderLevel::Minimal);
if let Some(description) = description {
text.push_str(&format!(
" | {} -- {}\n",
type_humanize_text, description
));
} else {
text.push_str(&format!(" | {}\n", type_humanize_text));
}
}
builder.add_type_expansion(text);
type_name
}
pub fn infer_prefix_global_name<'a>(
semantic_model: &'a SemanticModel,
member: &LuaMember,
) -> Option<&'a str> {
let root = semantic_model
.get_db()
.get_vfs()
.get_syntax_tree(&member.get_file_id())?
.get_red_root();
let cur_node = member.get_syntax_id().to_node_from_root(&root)?;
match cur_node.kind().into() {
LuaSyntaxKind::IndexExpr => {
let index_expr = LuaIndexExpr::cast(cur_node)?;
let semantic_decl = semantic_model.find_decl(
index_expr
.get_prefix_expr()?
.get_syntax_id()
.to_node_from_root(&root)
.unwrap()
.into(),
SemanticDeclLevel::default(),
);
if let Some(property_owner) = semantic_decl {
if let LuaSemanticDeclId::LuaDecl(id) = property_owner {
if let Some(decl) = semantic_model.get_db().get_decl_index().get_decl(&id) {
if decl.is_global() {
return Some(decl.get_name());
}
}
}
}
}
_ => {}
}
None
}