Merge pull request #22903 from dotnet/dev/toddgrun/NoMoreLineTrackingStringBuffer
Get rid of LineTrackingStringBuffer class and instead use the line in…
This commit is contained in:
commit
ef0eff7089
|
|
@ -75,45 +75,31 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
// We always consider a document to have at least a 0th line, even if it's empty.
|
||||
starts.Add(0);
|
||||
|
||||
var unprocessedCR = false;
|
||||
|
||||
// Length - 1 because we don't care if there was a linebreak as the last character.
|
||||
var length = _document.Length;
|
||||
for (var i = 0; i < length - 1; i++)
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
var c = _document[i];
|
||||
var isLineBreak = false;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case '\r':
|
||||
unprocessedCR = true;
|
||||
continue;
|
||||
if (i + 1 < length && _document[i + 1] == '\n')
|
||||
{
|
||||
i++;
|
||||
}
|
||||
|
||||
starts.Add(i + 1);
|
||||
break;
|
||||
|
||||
case '\n':
|
||||
unprocessedCR = false;
|
||||
isLineBreak = true;
|
||||
starts.Add(i + 1);
|
||||
break;
|
||||
|
||||
case '\u0085':
|
||||
case '\u2028':
|
||||
case '\u2029':
|
||||
isLineBreak = true;
|
||||
starts.Add(i + 1);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if (unprocessedCR)
|
||||
{
|
||||
// If we get here it means that we had a CR followed by something other than an LF.
|
||||
// Add the CR as a line break.
|
||||
starts.Add(i);
|
||||
unprocessedCR = false;
|
||||
}
|
||||
|
||||
if (isLineBreak)
|
||||
{
|
||||
starts.Add(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,216 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||
{
|
||||
internal class LineTrackingStringBuffer
|
||||
{
|
||||
private readonly IList<TextLine> _lines;
|
||||
private readonly string _filePath;
|
||||
private TextLine _currentLine;
|
||||
|
||||
public LineTrackingStringBuffer(string content, string filePath)
|
||||
: this(content.ToCharArray(), filePath)
|
||||
{
|
||||
}
|
||||
|
||||
public LineTrackingStringBuffer(char[] content, string filePath)
|
||||
{
|
||||
_lines = new List<TextLine>();
|
||||
|
||||
BuildTextLines(content);
|
||||
|
||||
_filePath = filePath;
|
||||
}
|
||||
|
||||
public int Length
|
||||
{
|
||||
get { return _lines[_lines.Count - 1].End; }
|
||||
}
|
||||
|
||||
public SourceLocation EndLocation
|
||||
{
|
||||
get { return new SourceLocation(_filePath, Length, _lines.Count - 1, _lines[_lines.Count - 1].Length); }
|
||||
}
|
||||
|
||||
public CharacterReference CharAt(int absoluteIndex)
|
||||
{
|
||||
var line = FindLine(absoluteIndex);
|
||||
if (line.IsDefault)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(absoluteIndex));
|
||||
}
|
||||
var idx = absoluteIndex - line.Start;
|
||||
return new CharacterReference(line.Content[idx], new SourceLocation(_filePath, absoluteIndex, line.Index, idx));
|
||||
}
|
||||
|
||||
private void BuildTextLines(char[] content)
|
||||
{
|
||||
string lineText;
|
||||
var lineStart = 0;
|
||||
|
||||
for (int i = 0; i < content.Length; i++)
|
||||
{
|
||||
if (ParserHelpers.IsNewLine(content[i]))
|
||||
{
|
||||
// \r on it's own: Start a new line, otherwise wait for \n
|
||||
// Other Newline: Start a new line
|
||||
if (content[i] == '\r' && i + 1 < content.Length && content[i + 1] == '\n')
|
||||
{
|
||||
i++;
|
||||
}
|
||||
|
||||
lineText = new string(content, lineStart, (i - lineStart) + 1); // +1 to include the current char
|
||||
_lines.Add(new TextLine(lineStart, _lines.Count, lineText));
|
||||
|
||||
lineStart = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
lineText = new string(content, lineStart, content.Length - lineStart); // no +1 as content.Length points past the last char already
|
||||
_lines.Add(new TextLine(lineStart, _lines.Count, lineText));
|
||||
}
|
||||
|
||||
private TextLine FindLine(int absoluteIndex)
|
||||
{
|
||||
TextLine selected;
|
||||
|
||||
if (_currentLine.IsDefault)
|
||||
{
|
||||
// Scan from line 0
|
||||
selected = ScanLines(absoluteIndex, 0, _lines.Count);
|
||||
}
|
||||
else if (absoluteIndex >= _currentLine.End)
|
||||
{
|
||||
if (_currentLine.Index + 1 < _lines.Count)
|
||||
{
|
||||
// This index is after the last read line
|
||||
var nextLine = _lines[_currentLine.Index + 1];
|
||||
|
||||
// Optimization to not search if it's the common case where the line after _currentLine is being requested.
|
||||
if (nextLine.Contains(absoluteIndex))
|
||||
{
|
||||
selected = nextLine;
|
||||
}
|
||||
else
|
||||
{
|
||||
selected = ScanLines(absoluteIndex, _currentLine.Index, _lines.Count);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
selected = default;
|
||||
}
|
||||
}
|
||||
else if (absoluteIndex < _currentLine.Start)
|
||||
{
|
||||
if (_currentLine.Index > 0)
|
||||
{
|
||||
// This index is before the last read line
|
||||
var prevLine = _lines[_currentLine.Index - 1];
|
||||
|
||||
// Optimization to not search if it's the common case where the line before _currentLine is being requested.
|
||||
if (prevLine.Contains(absoluteIndex))
|
||||
{
|
||||
selected = prevLine;
|
||||
}
|
||||
else
|
||||
{
|
||||
selected = ScanLines(absoluteIndex, 0, _currentLine.Index);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
selected = default;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This index is on the last read line
|
||||
selected = _currentLine;
|
||||
}
|
||||
|
||||
Debug.Assert(selected.IsDefault || selected.Contains(absoluteIndex));
|
||||
_currentLine = selected;
|
||||
return selected;
|
||||
}
|
||||
|
||||
private TextLine ScanLines(int absoluteIndex, int startLineIndex, int endLineIndex)
|
||||
{
|
||||
// binary search for the line containing absoluteIndex
|
||||
var lowIndex = startLineIndex;
|
||||
var highIndex = endLineIndex;
|
||||
|
||||
while (lowIndex != highIndex)
|
||||
{
|
||||
var midIndex = (lowIndex + highIndex) / 2;
|
||||
var midLine = _lines[midIndex];
|
||||
|
||||
if (absoluteIndex >= midLine.End)
|
||||
{
|
||||
lowIndex = midIndex + 1;
|
||||
}
|
||||
else if (absoluteIndex < midLine.Start)
|
||||
{
|
||||
highIndex = midIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
return midLine;
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
internal struct CharacterReference
|
||||
{
|
||||
public CharacterReference(char character, SourceLocation location)
|
||||
{
|
||||
Character = character;
|
||||
Location = location;
|
||||
}
|
||||
|
||||
public char Character { get; }
|
||||
|
||||
public SourceLocation Location { get; }
|
||||
}
|
||||
|
||||
private struct TextLine
|
||||
{
|
||||
public TextLine(int start, int index, string content)
|
||||
{
|
||||
Start = start;
|
||||
Index = index;
|
||||
Content = content;
|
||||
}
|
||||
|
||||
public string Content { get; }
|
||||
|
||||
public bool IsDefault => Content == null;
|
||||
|
||||
public int Length
|
||||
{
|
||||
get { return Content.Length; }
|
||||
}
|
||||
|
||||
public int Start { get; }
|
||||
public int Index { get; }
|
||||
|
||||
public int End
|
||||
{
|
||||
get { return Start + Length; }
|
||||
}
|
||||
|
||||
public bool Contains(int index)
|
||||
{
|
||||
return index < End && index >= Start;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,10 +18,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
|
||||
SourceDocument = source;
|
||||
var chars = new char[source.Length];
|
||||
source.CopyTo(0, chars, 0, source.Length);
|
||||
|
||||
Source = new SeekableTextReader(chars, source.FilePath);
|
||||
Source = new SeekableTextReader(SourceDocument);
|
||||
DesignTimeMode = options.DesignTime;
|
||||
FeatureFlags = options.FeatureFlags;
|
||||
ParseLeadingDirectives = options.ParseLeadingDirectives;
|
||||
|
|
|
|||
|
|
@ -3,32 +3,36 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Razor.Language.Syntax;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||
{
|
||||
internal class SeekableTextReader : TextReader, ITextDocument
|
||||
{
|
||||
private readonly LineTrackingStringBuffer _buffer;
|
||||
private readonly RazorSourceDocument _sourceDocument;
|
||||
private int _position = 0;
|
||||
private int _current;
|
||||
private SourceLocation _location;
|
||||
private (TextSpan Span, int LineIndex) _cachedLineInfo;
|
||||
|
||||
public SeekableTextReader(string source, string filePath) : this(source.ToCharArray(), filePath) { }
|
||||
public SeekableTextReader(string source, string filePath) : this(new StringSourceDocument(source, Encoding.UTF8, new RazorSourceDocumentProperties(filePath, relativePath: null))) { }
|
||||
|
||||
public SeekableTextReader(char[] source, string filePath)
|
||||
public SeekableTextReader(RazorSourceDocument source)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
|
||||
_buffer = new LineTrackingStringBuffer(source, filePath);
|
||||
_sourceDocument = source;
|
||||
_cachedLineInfo = (new TextSpan(0, _sourceDocument.Lines.GetLineLength(0)), 0);
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
public SourceLocation Location => _location;
|
||||
|
||||
public int Length => _buffer.Length;
|
||||
public int Length => _sourceDocument.Length;
|
||||
|
||||
public int Position
|
||||
{
|
||||
|
|
@ -55,22 +59,73 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
|
||||
private void UpdateState()
|
||||
{
|
||||
if (_position < _buffer.Length)
|
||||
if (_cachedLineInfo.Span.Contains(_position))
|
||||
{
|
||||
var chr = _buffer.CharAt(_position);
|
||||
_current = chr.Character;
|
||||
_location = chr.Location;
|
||||
_location = new SourceLocation(_sourceDocument.FilePath, _position, _cachedLineInfo.LineIndex, _position - _cachedLineInfo.Span.Start);
|
||||
_current = _sourceDocument[_location.AbsoluteIndex];
|
||||
|
||||
return;
|
||||
}
|
||||
else if (_buffer.Length == 0)
|
||||
|
||||
if (_position < _sourceDocument.Length)
|
||||
{
|
||||
if (_position >= _cachedLineInfo.Span.End)
|
||||
{
|
||||
// Try to avoid the GetLocation call by checking if the next line contains the position
|
||||
var nextLineIndex = _cachedLineInfo.LineIndex + 1;
|
||||
var nextLineLength = _sourceDocument.Lines.GetLineLength(nextLineIndex);
|
||||
var nextLineSpan = new TextSpan(_cachedLineInfo.Span.End, nextLineLength);
|
||||
|
||||
if (nextLineSpan.Contains(_position))
|
||||
{
|
||||
_cachedLineInfo = (nextLineSpan, nextLineIndex);
|
||||
_location = new SourceLocation(_sourceDocument.FilePath, _position, nextLineIndex, _position - nextLineSpan.Start);
|
||||
_current = _sourceDocument[_location.AbsoluteIndex];
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try to avoid the GetLocation call by checking if the previous line contains the position
|
||||
var prevLineIndex = _cachedLineInfo.LineIndex - 1;
|
||||
var prevLineLength = _sourceDocument.Lines.GetLineLength(prevLineIndex);
|
||||
var prevLineSpan = new TextSpan(_cachedLineInfo.Span.Start - prevLineLength, prevLineLength);
|
||||
|
||||
if (prevLineSpan.Contains(_position))
|
||||
{
|
||||
_cachedLineInfo = (prevLineSpan, prevLineIndex);
|
||||
_location = new SourceLocation(_sourceDocument.FilePath, _position, prevLineIndex, _position - prevLineSpan.Start);
|
||||
_current = _sourceDocument[_location.AbsoluteIndex];
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The call to GetLocation is expensive
|
||||
_location = _sourceDocument.Lines.GetLocation(_position);
|
||||
|
||||
var lineLength = _sourceDocument.Lines.GetLineLength(_location.LineIndex);
|
||||
var lineSpan = new TextSpan(_position - _location.CharacterIndex, lineLength);
|
||||
_cachedLineInfo = (lineSpan, _location.LineIndex);
|
||||
|
||||
_current = _sourceDocument[_location.AbsoluteIndex];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_sourceDocument.Length == 0)
|
||||
{
|
||||
_current = -1;
|
||||
_location = SourceLocation.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
_current = -1;
|
||||
_location = _buffer.EndLocation;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var lineNumber = _sourceDocument.Lines.Count - 1;
|
||||
_location = new SourceLocation(_sourceDocument.FilePath, Length, lineNumber, _sourceDocument.Lines.GetLineLength(lineNumber));
|
||||
|
||||
_current = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||
{
|
||||
public class LineTrackingStringBufferTest
|
||||
{
|
||||
[Fact]
|
||||
public void CtorInitializesProperties()
|
||||
{
|
||||
var buffer = new LineTrackingStringBuffer(string.Empty, "test.cshtml");
|
||||
Assert.Equal(0, buffer.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CharAtCorrectlyReturnsLocation()
|
||||
{
|
||||
var buffer = new LineTrackingStringBuffer("foo\rbar\nbaz\r\nbiz", "test.cshtml");
|
||||
var chr = buffer.CharAt(14);
|
||||
Assert.Equal('i', chr.Character);
|
||||
Assert.Equal(new SourceLocation("test.cshtml", 14, 3, 1), chr.Location);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue