use std::collections::HashMap;
use std::path::{Path, PathBuf};
use bynk_syntax::span::Span;
use tower_lsp::lsp_types::Url;
pub struct HoverAnalysis<'a> {
pub index: &'a bynk_check::index::ProjectIndex,
pub snapshots: &'a HashMap<PathBuf, String>,
pub locals: &'a bynk_check::locals::FileLocals,
pub expr_types: &'a bynk_check::expr_types::FileExprTypes,
pub rel: &'a Path,
pub offset: usize,
pub project_root: &'a Path,
pub doc_scope: &'a HashMap<String, Vec<String>>,
}
pub struct HoverInput<'a> {
pub analysis: Option<HoverAnalysis<'a>>,
pub doc: Option<(&'a str, usize)>,
pub uri: &'a Url,
pub files: Option<&'a [PathBuf]>,
}
pub fn hover_content(input: &HoverInput<'_>) -> Option<String> {
if let Some(a) = &input.analysis {
if let Some((key, def)) = crate::index_queries::definition_at(a.index, a.rel, a.offset)
&& let Some(def_text) = a.snapshots.get(&def.path)
&& let Some(content) = crate::symbols::describe_symbol(def_text, &key.name)
{
return Some(crate::symbols::linkify_doc_links(
&content,
a.index,
a.doc_scope,
a.project_root,
&key.unit,
));
}
if let Some(text) = a.snapshots.get(a.rel)
&& let Some(content) = crate::symbols::describe_store_op_at(
text,
a.offset,
a.locals.get(a.rel).map_or(&[], |l| l.as_slice()),
)
{
return Some(content);
}
if let Some(text) = a.snapshots.get(a.rel) {
let local = a
.locals
.get(a.rel)
.and_then(|locals| crate::locals_nav::describe_local_at(locals, text, a.offset))
.or_else(|| {
let entries = a.expr_types.get(a.rel)?;
crate::symbols::describe_self_at(text, a.offset, entries)
});
if let Some(content) = local {
return Some(content);
}
}
}
let (text, offset) = input.doc?;
let Some((name, span)) = identifier_at(text, offset) else {
return crate::symbols::describe_keyword_at(text, offset).map(str::to_string);
};
if let Some(content) = crate::symbols::describe_symbol(text, &name) {
return Some(content);
}
if let Some(content) = crate::symbols::describe_agent_state_at(text, span.start) {
return Some(content);
}
if let Some(content) = crate::symbols::describe_handler_annotation_at(text, span.start) {
return Some(content);
}
crate::symbols::qualified_callee_at(text, span)
.and_then(|callee| crate::signature_help::resolve_label(&callee, text, input.files))
.map(|sig| format!("```bynk\n{sig}\n```"))
.or_else(|| {
input
.files
.and_then(|root| crate::symbols::describe_symbol_cross_file(root, input.uri, &name))
.map(|(_other_uri, desc)| desc)
})
.or_else(|| crate::symbols::describe_firstparty_symbol(&name))
}
fn identifier_at(text: &str, offset: usize) -> Option<(String, Span)> {
use bynk_syntax::lexer::TokenKind;
let tokens = bynk_syntax::lexer::tokenize_expanding_holes(text).ok()?;
tokens
.iter()
.find(|t| {
t.span.start <= offset
&& offset < t.span.end
&& matches!(
t.kind,
TokenKind::Ident
| TokenKind::Int
| TokenKind::String
| TokenKind::Bool
| TokenKind::Float
| TokenKind::Result
| TokenKind::Option
| TokenKind::Effect
)
})
.map(|t| (text[t.span.start..t.span.end].to_string(), t.span))
}