Skip to content

Commit ba7a746

Browse files
authored
Group code actions so Razor shows first (#9588)
2 parents 5cc79ef + 5fd3bc2 commit ba7a746

File tree

3 files changed

+67
-4
lines changed

3 files changed

+67
-4
lines changed

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/CodeActionEndpoint.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,15 @@ public CodeActionEndpoint(
9898

9999
using var _ = ArrayBuilderPool<SumType<Command, CodeAction>>.GetPooledObject(out var commandsOrCodeActions);
100100

101-
ConvertCodeActionsToSumType(razorCodeActions);
102-
ConvertCodeActionsToSumType(delegatedCodeActions);
101+
// Grouping the code actions causes VS to sort them into groups, rather than just alphabetically sorting them
102+
// by title. The latter is bad for us because it can put "Remove <div>" at the top in some locales, and our fully
103+
// qualify component code action at the bottom, depending on the users namespace.
104+
ConvertCodeActionsToSumType(razorCodeActions, "A-Razor");
105+
ConvertCodeActionsToSumType(delegatedCodeActions, "B-Delegated");
103106

104107
return commandsOrCodeActions.ToArray();
105108

106-
void ConvertCodeActionsToSumType(ImmutableArray<RazorVSInternalCodeAction> codeActions)
109+
void ConvertCodeActionsToSumType(ImmutableArray<RazorVSInternalCodeAction> codeActions, string groupName)
107110
{
108111
// We must cast the RazorCodeAction into a platform compliant code action
109112
// For VS (SupportsCodeActionResolve = true) this means just encapsulating the RazorCodeAction in the `CommandOrCodeAction` struct
@@ -112,6 +115,8 @@ void ConvertCodeActionsToSumType(ImmutableArray<RazorVSInternalCodeAction> codeA
112115
{
113116
foreach (var action in codeActions)
114117
{
118+
// Make sure we honour the grouping that a delegated server may have created
119+
action.Group = groupName + (action.Group ?? string.Empty);
115120
commandsOrCodeActions.Add(action);
116121
}
117122
}

src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndpointTest.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,59 @@ public async Task Handle_MultipleMixedProvider_SupportsCodeActionResolveTrue()
478478
});
479479
}
480480

481+
[Fact]
482+
public async Task Handle_MixedProvider_SupportsCodeActionResolveTrue_UsesGroups()
483+
{
484+
// Arrange
485+
var documentPath = new Uri("C:/path/to/Page.razor");
486+
var codeDocument = CreateCodeDocument("@code {}");
487+
var documentContext = CreateDocumentContext(documentPath, codeDocument);
488+
var documentMappingService = CreateDocumentMappingService();
489+
var languageServer = CreateLanguageServer();
490+
var codeActionEndpoint = new CodeActionEndpoint(
491+
documentMappingService,
492+
new IRazorCodeActionProvider[] {
493+
new MockRazorCodeActionProvider(),
494+
},
495+
new ICSharpCodeActionProvider[] {
496+
new MockCSharpCodeActionProvider()
497+
},
498+
Array.Empty<IHtmlCodeActionProvider>(),
499+
languageServer,
500+
_languageServerFeatureOptions,
501+
telemetryReporter: null)
502+
{
503+
_supportsCodeActionResolve = true
504+
};
505+
506+
var request = new VSCodeActionParams()
507+
{
508+
TextDocument = new VSTextDocumentIdentifier { Uri = documentPath },
509+
Range = new Range { Start = new Position(0, 1), End = new Position(0, 1) },
510+
Context = new VSInternalCodeActionContext()
511+
};
512+
var requestContext = CreateRazorRequestContext(documentContext);
513+
514+
// Act
515+
var commandOrCodeActionContainer = await codeActionEndpoint.HandleRequestAsync(request, requestContext, default);
516+
517+
// Assert
518+
Assert.NotNull(commandOrCodeActionContainer);
519+
Assert.Collection(commandOrCodeActionContainer,
520+
c =>
521+
{
522+
Assert.True(c.TryGetSecond(out var codeAction));
523+
Assert.True(codeAction is VSInternalCodeAction);
524+
Assert.Equal("A-Razor", ((VSInternalCodeAction)codeAction).Group);
525+
},
526+
c =>
527+
{
528+
Assert.True(c.TryGetSecond(out var codeAction));
529+
Assert.True(codeAction is VSInternalCodeAction);
530+
Assert.Equal("B-Delegated", ((VSInternalCodeAction)codeAction).Group);
531+
});
532+
}
533+
481534
[Fact]
482535
public async Task Handle_MultipleMixedProvider_SupportsCodeActionResolveFalse()
483536
{

src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/RazorCodeActionsTests.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT license. See License.txt in the project root for license information.
33

4+
using System.Linq;
45
using System.Threading.Tasks;
56
using Xunit;
67
using Xunit.Abstractions;
@@ -26,7 +27,11 @@ public async Task RazorCodeActions_AddUsing()
2627
var codeActions = await TestServices.Editor.InvokeCodeActionListAsync(ControlledHangMitigatingCancellationToken);
2728

2829
// Assert
29-
var codeActionSet = Assert.Single(codeActions);
30+
31+
// We expect two groups, one for Razor, one for Html
32+
Assert.Equal(2, codeActions.Count());
33+
// Razor should be first
34+
var codeActionSet = codeActions.First();
3035
var usingString = $"@using {RazorProjectConstants.BlazorProjectName}.Shared";
3136
var codeAction = Assert.Single(codeActionSet.Actions, a => a.DisplayText.Equals(usingString));
3237

0 commit comments

Comments
 (0)