diff --git a/src/Tools/ExternalAccess/LiveShare/RemoteLanguageServiceWorkspaceHost.cs b/src/Tools/ExternalAccess/LiveShare/RemoteLanguageServiceWorkspaceHost.cs index e97147443449e90b044c525dcd07f5d771df015a..a1a94215cf281a5dffd48b5bd6df506fbfe4acd5 100644 --- a/src/Tools/ExternalAccess/LiveShare/RemoteLanguageServiceWorkspaceHost.cs +++ b/src/Tools/ExternalAccess/LiveShare/RemoteLanguageServiceWorkspaceHost.cs @@ -6,8 +6,13 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ExternalAccess.LiveShare.Projects; +using Microsoft.CodeAnalysis.Options; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.LanguageServices.Implementation.Options; using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; +using Microsoft.VisualStudio.LanguageServices.Setup; using Microsoft.VisualStudio.LiveShare; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Threading; @@ -37,6 +42,7 @@ internal sealed class RemoteLanguageServiceWorkspaceHost : ICollaborationService // TODO: remove this project language to extension map with the switch to LSP private readonly ImmutableDictionary _projectLanguageToExtensionMap; private readonly SVsServiceProvider _serviceProvider; + private readonly IThreadingContext _threadingContext; public Workspace Workspace => _remoteLanguageServiceWorkspace; @@ -47,11 +53,13 @@ internal sealed class RemoteLanguageServiceWorkspaceHost : ICollaborationService [ImportingConstructor] public RemoteLanguageServiceWorkspaceHost(RemoteLanguageServiceWorkspace remoteLanguageServiceWorkspace, RemoteProjectInfoProvider remoteProjectInfoProvider, - SVsServiceProvider serviceProvider) + SVsServiceProvider serviceProvider, + IThreadingContext threadingContext) { _remoteLanguageServiceWorkspace = Requires.NotNull(remoteLanguageServiceWorkspace, nameof(remoteLanguageServiceWorkspace)); _remoteProjectInfoProvider = Requires.NotNull(remoteProjectInfoProvider, nameof(remoteProjectInfoProvider)); _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + _threadingContext = Requires.NotNull(threadingContext, nameof(threadingContext)); var builder = ImmutableDictionary.CreateBuilder(StringComparer.OrdinalIgnoreCase); builder.Add("TypeScript", new string[] { ".js", ".jsx", ".ts", ".tsx" }); @@ -62,6 +70,8 @@ internal sealed class RemoteLanguageServiceWorkspaceHost : ICollaborationService public async Task CreateServiceAsync(CollaborationSession collaborationSession, CancellationToken cancellationToken) { await _remoteLanguageServiceWorkspace.SetSession(collaborationSession).ConfigureAwait(false); + + await InitOptionsAsync(cancellationToken).ConfigureAwait(false); _remoteLanguageServiceWorkspace.Init(); // Kick off loading the projects in the background. @@ -111,6 +121,21 @@ public async Task EnsureProjectsLoadedAsync(CancellationToken cancellationToken) } } + /// + /// Initialize the options. This must be done on the UI thread because + /// multiple are required to be initialized on the UI thread. + /// Typically this is done by but this is not to guaranteed + /// to procede liveshare initialization. + /// TODO - https://github.com/dotnet/roslyn/issues/37377 + /// + private async Task InitOptionsAsync(CancellationToken cancellationToken) + { + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + var componentModel = (IComponentModel)_serviceProvider.GetService(typeof(SComponentModel)); + // Ensure the options persisters are loaded since we have to fetch options from the shell + componentModel.GetExtensions(); + } + /// /// Loads (or reloads) the corresponding Roslyn project and the direct referenced projects in the host environment. ///