use super::std_hover::{hover_std_description, is_std};
use emmylua_code_analysis::{
format_union_type, DbIndex, InFiled, LuaMember, LuaMemberOwner, LuaMultiLineUnion,
LuaSemanticDeclId, LuaType, LuaUnionType, RenderLevel, SemanticDeclLevel, SemanticModel,
};
use emmylua_code_analysis::humanize_type;
use emmylua_parser::{LuaAstNode, LuaExpr, LuaIndexExpr, LuaStat, LuaSyntaxId, LuaSyntaxKind};
use rowan::TextRange;
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_humanize_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_humanize_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
}
#[derive(Debug, Clone)]
pub struct DescriptionInfo {
pub description: Option<String>,
pub see_content: Option<String>,
pub other_content: Option<String>,
}
impl DescriptionInfo {
pub fn new() -> Self {
Self {
description: None,
see_content: None,
other_content: None,
}
}
pub fn is_empty(&self) -> bool {
self.description.is_none() && self.see_content.is_none() && self.other_content.is_none()
}
}
pub fn extract_description_from_property_owner(
semantic_model: &SemanticModel,
property_owner: &LuaSemanticDeclId,
) -> Option<DescriptionInfo> {
let property = semantic_model
.get_db()
.get_property_index()
.get_property(&property_owner)?;
let mut result = DescriptionInfo::new();
if let Some(detail) = &property.description {
let mut description = detail.to_string();
match property_owner {
LuaSemanticDeclId::Member(id) => {
if let Some(member) = semantic_model.get_db().get_member_index().get_member(&id) {
if let Some(LuaMemberOwner::Type(ty)) = semantic_model
.get_db()
.get_member_index()
.get_current_owner(&id)
{
if is_std(semantic_model.get_db(), member.get_file_id()) {
let std_desc =
hover_std_description(ty.get_name(), member.get_key().get_name());
if !std_desc.is_empty() {
description = std_desc;
}
}
}
}
}
LuaSemanticDeclId::LuaDecl(id) => {
if let Some(decl) = semantic_model.get_db().get_decl_index().get_decl(&id) {
if is_std(semantic_model.get_db(), decl.get_file_id()) {
let std_desc = hover_std_description(decl.get_name(), None);
if !std_desc.is_empty() {
description = std_desc;
}
}
}
}
_ => {}
}
result.description = Some(description);
}
if let Some(see) = &property.see_content {
result.see_content = Some(see.to_string());
}
if let Some(other) = &property.other_content {
result.other_content = Some(other.to_string());
}
if result.is_empty() {
None
} else {
Some(result)
}
}
pub fn extract_owner_name_from_element(
semantic_model: &SemanticModel,
element_id: &InFiled<TextRange>,
) -> Option<String> {
let root = semantic_model
.get_db()
.get_vfs()
.get_syntax_tree(&element_id.file_id)?
.get_red_root();
let node = LuaSyntaxId::to_node_at_range(&root, element_id.value)?;
let stat = LuaStat::cast(node.clone().parent()?)?;
match stat {
LuaStat::LocalStat(local_stat) => {
let value = LuaExpr::cast(node)?;
let local_name = local_stat.get_local_name_by_value(value);
if let Some(local_name) = local_name {
return Some(local_name.get_name_token()?.get_name_text().to_string());
}
}
_ => {}
}
None
}