Skip to content

Commit ec50e86

Browse files
authored
Allow clients to send range ending at the line after the last line in the document (#80310)
* Allow clients to send range ending at the line after the last line in the document * update comment
1 parent 1a8bea9 commit ec50e86

File tree

2 files changed

+51
-2
lines changed

2 files changed

+51
-2
lines changed

src/LanguageServer/Protocol/Extensions/ProtocolConversions.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,20 @@ public static TextSpan RangeToTextSpan(LSP.Range range, SourceText text)
315315
{
316316
var linePositionSpan = RangeToLinePositionSpan(range);
317317

318+
// Handle the specific case where the end position is exactly one line beyond the document bounds
319+
// and the end character is 0 (start of the non-existent next line).
320+
// This can happen when deleting the last line, where LSP clients are allowed (by the spec) to
321+
// send an end position referencing the start of the next line (which doesn't exist).
322+
if (text.Lines.Count > 0 &&
323+
linePositionSpan.End.Line == text.Lines.Count &&
324+
linePositionSpan.End.Character == 0)
325+
{
326+
// Clamp the end position to the end of the last line
327+
var lastLine = text.Lines[text.Lines.Count - 1];
328+
var clampedEnd = new LinePosition(text.Lines.Count - 1, lastLine.End - lastLine.Start);
329+
linePositionSpan = new LinePositionSpan(linePositionSpan.Start, clampedEnd);
330+
}
331+
318332
try
319333
{
320334
try

src/LanguageServer/ProtocolUnitTests/ProtocolConversionsTests.cs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,48 @@ void M()
245245
Assert.Equal(32, textSpan.End);
246246
}
247247

248+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80119")]
249+
public void RangeToTextSpanDoesNotThrow_WhenReferencingStartOfNextLineAfterLastLine()
250+
{
251+
var markup = GetTestMarkup();
252+
var sourceText = SourceText.From(markup);
253+
254+
// The spec allows clients to send a range referencing the start of the next line
255+
// after the last line in the document (and outside the bounds of the document).
256+
// This should not throw.
257+
var lastLineIndex = sourceText.Lines.Count - 1;
258+
var range = new Range()
259+
{
260+
Start = new Position(lastLineIndex, 0),
261+
End = new Position(lastLineIndex + 1, 0)
262+
};
263+
264+
var textSpan = ProtocolConversions.RangeToTextSpan(range, sourceText);
265+
266+
// Should span from the start of the last line to the end of the document
267+
var lastLine = sourceText.Lines[lastLineIndex];
268+
Assert.Equal(lastLine.Start, textSpan.Start);
269+
Assert.Equal(sourceText.Length, textSpan.End);
270+
}
271+
272+
[Fact]
273+
public void RangeToTextSpanThrows_LineOutOfRange()
274+
{
275+
var markup = GetTestMarkup();
276+
var sourceText = SourceText.From(markup);
277+
278+
// Ranges that are outside the document bounds should throw.
279+
var range = new Range() { Start = new Position(0, 0), End = new Position(sourceText.Lines.Count + 1, 0) };
280+
Assert.Throws<ArgumentException>(() => ProtocolConversions.RangeToTextSpan(range, sourceText));
281+
}
282+
248283
[Fact]
249-
public void RangeToTextSpanLineOutOfRangeError()
284+
public void RangeToTextSpanWThrows_CharacterOutOfRange()
250285
{
251286
var markup = GetTestMarkup();
252287
var sourceText = SourceText.From(markup);
253288

254-
var range = new Range() { Start = new Position(0, 0), End = new Position(sourceText.Lines.Count, 0) };
289+
var range = new Range() { Start = new Position(0, 0), End = new Position(sourceText.Lines.Count, 5) };
255290
Assert.Throws<ArgumentException>(() => ProtocolConversions.RangeToTextSpan(range, sourceText));
256291
}
257292

0 commit comments

Comments
 (0)