Simplified provisional completion implementation (#1749)

This commit is contained in:
Ajay Bhargav B 2020-04-02 14:09:43 -07:00 коммит произвёл GitHub
Родитель 4c8dbd0beb
Коммит 43850ca22f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
16 изменённых файлов: 118 добавлений и 168 удалений

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

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Text;
@ -12,11 +11,8 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
internal class CSharpVirtualDocument : VirtualDocument
{
private long? _hostDocumentSyncVersion;
private CSharpVirtualDocumentSnapshot _previousSnapshot;
private CSharpVirtualDocumentSnapshot _currentSnapshot;
private bool _hasProvisionalChanges = false;
public CSharpVirtualDocument(Uri uri, ITextBuffer textBuffer)
{
if (uri is null)
@ -31,7 +27,6 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
Uri = uri;
TextBuffer = textBuffer;
_previousSnapshot = null;
_currentSnapshot = UpdateSnapshot();
}
@ -43,7 +38,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
public override VirtualDocumentSnapshot CurrentSnapshot => _currentSnapshot;
public override VirtualDocumentSnapshot Update(IReadOnlyList<TextChange> changes, long hostDocumentVersion, bool provisional = false)
public override VirtualDocumentSnapshot Update(IReadOnlyList<TextChange> changes, long hostDocumentVersion)
{
if (changes is null)
{
@ -52,16 +47,13 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
_hostDocumentSyncVersion = hostDocumentVersion;
TryRevertProvisionalChanges();
_hasProvisionalChanges = provisional;
if (changes.Count == 0)
{
_currentSnapshot = UpdateSnapshot();
return _currentSnapshot;
}
using var edit = TextBuffer.CreateEdit();
using var edit = TextBuffer.CreateEdit(EditOptions.None, reiteratedVersionNumber: null, InviolableEditTag.Instance);
for (var i = 0; i < changes.Count; i++)
{
var change = changes[i];
@ -90,42 +82,6 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
return _currentSnapshot;
}
private bool TryRevertProvisionalChanges()
{
if (!_hasProvisionalChanges)
{
return false;
}
Debug.Assert(_previousSnapshot != null);
using var revertEdit = TextBuffer.CreateEdit(EditOptions.None, _previousSnapshot.Snapshot.Version.VersionNumber, InviolableEditTag.Instance);
var previousChanges = _previousSnapshot.Snapshot.Version.Changes;
for (var i = 0; i < previousChanges.Count; i++)
{
var change = previousChanges[i];
revertEdit.Replace(change.NewSpan, change.OldText);
}
revertEdit.Apply();
_hasProvisionalChanges = false;
return true;
}
private CSharpVirtualDocumentSnapshot UpdateSnapshot()
{
_previousSnapshot = _currentSnapshot;
return new CSharpVirtualDocumentSnapshot(Uri, TextBuffer.CurrentSnapshot, HostDocumentSyncVersion);
}
// This indicates that no other entity should respond to the edit event associated with this tag.
private class InviolableEditTag : IInviolableEditTag
{
private InviolableEditTag() { }
public readonly static IInviolableEditTag Instance = new InviolableEditTag();
}
private CSharpVirtualDocumentSnapshot UpdateSnapshot() => new CSharpVirtualDocumentSnapshot(Uri, TextBuffer.CurrentSnapshot, HostDocumentSyncVersion);
}
}

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

@ -58,14 +58,14 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
}
}
public override LSPDocumentSnapshot UpdateVirtualDocument<TVirtualDocument>(IReadOnlyList<TextChange> changes, long hostDocumentVersion, bool provisional = false)
public override LSPDocumentSnapshot UpdateVirtualDocument<TVirtualDocument>(IReadOnlyList<TextChange> changes, long hostDocumentVersion)
{
if (!TryGetVirtualDocument<TVirtualDocument>(out var virtualDocument))
{
throw new InvalidOperationException($"Cannot update virtual document of type {typeof(TVirtualDocument)} because LSP document {Uri} does not contain a virtual document of that type.");
}
virtualDocument.Update(changes, hostDocumentVersion, provisional);
virtualDocument.Update(changes, hostDocumentVersion);
_currentSnapshot = UpdateSnapshot();

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

@ -101,8 +101,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
public override void UpdateVirtualDocument<TVirtualDocument>(
Uri hostDocumentUri,
IReadOnlyList<TextChange> changes,
long hostDocumentVersion,
bool provisional = false)
long hostDocumentVersion)
{
if (hostDocumentUri is null)
{
@ -133,7 +132,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
var old = lspDocument.CurrentSnapshot;
var oldVirtual = virtualDocument.CurrentSnapshot;
var @new = lspDocument.UpdateVirtualDocument<TVirtualDocument>(changes, hostDocumentVersion, provisional);
var @new = lspDocument.UpdateVirtualDocument<TVirtualDocument>(changes, hostDocumentVersion);
if (old == @new)
{

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

@ -78,6 +78,24 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
return null;
}
var serverKind = projectionResult.LanguageKind == RazorLanguageKind.CSharp ? LanguageServerKind.CSharp : LanguageServerKind.Html;
var (succeeded, result) = await TryGetProvisionalCompletionsAsync(request, documentSnapshot, projectionResult, cancellationToken).ConfigureAwait(false);
if (succeeded)
{
// This means the user has just typed a dot after some identifier such as (cursor is pipe): "DateTime.| "
// In this case Razor interprets after the dot as Html and before it as C#.
// We use this criteria to provide a better completion experience for what we call provisional changes.
}
else if (!TriggerAppliesToProjection(request.Context, projectionResult.LanguageKind))
{
return null;
}
else
{
// This is a valid non-provisional completion request.
var completionParams = new CompletionParams()
{
Context = request.Context,
@ -88,27 +106,12 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
}
};
var serverKind = projectionResult.LanguageKind == RazorLanguageKind.CSharp ? LanguageServerKind.CSharp : LanguageServerKind.Html;
var provisionalCompletionParams = await GetProvisionalCompletionParamsAsync(request, documentSnapshot, projectionResult, cancellationToken).ConfigureAwait(false);
if (provisionalCompletionParams != null)
{
// This means the user has just typed a dot after some identifier such as (cursor is pipe): "DateTime.| "
// In this case Razor interprets after the dot as Html and before it as C#.
// We use this criteria to provide a better completion experience for what we call provisional changes.
completionParams = provisionalCompletionParams;
serverKind = LanguageServerKind.CSharp;
}
else if (!TriggerAppliesToProjection(request.Context, projectionResult.LanguageKind))
{
return null;
}
var result = await _requestInvoker.RequestServerAsync<CompletionParams, SumType<CompletionItem[], CompletionList>?>(
result = await _requestInvoker.RequestServerAsync<CompletionParams, SumType<CompletionItem[], CompletionList>?>(
Methods.TextDocumentCompletionName,
serverKind,
completionParams,
cancellationToken).ConfigureAwait(false);
}
if (result.HasValue)
{
@ -119,43 +122,43 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
return result;
}
internal async Task<CompletionParams> GetProvisionalCompletionParamsAsync(CompletionParams request, LSPDocumentSnapshot documentSnapshot, ProjectionResult projection, CancellationToken cancellationToken)
internal async Task<(bool, SumType<CompletionItem[], CompletionList>?)> TryGetProvisionalCompletionsAsync(CompletionParams request, LSPDocumentSnapshot documentSnapshot, ProjectionResult projection, CancellationToken cancellationToken)
{
SumType<CompletionItem[], CompletionList>? result = null;
if (projection.LanguageKind != RazorLanguageKind.Html ||
request.Context.TriggerKind != CompletionTriggerKind.TriggerCharacter ||
request.Context.TriggerCharacter != ".")
{
return null;
return (false, result);
}
if (projection.Position.Character == 0)
{
// We're at the start of line. Can't have provisional completions here.
return null;
return (false, result);
}
var previousCharacterPosition = new Position(projection.Position.Line, projection.Position.Character - 1);
var previousCharacterProjection = await _projectionProvider.GetProjectionAsync(documentSnapshot, previousCharacterPosition, cancellationToken).ConfigureAwait(false);
if (previousCharacterProjection == null || previousCharacterProjection.LanguageKind != RazorLanguageKind.CSharp)
{
return null;
return (false, result);
}
if (!(_documentManager is TrackingLSPDocumentManager trackingDocumentManager))
{
return null;
return (false, result);
}
// Edit the CSharp projected document to contain a '.'. This allows C# completion to provide valid
// completion items for moments when a user has typed a '.' that's typically interpreted as Html.
var changes = new[]
{
new TextChange(TextSpan.FromBounds(previousCharacterProjection.PositionIndex, previousCharacterProjection.PositionIndex), "."),
};
var addProvisionalDot = new TextChange(
TextSpan.FromBounds(previousCharacterProjection.PositionIndex, previousCharacterProjection.PositionIndex),
".");
await _joinableTaskFactory.SwitchToMainThreadAsync();
trackingDocumentManager.UpdateVirtualDocument<CSharpVirtualDocument>(documentSnapshot.Uri, changes, previousCharacterProjection.HostDocumentVersion, provisional: true);
trackingDocumentManager.UpdateVirtualDocument<CSharpVirtualDocument>(documentSnapshot.Uri, new[] { addProvisionalDot }, previousCharacterProjection.HostDocumentVersion);
var provisionalCompletionParams = new CompletionParams()
{
@ -164,7 +167,20 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
TextDocument = new TextDocumentIdentifier() { Uri = previousCharacterProjection.Uri }
};
return provisionalCompletionParams;
result = await _requestInvoker.RequestServerAsync<CompletionParams, SumType<CompletionItem[], CompletionList>?>(
Methods.TextDocumentCompletionName,
LanguageServerKind.CSharp,
provisionalCompletionParams,
cancellationToken).ConfigureAwait(true);
// We have now obtained the necessary completion items. We no longer need the provisional change. Revert.
var removeProvisionalDot = new TextChange(
TextSpan.FromBounds(previousCharacterProjection.PositionIndex, previousCharacterProjection.PositionIndex + 1),
string.Empty);
trackingDocumentManager.UpdateVirtualDocument<CSharpVirtualDocument>(documentSnapshot.Uri, new[] { removeProvisionalDot }, previousCharacterProjection.HostDocumentVersion);
return (true, result);
}
// Internal for testing

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

@ -38,7 +38,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
public override VirtualDocumentSnapshot CurrentSnapshot => _currentSnapshot;
public override VirtualDocumentSnapshot Update(IReadOnlyList<TextChange> changes, long hostDocumentVersion, bool provisional = false)
public override VirtualDocumentSnapshot Update(IReadOnlyList<TextChange> changes, long hostDocumentVersion)
{
if (changes is null)
{
@ -53,7 +53,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
return _currentSnapshot;
}
using var edit = TextBuffer.CreateEdit();
using var edit = TextBuffer.CreateEdit(EditOptions.None, reiteratedVersionNumber: null, InviolableEditTag.Instance);
for (var i = 0; i < changes.Count; i++)
{
var change = changes[i];

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

@ -0,0 +1,15 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.VisualStudio.Text;
namespace Microsoft.VisualStudio.LanguageServerClient.Razor
{
// Used to indicate that no other entity should respond to the edit event associated with this tag.
internal class InviolableEditTag : IInviolableEditTag
{
private InviolableEditTag() { }
public readonly static IInviolableEditTag Instance = new InviolableEditTag();
}
}

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

@ -20,7 +20,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
public abstract IReadOnlyList<VirtualDocument> VirtualDocuments { get; }
public abstract LSPDocumentSnapshot UpdateVirtualDocument<TVirtualDocument>(IReadOnlyList<TextChange> changes, long hostDocumentVersion, bool provisional = false) where TVirtualDocument : VirtualDocument;
public abstract LSPDocumentSnapshot UpdateVirtualDocument<TVirtualDocument>(IReadOnlyList<TextChange> changes, long hostDocumentVersion) where TVirtualDocument : VirtualDocument;
public bool TryGetVirtualDocument<TVirtualDocument>(out TVirtualDocument virtualDocument) where TVirtualDocument : VirtualDocument
{

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

@ -17,7 +17,6 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
public abstract void UpdateVirtualDocument<TVirtualDocument>(
Uri hostDocumentUri,
IReadOnlyList<TextChange> changes,
long hostDocumentVersion,
bool provisional = false) where TVirtualDocument : VirtualDocument;
long hostDocumentVersion) where TVirtualDocument : VirtualDocument;
}
}

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

@ -18,6 +18,6 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
public abstract long? HostDocumentSyncVersion { get; }
public abstract VirtualDocumentSnapshot Update(IReadOnlyList<TextChange> changes, long hostDocumentVersion, bool provisional = false);
public abstract VirtualDocumentSnapshot Update(IReadOnlyList<TextChange> changes, long hostDocumentVersion);
}
}

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

