Add documentation to hovers

This commit is contained in:
Sofia 2025-08-14 16:52:45 +03:00
parent 6dccab8b12
commit dcb4e76a40
2 changed files with 84 additions and 38 deletions

View File

@ -193,6 +193,33 @@ impl AnalysisState {
} }
} }
pub fn set_documentation(&mut self, token_idx: usize, documentation: Option<String>) {
if let Some(documentation) = documentation {
if let Some(token) = self.map.get_mut(&token_idx) {
if let Some(hover) = &mut token.hover {
hover.documentation = Some(documentation);
} else {
token.hover = Some(Hover {
documentation: Some(documentation),
kind: None,
});
}
} else {
self.map.insert(
token_idx,
SemanticToken {
hover: Some(Hover {
documentation: Some(documentation),
kind: None,
}),
autocomplete: Vec::new(),
symbol: Default::default(),
},
);
}
}
}
pub fn set_symbol(&mut self, idx: usize, symbol: SymbolId) { pub fn set_symbol(&mut self, idx: usize, symbol: SymbolId) {
self.symbol_to_token.insert(symbol, idx); self.symbol_to_token.insert(symbol, idx);
if let Some(token) = self.map.get_mut(&idx) { if let Some(token) = self.map.get_mut(&idx) {
@ -246,8 +273,8 @@ pub struct AnalysisScope<'a> {
tokens: &'a Vec<FullToken>, tokens: &'a Vec<FullToken>,
variables: HashMap<String, SymbolId>, variables: HashMap<String, SymbolId>,
types: HashMap<TypeKind, (SourceModuleId, SymbolId)>, types: HashMap<TypeKind, (SourceModuleId, SymbolId)>,
functions: HashMap<String, (SourceModuleId, SymbolId)>, functions: HashMap<String, (SourceModuleId, SymbolId, Option<String>)>,
associated_functions: HashMap<(TypeKind, String), (SourceModuleId, SymbolId)>, associated_functions: HashMap<(TypeKind, String), (SourceModuleId, SymbolId, Option<String>)>,
map: &'a StateMap, map: &'a StateMap,
} }
@ -646,9 +673,10 @@ pub fn analyze_context(
if source_id != module.module_id { if source_id != module.module_id {
if let Some(state) = map.get(&source_id) { if let Some(state) = map.get(&source_id) {
if let Some(symbol) = state.associated_functions.get(&(ty.clone(), function.name.clone())) { if let Some(symbol) = state.associated_functions.get(&(ty.clone(), function.name.clone())) {
scope scope.associated_functions.insert(
.associated_functions (ty.clone(), function.name.clone()),
.insert((ty.clone(), function.name.clone()), (source_id, *symbol)); (source_id, *symbol, function.documentation.clone()),
);
} }
} }
continue; continue;
@ -664,9 +692,10 @@ pub fn analyze_context(
.state .state
.associated_functions .associated_functions
.insert((ty.clone(), function.name.clone()), symbol); .insert((ty.clone(), function.name.clone()), symbol);
scope scope.associated_functions.insert(
.associated_functions (ty.clone(), function.name.clone()),
.insert((ty.clone(), function.name.clone()), (module.module_id, symbol)); (module.module_id, symbol, function.documentation.clone()),
);
} }
for (_, function) in &module.associated_functions { for (_, function) in &module.associated_functions {
@ -692,7 +721,10 @@ pub fn analyze_context(
if source_id != module.module_id { if source_id != module.module_id {
if let Some(state) = map.get(&source_id) { if let Some(state) = map.get(&source_id) {
if let Some(symbol) = state.functions.get(&function.name) { if let Some(symbol) = state.functions.get(&function.name) {
scope.functions.insert(function.name.clone(), (source_id, *symbol)); scope.functions.insert(
function.name.clone(),
(source_id, *symbol, function.documentation.clone()),
);
} }
} }
continue; continue;
@ -702,7 +734,7 @@ pub fn analyze_context(
scope.state.init_hover( scope.state.init_hover(
&function.signature(), &function.signature(),
Some(HoverKind::Type(function.return_type.clone())), Some(HoverKind::Type(function.return_type.clone())),
None, function.documentation.clone(),
); );
let idx = scope let idx = scope
@ -711,9 +743,10 @@ pub fn analyze_context(
let function_symbol = scope.state.new_symbol(idx, SemanticKind::Function); let function_symbol = scope.state.new_symbol(idx, SemanticKind::Function);
scope.state.set_symbol(idx, function_symbol); scope.state.set_symbol(idx, function_symbol);
scope.state.functions.insert(function.name.clone(), function_symbol); scope.state.functions.insert(function.name.clone(), function_symbol);
scope scope.functions.insert(
.functions function.name.clone(),
.insert(function.name.clone(), (module.module_id, function_symbol)); (module.module_id, function_symbol, function.documentation.clone()),
);
} }
for function in &module.functions { for function in &module.functions {
@ -777,10 +810,12 @@ pub fn analyze_context(
} }
} }
let symbol = if let Some((source_id, symbol_id)) = scope.functions.get(&import_name) { let symbol = if let Some((source_id, symbol_id, doc)) = scope.functions.get(&import_name) {
scope let symbol_id = scope
.state .state
.new_symbol(import_idx, SemanticKind::Reference(*source_id, *symbol_id)) .new_symbol(import_idx, SemanticKind::Reference(*source_id, *symbol_id));
scope.state.set_documentation(import_idx, doc.clone());
symbol_id
} else if let Some(module_source) = scope.map.values().find(|s| s.module_name == *module_name) { } else if let Some(module_source) = scope.map.values().find(|s| s.module_name == *module_name) {
if let Some((source_id, symbol_id)) = scope.types.get(&TypeKind::CustomType(CustomTypeKey( if let Some((source_id, symbol_id)) = scope.types.get(&TypeKind::CustomType(CustomTypeKey(
import_name.clone(), import_name.clone(),
@ -1070,10 +1105,12 @@ pub fn analyze_expr(
let idx = scope let idx = scope
.token_idx(&meta, |t| matches!(t, Token::Identifier(_))) .token_idx(&meta, |t| matches!(t, Token::Identifier(_)))
.unwrap_or(meta.range.end); .unwrap_or(meta.range.end);
let symbol = if let Some((module_id, symbol_id)) = scope.functions.get(name) { let symbol = if let Some((module_id, symbol_id, doc)) = scope.functions.get(name) {
scope let symbol = scope
.state .state
.new_symbol(idx, SemanticKind::Reference(*module_id, *symbol_id)) .new_symbol(idx, SemanticKind::Reference(*module_id, *symbol_id));
scope.state.set_documentation(idx, doc.clone());
symbol
} else { } else {
scope.state.new_symbol(idx, SemanticKind::Function) scope.state.new_symbol(idx, SemanticKind::Function)
}; };
@ -1106,12 +1143,14 @@ pub fn analyze_expr(
let fn_idx = scope let fn_idx = scope
.token_idx(&meta, |t| matches!(t, Token::Identifier(_))) .token_idx(&meta, |t| matches!(t, Token::Identifier(_)))
.unwrap_or(meta.range.end); .unwrap_or(meta.range.end);
let fn_symbol = if let Some((module_id, symbol_id)) = let fn_symbol = if let Some((module_id, symbol_id, doc)) =
scope.associated_functions.get(&(invoked_ty.clone(), name.clone())) scope.associated_functions.get(&(invoked_ty.clone(), name.clone()))
{ {
scope let symbol = scope
.state .state
.new_symbol(fn_idx, SemanticKind::Reference(*module_id, *symbol_id)) .new_symbol(fn_idx, SemanticKind::Reference(*module_id, *symbol_id));
scope.state.set_documentation(fn_idx, doc.clone());
symbol
} else { } else {
scope.state.new_symbol(fn_idx, SemanticKind::Function) scope.state.new_symbol(fn_idx, SemanticKind::Function)
}; };

View File

@ -12,12 +12,12 @@ use tower_lsp::lsp_types::{
self, CompletionItem, CompletionItemKind, CompletionOptions, CompletionParams, CompletionResponse, Diagnostic, self, CompletionItem, CompletionItemKind, CompletionOptions, CompletionParams, CompletionResponse, Diagnostic,
DiagnosticSeverity, DidChangeTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, DiagnosticSeverity, DidChangeTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams,
DocumentFilter, GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams, DocumentFilter, GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams,
HoverProviderCapability, InitializeParams, InitializeResult, InitializedParams, Location, MarkupContent, HoverProviderCapability, InitializeParams, InitializeResult, InitializedParams, Location, MarkedString,
MarkupKind, MessageType, OneOf, Range, ReferenceParams, RenameParams, SemanticToken, SemanticTokensLegend, MarkupContent, MarkupKind, MessageType, OneOf, Range, ReferenceParams, RenameParams, SemanticToken,
SemanticTokensOptions, SemanticTokensParams, SemanticTokensResult, SemanticTokensServerCapabilities, SemanticTokensLegend, SemanticTokensOptions, SemanticTokensParams, SemanticTokensResult,
ServerCapabilities, TextDocumentItem, TextDocumentRegistrationOptions, TextDocumentSyncCapability, SemanticTokensServerCapabilities, ServerCapabilities, TextDocumentItem, TextDocumentRegistrationOptions,
TextDocumentSyncKind, TextDocumentSyncOptions, TextDocumentSyncSaveOptions, TextEdit, Url, WorkspaceEdit, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, TextDocumentSyncSaveOptions, TextEdit,
WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities, Url, WorkspaceEdit, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
}; };
use tower_lsp::{Client, LanguageServer, LspService, Server, jsonrpc}; use tower_lsp::{Client, LanguageServer, LspService, Server, jsonrpc};
@ -199,7 +199,7 @@ impl LanguageServer for Backend {
None None
}; };
let (range, ty) = if let Some((idx, token)) = token { let (range, ty, documentation) = if let Some((idx, token)) = token {
if let Some(analysis) = self.analysis.get(&path).unwrap().state.map.get(&idx) { if let Some(analysis) = self.analysis.get(&path).unwrap().state.map.get(&idx) {
let start = token.position; let start = token.position;
let end = token.position.add(token.token.len() as u32); let end = token.position.add(token.token.len() as u32);
@ -216,7 +216,9 @@ impl LanguageServer for Backend {
if let Some(hover) = analysis.hover.clone() { if let Some(hover) = analysis.hover.clone() {
if let Some(kind) = hover.kind { if let Some(kind) = hover.kind {
match kind { match kind {
analysis::HoverKind::Type(type_kind) => (Some(range), format!("{}", type_kind)), analysis::HoverKind::Type(type_kind) => {
(Some(range), format!("{}", type_kind), hover.documentation)
}
analysis::HoverKind::Function(name, function_params, return_type) => ( analysis::HoverKind::Function(name, function_params, return_type) => (
Some(range), Some(range),
format!( format!(
@ -229,25 +231,30 @@ impl LanguageServer for Backend {
.join(", "), .join(", "),
return_type return_type
), ),
hover.documentation,
), ),
} }
} else { } else {
(Some(range), String::from("No type")) (Some(range), String::from("No type"), hover.documentation)
} }
} else { } else {
(Some(range), String::from("No hover")) (Some(range), String::from("No hover"), None)
} }
} else { } else {
(None, String::from("no type")) (None, String::from("no type"), None)
} }
} else { } else {
(None, String::from("no token")) (None, String::from("no token"), None)
}; };
let contents = HoverContents::Markup(MarkupContent { let contents = if let Some(doc) = documentation {
kind: MarkupKind::Markdown, HoverContents::Array(vec![MarkedString::String(doc), MarkedString::String(format!("`{ty}`"))])
value: format!("`{ty}`"), } else {
}); HoverContents::Markup(MarkupContent {
kind: MarkupKind::Markdown,
value: format!("`{ty}`"),
})
};
Ok(Some(Hover { contents, range })) Ok(Some(Hover { contents, range }))
} }