提交 49f71664 编写于 作者: P Paul Harrington

Merge pull request #771 from mkosieradzki/master

Implemented incremental computation of ChangedText.Lines

This addresses issue #608 from @mkosieradzki.
Note that the Visual Studio IDE scenario will not be impacted by this change because, when hosted in Visual Studio, we use editor TextBuffer snapshots as the backing store and they already do their own line tracking.
......@@ -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,94 @@ public void TestGetTextChangesToChangedText()
Assert.Equal(expected.NewText, actual.NewText);
}
}
private 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();
}
}
private static void AssertChangedTextLinesHelper(string originalText, params TextChange[] changes)
{
var changedText = SourceText.From(originalText).WithChanges(changes);
Assert.Equal(SourceText.From(changedText.ToString()).Lines, changedText.Lines, new TextLineEqualityComparer());
}
[Fact]
public void TestOptimizedSourceTextLinesSimpleSubstition()
{
AssertChangedTextLinesHelper("Line1\r\nLine2\r\nLine3",
new TextChange(new TextSpan(8, 2), "IN"),
new TextChange(new TextSpan(15, 2), "IN"));
}
[Fact]
public void TestOptimizedSourceTextLinesSubstitionWithLongerText()
{
AssertChangedTextLinesHelper("Line1\r\nLine2\r\nLine3",
new TextChange(new TextSpan(8, 2), new string('a', 10)),
new TextChange(new TextSpan(15, 2), new string('a', 10)));
}
[Fact]
public void TestOptimizedSourceTextLinesInsertCrLf()
{
AssertChangedTextLinesHelper("Line1\r\nLine2\r\nLine3",
new TextChange(new TextSpan(8, 2), "\r\n"),
new TextChange(new TextSpan(15, 2), "\r\n"));
}
[Fact]
public void TestOptimizedSourceTextLinesSimpleCr()
{
AssertChangedTextLinesHelper("Line1\rLine2\rLine3",
new TextChange(new TextSpan(6, 0), "aa\r"),
new TextChange(new TextSpan(11, 0), "aa\r"));
}
[Fact]
public void TestOptimizedSourceTextLinesSimpleLf()
{
AssertChangedTextLinesHelper("Line1\nLine2\nLine3",
new TextChange(new TextSpan(6, 0), "aa\n"),
new TextChange(new TextSpan(11, 0), "aa\n"));
}
[Fact]
public void TestOptimizedSourceTextLinesRemoveCrLf()
{
AssertChangedTextLinesHelper("Line1\r\nLine2\r\nLine3",
new TextChange(new TextSpan(4, 4), "aaaaaa"),
new TextChange(new TextSpan(15, 4), "aaaaaa"));
}
[Fact]
public void TestOptimizedSourceTextLinesBrakeCrLf()
{
AssertChangedTextLinesHelper("Test\r\nMessage",
new TextChange(new TextSpan(5, 0), "aaaaaa"));
}
[Fact]
public void TestOptimizedSourceTextLinesBrakeCrLfWithLfPrefixedAndCrSufixed()
{
AssertChangedTextLinesHelper("Test\r\nMessage",
new TextChange(new TextSpan(5, 0), "\naaaaaa\r"));
}
[Fact]
public void TestOptimizedSourceTextLineInsertAtEnd()
{
AssertChangedTextLinesHelper("Line1\r\nLine2\r\nLine3\r\n",
new TextChange(new TextSpan(21, 0), "Line4\r\n"),
new TextChange(new TextSpan(21, 0), "Line5\r\n"));
}
}
}
......@@ -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.
先完成此消息的编辑!
想要评论请 注册