@ -119,7 +119,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
edit.Setup(e => e.Replace(replace.Span.Start, replace.Span.Length, replace.NewText));
var textBuffer = new Mock<ITextBuffer>();
var textBufferSnapshot = Mock.Of<ITextSnapshot>();
textBuffer.Setup(buffer => buffer.CreateEdit())
textBuffer.Setup(buffer => buffer.CreateEdit(EditOptions.None, null, It.IsAny<IInviolableEditTag>()))
.Returns(edit.Object);
textBuffer.Setup(buffer => buffer.CurrentSnapshot)
.Returns(() => textBufferSnapshot);
@ -138,55 +138,9 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
Assert.Same(editedSnapshot, document.CurrentSnapshot.Snapshot);
}
[Fact]
public void Update_Provisional_AppliesAndRevertsProvisionalChanges()
public static ITextBuffer CreateTextBuffer(ITextEdit edit)
{
// Arrange
var insert = new TextChange(new TextSpan(123, 0), ".");
var edit = new Mock<ITextEdit>();
edit.Setup(e => e.Insert(insert.Span.Start, insert.NewText)).Verifiable();
edit.Setup(e => e.Apply()).Verifiable();
var revertEdit = new Mock<ITextEdit>();
revertEdit.Setup(e => e.Replace(new Span(123, 1), string.Empty)).Verifiable();
revertEdit.Setup(e => e.Apply()).Verifiable();
var textBuffer = CreateTextBuffer(edit.Object, revertEdit.Object, new[] { insert });
var document = new CSharpVirtualDocument(Uri, textBuffer);
// Make a provisional edit followed by another edit.
// Act 1
document.Update(new[] { insert }, hostDocumentVersion: 1, provisional: true);
// Assert 1
edit.VerifyAll();
// Act 2
document.Update(new[] { new TextChange(new TextSpan(125, 0), "Some other edit") }, hostDocumentVersion: 2, provisional: false);
// Assert 2
revertEdit.VerifyAll();
}
public static ITextBuffer CreateTextBuffer(ITextEdit edit, ITextEdit revertEdit = null, TextChange[] provisionalChanges = null)
{
var changes = new TestTextChangeCollection();
if (provisionalChanges != null)
{
foreach (var provisionalChange in provisionalChanges)
{
var change = new Mock<ITextChange>();
change.SetupGet(c => c.NewSpan).Returns(new Span(provisionalChange.Span.Start, provisionalChange.NewText.Length));
change.SetupGet(c => c.OldText).Returns(string.Empty);
changes.Add(change.Object);
}
}
var textBuffer = Mock.Of<ITextBuffer>(
buffer => buffer.CreateEdit() == edit &&
buffer.CreateEdit(EditOptions.None, It.IsAny<int?>(), It.IsAny<IInviolableEditTag>()) == revertEdit &&
buffer.CurrentSnapshot == Mock.Of<ITextSnapshot>(s => s.Version.Changes == changes));
var textBuffer = Mock.Of<ITextBuffer>(buffer => buffer.CreateEdit(EditOptions.None, null, It.IsAny<IInviolableEditTag>()) == edit && buffer.CurrentSnapshot == Mock.Of<ITextSnapshot>());
return textBuffer;
}

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

