File: Services\VSCodeRemoteServicesInitializer.cs
Web Access
Project: src\src\Razor\src\Razor\src\Microsoft.VisualStudioCode.RazorExtension\Microsoft.VisualStudioCode.RazorExtension.csproj (Microsoft.VisualStudioCode.RazorExtension)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Composition;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.Remote;
using Microsoft.CodeAnalysis.Razor.SemanticTokens;
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Microsoft.CodeAnalysis.Razor.Workspaces.Settings;
using Microsoft.CodeAnalysis.Remote.Razor;
using Microsoft.VisualStudio.Razor.LanguageClient.Cohost;
using Microsoft.VisualStudio.Threading;
 
namespace Microsoft.VisualStudioCode.RazorExtension.Services;
 
[Shared]
[Export(typeof(IRazorCohostStartupService))]
[method: ImportingConstructor]
internal sealed class VSCodeRemoteServicesInitializer(
    LanguageServerFeatureOptions featureOptions,
    ISemanticTokensLegendService semanticTokensLegendService,
    IWorkspaceProvider workspaceProvider,
    IClientSettingsManager clientSettingsManager,
    ILoggerFactory loggerFactory) : IRazorCohostStartupService, IDisposable
{
    private readonly LanguageServerFeatureOptions _featureOptions = featureOptions;
    private readonly ISemanticTokensLegendService _semanticTokensLegendService = semanticTokensLegendService;
    private readonly IWorkspaceProvider _workspaceProvider = workspaceProvider;
    private readonly IClientSettingsManager _clientSettingsManager = clientSettingsManager;
    private readonly ILoggerFactory _loggerFactory = loggerFactory;
 
    private IRemoteClientSettingsService? _clientSettingsService;
 
    public int Order => WellKnownStartupOrder.RemoteServices;
 
    public async Task StartupAsync(VSInternalClientCapabilities clientCapabilities, RazorCohostRequestContext requestContext, CancellationToken cancellationToken)
    {
        // Normal remote service invoker logic requires a solution, but we don't have one here. Fortunately we don't need one, and since
        // we know this is VS Code specific, its all just smoke and mirrors anyway. We can avoid the smoke :)
        var serviceInterceptor = new VSCodeBrokeredServiceInterceptor();
 
        // First things first, set the cache directory for the MEF composition.
        RemoteMefComposition.CacheDirectory = Path.Combine(Path.GetDirectoryName(this.GetType().Assembly.Location)!, "cache");
 
        var logger = _loggerFactory.GetOrCreateLogger<VSCodeRemoteServicesInitializer>();
        logger.LogDebug("Initializing remote services.");
        var service = await InProcServiceFactory.CreateServiceAsync<IRemoteClientInitializationService>(serviceInterceptor, _workspaceProvider, _loggerFactory).ConfigureAwait(false);
        logger.LogDebug("Initialized remote services.");
 
        await service.InitializeAsync(new RemoteClientInitializationOptions
        {
            ReturnCodeActionAndRenamePathsWithPrefixedSlash = _featureOptions.ReturnCodeActionAndRenamePathsWithPrefixedSlash,
            SupportsFileManipulation = _featureOptions.SupportsFileManipulation,
            ShowAllCSharpCodeActions = _featureOptions.ShowAllCSharpCodeActions,
        }, cancellationToken).ConfigureAwait(false);
 
        await service.InitializeLspAsync(new RemoteClientLSPInitializationOptions
        {
            ClientCapabilities = clientCapabilities,
            TokenTypes = _semanticTokensLegendService.TokenTypes.All,
            TokenModifiers = _semanticTokensLegendService.TokenModifiers.All,
        }, cancellationToken).ConfigureAwait(false);
 
        _clientSettingsService = await InProcServiceFactory.CreateServiceAsync<IRemoteClientSettingsService>(serviceInterceptor, _workspaceProvider, _loggerFactory).ConfigureAwait(false);
        // Client settings are initialized after this service, so there is no point updating settings at startup.
        _clientSettingsManager.ClientSettingsChanged += ClientSettingsManager_ClientSettingsChanged;
    }
 
    public void Dispose()
    {
        _clientSettingsManager?.ClientSettingsChanged -= ClientSettingsManager_ClientSettingsChanged;
    }
 
    private void ClientSettingsManager_ClientSettingsChanged(object? sender, EventArgs e)
    {
        UpdateClientSettingsAsync(CancellationToken.None).Forget();
    }
 
    private Task UpdateClientSettingsAsync(CancellationToken cancellationToken)
    {
        if (_clientSettingsService is not { } clientSettingsService)
        {
            throw new InvalidOperationException($"{nameof(VSCodeRemoteServicesInitializer)} has not been started.");
        }
 
        return clientSettingsService.UpdateAsync(_clientSettingsManager.GetClientSettings(), cancellationToken).AsTask();
    }
}