From 02c2ce76d37a2d2abbf7b15a1145282b0e50bd84 Mon Sep 17 00:00:00 2001 From: nub31 Date: Thu, 23 Oct 2025 13:49:12 +0200 Subject: [PATCH] Completions --- compiler/NubLang.LSP/AstExtensions.cs | 37 +++++ compiler/NubLang.LSP/CompletionHandler.cs | 181 ++++++++++++++++++++++ compiler/NubLang.LSP/HoverHandler.cs | 43 +---- compiler/NubLang.LSP/Program.cs | 2 +- 4 files changed, 222 insertions(+), 41 deletions(-) create mode 100644 compiler/NubLang.LSP/CompletionHandler.cs diff --git a/compiler/NubLang.LSP/AstExtensions.cs b/compiler/NubLang.LSP/AstExtensions.cs index d5b1c22..2c2b63c 100644 --- a/compiler/NubLang.LSP/AstExtensions.cs +++ b/compiler/NubLang.LSP/AstExtensions.cs @@ -4,6 +4,43 @@ namespace NubLang.LSP; public static class AstExtensions { + public static bool ContainsPosition(this 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; + } + public static IEnumerable EnumerateDescendantsAndSelf(this Node node) { yield return node; diff --git a/compiler/NubLang.LSP/CompletionHandler.cs b/compiler/NubLang.LSP/CompletionHandler.cs new file mode 100644 index 0000000..583e9e5 --- /dev/null +++ b/compiler/NubLang.LSP/CompletionHandler.cs @@ -0,0 +1,181 @@ +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 CompletionHandler(WorkspaceManager workspaceManager) : CompletionHandlerBase +{ + private readonly CompletionItem[] _definitionSnippets = + [ + new() + { + Kind = CompletionItemKind.Keyword, + Label = "func", + InsertTextFormat = InsertTextFormat.Snippet, + InsertText = "func ${1:name}(${2:params})\n{\n $0\n}", + }, + new() + { + Kind = CompletionItemKind.Keyword, + Label = "struct", + InsertTextFormat = InsertTextFormat.Snippet, + InsertText = "struct ${1:name}\n{\n $0\n}", + }, + new() + { + Kind = CompletionItemKind.Keyword, + Label = "module", + InsertTextFormat = InsertTextFormat.Snippet, + InsertText = "module \"$0\"", + }, + new() + { + Kind = CompletionItemKind.Keyword, + Label = "import", + InsertTextFormat = InsertTextFormat.Snippet, + InsertText = "import \"$0\"", + } + ]; + + private readonly CompletionItem[] _statementSnippets = + [ + new() + { + Kind = CompletionItemKind.Keyword, + Label = "let", + InsertTextFormat = InsertTextFormat.Snippet, + InsertText = "let ${1:name} = $0", + }, + new() + { + Kind = CompletionItemKind.Keyword, + Label = "if", + InsertTextFormat = InsertTextFormat.Snippet, + InsertText = "if ${1:condition}\n{\n $0\n}", + }, + new() + { + Kind = CompletionItemKind.Keyword, + Label = "else if", + InsertTextFormat = InsertTextFormat.Snippet, + InsertText = "else if ${1:condition}\n{\n $0\n}", + }, + new() + { + Kind = CompletionItemKind.Keyword, + Label = "else", + InsertTextFormat = InsertTextFormat.Snippet, + InsertText = "else\n{\n $0\n}", + }, + new() + { + Kind = CompletionItemKind.Keyword, + Label = "while", + InsertTextFormat = InsertTextFormat.Snippet, + InsertText = "while ${1:condition}\n{\n $0\n}", + }, + new() + { + Kind = CompletionItemKind.Keyword, + Label = "for", + InsertTextFormat = InsertTextFormat.Snippet, + InsertText = "for ${1:name}, ${2:index} in ${3:array}\n{\n $0\n}", + }, + new() + { + Kind = CompletionItemKind.Keyword, + Label = "return", + InsertTextFormat = InsertTextFormat.Snippet, + InsertText = "return $0", + }, + new() + { + Kind = CompletionItemKind.Keyword, + Label = "defer", + InsertTextFormat = InsertTextFormat.Snippet, + InsertText = "defer $0", + } + ]; + + protected override CompletionRegistrationOptions CreateRegistrationOptions(CompletionCapability capability, ClientCapabilities clientCapabilities) + { + return new CompletionRegistrationOptions(); + } + + public override Task Handle(CompletionParams request, CancellationToken cancellationToken) + { + return Task.FromResult(HandleSync(request, cancellationToken)); + } + + private CompletionList HandleSync(CompletionParams request, CancellationToken cancellationToken) + { + var completions = new List(); + var position = request.Position; + + var uri = request.TextDocument.Uri; + var compilationUnit = workspaceManager.GetCompilationUnit(uri); + if (compilationUnit != null) + { + var function = compilationUnit.Functions.FirstOrDefault(x => x.Body != null && x.Body.ContainsPosition(position.Line, position.Character)); + if (function != null) + { + completions.AddRange(_statementSnippets); + + foreach (var prototype in compilationUnit.ImportedFunctions) + { + var parameterStrings = new List(); + foreach (var (index, parameter) in prototype.Parameters.Index()) + { + parameterStrings.AddRange($"${{{index + 1}:{parameter.Name}}}"); + } + + completions.Add(new CompletionItem + { + Kind = CompletionItemKind.Function, + Label = $"{prototype.Module}::{prototype.Name}", + InsertTextFormat = InsertTextFormat.Snippet, + InsertText = $"{prototype.Module}::{prototype.Name}({string.Join(", ", parameterStrings)})", + }); + } + + foreach (var parameter in function.Prototype.Parameters) + { + completions.Add(new CompletionItem + { + Kind = CompletionItemKind.Variable, + Label = parameter.Name, + InsertText = parameter.Name, + }); + } + + var variables = function + .Body! + .EnumerateDescendantsAndSelf() + .OfType(); + + foreach (var variable in variables) + { + completions.Add(new CompletionItem + { + Kind = CompletionItemKind.Variable, + Label = variable.Name, + InsertText = variable.Name, + }); + } + } + else + { + completions.AddRange(_definitionSnippets); + } + } + + return new CompletionList(completions, false); + } + + public override Task Handle(CompletionItem request, CancellationToken cancellationToken) + { + return Task.FromResult(new CompletionItem()); + } +} \ No newline at end of file diff --git a/compiler/NubLang.LSP/HoverHandler.cs b/compiler/NubLang.LSP/HoverHandler.cs index 3567df7..0974063 100644 --- a/compiler/NubLang.LSP/HoverHandler.cs +++ b/compiler/NubLang.LSP/HoverHandler.cs @@ -18,10 +18,10 @@ internal class HoverHandler(WorkspaceManager workspaceManager) : HoverHandlerBas public override Task Handle(HoverParams request, CancellationToken cancellationToken) { - return Task.FromResult(HandleSync(request)); + return Task.FromResult(HandleSync(request, cancellationToken)); } - private Hover? HandleSync(HoverParams request) + private Hover? HandleSync(HoverParams request, CancellationToken cancellationToken) { var compilationUnit = workspaceManager.GetCompilationUnit(request.TextDocument.Uri); if (compilationUnit == null) @@ -34,7 +34,7 @@ internal class HoverHandler(WorkspaceManager workspaceManager) : HoverHandlerBas var hoveredNode = compilationUnit.Functions .SelectMany(x => x.EnumerateDescendantsAndSelf()) - .Where(n => IsHoveringOverNode(n, line, character)) + .Where(n => n.ContainsPosition(line, character)) .OrderBy(n => n.Tokens.First().Span.Start.Line) .ThenBy(n => n.Tokens.First().Span.Start.Column) .LastOrDefault(); @@ -155,41 +155,4 @@ internal class HoverHandler(WorkspaceManager workspaceManager) : HoverHandlerBas ``` """; } - - 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; - } } \ No newline at end of file diff --git a/compiler/NubLang.LSP/Program.cs b/compiler/NubLang.LSP/Program.cs index 4341c34..8218615 100644 --- a/compiler/NubLang.LSP/Program.cs +++ b/compiler/NubLang.LSP/Program.cs @@ -16,9 +16,9 @@ var server = await LanguageServer.From(options => options .SetMinimumLevel(LogLevel.Debug)) .WithHandler() .WithHandler() + .WithHandler() .OnInitialize((server, request, ct) => { - server.SendNotification("TEST"); var workspaceManager = server.GetRequiredService(); if (request.RootPath != null)