Skip to content

Commit 8d39dd3

Browse files
authored
Skip past whitespace on the same line when wrapping with tag (#11687)
2 parents 616caa5 + b7d3f58 commit 8d39dd3

File tree

2 files changed

+126
-2
lines changed

2 files changed

+126
-2
lines changed

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/WrapWithTag/WrapWithTagEndpoint.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,30 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(WrapWithTagParams reques
4747
return null;
4848
}
4949

50-
var sourceText = await documentContext.GetSourceTextAsync(cancellationToken).ConfigureAwait(false);
50+
var sourceText = codeDocument.Source.Text;
5151
if (request.Range?.Start is not { } start ||
5252
!sourceText.TryGetAbsoluteIndex(start, out var hostDocumentIndex))
5353
{
5454
return null;
5555
}
5656

57+
// First thing we do is make sure we start at a non-whitespace character. This is important because in some
58+
// situations the whitespace can be technically C#, but move one character to the right and it's HTML. eg
59+
//
60+
// @if (true) {
61+
// | <p></p>
62+
// }
63+
//
64+
// Limiting this to only whitespace on the same line, as it's not clear what user expectation would be otherwise.
65+
var requestSpan = sourceText.GetTextSpan(request.Range);
66+
if (sourceText.TryGetFirstNonWhitespaceOffset(requestSpan, out var offset, out var newLineCount) &&
67+
newLineCount == 0)
68+
{
69+
request.Range.Start.Character += offset;
70+
requestSpan = sourceText.GetTextSpan(request.Range);
71+
hostDocumentIndex += offset;
72+
}
73+
5774
// Since we're at the start of the selection, lets prefer the language to the right of the cursor if possible.
5875
// That way with the following situation:
5976
//
@@ -89,7 +106,6 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(WrapWithTagParams reques
89106
// <p>[|@currentCount|]</p>
90107

91108
var tree = await documentContext.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
92-
var requestSpan = sourceText.GetTextSpan(request.Range);
93109
var node = tree.Root.FindNode(requestSpan, includeWhitespace: false, getInnermostNodeForTie: true);
94110
if (node?.FirstAncestorOrSelf<CSharpImplicitExpressionSyntax>() is { Parent: CSharpCodeBlockSyntax codeBlock } &&
95111
(requestSpan == codeBlock.FullSpan || requestSpan.Length == 0))

src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/WrapWithTag/WrapWithTagEndpointTests.cs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,114 @@ public async Task Handle_RazorBlockStart_ReturnsResult()
146146
Mock.Get(clientConnection).Verify();
147147
}
148148

149+
[Fact]
150+
public async Task Handle_HtmlInCSharp()
151+
{
152+
// Arrange
153+
var input = new TestCode("""
154+
@if (true)
155+
{
156+
[|<p></p>|]
157+
}
158+
""");
159+
var codeDocument = CreateCodeDocument(input.Text);
160+
var uri = new Uri("file://path/test.razor");
161+
var documentContext = CreateDocumentContext(uri, codeDocument);
162+
var response = new WrapWithTagResponse();
163+
164+
var clientConnection = TestMocks.CreateClientConnection(builder =>
165+
{
166+
builder.SetupSendRequest<WrapWithTagParams, WrapWithTagResponse>(LanguageServerConstants.RazorWrapWithTagEndpoint, response: new(), verifiable: true);
167+
});
168+
169+
var endpoint = new WrapWithTagEndpoint(clientConnection, LoggerFactory);
170+
171+
var range = codeDocument.Source.Text.GetRange(input.Span);
172+
var wrapWithDivParams = new WrapWithTagParams(new TextDocumentIdentifier { Uri = uri })
173+
{
174+
Range = range
175+
};
176+
var requestContext = CreateRazorRequestContext(documentContext);
177+
178+
// Act
179+
var result = await endpoint.HandleRequestAsync(wrapWithDivParams, requestContext, DisposalToken);
180+
181+
// Assert
182+
Assert.NotNull(result);
183+
Mock.Get(clientConnection).Verify();
184+
}
185+
186+
[Fact]
187+
public async Task Handle_HtmlInCSharp_WithWhitespace()
188+
{
189+
// Arrange
190+
var input = new TestCode("""
191+
@if (true)
192+
{
193+
[| <p></p>|]
194+
}
195+
""");
196+
var codeDocument = CreateCodeDocument(input.Text);
197+
var uri = new Uri("file://path/test.razor");
198+
var documentContext = CreateDocumentContext(uri, codeDocument);
199+
var response = new WrapWithTagResponse();
200+
201+
var clientConnection = TestMocks.CreateClientConnection(builder =>
202+
{
203+
builder.SetupSendRequest<WrapWithTagParams, WrapWithTagResponse>(LanguageServerConstants.RazorWrapWithTagEndpoint, response: new(), verifiable: true);
204+
});
205+
206+
var endpoint = new WrapWithTagEndpoint(clientConnection, LoggerFactory);
207+
208+
var range = codeDocument.Source.Text.GetRange(input.Span);
209+
var wrapWithDivParams = new WrapWithTagParams(new TextDocumentIdentifier { Uri = uri })
210+
{
211+
Range = range
212+
};
213+
var requestContext = CreateRazorRequestContext(documentContext);
214+
215+
// Act
216+
var result = await endpoint.HandleRequestAsync(wrapWithDivParams, requestContext, DisposalToken);
217+
218+
// Assert
219+
Assert.NotNull(result);
220+
Mock.Get(clientConnection).Verify();
221+
}
222+
223+
[Fact]
224+
public async Task Handle_HtmlInCSharp_WithNewline()
225+
{
226+
// Arrange
227+
var input = new TestCode("""
228+
@if (true)
229+
{[|
230+
<p></p>|]
231+
}
232+
""");
233+
var codeDocument = CreateCodeDocument(input.Text);
234+
var uri = new Uri("file://path/test.razor");
235+
var documentContext = CreateDocumentContext(uri, codeDocument);
236+
var response = new WrapWithTagResponse();
237+
238+
var clientConnection = TestMocks.CreateClientConnection(builder => { });
239+
240+
var endpoint = new WrapWithTagEndpoint(clientConnection, LoggerFactory);
241+
242+
var range = codeDocument.Source.Text.GetRange(input.Span);
243+
var wrapWithDivParams = new WrapWithTagParams(new TextDocumentIdentifier { Uri = uri })
244+
{
245+
Range = range
246+
};
247+
var requestContext = CreateRazorRequestContext(documentContext);
248+
249+
// Act
250+
var result = await endpoint.HandleRequestAsync(wrapWithDivParams, requestContext, DisposalToken);
251+
252+
// Assert
253+
Assert.Null(result);
254+
Mock.Get(clientConnection).Verify();
255+
}
256+
149257
[Fact]
150258
public async Task Handle_CSharp_PartOfImplicitStatement_ReturnsNull()
151259
{

0 commit comments

Comments
 (0)