File: TextDifferencing\SourceTextDiffer.TextSpanDiffer.cs
Web Access
Project: src\src\Razor\src\Razor\src\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj (Microsoft.CodeAnalysis.Razor.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.Razor.TextDifferencing;
 
internal partial class SourceTextDiffer
{
    private abstract class TextSpanDiffer : SourceTextDiffer
    {
        private readonly ImmutableArray<TextSpan> _oldSpans = [];
        private readonly ImmutableArray<TextSpan> _newSpans = [];
 
        private char[] _oldBuffer;
        private char[] _newBuffer;
        private char[] _appendBuffer;
 
        protected override int OldSourceLength { get; }
        protected override int NewSourceLength { get; }
 
        public TextSpanDiffer(SourceText oldText, SourceText newText)
            : base(oldText, newText)
        {
            _oldBuffer = RentArray(1024);
            _newBuffer = RentArray(1024);
            _appendBuffer = RentArray(1024);
 
            if (oldText.Length > 0)
            {
                _oldSpans = Tokenize(oldText);
            }
 
            if (newText.Length > 0)
            {
                _newSpans = Tokenize(newText);
            }
 
            OldSourceLength = _oldSpans.Length;
            NewSourceLength = _newSpans.Length;
        }
 
        protected abstract ImmutableArray<TextSpan> Tokenize(SourceText text);
 
        public override void Dispose()
        {
            ReturnArray(_oldBuffer);
            ReturnArray(_newBuffer);
            ReturnArray(_appendBuffer);
        }
 
        protected override bool SourceEqual(int oldSourceIndex, int newSourceIndex)
        {
            var oldSpan = _oldSpans[oldSourceIndex];
            var newSpan = _newSpans[newSourceIndex];
 
            if (oldSpan.Length != newSpan.Length)
            {
                return false;
            }
 
            var length = oldSpan.Length;
 
            // Simple case: Both lines are empty.
            if (length == 0)
            {
                return true;
            }
 
            // Copy the text into char arrays for comparison. Note: To avoid allocation,
            // we try to reuse the same char buffers and only grow them when a longer
            // line is encountered.
            var oldChars = EnsureBuffer(ref _oldBuffer, length);
            var newChars = EnsureBuffer(ref _newBuffer, length);
 
            OldText.CopyTo(oldSpan.Start, oldChars, 0, length);
            NewText.CopyTo(newSpan.Start, newChars, 0, length);
 
            for (var i = 0; i < length; i++)
            {
                if (oldChars[i] != newChars[i])
                {
                    return false;
                }
            }
 
            return true;
        }
 
        protected override int GetEditPosition(DiffEdit edit)
            => _oldSpans[edit.Position].Start;
 
        protected override int AppendEdit(DiffEdit edit, StringBuilder builder)
        {
            if (edit.Kind == DiffEditKind.Insert)
            {
                Assumes.NotNull(edit.NewTextPosition);
                var newTextPosition = edit.NewTextPosition.GetValueOrDefault();
 
                for (var i = 0; i < edit.Length; i++)
                {
                    var newSpan = _newSpans[newTextPosition + i];
 
                    if (newSpan.Length > 0)
                    {
                        var buffer = EnsureBuffer(ref _appendBuffer, newSpan.Length);
                        NewText.CopyTo(newSpan.Start, buffer, 0, newSpan.Length);
 
                        builder.Append(buffer, 0, newSpan.Length);
                    }
                }
 
                return _oldSpans[edit.Position].Start;
            }
 
            return _oldSpans[edit.Position + edit.Length - 1].End;
        }
    }
}