diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.cs deleted file mode 100644 index 37a86c3fe05dd..0000000000000 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.cs +++ /dev/null @@ -1,90 +0,0 @@ -// 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. - -using System.Composition; -using Microsoft.CodeAnalysis.ExternalAccess.Razor; -using Microsoft.CodeAnalysis.ExternalAccess.Razor.Features; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.Extensions.Logging; -using Roslyn.LanguageServer.Protocol; - -namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.Razor; - -[Shared] -[Export(typeof(IDynamicFileInfoProvider))] -[ExportRazorStatelessLspService(typeof(RazorDynamicFileInfoProvider))] -[ExportMetadata("Extensions", new string[] { "cshtml", "razor", })] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed partial class RazorDynamicFileInfoProvider(Lazy workspaceFactory, ILoggerFactory loggerFactory) : IDynamicFileInfoProvider, ILspService, IOnInitialized, IDisposable -{ - private RazorWorkspaceService? _razorWorkspaceService; - private RazorLspDynamicFileInfoProvider? _dynamicFileInfoProvider; - - public event EventHandler? Updated; - - private readonly ILogger _logger = loggerFactory.CreateLogger(); - - public async Task GetDynamicFileInfoAsync(ProjectId projectId, string? projectFilePath, string filePath, CancellationToken cancellationToken) - { - if (_dynamicFileInfoProvider is null || _razorWorkspaceService is null) - { - return null; - } - - _razorWorkspaceService.NotifyDynamicFile(projectId); - - var dynamicInfo = await _dynamicFileInfoProvider.GetDynamicFileInfoAsync(workspaceFactory.Value.HostWorkspace, projectId, projectFilePath, filePath, cancellationToken).ConfigureAwait(false); - if (dynamicInfo is null) - { - return null; - } - - return new DynamicFileInfo( - dynamicInfo.FilePath, - dynamicInfo.SourceCodeKind, - dynamicInfo.TextLoader, - designTimeOnly: true, - documentServiceProvider: new RazorDocumentServiceProviderWrapper(dynamicInfo.DocumentServiceProvider)); - } - - public Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) - { - _razorWorkspaceService = context.GetService(); - _dynamicFileInfoProvider = context.GetService(); - - if (_razorWorkspaceService is null || _dynamicFileInfoProvider is null) - { - _logger.LogError("RazorDynamicFileInfoProvider not initialized. RazorWorkspaceService or RazorLspDynamicFileInfoProvider is null."); - return Task.CompletedTask; - } - - _dynamicFileInfoProvider.Updated += (s, uri) => - { - var filePath = ProtocolConversions.GetDocumentFilePathFromUri(uri); - Updated?.Invoke(this, filePath); - }; - - return Task.CompletedTask; - } - - public async Task RemoveDynamicFileInfoAsync(ProjectId projectId, string? projectFilePath, string filePath, CancellationToken cancellationToken) - { - if (_dynamicFileInfoProvider is null) - { - return; - } - - await _dynamicFileInfoProvider.RemoveDynamicFileInfoAsync(workspaceFactory.Value.HostWorkspace, projectId, projectFilePath, filePath, cancellationToken).ConfigureAwait(false); - } - - public void Dispose() - { - // Dispose is called when the LSP server is being shut down. Clear the dynamic file provider in case a workspace - // event is raised after, as the actual provider will try to make LSP requests. - _dynamicFileInfoProvider = null; - } -} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/RazorDynamicDocumentSyncRegistration.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/RazorDynamicDocumentSyncRegistration.cs deleted file mode 100644 index 8dc4944473c18..0000000000000 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/RazorDynamicDocumentSyncRegistration.cs +++ /dev/null @@ -1,106 +0,0 @@ -// 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. - -using System.Composition; -using System.Text.Json.Nodes; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.Handler; -using Microsoft.CodeAnalysis.Options; -using Roslyn.LanguageServer.Protocol; - -namespace Microsoft.CodeAnalysis.LanguageServer.LanguageServer; - -[ExportCSharpVisualBasicLspServiceFactory(typeof(OnInitialized)), Shared] -[method: ImportingConstructor] -[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class RazorDynamicDocumentSyncRegistration(IGlobalOptionService globalOptionService) : ILspServiceFactory -{ - public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) - => new OnInitialized(globalOptionService); - - public sealed class OnInitialized(IGlobalOptionService globalOptionService) : IOnInitialized, ILspService - { - public async Task OnInitializedAsync(ClientCapabilities clientCapabilities, RequestContext context, CancellationToken cancellationToken) - { - // Hot reload only works in devkit scenarios. Without, there is no need to register for dynamic document sync. - if (!globalOptionService.GetOption(LspOptionsStorage.LspUsingDevkitFeatures)) - { - return; - } - - var languageServerManager = context.GetRequiredLspService(); - - // We know devkit is enabled, but we need to check cohosting too. Cohosting will register for document sync, and if we do - // it here as well, VS Code will send us duplicate open/close/change events for the same file, corrupting our documents. - if (clientCapabilities.Workspace?.Configuration == true && - await IsCohostingEnabledAsync(languageServerManager, cancellationToken).ConfigureAwait(false)) - { - return; - } - - // If dynamic registration for text document synchronization is supported, register for .razor and .cshtml files - // so that they are up to date for hot reload scenarios rather than depending on the file watchers to update - // the contents. - if (clientCapabilities.TextDocument?.Synchronization?.DynamicRegistration is true) - { - var documentFilters = new[] { new DocumentFilter() { Pattern = "**/*.{razor, cshtml}", Language = "aspnetcorerazor" } }; - var registrationOptions = new TextDocumentRegistrationOptions() - { - DocumentSelector = documentFilters - }; - - await languageServerManager.SendRequestAsync(Methods.ClientRegisterCapabilityName, - new RegistrationParams() - { - Registrations = [ - new() - { - Id = Guid.NewGuid().ToString(), // No need to save this for unregistering - Method = Methods.TextDocumentDidOpenName, - RegisterOptions = registrationOptions - }, - new() - { - Id = Guid.NewGuid().ToString(), // No need to save this for unregistering - Method = Methods.TextDocumentDidChangeName, - RegisterOptions = new TextDocumentChangeRegistrationOptions() - { - DocumentSelector = documentFilters, - SyncKind = TextDocumentSyncKind.Incremental - } - }, - new() - { - Id = Guid.NewGuid().ToString(), // No need to save this for unregistering - Method = Methods.TextDocumentDidCloseName, - RegisterOptions = registrationOptions - } - ] - }, - cancellationToken).ConfigureAwait(false); - } - } - - private static async Task IsCohostingEnabledAsync(IClientLanguageServerManager languageServerManager, CancellationToken cancellationToken) - { - var configurationParams = new ConfigurationParams() - { - Items = [ - // Roslyn's typescript config handler will convert underscores to camelcase, so this checking - // the 'razor.languageServer.cohostingEnabled' option - new ConfigurationItem { Section = "razor.language_server.cohosting_enabled" }, - ] - }; - - var options = await languageServerManager.SendRequestAsync( - Methods.WorkspaceConfigurationName, - configurationParams, - cancellationToken).ConfigureAwait(false); - - return options is [{ } result] && - result.ToString() == "true"; - } - } -} - diff --git a/src/Tools/ExternalAccess/Razor/Features/RazorLspDynamicFileInfoProvider.cs b/src/Tools/ExternalAccess/Razor/Features/RazorLspDynamicFileInfoProvider.cs index 20c75b6d500c0..db4e6b26e785f 100644 --- a/src/Tools/ExternalAccess/Razor/Features/RazorLspDynamicFileInfoProvider.cs +++ b/src/Tools/ExternalAccess/Razor/Features/RazorLspDynamicFileInfoProvider.cs @@ -8,6 +8,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Features; +[Obsolete("Not required when cohosting is the only game in town", error: false)] internal abstract class RazorLspDynamicFileInfoProvider : AbstractRazorLspService { public abstract Task GetDynamicFileInfoAsync(Workspace workspace, ProjectId projectId, string? projectFilePath, string filePath, CancellationToken cancellationToken); diff --git a/src/Tools/ExternalAccess/Razor/Features/RazorWorkspaceService.cs b/src/Tools/ExternalAccess/Razor/Features/RazorWorkspaceService.cs index ab8eeab324bf4..f4d7824002161 100644 --- a/src/Tools/ExternalAccess/Razor/Features/RazorWorkspaceService.cs +++ b/src/Tools/ExternalAccess/Razor/Features/RazorWorkspaceService.cs @@ -2,8 +2,11 @@ // 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; + namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Features; +[Obsolete("Not required when cohosting is the only game in town", error: false)] internal abstract class RazorWorkspaceService : AbstractRazorLspService { public abstract void Initialize(Workspace workspace, string pipeName);