@ -27,7 +27,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
document.Uri == Uri &&
document.CurrentSnapshot == LSPDocumentSnapshot &&
document.VirtualDocuments == new[] { new TestVirtualDocument() } &&
document.UpdateVirtualDocument<TestVirtualDocument>(It.IsAny<IReadOnlyList<TextChange>>(), It.IsAny<long>(), It.IsAny<bool>()) == Mock.Of<LSPDocumentSnapshot>());
document.UpdateVirtualDocument<TestVirtualDocument>(It.IsAny<IReadOnlyList<TextChange>>(), It.IsAny<long>()) == Mock.Of<LSPDocumentSnapshot>());
LSPDocumentFactory = Mock.Of<LSPDocumentFactory>(factory => factory.Create(TextBuffer) == LSPDocument);
}
@ -146,7 +146,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
var changes = new[] { new TextChange(new TextSpan(1, 1), string.Empty) };
// Act
manager.UpdateVirtualDocument<TestVirtualDocument>(Uri, changes, 123, provisional: true);
manager.UpdateVirtualDocument<TestVirtualDocument>(Uri, changes, 123);
// Assert
Assert.True(changedCalled);
@ -207,7 +207,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
public override long? HostDocumentSyncVersion => 123;
public override VirtualDocumentSnapshot Update(IReadOnlyList<TextChange> changes, long hostDocumentVersion, bool provisional)
public override VirtualDocumentSnapshot Update(IReadOnlyList<TextChange> changes, long hostDocumentVersion)
{
throw new NotImplementedException();
}

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

