diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.cs index 57d0d90555c93..1089ab88e8d78 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/LanguageServerProjectLoader.cs @@ -133,7 +133,7 @@ public async Task ReportErrorAsync(LSP.MessageType errorKind, string message, Ca } } - private async ValueTask ReloadProjectsAsync(ImmutableSegmentedList projectPathsToLoadOrReload, CancellationToken cancellationToken) + private async ValueTask ReloadProjectsAsync(ImmutableSegmentedList projectsToLoadOrReload, CancellationToken cancellationToken) { var stopwatch = Stopwatch.StartNew(); @@ -145,15 +145,15 @@ private async ValueTask ReloadProjectsAsync(ImmutableSegmentedList.RunParallelAsync( - source: projectPathsToLoadOrReload, - produceItems: static async (projectToLoad, callback, args, cancellationToken) => + source: projectsToLoadOrReload, + produceItems: static async (projectToLoad, produceItem, args, cancellationToken) => { var (@this, toastErrorReporter, buildHostProcessManager) = args; var projectNeedsRestore = await @this.ReloadProjectAsync( projectToLoad, toastErrorReporter, buildHostProcessManager, cancellationToken); if (projectNeedsRestore) - callback(projectToLoad.Path); + produceItem(projectToLoad.Path); }, args: (@this: this, toastErrorReporter, buildHostProcessManager), cancellationToken).ConfigureAwait(false); @@ -165,7 +165,7 @@ private async ValueTask ReloadProjectsAsync(ImmutableSegmentedList projectPaths, CancellationToken cancellationToken) + internal static async Task SendProjectNeedsRestoreRequestAsync(ImmutableArray projectPaths, CancellationToken cancellationToken) { if (projectPaths.IsEmpty) return; diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectInitializationHandler.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectInitializationHandler.cs index 7187af4dad186..95853e978f9fd 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectInitializationHandler.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/ProjectInitializationHandler.cs @@ -42,7 +42,7 @@ public ProjectInitializationHandler(IServiceBrokerProvider serviceBrokerProvider _projectInitializationCompleteObserver = new ProjectInitializationCompleteObserver(_logger); } - public static async Task SendProjectInitializationCompleteNotificationAsync() + public static async ValueTask SendProjectInitializationCompleteNotificationAsync() { Contract.ThrowIfNull(LanguageServerHost.Instance, "We don't have an LSP channel yet to send this request through."); var languageServerManager = LanguageServerHost.Instance.GetRequiredLspService(); @@ -113,7 +113,7 @@ public void OnNext(ProjectInitializationCompletionState value) { _logger.LogDebug("Devkit project initialization completed"); VSCodeRequestTelemetryLogger.ReportProjectInitializationComplete(); - _ = SendProjectInitializationCompleteNotificationAsync().ReportNonFatalErrorAsync(); + _ = SendProjectInitializationCompleteNotificationAsync().AsTask().ReportNonFatalErrorAsync(); } } } diff --git a/src/LanguageServer/Protocol/Handler/Testing/WaitForAsyncOperationsHandler.cs b/src/LanguageServer/Protocol/Handler/Testing/WaitForAsyncOperationsHandler.cs new file mode 100644 index 0000000000000..904091915f3e7 --- /dev/null +++ b/src/LanguageServer/Protocol/Handler/Testing/WaitForAsyncOperationsHandler.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.TestHooks; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.TestHooks; + +/// +/// Implements an LSP request handler to allow the integration tests on the client side to wait for certain server side operations to complete. +/// This is useful in a few cases where the client makes requests to the server but does not wait for the processing to complete, for example +/// 1. Loading projects +/// 2. Reacting to LSP notifications (which are fire and forget) +/// +/// This should generally only be used as a last resort when it is impossible for the client to wait specifically for a result it asked for. +/// +[ExportCSharpVisualBasicStatelessLspService(typeof(WaitForAsyncOperationsHandler)), Shared] +[Method(MethodName)] +internal class WaitForAsyncOperationsHandler : ILspServiceRequestHandler +{ + internal const string MethodName = "workspace/waitForAsyncOperations"; + + private readonly AsynchronousOperationListenerProvider _provider; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public WaitForAsyncOperationsHandler(AsynchronousOperationListenerProvider listenerProvider) + { + _provider = listenerProvider; + } + + public bool MutatesSolutionState => false; + + public bool RequiresLSPSolution => true; + + public async Task HandleRequestAsync(WaitForAsyncOperationsParams request, RequestContext context, CancellationToken _) + { + context.TraceInformation($"Waiting for {string.Join(", ", request.Operations)} to complete"); + await _provider.WaitAllAsync(context.Solution!.Workspace, request.Operations).ConfigureAwait(false); + return new WaitForAsyncOperationsResponse(); + } +} + +internal record WaitForAsyncOperationsParams([property: JsonPropertyName("operations")] string[] Operations); + +internal record WaitForAsyncOperationsResponse();