using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Text; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Text; internal sealed class ChangedText : SourceText { private class ChangeInfo { public ImmutableArray ChangeRanges { get; } public WeakReference WeakOldText { get; } public ChangeInfo? Previous { get; private set; } public ChangeInfo(ImmutableArray changeRanges, WeakReference weakOldText, ChangeInfo? previous) { ChangeRanges = changeRanges; WeakOldText = weakOldText; Previous = previous; Clean(); } private void Clean() { ChangeInfo changeInfo = this; for (ChangeInfo changeInfo2 = this; changeInfo2 != null; changeInfo2 = changeInfo2.Previous) { if (changeInfo2.WeakOldText.TryGetTarget(out SourceText _)) { changeInfo = changeInfo2; } } while (changeInfo != null) { ChangeInfo previous = changeInfo.Previous; changeInfo.Previous = null; changeInfo = previous; } } } internal static class TestAccessor { public static ImmutableArray Merge(ImmutableArray oldChanges, ImmutableArray newChanges) { return TextChangeRangeExtensions.Merge(oldChanges, newChanges); } } private readonly SourceText _newText; private readonly ChangeInfo _info; public override Encoding? Encoding => _newText.Encoding; public IEnumerable Changes => _info.ChangeRanges; public override int Length => _newText.Length; internal override int StorageSize => _newText.StorageSize; internal override ImmutableArray Segments => _newText.Segments; internal override SourceText StorageKey => _newText.StorageKey; public override char this[int position] => _newText[position]; public ChangedText(SourceText oldText, SourceText newText, ImmutableArray changeRanges) : base(default(ImmutableArray), oldText.ChecksumAlgorithm) { RequiresChangeRangesAreValid(oldText, newText, changeRanges); _newText = newText; _info = new ChangeInfo(changeRanges, new WeakReference(oldText), (oldText as ChangedText)?._info); } private static void RequiresChangeRangesAreValid(SourceText oldText, SourceText newText, ImmutableArray changeRanges) { int num = 0; ImmutableArray.Enumerator enumerator = changeRanges.GetEnumerator(); while (enumerator.MoveNext()) { TextChangeRange current = enumerator.Current; num += current.NewLength - current.Span.Length; } if (oldText.Length + num != newText.Length) { throw new InvalidOperationException("Delta length difference of change ranges didn't match before/after text length."); } int num2 = 0; enumerator = changeRanges.GetEnumerator(); while (enumerator.MoveNext()) { TextChangeRange current2 = enumerator.Current; if (current2.Span.Start < num2) { throw new InvalidOperationException("Change preceded current position in oldText"); } if (current2.Span.Start > oldText.Length) { throw new InvalidOperationException("Change start was after the end of oldText"); } if (current2.Span.End > oldText.Length) { throw new InvalidOperationException("Change end was after the end of oldText"); } num2 = current2.Span.End; } } public override string ToString(TextSpan span) { return _newText.ToString(span); } public override SourceText GetSubText(TextSpan span) { return _newText.GetSubText(span); } public override void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count) { _newText.CopyTo(sourceIndex, destination, destinationIndex, count); } public override SourceText WithChanges(IEnumerable changes) { if (_newText.WithChanges(changes) is ChangedText changedText) { return new ChangedText(this, changedText._newText, changedText._info.ChangeRanges); } return this; } public override IReadOnlyList GetChangeRanges(SourceText oldText) { if (oldText == null) { throw new ArgumentNullException("oldText"); } if (this == oldText) { return TextChangeRange.NoChanges; } if (_info.WeakOldText.TryGetTarget(out SourceText target) && target == oldText) { return _info.ChangeRanges; } if (IsChangedFrom(oldText)) { IReadOnlyList> changesBetween = GetChangesBetween(oldText, this); if (changesBetween.Count > 1) { return Merge(changesBetween); } } if (target != null && target.GetChangeRanges(oldText).Count == 0) { return _info.ChangeRanges; } return ImmutableArray.Create(new TextChangeRange(new TextSpan(0, oldText.Length), _newText.Length)); } private bool IsChangedFrom(SourceText oldText) { for (ChangeInfo changeInfo = _info; changeInfo != null; changeInfo = changeInfo.Previous) { if (changeInfo.WeakOldText.TryGetTarget(out SourceText target) && target == oldText) { return true; } } return false; } private static IReadOnlyList> GetChangesBetween(SourceText oldText, ChangedText newText) { List> list = new List>(); ChangeInfo changeInfo = newText._info; list.Add(changeInfo.ChangeRanges); while (changeInfo != null) { changeInfo.WeakOldText.TryGetTarget(out SourceText target); if (target == oldText) { return list; } changeInfo = changeInfo.Previous; if (changeInfo != null) { list.Insert(0, changeInfo.ChangeRanges); } } list.Clear(); return list; } private static ImmutableArray Merge(IReadOnlyList> changeSets) { ImmutableArray immutableArray = changeSets[0]; for (int i = 1; i < changeSets.Count; i++) { immutableArray = TextChangeRangeExtensions.Merge(immutableArray, changeSets[i]); } return immutableArray; } protected override TextLineCollection GetLinesCore() { if (!_info.WeakOldText.TryGetTarget(out SourceText target) || !target.TryGetLines(out TextLineCollection lines)) { return base.GetLinesCore(); } ArrayBuilder instance = ArrayBuilder.GetInstance(lines.Count); instance.Add(0); int num = 0; int num2 = 0; bool flag = false; ImmutableArray.Enumerator enumerator = _info.ChangeRanges.GetEnumerator(); while (enumerator.MoveNext()) { TextChangeRange current = enumerator.Current; if (current.Span.Start > num) { if (flag && _newText[num + num2] == '\n') { instance.RemoveLast(); } LinePositionSpan linePositionSpan = lines.GetLinePositionSpan(TextSpan.FromBounds(num, current.Span.Start)); for (int i = linePositionSpan.Start.Line + 1; i <= linePositionSpan.End.Line; i++) { instance.Add(lines[i].Start + num2); } flag = target[current.Span.Start - 1] == '\r'; if (flag && current.Span.Start < target.Length && target[current.Span.Start] == '\n') { instance.Add(current.Span.Start + num2); } } if (current.NewLength > 0) { int num3 = current.Span.Start + num2; SourceText subText = GetSubText(new TextSpan(num3, current.NewLength)); if (flag && subText[0] == '\n') { instance.RemoveLast(); } for (int j = 1; j < subText.Lines.Count; j++) { instance.Add(num3 + subText.Lines[j].Start); } flag = subText[current.NewLength - 1] == '\r'; } num = current.Span.End; num2 += current.NewLength - current.Span.Length; } if (num < target.Length) { if (flag && _newText[num + num2] == '\n') { instance.RemoveLast(); } LinePositionSpan linePositionSpan2 = lines.GetLinePositionSpan(TextSpan.FromBounds(num, target.Length)); for (int k = linePositionSpan2.Start.Line + 1; k <= linePositionSpan2.End.Line; k++) { instance.Add(lines[k].Start + num2); } } return new LineInfo(this, instance.ToArrayAndFree()); } }