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

301 lines
12 KiB
C#

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 Newtonsoft.Json.Linq;
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 findNode = doc.Bound.GetBottomNodeAtPosition(position.Line, 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.FunctionOption.First().ResultType}");
}
}
else if (findNode is BoundVariableAssignment variableAssignment)
{
contents.Append($"\n\nType: {variableAssignment.Variable.Type}");
}
else if (findNode is BoundFunctionExpression fe)
{
contents.Append($"\n\nReturns: {fe.ReturnType}");
}
else if (findNode is BoundFunctionCallExpression functionCall)
{
contents.Append(
$"\n\nParameters: {string.Join(", ", functionCall.Parameters.Select(x => x.Type))}");
contents.Append($"\n\nReturns: {functionCall.Type}");
}
return new Hover()
{
Contents = contents.ToString()
};
}
}
}
return new Hover();
}
[JsonRpcMethod]
public async Task<SignatureHelp> SignatureHelp(TextDocumentIdentifier textDocument, Position position)
{
if (Session.Documents.TryGetValue(textDocument.Uri, out var doc))
{
await doc.WaitForDocumentBound();
if (doc.Bound != null && doc.SourceText != null)
{
var findNode = doc.Bound.GetBottomNodeAtPosition(position.Line, position.Character);
if (findNode != null)
{
if (findNode.Kind == BoundKind.BoundFunctionCallExpression)
{
var functionCall = (BoundFunctionCallExpression)findNode;
var exp = Binder.ResolveVariable(functionCall.Identifier, null);
if (exp is InternalFunctionVariableSymbol internalFunction)
{
var n = new SignatureHelp(new List<SignatureInformation>()
{
new SignatureInformation(exp.Name, string.Join(" \n", exp.CommentValue),
((InternalFunctionVariableOption)internalFunction.FunctionOption.First()).FunctionParameters.Select(
x => new ParameterInformation("", $"{x.Name} ({x.ValidTypes})")).ToList())
}, 0, functionCall.Parameters.Length);
return n;
}
}
}
}
}
return new SignatureHelp();
}
[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,
session.Settings.ModuleDirectory);
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,
session.Settings.ModuleDirectory);
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))
return new CompletionList(new CompletionItem[0], false);
await doc.WaitForDocumentBound();
if (doc.Bound == null)
return new CompletionList(new CompletionItem[0], false);
using (var nodeIterator = doc.Bound.GetNodeAtPosition(position.Line, position.Character).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(position.Line, position.Character);
var variables = scope.GetBoundScopeVisibleVariables();
if (scope != null)
{
return new CompletionList(
variables.Select(x =>
{
var (_, value) = x;
return BuildItem(value);
}));
}
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 (_, value) = x;
return BuildItem(value);
}));
}
if (variableSymbol is TableVariableSymbol tableSymbol)
{
return new CompletionList(
tableSymbol.Variables.Select(x =>
{
var (_, value) = x;
return BuildItem(value);
}));
}
return null;
}
private static CompletionItem BuildItem(VariableSymbol symbol)
{
var kind = CompletionItemKind.Variable;
var data = new JObject();
var documentation = symbol.CommentValue == null
? ""
: $"{string.Join(" \n", symbol.CommentValue)}";
if (symbol is FunctionVariableSymbol fun)
{
kind = CompletionItemKind.Function;
switch (fun)
{
case InternalFunctionVariableSymbol internalFunctionVariableSymbol:
data.Add("varCount",
((InternalFunctionVariableOption) internalFunctionVariableSymbol.FunctionOption.First())
.FunctionParameters.Count(x => !x.IsOptional));
break;
case ScriptFunctionVariableSymbol scriptFunctionVariableSymbol:
data.Add("varCount",
((ScriptFunctionVariableOption) scriptFunctionVariableSymbol.FunctionOption.First())
.Parameters.Length);
break;
}
}
else if (symbol is UserDataVariableSymbol boundVar)
{
if (boundVar.TypeContainer == Type.Function)
{
}
else if (boundVar.TypeContainer == Type.UserData)
{
if (boundVar.BoundTypeDefinition is UserDataBoundEnumDefinition)
{
kind = CompletionItemKind.Enum;
}
}
}
return new CompletionItem(symbol.Name, kind, symbol.TypeContainer.ToString(), documentation, data);
}
private static CompletionItem BuildItem(UserDataBoundProperty property)
{
var kind = CompletionItemKind.Variable;
var data = new JObject();
if (property.Type == Type.Function)
{
var fun = (UserDataBoundMethod)property;
kind = CompletionItemKind.Function;
data.Add("varCount", fun.Options.First().Parameters.Count(x => !x.IsOptional));
}
const string documentation = "";
return new CompletionItem(property.Name, kind, property.Type.ToString(), documentation, data);
}
}
}