File: LanguageServer\AlwaysActiveLanguageClientEventListener.cs
Web Access
Project: src\src\EditorFeatures\Core\Microsoft.CodeAnalysis.EditorFeatures.csproj (Microsoft.CodeAnalysis.EditorFeatures)
// 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.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Composition;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.LanguageServer.Client;
using Microsoft.VisualStudio.Threading;
 
namespace Microsoft.CodeAnalysis.Editor.Implementation.LanguageClient;
 
// unfortunately, we can't implement this on LanguageServerClient since this uses MEF v2 and
// ILanguageClient requires MEF v1 and two can't be mixed exported in 1 class.
[Export]
[ExportEventListener(WellKnownEventListeners.Workspace, WorkspaceKind.Host), Shared]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal class AlwaysActiveLanguageClientEventListener(
    AlwaysActivateInProcLanguageClient languageClient,
    Lazy<ILanguageClientBroker> languageClientBroker,
    IAsynchronousOperationListenerProvider listenerProvider) : IEventListener<object>
{
    private readonly AlwaysActivateInProcLanguageClient _languageClient = languageClient;
    private readonly Lazy<ILanguageClientBroker> _languageClientBroker = languageClientBroker;
 
    private readonly IAsynchronousOperationListener _asynchronousOperationListener = listenerProvider.GetListener(FeatureAttribute.LanguageServer);
 
    /// <summary>
    /// LSP clients do not necessarily know which language servers (and when) to activate as they are language
    /// agnostic.  We know we can provide <see cref="AlwaysActivateInProcLanguageClient"/> as soon as the
    /// workspace is started, so tell the <see cref="ILanguageClientBroker"/> to start loading it.
    /// </summary>
    public void StartListening(Workspace workspace, object serviceOpt)
    {
        // Trigger a fire and forget request to the VS LSP client to load our ILanguageClient.
        _ = LoadAsync();
    }
 
    private async Task LoadAsync()
    {
        try
        {
            using var token = _asynchronousOperationListener.BeginAsyncOperation(nameof(LoadAsync));
 
            // Explicitly switch to the bg so that if this causes any expensive work (like mef loads) it 
            // doesn't block the UI thread. Note, we always yield because sometimes our caller starts
            // on the threadpool thread but is indirectly blocked on by the UI thread.
            await TaskScheduler.Default.SwitchTo(alwaysYield: true);
 
            await _languageClientBroker.Value.LoadAsync(new LanguageClientMetadata(
            [
                ContentTypeNames.CSharpContentType,
                ContentTypeNames.VisualBasicContentType,
                ContentTypeNames.FSharpContentType
            ]), _languageClient).ConfigureAwait(false);
        }
        catch (Exception e) when (FatalError.ReportAndCatch(e))
        {
        }
    }
 
    /// <summary>
    /// The <see cref="ILanguageClientBroker.LoadAsync(ILanguageClientMetadata, ILanguageClient)"/> 
    /// requires that we pass the <see cref="ILanguageClientMetadata"/> along with the language client instance.
    /// The implementation of <see cref="ILanguageClientMetadata"/> is not public, so have to re-implement.
    /// https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1043922 tracking to remove this.
    /// </summary>
    private class LanguageClientMetadata(string[] contentTypes, string clientName = null) : ILanguageClientMetadata
    {
        public string ClientName { get; } = clientName;
 
        public IEnumerable<string> ContentTypes { get; } = contentTypes;
    }
}