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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text;
@ -12,11 +11,8 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
internal class CSharpVirtualDocument : VirtualDocument internal class CSharpVirtualDocument : VirtualDocument
{ {
private long? _hostDocumentSyncVersion; private long? _hostDocumentSyncVersion;
private CSharpVirtualDocumentSnapshot _previousSnapshot;
private CSharpVirtualDocumentSnapshot _currentSnapshot; private CSharpVirtualDocumentSnapshot _currentSnapshot;
private bool _hasProvisionalChanges = false;
public CSharpVirtualDocument(Uri uri, ITextBuffer textBuffer) public CSharpVirtualDocument(Uri uri, ITextBuffer textBuffer)
{ {
if (uri is null) if (uri is null)
@ -31,7 +27,6 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
Uri = uri; Uri = uri;
TextBuffer = textBuffer; TextBuffer = textBuffer;
_previousSnapshot = null;
_currentSnapshot = UpdateSnapshot(); _currentSnapshot = UpdateSnapshot();
} }
@ -43,7 +38,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
public override VirtualDocumentSnapshot CurrentSnapshot => _currentSnapshot; 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) if (changes is null)
{ {
@ -52,16 +47,13 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
_hostDocumentSyncVersion = hostDocumentVersion; _hostDocumentSyncVersion = hostDocumentVersion;
TryRevertProvisionalChanges();
_hasProvisionalChanges = provisional;
if (changes.Count == 0) if (changes.Count == 0)
{ {
_currentSnapshot = UpdateSnapshot(); _currentSnapshot = UpdateSnapshot();
return _currentSnapshot; 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++) for (var i = 0; i < changes.Count; i++)
{ {
var change = changes[i]; var change = changes[i];
@ -90,42 +82,6 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
return _currentSnapshot; return _currentSnapshot;
} }
private bool TryRevertProvisionalChanges() private CSharpVirtualDocumentSnapshot UpdateSnapshot() => new CSharpVirtualDocumentSnapshot(Uri, TextBuffer.CurrentSnapshot, HostDocumentSyncVersion);
{
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();
}
} }
} }

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

@ -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)) 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."); 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(); _currentSnapshot = UpdateSnapshot();

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

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

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

