Initial commit

This commit is contained in:
Deukhoofd 2018-11-25 22:19:02 +01:00
commit 474bd3c2f7
No known key found for this signature in database
GPG Key ID: B4C087AC81641654
27 changed files with 3175 additions and 0 deletions

18
Client/.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,18 @@
// A launch configuration that compiles the extension and then opens it inside a new window
{
"version": "0.2.0",
"configurations": [
{
"type": "extensionHost",
"request": "launch",
"name": "Launch Client",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceRoot}"],
"outFiles": ["${workspaceRoot}/client/out/**/*.js"],
"preLaunchTask": {
"type": "npm",
"script": "watch"
}
}
]
}

29
Client/.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,29 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "compile",
"group": "build",
"presentation": {
"panel": "dedicated",
"reveal": "never"
},
"problemMatcher": ["$tsc"]
},
{
"type": "npm",
"script": "watch",
"isBackground": true,
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"panel": "dedicated",
"reveal": "never"
},
"problemMatcher": ["$tsc-watch"]
}
]
}

82
Client/out/extension.js Normal file
View File

@ -0,0 +1,82 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
const vscode = require("vscode");
const languageClient = require("vscode-languageclient");
const path = require("path");
const fs = require("fs");
// Defines the search path of your language server DLL. (.NET Core)
const languageServerPaths = [
"server/DemoLanguageServer.dll",
"../UpsilonLanguageServer/UpsilonLanguageServer/bin/Debug/netcoreapp2.1/UpsilonLanguageServer.dll"
];
function activateLanguageServer(context) {
// The server is implemented in an executable application.
let serverModule = null;
for (let p of languageServerPaths) {
p = context.asAbsolutePath(p);
// console.log(p);
if (fs.existsSync(p)) {
serverModule = p;
break;
}
}
if (!serverModule)
throw new URIError("Cannot find the language server module.");
let workPath = path.dirname(serverModule);
console.log(`Use ${serverModule} as server module.`);
console.log(`Work path: ${workPath}.`);
// If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used
let serverOptions = {
run: {
command: "dotnet",
args: [serverModule],
options: { cwd: workPath }
},
debug: {
command: "dotnet",
args: [serverModule, "--debug"],
options: { cwd: workPath }
}
};
// Options to control the language client
let clientOptions = {
// Register the server for plain text documents
documentSelector: [
{
language: "upsilon",
scheme: "file"
}
],
synchronize: {
// Synchronize the setting section 'languageServerExample' to the server
configurationSection: "upsilonLanguageServer",
// Notify the server about file changes to '.clientrc files contain in the workspace
fileEvents: [
vscode.workspace.createFileSystemWatcher("**/.clientrc"),
vscode.workspace.createFileSystemWatcher("**/.yup"),
vscode.workspace.createFileSystemWatcher("**/.lua")
]
}
};
// Create the language client and start the client.
let client = new languageClient.LanguageClient("upsilonLanguageServer", "Upsilon Language Server", serverOptions, clientOptions);
let disposable = client.start();
// Push the disposable to the context's subscriptions so that the
// client can be deactivated on extension deactivation
context.subscriptions.push(disposable);
}
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
function activate(context) {
console.log("Upsilon extension is now activated.");
activateLanguageServer(context);
}
exports.activate = activate;
// this method is called when your extension is deactivated
function deactivate() { }
exports.deactivate = deactivate;
6;
//# sourceMappingURL=extension.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AACb,6DAA6D;AAC7D,8EAA8E;AAE9E,iCAAiC;AACjC,wDAAwD;AACxD,6BAA6B;AAC7B,yBAAyB;AAEzB,mEAAmE;AACnE,MAAM,mBAAmB,GAAG;IAC1B,+BAA+B;IAC/B,kGAAkG;CACnG,CAAC;AAEF,SAAS,sBAAsB,CAAC,OAAgC;IAC9D,0DAA0D;IAC1D,IAAI,YAAY,GAAW,IAAI,CAAC;IAChC,KAAK,IAAI,CAAC,IAAI,mBAAmB,EAAE;QACjC,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;QAC9B,kBAAkB;QAClB,IAAI,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;YACpB,YAAY,GAAG,CAAC,CAAC;YACjB,MAAM;SACP;KACF;IACD,IAAI,CAAC,YAAY;QACf,MAAM,IAAI,QAAQ,CAAC,yCAAyC,CAAC,CAAC;IAChE,IAAI,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IAC1C,OAAO,CAAC,GAAG,CAAC,OAAO,YAAY,oBAAoB,CAAC,CAAC;IACrD,OAAO,CAAC,GAAG,CAAC,cAAc,QAAQ,GAAG,CAAC,CAAC;IAEvC,oFAAoF;IACpF,qCAAqC;IACrC,IAAI,aAAa,GAAiC;QAChD,GAAG,EAAE;YACH,OAAO,EAAE,QAAQ;YACjB,IAAI,EAAE,CAAC,YAAY,CAAC;YACpB,OAAO,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE;SAC3B;QACD,KAAK,EAAE;YACL,OAAO,EAAE,QAAQ;YACjB,IAAI,EAAE,CAAC,YAAY,EAAE,SAAS,CAAC;YAC/B,OAAO,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE;SAC3B;KACF,CAAC;IACF,yCAAyC;IACzC,IAAI,aAAa,GAAyC;QACxD,+CAA+C;QAC/C,gBAAgB,EAAE;YAChB;gBACE,QAAQ,EAAE,SAAS;gBACnB,MAAM,EAAE,MAAM;aACf;SACF;QACD,WAAW,EAAE;YACX,wEAAwE;YACxE,oBAAoB,EAAE,uBAAuB;YAC7C,oFAAoF;YACpF,UAAU,EAAE;gBACV,MAAM,CAAC,SAAS,CAAC,uBAAuB,CAAC,cAAc,CAAC;gBACxD,MAAM,CAAC,SAAS,CAAC,uBAAuB,CAAC,SAAS,CAAC;gBACnD,MAAM,CAAC,SAAS,CAAC,uBAAuB,CAAC,SAAS,CAAC;aACpD;SACF;KACF,CAAC;IAEF,mDAAmD;IACnD,IAAI,MAAM,GAAG,IAAI,cAAc,CAAC,cAAc,CAC5C,uBAAuB,EACvB,yBAAyB,EACzB,aAAa,EACb,aAAa,CACd,CAAC;IACF,IAAI,UAAU,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC;IAEhC,iEAAiE;IACjE,sDAAsD;IACtD,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;AACzC,CAAC;AAED,yDAAyD;AACzD,0EAA0E;AAC1E,SAAgB,QAAQ,CAAC,OAAgC;IACvD,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;IACnD,sBAAsB,CAAC,OAAO,CAAC,CAAC;AAClC,CAAC;AAHD,4BAGC;AAED,2DAA2D;AAC3D,SAAgB,UAAU,KAAI,CAAC;AAA/B,gCAA+B;AAC/B,CAAC,CAAC"}

