diff --git a/src/lsp.rs b/src/lsp.rs index bcbaf4f..f16f3e9 100644 --- a/src/lsp.rs +++ b/src/lsp.rs @@ -356,12 +356,57 @@ impl LspServer { } } - // Fall back to hardcoded info - - // Extract the word at the cursor position + // Fall back: try looking up the declaration name when hovering on keywords 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) .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 { + let chars: Vec = 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)> { match word { // Keywords