File: HostWorkspace\RazorWorkspaceListenerInitializer.cs
Web Access
Project: src\src\LanguageServer\Microsoft.CodeAnalysis.LanguageServer\Microsoft.CodeAnalysis.LanguageServer.csproj (Microsoft.CodeAnalysis.LanguageServer)
// 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.Composition;
using Microsoft.AspNetCore.Razor.ExternalAccess.RoslynWorkspace;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.Extensions.Logging;
 
namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace;
 
[Export(typeof(RazorWorkspaceListenerInitializer)), Shared]
internal sealed class RazorWorkspaceListenerInitializer
{
    private readonly ILogger _logger;
    private readonly Workspace _workspace;
    private readonly ILoggerFactory _loggerFactory;
 
    // Locks all access to _razorWorkspaceListener and _projectIdWithDynamicFiles
    private readonly object _initializeGate = new();
    private HashSet<ProjectId> _projectIdWithDynamicFiles = [];
 
    private RazorWorkspaceListener? _razorWorkspaceListener;
 
    [ImportingConstructor]
    [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
    public RazorWorkspaceListenerInitializer(LanguageServerWorkspaceFactory workspaceFactory, ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger(nameof(RazorWorkspaceListenerInitializer));
 
        _workspace = workspaceFactory.Workspace;
        _loggerFactory = loggerFactory;
    }
 
    internal void Initialize(string pipeName)
    {
        HashSet<ProjectId> projectsToInitialize;
        lock (_initializeGate)
        {
            // Only initialize once
            if (_razorWorkspaceListener is not null)
            {
                return;
            }
 
            _logger.LogTrace("Initializing the Razor workspace listener with pipe name {0}", pipeName);
            _razorWorkspaceListener = new RazorWorkspaceListener(_loggerFactory);
            _razorWorkspaceListener.EnsureInitialized(_workspace, pipeName);
 
            projectsToInitialize = _projectIdWithDynamicFiles;
            // May as well clear out the collection, it will never get used again anyway.
            _projectIdWithDynamicFiles = [];
        }
 
        foreach (var projectId in projectsToInitialize)
        {
            _logger.LogTrace("{projectId} notifying a dynamic file for the first time", projectId);
            _razorWorkspaceListener.NotifyDynamicFile(projectId);
        }
    }
 
    internal void NotifyDynamicFile(ProjectId projectId)
    {
        lock (_initializeGate)
        {
            if (_razorWorkspaceListener is null)
            {
                // We haven't been initialized by the extension yet, so just store the project id, to tell Razor later
                _logger.LogTrace("{projectId} queuing up a dynamic file notify for later", projectId);
                _projectIdWithDynamicFiles.Add(projectId);
 
                return;
            }
        }
 
        // We've been initialized, so just pass the information along
        _logger.LogTrace("{projectId} forwarding on a dynamic file notification because we're initialized", projectId);
        _razorWorkspaceListener.NotifyDynamicFile(projectId);
    }
}