// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Markdig.Helpers
{
///
/// A group of .
///
///
public struct StringLineGroup : IEnumerable
{
///
/// Initializes a new instance of the class.
///
///
public StringLineGroup(int capacity)
{
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity));
Lines = new StringLine[capacity];
Count = 0;
}
///
/// Initializes a new instance of the class.
///
/// The text.
///
public StringLineGroup(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
Lines = new StringLine[1];
Count = 0;
Add(new StringSlice(text));
}
///
/// Gets the lines.
///
public StringLine[] Lines { get; private set; }
///
/// Gets the number of lines.
///
public int Count { get; private set; }
///
/// Clears this instance.
///
public void Clear()
{
Array.Clear(Lines, 0, Lines.Length);
Count = 0;
}
///
/// Removes the line at the specified index.
///
/// The index.
public void RemoveAt(int index)
{
if (Count - 1 == index)
{
Count--;
}
else
{
Array.Copy(Lines, index + 1, Lines, index, Count - index - 1);
Lines[Count - 1] = new StringLine();
Count--;
}
}
///
/// Adds the specified line to this instance.
///
/// The line.
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public void Add(ref StringLine line)
{
if (Count == Lines.Length) IncreaseCapacity();
Lines[Count++] = line;
}
///
/// Adds the specified slice to this instance.
///
/// The slice.
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public void Add(StringSlice slice)
{
if (Count == Lines.Length) IncreaseCapacity();
Lines[Count++] = new StringLine(ref slice);
}
public override string ToString()
{
return ToSlice().ToString();
}
///
/// Converts the lines to a single by concatenating the lines.
///
/// The position of the `\n` line offsets from the beginning of the returned slice.
/// A single slice concatenating the lines of this instance
public StringSlice ToSlice(List lineOffsets = null)
{
// Optimization case when no lines
if (Count == 0)
{
return new StringSlice(string.Empty);
}
// Optimization case for a single line.
if (Count == 1)
{
if (lineOffsets != null)
{
lineOffsets.Add(new LineOffset(Lines[0].Position, Lines[0].Slice.Start, Lines[0].Slice.End + 1));
}
return Lines[0];
}
// Else use a builder
var builder = StringBuilderCache.Local();
int previousStartOfLine = 0;
for (int i = 0; i < Count; i++)
{
if (i > 0)
{
if (lineOffsets != null)
{
lineOffsets.Add(new LineOffset(Lines[i - 1].Position, previousStartOfLine, builder.Length));
}
builder.Append('\n');
previousStartOfLine = builder.Length;
}
if (!Lines[i].Slice.IsEmpty)
{
builder.Append(Lines[i].Slice.Text, Lines[i].Slice.Start, Lines[i].Slice.Length);
}
}
if (lineOffsets != null)
{
lineOffsets.Add(new LineOffset(Lines[Count - 1].Position, previousStartOfLine, builder.Length));
}
var str = builder.ToString();
builder.Length = 0;
return new StringSlice(str);
}
///
/// Converts this instance into a .
///
///
public Iterator ToCharIterator()
{
return new Iterator(this);
}
///
/// Trims each lines of the specified .
///
public void Trim()
{
for (int i = 0; i < Count; i++)
{
Lines[i].Slice.Trim();
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return Lines.GetEnumerator();
}
private void IncreaseCapacity()
{
var newItems = new StringLine[Lines.Length * 2];
if (Count > 0)
{
Array.Copy(Lines, 0, newItems, 0, Count);
}
Lines = newItems;
}
///
/// The iterator used to iterate other the lines.
///
///
public struct Iterator : ICharIterator
{
private readonly StringLineGroup lines;
private int offset;
public Iterator(StringLineGroup lines)
{
this.lines = lines;
Start = -1;
offset = -1;
SliceIndex = 0;
CurrentChar = '\0';
End = -2;
for (int i = 0; i < lines.Count; i++)
{
End += lines.Lines[i].Slice.Length + 1; // Add chars
}
NextChar();
}
public int Start { get; private set; }
public char CurrentChar { get; private set; }
public int End { get; private set; }
public bool IsEmpty => Start > End;
public int SliceIndex { get; private set; }
public char NextChar()
{
Start++;
offset++;
if (Start <= End)
{
var slice = (StringSlice)lines.Lines[SliceIndex];
if (offset < slice.Length)
{
CurrentChar = slice[slice.Start + offset];
}
else
{
CurrentChar = '\n';
SliceIndex++;
offset = -1;
}
}
else
{
CurrentChar = '\0';
Start = End + 1;
SliceIndex = lines.Count;
offset--;
}
return CurrentChar;
}
public bool TrimStart()
{
var c = CurrentChar;
bool hasSpaces = false;
while (c.IsWhitespace())
{
hasSpaces = true;
c = NextChar();
}
return hasSpaces;
}
}
public struct LineOffset
{
public LineOffset(int linePosition, int start, int end)
{
LinePosition = linePosition;
Start = start;
End = end;
}
public readonly int LinePosition;
public readonly int Start;
public readonly int End;
}
}
}