fix: improve LSP hover to show function info when cursor is on fn keyword

When hovering on declaration keywords (fn, type, effect, let, trait),
look ahead to find the declaration name and show that symbol's full
info from the symbol table instead of generic keyword documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-17 08:32:01 -05:00
parent 98605d2b70
commit db82ca1a1c

View File

@@ -356,12 +356,57 @@ impl LspServer {
} }
} }
// Fall back to hardcoded info // Fall back: try looking up the declaration name when hovering on keywords
// Extract the word at the cursor position
let word = self.get_word_at_position(source, position)?; let word = self.get_word_at_position(source, position)?;
// Look up rich documentation for known symbols // When hovering on a keyword like 'fn', 'type', 'effect', 'let', 'trait',
// look ahead to find the declaration name and show that symbol's info
if let Some(ref table) = doc.symbol_table {
let decl_name = match word.as_str() {
"fn" | "type" | "effect" | "let" | "trait" | "handler" | "impl" => {
let offset = self.position_to_offset(source, position);
self.find_next_ident(source, offset + word.len())
}
_ => None,
};
if let Some(name) = decl_name {
// Look up the declaration name in the symbol table
for sym in table.global_symbols() {
if sym.name == name {
let signature = sym.type_signature.as_ref()
.map(|s| s.as_str())
.unwrap_or(&sym.name);
let kind_str = match sym.kind {
SymbolKind::Function => "function",
SymbolKind::Variable => "variable",
SymbolKind::Parameter => "parameter",
SymbolKind::Type => "type",
SymbolKind::TypeParameter => "type parameter",
SymbolKind::Variant => "variant",
SymbolKind::Effect => "effect",
SymbolKind::EffectOperation => "effect operation",
SymbolKind::Field => "field",
SymbolKind::Module => "module",
};
let doc_str = sym.documentation.as_ref()
.map(|d| format!("\n\n{}", d))
.unwrap_or_default();
let formatted_sig = format_signature_for_hover(signature);
let property_docs = extract_property_docs(signature);
return Some(Hover {
contents: HoverContents::Markup(MarkupContent {
kind: MarkupKind::Markdown,
value: format!("```lux\n{}\n```\n\n*{}*{}{}", formatted_sig, kind_str, property_docs, doc_str),
}),
range: None,
});
}
}
}
}
// Final fallback: rich documentation for keywords
let info = self.get_rich_symbol_info(&word) let info = self.get_rich_symbol_info(&word)
.or_else(|| self.get_symbol_info(&word).map(|(s, d)| (s.to_string(), d.to_string()))); .or_else(|| self.get_symbol_info(&word).map(|(s, d)| (s.to_string(), d.to_string())));
@@ -402,6 +447,26 @@ impl LspServer {
} }
} }
/// Find the next identifier in source after the given offset (skipping whitespace)
fn find_next_ident(&self, source: &str, start: usize) -> Option<String> {
let chars: Vec<char> = source.chars().collect();
let mut pos = start;
// Skip whitespace
while pos < chars.len() && (chars[pos] == ' ' || chars[pos] == '\t' || chars[pos] == '\n' || chars[pos] == '\r') {
pos += 1;
}
// Collect identifier
let ident_start = pos;
while pos < chars.len() && (chars[pos].is_alphanumeric() || chars[pos] == '_') {
pos += 1;
}
if pos > ident_start {
Some(chars[ident_start..pos].iter().collect())
} else {
None
}
}
fn get_symbol_info(&self, word: &str) -> Option<(&'static str, &'static str)> { fn get_symbol_info(&self, word: &str) -> Option<(&'static str, &'static str)> {
match word { match word {
// Keywords // Keywords