提交 20555fb0 编写于 作者: M Marcin Kosieradzki

Implemented incremental computation of ChangedText.Lines and added tests [#608]

上级 5738b06c
......@@ -6,6 +6,7 @@
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Xunit;
using System.Collections.Generic;
namespace Microsoft.CodeAnalysis.UnitTests
{
......@@ -220,5 +221,97 @@ public void TestGetTextChangesToChangedText()
Assert.Equal(expected.NewText, actual.NewText);
}
}
sealed class TextLineEqualityComparer : IEqualityComparer<TextLine>
{
public bool Equals(TextLine x, TextLine y)
{
return x.Span == y.Span;
}
public int GetHashCode(TextLine obj)
{
return obj.Span.GetHashCode();
}
}
[Fact]
public void TestOptimizedSourceTextLinesSimpleSubstition()
{
var changedText = SourceText.From("Line1\r\nLine2\r\nLine3").WithChanges(
new TextChange(new TextSpan(8, 2), "IN"),
new TextChange(new TextSpan(15, 2), "IN"));
Assert.Equal(SourceText.From(changedText.ToString()).Lines, changedText.Lines, new TextLineEqualityComparer());
}
[Fact]
public void TestOptimizedSourceTextLinesSubstitionWithLongerText()
{
var changedText = SourceText.From("Line1\r\nLine2\r\nLine3").WithChanges(
new TextChange(new TextSpan(8, 2), new string('a', 10)),
new TextChange(new TextSpan(15, 2), new string('a', 10)));
Assert.Equal(SourceText.From(changedText.ToString()).Lines, changedText.Lines, new TextLineEqualityComparer());
}
[Fact]
public void TestOptimizedSourceTextLinesInsertCrLf()
{
var changedText = SourceText.From("Line1\r\nLine2\r\nLine3").WithChanges(
new TextChange(new TextSpan(8, 2), "\r\n"),
new TextChange(new TextSpan(15, 2), "\r\n"));
Assert.Equal(SourceText.From(changedText.ToString()).Lines, changedText.Lines, new TextLineEqualityComparer());
}
[Fact]
public void TestOptimizedSourceTextLinesSimpleCr()
{
var changedText = SourceText.From("Line1\rLine2\rLine3").WithChanges(
new TextChange(new TextSpan(6, 0), "aa\r"),
new TextChange(new TextSpan(11, 0), "aa\r"));
Assert.Equal(SourceText.From(changedText.ToString()).Lines, changedText.Lines, new TextLineEqualityComparer());
}
[Fact]
public void TestOptimizedSourceTextLinesSimpleLf()
{
var changedText = SourceText.From("Line1\nLine2\nLine3").WithChanges(
new TextChange(new TextSpan(6, 0), "aa\n"),
new TextChange(new TextSpan(11, 0), "aa\n"));
Assert.Equal(SourceText.From(changedText.ToString()).Lines, changedText.Lines, new TextLineEqualityComparer());
}
[Fact]
public void TestOptimizedSourceTextLinesRemoveCrLf()
{
var changedText = SourceText.From("Line1\r\nLine2\r\nLine3").WithChanges(
new TextChange(new TextSpan(4, 4), "aaaaaa"),
new TextChange(new TextSpan(15, 4), "aaaaaa"));
Assert.Equal(SourceText.From(changedText.ToString()).Lines, changedText.Lines, new TextLineEqualityComparer());
}
[Fact]
public void TestOptimizedSourceTextLinesBrakeCrLf()
{
var changedText = SourceText.From("Test\r\nMessage").WithChanges(
new TextChange(new TextSpan(5, 0), "aaaaaa"));
Assert.Equal(SourceText.From(changedText.ToString()).Lines, changedText.Lines, new TextLineEqualityComparer());
}
[Fact]
public void TestOptimizedSourceTextLinesBrakeCrLfWithLfPrefixedAndCrSufixed()
{
var changedText = SourceText.From("Test\r\nMessage").WithChanges(
new TextChange(new TextSpan(5, 0), "\naaaaaa\r"));
Assert.Equal(SourceText.From(changedText.ToString()).Lines, changedText.Lines, new TextLineEqualityComparer());
}
[Fact]
public void TestOptimizedSourceTextLineInsertAtEnd()
{
var changedText = SourceText.From("Line1\r\nLine2\r\nLine3\r\n").WithChanges(
new TextChange(new TextSpan(21, 0), "Line4\r\n"),
new TextChange(new TextSpan(21, 0), "Line5\r\n"));
Assert.Equal(SourceText.From(changedText.ToString()).Lines, changedText.Lines, new TextLineEqualityComparer());
}
}
}
......@@ -97,5 +97,98 @@ public override IReadOnlyList<TextChangeRange> GetChangeRanges(SourceText oldTex
return ImmutableArray.Create(new TextChangeRange(new TextSpan(0, oldText.Length), _newText.Length));
}
protected override TextLineCollection GetLinesCore()
{
var oldLineInfo = _oldText.Lines;
var lineStarts = ArrayBuilder<int>.GetInstance();
lineStarts.Add(0);
// position in the original document
var position = 0;
// delta generated by already processed changes (position in the new document = position + delta)
var delta = 0;
// true if last segment ends with CR and we need to check for CR+LF code below assumes that both CR and LF are also line breaks alone
var endsWithCR = false;
foreach (var change in _changes)
{
// change.Span.Start < position already ruled out by SourceText.WithChanges
// if we've skipped a range, add
if (change.Span.Start > position)
{
if (endsWithCR && _newText[position + delta] == '\n')
{
lineStarts.RemoveLast();
}
var lps = oldLineInfo.GetLinePositionSpan(TextSpan.FromBounds(position, change.Span.Start));
for (int i = lps.Start.Line + 1; i <= lps.End.Line; i++)
{
lineStarts.Add(oldLineInfo[i].Start + delta);
}
endsWithCR = _oldText[change.Span.Start - 1] == '\r';
// in case change is inserted between CR+LF we treat CR as line break alone, but this line break might be retracted and replaced with new one in case LF is inserted
if (endsWithCR && change.Span.Start < _oldText.Length && _oldText[change.Span.Start] == '\n')
{
lineStarts.Add(change.Span.Start + delta);
}
}
if (change.NewLength > 0)
{
var text = GetSubText(new TextSpan(change.Span.Start + delta, change.NewLength));
// optimizations copied from SourceText.LineInfo.ParseLineStarts
var index = 0;
while (index < text.Length)
{
char c = text[index++];
// Common case - ASCII & not a line break
// if (c > '\r' && c <= 127)
// if (c >= ('\r'+1) && c <= 127)
const uint bias = '\r' + 1;
if (unchecked(c - bias) <= (127 - bias))
{
continue;
}
if (endsWithCR && c == '\n')
{
lineStarts.RemoveLast();
}
else if (c == '\r' && index < text.Length && text[index] == '\n')
{
index++;
}
else if (!TextUtilities.IsAnyLineBreakCharacter(c))
{
continue;
}
lineStarts.Add(change.Span.Start + delta + index);
}
endsWithCR = text[change.NewLength - 1] == '\r';
}
position = change.Span.End;
delta += (change.NewLength - change.Span.Length);
}
if (position < _oldText.Length)
{
if (endsWithCR && _newText[position + delta] == '\n')
{
lineStarts.RemoveLast();
}
var lps = oldLineInfo.GetLinePositionSpan(TextSpan.FromBounds(position, _oldText.Length));
for (int i = lps.Start.Line + 1; i <= lps.End.Line; i++)
{
lineStarts.Add(oldLineInfo[i].Start + delta);
}
}
return new LineInfo(this, lineStarts.ToArrayAndFree());
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册