Upsilon-VsCode/UpsilonLanguageServer/UpsilonLanguageServer/Services/TextDocumentServer.cs

212 lines
8.6 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JsonRpc.Standard.Contracts;
using LanguageServer.VsCode;
using LanguageServer.VsCode.Contracts;
using Upsilon.Binder;
using Upsilon.Binder.VariableSymbols;
using Upsilon.BoundTypes;
using Upsilon.Utilities;
using Type = Upsilon.BaseTypes.Type;
namespace UpsilonLanguageServer.Services
{
[JsonRpcScope(MethodPrefix = "textDocument/")]
public class TextDocumentService : UpsilonLanguageServiceBase
{
[JsonRpcMethod]
public async Task<Hover> Hover(TextDocumentIdentifier textDocument, Position position, CancellationToken ct)
{
// Note that Hover is cancellable.
//await Task.Delay(1000, ct);
if (Session.Documents.TryGetValue(textDocument.Uri, out var doc))
{
if (doc.Bound != null && doc.SourceText != null)
{
var linePos = doc.SourceText.GetLineStartPos(position.Line);
var findNode = doc.Bound.GetBottomNodeAtPosition(linePos + position.Character);
if (findNode != null)
{
var contents = new StringBuilder($"Kind: {findNode.Kind}");
if (findNode is BoundExpression expression)
{
contents.Append($"\n\nType: {expression.Type}");
}
if (findNode is BoundVariableSymbol varSymbol)
{
if (varSymbol.VariableSymbol.CommentValue != null)
{
contents.Append($"\n\n{string.Join(" \n", varSymbol.VariableSymbol.CommentValue)}");
}
if (varSymbol.VariableSymbol is FunctionVariableSymbol fVar)
{
contents.Append($"\n\nReturns: {fVar.ResultType}");
}
}
else if (findNode is BoundFunctionExpression fe)
{
contents.Append($"\n\nReturns: {fe.ReturnType}");
}
return new Hover()
{
Contents = contents.ToString()
};
}
}
}
return new Hover();
}
[JsonRpcMethod]
public SignatureHelp SignatureHelp(TextDocumentIdentifier textDocument, Position position)
{
// TODO
return new SignatureHelp(new List<SignatureInformation>
{
});
}
[JsonRpcMethod(IsNotification = true)]
public async Task DidOpen(TextDocumentItem textDocument)
{
if (textDocument.Uri.IsUntitled())
{
var workspace = Session.Client.Workspace;
if (workspace != null)
{
}
}
var doc = new SessionDocument(textDocument);
var session = Session;
doc.DocumentChanged += async (sender, args) =>
{
// Lint the document when it's changed.
var sessionDocument = ((SessionDocument) sender);
var doc1 = sessionDocument.Document;
var diag1 = DiagnosticProvider.LintDocument(doc1, sessionDocument, session.Settings.MaxNumberOfProblems);
if (session.Documents.ContainsKey(doc1.Uri))
{
// In case the document has been closed when we were linting…
await session.Client.Document.PublishDiagnostics(doc1.Uri, await diag1);
}
};
Session.Documents.TryAdd(textDocument.Uri, doc);
var diag = DiagnosticProvider.LintDocument(doc.Document, doc, Session.Settings.MaxNumberOfProblems);
await Client.Document.PublishDiagnostics(textDocument.Uri, await diag);
}
[JsonRpcMethod(IsNotification = true)]
public void DidChange(TextDocumentIdentifier textDocument,
ICollection<TextDocumentContentChangeEvent> contentChanges)
{
Session.Documents[textDocument.Uri].NotifyChanges(contentChanges);
}
[JsonRpcMethod(IsNotification = true)]
public void WillSave(TextDocumentIdentifier textDocument, TextDocumentSaveReason reason)
{
//Client.Window.LogMessage(MessageType.Log, "-----------");
//Client.Window.LogMessage(MessageType.Log, Documents[textDocument].Content);
}
[JsonRpcMethod(IsNotification = true)]
public async Task DidClose(TextDocumentIdentifier textDocument)
{
if (textDocument.Uri.IsUntitled())
{
await Client.Document.PublishDiagnostics(textDocument.Uri, new Diagnostic[0]);
}
Session.Documents.TryRemove(textDocument.Uri, out _);
}
[JsonRpcMethod]
public async Task<CompletionList> Completion(TextDocumentIdentifier textDocument, Position position, CompletionContext context)
{
if (Session.Documents.TryGetValue(textDocument.Uri, out var doc) && doc.Bound != null)
{
var linePos = doc.SourceText.GetLineStartPos(position.Line);
var characterPosition = linePos + position.Character;
using (var nodeIterator = doc.Bound.GetNodeAtPosition(characterPosition).GetEnumerator())
{
if (nodeIterator.MoveNext())
{
var node = nodeIterator.Current;
if (node.Kind != BoundKind.BoundFullstopIndexExpression && nodeIterator.MoveNext())
{
node = nodeIterator.Current;
}
if (node is BoundFullStopIndexExpression indexExpression)
{
var expression = indexExpression.Expression;
var variableSymbol = Binder.ResolveVariable(expression, null);
var result = await GetListFromVariableSymbol(variableSymbol);
if (result != null)
{
return result;
}
}
}
}
var scope = doc.Bound.GetScopeAt(characterPosition);
var variables = scope.GetBoundScopeVisibleVariables();
if (scope != null)
{
return new CompletionList(
variables.Select(x =>
{
var (key, value) = x;
return new CompletionItem(value.Name,
CompletionItemKind.Variable, value.Type.ToString(),
x.Value.CommentValue == null
? ""
: $"\n\n{string.Join(" \n", value.CommentValue)}");
}));
}
}
return new CompletionList(new CompletionItem[0], false);
}
private static async Task<CompletionList> GetListFromVariableSymbol(VariableSymbol variableSymbol)
{
if (variableSymbol is UserDataVariableSymbol parameterSymbol &&
parameterSymbol.BoundTypeDefinition is UserDataBoundTypeDefinition udBoundDef)
{
return new CompletionList(
udBoundDef.Properties.Select(x =>
{
var (key, value) = x;
return new CompletionItem(key, CompletionItemKind.Variable,
$"{value.ActualType}({value.Type})", $"{value.Comment}", null);
}));
}
if (variableSymbol is TableVariableSymbol tableSymbol)
{
return new CompletionList(
tableSymbol.Variables.Select(x =>
{
var (key, value) = x;
return new CompletionItem(key,
CompletionItemKind.Variable, value.Type.ToString(),
x.Value.CommentValue == null
? ""
: $"{string.Join(" \n", value.CommentValue)}", null);
}));
}
return null;
}
}
}