aspnetcore/src/Microsoft.AspNetCore.Razor..../DefaultRazorSourceLineColle...

124 lines
3.9 KiB
C#

// 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;
namespace Microsoft.AspNetCore.Razor.Evolution
{
internal class DefaultRazorSourceLineCollection : RazorSourceLineCollection
{
private readonly RazorSourceDocument _document;
private readonly int[] _lineStarts;
public DefaultRazorSourceLineCollection(RazorSourceDocument document)
{
_document = document;
_lineStarts = GetLineStarts();
}
public override int Count => _lineStarts.Length;
public override int GetLineLength(int index)
{
if (index < 0 || index >= _lineStarts.Length)
{
throw new IndexOutOfRangeException(nameof(index));
}
if (index == _lineStarts.Length - 1)
{
// Last line is special.
return _document.Length - _lineStarts[index];
}
return _lineStarts[index + 1] - _lineStarts[index];
}
internal override SourceLocation GetLocation(int position)
{
if (position < 0 || position >= _document.Length)
{
throw new IndexOutOfRangeException(nameof(position));
}
var index = Array.BinarySearch<int>(_lineStarts, position);
if (index >= 0)
{
// We have an exact match for the start of a line.
Debug.Assert(_lineStarts[index] == position);
return new SourceLocation(_document.FileName, position, index, characterIndex: 0);
}
// Index is the complement of the line *after* the one we want, because BinarySearch tells
// us where we'd put position *if* it were the start of a line.
index = (~index) - 1;
if (index == -1)
{
// There's no preceding line, so it's based on the start of the string
return new SourceLocation(_document.FileName, position, 0, position);
}
else
{
var characterIndex = position - _lineStarts[index];
return new SourceLocation(_document.FileName, position, index, characterIndex);
}
}
private int[] GetLineStarts()
{
var starts = new List<int>();
// 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++)
{
var c = _document[i];
var isLineBreak = false;
switch (c)
{
case '\r':
unprocessedCR = true;
continue;
case '\n':
unprocessedCR = false;
isLineBreak = true;
break;
case '\u0085':
case '\u2028':
case '\u2029':
isLineBreak = true;
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);
}
}
return starts.ToArray();
}
}
}