[closes gh-39] Committing completion with '(' will now trigger signature help.

This commit is contained in:
Andrey Shchekin 2016-12-30 15:04:48 +13:00
Родитель feb0f6268a
Коммит 8b6c592e25
13 изменённых файлов: 97 добавлений и 48 удалений

Просмотреть файл

@ -20,7 +20,7 @@ namespace MirrorSharp.Benchmarks {
public void Setup() {
_sessionWithHelp = TestHelper.SessionFromTextWithCursor("class C { void M(int a) { M| } }");
_sessionWithNoHelp = TestHelper.SessionFromTextWithCursor("class C { void M(int a) { M()| } }");
_handler = new TypeCharHandler(new CompletionSupport(), new SignatureHelpSupport());
_handler = new TypeCharHandler(new TypedCharEffects(new CompletionSupport(), new SignatureHelpSupport()));
}
[Benchmark]

Просмотреть файл

@ -35,15 +35,16 @@ namespace MirrorSharp.Advanced {
internal IReadOnlyCollection<ICommandHandler> CreateHandlers() {
var completion = new CompletionSupport();
var signatureHelp = new SignatureHelpSupport();
var typedCharEffects = new TypedCharEffects(completion, signatureHelp);
return new ICommandHandler[] {
new ApplyDiagnosticActionHandler(),
new CompletionStateHandler(completion),
new MoveCursorHandler(signatureHelp),
new ReplaceTextHandler(signatureHelp, completion),
new ReplaceTextHandler(signatureHelp, completion, typedCharEffects),
new RequestSelfDebugDataHandler(),
new SetOptionsHandler(_languages, _options?.SetOptionsFromClient),
new SlowUpdateHandler(_options?.SlowUpdate),
new TypeCharHandler(completion, signatureHelp)
new TypeCharHandler(typedCharEffects)
};
}

Просмотреть файл

@ -10,6 +10,12 @@ namespace MirrorSharp.Internal {
public CompletionService Service { get; }
[CanBeNull] public CompletionList CurrentList { get; set; }
public bool ChangeEchoPending { get; set; }
public CompletionTrigger? PendingTrigger { get; set; }
public char? PendingChar { get; set; }
public void ResetPending() {
ChangeEchoPending = false;
PendingChar = null;
}
}
}

Просмотреть файл

@ -16,13 +16,13 @@ namespace MirrorSharp.Internal.Handlers {
public Task ExecuteAsync(ArraySegment<byte> data, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
var first = data.Array[data.Offset];
if (first == (byte)'X')
return _completion.ApplyCompletionCancellationAsync(session, sender, cancellationToken);
return _completion.CancelCompletionAsync(session, sender, cancellationToken);
if (first == (byte)'F')
return _completion.ApplyCompletionForceAsync(session, sender, cancellationToken);
return _completion.ForceCompletionAsync(session, sender, cancellationToken);
var itemIndex = FastConvert.Utf8ByteArrayToInt32(data);
return _completion.ApplyCompletionSelectionAsync(itemIndex, session, sender, cancellationToken);
return _completion.SelectCompletionAsync(itemIndex, session, sender, cancellationToken);
}
}
}

Просмотреть файл

