Skip to content

Commit 0fc11a9

Browse files
authored
Fix code actions appearing for the wrong context when invoked from light bulb (#11537)
Fixes #11536
2 parents f6c3945 + 3fc837f commit 0fc11a9

5 files changed

Lines changed: 50 additions & 27 deletions

File tree

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

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,7 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(VSCodeActionParams reque
5555
return null;
5656
}
5757

58-
// VS Provides `CodeActionParams.Context.SelectionRange` in addition to
59-
// `CodeActionParams.Range`. The `SelectionRange` is relative to where the
60-
// code action was invoked (ex. line 14, char 3) whereas the `Range` is
61-
// always at the start of the line (ex. line 14, char 0). We want to utilize
62-
// the relative positioning to ensure we provide code actions for the appropriate
63-
// context.
64-
//
65-
// Note: VS Code doesn't provide a `SelectionRange`.
66-
var vsCodeActionContext = request.Context;
67-
if (vsCodeActionContext.SelectionRange != null)
68-
{
69-
request.Range = vsCodeActionContext.SelectionRange;
70-
}
58+
CodeActionsService.AdjustRequestRangeIfNecessary(request);
7159

7260
var correlationId = Guid.NewGuid();
7361
using var __ = _telemetryReporter.TrackLspRequest(LspEndpointName, LanguageServerConstants.RazorLanguageServerName, TelemetryThresholds.CodeActionRazorTelemetryThreshold, correlationId);

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/CodeActions/CodeActionsService.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,4 +279,27 @@ private static ImmutableHashSet<string> GetAllAvailableCodeActionNames()
279279

280280
return availableCodeActionNames.ToImmutableHashSet();
281281
}
282+
283+
public static void AdjustRequestRangeIfNecessary(VSCodeActionParams request)
284+
{
285+
// VS Provides `CodeActionParams.Context.SelectionRange` in addition to
286+
// `CodeActionParams.Range`. The `SelectionRange` is relative to where the
287+
// code action was invoked (ex. line 14, char 3) whereas the `Range` is
288+
// always at the start of the line (ex. line 14, char 0). We want to utilize
289+
// the relative positioning to ensure we provide code actions for the appropriate
290+
// context.
291+
//
292+
// We only do this if the Range contains the SelectionRange, or in other words if
293+
// the SelectionRange serves to better focus the Range. It is possible for the selection
294+
// to be on one line, and the code action request to be for an entirely different line
295+
// if the user is invoking from the lightbulb button directly, for example on hovering
296+
// over a diagnostic. In those cases, using SelectionRange would be wrong.
297+
//
298+
// Note: VS Code doesn't provide a `SelectionRange`.
299+
if (request.Context.SelectionRange is { } selectionRange &&
300+
request.Range.Contains(selectionRange))
301+
{
302+
request.Range = selectionRange;
303+
}
304+
}
282305
}

src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Cohost/CohostCodeActionsEndpoint.cs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.CodeAnalysis;
1313
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost;
1414
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers;
15+
using Microsoft.CodeAnalysis.Razor.CodeActions;
1516
using Microsoft.CodeAnalysis.Razor.CodeActions.Models;
1617
using Microsoft.CodeAnalysis.Razor.Protocol;
1718
using Microsoft.CodeAnalysis.Razor.Protocol.CodeActions;
@@ -72,19 +73,7 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie
7273
var correlationId = Guid.NewGuid();
7374
using var _ = _telemetryReporter.TrackLspRequest(Methods.TextDocumentCodeActionName, LanguageServerConstants.RazorLanguageServerName, TelemetryThresholds.CodeActionRazorTelemetryThreshold, correlationId);
7475

75-
// VS Provides `CodeActionParams.Context.SelectionRange` in addition to
76-
// `CodeActionParams.Range`. The `SelectionRange` is relative to where the
77-
// code action was invoked (ex. line 14, char 3) whereas the `Range` is
78-
// always at the start of the line (ex. line 14, char 0). We want to utilize
79-
// the relative positioning to ensure we provide code actions for the appropriate
80-
// context.
81-
//
82-
// Note: VS Code doesn't provide a `SelectionRange`.
83-
var vsCodeActionContext = request.Context;
84-
if (vsCodeActionContext.SelectionRange != null)
85-
{
86-
request.Range = vsCodeActionContext.SelectionRange;
87-
}
76+
CodeActionsService.AdjustRequestRangeIfNecessary(request);
8877

8978
var requestInfo = await _remoteServiceInvoker.TryInvokeAsync<IRemoteCodeActionsService, CodeActionRequestInfo>(
9079
razorDocument.Project.Solution,

src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTest.NetFx.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,30 @@ public class CodeActionEndToEndTest(ITestOutputHelper testOutput) : CodeActionEn
2525

2626
#region CSharp CodeAction Tests
2727

28+
[Fact]
29+
public async Task Handle_GenerateConstructor_SelectionOutsideRange()
30+
{
31+
var input = """
32+
33+
<div></div>
34+
35+
@functions
36+
{
37+
public Goo [|M()|]
38+
{
39+
return new Goo();
40+
}
41+
42+
public class {|selection:Goo|}
43+
{
44+
}
45+
}
46+
47+
""";
48+
49+
await ValidateCodeActionAsync(input, expected: null, RazorPredefinedCodeRefactoringProviderNames.GenerateConstructorFromMembers);
50+
}
51+
2852
[Fact]
2953
public async Task Handle_GenerateConstructor()
3054
{

src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/CodeActions/CodeActionEndToEndTestBase.NetFx.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,6 @@ internal async Task ValidateCodeActionAsync(
160160
diagnostics,
161161
selectionRange: selectionRange);
162162

163-
Assert.NotEmpty(result);
164163
var codeActionToRun = GetCodeActionToRun(codeAction, childActionIndex, result);
165164

166165
if (expected is null)

0 commit comments

Comments
 (0)