Skip to content

Commit

Permalink
(GH-70) Initial implementation of Language Server
Browse files Browse the repository at this point in the history
- This still needs some work, but it is at least working now
- Single rule for templated values has been implemented
- Question remains over how to get server into file system
- Question remains over sending entire file to server
  • Loading branch information
gep13 committed Feb 13, 2019
1 parent a32e589 commit 2a7d188
Show file tree
Hide file tree
Showing 10 changed files with 588 additions and 5 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ yarn.lock
# License file copied to chocolatey directory
chocolatey/tools/LICENSE.txt
config.wyam.hash

[Bb]in/
[Oo]bj/
32 changes: 32 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@
"onCommand:chocolatey.pack",
"onCommand:chocolatey.delete",
"onCommand:chocolatey.push",
"onCommand:chocolatey.installTemplates"
"onCommand:chocolatey.installTemplates",
"workspaceContains:**/*.nuspec"
],
"engines": {
"vscode": "^1.24.0"
"vscode": "^1.30.0"
},
"categories": [
"Other"
Expand Down Expand Up @@ -127,7 +128,9 @@
"tslint": "^5.12.1",
"typemoq": "^2.1.0",
"typescript": "^3.3.3",
"vscode": "^1.1.29"
"vscode": "^1.1.29",
"vscode-languageclient": "^5.2.1",
"vscode-jsonrpc": "^4.0.0"
},
"dependencies": {
"xml2js": "^0.4.19"
Expand Down
40 changes: 40 additions & 0 deletions src/Chocolatey.Language.Server/BufferManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Collections.Concurrent;
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
using Buffer = Microsoft.Language.Xml.Buffer;

namespace Chocolatey.Language.Server
{
public class BufferManager
{
public EventHandler<DocumentUpdatedEventArgs> BufferUpdated;

public BufferManager()
{
}

private ConcurrentDictionary<Uri, Buffer> _buffers = new ConcurrentDictionary<Uri, Buffer>();

public void UpdateBuffer(Uri uri, Buffer buffer)
{
_buffers.AddOrUpdate(uri, buffer, (k, v) => buffer);

BufferUpdated?.Invoke(this, new DocumentUpdatedEventArgs(uri));
}

public Buffer GetBuffer(Uri uri)
{
return _buffers.TryGetValue(uri, out var buffer) ? buffer : null;
}
}

public class DocumentUpdatedEventArgs : EventArgs
{
public Uri Uri { get; }

public DocumentUpdatedEventArgs(Uri uri)
{
Uri = uri;
}
}
}
14 changes: 14 additions & 0 deletions src/Chocolatey.Language.Server/Chocolatey.Language.Server.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="GuiLabs.Language.Xml" Version="1.2.35" />
<PackageReference Include="OmniSharp.Extensions.LanguageServer" Version="0.11.3" />
</ItemGroup>

</Project>
68 changes: 68 additions & 0 deletions src/Chocolatey.Language.Server/DiagnosticsHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Schema;
using Microsoft.Language.Xml;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
using Buffer = Microsoft.Language.Xml.Buffer;
using DiagnosticSeverity = OmniSharp.Extensions.LanguageServer.Protocol.Models.DiagnosticSeverity;

namespace Chocolatey.Language.Server
{
public class DiagnosticsHandler
{
private readonly ILanguageServer _router;
private readonly BufferManager _bufferManager;
private static readonly IReadOnlyCollection<string> TemplatedValues = new []
{
"__replace",
"space_separated",
"tag1"
};

public DiagnosticsHandler(ILanguageServer router, BufferManager bufferManager)
{
_router = router;
_bufferManager = bufferManager;
}

public void PublishDiagnostics(Uri uri, Buffer buffer)
{
var text = buffer.GetText(0, buffer.Length);
var syntaxTree = Parser.Parse(buffer);
var textPositions = new TextPositions(text);
var diagnostics = new List<Diagnostic>();

diagnostics.AddRange(NuspecDoesNotContainTemplatedValuesRequirement(syntaxTree, textPositions));

_router.Document.PublishDiagnostics(new PublishDiagnosticsParams
{
Uri = uri,
Diagnostics = diagnostics
});
}

private IEnumerable<Diagnostic> NuspecDoesNotContainTemplatedValuesRequirement(XmlDocumentSyntax syntaxTree, TextPositions textPositions)
{
foreach (var node in syntaxTree.DescendantNodesAndSelf().OfType<XmlTextSyntax>())
{
if (!TemplatedValues.Any(x => node.Value.Contains(x, StringComparison.OrdinalIgnoreCase)))
{
continue;
}

var range = textPositions.GetRange(node.Start, node.End);

yield return new Diagnostic {
Message = "Templated value which should be removed",
Severity = DiagnosticSeverity.Error,
Range = range
};
}
}
}
}
43 changes: 43 additions & 0 deletions src/Chocolatey.Language.Server/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OmniSharp.Extensions.LanguageServer.Server;

namespace Chocolatey.Language.Server
{
class Program
{
static async Task Main(string[] args)
{
var options = new LanguageServerOptions()
.WithInput(Console.OpenStandardInput())
.WithOutput(Console.OpenStandardOutput())
.WithLoggerFactory(new LoggerFactory())
.AddDefaultLoggingProvider()
.WithMinimumLogLevel(LogLevel.Trace)
.WithServices(ConfigureServices)
.WithHandler<TextDocumentSyncHandler>()
.OnInitialize((s, _) => {
var serviceProvider = (s as LanguageServer).Services;
var bufferManager = serviceProvider.GetService<BufferManager>();
var diagnosticsHandler = serviceProvider.GetService<DiagnosticsHandler>();
// Hook up diagnostics
bufferManager.BufferUpdated += (__, x) => diagnosticsHandler.PublishDiagnostics(x.Uri, bufferManager.GetBuffer(x.Uri));
return Task.CompletedTask;
});

var server = await LanguageServer.From(options);

await server.WaitForExit;
}

static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<BufferManager>();
services.AddSingleton<DiagnosticsHandler>();
}
}
}
103 changes: 103 additions & 0 deletions src/Chocolatey.Language.Server/TextDocumentSyncHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Schema;
using Microsoft.Language.Xml;
using OmniSharp.Extensions.Embedded.MediatR;
using OmniSharp.Extensions.LanguageServer.Protocol;
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
using OmniSharp.Extensions.LanguageServer.Protocol.Server.Capabilities;
using DiagnosticSeverity = OmniSharp.Extensions.LanguageServer.Protocol.Models.DiagnosticSeverity;

