From 9880902719dddb6099a5edcefdf5571aebf58def Mon Sep 17 00:00:00 2001 From: Allison Chou Date: Thu, 12 Jan 2023 18:32:29 -0800 Subject: [PATCH] [VS Code] Fix code action and rename file paths (#8129) --- .../DefaultLanguageServerFeatureOptions.cs | 6 +++++ .../CreateComponentCodeActionResolver.cs | 11 +++++++-- .../ExtractToCodeBehindCodeActionResolver.cs | 15 ++++++++++-- .../Refactoring/RenameEndpoint.cs | 24 +++++++++++++++---- .../LanguageServerFeatureOptions.cs | 4 ++++ ...udioWindowsLanguageServerFeatureOptions.cs | 2 ++ ...alStudioMacLanguageServerFeatureOptions.cs | 2 ++ .../CreateComponentCodeActionResolverTest.cs | 10 ++++---- ...tractToCodeBehindCodeActionResolverTest.cs | 12 +++++----- .../Refactoring/RenameEndpointTest.cs | 11 +++++---- .../TestLanguageServerFeatureOptions.cs | 2 ++ 11 files changed, 75 insertions(+), 24 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/DefaultLanguageServerFeatureOptions.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/DefaultLanguageServerFeatureOptions.cs index e1035960eb..78ba029c43 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/DefaultLanguageServerFeatureOptions.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer.Common/DefaultLanguageServerFeatureOptions.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. +using System.Runtime.InteropServices; using Microsoft.AspNetCore.Razor.LanguageServer.Common; using Microsoft.CodeAnalysis.Razor.Workspaces; @@ -21,4 +22,9 @@ internal class DefaultLanguageServerFeatureOptions : LanguageServerFeatureOption public override bool SingleServerSupport => false; public override bool SupportsDelegatedCodeActions => false; + + // Code action and rename paths in Windows VS Code need to be prefixed with '/': + // https://github.com/dotnet/razor/issues/8131 + public override bool ReturnCodeActionAndRenamePathsWithPrefixedSlash + => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/CreateComponentCodeActionResolver.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/CreateComponentCodeActionResolver.cs index e9a4bbe04a..f343b93ec9 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/CreateComponentCodeActionResolver.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/CreateComponentCodeActionResolver.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Razor.Language.Syntax; using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models; using Microsoft.AspNetCore.Razor.LanguageServer.Common; using Microsoft.AspNetCore.Razor.LanguageServer.Common.Extensions; +using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.VisualStudio.LanguageServer.Protocol; using Newtonsoft.Json.Linq; @@ -20,10 +21,12 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions; internal class CreateComponentCodeActionResolver : RazorCodeActionResolver { private readonly DocumentContextFactory _documentContextFactory; + private readonly LanguageServerFeatureOptions _languageServerFeatureOptions; - public CreateComponentCodeActionResolver(DocumentContextFactory documentContextFactory) + public CreateComponentCodeActionResolver(DocumentContextFactory documentContextFactory, LanguageServerFeatureOptions languageServerFeatureOptions) { _documentContextFactory = documentContextFactory ?? throw new ArgumentNullException(nameof(documentContextFactory)); + _languageServerFeatureOptions = languageServerFeatureOptions ?? throw new ArgumentException(nameof(languageServerFeatureOptions)); } public override string Action => LanguageServerConstants.CodeActions.CreateComponentFromTag; @@ -58,10 +61,14 @@ internal class CreateComponentCodeActionResolver : RazorCodeActionResolver return null; } + // VS Code in Windows expects path to start with '/' + var updatedPath = _languageServerFeatureOptions.ReturnCodeActionAndRenamePathsWithPrefixedSlash && !actionParams.Path.StartsWith("/") + ? '/' + actionParams.Path + : actionParams.Path; var newComponentUri = new UriBuilder() { Scheme = Uri.UriSchemeFile, - Path = actionParams.Path, + Path = updatedPath, Host = string.Empty, }.Uri; diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToCodeBehindCodeActionResolver.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToCodeBehindCodeActionResolver.cs index 80144feed5..a893129e14 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToCodeBehindCodeActionResolver.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/Razor/ExtractToCodeBehindCodeActionResolver.cs @@ -18,6 +18,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Razor; +using Microsoft.CodeAnalysis.Razor.Workspaces; using Microsoft.VisualStudio.LanguageServer.Protocol; using Newtonsoft.Json.Linq; using CSharpSyntaxFactory = Microsoft.CodeAnalysis.CSharp.SyntaxFactory; @@ -28,10 +29,14 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions; internal class ExtractToCodeBehindCodeActionResolver : RazorCodeActionResolver { private readonly DocumentContextFactory _documentContextFactory; + private readonly LanguageServerFeatureOptions _languageServerFeatureOptions; - public ExtractToCodeBehindCodeActionResolver(DocumentContextFactory documentContextFactory) + public ExtractToCodeBehindCodeActionResolver( + DocumentContextFactory documentContextFactory, + LanguageServerFeatureOptions languageServerFeatureOptions) { _documentContextFactory = documentContextFactory ?? throw new ArgumentNullException(nameof(documentContextFactory)); + _languageServerFeatureOptions = languageServerFeatureOptions; } public override string Action => LanguageServerConstants.CodeActions.ExtractToCodeBehindAction; @@ -69,10 +74,16 @@ internal class ExtractToCodeBehindCodeActionResolver : RazorCodeActionResolver } var codeBehindPath = GenerateCodeBehindPath(path); + + // VS Code in Windows expects path to start with '/' + var updatedCodeBehindPath = _languageServerFeatureOptions.ReturnCodeActionAndRenamePathsWithPrefixedSlash && !codeBehindPath.StartsWith("/") + ? '/' + codeBehindPath + : codeBehindPath; + var codeBehindUri = new UriBuilder { Scheme = Uri.UriSchemeFile, - Path = codeBehindPath, + Path = updatedCodeBehindPath, Host = string.Empty, }.Uri; diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Refactoring/RenameEndpoint.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Refactoring/RenameEndpoint.cs index 9a37a55dbc..eba02506de 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Refactoring/RenameEndpoint.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Refactoring/RenameEndpoint.cs @@ -188,17 +188,27 @@ internal class RenameEndpoint : AbstractRazorDelegatingEndpoint> documentChanges, DocumentSnapshot documentSnapshot, string newPath) + public void AddFileRenameForComponent(List> documentChanges, DocumentSnapshot documentSnapshot, string newPath) { + // VS Code in Windows expects path to start with '/' + var updatedOldPath = _languageServerFeatureOptions.ReturnCodeActionAndRenamePathsWithPrefixedSlash && !documentSnapshot.FilePath.StartsWith("/") + ? '/' + documentSnapshot.FilePath + : documentSnapshot.FilePath; var oldUri = new UriBuilder { - Path = documentSnapshot.FilePath, + Path = updatedOldPath, Host = string.Empty, Scheme = Uri.UriSchemeFile, }.Uri; + + // VS Code in Windows expects path to start with '/' + var updatedNewPath = _languageServerFeatureOptions.ReturnCodeActionAndRenamePathsWithPrefixedSlash && !newPath.StartsWith("/") + ? '/' + newPath + : newPath; var newUri = new UriBuilder { - Path = newPath, + + Path = updatedNewPath, Host = string.Empty, Scheme = Uri.UriSchemeFile, }.Uri; @@ -219,7 +229,7 @@ internal class RenameEndpoint : AbstractRazorDelegatingEndpoint> documentChanges, IReadOnlyList originTagHelpers, string newName, @@ -241,9 +251,13 @@ internal class RenameEndpoint : AbstractRazorDelegatingEndpoint razorFilePath + CSharpVirtualDocumentSuffix; public string GetRazorHtmlFilePath(string razorFilePath) => razorFilePath + HtmlVirtualDocumentSuffix; diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/VisualStudioWindowsLanguageServerFeatureOptions.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/VisualStudioWindowsLanguageServerFeatureOptions.cs index 130cd27d7f..11ec68036e 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/VisualStudioWindowsLanguageServerFeatureOptions.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/VisualStudioWindowsLanguageServerFeatureOptions.cs @@ -60,5 +60,7 @@ internal class VisualStudioWindowsLanguageServerFeatureOptions : LanguageServerF public override bool SupportsDelegatedCodeActions => true; + public override bool ReturnCodeActionAndRenamePathsWithPrefixedSlash => false; + private bool IsCodespacesOrLiveshare => _lspEditorFeatureDetector.IsRemoteClient() || _lspEditorFeatureDetector.IsLiveShareHost(); } diff --git a/src/Razor/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/VisualStudioMacLanguageServerFeatureOptions.cs b/src/Razor/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/VisualStudioMacLanguageServerFeatureOptions.cs index 28d2484068..b61375f6da 100644 --- a/src/Razor/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/VisualStudioMacLanguageServerFeatureOptions.cs +++ b/src/Razor/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/VisualStudioMacLanguageServerFeatureOptions.cs @@ -39,6 +39,8 @@ internal class VisualStudioMacLanguageServerFeatureOptions : LanguageServerFeatu public override bool SupportsDelegatedCodeActions => true; + public override bool ReturnCodeActionAndRenamePathsWithPrefixedSlash => false; + private bool IsCodespacesOrLiveshare => _lspEditorFeatureDetector.IsRemoteClient() || _lspEditorFeatureDetector.IsLiveShareHost(); } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/CreateComponentCodeActionResolverTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/CreateComponentCodeActionResolverTest.cs index 38487dcfae..906794c652 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/CreateComponentCodeActionResolverTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/CreateComponentCodeActionResolverTest.cs @@ -36,7 +36,7 @@ public class CreateComponentCodeActionResolverTest : LanguageServerTestBase public async Task Handle_MissingFile() { // Arrange - var resolver = new CreateComponentCodeActionResolver(_emptyDocumentContextFactory); + var resolver = new CreateComponentCodeActionResolver(_emptyDocumentContextFactory, TestLanguageServerFeatureOptions.Instance); var data = JObject.FromObject(new CreateComponentCodeActionParams() { Uri = new Uri("c:/Test.razor"), @@ -59,7 +59,7 @@ public class CreateComponentCodeActionResolverTest : LanguageServerTestBase var codeDocument = CreateCodeDocument(contents); codeDocument.SetUnsupported(); - var resolver = new CreateComponentCodeActionResolver(CreateDocumentContextFactory(documentPath, codeDocument)); + var resolver = new CreateComponentCodeActionResolver(CreateDocumentContextFactory(documentPath, codeDocument), TestLanguageServerFeatureOptions.Instance); var data = JObject.FromObject(new CreateComponentCodeActionParams() { Uri = documentPath, @@ -82,7 +82,7 @@ public class CreateComponentCodeActionResolverTest : LanguageServerTestBase var codeDocument = CreateCodeDocument(contents); codeDocument.SetFileKind(FileKinds.Legacy); - var resolver = new CreateComponentCodeActionResolver(CreateDocumentContextFactory(documentPath, codeDocument)); + var resolver = new CreateComponentCodeActionResolver(CreateDocumentContextFactory(documentPath, codeDocument), TestLanguageServerFeatureOptions.Instance); var data = JObject.FromObject(new CreateComponentCodeActionParams() { Uri = documentPath, @@ -104,7 +104,7 @@ public class CreateComponentCodeActionResolverTest : LanguageServerTestBase var contents = $"@page \"/test\""; var codeDocument = CreateCodeDocument(contents); - var resolver = new CreateComponentCodeActionResolver(CreateDocumentContextFactory(documentPath, codeDocument)); + var resolver = new CreateComponentCodeActionResolver(CreateDocumentContextFactory(documentPath, codeDocument), TestLanguageServerFeatureOptions.Instance); var actionParams = new CreateComponentCodeActionParams { Uri = documentPath, @@ -132,7 +132,7 @@ public class CreateComponentCodeActionResolverTest : LanguageServerTestBase var contents = $"@page \"/test\"{Environment.NewLine}@namespace Another.Namespace"; var codeDocument = CreateCodeDocument(contents); - var resolver = new CreateComponentCodeActionResolver(CreateDocumentContextFactory(documentPath, codeDocument)); + var resolver = new CreateComponentCodeActionResolver(CreateDocumentContextFactory(documentPath, codeDocument), TestLanguageServerFeatureOptions.Instance); var actionParams = new CreateComponentCodeActionParams { Uri = documentPath, diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToCodeBehindCodeActionResolverTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToCodeBehindCodeActionResolverTest.cs index bf4352cfc4..03f2071e9a 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToCodeBehindCodeActionResolverTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/Razor/ExtractToCodeBehindCodeActionResolverTest.cs @@ -39,7 +39,7 @@ public class ExtractToCodeBehindCodeActionResolverTest : LanguageServerTestBase public async Task Handle_MissingFile() { // Arrange - var resolver = new ExtractToCodeBehindCodeActionResolver(_emptyDocumentContextFactory); + var resolver = new ExtractToCodeBehindCodeActionResolver(_emptyDocumentContextFactory, TestLanguageServerFeatureOptions.Instance); var data = JObject.FromObject(new ExtractToCodeBehindCodeActionParams() { Uri = new Uri("c:/Test.razor"), @@ -66,7 +66,7 @@ public class ExtractToCodeBehindCodeActionResolverTest : LanguageServerTestBase var codeDocument = CreateCodeDocument(contents); codeDocument.SetUnsupported(); - var resolver = new ExtractToCodeBehindCodeActionResolver(CreateDocumentContextFactory(documentPath, codeDocument)); + var resolver = new ExtractToCodeBehindCodeActionResolver(CreateDocumentContextFactory(documentPath, codeDocument), TestLanguageServerFeatureOptions.Instance); var data = JObject.FromObject(new ExtractToCodeBehindCodeActionParams() { Uri = new Uri("c:/Test.razor"), @@ -93,7 +93,7 @@ public class ExtractToCodeBehindCodeActionResolverTest : LanguageServerTestBase var codeDocument = CreateCodeDocument(contents); codeDocument.SetFileKind(FileKinds.Legacy); - var resolver = new ExtractToCodeBehindCodeActionResolver(CreateDocumentContextFactory(documentPath, codeDocument)); + var resolver = new ExtractToCodeBehindCodeActionResolver(CreateDocumentContextFactory(documentPath, codeDocument), TestLanguageServerFeatureOptions.Instance); var data = JObject.FromObject(new ExtractToCodeBehindCodeActionParams() { Uri = new Uri("c:/Test.razor"), @@ -120,7 +120,7 @@ public class ExtractToCodeBehindCodeActionResolverTest : LanguageServerTestBase var codeDocument = CreateCodeDocument(contents); Assert.True(codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var @namespace)); - var resolver = new ExtractToCodeBehindCodeActionResolver(CreateDocumentContextFactory(documentPath, codeDocument)); + var resolver = new ExtractToCodeBehindCodeActionResolver(CreateDocumentContextFactory(documentPath, codeDocument), TestLanguageServerFeatureOptions.Instance); var actionParams = new ExtractToCodeBehindCodeActionParams { Uri = documentPath, @@ -169,7 +169,7 @@ public class ExtractToCodeBehindCodeActionResolverTest : LanguageServerTestBase var codeDocument = CreateCodeDocument(contents); Assert.True(codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var @namespace)); - var resolver = new ExtractToCodeBehindCodeActionResolver(CreateDocumentContextFactory(documentPath, codeDocument)); + var resolver = new ExtractToCodeBehindCodeActionResolver(CreateDocumentContextFactory(documentPath, codeDocument), TestLanguageServerFeatureOptions.Instance); var actionParams = new ExtractToCodeBehindCodeActionParams { Uri = documentPath, @@ -218,7 +218,7 @@ public class ExtractToCodeBehindCodeActionResolverTest : LanguageServerTestBase var codeDocument = CreateCodeDocument(contents); Assert.True(codeDocument.TryComputeNamespace(fallbackToRootNamespace: true, out var @namespace)); - var resolver = new ExtractToCodeBehindCodeActionResolver(CreateDocumentContextFactory(documentPath, codeDocument)); + var resolver = new ExtractToCodeBehindCodeActionResolver(CreateDocumentContextFactory(documentPath, codeDocument), TestLanguageServerFeatureOptions.Instance); var actionParams = new ExtractToCodeBehindCodeActionParams { Uri = documentPath, diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Refactoring/RenameEndpointTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Refactoring/RenameEndpointTest.cs index 05b32537da..bc7bfd777d 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Refactoring/RenameEndpointTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Refactoring/RenameEndpointTest.cs @@ -47,7 +47,7 @@ public class RenameEndpointTest : LanguageServerTestBase public async Task Handle_Rename_FileManipulationNotSupported_ReturnsNull() { // Arrange - var languageServerFeatureOptions = Mock.Of(options => options.SupportsFileManipulation == false, MockBehavior.Strict); + var languageServerFeatureOptions = Mock.Of(options => options.SupportsFileManipulation == false && options.ReturnCodeActionAndRenamePathsWithPrefixedSlash == false, MockBehavior.Strict); var endpoint = CreateEndpoint(languageServerFeatureOptions); var uri = new Uri("file:///c:/First/Component1.razor"); var request = new RenameParamsBridge @@ -421,7 +421,8 @@ public class RenameEndpointTest : LanguageServerTestBase public async Task Handle_Rename_SingleServer_CallsDelegatedLanguageServer() { // Arrange - var languageServerFeatureOptions = Mock.Of(options => options.SupportsFileManipulation == true && options.SingleServerSupport == true, MockBehavior.Strict); + var languageServerFeatureOptions = Mock.Of( + options => options.SupportsFileManipulation == true && options.SingleServerSupport == true && options.ReturnCodeActionAndRenamePathsWithPrefixedSlash == false, MockBehavior.Strict); var delegatedEdit = new WorkspaceEdit(); @@ -469,7 +470,8 @@ public class RenameEndpointTest : LanguageServerTestBase public async Task Handle_Rename_SingleServer_DoesntDelegateForRazor() { // Arrange - var languageServerFeatureOptions = Mock.Of(options => options.SupportsFileManipulation == true && options.SingleServerSupport == true, MockBehavior.Strict); + var languageServerFeatureOptions = Mock.Of( + options => options.SupportsFileManipulation == true && options.SingleServerSupport == true && options.ReturnCodeActionAndRenamePathsWithPrefixedSlash == false, MockBehavior.Strict); var languageServerMock = new Mock(MockBehavior.Strict); var documentMappingServiceMock = new Mock(MockBehavior.Strict); documentMappingServiceMock @@ -640,7 +642,8 @@ public class RenameEndpointTest : LanguageServerTestBase d.TryCreateAsync(new Uri(itemDirectory2.FilePath), It.IsAny()) == Task.FromResult(directory2Component), MockBehavior.Strict); var searchEngine = new DefaultRazorComponentSearchEngine(Dispatcher, projectSnapshotManagerAccessor, LoggerFactory); - languageServerFeatureOptions ??= Mock.Of(options => options.SupportsFileManipulation == true && options.SingleServerSupport == false, MockBehavior.Strict); + languageServerFeatureOptions ??= Mock.Of( + options => options.SupportsFileManipulation == true && options.SingleServerSupport == false && options.ReturnCodeActionAndRenamePathsWithPrefixedSlash == false, MockBehavior.Strict); var documentMappingServiceMock = new Mock(MockBehavior.Strict); documentMappingServiceMock diff --git a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test.Common/TestLanguageServerFeatureOptions.cs b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test.Common/TestLanguageServerFeatureOptions.cs index 91717f2e80..a0531b758c 100644 --- a/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test.Common/TestLanguageServerFeatureOptions.cs +++ b/src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test.Common/TestLanguageServerFeatureOptions.cs @@ -26,4 +26,6 @@ internal class TestLanguageServerFeatureOptions : LanguageServerFeatureOptions public override bool SingleServerSupport => false; public override bool SupportsDelegatedCodeActions => false; + + public override bool ReturnCodeActionAndRenamePathsWithPrefixedSlash => false; }