Dependencies/Pixie/Pixie/Code/StringDocument.cs
using System;


namespace WasmBox.Pixie.Code {
    /// <summary>
    /// A document of source code that is stored as a string.
    /// </summary>
    public sealed class StringDocument : SourceDocument {
        /// <summary>
        /// Creates a string document from an identifier and a contents
        /// string.
        /// </summary>
        /// <param name="identifier">The document's identifier.</param>
        /// <param name="contents">The document's contents.</param>
        public StringDocument(string identifier, string contents) {
            this.ident = identifier;
            this.Contents = contents;
            this.lineOffsets = ComputeLineOffsets(contents);
        }

        private string ident;

        /// <summary>
        /// Gets the string that defines this string document's contents.
        /// </summary>
        /// <returns>The document's contents string.</returns>
        public string Contents { get; private set; }

        private List<int> lineOffsets;

        /// <inheritdoc/>
        public override string Identifier => ident;

        /// <inheritdoc/>
        public override int Length => Contents.Length;

        /// <inheritdoc/>
        public override int LineCount => lineOffsets.Count;

        /// <inheritdoc/>
        public override System.IO.TextReader Open(int offset) {
            var reader = new StringReader(Contents);
            if (offset > 0) {
                int bufSize = 1024;
                var buffer = new char[bufSize];
                while (offset > 0) {
                    var readCount = Math.Min(offset, bufSize);
                    reader.Read(buffer, 0, readCount);
                }
            }
            return reader;
        }

        /// <inheritdoc/>
        public override string GetText(int offset, int length) {
            return Contents.Substring(offset, length);
        }

        /// <inheritdoc/>
        public override GridPosition GetGridPosition(int offset) {
            // Choose the last line index such that `offset` is greater
            // than or equal to the offset of the line start.

            int lineIndex = 0;
            for (int i = 0; i < lineOffsets.Count; i++) {
                if (lineOffsets[i] > offset) {
                    break;
                }

                lineIndex = i;
            }

            int lineStartOffset = lineOffsets[lineIndex];

            return new GridPosition(lineIndex, offset - lineStartOffset);
        }

        /// <inheritdoc/>
        public override int GetLineOffset(int lineIndex) {
            if (lineIndex < 0)
                return 0;
            else if (lineIndex >= lineOffsets.Count)
                return Length;
            else
                return lineOffsets[lineIndex];
        }

        /// <summary>
        /// Computes the offsets at which new lines start in a particular string.
        /// </summary>
        /// <param name="str">The string to examine.</param>
        /// <returns>The offsets at which new lines start.</returns>
        private static List<int> ComputeLineOffsets(string str) {
            var results = new List<int>();
            results.Add(0);
            int i = 0;
            while (i < str.Length) {
                // Skip to the next newline.
                i = str.IndexOf('\n', i);

                if (i < 0) {
                    break;
                }

                i++;

                // Skip carriage returns.
                while (i < str.Length && str.Substring(i, 1) == "\r") {
                    i++;
                }

                // Log the first character of the new line.
                results.Add(i);
            }

            return results;
        }
    }
}