Skip to content

Commit

Permalink
Rewritten command handling to separate responsibilities.
Browse files Browse the repository at this point in the history
  • Loading branch information
ashmind committed Sep 26, 2016
1 parent 627dfec commit 146381d
Show file tree
Hide file tree
Showing 27 changed files with 590 additions and 432 deletions.
25 changes: 23 additions & 2 deletions MirrorSharp.Common/Advanced/MiddlewareBase.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,43 @@
using System.Net.WebSockets;
using System.Collections.Immutable;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using MirrorSharp.Internal;
using MirrorSharp.Internal.Commands;

namespace MirrorSharp.Advanced {
public abstract class MiddlewareBase {
private ImmutableArray<ICommandHandler> _commands;

private readonly MirrorSharpOptions _options;

protected MiddlewareBase(MirrorSharpOptions options) {
_options = options;
_commands = CreateCommands();
}

private ImmutableArray<ICommandHandler> CreateCommands() {
var commands = new ICommandHandler[26];
foreach (var command in new ICommandHandler[] {
new CommitCompletionHandler(),
new MoveCursorHandler(),
new ReplaceTextHandler(),
new SlowUpdateHandler(),
new TypeCharHandler()
}) {
foreach (var id in command.CommandIds) {
commands[id - 'A'] = command;
}
}
return ImmutableArray.CreateRange(commands);
}

protected async Task WebSocketLoopAsync(WebSocket socket, CancellationToken cancellationToken) {
WorkSession session = null;
Connection connection = null;
try {
session = new WorkSession();
connection = new Connection(socket, session, _options);
connection = new Connection(socket, session, _commands, _options);

while (connection.IsConnected) {
try {
Expand Down
5 changes: 5 additions & 0 deletions MirrorSharp.Common/Internal/Buffers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace MirrorSharp.Internal {
public class Buffers {
public char[] CharArray { get; } = new char[2048];
}
}
31 changes: 31 additions & 0 deletions MirrorSharp.Common/Internal/Commands/CommitCompletionHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;

namespace MirrorSharp.Internal.Commands {
public class CommitCompletionHandler : ICommandHandler {
public IImmutableList<char> CommandIds { get; } = ImmutableList.Create('S');

public async Task ExecuteAsync(ArraySegment<byte> data, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
var itemIndex = FastConvert.Utf8ByteArrayToInt32(data);
// ReSharper disable once PossibleNullReferenceException
var item = session.CurrentCompletionList.Items[itemIndex];
var change = await session.CompletionService.GetChangeAsync(session.Document, item, cancellationToken: cancellationToken).ConfigureAwait(false);

var writer = sender.StartJsonMessage("changes");
writer.WritePropertyStartArray("changes");
foreach (var textChange in change.TextChanges) {
writer.WriteStartObject();
writer.WriteProperty("start", textChange.Span.Start);
writer.WriteProperty("length", textChange.Span.Length);
writer.WriteProperty("text", textChange.NewText);
writer.WriteEndObject();
}
writer.WriteEndArray();
await sender.SendJsonMessageAsync(cancellationToken).ConfigureAwait(false);
}

public bool CanChangeSession => false;
}
}
13 changes: 13 additions & 0 deletions MirrorSharp.Common/Internal/Commands/ICommandHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;

namespace MirrorSharp.Internal.Commands {
public interface ICommandHandler {
[NotNull] IImmutableList<char> CommandIds { get; }
[NotNull] Task ExecuteAsync(ArraySegment<byte> data, [NotNull] WorkSession session, [NotNull] ICommandResultSender sender, CancellationToken cancellationToken);
bool CanChangeSession { get; }
}
}
10 changes: 10 additions & 0 deletions MirrorSharp.Common/Internal/Commands/ICommandResultSender.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace MirrorSharp.Internal.Commands {
public interface ICommandResultSender {
JsonWriter StartJsonMessage(string messageTypeName);
Task SendJsonMessageAsync(CancellationToken cancellationToken);
}
}
17 changes: 17 additions & 0 deletions MirrorSharp.Common/Internal/Commands/MoveCursorHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;

namespace MirrorSharp.Internal.Commands {
public class MoveCursorHandler : ICommandHandler {
public IImmutableList<char> CommandIds => ImmutableList.Create('M');

public Task ExecuteAsync(ArraySegment<byte> data, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
session.CursorPosition = FastConvert.Utf8ByteArrayToInt32(data);
return TaskEx.CompletedTask;
}

public bool CanChangeSession => true;
}
}
53 changes: 53 additions & 0 deletions MirrorSharp.Common/Internal/Commands/ReplaceTextHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Collections.Immutable;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Text;

namespace MirrorSharp.Internal.Commands {
public class ReplaceTextHandler : ICommandHandler {
public IImmutableList<char> CommandIds { get; } = ImmutableList.Create('P', 'R');

public Task ExecuteAsync(ArraySegment<byte> data, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
var endOffset = data.Offset + data.Count - 1;
var partStart = data.Offset;

int? start = null;
int? length = null;
int? cursorPosition = null;

for (var i = data.Offset; i <= endOffset; i++) {
if (data.Array[i] != (byte)':')
continue;

var part = new ArraySegment<byte>(data.Array, partStart, i - partStart);
if (start == null) {
start = FastConvert.Utf8ByteArrayToInt32(part);
partStart = i + 1;
continue;
}

if (length == null) {
length = FastConvert.Utf8ByteArrayToInt32(part);
partStart = i + 1;
continue;
}

cursorPosition = FastConvert.Utf8ByteArrayToInt32(part);
partStart = i + 1;
break;
}
if (start == null || length == null || cursorPosition == null)
throw new FormatException("Command arguments must be 'start:length:cursor:text'.");

var text = Encoding.UTF8.GetString(data.Array, partStart, endOffset - partStart + 1);

session.SourceText = session.SourceText.WithChanges(new TextChange(new TextSpan(start.Value, length.Value), text));
session.CursorPosition = cursorPosition.Value;
return TaskEx.CompletedTask;
}

public bool CanChangeSession => true;
}
}
43 changes: 43 additions & 0 deletions MirrorSharp.Common/Internal/Commands/SlowUpdateHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

namespace MirrorSharp.Internal.Commands {
public class SlowUpdateHandler : ICommandHandler {
public IImmutableList<char> CommandIds { get; } = ImmutableList.Create('U');

public async Task ExecuteAsync(ArraySegment<byte> data, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
var compilation = await session.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var diagnostics = await compilation.WithAnalyzers(session.Analyzers).GetAllDiagnosticsAsync(cancellationToken).ConfigureAwait(false);

await SendSlowUpdateAsync(diagnostics, sender, cancellationToken).ConfigureAwait(false);
}

private Task SendSlowUpdateAsync(ImmutableArray<Diagnostic> diagnostics, ICommandResultSender sender, CancellationToken cancellationToken) {
var writer = sender.StartJsonMessage("slowUpdate");
writer.WritePropertyStartArray("diagnostics");
foreach (var diagnostic in diagnostics) {
writer.WriteStartObject();
writer.WriteProperty("message", diagnostic.GetMessage());
writer.WriteProperty("severity", diagnostic.Severity.ToString("G").ToLowerInvariant());
writer.WritePropertyStartArray("tags");
foreach (var tag in diagnostic.Descriptor.CustomTags) {
if (tag != WellKnownDiagnosticTags.Unnecessary)
continue;
writer.WriteValue(tag.ToLowerInvariant());
}
writer.WriteEndArray();
writer.WritePropertyName("span");
writer.WriteSpan(diagnostic.Location.SourceSpan);
writer.WriteEndObject();
}
writer.WriteEndArray();
return sender.SendJsonMessageAsync(cancellationToken);
}

public bool CanChangeSession => false;
}
}
57 changes: 57 additions & 0 deletions MirrorSharp.Common/Internal/Commands/TypeCharHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Text;

namespace MirrorSharp.Internal.Commands {
public class TypeCharHandler : ICommandHandler {
public IImmutableList<char> CommandIds { get; } = ImmutableList.Create('C');

public async Task ExecuteAsync(ArraySegment<byte> data, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
var @char = FastConvert.Utf8ByteArrayToChar(data, session.Buffers.CharArray);
session.SourceText = session.SourceText.WithChanges(
new TextChange(new TextSpan(session.CursorPosition, 0), FastConvert.CharToString(@char))
);
session.CursorPosition += 1;

if (!session.CompletionService.ShouldTriggerCompletion(session.SourceText, session.CursorPosition, CompletionTrigger.CreateInsertionTrigger(@char)))
return;

session.CurrentCompletionList = await session.CompletionService.GetCompletionsAsync(session.Document, session.CursorPosition, cancellationToken: cancellationToken).ConfigureAwait(false);
if (session.CurrentCompletionList == null)
return;

await SendListAsync(session.CurrentCompletionList, sender, cancellationToken).ConfigureAwait(false);
}

private Task SendListAsync(CompletionList completionList, ICommandResultSender sender, CancellationToken cancellationToken) {
var writer = sender.StartJsonMessage("completions");
writer.WritePropertyStartObject("completions");
writer.WritePropertyName("span");
// ReSharper disable once PossibleNullReferenceException
writer.WriteSpan(completionList.DefaultSpan);
writer.WritePropertyStartArray("list");
foreach (var item in completionList.Items) {
writer.WriteStartObject();
writer.WriteProperty("displayText", item.DisplayText);
writer.WritePropertyStartArray("tags");
foreach (var tag in item.Tags) {
writer.WriteValue(tag.ToLowerInvariant());
}
writer.WriteEndArray();
if (item.Span != completionList.DefaultSpan) {
writer.WritePropertyName("span");
writer.WriteSpan(item.Span);
}
writer.WriteEndObject();
}
writer.WriteEndArray();
writer.WriteEndObject();
return sender.SendJsonMessageAsync(cancellationToken);
}

public bool CanChangeSession => true;
}
}
Loading

0 comments on commit 146381d

Please sign in to comment.