File: Handler\DocumentChanges\DidChangeHandler.cs
Web Access
Project: src\src\LanguageServer\Protocol\Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj (Microsoft.CodeAnalysis.LanguageServer.Protocol)
// 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.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 Task<object?> HandleRequestAsync(DidChangeTextDocumentParams request, RequestContext context, CancellationToken cancellationToken)
    {
        var text = context.GetTrackedDocumentSourceText(request.TextDocument.Uri);
 
        text = GetUpdatedSourceText(request.ContentChanges, text);
 
        context.UpdateTrackedDocument(request.TextDocument.Uri, text);
 
        return SpecializedTasks.Default<object>();
    }
 
    internal static bool AreChangesInReverseOrder(TextDocumentContentChangeEvent[] 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(TextDocumentContentChangeEvent[] contentChanges, SourceText 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(contentChanges))
        {
            // 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 = contentChanges.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 contentChanges)
                text = text.WithChanges(ProtocolConversions.ContentChangeEventToTextChange(change, text));
        }
 
        return text;
    }
}