Improve function hover texts

This commit is contained in:
Sofia 2025-08-14 17:05:32 +03:00
parent dcb4e76a40
commit ceee2f286a
2 changed files with 163 additions and 85 deletions

View File

@ -85,7 +85,7 @@ impl StaticAnalysis {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SemanticToken { pub struct SemanticToken {
pub hover: Option<Hover>, pub hover: Hover,
pub autocomplete: Vec<Autocomplete>, pub autocomplete: Vec<Autocomplete>,
pub symbol: Option<SymbolId>, pub symbol: Option<SymbolId>,
} }
@ -162,15 +162,19 @@ impl AnalysisState {
} }
impl AnalysisState { impl AnalysisState {
pub fn init_hover(&mut self, meta: &mir::Metadata, kind: Option<HoverKind>, documentation: Option<String>) { pub fn set_hover_meta(&mut self, meta: &mir::Metadata, hover: Hover) {
for token in meta.range.start..=meta.range.end { for token in meta.range.start..=meta.range.end {
self.set_hover(token, hover.clone());
}
}
pub fn set_hover(&mut self, token_idx: usize, hover: Hover) {
if let Some(token) = self.map.get_mut(&token_idx) {
token.hover = hover.clone();
} else {
self.map.insert( self.map.insert(
token, token_idx,
SemanticToken { SemanticToken {
hover: Some(Hover { hover: hover.clone(),
documentation: documentation.clone(),
kind: kind.clone(),
}),
autocomplete: Vec::new(), autocomplete: Vec::new(),
symbol: Default::default(), symbol: Default::default(),
}, },
@ -185,7 +189,10 @@ impl AnalysisState {
self.map.insert( self.map.insert(
token_idx, token_idx,
SemanticToken { SemanticToken {
hover: None, hover: Hover {
documentation: None,
kind: None,
},
autocomplete: autocomplete.clone(), autocomplete: autocomplete.clone(),
symbol: Default::default(), symbol: Default::default(),
}, },
@ -196,22 +203,15 @@ impl AnalysisState {
pub fn set_documentation(&mut self, token_idx: usize, documentation: Option<String>) { pub fn set_documentation(&mut self, token_idx: usize, documentation: Option<String>) {
if let Some(documentation) = documentation { if let Some(documentation) = documentation {
if let Some(token) = self.map.get_mut(&token_idx) { if let Some(token) = self.map.get_mut(&token_idx) {
if let Some(hover) = &mut token.hover { token.hover.documentation = Some(documentation);
hover.documentation = Some(documentation);
} else {
token.hover = Some(Hover {
documentation: Some(documentation),
kind: None,
});
}
} else { } else {
self.map.insert( self.map.insert(
token_idx, token_idx,
SemanticToken { SemanticToken {
hover: Some(Hover { hover: Hover {
documentation: Some(documentation), documentation: Some(documentation),
kind: None, kind: None,
}), },
autocomplete: Vec::new(), autocomplete: Vec::new(),
symbol: Default::default(), symbol: Default::default(),
}, },
@ -228,7 +228,10 @@ impl AnalysisState {
self.map.insert( self.map.insert(
idx, idx,
SemanticToken { SemanticToken {
hover: None, hover: Hover {
documentation: None,
kind: None,
},
autocomplete: Vec::new(), autocomplete: Vec::new(),
symbol: Some(symbol), symbol: Some(symbol),
}, },
@ -273,8 +276,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, Option<String>)>, functions: HashMap<String, (SourceModuleId, SymbolId, Hover)>,
associated_functions: HashMap<(TypeKind, String), (SourceModuleId, SymbolId, Option<String>)>, associated_functions: HashMap<(TypeKind, String), (SourceModuleId, SymbolId, Hover)>,
map: &'a StateMap, map: &'a StateMap,
} }
@ -576,7 +579,7 @@ pub fn analyze_context(
.token_idx(&field.2, |t| matches!(t, Token::Identifier(_))) .token_idx(&field.2, |t| matches!(t, Token::Identifier(_)))
.unwrap_or(field.2.range.end); .unwrap_or(field.2.range.end);
scope.state.init_hover( scope.state.set_hover_meta(
&Metadata { &Metadata {
source_module_id: field.2.source_module_id, source_module_id: field.2.source_module_id,
range: TokenRange { range: TokenRange {
@ -585,8 +588,10 @@ pub fn analyze_context(
}, },
position: None, position: None,
}, },
Some(HoverKind::Type(field.1.clone())), Hover {
None, kind: Some(HoverKind::Type(field.1.clone())),
documentation: None,
},
); );
let field_symbol = scope.state.new_symbol(field_idx, SemanticKind::Property); let field_symbol = scope.state.new_symbol(field_idx, SemanticKind::Property);
@ -628,7 +633,7 @@ pub fn analyze_context(
scope.state.new_symbol(field_ty_idx, SemanticKind::Type) scope.state.new_symbol(field_ty_idx, SemanticKind::Type)
}; };
scope.state.init_hover( scope.state.set_hover_meta(
&Metadata { &Metadata {
source_module_id: field.2.source_module_id, source_module_id: field.2.source_module_id,
range: TokenRange { range: TokenRange {
@ -637,8 +642,10 @@ pub fn analyze_context(
}, },
position: None, position: None,
}, },
Some(HoverKind::Type(field.1.clone())), Hover {
None, kind: Some(HoverKind::Type(field.1.clone())),
documentation: None,
},
); );
scope.state.set_symbol(field_ty_idx, field_ty_symbol); scope.state.set_symbol(field_ty_idx, field_ty_symbol);
} }
@ -649,9 +656,13 @@ pub fn analyze_context(
for binop in &module.binop_defs { for binop in &module.binop_defs {
if binop.meta.source_module_id == module.module_id { if binop.meta.source_module_id == module.module_id {
for param in [&binop.lhs, &binop.rhs] { for param in [&binop.lhs, &binop.rhs] {
scope scope.state.set_hover_meta(
.state &param.meta,
.init_hover(&param.meta, Some(HoverKind::Type(param.ty.clone())), None); Hover {
kind: Some(HoverKind::Type(param.ty.clone())),
documentation: None,
},
);
let idx = scope let idx = scope
.token_idx(&param.meta, |t| matches!(t, Token::Identifier(_))) .token_idx(&param.meta, |t| matches!(t, Token::Identifier(_)))
.unwrap_or(param.meta.range.end); .unwrap_or(param.meta.range.end);
@ -675,7 +686,18 @@ pub fn analyze_context(
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.associated_functions.insert( scope.associated_functions.insert(
(ty.clone(), function.name.clone()), (ty.clone(), function.name.clone()),
(source_id, *symbol, function.documentation.clone()), (
source_id,
*symbol,
Hover {
documentation: function.documentation.clone(),
kind: Some(HoverKind::Function(
function.name.clone(),
function.parameters.clone(),
function.return_type.clone(),
)),
},
),
); );
} }
} }
@ -694,7 +716,18 @@ pub fn analyze_context(
.insert((ty.clone(), function.name.clone()), symbol); .insert((ty.clone(), function.name.clone()), symbol);
scope.associated_functions.insert( scope.associated_functions.insert(
(ty.clone(), function.name.clone()), (ty.clone(), function.name.clone()),
(module.module_id, symbol, function.documentation.clone()), (
module.module_id,
symbol,
Hover {
documentation: function.documentation.clone(),
kind: Some(HoverKind::Function(
function.name.clone(),
function.parameters.clone(),
function.return_type.clone(),
)),
},
),
); );
} }
@ -723,7 +756,18 @@ pub fn analyze_context(
if let Some(symbol) = state.functions.get(&function.name) { if let Some(symbol) = state.functions.get(&function.name) {
scope.functions.insert( scope.functions.insert(
function.name.clone(), function.name.clone(),
(source_id, *symbol, function.documentation.clone()), (
source_id,
*symbol,
Hover {
documentation: function.documentation.clone(),
kind: Some(HoverKind::Function(
function.name.clone(),
function.parameters.clone(),
function.return_type.clone(),
)),
},
),
); );
} }
} }
@ -731,10 +775,12 @@ pub fn analyze_context(
} }
} }
scope.state.init_hover( scope.state.set_hover_meta(
&function.signature(), &function.signature(),
Some(HoverKind::Type(function.return_type.clone())), Hover {
function.documentation.clone(), kind: Some(HoverKind::Type(function.return_type.clone())),
documentation: None,
},
); );
let idx = scope let idx = scope
@ -745,7 +791,18 @@ pub fn analyze_context(
scope.state.functions.insert(function.name.clone(), function_symbol); scope.state.functions.insert(function.name.clone(), function_symbol);
scope.functions.insert( scope.functions.insert(
function.name.clone(), function.name.clone(),
(module.module_id, function_symbol, function.documentation.clone()), (
module.module_id,
function_symbol,
Hover {
documentation: function.documentation.clone(),
kind: Some(HoverKind::Function(
function.name.clone(),
function.parameters.clone(),
function.return_type.clone(),
)),
},
),
); );
} }
@ -768,7 +825,13 @@ pub fn analyze_context(
} }
for import in &module.imports { for import in &module.imports {
scope.state.init_hover(&import.1, None, None); scope.state.set_hover_meta(
&import.1,
Hover {
kind: None,
documentation: None,
},
);
if let Some((module_name, _)) = import.0.get(0) { if let Some((module_name, _)) = import.0.get(0) {
let module_idx = scope let module_idx = scope
.token_idx(&import.1, |t| matches!(t, Token::Identifier(_))) .token_idx(&import.1, |t| matches!(t, Token::Identifier(_)))
@ -810,11 +873,11 @@ pub fn analyze_context(
} }
} }
let symbol = if let Some((source_id, symbol_id, doc)) = scope.functions.get(&import_name) { let symbol = if let Some((source_id, symbol_id, hover)) = scope.functions.get(&import_name) {
let symbol_id = 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()); scope.state.set_hover(import_idx, hover.clone());
symbol_id 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(
@ -850,9 +913,13 @@ pub fn analyze_function_parameters(
scope: &mut AnalysisScope, scope: &mut AnalysisScope,
) { ) {
for param in &function.parameters { for param in &function.parameters {
scope scope.state.set_hover_meta(
.state &param.meta,
.init_hover(&param.meta, Some(HoverKind::Type(param.ty.clone())), None); Hover {
kind: Some(HoverKind::Type(param.ty.clone())),
documentation: None,
},
);
if param.meta.source_module_id == module.module_id { if param.meta.source_module_id == module.module_id {
let param_var_idx = scope let param_var_idx = scope
@ -891,14 +958,16 @@ pub fn analyze_block(
for statement in &block.statements { for statement in &block.statements {
match &statement.0 { match &statement.0 {
mir::StmtKind::Let(named_variable_ref, _, expression) => { mir::StmtKind::Let(named_variable_ref, _, expression) => {
scope.state.init_hover( scope.state.set_hover_meta(
&named_variable_ref.2, &named_variable_ref.2,
expression Hover {
documentation: None,
kind: expression
.return_type(&TypeRefs::unknown(), source_module.module_id) .return_type(&TypeRefs::unknown(), source_module.module_id)
.ok() .ok()
.map(|(_, ty)| ty) .map(|(_, ty)| ty)
.map(|t| HoverKind::Type(t)), .map(|t| HoverKind::Type(t)),
None, },
); );
let idx = scope let idx = scope
.token_idx(&named_variable_ref.2, |t| matches!(t, Token::Identifier(_))) .token_idx(&named_variable_ref.2, |t| matches!(t, Token::Identifier(_)))
@ -949,19 +1018,26 @@ pub fn analyze_expr(
expr: &mir::Expression, expr: &mir::Expression,
scope: &mut AnalysisScope, scope: &mut AnalysisScope,
) { ) {
scope.state.init_hover( scope.state.set_hover_meta(
&expr.1, &expr.1,
expr.return_type(&TypeRefs::unknown(), source_module.module_id) Hover {
documentation: None,
kind: expr
.return_type(&TypeRefs::unknown(), source_module.module_id)
.ok() .ok()
.map(|(_, t)| HoverKind::Type(t)), .map(|(_, t)| HoverKind::Type(t)),
None, },
); );
match &expr.0 { match &expr.0 {
mir::ExprKind::Variable(var_ref) => { mir::ExprKind::Variable(var_ref) => {
scope scope.state.set_hover_meta(
.state &var_ref.2,
.init_hover(&var_ref.2, Some(HoverKind::Type(var_ref.0.clone())), None); Hover {
documentation: None,
kind: Some(HoverKind::Type(var_ref.0.clone())),
},
);
let idx = scope let idx = scope
.token_idx(&var_ref.2, |t| matches!(t, Token::Identifier(_))) .token_idx(&var_ref.2, |t| matches!(t, Token::Identifier(_)))
@ -1105,11 +1181,11 @@ 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, doc)) = scope.functions.get(name) { let symbol = if let Some((module_id, symbol_id, hover)) = scope.functions.get(name) {
let symbol = 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()); scope.state.set_hover(idx, hover.clone());
symbol symbol
} else { } else {
scope.state.new_symbol(idx, SemanticKind::Function) scope.state.new_symbol(idx, SemanticKind::Function)
@ -1143,13 +1219,13 @@ 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, doc)) = let fn_symbol = if let Some((module_id, symbol_id, hover)) =
scope.associated_functions.get(&(invoked_ty.clone(), name.clone())) scope.associated_functions.get(&(invoked_ty.clone(), name.clone()))
{ {
let symbol = 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()); scope.state.set_hover(fn_idx, hover.clone());
symbol symbol
} else { } else {
scope.state.new_symbol(fn_idx, SemanticKind::Function) scope.state.new_symbol(fn_idx, SemanticKind::Function)

View File

@ -213,12 +213,13 @@ impl LanguageServer for Backend {
character: (end.0 as i32 - 1).max(0) as u32, character: (end.0 as i32 - 1).max(0) as u32,
}, },
}; };
if let Some(hover) = analysis.hover.clone() { if let Some(kind) = &analysis.hover.kind {
if let Some(kind) = hover.kind {
match kind { match kind {
analysis::HoverKind::Type(type_kind) => { analysis::HoverKind::Type(type_kind) => (
(Some(range), format!("{}", type_kind), hover.documentation) Some(range),
} format!("{}", type_kind),
analysis.hover.documentation.clone(),
),
analysis::HoverKind::Function(name, function_params, return_type) => ( analysis::HoverKind::Function(name, function_params, return_type) => (
Some(range), Some(range),
format!( format!(
@ -231,14 +232,15 @@ impl LanguageServer for Backend {
.join(", "), .join(", "),
return_type return_type
), ),
hover.documentation, analysis.hover.documentation.clone(),
), ),
} }
} else { } else {
(Some(range), String::from("No type"), hover.documentation) (
} Some(range),
} else { String::from("No type"),
(Some(range), String::from("No hover"), None) analysis.hover.documentation.clone(),
)
} }
} else { } else {
(None, String::from("no type"), None) (None, String::from("no type"), None)