Initial commit

This commit is contained in:
2018-11-25 22:19:02 +01:00
commit 474bd3c2f7
27 changed files with 3175 additions and 0 deletions

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");
}
}
}