namespace Chocolatey.Language.Server
{
public class TextDocumentSyncHandler : ITextDocumentSyncHandler
{
private readonly ILanguageServer _router;
private readonly BufferManager _bufferManager;

private readonly DocumentSelector _documentSelector = new DocumentSelector(
new DocumentFilter()
{
Pattern = "**/*.nuspec"
}
);

private SynchronizationCapability _capability;

public TextDocumentSyncHandler(ILanguageServer router, BufferManager bufferManager)
{
_router = router;
_bufferManager = bufferManager;
}

public TextDocumentSyncKind Change { get; } = TextDocumentSyncKind.Full;

public TextDocumentChangeRegistrationOptions GetRegistrationOptions()
{
return new TextDocumentChangeRegistrationOptions()
{
DocumentSelector = _documentSelector,
SyncKind = Change
};
}

public TextDocumentAttributes GetTextDocumentAttributes(Uri uri)
{
return new TextDocumentAttributes(uri, "xml");
}

public Task<Unit> Handle(DidChangeTextDocumentParams request, CancellationToken cancellationToken)
{
var uri = request.TextDocument.Uri;
var text = request.ContentChanges.FirstOrDefault()?.Text;

_bufferManager.UpdateBuffer(uri, new StringBuffer(text));
return Unit.Task;
}

public Task<Unit> Handle(DidOpenTextDocumentParams request, CancellationToken cancellationToken)
{
_bufferManager.UpdateBuffer(request.TextDocument.Uri, new StringBuffer(request.TextDocument.Text));
return Unit.Task;
}

public Task<Unit> Handle(DidCloseTextDocumentParams request, CancellationToken cancellationToken)
{
return Unit.Task;
}

public Task<Unit> Handle(DidSaveTextDocumentParams request, CancellationToken cancellationToken)
{
return Unit.Task;
}

public void SetCapability(SynchronizationCapability capability)
{
_capability = capability;
}

TextDocumentRegistrationOptions IRegistration<TextDocumentRegistrationOptions>.GetRegistrationOptions()
{
return new TextDocumentRegistrationOptions()
{
DocumentSelector = _documentSelector
};
}

TextDocumentSaveRegistrationOptions IRegistration<TextDocumentSaveRegistrationOptions>.GetRegistrationOptions()
{
return new TextDocumentSaveRegistrationOptions()
{
DocumentSelector = _documentSelector,
IncludeText = default
};
}
}
}
Loading

0 comments on commit 2a7d188

Please sign in to comment.