diff --git a/src/LanguageServer/Protocol/Handler/AbstractRefreshQueue.cs b/src/LanguageServer/Protocol/Handler/AbstractRefreshQueue.cs index 436a66f1f83f6..47c8c54042299 100644 --- a/src/LanguageServer/Protocol/Handler/AbstractRefreshQueue.cs +++ b/src/LanguageServer/Protocol/Handler/AbstractRefreshQueue.cs @@ -30,8 +30,6 @@ internal abstract class AbstractRefreshQueue : private readonly CancellationTokenSource _disposalTokenSource; private readonly LspWorkspaceRegistrationService _lspWorkspaceRegistrationService; - protected bool _isQueueCreated; - protected abstract string GetFeatureAttribute(); protected abstract bool? GetRefreshSupport(ClientCapabilities clientCapabilities); protected abstract string GetWorkspaceRefreshName(); @@ -42,7 +40,6 @@ public AbstractRefreshQueue( LspWorkspaceManager lspWorkspaceManager, IClientLanguageServerManager notificationManager) { - _isQueueCreated = false; _asyncListener = asynchronousOperationListenerProvider.GetListener(GetFeatureAttribute()); _lspWorkspaceRegistrationService = lspWorkspaceRegistrationService; _disposalTokenSource = new(); @@ -71,7 +68,6 @@ public void Initialize(ClientCapabilities clientCapabilities) equalityComparer: EqualityComparer.Default, asyncListener: _asyncListener, _disposalTokenSource.Token); - _isQueueCreated = true; _lspWorkspaceRegistrationService.LspSolutionChanged += OnLspSolutionChanged; } } @@ -95,16 +91,15 @@ protected virtual void OnLspSolutionChanged(object? sender, WorkspaceChangeEvent } } + /// + /// Enqueues a request to refresh the workspace. If is null, then the refresh will + /// always happen. If non-null, the refresh will only happen if the client is not tracking that document. + /// If the client is tracking the document, no refresh is necessary as the client clearly knows about the change. + /// protected void EnqueueRefreshNotification(DocumentUri? documentUri) - { - if (_isQueueCreated) - { - Contract.ThrowIfNull(_refreshQueue); - _refreshQueue.AddWork(documentUri); - } - } + => _refreshQueue?.AddWork(documentUri); - private ValueTask FilterLspTrackedDocumentsAsync( + private async ValueTask FilterLspTrackedDocumentsAsync( LspWorkspaceManager lspWorkspaceManager, IClientLanguageServerManager notificationManager, ImmutableSegmentedList documentUris, @@ -117,7 +112,11 @@ private ValueTask FilterLspTrackedDocumentsAsync( { try { - return notificationManager.SendRequestAsync(GetWorkspaceRefreshName(), cancellationToken); + // Fire the notification and immediately return. Refresh notifications are server-wide, and are not + // associated with a particular project/document. So once we've sent one, we can stop processing + // entirely. + await notificationManager.SendRequestAsync(GetWorkspaceRefreshName(), cancellationToken).ConfigureAwait(false); + return; } catch (Exception ex) when (ex is ObjectDisposedException or ConnectionLostException) { @@ -128,7 +127,6 @@ private ValueTask FilterLspTrackedDocumentsAsync( } // LSP is already tracking all changed documents so we don't need to send a refresh request. - return ValueTask.CompletedTask; } public virtual void Dispose() diff --git a/src/LanguageServer/Protocol/Handler/SourceGenerators/SourceGeneratorRefreshQueue.cs b/src/LanguageServer/Protocol/Handler/SourceGenerators/SourceGeneratorRefreshQueue.cs index 3658140436c78..1e2cf287c54e5 100644 --- a/src/LanguageServer/Protocol/Handler/SourceGenerators/SourceGeneratorRefreshQueue.cs +++ b/src/LanguageServer/Protocol/Handler/SourceGenerators/SourceGeneratorRefreshQueue.cs @@ -120,27 +120,25 @@ await newProject.GetDependentVersionAsync(_disposalTokenSource.Token).ConfigureA } } - private ValueTask RefreshSourceGeneratedDocumentsAsync( + private async ValueTask RefreshSourceGeneratedDocumentsAsync( CancellationToken cancellationToken) { var hasOpenSourceGeneratedDocuments = _lspWorkspaceManager.GetTrackedLspText().Keys.Any(uri => uri.ParsedUri?.Scheme == SourceGeneratedDocumentUri.Scheme); if (!hasOpenSourceGeneratedDocuments) { // There are no opened source generated documents - we don't need to bother asking the client to refresh anything. - return ValueTask.CompletedTask; + return; } try { - return _notificationManager.SendNotificationAsync(RefreshSourceGeneratedDocumentName, cancellationToken); + await _notificationManager.SendNotificationAsync(RefreshSourceGeneratedDocumentName, cancellationToken).ConfigureAwait(false); } catch (Exception ex) when (ex is ObjectDisposedException or ConnectionLostException) { // It is entirely possible that we're shutting down and the connection is lost while we're trying to send a notification // as this runs outside of the guaranteed ordering in the queue. We can safely ignore this exception. } - - return ValueTask.CompletedTask; } public void Dispose()