Fix HTML completion race when typing too quickly

- There was a race when typing too quickly our synchronization mechanism would try and reduce the work that was done to cancel pre-existing requests. So in the scenario when you were to type `<tr` what would happen is that 3 completion requests would fire:
    1. Triggered completion for `<`
    2. Typing completion for `t`
    3. Typing completion for `r`
    Now when we sped things up we were able to process requests in parallel which meant that we could handle simultaneous requests for `<`, `t` and `r` all at the same time; however, this in turn results in an interesting behavior where if we ask for completion at `r` while `<` and `t` are still active our synchronization mechanism would aggressively cancel the older requests. For completion this is catastrohpic because it would result in a 0 length completion list because HTML does not respect completion requests beyond trigger characters and the beginning of the word.
- To fix this issue I added a new mechanism for synchronization which takes a flag `rejectOnNewerParallelRequest` which states do not reject a synchronization request aggressively if this flag is `true`. Now this doesn't mean the requests never get rejected. If there's a batched document update or document close / open this will still reject the document; it just means that on the requesting of synchronization that older completion requests are not rejected eagerly.
- Added tests to our projection and synchronization stack to account for this
- Ensured that these changes are not breaking changes so marked some bits as virtual.

### Before

![before image](https://i.imgur.com/sZfGaub.gif)

### After

![after image](https://i.imgur.com/5EsJdDm.gif)

Fixes #5743
This commit is contained in:
N. Taylor Mullen 2021-11-16 17:17:33 -08:00
Родитель 4be2e4ce79
Коммит e9cd763349
10 изменённых файлов: 174 добавлений и 36 удалений

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

@ -38,6 +38,9 @@ namespace Microsoft.VisualStudio.LanguageServer.ContainedLanguage
}
public override Task<bool> TrySynchronizeVirtualDocumentAsync(int requiredHostDocumentVersion, VirtualDocumentSnapshot virtualDocument, CancellationToken cancellationToken)
=> TrySynchronizeVirtualDocumentAsync(requiredHostDocumentVersion, virtualDocument, rejectOnNewerParallelRequest: true, cancellationToken);
public override Task<bool> TrySynchronizeVirtualDocumentAsync(int requiredHostDocumentVersion, VirtualDocumentSnapshot virtualDocument, bool rejectOnNewerParallelRequest, CancellationToken cancellationToken)
{
if (virtualDocument is null)
{
@ -59,7 +62,7 @@ namespace Microsoft.VisualStudio.LanguageServer.ContainedLanguage
}
// Currently tracked synchronizing context is not sufficient, need to update a new one.
var onSynchronizedTask = documentContext.GetSynchronizationTaskAsync(requiredHostDocumentVersion, cancellationToken);
var onSynchronizedTask = documentContext.GetSynchronizationTaskAsync(requiredHostDocumentVersion, rejectOnNewerParallelRequest, cancellationToken);
return onSynchronizedTask;
}
}
@ -192,14 +195,15 @@ namespace Microsoft.VisualStudio.LanguageServer.ContainedLanguage
}
}
public Task<bool> GetSynchronizationTaskAsync(int requiredHostDocumentVersion, CancellationToken cancellationToken)
public Task<bool> GetSynchronizationTaskAsync(int requiredHostDocumentVersion, bool rejectOnNewerParallelRequest, CancellationToken cancellationToken)
{
// Cancel any out-of-date existing synchronizing contexts.
for (var i = _synchronizingContexts.Count - 1; i >= 0; i--)
{
var context = _synchronizingContexts[i];
if (context.RequiredHostDocumentVersion < requiredHostDocumentVersion)
if (context.RejectOnNewerParallelRequest &&
context.RequiredHostDocumentVersion < requiredHostDocumentVersion)
{
// All of the existing synchronizations that are older than this version are no longer valid.
context.SetSynchronized(result: false);
@ -207,7 +211,7 @@ namespace Microsoft.VisualStudio.LanguageServer.ContainedLanguage
}
}
var synchronizingContext = new DocumentSynchronizingContext(requiredHostDocumentVersion, _synchronizingTimeout, cancellationToken);
var synchronizingContext = new DocumentSynchronizingContext(requiredHostDocumentVersion, rejectOnNewerParallelRequest, _synchronizingTimeout, cancellationToken);
_synchronizingContexts.Add(synchronizingContext);
return synchronizingContext.OnSynchronizedAsync;
}
@ -230,10 +234,12 @@ namespace Microsoft.VisualStudio.LanguageServer.ContainedLanguage
public DocumentSynchronizingContext(
int requiredHostDocumentVersion,
bool rejectOnNewerParallelRequest,
TimeSpan timeout,
CancellationToken requestCancellationToken)
{
RequiredHostDocumentVersion = requiredHostDocumentVersion;
RejectOnNewerParallelRequest = rejectOnNewerParallelRequest;
_onSynchronizedSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
_cts = CancellationTokenSource.CreateLinkedTokenSource(requestCancellationToken);
@ -243,6 +249,8 @@ namespace Microsoft.VisualStudio.LanguageServer.ContainedLanguage
_cts.CancelAfter(timeout);
}
public bool RejectOnNewerParallelRequest { get; }
public int RequiredHostDocumentVersion { get; }
public Task<bool> OnSynchronizedAsync => _onSynchronizedSource.Task;

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

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
@ -9,5 +10,21 @@ namespace Microsoft.VisualStudio.LanguageServer.ContainedLanguage
internal abstract class LSPDocumentSynchronizer : LSPDocumentChangeListener
{
public abstract Task<bool> TrySynchronizeVirtualDocumentAsync(int requiredHostDocumentVersion, VirtualDocumentSnapshot virtualDocument, CancellationToken cancellationToken);
/// <summary>
/// Attempts to synchronize a virtual document to a corresponding host document version.
/// </summary>
/// <param name="requiredHostDocumentVersion">The corresponding host document version required for the generated <paramref name="virtualDocument"/></param>
/// <param name="virtualDocument">A generated document to correlate <paramref name="requiredHostDocumentVersion"/>'s for.</param>
/// <param name="rejectOnNewerParallelRequest">
/// When <c>true</c> if a second synchronization request for the same virtual document comes in with a newer required host document version all pending synchronization requests will be rejected.
/// If <c>false</c>, active synchronization requests will be fulfilled as virtul document buffers get updated. Fulfillment could be a rejection or an approval.
/// </param>
/// <param name="cancellationToken"></param>
/// <returns><c>true</c> if we were able to successfully synchronize; <c>false</c> otherwise.</returns>
public virtual Task<bool> TrySynchronizeVirtualDocumentAsync(int requiredHostDocumentVersion, VirtualDocumentSnapshot virtualDocument, bool rejectOnNewerParallelRequest, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
}

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

@ -147,7 +147,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
return null;
}
var projectionResult = await _projectionProvider.GetProjectionAsync(
var projectionResult = await _projectionProvider.GetProjectionForCompletionAsync(
documentSnapshot,
request.Position,
cancellationToken).ConfigureAwait(false);
@ -532,7 +532,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
}
var previousCharacterPosition = new Position(projection.Position.Line, projection.Position.Character - 1);
var previousCharacterProjection = await _projectionProvider.GetProjectionAsync(
var previousCharacterProjection = await _projectionProvider.GetProjectionForCompletionAsync(
documentSnapshot,
previousCharacterPosition,
cancellationToken).ConfigureAwait(false);

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

@ -61,7 +61,13 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
_loggerProvider = loggerProvider;
}
public override async Task<ProjectionResult?> GetProjectionAsync(LSPDocumentSnapshot documentSnapshot, Position position, CancellationToken cancellationToken)
public override Task<ProjectionResult?> GetProjectionAsync(LSPDocumentSnapshot documentSnapshot, Position position, CancellationToken cancellationToken)
=> GetProjectionCoreAsync(documentSnapshot, position, rejectOnNewerParallelRequest: true, cancellationToken);
public override Task<ProjectionResult?> GetProjectionForCompletionAsync(LSPDocumentSnapshot documentSnapshot, Position position, CancellationToken cancellationToken)
=> GetProjectionCoreAsync(documentSnapshot, position, rejectOnNewerParallelRequest: false, cancellationToken);
private async Task<ProjectionResult?> GetProjectionCoreAsync(LSPDocumentSnapshot documentSnapshot, Position position, bool rejectOnNewerParallelRequest, CancellationToken cancellationToken)
{
if (documentSnapshot is null)
{
@ -126,7 +132,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
}
else
{
var synchronized = await _documentSynchronizer.TrySynchronizeVirtualDocumentAsync(documentSnapshot.Version, virtualDocument, cancellationToken).ConfigureAwait(false);
var synchronized = await _documentSynchronizer.TrySynchronizeVirtualDocumentAsync(documentSnapshot.Version, virtualDocument, rejectOnNewerParallelRequest, cancellationToken).ConfigureAwait(false);
if (!synchronized)
{
_logHubLogger.LogInformation("Could not synchronize.");

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

@ -13,5 +13,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
internal abstract class LSPProjectionProvider
{
public abstract Task<ProjectionResult?> GetProjectionAsync(LSPDocumentSnapshot documentSnapshot, Position position, CancellationToken cancellationToken);
public abstract Task<ProjectionResult?> GetProjectionForCompletionAsync(LSPDocumentSnapshot documentSnapshot, Position position, CancellationToken cancellationToken);
}
}

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

@ -175,6 +175,66 @@ namespace Microsoft.VisualStudio.LanguageServer.ContainedLanguage
Assert.True(result2);
}
[Fact]
public async Task TrySynchronizeVirtualDocumentAsync_NewerVersionRequested_CancelsActiveRequest()
{
// Arrange
var (lspDocument, virtualDocument) = CreateDocuments(lspDocumentVersion: 124, virtualDocumentSyncVersion: 123);
var fileUriProvider = CreateUriProviderFor(VirtualDocumentTextBuffer, virtualDocument.Uri);
var synchronizer = new DefaultLSPDocumentSynchronizer(fileUriProvider)
{
_synchronizationTimeout = TimeSpan.FromMilliseconds(500)
};
NotifyLSPDocumentAdded(lspDocument, synchronizer);
// Act
// Start synchronization, this will hang until we notify the buffer versions been updated because the above virtual document expects host doc version 123 but the host doc is 124
var synchronizeTask1 = synchronizer.TrySynchronizeVirtualDocumentAsync(lspDocument.Version, virtualDocument, CancellationToken.None);
var (newLSPDocument, newVirtualDocument) = CreateDocuments(lspDocumentVersion: 125, virtualDocumentSyncVersion: 124);
var synchronizeTask2 = synchronizer.TrySynchronizeVirtualDocumentAsync(newLSPDocument.Version, newVirtualDocument, CancellationToken.None);
NotifyBufferVersionUpdated(VirtualDocumentTextBuffer, lspDocument.Version);
NotifyBufferVersionUpdated(VirtualDocumentTextBuffer, newLSPDocument.Version);
var result1 = await synchronizeTask1.ConfigureAwait(false);
var result2 = await synchronizeTask2.ConfigureAwait(false);
// Assert
Assert.False(result1);
Assert.True(result2);
}
[Fact]
public async Task TrySynchronizeVirtualDocumentAsync_RejectOnNewerParallelRequest_NewerVersionRequested_CancelsActiveRequest()
{
// Arrange
var (lspDocument, virtualDocument) = CreateDocuments(lspDocumentVersion: 124, virtualDocumentSyncVersion: 123);
var fileUriProvider = CreateUriProviderFor(VirtualDocumentTextBuffer, virtualDocument.Uri);
var synchronizer = new DefaultLSPDocumentSynchronizer(fileUriProvider)
{
_synchronizationTimeout = TimeSpan.FromMilliseconds(500)
};
NotifyLSPDocumentAdded(lspDocument, synchronizer);
// Act
// Start synchronization, this will hang until we notify the buffer versions been updated because the above virtual document expects host doc version 123 but the host doc is 124
var synchronizeTask1 = synchronizer.TrySynchronizeVirtualDocumentAsync(lspDocument.Version, virtualDocument, rejectOnNewerParallelRequest: false, CancellationToken.None);
var (newLSPDocument, newVirtualDocument) = CreateDocuments(lspDocumentVersion: 125, virtualDocumentSyncVersion: 124);
var synchronizeTask2 = synchronizer.TrySynchronizeVirtualDocumentAsync(newLSPDocument.Version, newVirtualDocument, CancellationToken.None);
NotifyBufferVersionUpdated(VirtualDocumentTextBuffer, lspDocument.Version);
NotifyBufferVersionUpdated(VirtualDocumentTextBuffer, newLSPDocument.Version);
var result1 = await synchronizeTask1.ConfigureAwait(false);
var result2 = await synchronizeTask2.ConfigureAwait(false);
// Assert
Assert.True(result1);
Assert.True(result2);
}
[Fact]
public async Task TrySynchronizeVirtualDocumentAsync_DocumentRemoved_CancelsActiveRequests()
{

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

@ -357,6 +357,9 @@ $@"public class SomeRazorFile
return Task.FromResult(projectionResult);
}
public override Task<ProjectionResult> GetProjectionForCompletionAsync(LSPDocumentSnapshot documentSnapshot, Position position, CancellationToken cancellationToken)
=> GetProjectionAsync(documentSnapshot, position, cancellationToken);
}
}
}

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