@ -31,12 +31,11 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
var originalSnapshot = document.CurrentSnapshot;
// Act
document.UpdateVirtualDocument<TestVirtualDocument>(changes, hostDocumentVersion: 1337, provisional: true);
document.UpdateVirtualDocument<TestVirtualDocument>(changes, hostDocumentVersion: 1337);
// Assert
Assert.Equal(1337, virtualDocument.HostDocumentSyncVersion);
Assert.Same(changes, virtualDocument.Changes);
Assert.True(virtualDocument.Provisional);
Assert.NotEqual(originalSnapshot, document.CurrentSnapshot);
}
@ -46,8 +45,6 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
public IReadOnlyList<TextChange> Changes { get; private set; }
public bool Provisional { get; private set; }
public override Uri Uri => throw new NotImplementedException();
public override ITextBuffer TextBuffer => throw new NotImplementedException();
@ -56,11 +53,10 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
public override long? HostDocumentSyncVersion => _hostDocumentVersion;
public override VirtualDocumentSnapshot Update(IReadOnlyList<TextChange> changes, long hostDocumentVersion, bool provisional)
public override VirtualDocumentSnapshot Update(IReadOnlyList<TextChange> changes, long hostDocumentVersion)
{
_hostDocumentVersion = hostDocumentVersion;
Changes = changes;
Provisional = provisional;
return null;
}

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