2138
Client/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

64
Client/package.json Normal file
View File

@ -0,0 +1,64 @@
{
"name": "upsilon-language-extension",
"version": "1.0.0",
"engines": {
"vscode": "^1.29.1"
},
"activationEvents": [
"onLanguage:upsilon"
],
"main": "./out/extension",
"contributes": {
"languages": [
{
"id": "upsilon",
"aliases": [
"Upsilon"
],
"extensions": [
".yup",
".lua"
]
}
],
"configuration": {
"type": "object",
"title": "Example configuration",
"properties": {
"upsilonLanguageServer.maxNumberOfProblems": {
"type": "number",
"default": 100,
"description": "Controls the maximum number of problems produced by the server."
},
"upsilonLanguageServer.trace.server": {
"scope": "window",
"type": "string",
"enum": [
"off",
"messages",
"verbose"
],
"default": "off",
"description": "Traces the communication between VSCode and the upsilonLanguageServer service."
}
}
}
},
"scripts": {
"vscode:prepublish": "vscode-install && npm run compile",
"compile": "tsc -b",
"watch": "tsc -b -w",
"postinstall": "cd client && npm install && cd ../server && npm install && cd ..",
"test": "sh ./scripts/e2e.sh"
},
"dependencies": {
"vscode-languageclient": "^4.1.4",
"vscode": "^1.1.21"
},
"devDependencies": {
"@types/node": "^10.12.10",
"vscode": "^1.1.21",
"tslint": "^5.11.0",
"typescript": "^3.1.3"
}
}

91
Client/src/extension.ts Normal file
View File

