using System.Globalization; using NubLang.Ast; using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; using OmniSharp.Extensions.LanguageServer.Protocol.Document; using OmniSharp.Extensions.LanguageServer.Protocol.Models; namespace NubLang.LSP; internal class HoverHandler(WorkspaceManager workspaceManager) : HoverHandlerBase { protected override HoverRegistrationOptions CreateRegistrationOptions(HoverCapability capability, ClientCapabilities clientCapabilities) { return new HoverRegistrationOptions { DocumentSelector = TextDocumentSelector.ForLanguage("nub") }; } public override Task Handle(HoverParams request, CancellationToken cancellationToken) { return Task.FromResult(HandleSync(request)); } private Hover? HandleSync(HoverParams request) { var compilationUnit = workspaceManager.GetCompilationUnit(request.TextDocument.Uri); if (compilationUnit == null) { return null; } var line = request.Position.Line; var character = request.Position.Character; var hoveredNode = compilationUnit.Functions .SelectMany(x => x.EnumerateDescendantsAndSelf()) .Where(n => IsHoveringOverNode(n, line, character)) .OrderBy(n => n.Tokens.First().Span.Start.Line) .ThenBy(n => n.Tokens.First().Span.Start.Column) .LastOrDefault(); if (hoveredNode == null) { return null; } var message = CreateMessage(hoveredNode, compilationUnit); if (message == null) { return null; } return new Hover { Contents = new MarkedStringsOrMarkupContent(new MarkupContent { Value = message, Kind = MarkupKind.Markdown, }) }; } private static string? CreateMessage(Node hoveredNode, CompilationUnit compilationUnit) { return hoveredNode switch { FuncNode funcNode => CreateFuncPrototypeNodeMessage(funcNode.Prototype), FuncPrototypeNode funcPrototypeNode => CreateFuncPrototypeNodeMessage(funcPrototypeNode), FuncIdentifierNode funcIdentifierNode => CreateFuncIdentifierNodeMessage(funcIdentifierNode, compilationUnit), FuncParameterNode funcParameterNode => CreateVariableIdentifierNodeMessage(funcParameterNode.Name, funcParameterNode.Type), VariableIdentifierNode variableIdentifierNode => CreateVariableIdentifierNodeMessage(variableIdentifierNode.Name, variableIdentifierNode.Type), VariableDeclarationNode variableDeclarationNode => CreateVariableIdentifierNodeMessage(variableDeclarationNode.Name, variableDeclarationNode.Type), CStringLiteralNode cStringLiteralNode => CreateLiteralNodeMessage(cStringLiteralNode.Type, '"' + cStringLiteralNode.Value + '"'), StringLiteralNode stringLiteralNode => CreateLiteralNodeMessage(stringLiteralNode.Type, '"' + stringLiteralNode.Value + '"'), BoolLiteralNode boolLiteralNode => CreateLiteralNodeMessage(boolLiteralNode.Type, boolLiteralNode.Value.ToString()), Float32LiteralNode float32LiteralNode => CreateLiteralNodeMessage(float32LiteralNode.Type, float32LiteralNode.Value.ToString(CultureInfo.InvariantCulture)), Float64LiteralNode float64LiteralNode => CreateLiteralNodeMessage(float64LiteralNode.Type, float64LiteralNode.Value.ToString(CultureInfo.InvariantCulture)), I8LiteralNode i8LiteralNode => CreateLiteralNodeMessage(i8LiteralNode.Type, i8LiteralNode.Value.ToString()), I16LiteralNode i16LiteralNode => CreateLiteralNodeMessage(i16LiteralNode.Type, i16LiteralNode.Value.ToString()), I32LiteralNode i32LiteralNode => CreateLiteralNodeMessage(i32LiteralNode.Type, i32LiteralNode.Value.ToString()), I64LiteralNode i64LiteralNode => CreateLiteralNodeMessage(i64LiteralNode.Type, i64LiteralNode.Value.ToString()), U8LiteralNode u8LiteralNode => CreateLiteralNodeMessage(u8LiteralNode.Type, u8LiteralNode.Value.ToString()), U16LiteralNode u16LiteralNode => CreateLiteralNodeMessage(u16LiteralNode.Type, u16LiteralNode.Value.ToString()), U32LiteralNode u32LiteralNode => CreateLiteralNodeMessage(u32LiteralNode.Type, u32LiteralNode.Value.ToString()), U64LiteralNode u64LiteralNode => CreateLiteralNodeMessage(u64LiteralNode.Type, u64LiteralNode.Value.ToString()), // Expressions can have a generic fallback showing the resulting type ExpressionNode expressionNode => CreateGenericExpressionNodeMessage(expressionNode), // Explicit null returns, can be removed when the default is null instead of the debug type BlockNode => null, StatementNode statementNode => CreateGenericStatementNodeMessage(statementNode), _ => hoveredNode.GetType().Name }; } private static string CreateLiteralNodeMessage(NubType type, string value) { return $""" **Literal** `{type}` ```nub {value}: {type} ``` """; } private static string CreateVariableIdentifierNodeMessage(string name, NubType type) { return $""" **Variable** `{name}` ```nub {name}: {type} ``` """; } private static string CreateFuncIdentifierNodeMessage(FuncIdentifierNode funcIdentifierNode, CompilationUnit compilationUnit) { var func = compilationUnit.ImportedFunctions.FirstOrDefault(x => x.Module == funcIdentifierNode.Module && x.Name == funcIdentifierNode.Name); if (func == null) { return $""" **Function** `{funcIdentifierNode.Module}::{funcIdentifierNode.Name}` ```nub // Declaration not found ``` """; } return CreateFuncPrototypeNodeMessage(func); } private static string CreateFuncPrototypeNodeMessage(FuncPrototypeNode funcPrototypeNode) { var parameterText = string.Join(", ", funcPrototypeNode.Parameters.Select(x => $"{x.Name}: {x.Type}")); var externText = funcPrototypeNode.ExternSymbol != null ? $"extern \"{funcPrototypeNode.ExternSymbol}\" " : ""; return $""" **Function** `{funcPrototypeNode.Name}` ```nub {externText}func {funcPrototypeNode.Name}({parameterText}): {funcPrototypeNode.ReturnType} ``` """; } private static string CreateGenericStatementNodeMessage(StatementNode statementNode) { return $"**Statement** `{statementNode.GetType().Name}`"; } private static string CreateGenericExpressionNodeMessage(ExpressionNode expressionNode) { return $""" **Expression** `{expressionNode.GetType().Name}` ```nub {expressionNode.Type} ``` """; } private static bool IsHoveringOverNode(Node node, int line, int character) { if (node.Tokens.Count == 0) { return false; } var start = node.Tokens.First().Span.Start; var end = node.Tokens.Last().Span.End; var startLine = start.Line - 1; var startChar = start.Column - 1; var endLine = end.Line - 1; var endChar = end.Column - 1; if (line < startLine || line > endLine) return false; if (line > startLine && line < endLine) return true; if (startLine == endLine) { return character >= startChar && character <= endChar; } if (line == startLine) { return character >= startChar; } if (line == endLine) { return character <= endChar; } return false; } }