diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileChangedHandler.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileChangedHandler.cs new file mode 100644 index 0000000000000..48c1c3fa8595f --- /dev/null +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileChangedHandler.cs @@ -0,0 +1,34 @@ +// 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.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.Handler; + +namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.Razor; + +[Shared] +[ExportCSharpVisualBasicStatelessLspService(typeof(RazorDynamicFileChangedHandler))] +[Method("razor/dynamicFileInfoChanged")] +internal class RazorDynamicFileChangedHandler : ILspServiceNotificationHandler +{ + private readonly RazorDynamicFileInfoProvider _razorDynamicFileInfoProvider; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public RazorDynamicFileChangedHandler(RazorDynamicFileInfoProvider razorDynamicFileInfoProvider) + { + _razorDynamicFileInfoProvider = razorDynamicFileInfoProvider; + } + + public bool MutatesSolutionState => false; + public bool RequiresLSPSolution => false; + + public Task HandleNotificationAsync(RazorDynamicFileChangedParams request, RequestContext requestContext, CancellationToken cancellationToken) + { + var filePath = ProtocolConversions.GetDocumentFilePathFromUri(request.RazorDocument.Uri); + _razorDynamicFileInfoProvider.Update(filePath); + return Task.CompletedTask; + } +} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileChangedParams.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileChangedParams.cs new file mode 100644 index 0000000000000..73a2cf4dd186c --- /dev/null +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileChangedParams.cs @@ -0,0 +1,14 @@ +// 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.Text.Json.Serialization; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.Razor; + +internal class RazorDynamicFileChangedParams +{ + [JsonPropertyName("razorDocument")] + public required TextDocumentIdentifier RazorDocument { get; set; } +} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.TextChangesTextLoader.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.TextChangesTextLoader.cs new file mode 100644 index 0000000000000..a4d5b99895f50 --- /dev/null +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.TextChangesTextLoader.cs @@ -0,0 +1,96 @@ +// 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.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.LanguageServer.LanguageServer; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.Razor; + +internal partial class RazorDynamicFileInfoProvider +{ + private sealed class TextChangesTextLoader( + TextDocument? document, + IEnumerable changes, + byte[] checksum, + SourceHashAlgorithm checksumAlgorithm, + int? codePage, + Uri razorUri) : TextLoader + { + private readonly Lazy _emptySourceText = new Lazy(() => + { + var encoding = codePage is null ? null : Encoding.GetEncoding(codePage.Value); + return SourceText.From("", checksumAlgorithm: checksumAlgorithm, encoding: encoding); + }); + + public override async Task LoadTextAndVersionAsync(LoadTextOptions options, CancellationToken cancellationToken) + { + if (document is null) + { + var text = _emptySourceText.Value.WithChanges(changes); + return TextAndVersion.Create(text, VersionStamp.Default.GetNewerVersion()); + } + + var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + + // Validate the checksum information so the edits are known to be correct + if (IsSourceTextMatching(sourceText)) + { + var version = await document.GetTextVersionAsync(cancellationToken).ConfigureAwait(false); + var newText = sourceText.WithChanges(changes); + return TextAndVersion.Create(newText, version.GetNewerVersion()); + } + + return await GetFullDocumentFromServerAsync(razorUri, cancellationToken).ConfigureAwait(false); + } + + private bool IsSourceTextMatching(SourceText sourceText) + { + if (sourceText.ChecksumAlgorithm != checksumAlgorithm) + { + return false; + } + + if (sourceText.Encoding?.CodePage != codePage) + { + return false; + } + + if (!sourceText.GetChecksum().SequenceEqual(checksum)) + { + return false; + } + + return true; + } + + private async Task GetFullDocumentFromServerAsync(Uri razorUri, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(LanguageServerHost.Instance, "We don't have an LSP channel yet to send this request through."); + var clientLanguageServerManager = LanguageServerHost.Instance.GetRequiredLspService(); + + var response = await clientLanguageServerManager.SendRequestAsync( + ProvideRazorDynamicFileInfoMethodName, + new RazorProvideDynamicFileParams + { + RazorDocument = new() + { + Uri = razorUri, + }, + FullText = true + }, + cancellationToken); + + RoslynDebug.AssertNotNull(response.Edits); + RoslynDebug.Assert(response.Edits.IsSingle()); + + var textChanges = response.Edits.Select(e => new TextChange(e.Span.ToTextSpan(), e.NewText)); + var text = _emptySourceText.Value.WithChanges(textChanges); + return TextAndVersion.Create(text, VersionStamp.Default.GetNewerVersion()); + } + } + +} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.cs new file mode 100644 index 0000000000000..f6a7c1f4147b6 --- /dev/null +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorDynamicFileInfoProvider.cs @@ -0,0 +1,148 @@ +// 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; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer.LanguageServer; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.Razor; + +[Shared] +[Export(typeof(IDynamicFileInfoProvider))] +[Export(typeof(RazorDynamicFileInfoProvider))] +[ExportMetadata("Extensions", new string[] { "cshtml", "razor", })] +internal partial class RazorDynamicFileInfoProvider : IDynamicFileInfoProvider +{ + private const string ProvideRazorDynamicFileInfoMethodName = "razor/provideDynamicFileInfo"; + private const string RemoveRazorDynamicFileInfoMethodName = "razor/removeDynamicFileInfo"; + + private readonly Lazy _razorWorkspaceListenerInitializer; + private readonly LanguageServerWorkspaceFactory _workspaceFactory; + private readonly AsyncBatchingWorkQueue _updateWorkQueue; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public RazorDynamicFileInfoProvider( + Lazy razorWorkspaceListenerInitializer, + LanguageServerWorkspaceFactory workspaceFactory, + IAsynchronousOperationListenerProvider listenerProvider) + { + _razorWorkspaceListenerInitializer = razorWorkspaceListenerInitializer; + _updateWorkQueue = new AsyncBatchingWorkQueue( + TimeSpan.FromMilliseconds(200), + UpdateAsync, + listenerProvider.GetListener(nameof(RazorDynamicFileInfoProvider)), + CancellationToken.None); + _workspaceFactory = workspaceFactory; + } + + public event EventHandler? Updated; + + public void Update(string filePath) + { + _updateWorkQueue.AddWork(filePath); + } + + public async Task GetDynamicFileInfoAsync(ProjectId projectId, string? projectFilePath, string filePath, CancellationToken cancellationToken) + { + _razorWorkspaceListenerInitializer.Value.NotifyDynamicFile(projectId); + + var razorUri = ProtocolConversions.CreateAbsoluteUri(filePath); + var requestParams = new RazorProvideDynamicFileParams + { + RazorDocument = new() + { + Uri = razorUri + } + }; + + Contract.ThrowIfNull(LanguageServerHost.Instance, "We don't have an LSP channel yet to send this request through."); + var clientLanguageServerManager = LanguageServerHost.Instance.GetRequiredLspService(); + + var response = await clientLanguageServerManager.SendRequestAsync( + ProvideRazorDynamicFileInfoMethodName, requestParams, cancellationToken); + + if (response.CSharpDocument is null) + { + return null; + } + + // Since we only sent one file over, we should get either zero or one URI back + var responseUri = response.CSharpDocument.Uri; + var dynamicFileInfoFilePath = ProtocolConversions.GetDocumentFilePathFromUri(responseUri); + + if (response.Edits is not null) + { + var textDocument = await _workspaceFactory.Workspace.CurrentSolution.GetTextDocumentAsync(response.CSharpDocument, cancellationToken).ConfigureAwait(false); + var textChanges = response.Edits.Select(e => new TextChange(e.Span.ToTextSpan(), e.NewText)); + var checksum = Convert.FromBase64String(response.Checksum); + var textLoader = new TextChangesTextLoader( + textDocument, + textChanges, + checksum, + response.ChecksumAlgorithm, + response.SourceEncodingCodePage, + razorUri); + + return new DynamicFileInfo( + dynamicFileInfoFilePath, + SourceCodeKind.Regular, + textLoader, + designTimeOnly: true, + documentServiceProvider: null); + } + + return new DynamicFileInfo( + dynamicFileInfoFilePath, + SourceCodeKind.Regular, + EmptyStringTextLoader.Instance, + designTimeOnly: true, + documentServiceProvider: null); + } + + public Task RemoveDynamicFileInfoAsync(ProjectId projectId, string? projectFilePath, string filePath, CancellationToken cancellationToken) + { + var notificationParams = new RazorRemoveDynamicFileParams + { + CSharpDocument = new() + { + Uri = ProtocolConversions.CreateAbsoluteUri(filePath) + } + }; + + Contract.ThrowIfNull(LanguageServerHost.Instance, "We don't have an LSP channel yet to send this request through."); + var clientLanguageServerManager = LanguageServerHost.Instance.GetRequiredLspService(); + + return clientLanguageServerManager.SendNotificationAsync( + RemoveRazorDynamicFileInfoMethodName, notificationParams, cancellationToken).AsTask(); + } + + private ValueTask UpdateAsync(ImmutableSegmentedList paths, CancellationToken token) + { + foreach (var path in paths) + { + token.ThrowIfCancellationRequested(); + Updated?.Invoke(this, path); + } + + return ValueTask.CompletedTask; + } + + private sealed class EmptyStringTextLoader() : TextLoader + { + public static readonly TextLoader Instance = new EmptyStringTextLoader(); + + public override Task LoadTextAndVersionAsync(LoadTextOptions options, CancellationToken cancellationToken) + { + return Task.FromResult(TextAndVersion.Create(SourceText.From(""), VersionStamp.Default)); + } + } +} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/RazorInitializeHandler.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorInitializeHandler.cs similarity index 95% rename from src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/RazorInitializeHandler.cs rename to src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorInitializeHandler.cs index c872b2303ef39..546b0a3f6edd7 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/RazorInitializeHandler.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorInitializeHandler.cs @@ -8,7 +8,7 @@ using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CommonLanguageServerProtocol.Framework; -namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; +namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.Razor; [ExportCSharpVisualBasicStatelessLspService(typeof(RazorInitializeHandler)), Shared] [Method("razor/initialize")] diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorProvideDynamicFileParams.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorProvideDynamicFileParams.cs new file mode 100644 index 0000000000000..1d6f4a24dbd22 --- /dev/null +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorProvideDynamicFileParams.cs @@ -0,0 +1,21 @@ +// 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.Text.Json.Serialization; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.Razor; + +internal class RazorProvideDynamicFileParams +{ + [JsonPropertyName("razorDocument")] + public required TextDocumentIdentifier RazorDocument { get; set; } + + /// + /// When true, the full text of the document will be sent over as a single + /// edit instead of diff edits + /// + [JsonPropertyName("fullText")] + public bool FullText { get; set; } +} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorProvideDynamicFileResponse.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorProvideDynamicFileResponse.cs new file mode 100644 index 0000000000000..371624091e313 --- /dev/null +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorProvideDynamicFileResponse.cs @@ -0,0 +1,27 @@ +// 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.Text.Json.Serialization; +using Microsoft.CodeAnalysis.Text; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.Razor; + +internal class RazorProvideDynamicFileResponse +{ + [JsonPropertyName("csharpDocument")] + public required TextDocumentIdentifier CSharpDocument { get; set; } + + [JsonPropertyName("edits")] + public ServerTextChange[]? Edits { get; set; } + + [JsonPropertyName("checksum")] + public required string Checksum { get; set; } + + [JsonPropertyName("checksumAlgorithm")] + public SourceHashAlgorithm ChecksumAlgorithm { get; set; } + + [JsonPropertyName("encodingCodePage")] + public int? SourceEncodingCodePage { get; set; } +} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorRemoveDynamicFileParams.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorRemoveDynamicFileParams.cs new file mode 100644 index 0000000000000..2388208108769 --- /dev/null +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorRemoveDynamicFileParams.cs @@ -0,0 +1,14 @@ +// 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.Text.Json.Serialization; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.Razor; + +internal class RazorRemoveDynamicFileParams +{ + [JsonPropertyName("csharpDocument")] + public required TextDocumentIdentifier CSharpDocument { get; set; } +} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/RazorWorkspaceListenerInitializer.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorWorkspaceListenerInitializer.cs similarity index 97% rename from src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/RazorWorkspaceListenerInitializer.cs rename to src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorWorkspaceListenerInitializer.cs index b0d2ab8a94e4c..80d366ca58c13 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/RazorWorkspaceListenerInitializer.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/RazorWorkspaceListenerInitializer.cs @@ -7,7 +7,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.Extensions.Logging; -namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; +namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.Razor; [Export(typeof(RazorWorkspaceListenerInitializer)), Shared] internal sealed class RazorWorkspaceListenerInitializer diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/ServerTextChange.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/ServerTextChange.cs new file mode 100644 index 0000000000000..272a69316d4b0 --- /dev/null +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/ServerTextChange.cs @@ -0,0 +1,16 @@ +// 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.Text.Json.Serialization; + +namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.Razor; + +internal class ServerTextChange +{ + [JsonPropertyName("span")] + public required ServerTextSpan Span { get; set; } + + [JsonPropertyName("newText")] + public required string NewText { get; set; } +} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/ServerTextSpan.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/ServerTextSpan.cs new file mode 100644 index 0000000000000..80d3d035aeb3e --- /dev/null +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/Razor/ServerTextSpan.cs @@ -0,0 +1,20 @@ +// 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.Text.Json.Serialization; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace.Razor; + +internal class ServerTextSpan +{ + [JsonPropertyName("start")] + public int Start { get; set; } + + [JsonPropertyName("length")] + public int Length { get; set; } + + public TextSpan ToTextSpan() + => new(Start, Length); +} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/RazorDynamicFileInfoProvider.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/RazorDynamicFileInfoProvider.cs deleted file mode 100644 index 4e3d57878fdb8..0000000000000 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/RazorDynamicFileInfoProvider.cs +++ /dev/null @@ -1,115 +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.Serialization; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.LanguageServer.LanguageServer; -using Microsoft.CodeAnalysis.Text; -using Roslyn.LanguageServer.Protocol; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; - -[Export(typeof(IDynamicFileInfoProvider)), Shared] -[ExportMetadata("Extensions", new string[] { "cshtml", "razor", })] -internal class RazorDynamicFileInfoProvider : IDynamicFileInfoProvider -{ - private const string ProvideRazorDynamicFileInfoMethodName = "razor/provideDynamicFileInfo"; - - private class ProvideDynamicFileParams - { - [JsonPropertyName("razorDocument")] - public required TextDocumentIdentifier RazorDocument { get; set; } - } - - private class ProvideDynamicFileResponse - { - [JsonPropertyName("csharpDocument")] - public required TextDocumentIdentifier CSharpDocument { get; set; } - } - - private const string RemoveRazorDynamicFileInfoMethodName = "razor/removeDynamicFileInfo"; - - private class RemoveDynamicFileParams - { - [JsonPropertyName("csharpDocument")] - public required TextDocumentIdentifier CSharpDocument { get; set; } - } - -#pragma warning disable CS0067 // We won't fire the Updated event -- we expect Razor to send us textual changes via didChange instead - public event EventHandler? Updated; -#pragma warning restore CS0067 - - private readonly Lazy _razorWorkspaceListenerInitializer; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public RazorDynamicFileInfoProvider(Lazy razorWorkspaceListenerInitializer) - { - _razorWorkspaceListenerInitializer = razorWorkspaceListenerInitializer; - } - - public async Task GetDynamicFileInfoAsync(ProjectId projectId, string? projectFilePath, string filePath, CancellationToken cancellationToken) - { - _razorWorkspaceListenerInitializer.Value.NotifyDynamicFile(projectId); - - var requestParams = new ProvideDynamicFileParams - { - RazorDocument = new() - { - Uri = ProtocolConversions.CreateAbsoluteUri(filePath) - } - }; - - Contract.ThrowIfNull(LanguageServerHost.Instance, "We don't have an LSP channel yet to send this request through."); - var clientLanguageServerManager = LanguageServerHost.Instance.GetRequiredLspService(); - - var response = await clientLanguageServerManager.SendRequestAsync( - ProvideRazorDynamicFileInfoMethodName, requestParams, cancellationToken); - - // Since we only sent one file over, we should get either zero or one URI back - var responseUri = response.CSharpDocument?.Uri; - - if (responseUri == null) - { - return null; - } - else - { - var dynamicFileInfoFilePath = ProtocolConversions.GetDocumentFilePathFromUri(responseUri); - return new DynamicFileInfo(dynamicFileInfoFilePath, SourceCodeKind.Regular, EmptyStringTextLoader.Instance, designTimeOnly: true, documentServiceProvider: null); - } - } - - public Task RemoveDynamicFileInfoAsync(ProjectId projectId, string? projectFilePath, string filePath, CancellationToken cancellationToken) - { - var notificationParams = new RemoveDynamicFileParams - { - CSharpDocument = new() - { - Uri = ProtocolConversions.CreateAbsoluteUri(filePath) - } - }; - - Contract.ThrowIfNull(LanguageServerHost.Instance, "We don't have an LSP channel yet to send this request through."); - var clientLanguageServerManager = LanguageServerHost.Instance.GetRequiredLspService(); - - return clientLanguageServerManager.SendNotificationAsync( - RemoveRazorDynamicFileInfoMethodName, notificationParams, cancellationToken).AsTask(); - } - - private sealed class EmptyStringTextLoader : TextLoader - { - public static readonly TextLoader Instance = new EmptyStringTextLoader(); - - private EmptyStringTextLoader() { } - - public override Task LoadTextAndVersionAsync(LoadTextOptions options, CancellationToken cancellationToken) - { - return Task.FromResult(TextAndVersion.Create(SourceText.From(""), VersionStamp.Default)); - } - } -}