@ -0,0 +1,91 @@
"use strict";
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from "vscode";
import * as languageClient from "vscode-languageclient";
import * as path from "path";
import * as fs from "fs";
// Defines the search path of your language server DLL. (.NET Core)
const languageServerPaths = [
"server/DemoLanguageServer.dll",
"../UpsilonLanguageServer/UpsilonLanguageServer/bin/Debug/netcoreapp2.1/UpsilonLanguageServer.dll"
];
function activateLanguageServer(context: vscode.ExtensionContext) {
// The server is implemented in an executable application.
let serverModule: string = null;
for (let p of languageServerPaths) {
p = context.asAbsolutePath(p);
// console.log(p);
if (fs.existsSync(p)) {
serverModule = p;
break;
}
}
if (!serverModule)
throw new URIError("Cannot find the language server module.");
let workPath = path.dirname(serverModule);
console.log(`Use ${serverModule} as server module.`);
console.log(`Work path: ${workPath}.`);
// If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used
let serverOptions: languageClient.ServerOptions = {
run: {
command: "dotnet",
args: [serverModule],
options: { cwd: workPath }
},
debug: {
command: "dotnet",
args: [serverModule, "--debug"],
options: { cwd: workPath }
}
};
// Options to control the language client
let clientOptions: languageClient.LanguageClientOptions = {
// Register the server for plain text documents
documentSelector: [
{
language: "upsilon",
scheme: "file"
}
],
synchronize: {
// Synchronize the setting section 'languageServerExample' to the server
configurationSection: "upsilonLanguageServer",
// Notify the server about file changes to '.clientrc files contain in the workspace
fileEvents: [
vscode.workspace.createFileSystemWatcher("**/.clientrc"),
vscode.workspace.createFileSystemWatcher("**/.yup"),
vscode.workspace.createFileSystemWatcher("**/.lua")
]
}
};
// Create the language client and start the client.
let client = new languageClient.LanguageClient(
"upsilonLanguageServer",
"Upsilon Language Server",
serverOptions,
clientOptions
);
let disposable = client.start();
// Push the disposable to the context's subscriptions so that the
// client can be deactivated on extension deactivation
context.subscriptions.push(disposable);
}
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
console.log("Upsilon extension is now activated.");
activateLanguageServer(context);
}
// this method is called when your extension is deactivated
export function deactivate() {}
6;

12
Client/tsconfig.json Normal file
View File

@ -0,0 +1,12 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "out",
"rootDir": "src",
"lib": ["es6"],
"sourceMap": true
},
"include": ["src"],
"exclude": ["node_modules", ".vscode-test"]
}

@ -0,0 +1 @@
Subproject commit fc3c2f9dae32404e6556cf5bcc0b666fedbc3897

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