@ -12,10 +12,12 @@ namespace MirrorSharp.Internal.Handlers {
public char CommandId => 'R';
[NotNull] private readonly ISignatureHelpSupport _signatureHelp;
[NotNull] private readonly ICompletionSupport _completion;
[NotNull] private readonly ITypedCharEffects _typedCharEffects;
public ReplaceTextHandler([NotNull] ISignatureHelpSupport signatureHelp, [NotNull] ICompletionSupport completion) {
public ReplaceTextHandler([NotNull] ISignatureHelpSupport signatureHelp, [NotNull] ICompletionSupport completion, [NotNull] ITypedCharEffects typedCharEffects) {
_signatureHelp = signatureHelp;
_completion = completion;
_typedCharEffects = typedCharEffects;
}
public async Task ExecuteAsync(ArraySegment<byte> data, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
@ -25,7 +27,7 @@ namespace MirrorSharp.Internal.Handlers {
int? start = null;
int? length = null;
int? cursorPosition = null;
string trigger = null;
string reason = null;
for (var i = data.Offset; i <= endOffset; i++) {
if (data.Array[i] != (byte)':')
@ -50,19 +52,19 @@ namespace MirrorSharp.Internal.Handlers {
continue;
}
trigger = part.Count > 0 ? Encoding.UTF8.GetString(part.Array, part.Offset, part.Count) : string.Empty;
reason = part.Count > 0 ? Encoding.UTF8.GetString(part.Array, part.Offset, part.Count) : string.Empty;
partStart = i + 1;
break;
}
if (start == null || length == null || cursorPosition == null || trigger == null)
throw new FormatException("Command arguments must be 'start:length:cursor:trigger:text'.");
if (start == null || length == null || cursorPosition == null || reason == null)
throw new FormatException("Command arguments must be 'start:length:cursor:reason: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;
await _signatureHelp.ApplyCursorPositionChangeAsync(session, sender, cancellationToken).ConfigureAwait(false);
await _completion.ApplyReplacedTextAsync(trigger, session, sender, cancellationToken).ConfigureAwait(false);
await _completion.ApplyReplacedTextAsync(reason, _typedCharEffects, session, sender, cancellationToken).ConfigureAwait(false);
}
}
}

Просмотреть файл

@ -14,28 +14,27 @@ namespace MirrorSharp.Internal.Handlers.Shared {
if (session.Completion.CurrentList != null)
return TaskEx.CompletedTask;
var trigger = CompletionTrigger.CreateInsertionTrigger(@char);
if (session.Completion.ChangeEchoPending) {
session.Completion.PendingTrigger = trigger;
session.Completion.PendingChar = @char;
return TaskEx.CompletedTask;
}
var trigger = CompletionTrigger.CreateInsertionTrigger(@char);
return CheckCompletionAsync(trigger, session, sender, cancellationToken);
}
public Task ApplyReplacedTextAsync(string reason, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
public Task ApplyReplacedTextAsync(string reason, ITypedCharEffects typedCharEffects, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
if (reason != ChangeReasonCompletion)
return TaskEx.CompletedTask;
session.Completion.ChangeEchoPending = false;
var pendingTrigger = session.Completion.PendingTrigger;
if (pendingTrigger == null)
var pendingChar = session.Completion.PendingChar;
session.Completion.ResetPending();
if (pendingChar == null)
return TaskEx.CompletedTask;
session.Completion.PendingTrigger = null;
return CheckCompletionAsync(pendingTrigger.Value, session, sender, cancellationToken);
return typedCharEffects.ApplyTypedCharAsync(pendingChar.Value, session, sender, cancellationToken);
}
public async Task ApplyCompletionSelectionAsync(int selectedIndex, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
public async Task SelectCompletionAsync(int selectedIndex, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
// ReSharper disable once PossibleNullReferenceException
var completion = session.Completion.CurrentList;
// ReSharper disable once PossibleNullReferenceException
@ -73,14 +72,13 @@ namespace MirrorSharp.Internal.Handlers.Shared {
return textChanges;
}
public Task ApplyCompletionCancellationAsync(WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
public Task CancelCompletionAsync(WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
session.Completion.ResetPending();
session.Completion.CurrentList = null;
session.Completion.ChangeEchoPending = false;
session.Completion.PendingTrigger = null;
return TaskEx.CompletedTask;
}
public Task ApplyCompletionForceAsync(WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
public Task ForceCompletionAsync(WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
return TriggerCompletionAsync(session, sender, cancellationToken, CompletionTrigger.Default);
}
@ -96,9 +94,8 @@ namespace MirrorSharp.Internal.Handlers.Shared {
if (completionList == null)
return;
session.Completion.ResetPending();
session.Completion.CurrentList = completionList;
session.Completion.ChangeEchoPending = false;
session.Completion.PendingTrigger = null;
await SendCompletionListAsync(completionList, sender, cancellationToken).ConfigureAwait(false);
}

Просмотреть файл

@ -1,13 +1,14 @@
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using MirrorSharp.Internal.Results;
namespace MirrorSharp.Internal.Handlers.Shared {
internal interface ICompletionSupport {
Task ApplyTypedCharAsync(char @char, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken);
Task ApplyReplacedTextAsync(string reason, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken);
Task ApplyCompletionSelectionAsync(int selectedIndex, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken);
Task ApplyCompletionCancellationAsync(WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken);
Task ApplyCompletionForceAsync(WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken);
[NotNull] Task ApplyTypedCharAsync(char @char, [NotNull] WorkSession session, [NotNull] ICommandResultSender sender, CancellationToken cancellationToken);
[NotNull] Task ApplyReplacedTextAsync(string reason, [NotNull] ITypedCharEffects typedCharEffects, [NotNull] WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken);
[NotNull] Task SelectCompletionAsync(int selectedIndex, [NotNull] WorkSession session, [NotNull] ICommandResultSender sender, CancellationToken cancellationToken);
[NotNull] Task CancelCompletionAsync([NotNull] WorkSession session, [NotNull] ICommandResultSender sender, CancellationToken cancellationToken);
[NotNull] Task ForceCompletionAsync([NotNull] WorkSession session, [NotNull] ICommandResultSender sender, CancellationToken cancellationToken);
}
}

Просмотреть файл

@ -0,0 +1,10 @@
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using MirrorSharp.Internal.Results;
namespace MirrorSharp.Internal.Handlers.Shared {
internal interface ITypedCharEffects {
[NotNull] Task ApplyTypedCharAsync(char @char, [NotNull] WorkSession session, [NotNull] ICommandResultSender sender, CancellationToken cancellationToken);
}
}

Просмотреть файл

@ -0,0 +1,21 @@
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using MirrorSharp.Internal.Results;
namespace MirrorSharp.Internal.Handlers.Shared {
internal class TypedCharEffects : ITypedCharEffects {
private readonly ICompletionSupport _completion;
private readonly ISignatureHelpSupport _signatureHelp;
public TypedCharEffects([NotNull] ICompletionSupport completion, [NotNull] ISignatureHelpSupport signatureHelp) {
_completion = completion;
_signatureHelp = signatureHelp;
}
public async Task ApplyTypedCharAsync(char @char, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
await _completion.ApplyTypedCharAsync(@char, session, sender, cancellationToken).ConfigureAwait(false);
await _signatureHelp.ApplyTypedCharAsync(@char, session, sender, cancellationToken).ConfigureAwait(false);
}
}
}

Просмотреть файл

@ -9,23 +9,20 @@ using MirrorSharp.Internal.Results;
namespace MirrorSharp.Internal.Handlers {
internal class TypeCharHandler : ICommandHandler {
public char CommandId => 'C';
[NotNull] private readonly ICompletionSupport _completion;
[NotNull] private readonly ISignatureHelpSupport _signatureHelp;
[NotNull] private readonly ITypedCharEffects _effects;
public TypeCharHandler([NotNull] ICompletionSupport completion, [NotNull] ISignatureHelpSupport signatureHelp) {
_completion = completion;
_signatureHelp = signatureHelp;
public TypeCharHandler([NotNull] ITypedCharEffects effects) {
_effects = effects;
}
public async Task ExecuteAsync(ArraySegment<byte> data, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
public Task ExecuteAsync(ArraySegment<byte> data, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) {
var @char = FastConvert.Utf8ByteArrayToChar(data);
session.SourceText = session.SourceText.WithChanges(
new TextChange(new TextSpan(session.CursorPosition, 0), FastConvert.CharToString(@char))
);
session.CursorPosition += 1;
await _completion.ApplyTypedCharAsync(@char, session, sender, cancellationToken).ConfigureAwait(false);
await _signatureHelp.ApplyTypedCharAsync(@char, session, sender, cancellationToken).ConfigureAwait(false);
return _effects.ApplyTypedCharAsync(@char, session, sender, cancellationToken);
}
}
}

Просмотреть файл

@ -1,5 +1,4 @@
using System;
using System.Linq;
using System.Linq;
using System.Threading.Tasks;
using MirrorSharp.Internal;
using MirrorSharp.Internal.Handlers;

Просмотреть файл

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Text;

Просмотреть файл

@ -67,14 +67,30 @@ namespace MirrorSharp.Tests {
var newPosition = session.CursorPosition + ("int.".Length - "in.".Length);
var result = await ExecuteHandlerAsync<ReplaceTextHandler, CompletionsResult>(
session, Argument(session.CursorPosition - "in.".Length, "in".Length, "int", newPosition, trigger: "completion")
session, Argument(session.CursorPosition - "in.".Length, "in".Length, "int", newPosition, reason: "completion")
);
Assert.NotNull(result);
Assert.Contains(nameof(int.Parse), result.Completions.Select(c => c.DisplayText));
}
[Fact]
private HandlerTestArgument Argument(int start, int length, string newText, int newCursorPosition, string trigger = "") {
return $"{start}:{length}:{newCursorPosition}:{trigger}:{newText}";
public async Task ExecuteAsync_ProducesSignatureHelp_WhenCalledAfterCommitCharThatWouldHaveProducedIt() {
var session = SessionFromTextWithCursor(@"class C { void M() { int.| } }");
await TypeCharsAsync(session, "Pars");
await ExecuteHandlerAsync<CompletionStateHandler>(session, 1); // would complete "Parse" after echo
await TypeCharsAsync(session, "("); // this was the commit char, happens *before* echo
var newPosition = session.CursorPosition + ("Parse(".Length - "Pars(".Length);
var result = await ExecuteHandlerAsync<ReplaceTextHandler, SignaturesResult>(
session, Argument(session.CursorPosition - "Pars(".Length, "Pars".Length, "Parse", newPosition, reason: "completion")
);
var signature = result.Signatures.First(s => s.Selected);
Assert.Equal("int int.Parse(*string s*)", signature.ToString());
}
private HandlerTestArgument Argument(int start, int length, string newText, int newCursorPosition, string reason = "") {
return $"{start}:{length}:{newCursorPosition}:{reason}:{newText}";
}
}
}