未验证 提交 6a099895 编写于 作者: D David 提交者: GitHub

Merge pull request #40257 from dotnet/dev/dibarbet/activate_language_client

Activate language client during workspace load.
......@@ -13,9 +13,9 @@
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Experiments;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.ServiceHub.Client;
using Microsoft.VisualStudio.LanguageServer.Client;
using Microsoft.VisualStudio.LanguageServices.Remote;
......@@ -27,7 +27,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService
[ContentType(ContentTypeNames.CSharpContentType)]
[ContentType(ContentTypeNames.VisualBasicContentType)]
[Export(typeof(ILanguageClient))]
[ExportMetadata("Capabilities", "WorkspaceStreamingSymbolProvider")]
[Export(typeof(LanguageServerClient))]
internal sealed class LanguageServerClient : ILanguageClient
{
private const string ServiceHubClientName = "ManagedLanguage.IDE.LanguageServer";
......@@ -35,8 +35,6 @@ internal sealed class LanguageServerClient : ILanguageClient
private readonly IThreadingContext _threadingContext;
private readonly Workspace _workspace;
private readonly IEnumerable<Lazy<IOptionPersister>> _lazyOptions;
private readonly LanguageServerClientEventListener _eventListener;
private readonly IAsynchronousOperationListener _asyncListener;
/// <summary>
/// Gets the name of the language client (displayed to the user).
......@@ -70,15 +68,11 @@ internal sealed class LanguageServerClient : ILanguageClient
public LanguageServerClient(
IThreadingContext threadingContext,
VisualStudioWorkspace workspace,
[ImportMany]IEnumerable<Lazy<IOptionPersister>> lazyOptions,
LanguageServerClientEventListener eventListener,
IAsynchronousOperationListenerProvider listenerProvider)
[ImportMany]IEnumerable<Lazy<IOptionPersister>> lazyOptions)
{
_threadingContext = threadingContext;
_workspace = workspace;
_lazyOptions = lazyOptions;
_eventListener = eventListener;
_asyncListener = listenerProvider.GetListener(FeatureAttribute.LanguageServerWorkspaceSymbolSearch);
}
public async Task<Connection> ActivateAsync(CancellationToken cancellationToken)
......@@ -86,7 +80,10 @@ public async Task<Connection> ActivateAsync(CancellationToken cancellationToken)
var client = await RemoteHostClient.TryGetClientAsync(_workspace, cancellationToken).ConfigureAwait(false);
if (client == null)
{
// there is no OOP. either user turned it off, or process got killed.
// There is no OOP. either user turned it off, or process got killed.
// We should have already gotten a gold bar + nfw already if the OOP is missing.
// so just log telemetry here so we can connect the two with session explorer.
Logger.Log(FunctionId.LanguageServer_ActivateFailed, KeyValueLogMessage.NoProperty);
return null;
}
......@@ -104,38 +101,35 @@ public async Task<Connection> ActivateAsync(CancellationToken cancellationToken)
}
/// <summary>
/// Signals that the extension has been loaded. The server can be started immediately, or wait for user action to start.
/// To start the server, invoke the <see cref="StartAsync"/> event;
/// Signals that the extension has been loaded.
/// The caller expects that <see cref="ActivateAsync(CancellationToken)"/> can be called
/// immediately following the completion of this method.
/// </summary>
public Task OnLoadedAsync()
public async Task OnLoadedAsync()
{
var token = _asyncListener.BeginAsyncOperation("OnLoadedAsync");
// initialize things on UI thread
await InitializeOnUIAsync().ConfigureAwait(false);
// set up event stream so that we start LSP server once Roslyn is loaded
_eventListener.WorkspaceStarted.ContinueWith(async _ =>
// this might get called before solution is fully loaded and before file is opened.
// we delay our OOP start until then, but user might do vsstart before that. so we make sure we start OOP if
// it is not running yet. multiple start is no-op
((RemoteHostClientServiceFactory.RemoteHostClientService)_workspace.Services.GetService<IRemoteHostClientService>()).Enable();
// wait until remote host is available before let platform know that they can activate our LSP
var client = await RemoteHostClient.TryGetClientAsync(_workspace, CancellationToken.None).ConfigureAwait(false);
if (client == null)
{
// initialize things on UI thread
await InitializeOnUIAsync().ConfigureAwait(false);
// this might get called before solution is fully loaded and before file is opened.
// we delay our OOP start until then, but user might do vsstart before that. so we make sure we start OOP if
// it is not running yet. multiple start is no-op
((RemoteHostClientServiceFactory.RemoteHostClientService)_workspace.Services.GetService<IRemoteHostClientService>()).Enable();
// wait until remote host is available before let platform know that they can activate our LSP
var client = await RemoteHostClient.TryGetClientAsync(_workspace, CancellationToken.None).ConfigureAwait(false);
if (client == null)
{
// there is no OOP. either user turned it off, or process got killed.
// don't ask platform to start LSP
return;
}
// let platform know that they can start us
await StartAsync.InvokeAsync(this, EventArgs.Empty).ConfigureAwait(false);
}, TaskScheduler.Default).CompletesAsyncOperation(token);
// There is no OOP. either user turned it off, or process got killed.
// We should have already gotten a gold bar + nfw already if the OOP is missing.
// so just log telemetry here so we can connect the two with session explorer.
Logger.Log(FunctionId.LanguageServer_OnLoadedFailed, KeyValueLogMessage.NoProperty);
// don't ask platform to start LSP.
// we shouldn't throw as the LSP client does not expect exceptions here.
return;
}
return Task.CompletedTask;
// let platform know that they can start us
await StartAsync.InvokeAsync(this, EventArgs.Empty).ConfigureAwait(false);
async Task InitializeOnUIAsync()
{
......
......@@ -2,10 +2,17 @@
// 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;
using System.Collections.Generic;
using System.Composition;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor;
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.VisualStudio.LanguageServices.Implementation.LanguageService
{
......@@ -15,19 +22,54 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService
[ExportEventListener(WellKnownEventListeners.Workspace, WorkspaceKind.Host), Shared]
internal class LanguageServerClientEventListener : IEventListener<object>
{
private readonly TaskCompletionSource<object> _taskCompletionSource;
private readonly LanguageServerClient _languageServerClient;
private readonly Lazy<ILanguageClientBroker> _languageClientBroker;
public Task WorkspaceStarted => _taskCompletionSource.Task;
private readonly IAsynchronousOperationListener _asynchronousOperationListener;
public LanguageServerClientEventListener()
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public LanguageServerClientEventListener(LanguageServerClient languageServerClient, Lazy<ILanguageClientBroker> languageClientBroker,
IAsynchronousOperationListenerProvider listenerProvider)
{
_taskCompletionSource = new TaskCompletionSource<object>();
this._languageServerClient = languageServerClient;
this._languageClientBroker = languageClientBroker;
this._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="LanguageServerClient"/> 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)
{
// mark that roslyn solution is added
_taskCompletionSource.SetResult(null);
var token = this._asynchronousOperationListener.BeginAsyncOperation("LoadAsync");
// Trigger a fire and forget request to the VS LSP client to load our ILanguageClient.
// This needs to be done with .Forget() as the LoadAsync (VS LSP client) synchronously stores the result task of OnLoadedAsync.
// The synchronous execution happens under the sln load threaded wait dialog, so user actions cannot be made in between triggering LoadAsync and storing the result task from OnLoadedAsync.
// The result task from OnLoadedAsync is waited on before invoking LSP requests to the ILanguageClient.
this._languageClientBroker.Value.LoadAsync(new LanguageClientMetadata(new string[] { ContentTypeNames.CSharpContentType, ContentTypeNames.VisualBasicContentType }), this._languageServerClient)
.CompletesAsyncOperation(token).Forget();
}
/// <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 : ILanguageClientMetadata
{
public LanguageClientMetadata(string[] contentTypes, string clientName = null)
{
this.ContentTypes = contentTypes;
this.ClientName = clientName;
}
public string ClientName { get; }
public IEnumerable<string> ContentTypes { get; }
}
}
}
......@@ -472,5 +472,8 @@ internal enum FunctionId
DiagnosticAnalyzerService_GetDiagnosticsForSpanAsync = 380,
CodeFixes_GetCodeFixesAsync = 381,
LanguageServer_ActivateFailed = 382,
LanguageServer_OnLoadedFailed = 383,
}
}
......@@ -40,7 +40,7 @@ internal partial class FeatureAttribute
public const string Snippets = nameof(Snippets);
public const string SolutionCrawler = nameof(SolutionCrawler);
public const string TodoCommentList = nameof(TodoCommentList);
public const string LanguageServerWorkspaceSymbolSearch = nameof(LanguageServerWorkspaceSymbolSearch);
public const string LanguageServer = nameof(LanguageServer);
public const string Workspace = nameof(Workspace);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册