// // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // // This file contain implementations details that are subject to change without notice. // Use at your own risk. // using System; using System.Collections.Generic; using System.Diagnostics; namespace Microsoft.VisualStudio.Text.Differencing.Implementation { /// /// This class translates an and a set of line transforms /// into a list of lines that is suitable for diffing. The line transforms are evaluated /// as each line is requested, to minimize the extra memory required to translate every /// line of the snapshot. /// class SnapshotLineList : IList, ITokenizedStringListInternal { SnapshotSpan _snapshotSpan; Span _lineSpan; Func _getLineTextCallback; StringDifferenceOptions _options; public SnapshotLineList(SnapshotSpan snapshotSpan, Func getLineTextCallback, StringDifferenceOptions options) { if (getLineTextCallback == null) throw new ArgumentNullException(nameof(getLineTextCallback)); if ((options.DifferenceType & StringDifferenceTypes.Line) == 0) throw new InvalidOperationException("This collection can only be used for line differencing"); _snapshotSpan = snapshotSpan; _getLineTextCallback = getLineTextCallback; _options = options; // Figure out the first and last line in the span var startLine = snapshotSpan.Start.GetContainingLine(); int start = snapshotSpan.Start.GetContainingLine().LineNumber; //Perf hack to avoid calling GetContainingLine() if the lines are the same. SnapshotPoint endPoint = snapshotSpan.End; int end = ((endPoint.Position < startLine.EndIncludingLineBreak) ? start : endPoint.GetContainingLine().LineNumber) + 1; _lineSpan = Span.FromBounds(start, end); } public int Count { get { return _lineSpan.Length; } } public string this[int index] { get { SnapshotSpan lineSpan = GetSpanOfIndex(index); ITextSnapshotLine line = _snapshotSpan.Snapshot.GetLineFromLineNumber(_lineSpan.Start + index); bool isPartialLine = lineSpan.Length != line.LengthIncludingLineBreak; string text; if (isPartialLine) text = lineSpan.GetText(); else text = _getLineTextCallback(line); if (_options.IgnoreTrimWhiteSpace) { if (isPartialLine) { // For a partial line, only trim the sides that are included in the line. // This may not be entirely exact (the partial line may still include what would have been // leading whitespace), but we're ok with it for partial lines, which already don't use the // provided _getLineTextCallback. if (lineSpan.Start == line.Start) text = text.TrimStart(); if (lineSpan.End == line.EndIncludingLineBreak) text = text.TrimEnd(); } else { text = text.Trim(); } } return text; } set { throw new NotSupportedException(); } } SnapshotSpan GetSpanOfIndex(int index) { if (index < 0 || index >= _lineSpan.Length) throw new ArgumentOutOfRangeException(nameof(index)); ITextSnapshotLine line = _snapshotSpan.Snapshot.GetLineFromLineNumber(_lineSpan.Start + index); SnapshotSpan? lineSpan = line.ExtentIncludingLineBreak.Intersection(_snapshotSpan); if (!lineSpan.HasValue) { Debug.Fail("Unexpected - we have a line with no intersection."); return new SnapshotSpan(line.Start, 0); } return lineSpan.Value; } #region Not supported public int IndexOf(string item) { throw new NotSupportedException(); } public void Insert(int index, string item) { throw new NotSupportedException(); } public void RemoveAt(int index) { throw new NotSupportedException(); } public void Add(string item) { throw new NotSupportedException(); } public void Clear() { throw new NotSupportedException(); } public bool Contains(string item) { throw new NotSupportedException(); } public void CopyTo(string[] array, int arrayIndex) { throw new NotSupportedException(); } public bool IsReadOnly { get { return true; } } public bool Remove(string item) { throw new NotSupportedException(); } public IEnumerator GetEnumerator() { for (int i = 0; i < Count ; i++) { yield return this[i]; } } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } #endregion public string Original { get { return _snapshotSpan.GetText(); } } public string OriginalSubstring(int startIndex, int length) { return _snapshotSpan.Snapshot.GetText(_snapshotSpan.Start + startIndex, length); } public Span GetElementInOriginal(int index) { if (index == _lineSpan.Length) { return new Span(_snapshotSpan.End, 0); } else { // Get the span for the index, but make sure to offset by the _snapshotSpan, // so the coordinates are relative to Original (and not the snapshot itself) SnapshotSpan span = GetSpanOfIndex(index); return new Span(span.Start - _snapshotSpan.Start, span.Length); } } public Span GetSpanInOriginal(Span span) { int startPoint = GetElementInOriginal(span.Start).Start; if (span.IsEmpty) return new Span(startPoint, 0); int endPoint = GetElementInOriginal(span.End - 1).End; return Span.FromBounds(startPoint, endPoint); } } }