Skip to content

Commit 0242548

Browse files
authored
Revert "Handle textDocument/didChange notifications that don't pass a… (#78227)
…cross the range (#78165)" This reverts commit 59eae07, reversing changes made to 20e9836. Breaks Razor ``` 04/19/2025 07:45:59.186 [9876]: 2>Warning: System.MissingMethodException: Method not found: 'Roslyn.LanguageServer.Protocol.TextDocumentContentChangeEvent[] Roslyn.LanguageServer.Protocol.DidChangeTextDocumentParams.get_ContentChanges()'. while resolving 0xa0007f6 - Roslyn.LanguageServer.Protocol.DidChangeTextDocumentParams.get_ContentChanges. 04/19/2025 07:45:59.608 [9876]: 2>Method not found: 'Roslyn.LanguageServer.Protocol.TextDocumentContentChangeEvent[] Roslyn.LanguageServer.Protocol.DidChangeTextDocumentParams.get_ContentChanges()'. while compiling method <HandleNotificationAsync>d__7.MoveNext ```
2 parents 6dad8a6 + 868038d commit 0242548

File tree

6 files changed

+18
-115
lines changed

6 files changed

+18
-115
lines changed

src/LanguageServer/Protocol.TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -500,16 +500,13 @@ private static string GetDocumentFilePathFromName(string documentName)
500500

501501
private static LSP.DidChangeTextDocumentParams CreateDidChangeTextDocumentParams(
502502
Uri documentUri,
503-
ImmutableArray<(LSP.Range? Range, string Text)> changes)
503+
ImmutableArray<(LSP.Range Range, string Text)> changes)
504504
{
505-
var changeEvents = new LSP.SumType<LSP.TextDocumentContentChangeEvent, LSP.TextDocumentContentChangeFullReplacementEvent>[changes.Length];
506-
for (var i = 0; i < changes.Length; i++)
505+
var changeEvents = changes.Select(change => new LSP.TextDocumentContentChangeEvent
507506
{
508-
var (range, text) = changes[i];
509-
changeEvents[i] = range != null
510-
? new LSP.TextDocumentContentChangeEvent { Text = text, Range = range }
511-
: new LSP.TextDocumentContentChangeFullReplacementEvent { Text = text };
512-
}
507+
Text = change.Text,
508+
Range = change.Range,
509+
}).ToArray();
513510

514511
return new LSP.DidChangeTextDocumentParams()
515512
{
@@ -747,30 +744,21 @@ public async Task OpenDocumentInWorkspaceAsync(DocumentId documentId, bool openA
747744
await WaitForWorkspaceOperationsAsync(TestWorkspace);
748745
}
749746

750-
public Task ReplaceTextAsync(Uri documentUri, params (LSP.Range? Range, string Text)[] changes)
747+
public Task ReplaceTextAsync(Uri documentUri, params (LSP.Range Range, string Text)[] changes)
751748
{
752749
var didChangeParams = CreateDidChangeTextDocumentParams(
753750
documentUri,
754751
[.. changes]);
755752
return ExecuteRequestAsync<LSP.DidChangeTextDocumentParams, object>(LSP.Methods.TextDocumentDidChangeName, didChangeParams, CancellationToken.None);
756753
}
757754

758-
public Task InsertTextAsync(Uri documentUri, params (int? Line, int? Column, string Text)[] changes)
755+
public Task InsertTextAsync(Uri documentUri, params (int Line, int Column, string Text)[] changes)
759756
{
760-
var rangeChanges = new List<(LSP.Range? Range, string Text)>();
761-
foreach (var (line, column, text) in changes)
757+
return ReplaceTextAsync(documentUri, [.. changes.Select(change => (new LSP.Range
762758
{
763-
var range = (line is null || column is null)
764-
? null
765-
: new LSP.Range
766-
{
767-
Start = new LSP.Position { Line = line.Value, Character = column.Value },
768-
End = new LSP.Position { Line = line.Value, Character = column.Value }
769-
};
770-
rangeChanges.Add((range, text));
771-
}
772-
773-
return ReplaceTextAsync(documentUri, [.. rangeChanges]);
759+
Start = new LSP.Position { Line = change.Line, Character = change.Column },
760+
End = new LSP.Position { Line = change.Line, Character = change.Column }
761+
}, change.Text))]);
774762
}
775763

