[closes gh-39] Committing completion with '(' will now trigger signature help.
This commit is contained in:
Родитель
feb0f6268a
Коммит
8b6c592e25
|
@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче