Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/LanguageServer/Protocol/Extensions/ProtocolConversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,20 @@ public static TextSpan RangeToTextSpan(LSP.Range range, SourceText text)
{
var linePositionSpan = RangeToLinePositionSpan(range);

// Handle the specific case where the end position is exactly one line beyond the document bounds
// and the end character is 0 (start of the non-existent next line).
// This can happen when deleting the last line, where LSP clients may send
// an end position referencing the start of the next line (which doesn't exist).
if (text.Lines.Count > 0 &&
linePositionSpan.End.Line == text.Lines.Count &&
linePositionSpan.End.Character == 0)
{
// Clamp the end position to the end of the last line
var lastLine = text.Lines[text.Lines.Count - 1];
var clampedEnd = new LinePosition(text.Lines.Count - 1, lastLine.End - lastLine.Start);
linePositionSpan = new LinePositionSpan(linePositionSpan.Start, clampedEnd);
}

try
{
try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,13 +245,48 @@ void M()
Assert.Equal(32, textSpan.End);
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80119")]
public void RangeToTextSpanDoesNotThrow_WhenReferencingStartOfNextLineAfterLastLine()
{
var markup = GetTestMarkup();
var sourceText = SourceText.From(markup);

// The spec allows clients to send a range referencing the start of the next line
// after the last line in the document (and outside the bounds of the document).
// This should not throw.
var lastLineIndex = sourceText.Lines.Count - 1;
var range = new Range()
{
Start = new Position(lastLineIndex, 0),
End = new Position(lastLineIndex + 1, 0)
};

var textSpan = ProtocolConversions.RangeToTextSpan(range, sourceText);

// Should span from the start of the last line to the end of the document
var lastLine = sourceText.Lines[lastLineIndex];
Assert.Equal(lastLine.Start, textSpan.Start);
Assert.Equal(sourceText.Length, textSpan.End);
}

[Fact]
public void RangeToTextSpanThrows_LineOutOfRange()
{
var markup = GetTestMarkup();
var sourceText = SourceText.From(markup);

// Ranges that are outside the document bounds should throw.
var range = new Range() { Start = new Position(0, 0), End = new Position(sourceText.Lines.Count + 1, 0) };
Assert.Throws<ArgumentException>(() => ProtocolConversions.RangeToTextSpan(range, sourceText));
}

[Fact]
public void RangeToTextSpanLineOutOfRangeError()
public void RangeToTextSpanWThrows_CharacterOutOfRange()
{
var markup = GetTestMarkup();
var sourceText = SourceText.From(markup);

var range = new Range() { Start = new Position(0, 0), End = new Position(sourceText.Lines.Count, 0) };
var range = new Range() { Start = new Position(0, 0), End = new Position(sourceText.Lines.Count, 5) };
Assert.Throws<ArgumentException>(() => ProtocolConversions.RangeToTextSpan(range, sourceText));
}

Expand Down
Loading