@ -0,0 +1,63 @@
{
"runtimeTarget": {
"name": ".NETStandard,Version=v2.0/",
"signature": "7417c30610758d349c0e111cd15f02499f764b67"
},
"compilationOptions": {},
"targets": {
".NETStandard,Version=v2.0": {},
".NETStandard,Version=v2.0/": {
"Upsilon/1.0.0": {
"dependencies": {
"NETStandard.Library": "2.0.3",
"System.Collections.Immutable": "1.5.0"
},
"runtime": {
"Upsilon.dll": {}
}
},
"Microsoft.NETCore.Platforms/1.1.0": {},
"NETStandard.Library/2.0.3": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0"
}
},
"System.Collections.Immutable/1.5.0": {
"runtime": {
"lib/netstandard2.0/System.Collections.Immutable.dll": {
"assemblyVersion": "1.2.3.0",
"fileVersion": "4.6.26515.6"
}
}
}
}
},
"libraries": {
"Upsilon/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Microsoft.NETCore.Platforms/1.1.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==",
"path": "microsoft.netcore.platforms/1.1.0",
"hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512"
},
"NETStandard.Library/2.0.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
"path": "netstandard.library/2.0.3",
"hashPath": "netstandard.library.2.0.3.nupkg.sha512"
},
"System.Collections.Immutable/1.5.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-RGxi2aQoXgZ5ge0zxrKqI4PU9LrYYoLC+cnEnWXKsSduCOUhE1GEAAoTexUVT8RZOILQyy1B27HC8Xw/XLGzdQ==",
"path": "system.collections.immutable/1.5.0",
"hashPath": "system.collections.immutable.1.5.0.nupkg.sha512"
}
}
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UpsilonLanguageServer", "UpsilonLanguageServer\UpsilonLanguageServer.csproj", "{E8C3C18A-F82B-4D8D-9E87-7BE03C0D6AF1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E8C3C18A-F82B-4D8D-9E87-7BE03C0D6AF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E8C3C18A-F82B-4D8D-9E87-7BE03C0D6AF1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E8C3C18A-F82B-4D8D-9E87-7BE03C0D6AF1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E8C3C18A-F82B-4D8D-9E87-7BE03C0D6AF1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpAutoNaming/IsNamingAutoDetectionCompleted/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpAutoNaming/IsNotificationDisabled/@EntryValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using LanguageServer.VsCode.Contracts;
using LanguageServer.VsCode.Server;
using Upsilon;
using Upsilon.Evaluator;
namespace UpsilonLanguageServer
{
public class DiagnosticProvider
{
public DiagnosticProvider()
{
}
public static async Task<IEnumerable<Diagnostic>> LintDocument(TextDocument document, SessionDocument session,
int maxNumberOfProblems)
{
var diag = new List<Diagnostic>();
var content = document.Content;
if (string.IsNullOrWhiteSpace(content))
{
diag.Add(new Diagnostic(DiagnosticSeverity.Hint,
new Range(new Position(0, 0), document.PositionAt(content?.Length ?? 0)),
"DLS", "DLS0001", "Empty document. Try typing something, such as \".net core\"."));
return diag;
}
var task = RealLint(document, maxNumberOfProblems, session, content, diag);
// either wait until the script is linted, or until a second has passed (timeout)
await Task.WhenAny(task, Task.Delay(1000));
return diag;
}
private static async Task RealLint(TextDocument document, int maxNumberOfProblems, SessionDocument session,
string content, List<Diagnostic> diag)
{
try
{
using (var script = new Script(content))
{
session.SourceText = script.ScriptString;
if (script.Diagnostics.Messages.Count > 0)
{
foreach (var error in script.Diagnostics.Messages)
{
diag.Add(ConvertToVsCodeDiagnostic(error));
}
return;
}
var bound = script.Bind();
foreach (var error in script.Diagnostics.Messages)
{
diag.Add(ConvertToVsCodeDiagnostic(error));
}
if (script.Diagnostics.Messages.Count == 0)
{
session.Bound = bound;
}
}
}
catch (Exception e)
{
diag.Add(new Diagnostic(DiagnosticSeverity.Error,
new Range(new Position(0, 0), document.PositionAt(content?.Length ?? 0)),
"DLS", "DLS0001", e.ToString()));
}
}
private static Diagnostic ConvertToVsCodeDiagnostic(DiagnosticsMessage error)
{
var (startPosition, startPositionOnLine) = error.GetStartLine();
var (endPosition, endPositionOnLine) = error.GetEndLine();
var sourceString = error.Diagnostics.ScriptString;
if (endPosition >= sourceString.GetLineCount())
endPosition = sourceString.GetLineCount();
var range = new Range(startPosition, startPositionOnLine, endPosition,
endPositionOnLine);
return new Diagnostic(DiagnosticSeverity.Error, range, error.AtError(), "ERR001", error.Message);
}
}
}

View File

@ -0,0 +1,118 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JsonRpc.DynamicProxy.Client;
using JsonRpc.Standard.Client;
using JsonRpc.Standard.Contracts;
using LanguageServer.VsCode.Contracts;
using LanguageServer.VsCode.Contracts.Client;
using LanguageServer.VsCode.Server;
using Upsilon.Binder;
using Upsilon.Text;
namespace UpsilonLanguageServer
{
public class LanguageServerSession
{
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
public LanguageServerSession(JsonRpcClient rpcClient, IJsonRpcContractResolver contractResolver)
{
RpcClient = rpcClient ?? throw new ArgumentNullException(nameof(rpcClient));
var builder = new JsonRpcProxyBuilder {ContractResolver = contractResolver};
Client = new ClientProxy(builder, rpcClient);
Documents = new ConcurrentDictionary<Uri, SessionDocument>();
DiagnosticProvider = new DiagnosticProvider();
}
public CancellationToken CancellationToken => _cts.Token;
public JsonRpcClient RpcClient { get; }
public ClientProxy Client { get; }
public ConcurrentDictionary<Uri, SessionDocument> Documents { get; }
public DiagnosticProvider DiagnosticProvider { get; }
public LanguageServerSettings Settings { get; set; } = new LanguageServerSettings();
public void StopServer()
{
_cts.Cancel();
}
}
public class SessionDocument
{
/// <summary>
/// Actually makes the changes to the inner document per this milliseconds.
/// </summary>
private const int RenderChangesDelay = 100;
public SessionDocument(TextDocumentItem doc)
{
Document = TextDocument.Load<FullTextDocument>(doc);
}
private Task _updateChangesDelayTask;
private readonly object _syncLock = new object();
private List<TextDocumentContentChangeEvent> _impendingChanges = new List<TextDocumentContentChangeEvent>();
public event EventHandler DocumentChanged;
public TextDocument Document { get; set; }
public BoundScript Bound { get; set; }
public SourceText SourceText { get; set; }
public void NotifyChanges(IEnumerable<TextDocumentContentChangeEvent> changes)
{
lock (_syncLock)
{
if (_impendingChanges == null)
_impendingChanges = changes.ToList();
else
_impendingChanges.AddRange(changes);
}
if (_updateChangesDelayTask == null || _updateChangesDelayTask.IsCompleted)
{
_updateChangesDelayTask = Task.Delay(RenderChangesDelay);
_updateChangesDelayTask.ContinueWith(t => Task.Run((Action)MakeChanges));
}
}
private void MakeChanges()
{
List<TextDocumentContentChangeEvent> localChanges;
lock (_syncLock)
{
localChanges = _impendingChanges;
if (localChanges == null || localChanges.Count == 0) return;
_impendingChanges = null;
}
Document = Document.ApplyChanges(localChanges);
if (_impendingChanges == null)
{
localChanges.Clear();
lock (_syncLock)
{
if (_impendingChanges == null)
_impendingChanges = localChanges;
}
}
OnDocumentChanged();
}
protected virtual void OnDocumentChanged()
{
DocumentChanged?.Invoke(this, EventArgs.Empty);
}
}
}

View File

@ -0,0 +1,104 @@
// #define WAIT_FOR_DEBUGGER
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using JsonRpc.Standard.Client;
using JsonRpc.Standard.Contracts;
using JsonRpc.Standard.Server;
using JsonRpc.Streams;
using LanguageServer.VsCode;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Debug;
namespace UpsilonLanguageServer
{
static class Program
{
static void Main(string[] args)
{
var debugMode = args.Any(a => a.Equals("--debug", StringComparison.OrdinalIgnoreCase));
#if WAIT_FOR_DEBUGGER
while (!Debugger.IsAttached) Thread.Sleep(1000);
Debugger.Break();
#endif
StreamWriter logWriter = null;
if (debugMode)
{
logWriter = File.CreateText("messages-" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".log");
logWriter.AutoFlush = true;
}
using (logWriter)
using (var cin = Console.OpenStandardInput())
using (var bcin = new BufferedStream(cin))
using (var cout = Console.OpenStandardOutput())
using (var reader = new PartwiseStreamMessageReader(bcin))
using (var writer = new PartwiseStreamMessageWriter(cout))
{
var contractResolver = new JsonRpcContractResolver
{
NamingStrategy = new CamelCaseJsonRpcNamingStrategy(),
ParameterValueConverter = new CamelCaseJsonValueConverter(),
};
var clientHandler = new StreamRpcClientHandler();
var client = new JsonRpcClient(clientHandler);
if (debugMode)
{
// We want to capture log all the LSP server-to-client calls as well
clientHandler.MessageSending += (_, e) =>
{
lock (logWriter) logWriter.WriteLine("{0} <C{1}", Utility.GetTimeStamp(), e.Message);
};
clientHandler.MessageReceiving += (_, e) =>
{
lock (logWriter) logWriter.WriteLine("{0} >C{1}", Utility.GetTimeStamp(), e.Message);
};
}
// Configure & build service host
var session = new LanguageServerSession(client, contractResolver);
var host = BuildServiceHost(logWriter, contractResolver, debugMode);
var serverHandler = new StreamRpcServerHandler(host,
StreamRpcServerHandlerOptions.ConsistentResponseSequence |
StreamRpcServerHandlerOptions.SupportsRequestCancellation);
serverHandler.DefaultFeatures.Set(session);
// If we want server to stop, just stop the "source"
using (serverHandler.Attach(reader, writer))
using (clientHandler.Attach(reader, writer))
{
// Wait for the "stop" request.
session.CancellationToken.WaitHandle.WaitOne();
}
logWriter?.WriteLine("Exited");
}
}
private static IJsonRpcServiceHost BuildServiceHost(TextWriter logWriter,
IJsonRpcContractResolver contractResolver, bool debugMode)
{
var loggerFactory = new LoggerFactory();
loggerFactory.AddProvider(new DebugLoggerProvider(null));
var builder = new JsonRpcServiceHostBuilder
{
ContractResolver = contractResolver,
LoggerFactory = loggerFactory
};
builder.UseCancellationHandling();
builder.Register(typeof(Program).GetTypeInfo().Assembly);
if (debugMode)
{
// Log all the client-to-server calls.
builder.Intercept(async (context, next) =>
{
lock (logWriter) logWriter.WriteLine("{0} > {1}", Utility.GetTimeStamp(), context.Request);
await next();
lock (logWriter) logWriter.WriteLine("{0} < {1}", Utility.GetTimeStamp(), context.Response);
});
}
return builder.Build();
}
}
}

View File

@ -0,0 +1,20 @@
using JsonRpc.Standard.Contracts;
using LanguageServer.VsCode.Contracts;
namespace UpsilonLanguageServer.Services
{
[JsonRpcScope(MethodPrefix = "completionItem/")]
public class CompletionItemService : UpsilonLanguageServiceBase
{
// The request is sent from the client to the server to resolve additional information
// for a given completion item.
[JsonRpcMethod(AllowExtensionData = true)]
public CompletionItem Resolve()
{
var item = RequestContext.Request.Parameters.ToObject<CompletionItem>(Utility.CamelCaseJsonSerializer);
// Add a pair of square brackets around the inserted text.
item.InsertText = $"[{item.Label}]";
return item;
}
}
}

View File

@ -0,0 +1,58 @@
using System;
using System.Threading.Tasks;
using JsonRpc.Standard;
using JsonRpc.Standard.Contracts;
using JsonRpc.Standard.Server;
using LanguageServer.VsCode.Contracts;
using Newtonsoft.Json.Linq;
// ReSharper disable UnusedMember.Global
namespace UpsilonLanguageServer.Services
{
public class InitializationService : UpsilonLanguageServiceBase
{
[JsonRpcMethod(AllowExtensionData = true)]
public InitializeResult Initialize(int processId, Uri rootUri, ClientCapabilities capabilities,
JToken initializationOptions = null, string trace = null)
{
return new InitializeResult(new ServerCapabilities
{
HoverProvider = true,
SignatureHelpProvider = new SignatureHelpOptions("()"),
CompletionProvider = new CompletionOptions(true, "."),
TextDocumentSync = new TextDocumentSyncOptions
{
OpenClose = true,
WillSave = true,
Change = TextDocumentSyncKind.Incremental
},
});
}
[JsonRpcMethod(IsNotification = true)]
public async Task Initialized()
{
await Client.Window.ShowMessage(MessageType.Info, "Upsilon Language Server Initialized");
}
[JsonRpcMethod]
public void Shutdown()
{
}
[JsonRpcMethod(IsNotification = true)]
public void Exit()
{
Client.Window.ShowMessage(MessageType.Info, "Upsilon Language Server exited");
Session.StopServer();
}
[JsonRpcMethod("$/cancelRequest", IsNotification = true)]
public void CancelRequest(MessageId id)
{
RequestContext.Features.Get<IRequestCancellationFeature>().TryCancel(id);
}
}
}

View File

@ -0,0 +1,123 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using JsonRpc.Standard.Contracts;
using LanguageServer.VsCode;
using LanguageServer.VsCode.Contracts;
using LanguageServer.VsCode.Server;
using Upsilon.Binder;
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.GetNodeAtPosition(linePos + position.Character);
if (findNode != null)
{
var contents = $"Kind: {findNode.Kind}";
if (findNode is BoundExpression expression)
{
contents += $"\n\nType: {expression.Type}";
}
return new Hover()
{
Contents = contents
};
}
}
}
return new Hover();
}
[JsonRpcMethod]
public SignatureHelp SignatureHelp(TextDocumentIdentifier textDocument, Position position)
{
return new SignatureHelp(new List<SignatureInformation>
{
new SignatureInformation("**Function1**", "Documentation1"),
new SignatureInformation("**Function2** <strong>test</strong>", "Documentation2"),
});
}
[JsonRpcMethod(IsNotification = true)]
public async Task DidOpen(TextDocumentItem textDocument)
{
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 _);
}
private static readonly CompletionItem[] PredefinedCompletionItems =
{
new CompletionItem(".NET", CompletionItemKind.Keyword,
"Keyword1",
"Short for **.NET Framework**, a software framework by Microsoft (possibly its subsets) or later open source .NET Core.",
null),
new CompletionItem(".NET Standard", CompletionItemKind.Keyword,
"Keyword2",
"The .NET Standard is a formal specification of .NET APIs that are intended to be available on all .NET runtimes.",
null),
new CompletionItem(".NET Framework", CompletionItemKind.Keyword,
"Keyword3",
".NET Framework (pronounced dot net) is a software framework developed by Microsoft that runs primarily on Microsoft Windows.", null),
};
[JsonRpcMethod]
public async Task<CompletionList> Completion(TextDocumentIdentifier textDocument, Position position, CompletionContext context)
{
return new CompletionList(PredefinedCompletionItems, false);
}
}
}