@ -53,7 +53,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
{
// Arrange
var documentManager = new Mock<TrackingLSPDocumentManager>();
documentManager.Setup(manager => manager.UpdateVirtualDocument<CSharpVirtualDocument>(It.IsAny<Uri>(), It.IsAny<IReadOnlyList<TextChange>>(), 1337, false /* UpdateCSharpBuffer request should never be provisional */))
documentManager.Setup(manager => manager.UpdateVirtualDocument<CSharpVirtualDocument>(It.IsAny<Uri>(), It.IsAny<IReadOnlyList<TextChange>>(), 1337))
.Verifiable();
var target = new DefaultRazorLanguageServerCustomMessageTarget(documentManager.Object);
var request = new UpdateBufferRequest()

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

@ -343,7 +343,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
}
[Fact]
public async Task GetProvisionalCompletionParamsAsync_CSharpProjection_ReturnsNull()
public async Task TryGetProvisionalCompletionsAsync_CSharpProjection_ReturnsFalse()
{
// Arrange
var completionRequest = new CompletionParams()
@ -371,14 +371,15 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object);
// Act
var result = await completionHandler.GetProvisionalCompletionParamsAsync(completionRequest, Mock.Of<LSPDocumentSnapshot>(), projectionResult, CancellationToken.None).ConfigureAwait(false);
var (succeeded, result) = await completionHandler.TryGetProvisionalCompletionsAsync(completionRequest, Mock.Of<LSPDocumentSnapshot>(), projectionResult, CancellationToken.None).ConfigureAwait(false);
// Assert
Assert.False(succeeded);
Assert.Null(result);
}
[Fact]
public async Task GetProvisionalCompletionParamsAsync_TriggerCharacterNotDot_ReturnsNull()
public async Task TryGetProvisionalCompletionsAsync_TriggerCharacterNotDot_ReturnsFalse()
{
// Arrange
var completionRequest = new CompletionParams()
@ -406,14 +407,15 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object);
// Act
var result = await completionHandler.GetProvisionalCompletionParamsAsync(completionRequest, Mock.Of<LSPDocumentSnapshot>(), projectionResult, CancellationToken.None).ConfigureAwait(false);
var (succeeded, result) = await completionHandler.TryGetProvisionalCompletionsAsync(completionRequest, Mock.Of<LSPDocumentSnapshot>(), projectionResult, CancellationToken.None).ConfigureAwait(false);
// Assert
Assert.False(succeeded);
Assert.Null(result);
}
[Fact]
public async Task GetProvisionalCompletionParamsAsync_PreviousCharacterHtml_ReturnsNull()
public async Task TryGetProvisionalCompletionsAsync_PreviousCharacterHtml_ReturnsFalse()
{
// Arrange
var completionRequest = new CompletionParams()
@ -447,14 +449,15 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object);
// Act
var result = await completionHandler.GetProvisionalCompletionParamsAsync(completionRequest, Mock.Of<LSPDocumentSnapshot>(), projectionResult, CancellationToken.None).ConfigureAwait(false);
var (succeeded, result) = await completionHandler.TryGetProvisionalCompletionsAsync(completionRequest, Mock.Of<LSPDocumentSnapshot>(), projectionResult, CancellationToken.None).ConfigureAwait(false);
// Assert
Assert.False(succeeded);
Assert.Null(result);
}
[Fact]
public async Task GetProvisionalCompletionParamsAsync_ProjectionAtStartOfLine_ReturnsNull()
public async Task TryGetProvisionalCompletionsAsync_ProjectionAtStartOfLine_ReturnsFalse()
{
// Arrange
var completionRequest = new CompletionParams()
@ -488,14 +491,15 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object);
// Act
var result = await completionHandler.GetProvisionalCompletionParamsAsync(completionRequest, Mock.Of<LSPDocumentSnapshot>(), projectionResult, CancellationToken.None).ConfigureAwait(false);
var (succeeded, result) = await completionHandler.TryGetProvisionalCompletionsAsync(completionRequest, Mock.Of<LSPDocumentSnapshot>(), projectionResult, CancellationToken.None).ConfigureAwait(false);
// Assert
Assert.False(succeeded);
Assert.Null(result);
}
[Fact]
public async Task GetProvisionalCompletionParamsAsync_AtCorrectProvisionalCompletionPoint_ReturnsCorrectParams()
public async Task TryGetProvisionalCompletionsAsync_AtCorrectProvisionalCompletionPoint_ReturnsExpectedResult()
{
// Arrange
var completionRequest = new CompletionParams()
@ -513,7 +517,18 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
var documentManager = new TestDocumentManager();
var requestInvoker = new Mock<LSPRequestInvoker>();
var languageServerCalled = false;
var expectedItem = new CompletionItem() { InsertText = "DateTime" };
var requestInvoker = new Mock<LSPRequestInvoker>(MockBehavior.Strict);
requestInvoker
.Setup(r => r.RequestServerAsync<CompletionParams, SumType<CompletionItem[], CompletionList>?>(It.IsAny<string>(), LanguageServerKind.CSharp, It.IsAny<CompletionParams>(), It.IsAny<CancellationToken>()))
.Callback<string, LanguageServerKind, CompletionParams, CancellationToken>((method, serverKind, completionParams, ct) =>
{
Assert.Equal(Methods.TextDocumentCompletionName, method);
Assert.Equal(LanguageServerKind.CSharp, serverKind);
languageServerCalled = true;
})
.Returns(Task.FromResult<SumType<CompletionItem[], CompletionList>?>(new[] { expectedItem }));
var projectionResult = new ProjectionResult()
{
@ -533,15 +548,15 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object);
// Act
var result = await completionHandler.GetProvisionalCompletionParamsAsync(completionRequest, Mock.Of<LSPDocumentSnapshot>(), projectionResult, CancellationToken.None).ConfigureAwait(false);
var (succeeded, result) = await completionHandler.TryGetProvisionalCompletionsAsync(completionRequest, Mock.Of<LSPDocumentSnapshot>(), projectionResult, CancellationToken.None).ConfigureAwait(false);
// Assert
Assert.True(documentManager.UpdateVirtualDocumentCalled);
Assert.True(succeeded);
Assert.True(languageServerCalled);
Assert.Equal(2, documentManager.UpdateVirtualDocumentCallCount);
Assert.NotNull(result);
Assert.Same(completionRequest.Context, result.Context);
Assert.Equal(virtualDocumentUri, result.TextDocument.Uri);
Assert.Equal(previousCharacterProjection.Position.Line, result.Position.Line);
Assert.Equal(previousCharacterProjection.Position.Character + 1, result.Position.Character);
var item = Assert.Single((CompletionItem[])result.Value);
Assert.Equal(expectedItem.InsertText, item.InsertText);
}
private class TestDocumentManager : TrackingLSPDocumentManager
@ -550,7 +565,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
public override event EventHandler<LSPDocumentChangeEventArgs> Changed;
public bool UpdateVirtualDocumentCalled { get; private set; }
public int UpdateVirtualDocumentCallCount { get; private set; }
public override bool TryGetDocument(Uri uri, out LSPDocumentSnapshot lspDocumentSnapshot)
{
@ -574,9 +589,9 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
throw new NotImplementedException();
}
public override void UpdateVirtualDocument<TVirtualDocument>(Uri hostDocumentUri, IReadOnlyList<TextChange> changes, long hostDocumentVersion, bool provisional = false)
public override void UpdateVirtualDocument<TVirtualDocument>(Uri hostDocumentUri, IReadOnlyList<TextChange> changes, long hostDocumentVersion)
{
UpdateVirtualDocumentCalled = true;
UpdateVirtualDocumentCallCount++;
}
}
}

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

