use cairo_lang_defs::ids::{FunctionWithBodyId, LookupItemId};
use cairo_lang_diagnostics::ToOption;
use cairo_lang_semantic::Mutability;
use cairo_lang_semantic::db::SemanticGroup;
use cairo_lang_semantic::items::function_with_body::SemanticExprLookup;
use cairo_lang_semantic::lookup_item::LookupItemEx;
use cairo_lang_syntax::node::ast::{Expr, Pattern, TerminalIdentifier};
use cairo_lang_syntax::node::kind::SyntaxKind;
use cairo_lang_syntax::node::{SyntaxNode, TypedSyntaxNode};
use cairo_lang_utils::Upcast;
use lsp_types::Hover;
use crate::ide::hover::markdown_contents;
use crate::ide::hover::render::markdown::{RULE, fenced_code_block};
use crate::lang::db::{AnalysisDatabase, LsSemanticGroup};
pub fn legacy(db: &AnalysisDatabase, identifier: &TerminalIdentifier) -> Option<Hover> {
let node = identifier.as_syntax_node();
let lookup_item_id = db.find_lookup_item(&node)?;
let function_id = lookup_item_id.function_with_body()?;
let mut hints = Vec::new();
if let Some(hint) = get_pattern_hint(db, function_id, node.clone()) {
hints.push(hint);
} else if let Some(hint) = get_expr_hint(db, function_id, node.clone()) {
hints.push(hint);
};
if let Some(hint) = get_identifier_hint(db, lookup_item_id, node) {
hints.push(hint);
};
let hints = hints.join(RULE);
Some(Hover { contents: markdown_contents(hints), range: None })
}
fn get_identifier_hint(
db: &AnalysisDatabase,
lookup_item_id: LookupItemId,
node: SyntaxNode,
) -> Option<String> {
let syntax_db = db.upcast();
if node.kind(syntax_db) != SyntaxKind::TokenIdentifier {
return None;
}
let identifier = TerminalIdentifier::from_syntax_node(syntax_db, node.parent().unwrap());
let item = db.lookup_resolved_generic_item_by_ptr(lookup_item_id, identifier.stable_ptr())?;
Some(format!("`{}`", item.full_path(db)))
}
fn get_expr_hint(
db: &AnalysisDatabase,
function_id: FunctionWithBodyId,
node: SyntaxNode,
) -> Option<String> {
let semantic_expr = nearest_semantic_expr(db, node, function_id)?;
let text = match semantic_expr {
cairo_lang_semantic::Expr::FunctionCall(call) => {
let args = if let Ok(signature) =
call.function.get_concrete(db).generic_function.generic_signature(db.upcast())
{
signature
.params
.iter()
.map(|arg| {
let mutability = match arg.mutability {
Mutability::Immutable => "",
Mutability::Mutable => "mut ",
Mutability::Reference => "ref ",
};
format!("{mutability}{}: {}", arg.name, arg.ty.format(db.upcast()))
})
.collect::<Vec<String>>()
.join(", ")
} else {
"".to_owned()
};
let mut s = format!(
"fn {}({}) -> {}",
call.function.name(db.upcast()),
args,
call.ty.format(db.upcast())
);
s.retain(|c| c != '"');
s
}
_ => semantic_expr.ty().format(db),
};
Some(fenced_code_block(&text))
}
fn nearest_semantic_expr(
db: &AnalysisDatabase,
mut node: SyntaxNode,
function_id: FunctionWithBodyId,
) -> Option<cairo_lang_semantic::Expr> {
loop {
let syntax_db = db.upcast();
if Expr::is_variant(node.kind(syntax_db)) {
let expr_node = Expr::from_syntax_node(syntax_db, node.clone());
if let Some(expr_id) =
db.lookup_expr_by_ptr(function_id, expr_node.stable_ptr()).to_option()
{
let semantic_expr = db.expr_semantic(function_id, expr_id);
return Some(semantic_expr);
}
}
node = node.parent()?;
}
}
fn get_pattern_hint(
db: &AnalysisDatabase,
function_id: FunctionWithBodyId,
node: SyntaxNode,
) -> Option<String> {
let semantic_pattern = nearest_semantic_pat(db, node, function_id)?;
Some(format!("Type: `{}`", semantic_pattern.ty().format(db)))
}
fn nearest_semantic_pat(
db: &AnalysisDatabase,
mut node: SyntaxNode,
function_id: FunctionWithBodyId,
) -> Option<cairo_lang_semantic::Pattern> {
loop {
let syntax_db = db.upcast();
if Pattern::is_variant(node.kind(syntax_db)) {
let pattern_node = Pattern::from_syntax_node(syntax_db, node.clone());
if let Some(pattern_id) =
db.lookup_pattern_by_ptr(function_id, pattern_node.stable_ptr()).to_option()
{
let semantic_pattern = db.pattern_semantic(function_id, pattern_id);
return Some(semantic_pattern);
}
}
node = node.parent()?;
}
}