776764
public Task DeleteTextAsync(Uri documentUri, params (int StartLine, int StartColumn, int EndLine, int EndColumn)[] changes)

src/LanguageServer/Protocol/Handler/DocumentChanges/DidChangeHandler.cs

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6-
using System.Collections.Immutable;
76
using System.Composition;
87
using System.Linq;
98
using System.Threading;
@@ -38,7 +37,7 @@ public TextDocumentIdentifier GetTextDocumentIdentifier(DidChangeTextDocumentPar
3837
return SpecializedTasks.Default<object>();
3938
}
4039

41-
internal static bool AreChangesInReverseOrder(ImmutableArray<TextDocumentContentChangeEvent> contentChanges)
40+
internal static bool AreChangesInReverseOrder(TextDocumentContentChangeEvent[] contentChanges)
4241
{
4342
for (var i = 1; i < contentChanges.Length; i++)
4443
{
@@ -54,14 +53,8 @@ internal static bool AreChangesInReverseOrder(ImmutableArray<TextDocumentContent
5453
return true;
5554
}
5655

57-
private static SourceText GetUpdatedSourceText(SumType<TextDocumentContentChangeEvent, TextDocumentContentChangeFullReplacementEvent>[] contentChanges, SourceText text)
56+
private static SourceText GetUpdatedSourceText(TextDocumentContentChangeEvent[] contentChanges, SourceText text)
5857
{
59-
(var remainingContentChanges, text) = GetUpdatedSourceTextAndChangesAfterFullTextReplacementHandled(contentChanges, text);
60-
61-
// No range-based changes to apply.
62-
if (remainingContentChanges.IsEmpty)
63-
return text;
64-
6558
// Per the LSP spec, each text change builds upon the previous, so we don't need to translate any text
6659
// positions between changes. See
6760
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#didChangeTextDocumentParams
@@ -70,41 +63,20 @@ private static SourceText GetUpdatedSourceText(SumType<TextDocumentContentChange
7063
// If the host sends us changes in a way such that no earlier change can affect the position of a later change,
7164
// then we can merge the changes into a single TextChange, allowing creation of only a single new
7265
// source text.
73-
if (AreChangesInReverseOrder(remainingContentChanges))
66+
if (AreChangesInReverseOrder(contentChanges))
7467
{
7568
// The changes were in reverse document order, so we can merge them into a single operation on the source text.
7669
// Note that the WithChanges implementation works more efficiently with it's input in forward document order.
77-
var newChanges = remainingContentChanges.Reverse().SelectAsArray(change => ProtocolConversions.ContentChangeEventToTextChange(change, text));
70+
var newChanges = contentChanges.Reverse().SelectAsArray(change => ProtocolConversions.ContentChangeEventToTextChange(change, text));
7871
text = text.WithChanges(newChanges);
7972
}
8073
else
8174
{
8275
// The host didn't send us the items ordered, so we'll apply each one independently.
83-
foreach (var change in remainingContentChanges)
76+
foreach (var change in contentChanges)
8477
text = text.WithChanges(ProtocolConversions.ContentChangeEventToTextChange(change, text));
8578
}
8679

8780
return text;
8881
}
89-
90-
private static (ImmutableArray<TextDocumentContentChangeEvent>, SourceText) GetUpdatedSourceTextAndChangesAfterFullTextReplacementHandled(SumType<TextDocumentContentChangeEvent, TextDocumentContentChangeFullReplacementEvent>[] contentChanges, SourceText text)
91-
{
92-
// Per the LSP spec, each content change can be either a TextDocumentContentChangeEvent or TextDocumentContentChangeFullReplacementEvent.
93-
// The former is a range-based change while the latter is a full text replacement. If a TextDocumentContentChangeFullReplacementEvent is found,
94-
// then make a full text replacement for that and return all subsequent changes as remaining range-based changes.
95-
var lastFullTextChangeEventIndex = contentChanges.Length - 1;
96-
for (; lastFullTextChangeEventIndex >= 0; lastFullTextChangeEventIndex--)
97-
{
98-
var change = contentChanges[lastFullTextChangeEventIndex];
99-
if (change.Value is TextDocumentContentChangeFullReplacementEvent onlyTextEvent)
100-
{
101-
// Found a full text replacement. Create the new text and stop processing.
102-
text = text.WithChanges([new TextChange(new TextSpan(0, text.Length), onlyTextEvent.Text)]);
103-
break;
104-
}
105-
}
106-
107-
var remainingContentChanges = contentChanges.Skip(lastFullTextChangeEventIndex + 1).SelectAsArray(c => c.First);
108-
return (remainingContentChanges, text);
109-
}
11082
}

src/LanguageServer/Protocol/Protocol/DidChangeTextDocumentParams.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public VersionedTextDocumentIdentifier TextDocument
4545
/// </summary>
4646
[JsonPropertyName("contentChanges")]
4747
[JsonRequired]
48-
public SumType<TextDocumentContentChangeEvent, TextDocumentContentChangeFullReplacementEvent>[] ContentChanges
48+
public TextDocumentContentChangeEvent[] ContentChanges
4949
{
5050
get;
5151
set;

src/LanguageServer/Protocol/Protocol/TextDocumentContentChangeEvent.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ internal sealed class TextDocumentContentChangeEvent
1818
/// Gets or sets the range of the text that was changed.
1919
/// </summary>
2020
[JsonPropertyName("range")]
21-
[JsonRequired]
2221
public Range Range
2322
{
2423
get;
@@ -40,7 +39,6 @@ public int? RangeLength
4039
/// Gets or sets the new text of the range/document.
4140
/// </summary>
4241
[JsonPropertyName("text")]
43-
[JsonRequired]
4442
public string Text
4543
{
4644
get;

src/LanguageServer/Protocol/Protocol/TextDocumentContentChangeFullReplacementEvent.cs

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/LanguageServer/ProtocolUnitTests/DocumentChanges/DocumentChangesTests.cs

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -448,40 +448,6 @@ void M()
448448
}
449449
}
450450

451-
[Theory, CombinatorialData]
452-
public async Task DidChange_MultipleRequestsIncludingTextOnly(bool mutatingLspWorkspace)
453-
{
454-
var source =
455-
"""
456-
{|type:|}
457-
""";
458-
var expected =
459-
"""
460-
/* test */
461-
""";
462-
463-
var (testLspServer, locationTyped, _) = await GetTestLspServerAndLocationAsync(source, mutatingLspWorkspace);
464-
465-
await using (testLspServer)
466-
{
467-
await DidOpen(testLspServer, locationTyped.Uri);
468-
469-
var changes = new (int? line, int? column, string text)[]
470-
{
471-
(0, 0, "// hi"),
472-
(null, null, "/* */"),
473-
(0, 3, "test"),
474-
};
475-
476-
await DidChange(testLspServer, locationTyped.Uri, changes);
477-
478-
var document = testLspServer.GetTrackedTexts().FirstOrDefault();
479-
480-
AssertEx.NotNull(document);
481-
Assert.Equal(expected, document.ToString());
482-
}
483-
}
484-
485451
private async Task<(TestLspServer, LSP.Location, string)> GetTestLspServerAndLocationAsync(string source, bool mutatingLspWorkspace)
486452
{
487453
var testLspServer = await CreateTestLspServerAsync(source, mutatingLspWorkspace, CapabilitiesWithVSExtensions);
@@ -493,7 +459,7 @@ public async Task DidChange_MultipleRequestsIncludingTextOnly(bool mutatingLspWo
493459

494460
private static Task DidOpen(TestLspServer testLspServer, Uri uri) => testLspServer.OpenDocumentAsync(uri);
495461

496-
private static async Task DidChange(TestLspServer testLspServer, Uri uri, params (int? line, int? column, string text)[] changes)
462+
private static async Task DidChange(TestLspServer testLspServer, Uri uri, params (int line, int column, string text)[] changes)
497463
=> await testLspServer.InsertTextAsync(uri, changes);
498464

499465
private static async Task DidClose(TestLspServer testLspServer, Uri uri) => await testLspServer.CloseDocumentAsync(uri);

0 commit comments

Comments
 (0)