use crate::symbol_index::SymbolIndex;
#[derive(Debug, Clone)]
pub struct HoverResult {
pub contents: String,
pub range: Option<(usize, usize)>,
}
pub fn hover_for_symbol(index: &SymbolIndex, name: &str) -> Option<HoverResult> {
let info = index.lookup(name)?;
let mut markdown = String::new();
if let Some(sig) = &info.signature {
markdown.push_str("```cjc\n");
markdown.push_str(sig);
markdown.push_str("\n```\n\n");
}
markdown.push_str(&info.description);
if let Some(lib) = &info.library {
markdown.push_str(&format!("\n\n*From library: `{}`*", lib));
}
Some(HoverResult {
contents: markdown,
range: None,
})
}
pub fn word_at_offset(line: &str, offset: usize) -> Option<&str> {
if offset >= line.len() {
return None;
}
let bytes = line.as_bytes();
if !is_ident_char(bytes[offset]) {
return None;
}
let start = line[..offset]
.rfind(|c: char| !c.is_alphanumeric() && c != '_')
.map(|i| i + 1)
.unwrap_or(0);
let end = line[offset..]
.find(|c: char| !c.is_alphanumeric() && c != '_')
.map(|i| i + offset)
.unwrap_or(line.len());
Some(&line[start..end])
}
fn is_ident_char(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'_'
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_word_at_offset() {
assert_eq!(word_at_offset("let x = sqrt(42)", 8), Some("sqrt"));
assert_eq!(word_at_offset("let x = sqrt(42)", 9), Some("sqrt"));
assert_eq!(word_at_offset("let x = sqrt(42)", 12), None); assert_eq!(word_at_offset("vizor_plot()", 0), Some("vizor_plot"));
}
#[test]
fn test_hover_for_known_symbol() {
let mut idx = crate::symbol_index::SymbolIndex::new();
idx.populate_builtins();
let result = hover_for_symbol(&idx, "sqrt").unwrap();
assert!(result.contents.contains("sqrt"));
assert!(result.contents.contains("Square root"));
}
#[test]
fn test_hover_unknown_symbol() {
let idx = crate::symbol_index::SymbolIndex::new();
assert!(hover_for_symbol(&idx, "nonexistent").is_none());
}
}