File: TextDifferencing\SourceTextDiffer.CharDiffer.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;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.Razor.TextDifferencing;
 
internal partial class SourceTextDiffer
{
    private class CharDiffer : SourceTextDiffer
    {
        private readonly struct Buffer
        {
            public readonly char[] Array;
            public readonly int Start;
            public readonly int Length;
 
            public Buffer(char[] array, int start, int length)
                => (Array, Start, Length) = (array, start, length);
 
            public void Deconstruct(out char[] array, out int start, out int length)
                => (array, start, length) = (Array, Start, Length);
 
            public char this[int index]
                => Array[index - Start];
 
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            public bool Contains(int index)
                => index >= Start && index < Start + Length;
        }
 
        private const int BufferSize = 1024 * 16;
 
        protected override int OldSourceLength { get; }
        protected override int NewSourceLength { get; }
 
        private char[] _appendBuffer;
        private Buffer _oldBuffer;
        private Buffer _newBuffer;
 
        public CharDiffer(SourceText oldText, SourceText newText)
            : base(oldText, newText)
        {
            _appendBuffer = RentArray(BufferSize);
 
            _oldBuffer = new(RentArray(BufferSize), 0, BufferSize);
            OldText.CopyTo(0, _oldBuffer.Array, 0, Math.Min(OldText.Length, BufferSize));
 
            _newBuffer = new(RentArray(BufferSize), 0, BufferSize);
            NewText.CopyTo(0, _newBuffer.Array, 0, Math.Min(NewText.Length, BufferSize));
 
            OldSourceLength = oldText.Length;
            NewSourceLength = newText.Length;
        }
 
        public override void Dispose()
        {
            ReturnArray(_appendBuffer);
            ReturnArray(_oldBuffer.Array);
            ReturnArray(_newBuffer.Array);
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void FillBuffer(ref Buffer buffer, SourceText text, int index)
        {
            // We slide our buffer so that index is in the middle. However, we have
            // have to be careful not extend past either the start or end of the SourceText.
            // Note that we always assume that we're filling the buffer with
            // BufferSize # of characters. If the SourceText is smaller than BufferSize,
            // this method shouldn't be called.
 
            Debug.Assert(text.Length >= BufferSize);
 
            var start = Math.Max(index - (BufferSize / 2), 0);
 
            if (start + BufferSize > text.Length)
            {
                start = text.Length - BufferSize;
            }
 
            text.CopyTo(start, buffer.Array, 0, BufferSize);
            buffer = new(buffer.Array, start, BufferSize);
        }
 
        protected override bool SourceEqual(int oldSourceIndex, int newSourceIndex)
        {
            ref var oldBuffer = ref _oldBuffer;
            ref var newBuffer = ref _newBuffer;
 
            if (!oldBuffer.Contains(oldSourceIndex))
            {
                FillBuffer(ref oldBuffer, OldText, oldSourceIndex);
            }
 
            if (!newBuffer.Contains(newSourceIndex))
            {
                FillBuffer(ref newBuffer, NewText, newSourceIndex);
            }
 
            return oldBuffer[oldSourceIndex] == newBuffer[newSourceIndex];
        }
 
        protected override int GetEditPosition(DiffEdit edit)
            => edit.Position;
 
        protected override int AppendEdit(DiffEdit edit, StringBuilder builder)
        {
            if (edit.Kind == DiffEditKind.Insert)
            {
                Assumes.NotNull(edit.NewTextPosition);
                var newTextPosition = edit.NewTextPosition.GetValueOrDefault();
 
                if (edit.Length > 1)
                {
                    var buffer = EnsureBuffer(ref _appendBuffer, edit.Length);
                    NewText.CopyTo(newTextPosition, buffer, 0, edit.Length);
 
                    builder.Append(buffer, 0, edit.Length);
                }
                else if (edit.Length == 1)
                {
                    builder.Append(NewText[newTextPosition]);
                }
 
                return edit.Position;
            }
 
            return edit.Position + edit.Length;
        }
    }
}