@ -262,6 +262,9 @@ $@"public class SomeRazorFile
return Task.FromResult(projectionResult);
}
public override Task<ProjectionResult> GetProjectionForCompletionAsync(LSPDocumentSnapshot documentSnapshot, Position position, CancellationToken cancellationToken)
=> GetProjectionAsync(documentSnapshot, position, cancellationToken);
}
}
}

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

@ -86,7 +86,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
documentManager.AddDocument(Uri, new TestLSPDocumentSnapshot(new Uri("C:/path/file.razor"), 0, CSharpVirtualDocumentSnapshot));
var requestInvoker = Mock.Of<LSPRequestInvoker>(MockBehavior.Strict);
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict).Object;
Mock.Get(projectionProvider).Setup(projectionProvider => projectionProvider.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), CancellationToken.None))
Mock.Get(projectionProvider).Setup(projectionProvider => projectionProvider.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), CancellationToken.None))
.Returns(Task.FromResult<ProjectionResult>(null));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker, documentManager, projectionProvider, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
var completionRequest = new CompletionParams()
@ -137,7 +137,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
LanguageKind = RazorLanguageKind.Html,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -175,7 +175,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
LanguageKind = RazorLanguageKind.Html,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -221,7 +221,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
LanguageKind = RazorLanguageKind.CSharp,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -272,7 +272,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
LanguageKind = RazorLanguageKind.CSharp,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -352,7 +352,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
LanguageKind = RazorLanguageKind.CSharp,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -409,7 +409,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
LanguageKind = RazorLanguageKind.CSharp,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -468,7 +468,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
LanguageKind = RazorLanguageKind.CSharp,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -548,7 +548,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
LanguageKind = RazorLanguageKind.CSharp,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, navigatorSelector, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -624,7 +624,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
LanguageKind = RazorLanguageKind.CSharp,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, navigatorSelector, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -680,7 +680,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
LanguageKind = RazorLanguageKind.Html,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -712,7 +712,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
LanguageKind = RazorLanguageKind.CSharp,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -755,7 +755,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
LanguageKind = RazorLanguageKind.CSharp,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -802,7 +802,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
LanguageKind = RazorLanguageKind.CSharp,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -856,7 +856,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
LanguageKind = RazorLanguageKind.CSharp,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -940,7 +940,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
LanguageKind = RazorLanguageKind.CSharp,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, navigatorSelector, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -991,7 +991,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
LanguageKind = RazorLanguageKind.Html,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -1101,8 +1101,8 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
HostDocumentVersion = 1,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), new Position(1, 6), It.IsAny<CancellationToken>())).Returns(Task.FromResult(previousCharacterProjection));
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), completionRequest.Position, It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), new Position(1, 6), It.IsAny<CancellationToken>())).Returns(Task.FromResult(previousCharacterProjection));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), completionRequest.Position, It.IsAny<CancellationToken>())).Returns(Task.FromResult(projectionResult));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -1202,7 +1202,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
HostDocumentVersion = 1,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(previousCharacterProjection));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(previousCharacterProjection));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -1282,7 +1282,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
LanguageKind = RazorLanguageKind.Html,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(previousCharacterProjection));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(previousCharacterProjection));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -1324,7 +1324,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
LanguageKind = RazorLanguageKind.CSharp,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(previousCharacterProjection));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(previousCharacterProjection));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -1387,7 +1387,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
HostDocumentVersion = null,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(previousCharacterProjection));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(previousCharacterProjection));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);
@ -1452,7 +1452,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
HostDocumentVersion = 1,
};
var projectionProvider = new Mock<LSPProjectionProvider>(MockBehavior.Strict);
projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(previousCharacterProjection));
projectionProvider.Setup(p => p.GetProjectionForCompletionAsync(It.IsAny<LSPDocumentSnapshot>(), It.IsAny<Position>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(previousCharacterProjection));
var completionHandler = new CompletionHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, TextStructureNavigatorSelectorService, CompletionRequestContextCache, FormattingOptionsProvider, LoggerProvider);

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