View File

@ -0,0 +1,24 @@
using System;
using JsonRpc.Standard.Server;
using LanguageServer.VsCode.Contracts;
using LanguageServer.VsCode.Contracts.Client;
using LanguageServer.VsCode.Server;
namespace UpsilonLanguageServer.Services
{
public class UpsilonLanguageServiceBase : JsonRpcService
{
protected LanguageServerSession Session => RequestContext.Features.Get<LanguageServerSession>();
protected ClientProxy Client => Session.Client;
protected TextDocument GetDocument(Uri uri)
{
if (Session.Documents.TryGetValue(uri, out var sd))
return sd.Document;
return null;
}
protected TextDocument GetDocument(TextDocumentIdentifier id) => GetDocument(id.Uri);
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using JsonRpc.Standard.Contracts;
using LanguageServer.VsCode.Contracts;
namespace UpsilonLanguageServer.Services
{
[JsonRpcScope(MethodPrefix = "workspace/")]
public class WorkspaceService : UpsilonLanguageServiceBase
{
[JsonRpcMethod(IsNotification = true)]
public async Task DidChangeConfiguration(SettingsRoot settings)
{
Session.Settings = settings.UpsilonLanguageServer;
foreach (var doc in Session.Documents.Values)
{
var diag = await DiagnosticProvider.LintDocument(doc.Document, doc, Session.Settings.MaxNumberOfProblems);
await Client.Document.PublishDiagnostics(doc.Document.Uri, diag);
}
}
[JsonRpcMethod(IsNotification = true)]
public async Task DidChangeWatchedFiles(ICollection<FileEvent> changes)
{
foreach (var change in changes)
{
if (!change.Uri.IsFile) continue;
var localPath = change.Uri.AbsolutePath;
if (string.Equals(Path.GetExtension(localPath), ".yup", StringComparison.InvariantCultureIgnoreCase))
{
// If the file has been removed, we will clear the lint result about it.
// Note that pass null to PublishDiagnostics may mess up the client.
if (change.Type == FileChangeType.Deleted)
{
await Client.Document.PublishDiagnostics(change.Uri, new Diagnostic[0]);
}
}
else if (string.Equals(Path.GetExtension(localPath), ".lua", StringComparison.InvariantCultureIgnoreCase))
{
// If the file has been removed, we will clear the lint result about it.
// Note that pass null to PublishDiagnostics may mess up the client.
if (change.Type == FileChangeType.Deleted)
{
await Client.Document.PublishDiagnostics(change.Uri, new Diagnostic[0]);
}
}
}
}
}
}

View File

@ -0,0 +1,18 @@
namespace UpsilonLanguageServer
{
public class SettingsRoot
{
public LanguageServerSettings UpsilonLanguageServer { get; set; }
}
public class LanguageServerSettings
{
public int MaxNumberOfProblems { get; set; } = 10;
public LanguageServerTraceSettings Trace { get; } = new LanguageServerTraceSettings();
}
public class LanguageServerTraceSettings
{
public string Server { get; set; }
}}

View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CXuesong.JsonRpc.Streams" Version="0.4.2" />
<PackageReference Include="CXuesong.LanguageServer.VsCode" Version="0.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.TraceSource" Version="2.1.1" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Collections.Immutable, Version=1.2.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\Lib\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="Upsilon, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..\Lib\Upsilon.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,19 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace UpsilonLanguageServer
{
public static class Utility
{
public static readonly JsonSerializer CamelCaseJsonSerializer = new JsonSerializer
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
public static string GetTimeStamp()
{
return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
}
}
}