// 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; } } }