@ -88,7 +88,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
var documentSynchronizer = new Mock<LSPDocumentSynchronizer>(MockBehavior.Strict);
documentSynchronizer
.Setup(d => d.TrySynchronizeVirtualDocumentAsync(DocumentSnapshot.Version, HtmlVirtualDocumentSnapshot, It.IsAny<CancellationToken>()))
.Setup(d => d.TrySynchronizeVirtualDocumentAsync(DocumentSnapshot.Version, HtmlVirtualDocumentSnapshot, true, It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(true));
var projectionProvider = new DefaultLSPProjectionProvider(requestInvoker.Object, documentSynchronizer.Object, Mock.Of<RazorLogger>(MockBehavior.Strict), LoggerProvider);
@ -127,7 +127,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
var documentSynchronizer = new Mock<LSPDocumentSynchronizer>(MockBehavior.Strict);
documentSynchronizer
.Setup(d => d.TrySynchronizeVirtualDocumentAsync(DocumentSnapshot.Version, CSharpVirtualDocumentSnapshot, It.IsAny<CancellationToken>()))
.Setup(d => d.TrySynchronizeVirtualDocumentAsync(DocumentSnapshot.Version, CSharpVirtualDocumentSnapshot, true, It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(true));
var projectionProvider = new DefaultLSPProjectionProvider(requestInvoker.Object, documentSynchronizer.Object, Mock.Of<RazorLogger>(MockBehavior.Strict), LoggerProvider);
@ -166,7 +166,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
var documentSynchronizer = new Mock<LSPDocumentSynchronizer>(MockBehavior.Strict);
documentSynchronizer
.Setup(d => d.TrySynchronizeVirtualDocumentAsync(DocumentSnapshot.Version, HtmlVirtualDocumentSnapshot, It.IsAny<CancellationToken>()))
.Setup(d => d.TrySynchronizeVirtualDocumentAsync(DocumentSnapshot.Version, HtmlVirtualDocumentSnapshot, true, It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(true));
var logger = new Mock<RazorLogger>(MockBehavior.Strict);
@ -183,6 +183,45 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
Assert.Equal(expectedPosition, result.Position);
}
[Fact]
public async Task GetProjectionForCompletionAsync_CSharpProjection_ReturnsProjection()
{
// Arrange
var expectedPosition = new Position(0, 0);
var response = new RazorLanguageQueryResponse()
{
Kind = RazorLanguageKind.CSharp,
HostDocumentVersion = 1,
Position = new Position(expectedPosition.Line, expectedPosition.Character)
};
var requestInvoker = new Mock<LSPRequestInvoker>(MockBehavior.Strict);
requestInvoker
.Setup(r => r.ReinvokeRequestOnServerAsync<RazorLanguageQueryParams, RazorLanguageQueryResponse>(
It.IsAny<ITextBuffer>(),
It.IsAny<string>(),
RazorLSPConstants.RazorLanguageServerName,
It.IsAny<Func<JToken, bool>>(),
It.IsAny<RazorLanguageQueryParams>(),
It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(new ReinvocationResponse<RazorLanguageQueryResponse>("LanguageClient", response)));
var documentSynchronizer = new Mock<LSPDocumentSynchronizer>(MockBehavior.Strict);
documentSynchronizer
.Setup(d => d.TrySynchronizeVirtualDocumentAsync(DocumentSnapshot.Version, CSharpVirtualDocumentSnapshot, false, It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(true));
var projectionProvider = new DefaultLSPProjectionProvider(requestInvoker.Object, documentSynchronizer.Object, Mock.Of<RazorLogger>(MockBehavior.Strict), LoggerProvider);
// Act
var result = await projectionProvider.GetProjectionForCompletionAsync(DocumentSnapshot, new Position(), CancellationToken.None).ConfigureAwait(false);
// Assert
Assert.NotNull(result);
Assert.Equal(CSharpVirtualDocumentSnapshot.Uri, result.Uri);
Assert.Equal(RazorLanguageKind.CSharp, result.LanguageKind);
Assert.Equal(expectedPosition, result.Position);
}
[Fact]
public async Task GetProjectionAsync_SynchronizationFails_ReturnsNull()
{
@ -207,7 +246,7 @@ namespace Microsoft.VisualStudio.LanguageServerClient.Razor.HtmlCSharp
var documentSynchronizer = new Mock<LSPDocumentSynchronizer>(MockBehavior.Strict);
documentSynchronizer
.Setup(d => d.TrySynchronizeVirtualDocumentAsync(DocumentSnapshot.Version, CSharpVirtualDocumentSnapshot, It.IsAny<CancellationToken>()))
.Setup(d => d.TrySynchronizeVirtualDocumentAsync(DocumentSnapshot.Version, CSharpVirtualDocumentSnapshot, true, It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(false));
var projectionProvider = new DefaultLSPProjectionProvider(requestInvoker.Object, documentSynchronizer.Object, Mock.Of<RazorLogger>(MockBehavior.Strict), LoggerProvider);