@ -78,37 +78,40 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
return null; return null;
} }
var completionParams = new CompletionParams()
{
Context = request.Context,
Position = projectionResult.Position,
TextDocument = new TextDocumentIdentifier()
{
Uri = projectionResult.Uri
}
};
var serverKind = projectionResult.LanguageKind == RazorLanguageKind.CSharp ? LanguageServerKind.CSharp : LanguageServerKind.Html; var serverKind = projectionResult.LanguageKind == RazorLanguageKind.CSharp ? LanguageServerKind.CSharp : LanguageServerKind.Html;
var provisionalCompletionParams = await GetProvisionalCompletionParamsAsync(request, documentSnapshot, projectionResult, cancellationToken).ConfigureAwait(false); var (succeeded, result) = await TryGetProvisionalCompletionsAsync(request, documentSnapshot, projectionResult, cancellationToken).ConfigureAwait(false);
if (provisionalCompletionParams != null) if (succeeded)
{ {
// This means the user has just typed a dot after some identifier such as (cursor is pipe): "DateTime.| " // 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#. // 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. // 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)) else if (!TriggerAppliesToProjection(request.Context, projectionResult.LanguageKind))
{ {
return null; return null;
} }
else
{
// This is a valid non-provisional completion request.
var completionParams = new CompletionParams()
{
Context = request.Context,
Position = projectionResult.Position,
TextDocument = new TextDocumentIdentifier()
{
Uri = projectionResult.Uri
}
};
var result = await _requestInvoker.RequestServerAsync<CompletionParams, SumType<CompletionItem[], CompletionList>?>( result = await _requestInvoker.RequestServerAsync<CompletionParams, SumType<CompletionItem[], CompletionList>?>(
Methods.TextDocumentCompletionName, Methods.TextDocumentCompletionName,
serverKind, serverKind,
completionParams, completionParams,
cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
}
if (result.HasValue) if (result.HasValue)
{ {
@ -119,43 +122,43 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
return result; 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 || if (projection.LanguageKind != RazorLanguageKind.Html ||
request.Context.TriggerKind != CompletionTriggerKind.TriggerCharacter || request.Context.TriggerKind != CompletionTriggerKind.TriggerCharacter ||
request.Context.TriggerCharacter != ".") request.Context.TriggerCharacter != ".")
{ {
return null; return (false, result);
} }
if (projection.Position.Character == 0) if (projection.Position.Character == 0)
{ {
// We're at the start of line. Can't have provisional completions here. // 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 previousCharacterPosition = new Position(projection.Position.Line, projection.Position.Character - 1);
var previousCharacterProjection = await _projectionProvider.GetProjectionAsync(documentSnapshot, previousCharacterPosition, cancellationToken).ConfigureAwait(false); var previousCharacterProjection = await _projectionProvider.GetProjectionAsync(documentSnapshot, previousCharacterPosition, cancellationToken).ConfigureAwait(false);
if (previousCharacterProjection == null || previousCharacterProjection.LanguageKind != RazorLanguageKind.CSharp) if (previousCharacterProjection == null || previousCharacterProjection.LanguageKind != RazorLanguageKind.CSharp)
{ {
return null; return (false, result);
} }
if (!(_documentManager is TrackingLSPDocumentManager trackingDocumentManager)) 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 // 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. // completion items for moments when a user has typed a '.' that's typically interpreted as Html.
var changes = new[] var addProvisionalDot = new TextChange(
{ TextSpan.FromBounds(previousCharacterProjection.PositionIndex, previousCharacterProjection.PositionIndex),
new TextChange(TextSpan.FromBounds(previousCharacterProjection.PositionIndex, previousCharacterProjection.PositionIndex), "."), ".");
};
await _joinableTaskFactory.SwitchToMainThreadAsync(); 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() var provisionalCompletionParams = new CompletionParams()
{ {
@ -164,7 +167,20 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
TextDocument = new TextDocumentIdentifier() { Uri = previousCharacterProjection.Uri } 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 // Internal for testing

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

@ -38,7 +38,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
public override VirtualDocumentSnapshot CurrentSnapshot => _currentSnapshot; 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) if (changes is null)
{ {
@ -53,7 +53,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
return _currentSnapshot; 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++) for (var i = 0; i < changes.Count; i++)
{ {
var change = changes[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 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 public bool TryGetVirtualDocument<TVirtualDocument>(out TVirtualDocument virtualDocument) where TVirtualDocument : VirtualDocument
{ {

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

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

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

@ -18,6 +18,6 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
public abstract long? HostDocumentSyncVersion { get; } 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)); edit.Setup(e => e.Replace(replace.Span.Start, replace.Span.Length, replace.NewText));
var textBuffer = new Mock<ITextBuffer>(); var textBuffer = new Mock<ITextBuffer>();
var textBufferSnapshot = Mock.Of<ITextSnapshot>(); var textBufferSnapshot = Mock.Of<ITextSnapshot>();
textBuffer.Setup(buffer => buffer.CreateEdit()) textBuffer.Setup(buffer => buffer.CreateEdit(EditOptions.None, null, It.IsAny<IInviolableEditTag>()))
.Returns(edit.Object); .Returns(edit.Object);
textBuffer.Setup(buffer => buffer.CurrentSnapshot) textBuffer.Setup(buffer => buffer.CurrentSnapshot)
.Returns(() => textBufferSnapshot); .Returns(() => textBufferSnapshot);
@ -138,55 +138,9 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
Assert.Same(editedSnapshot, document.CurrentSnapshot.Snapshot); Assert.Same(editedSnapshot, document.CurrentSnapshot.Snapshot);
} }
[Fact] public static ITextBuffer CreateTextBuffer(ITextEdit edit)
public void Update_Provisional_AppliesAndRevertsProvisionalChanges()
{ {
// Arrange var textBuffer = Mock.Of<ITextBuffer>(buffer => buffer.CreateEdit(EditOptions.None, null, It.IsAny<IInviolableEditTag>()) == edit && buffer.CurrentSnapshot == Mock.Of<ITextSnapshot>());
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));
return textBuffer; return textBuffer;
} }

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

@ -27,7 +27,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
document.Uri == Uri && document.Uri == Uri &&
document.CurrentSnapshot == LSPDocumentSnapshot && document.CurrentSnapshot == LSPDocumentSnapshot &&
document.VirtualDocuments == new[] { new TestVirtualDocument() } && 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); 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) }; var changes = new[] { new TextChange(new TextSpan(1, 1), string.Empty) };
// Act // Act
manager.UpdateVirtualDocument<TestVirtualDocument>(Uri, changes, 123, provisional: true); manager.UpdateVirtualDocument<TestVirtualDocument>(Uri, changes, 123);
// Assert // Assert
Assert.True(changedCalled); Assert.True(changedCalled);
@ -207,7 +207,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
public override long? HostDocumentSyncVersion => 123; 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(); throw new NotImplementedException();
} }

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

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

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

@ -53,7 +53,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
{ {
// Arrange // Arrange
var documentManager = new Mock<TrackingLSPDocumentManager>(); 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(); .Verifiable();
var target = new DefaultRazorLanguageServerCustomMessageTarget(documentManager.Object); var target = new DefaultRazorLanguageServerCustomMessageTarget(documentManager.Object);
var request = new UpdateBufferRequest() var request = new UpdateBufferRequest()

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

@ -343,7 +343,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
} }
[Fact] [Fact]
public async Task GetProvisionalCompletionParamsAsync_CSharpProjection_ReturnsNull() public async Task TryGetProvisionalCompletionsAsync_CSharpProjection_ReturnsFalse()
{ {
// Arrange // Arrange
var completionRequest = new CompletionParams() var completionRequest = new CompletionParams()
@ -371,14 +371,15 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object); var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object);
// Act // 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
Assert.False(succeeded);
Assert.Null(result); Assert.Null(result);
} }
[Fact] [Fact]
public async Task GetProvisionalCompletionParamsAsync_TriggerCharacterNotDot_ReturnsNull() public async Task TryGetProvisionalCompletionsAsync_TriggerCharacterNotDot_ReturnsFalse()
{ {
// Arrange // Arrange
var completionRequest = new CompletionParams() var completionRequest = new CompletionParams()
@ -406,14 +407,15 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object); var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object);
// Act // 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
Assert.False(succeeded);
Assert.Null(result); Assert.Null(result);
} }
[Fact] [Fact]
public async Task GetProvisionalCompletionParamsAsync_PreviousCharacterHtml_ReturnsNull() public async Task TryGetProvisionalCompletionsAsync_PreviousCharacterHtml_ReturnsFalse()
{ {
// Arrange // Arrange
var completionRequest = new CompletionParams() var completionRequest = new CompletionParams()
@ -447,14 +449,15 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object); var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object);
// Act // 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
Assert.False(succeeded);
Assert.Null(result); Assert.Null(result);
} }
[Fact] [Fact]
public async Task GetProvisionalCompletionParamsAsync_ProjectionAtStartOfLine_ReturnsNull() public async Task TryGetProvisionalCompletionsAsync_ProjectionAtStartOfLine_ReturnsFalse()
{ {
// Arrange // Arrange
var completionRequest = new CompletionParams() var completionRequest = new CompletionParams()
@ -488,14 +491,15 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object); var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object);
// Act // 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
Assert.False(succeeded);
Assert.Null(result); Assert.Null(result);
} }
[Fact] [Fact]
public async Task GetProvisionalCompletionParamsAsync_AtCorrectProvisionalCompletionPoint_ReturnsCorrectParams() public async Task TryGetProvisionalCompletionsAsync_AtCorrectProvisionalCompletionPoint_ReturnsExpectedResult()
{ {
// Arrange // Arrange
var completionRequest = new CompletionParams() var completionRequest = new CompletionParams()
@ -513,7 +517,18 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
var documentManager = new TestDocumentManager(); 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() var projectionResult = new ProjectionResult()
{ {
@ -533,15 +548,15 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object); var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object);
// Act // 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
Assert.True(documentManager.UpdateVirtualDocumentCalled); Assert.True(succeeded);
Assert.True(languageServerCalled);
Assert.Equal(2, documentManager.UpdateVirtualDocumentCallCount);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Same(completionRequest.Context, result.Context); var item = Assert.Single((CompletionItem[])result.Value);
Assert.Equal(virtualDocumentUri, result.TextDocument.Uri); Assert.Equal(expectedItem.InsertText, item.InsertText);
Assert.Equal(previousCharacterProjection.Position.Line, result.Position.Line);
Assert.Equal(previousCharacterProjection.Position.Character + 1, result.Position.Character);
} }
private class TestDocumentManager : TrackingLSPDocumentManager private class TestDocumentManager : TrackingLSPDocumentManager
@ -550,7 +565,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
public override event EventHandler<LSPDocumentChangeEventArgs> Changed; 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) public override bool TryGetDocument(Uri uri, out LSPDocumentSnapshot lspDocumentSnapshot)
{ {
@ -574,9 +589,9 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
throw new NotImplementedException(); 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)); edit.Setup(e => e.Replace(replace.Span.Start, replace.Span.Length, replace.NewText));
var textBuffer = new Mock<ITextBuffer>(); var textBuffer = new Mock<ITextBuffer>();
var textBufferSnapshot = Mock.Of<ITextSnapshot>(); var textBufferSnapshot = Mock.Of<ITextSnapshot>();
textBuffer.Setup(buffer => buffer.CreateEdit()) textBuffer.Setup(buffer => buffer.CreateEdit(EditOptions.None, null, It.IsAny<IInviolableEditTag>()))
.Returns(edit.Object); .Returns(edit.Object);
textBuffer.Setup(buffer => buffer.CurrentSnapshot) textBuffer.Setup(buffer => buffer.CurrentSnapshot)
.Returns(() => textBufferSnapshot); .Returns(() => textBufferSnapshot);
@ -139,7 +139,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
public static ITextBuffer CreateTextBuffer(ITextEdit edit) 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; return textBuffer;
} }
} }

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

@ -58,7 +58,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor
public override VirtualDocumentSnapshot CurrentSnapshot => throw new NotImplementedException(); 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(); throw new NotImplementedException();
} }