Completions
This commit is contained in:
@@ -4,6 +4,43 @@ namespace NubLang.LSP;
|
|||||||
|
|
||||||
public static class AstExtensions
|
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<Node> EnumerateDescendantsAndSelf(this Node node)
|
public static IEnumerable<Node> EnumerateDescendantsAndSelf(this Node node)
|
||||||
{
|
{
|
||||||
yield return node;
|
yield return node;
|
||||||
|
|||||||
181
compiler/NubLang.LSP/CompletionHandler.cs
Normal file
181
compiler/NubLang.LSP/CompletionHandler.cs
Normal file
@@ -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<CompletionList> Handle(CompletionParams request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult(HandleSync(request, cancellationToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompletionList HandleSync(CompletionParams request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var completions = new List<CompletionItem>();
|
||||||
|
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<string>();
|
||||||
|
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<VariableDeclarationNode>();
|
||||||
|
|
||||||
|
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<CompletionItem> Handle(CompletionItem request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult(new CompletionItem());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,10 +18,10 @@ internal class HoverHandler(WorkspaceManager workspaceManager) : HoverHandlerBas
|
|||||||
|
|
||||||
public override Task<Hover?> Handle(HoverParams request, CancellationToken cancellationToken)
|
public override Task<Hover?> 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);
|
var compilationUnit = workspaceManager.GetCompilationUnit(request.TextDocument.Uri);
|
||||||
if (compilationUnit == null)
|
if (compilationUnit == null)
|
||||||
@@ -34,7 +34,7 @@ internal class HoverHandler(WorkspaceManager workspaceManager) : HoverHandlerBas
|
|||||||
|
|
||||||
var hoveredNode = compilationUnit.Functions
|
var hoveredNode = compilationUnit.Functions
|
||||||
.SelectMany(x => x.EnumerateDescendantsAndSelf())
|
.SelectMany(x => x.EnumerateDescendantsAndSelf())
|
||||||
.Where(n => IsHoveringOverNode(n, line, character))
|
.Where(n => n.ContainsPosition(line, character))
|
||||||
.OrderBy(n => n.Tokens.First().Span.Start.Line)
|
.OrderBy(n => n.Tokens.First().Span.Start.Line)
|
||||||
.ThenBy(n => n.Tokens.First().Span.Start.Column)
|
.ThenBy(n => n.Tokens.First().Span.Start.Column)
|
||||||
.LastOrDefault();
|
.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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -16,9 +16,9 @@ var server = await LanguageServer.From(options => options
|
|||||||
.SetMinimumLevel(LogLevel.Debug))
|
.SetMinimumLevel(LogLevel.Debug))
|
||||||
.WithHandler<TextDocumentSyncHandler>()
|
.WithHandler<TextDocumentSyncHandler>()
|
||||||
.WithHandler<HoverHandler>()
|
.WithHandler<HoverHandler>()
|
||||||
|
.WithHandler<CompletionHandler>()
|
||||||
.OnInitialize((server, request, ct) =>
|
.OnInitialize((server, request, ct) =>
|
||||||
{
|
{
|
||||||
server.SendNotification("TEST");
|
|
||||||
var workspaceManager = server.GetRequiredService<WorkspaceManager>();
|
var workspaceManager = server.GetRequiredService<WorkspaceManager>();
|
||||||
|
|
||||||
if (request.RootPath != null)
|
if (request.RootPath != null)
|
||||||
|
|||||||
Reference in New Issue
Block a user