/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/LanguageServer/Protocol/Handler/DocumentChanges/DidChangeHandler.cs
110 строк
5 KB
Joey Robichaud
Handle textDocument/didChange notifications that don't pass across the range (#84714)
03 авг 2026, 08:53
Не верифицирован
03 авг 2026, 08:53
ce65aa8
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Immutable; using System.Composition; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Text; using Roslyn.LanguageServer.Protocol; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler.DocumentChanges; [ExportCSharpVisualBasicStatelessLspService(typeof(DidChangeHandler)), Shared] [Method(Methods.TextDocumentDidChangeName)] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class DidChangeHandler() : ILspServiceDocumentRequestHandler<DidChangeTextDocumentParams, object?> { public bool MutatesSolutionState => true; public bool RequiresLSPSolution => false; public TextDocumentIdentifier GetTextDocumentIdentifier(DidChangeTextDocumentParams request) => request.TextDocument; public async Task<object?> HandleRequestAsync(DidChangeTextDocumentParams request, RequestContext context, CancellationToken cancellationToken) { var text = context.GetTrackedDocumentInfo(request.TextDocument.DocumentUri).SourceText; text = GetUpdatedSourceText(request.ContentChanges, text); context.UpdateTrackedDocument(request.TextDocument.DocumentUri, text, request.TextDocument.Version); return null; } internal static bool AreChangesInReverseOrder(ImmutableArray<TextDocumentContentChangePartial> contentChanges) { for (var i = 1; i < contentChanges.Length; i++) { var prevChange = contentChanges[i - 1]; var curChange = contentChanges[i]; if (prevChange.Range.Start.CompareTo(curChange.Range.End) < 0) { return false; } } return true; } private static SourceText GetUpdatedSourceText(SumType<TextDocumentContentChangePartial, TextDocumentContentChangeWholeDocument>[] contentChanges, SourceText text) { (var remainingContentChanges, text) = GetUpdatedSourceTextAndChangesAfterFullTextReplacementHandled(contentChanges, text); // No range-based changes to apply. if (remainingContentChanges.IsEmpty) return text; // Per the LSP spec, each text change builds upon the previous, so we don't need to translate any text // positions between changes. See // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#didChangeTextDocumentParams // for more details. // // If the host sends us changes in a way such that no earlier change can affect the position of a later change, // then we can merge the changes into a single TextChange, allowing creation of only a single new // source text. if (AreChangesInReverseOrder(remainingContentChanges)) { // The changes were in reverse document order, so we can merge them into a single operation on the source text. // Note that the WithChanges implementation works more efficiently with it's input in forward document order. var newChanges = remainingContentChanges.Reverse().SelectAsArray(change => ProtocolConversions.ContentChangeEventToTextChange(change, text)); text = text.WithChanges(newChanges); } else { // The host didn't send us the items ordered, so we'll apply each one independently. foreach (var change in remainingContentChanges) text = text.WithChanges(ProtocolConversions.ContentChangeEventToTextChange(change, text)); } return text; } private static (ImmutableArray<TextDocumentContentChangePartial>, SourceText) GetUpdatedSourceTextAndChangesAfterFullTextReplacementHandled(SumType<TextDocumentContentChangePartial, TextDocumentContentChangeWholeDocument>[] contentChanges, SourceText text) { // Per the LSP spec, each content change is either a range-based change (TextDocumentContentChangePartial) // or a full document text replacement (TextDocumentContentChangeWholeDocument). If a full replacement is found, // apply it and return only the subsequent range-based changes to be processed normally. var lastFullTextChangeEventIndex = contentChanges.Length - 1; for (; lastFullTextChangeEventIndex >= 0; lastFullTextChangeEventIndex--) { var change = contentChanges[lastFullTextChangeEventIndex]; if (change.Value is TextDocumentContentChangeWholeDocument onlyTextEvent) { // Found a full text replacement. Create the new text and stop processing. text = text.WithChanges([new TextChange(new TextSpan(0, text.Length), onlyTextEvent.Text)]); break; } } var remainingContentChanges = contentChanges.Skip(lastFullTextChangeEventIndex + 1).SelectAsArray(c => c.First); return (remainingContentChanges, text); } }