@ -118,7 +118,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
edit.Setup(e => e.Replace(replace.Span.Start, replace.Span.Length, replace.NewText));
var textBuffer = new Mock<ITextBuffer>();
var textBufferSnapshot = Mock.Of<ITextSnapshot>();
textBuffer.Setup(buffer => buffer.CreateEdit())
textBuffer.Setup(buffer => buffer.CreateEdit(EditOptions.None, null, It.IsAny<IInviolableEditTag>()))
.Returns(edit.Object);
textBuffer.Setup(buffer => buffer.CurrentSnapshot)
.Returns(() => textBufferSnapshot);
@ -139,7 +139,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
public static ITextBuffer CreateTextBuffer(ITextEdit edit)
{
var textBuffer = Mock.Of<ITextBuffer>(buffer => buffer.CreateEdit() == edit && buffer.CurrentSnapshot == Mock.Of<ITextSnapshot>());
var textBuffer = Mock.Of<ITextBuffer>(buffer => buffer.CreateEdit(EditOptions.None, null, It.IsAny<IInviolableEditTag>()) == edit && buffer.CurrentSnapshot == Mock.Of<ITextSnapshot>());
return textBuffer;
}
}

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

@ -58,7 +58,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
public override VirtualDocumentSnapshot CurrentSnapshot => throw new NotImplementedException();
public override VirtualDocumentSnapshot Update(IReadOnlyList<TextChange> changes, long hostDocumentVersion, bool provisional)
public override VirtualDocumentSnapshot Update(IReadOnlyList<TextChange> changes, long hostDocumentVersion)
{
throw new NotImplementedException();
}