File: Handler\SourceGenerators\SourceGeneratorRefreshQueue.cs
Web Access
Project: src\src\LanguageServer\Protocol\Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj (Microsoft.CodeAnalysis.LanguageServer.Protocol)
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.LanguageServer.Handler.TextDocumentContent;
using Microsoft.CodeAnalysis.Shared.TestHooks;
 
namespace Microsoft.CodeAnalysis.LanguageServer.Handler.SourceGenerators;
 
/// <summary>
/// Refresh queue for source generated documents. Detects when source generator output may have changed
/// (via execution version or dependent version checks) and sends per-URI refresh notifications to the client
/// using the LSP 3.18 <c>workspace/textDocumentContent/refresh</c> mechanism.
/// </summary>
internal sealed class SourceGeneratorRefreshQueue(
    IAsynchronousOperationListenerProvider asynchronousOperationListenerProvider,
    LspWorkspaceRegistrationService lspWorkspaceRegistrationService,
    LspWorkspaceManager lspWorkspaceManager,
    IClientLanguageServerManager notificationManager)
    : AbstractTextDocumentContentRefreshQueue(asynchronousOperationListenerProvider, lspWorkspaceRegistrationService, lspWorkspaceManager, notificationManager)
{
    protected override string Scheme => SourceGeneratedDocumentUri.Scheme;
 
    protected override async Task<bool> ShouldEnqueueRefreshNotificationAsync(WorkspaceChangeEventArgs e, CancellationToken cancellationToken)
    {
        var projectId = e.ProjectId ?? e.DocumentId?.ProjectId;
        if (projectId is not null)
        {
            // We have a specific changed project - do some additional checks to see if
            // source generators possibly changed.
 
            var oldProject = e.OldSolution.GetProject(projectId);
            var newProject = e.NewSolution.GetProject(projectId);
 
            // If the project has been added/removed, we need to update the generated files.
            if (oldProject is null || newProject is null)
            {
                return true;
            }
 
            // Trivial check.  see if the SG version of these projects changed.  If so, we definitely want to update generated files.
            if (e.OldSolution.GetSourceGeneratorExecutionVersion(projectId) !=
                e.NewSolution.GetSourceGeneratorExecutionVersion(projectId))
            {
                return true;
            }
 
            var configuration = e.NewSolution.Services.GetRequiredService<IWorkspaceConfigurationService>().Options;
            // When running in balanced mode, we do not need to refresh generated documents if only a document changed and the execution version did not.
            if (e.Kind is WorkspaceChangeKind.DocumentChanged &&
                configuration.SourceGeneratorExecution == SourceGeneratorExecutionPreference.Balanced)
            {
                return false;
            }
 
            // More expensive check - see if the dependent versions are different.
            if (await oldProject.GetDependentVersionAsync(cancellationToken).ConfigureAwait(false) !=
                await newProject.GetDependentVersionAsync(cancellationToken).ConfigureAwait(false))
            {
                return true;
            }
        }
        else
        {
            // We don't have a specific project change - if this is a solution change we need to queue a refresh anyway.
            if (e.Kind is WorkspaceChangeKind.SolutionChanged or WorkspaceChangeKind.SolutionAdded or WorkspaceChangeKind.SolutionRemoved or WorkspaceChangeKind.SolutionReloaded or WorkspaceChangeKind.SolutionCleared)
            {
                return true;
            }
        }
 
        return false;
    }
}