301 lines
12 KiB
C#
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);
|
|
}
|
|
|
|
}
|
|
}
|