//
// 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);
}
}
}