|
// 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;
}
}
|