+++ /dev/null
-// Copyright (c) 2013-2021 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
-//
-// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
-
-using System;
-
-namespace Crow
-{
- /// <summary>
- /// Measurement unit
- /// </summary>
- public enum Unit {Undefined, Pixel, Percent, Inherit }
- /// <summary>
- /// Measure class allow proportional sizes as well as stretched and fit on content.
- /// </summary>
- public struct Measure : IEquatable<Measure>
- {
- /// <summary>
- /// Integer value of the measure
- /// </summary>
- public int Value;
- /// <summary>
- /// Measurement unit
- /// </summary>
- public Unit Units;
-
- /// <summary>
- /// Fit on content, this special measure is defined as a fixed integer set to -1 pixel
- /// </summary>
- public static Measure Fit = new Measure(-1,Unit.Percent);
- /// <summary>
- /// Stretched into parent client area. This special measure is defined as a proportional cote
- /// set to 100 Percents
- /// </summary>
- public static Measure Stretched = new Measure(100, Unit.Percent);
- public static Measure Inherit = new Measure (0, Unit.Inherit);
- #region CTOR
- public Measure (int _value, Unit _units = Unit.Pixel)
- {
- Value = _value;
- Units = _units;
- }
- #endregion
-
- /// <summary>
- /// True is size is fixed in pixels, this means not proportional, stretched nor fit.
- /// </summary>
- public bool IsFixed { get { return Units == Unit.Pixel; }}
- public bool IsFit { get { return Value == -1 && Units == Unit.Percent; }}
- /// <summary>
- /// True if width is proportional to parent client rectangle
- /// </summary>
- public bool IsRelativeToParent { get { return Value >= 0 && Units == Unit.Percent; }}
- #region Operators
- public static implicit operator int(Measure m) => m.Value;
- public static implicit operator Measure(int i) => new Measure(i);
- public static implicit operator string(Measure m) => m.ToString();
- public static implicit operator Measure(string s) => Measure.Parse(s);
-
- public static bool operator ==(Measure m1, Measure m2) => m1.Equals (m2);
- public static bool operator !=(Measure m1, Measure m2) => !m1.Equals (m2);
- #endregion
-
- public bool Equals(Measure other) => Value == other.Value && Units == other.Units;
-
- #region Object overrides
- public override int GetHashCode () => HashCode.Combine (Value, Units);
- public override bool Equals (object obj) => obj is Measure m ? Equals (m) : false;
- public override string ToString ()
- {
- return Units == Unit.Inherit ? "Inherit" :
- Value == -1 ? "Fit" :
- Units == Unit.Percent ? Value == 100 ? "Stretched" :
- Value.ToString () + "%" : Value.ToString ();
- }
- #endregion
-
- public static Measure Parse(string s){
- if (string.IsNullOrEmpty (s))
- return default(Measure);
-
- string st = s.Trim ();
- int tmp = 0;
-
- if (string.Equals ("Inherit", st, StringComparison.Ordinal))
- return Measure.Inherit;
- else if (string.Equals ("Fit", st, StringComparison.Ordinal))
- return Measure.Fit;
- else if (string.Equals ("Stretched", s, StringComparison.Ordinal))
- return Measure.Stretched;
- else {
- if (st.EndsWith ("%", StringComparison.Ordinal)) {
- if (int.TryParse (s.Substring(0, st.Length - 1), out tmp))
- return new Measure (tmp, Unit.Percent);
- }else if (int.TryParse (s, out tmp))
- return new Measure (tmp);
- }
-
- throw new Exception ("Error parsing Measure.");
- }
-
- }
-}
}
return Alignment.Center;
}
- public static void Raise(this EventHandler handler, object sender, EventArgs e)
- {
- handler?.Invoke (sender, e);
- }
- public static void Raise<T>(this EventHandler<T> handler, object sender, T e)
- {
- handler?.Invoke (sender, e);
- }
- public static byte[] GetBytes(this string str)
- {
- byte[] bytes = new byte[str.Length * sizeof(char)];
- System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
- return bytes;
- }
- public static bool IsWhiteSpaceOrNewLine (this char c)
- {
- return c == '\t' || c.IsAnyLineBreakCharacter() || char.IsWhiteSpace (c);
- }
- public static object GetDefaultValue(this object obj)
+ /*public static object GetDefaultValue(this object obj)
{
Type t = obj.GetType ();
if (t.IsValueType)
return Activator.CreateInstance (t);
return null;
- }
+ }*/
public static FileSystemInfo [] GetFileSystemInfosOrdered (this DirectoryInfo di) {
try {
return null;
}
}
-
- internal static bool IsAnyLineBreakCharacter (this char c)
- => c == '\n' || c == '\r' || c == '\u0085' || c == '\u2028' || c == '\u2029';
-
- public static bool TryGetResource (this Assembly a, string resId, out Stream stream) {
- stream = null;
- if (a == null)
- return false;
- stream = a.GetManifestResourceStream (resId);
- return stream != null;
- }
}
}
+++ /dev/null
-// Copyright (c) 2013-2021 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
-//
-// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
-using System;
-using System.Diagnostics;
-
-namespace Crow.Text
-{
- [DebuggerDisplay ("{Line}, {Column}, {VisualCharXPosition}")]
- public struct CharLocation : IEquatable<CharLocation>
- {
- public readonly int Line;
- /// <summary>
- /// Character position in current line.
- /// If Column value is '-1', the visualX must contains the on screen position, and column will be computed from it.
- /// </summary>
- public int Column;
- /// <summary>
- /// The column as presented to the user, counting spaces in tabulations.
- /// Its value is set during computations.
- /// </summary>
- public int TabulatedColumn;
- public double VisualCharXPosition;
- public CharLocation (int line, int column, double visualX = -1) {
- Line = line;
- Column = column;
- TabulatedColumn = -1;
- VisualCharXPosition = visualX;
- }
- public bool HasVisualX => Column >= 0 && VisualCharXPosition >= 0;
- public void ResetVisualX () => VisualCharXPosition = -1;
- public static bool operator == (CharLocation a, CharLocation b)
- => a.Equals (b);
- public static bool operator != (CharLocation a, CharLocation b)
- => !a.Equals (b);
- public bool Equals (CharLocation other) {
- return Column < 0 ?
- Line == other.Line && VisualCharXPosition == other.VisualCharXPosition :
- Line == other.Line && Column == other.Column;
- }
- public override bool Equals (object obj) => obj is CharLocation loc ? Equals (loc) : false;
- public override int GetHashCode () {
- return Column < 0 ?
- HashCode.Combine (Line, VisualCharXPosition) :
- HashCode.Combine (Line, Column);
- }
-
- public override string ToString () => $"{Line}, {Column}";
- }
-}
+++ /dev/null
-// Copyright (c) 2013-2021 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
-//
-// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
-using System;
-
-namespace Crow.Text
-{
- public static class Extensions
- {
- public static ReadOnlySpan<char> GetLine (this ReadOnlySpan<char> str, TextLine ls) {
- if (ls.Start >= str.Length)
- return "".AsSpan ();
- return str.Slice (ls.Start, ls.Length);
- }
- public static ReadOnlySpan<char> GetLine (this ReadOnlySpan<char> str, TextLine ls, int offset) {
- int start = ls.Start + offset;
- if (start >= str.Length)
- return "".AsSpan ();
- return str.Slice (start, ls.Length);
- }
- public static ReadOnlySpan<char> GetLineIncludingLineBreak (this string str, TextLine ls) {
- if (ls.Start >= str.Length)
- return "".AsSpan ();
- return str.AsSpan ().Slice (ls.Start, ls.LengthIncludingLineBreak);
- }
- public static ReadOnlySpan<char> GetLineBreak (this ReadOnlySpan<char> str, TextLine ls) {
- if (ls.LineBreakLength == 0)
- return "".AsSpan ();
- return str.Slice (ls.End, ls.LineBreakLength);
- }
- public static ReadOnlySpan<char> GetLineIncludingLineBreak (this string str, TextLine ls, int offset) {
- int start = ls.Start + offset;
- if (start >= str.Length)
- return "".AsSpan ();
- return str.AsSpan ().Slice (start, ls.LengthIncludingLineBreak);
- }
-
- public static ReadOnlySpan<char> ToCharSpan (this LineBreakKind lineBreak) {
- switch (lineBreak) {
- case LineBreakKind.Unix:
- return "\n".AsSpan ();
- case LineBreakKind.Windows:
- return "\r\n".AsSpan ();
- case LineBreakKind.Other:
- return "\r".AsSpan ();
- default:
- return "\r\n".AsSpan ();
- }
- }
-
- }
-}
+++ /dev/null
-// Copyright (c) 2013-2021 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
-//
-// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
-using System;
-
-namespace Crow.Text
-{
- public struct LineSpan : IEquatable<LineSpan>
- {
- public readonly CharLocation Start;
- public readonly CharLocation End;
- public LineSpan (CharLocation start, CharLocation end) {
- Start = start;
- End = end;
- }
- public bool IsEmpty => Start == End;
-
- public bool Equals(LineSpan other)
- => Start == other.Start && End == other.End;
- public override bool Equals(object obj)
- => obj is LineSpan ts ? Equals(ts) : false;
-
- public override int GetHashCode()
- => HashCode.Combine(Start, End);
- public static bool operator ==(LineSpan left, LineSpan right)
- => left.Equals (right);
- public static bool operator !=(LineSpan left, LineSpan right)
- => !left.Equals (right);
- public override string ToString() => $"{Start},{End}";
- }
-}
+++ /dev/null
-// Copyright (c) 2013-2021 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
-//
-// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Crow.Text
-{
- public struct TextChange
- {
- public readonly int Start;
- public readonly int Length;
- public string ChangedText;
-
- public int End => Start + Length;
- public int End2 => End + CharDiff;
-
- public int CharDiff => string.IsNullOrEmpty (ChangedText) ? - Length : ChangedText.Length - Length;
- public bool IsEmpty => string.IsNullOrEmpty (ChangedText) && Length == 0;
- public bool HasNewText => !string.IsNullOrEmpty (ChangedText);
-
- public bool HasNoEffect(ReadOnlySpan<char> src)
- => src.Slice(Start, Length).Equals(ChangedText, StringComparison.Ordinal);
-
- public TextChange (int position, int length, string changedText = null) {
- Start = position;
- Length = length;
- ChangedText = changedText;
- }
- public TextChange (int position, int length, ReadOnlySpan<char> changedText) {
- Start = position;
- Length = length;
- ChangedText = changedText.ToString();
- }
- public TextChange Inverse (ReadOnlySpan<char> src)
- => new TextChange (Start, string.IsNullOrEmpty (ChangedText) ? 0 : ChangedText.Length,
- Length == 0 ? "" : src.Slice (Start, Length).ToString ());
- public override string ToString() => $"{Start},{ChangedText}";
- }
-}
+++ /dev/null
-// Copyright (c) 2013-2021 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
-//
-// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Text;
-
-namespace Crow.Text
-{
- public enum LineBreakKind
- {
- Undefined,
- Unix,
- Windows,
- Other
- }
- /// <summary>
- /// represent a single line span with end of line handling.
- /// </summary>
- [DebuggerDisplay ("{Start}, {Length}, {LengthInPixel}")]
- public struct TextLine : IComparable<TextLine>
- {
- /// <summary>
- /// Line start absolute character position.
- /// </summary>
- public int Start;
- /// <summary>
- /// Line's character count not including linebreak if any.
- /// </summary>
- public int Length;
- /// <summary>
- /// Total line's Character count including linebreak characters if any.
- /// </summary>
- public int LengthIncludingLineBreak;
- /// <summary>
- /// Cached line's length in pixel. If not yet computed by renderer, value is '-1'
- /// </summary>
- public int LengthInPixel;
- /// <summary>
- /// Absolute end character position just before linebreak if any.
- /// </summary>
- public int End => Start + Length;
- public TextSpan Span => new TextSpan (Start, End);
- public TextSpan SpanIncludingLineBreak => new TextSpan (Start, EndIncludingLineBreak);
- /// <summary>
- /// Absolute line's end position after linebreak if any.
- /// </summary>
- public int EndIncludingLineBreak => Start + LengthIncludingLineBreak;
- /// <summary>
- /// Character count of the linebreak, 0 if no linebreak.
- /// </summary>
- public int LineBreakLength => LengthIncludingLineBreak - Length;
- /// <summary>
- /// True line has a linebreak, false otherwise.
- /// </summary>
- public bool HasLineBreak => LineBreakLength > 0;
- /// <summary>
- /// Create a new TextLine span using the absolute start and end character positions.
- /// </summary>
- public TextLine (int start, int end, int endIncludingLineBreak) {
- Start = start;
- Length = end - start;
- LengthIncludingLineBreak = endIncludingLineBreak - start;
- LengthInPixel = -1;
- }
- /// <summary>
- /// Create an empty line span without linebreak starting at absolute charater position given in argument.
- /// </summary>
- /// <param name="start"></param>
- public TextLine (int start) {
- Start = start;
- Length = 0;
- LengthIncludingLineBreak = 0;
- LengthInPixel = -1;
- }
- /// <summary>
- /// Set a new line's length and reset computed length in pixel to '-1'.
- /// </summary>
- /// <param name="newLength"></param>
- public void SetLength (int newLength) {
- LengthInPixel = -1;
- Length = newLength;
- }
- /// <summary>
- /// Create a new TextLine span with a start offset, length in pixel is reseted.
- /// </summary>
- /// <param name="start"></param>
- /// <returns></returns>
- public TextLine WithStartOffset (int start) => new TextLine (Start + start, End, EndIncludingLineBreak);
- public int CompareTo (TextLine other) => Start - other.Start;
- }
-}
+++ /dev/null
-// Copyright (c) 2013-2021 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
-//
-// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Crow.Text
-{
- public class LineCollection : IList<TextLine>
- {
- TextLine[] lines;
- int length;
-
- #region CTOR
- public LineCollection (int capacity) {
- lines = new TextLine[capacity];
- length = 0;
- }
- public LineCollection (TextLine[] _lines, int capacity = -1) {
- if (capacity >= _lines.Length) {
- lines = new TextLine[capacity];
- _lines.AsSpan ().CopyTo (lines);
- } else
- lines =_lines;
-
- length = _lines.Length;
- }
-
- public LineCollection (string _text, int capacity = 4) : this (capacity) {
- Update (_text.AsSpan ());
- }
- #endregion
-
- public void Update (ReadOnlySpan<char> _text) {
- length = 0;
- int start = 0, i = 0;
- while (i < _text.Length) {
- char c = _text[i];
- if (c == '\r') {
- if (++i < _text.Length) {
- if (_text[i] == '\n')
- Add (new TextLine (start, i - 1, ++i));
- else
- Add (new TextLine (start, i - 1, i));
- } else
- Add (new TextLine (start, i - 1, i));
- start = i;
- } else if (c == '\n') {
- if (++i < _text.Length) {
- if (_text[i] == '\r')
- Add (new TextLine (start, i - 1, ++i));
- else
- Add (new TextLine (start, i - 1, i));
- } else
- Add (new TextLine (start, i - 1, i));
- start = i;
-
- } else if (c == '\u0085' || c == '\u2028' || c == '\u2029')
- Add (new TextLine (start, i - 1, i));
- else
- i++;
- }
-
- if (start < i)
- Add (new TextLine (start, _text.Length, _text.Length));
- else
- Add (new TextLine (_text.Length, _text.Length, _text.Length));
- }
-
- public void Update (TextChange change) {
- CharLocation locStart = GetLocation (change.Start);
- int charsDiff = change.ChangedText.Length - change.Length;
- int lineEnd = locStart.Line;
- while (lineEnd < length - 1 && change.End >= lines[lineEnd + 1].Start)
- lineEnd++;
- int columnEnd = change.End - lines[lineEnd].Start;
- int lineEndLineBreakLength = lines[lineEnd].LineBreakLength;
-
- LineCollection newLines = new LineCollection (change.ChangedText);
- int linesDiff = newLines.length - 1 - (lineEnd - locStart.Line);
- TextLine endTl = lines[lineEnd];
-
- if (linesDiff < 0)
- RemoveAt (locStart.Line + 1, -linesDiff);
- else if (linesDiff > 0) {
- for (int i = 0; i < linesDiff; i++)
- Insert (locStart.Line + 1, default);
- }
-
- int remainingColumns = endTl.Length - columnEnd;
- lineEnd += linesDiff;
- lines[lineEnd].SetLength (0);
- lines[locStart.Line].SetLength (locStart.Column + newLines[0].Length);
- lines[lineEnd].Length += remainingColumns;
- if (newLines.Count > 1) {
- lines[lineEnd].Length += newLines[newLines.Count - 1].Length;
- lines[locStart.Line].LengthIncludingLineBreak = lines[locStart.Line].Length + newLines[0].LineBreakLength;
- }
- lines[lineEnd].LengthIncludingLineBreak = lines[lineEnd].Length + endTl.LineBreakLength;
-
- for (int i = 1; i < newLines.Count - 1; i++) {
- int l = locStart.Line + i;
- lines[l] = newLines[i];
- lines[l].Start = lines[l - 1].EndIncludingLineBreak;
- }
- if (lineEnd > 0)
- lines[lineEnd].Start = lines[lineEnd - 1].EndIncludingLineBreak;
-
- //shift start for remaining lines
- for (int i = lineEnd + 1; i < length; i++)
- lines[i].Start += charsDiff;
- }
- public int GetAbsolutePosition (CharLocation loc) => lines[loc.Line].Start + loc.Column;
- public CharLocation GetLocation (int absolutePosition) {
- TextLine tl = new TextLine (absolutePosition);
- int result = lines.AsSpan (0, length).BinarySearch (tl);
- if (result < 0) {
- result = ~result;
- return new CharLocation (result - 1, absolutePosition - lines[result - 1].Start);
- }
- return new CharLocation (result, absolutePosition - lines[result].Start);
- }
- [Obsolete]
- public void UpdateLineLengthInPixel (int index, int lengthInPixel) {
- lines[index].LengthInPixel = lengthInPixel;
- }
- public int Count => length;
- public bool IsReadOnly => false;
- public bool IsEmpty => length == 0;
-
- public TextLine this[int index] { get => lines[index]; set => lines[index] = value; }
-
- public void Add (TextLine item) {
- if (lines.Length < length + 1) {
- TextLine[] tmp = new TextLine[length * 2];
- lines.AsSpan ().CopyTo (tmp);
- lines = tmp;
- }
- lines[length] = item;
- length++;
- }
-
- public void Clear () {
- length = 0;
- }
-
- public bool Contains (TextLine item) => Array.IndexOf<TextLine> (lines, item) >= 0;
-
- public void CopyTo (TextLine[] array, int arrayIndex) {
- lines.AsSpan (0, length).CopyTo (array.AsSpan (arrayIndex));
- }
-
- public bool Remove (TextLine item) {
- int idx = Array.IndexOf<TextLine> (lines, item);
- if (idx < 0)
- return false;
- if (idx + 1 < length)
- lines.AsSpan (idx + 1, length - idx - 1).CopyTo (lines.AsSpan (idx));
- length--;
- return true;
- }
- public IEnumerator<TextLine> GetEnumerator () => new Enumerator (this);
- IEnumerator IEnumerable.GetEnumerator () => GetEnumerator ();
-
- public int IndexOf (TextLine item) => Array.IndexOf (lines, item);
-
- public void Insert (int index, TextLine item) {
- if (lines.Length < length + 1) {
- TextLine[] tmp = new TextLine[length * 2];
- lines.AsSpan (0, index).CopyTo (tmp);
- lines.AsSpan (index).CopyTo (tmp.AsSpan (index + 1));
- lines = tmp;
- }else
- lines.AsSpan (index, length - index).CopyTo (lines.AsSpan (index + 1));
- lines[index] = item;
- length++;
- }
-
- public void RemoveAt (int index) {
- if (index + 1 < length)
- lines.AsSpan (index + 1, length - index - 1).CopyTo (lines.AsSpan (index));
- length--;
- }
- public void RemoveAt (int index, int count) {
- if (index + count < length)
- lines.AsSpan (index + count, length - index - count).CopyTo (lines.AsSpan (index));
- length -= count;
- }
-
- public class Enumerator : IEnumerator<TextLine>
- {
- TextLine[] lines;
- int length, position = -1;
- public Enumerator (LineCollection coll) {
- lines = coll.lines;
- length = coll.length;
- }
- public TextLine Current => lines[position];
- object IEnumerator.Current => Current;
- public void Dispose () { }
- public bool MoveNext () => ++position < length;
- public void Reset () {
- position = -1;
- }
- }
- }
-}
+++ /dev/null
-// Copyright (c) 2013-2021 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
-//
-// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Crow.Text
-{
- public struct TextSpan : IEquatable<TextSpan>
- {
- public readonly int Start;
- public readonly int End;
- public TextSpan (int start, int end) {
- Start = start;
- End = end;
- }
- public static TextSpan FromStartAndLength (int start, int length = 0) => new TextSpan (start, start + length);
- public bool IsEmpty => Start == End;
- public int Length => End - Start;
-
- public bool Equals(TextSpan other)
- => Start == other.Start && End == other.End;
- public override bool Equals(object obj)
- => obj is TextSpan ts ? Equals(ts) : false;
-
- public override int GetHashCode()
- => HashCode.Combine(Start, End);
- public static bool operator ==(TextSpan left, TextSpan right)
- => left.Equals (right);
- public static bool operator !=(TextSpan left, TextSpan right)
- => !left.Equals (right);
- public override string ToString() => $"{Start},{End}";
- public bool Contains (int absolutePosition)
- => absolutePosition >= Start && absolutePosition < End;
- public bool Contains (TextSpan span)
- => Start <= span.Start && End >= span.End;
- }
-}
--- /dev/null
+// Copyright (c) 2013-2021 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
+using System.Diagnostics;
+
+namespace Crow.Text
+{
+ [DebuggerDisplay ("{Line}, {Column}, {VisualCharXPosition}")]
+ public struct CharLocation : IEquatable<CharLocation>
+ {
+ public readonly int Line;
+ /// <summary>
+ /// Character position in current line.
+ /// If Column value is '-1', the visualX must contains the on screen position, and column will be computed from it.
+ /// </summary>
+ public int Column;
+ /// <summary>
+ /// The column as presented to the user, counting spaces in tabulations.
+ /// Its value is set during computations.
+ /// </summary>
+ public int TabulatedColumn;
+ public double VisualCharXPosition;
+ public CharLocation (int line, int column, double visualX = -1) {
+ Line = line;
+ Column = column;
+ TabulatedColumn = -1;
+ VisualCharXPosition = visualX;
+ }
+ public bool HasVisualX => Column >= 0 && VisualCharXPosition >= 0;
+ public void ResetVisualX () => VisualCharXPosition = -1;
+ public static bool operator == (CharLocation a, CharLocation b)
+ => a.Equals (b);
+ public static bool operator != (CharLocation a, CharLocation b)
+ => !a.Equals (b);
+ public bool Equals (CharLocation other) {
+ return Column < 0 ?
+ Line == other.Line && VisualCharXPosition == other.VisualCharXPosition :
+ Line == other.Line && Column == other.Column;
+ }
+ public override bool Equals (object obj) => obj is CharLocation loc ? Equals (loc) : false;
+ public override int GetHashCode () {
+ return Column < 0 ?
+ HashCode.Combine (Line, VisualCharXPosition) :
+ HashCode.Combine (Line, Column);
+ }
+
+ public override string ToString () => $"{Line}, {Column}";
+ }
+}
--- /dev/null
+// Copyright (c) 2013-2022 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+
+using Crow.Text;
+using System;
+using System.IO;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+
+using Drawing2D;
+
+namespace Crow
+{
+ public static class ExtensionsMethods
+ {
+ public static void Raise(this EventHandler handler, object sender, EventArgs e)
+ {
+ handler?.Invoke (sender, e);
+ }
+ public static void Raise<T>(this EventHandler<T> handler, object sender, T e)
+ {
+ handler?.Invoke (sender, e);
+ }
+ public static byte[] GetBytes(this string str)
+ {
+ byte[] bytes = new byte[str.Length * sizeof(char)];
+ System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
+ return bytes;
+ }
+ public static bool IsWhiteSpaceOrNewLine (this char c)
+ {
+ return c == '\t' || c.IsAnyLineBreakCharacter() || char.IsWhiteSpace (c);
+ }
+ internal static bool IsAnyLineBreakCharacter (this char c)
+ => c == '\n' || c == '\r' || c == '\u0085' || c == '\u2028' || c == '\u2029';
+ public static ReadOnlySpan<char> GetLine (this ReadOnlySpan<char> str, TextLine ls) {
+ if (ls.Start >= str.Length)
+ return "".AsSpan ();
+ return str.Slice (ls.Start, ls.Length);
+ }
+ public static ReadOnlySpan<char> GetLine (this ReadOnlySpan<char> str, TextLine ls, int offset) {
+ int start = ls.Start + offset;
+ if (start >= str.Length)
+ return "".AsSpan ();
+ return str.Slice (start, ls.Length);
+ }
+ public static ReadOnlySpan<char> GetLineIncludingLineBreak (this string str, TextLine ls) {
+ if (ls.Start >= str.Length)
+ return "".AsSpan ();
+ return str.AsSpan ().Slice (ls.Start, ls.LengthIncludingLineBreak);
+ }
+ public static ReadOnlySpan<char> GetLineBreak (this ReadOnlySpan<char> str, TextLine ls) {
+ if (ls.LineBreakLength == 0)
+ return "".AsSpan ();
+ return str.Slice (ls.End, ls.LineBreakLength);
+ }
+ public static ReadOnlySpan<char> GetLineIncludingLineBreak (this string str, TextLine ls, int offset) {
+ int start = ls.Start + offset;
+ if (start >= str.Length)
+ return "".AsSpan ();
+ return str.AsSpan ().Slice (start, ls.LengthIncludingLineBreak);
+ }
+
+ public static ReadOnlySpan<char> ToCharSpan (this LineBreakKind lineBreak) {
+ switch (lineBreak) {
+ case LineBreakKind.Unix:
+ return "\n".AsSpan ();
+ case LineBreakKind.Windows:
+ return "\r\n".AsSpan ();
+ case LineBreakKind.Other:
+ return "\r".AsSpan ();
+ default:
+ return "\r\n".AsSpan ();
+ }
+ }
+ }
+}
+
--- /dev/null
+// Copyright (c) 2013-2021 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
+
+namespace Crow.Text
+{
+ public struct LineSpan : IEquatable<LineSpan>
+ {
+ public readonly CharLocation Start;
+ public readonly CharLocation End;
+ public LineSpan (CharLocation start, CharLocation end) {
+ Start = start;
+ End = end;
+ }
+ public bool IsEmpty => Start == End;
+
+ public bool Equals(LineSpan other)
+ => Start == other.Start && End == other.End;
+ public override bool Equals(object obj)
+ => obj is LineSpan ts ? Equals(ts) : false;
+
+ public override int GetHashCode()
+ => HashCode.Combine(Start, End);
+ public static bool operator ==(LineSpan left, LineSpan right)
+ => left.Equals (right);
+ public static bool operator !=(LineSpan left, LineSpan right)
+ => !left.Equals (right);
+ public override string ToString() => $"{Start},{End}";
+ }
+}
--- /dev/null
+// Copyright (c) 2013-2021 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+
+using System;
+
+namespace Crow
+{
+ /// <summary>
+ /// Measurement unit
+ /// </summary>
+ public enum Unit {Undefined, Pixel, Percent, Inherit }
+ /// <summary>
+ /// Measure class allow proportional sizes as well as stretched and fit on content.
+ /// </summary>
+ public struct Measure : IEquatable<Measure>
+ {
+ /// <summary>
+ /// Integer value of the measure
+ /// </summary>
+ public int Value;
+ /// <summary>
+ /// Measurement unit
+ /// </summary>
+ public Unit Units;
+
+ /// <summary>
+ /// Fit on content, this special measure is defined as a fixed integer set to -1 pixel
+ /// </summary>
+ public static Measure Fit = new Measure(-1,Unit.Percent);
+ /// <summary>
+ /// Stretched into parent client area. This special measure is defined as a proportional cote
+ /// set to 100 Percents
+ /// </summary>
+ public static Measure Stretched = new Measure(100, Unit.Percent);
+ public static Measure Inherit = new Measure (0, Unit.Inherit);
+ #region CTOR
+ public Measure (int _value, Unit _units = Unit.Pixel)
+ {
+ Value = _value;
+ Units = _units;
+ }
+ #endregion
+
+ /// <summary>
+ /// True is size is fixed in pixels, this means not proportional, stretched nor fit.
+ /// </summary>
+ public bool IsFixed { get { return Units == Unit.Pixel; }}
+ public bool IsFit { get { return Value == -1 && Units == Unit.Percent; }}
+ /// <summary>
+ /// True if width is proportional to parent client rectangle
+ /// </summary>
+ public bool IsRelativeToParent { get { return Value >= 0 && Units == Unit.Percent; }}
+ #region Operators
+ public static implicit operator int(Measure m) => m.Value;
+ public static implicit operator Measure(int i) => new Measure(i);
+ public static implicit operator string(Measure m) => m.ToString();
+ public static implicit operator Measure(string s) => Measure.Parse(s);
+
+ public static bool operator ==(Measure m1, Measure m2) => m1.Equals (m2);
+ public static bool operator !=(Measure m1, Measure m2) => !m1.Equals (m2);
+ #endregion
+
+ public bool Equals(Measure other) => Value == other.Value && Units == other.Units;
+
+ #region Object overrides
+ public override int GetHashCode () => HashCode.Combine (Value, Units);
+ public override bool Equals (object obj) => obj is Measure m ? Equals (m) : false;
+ public override string ToString ()
+ {
+ return Units == Unit.Inherit ? "Inherit" :
+ Value == -1 ? "Fit" :
+ Units == Unit.Percent ? Value == 100 ? "Stretched" :
+ Value.ToString () + "%" : Value.ToString ();
+ }
+ #endregion
+
+ public static Measure Parse(string s){
+ if (string.IsNullOrEmpty (s))
+ return default(Measure);
+
+ string st = s.Trim ();
+ int tmp = 0;
+
+ if (string.Equals ("Inherit", st, StringComparison.Ordinal))
+ return Measure.Inherit;
+ else if (string.Equals ("Fit", st, StringComparison.Ordinal))
+ return Measure.Fit;
+ else if (string.Equals ("Stretched", s, StringComparison.Ordinal))
+ return Measure.Stretched;
+ else {
+ if (st.EndsWith ("%", StringComparison.Ordinal)) {
+ if (int.TryParse (s.Substring(0, st.Length - 1), out tmp))
+ return new Measure (tmp, Unit.Percent);
+ }else if (int.TryParse (s, out tmp))
+ return new Measure (tmp);
+ }
+
+ throw new Exception ("Error parsing Measure.");
+ }
+
+ }
+}
--- /dev/null
+// Copyright (c) 2021-2025 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using Crow.Text;
+using Crow;
+
+namespace CrowEditBase
+{
+ public class TextBuffer {
+ static int bufferExpension = 100;
+ int length;
+ Memory<char> buffer;
+ ReadOnlyMemory<char> origBuffer;
+ LineCollection lines;
+ public bool mixedLineBreak = false;
+ public string lineBreak = null;
+
+ internal LineCollection GetLineListCopy() => new LineCollection(lines.ToArray());
+ public Span<char> Span => buffer.Span.Slice(0, length);
+ public ReadOnlySpan<char> ReadOnlySpan => buffer.Span.Slice(0, length);
+ public bool IsEmpty => length == 0;
+ public bool IsDirty => !origBuffer.Span.Equals(Span, StringComparison.Ordinal);
+ public int LinesCount => lines.Count;
+ public int Length => length;
+ public ReadOnlyMemory<char> ReadOnlyCopy {
+ get {
+ return ReadOnlySpan.ToArray();
+ }
+ }
+ public void ResetDirtyState () {
+ origBuffer = Span.ToArray();
+ }
+ public TextBuffer(ReadOnlySpan<char> origText) {
+ length = origText.Length;
+ buffer = new char[length + bufferExpension];
+ origText.CopyTo(buffer.Span);
+ lines = new LineCollection (10);
+ if (IsEmpty)
+ lines.Add (new TextLine (0, 0, 0));
+ else
+ lines.Update (Span);
+ ResetDirtyState ();
+ }
+ public void Update (TextChange change) {
+ ReadOnlySpan<char> orig = buffer.Span;
+ char[] newBuff = null;
+ Span<char> tmp;
+ if (buffer.Length < length + change.CharDiff) {
+ newBuff = new char[length + change.CharDiff + bufferExpension];
+ tmp = newBuff;
+ orig.Slice(0, change.Start).CopyTo(tmp);
+ if (change.CharDiff == 0)
+ orig.Slice(change.End, length - change.End).CopyTo(tmp.Slice(change.End));
+ } else
+ tmp = buffer.Span;
+
+ if (change.CharDiff != 0)
+ orig.Slice(change.End, length - change.End).CopyTo(tmp.Slice(change.End2));
+ if (!string.IsNullOrEmpty (change.ChangedText))
+ change.ChangedText.AsSpan ().CopyTo (tmp.Slice (change.Start));
+
+ if (newBuff != null)
+ buffer = newBuff;
+ length += change.CharDiff;
+
+ lines.Update (change);
+ }
+ public string GetLineBreak () {
+ if (string.IsNullOrEmpty (lineBreak)) {
+ mixedLineBreak = false;
+
+ if (lines.Count == 0 || lines[0].LineBreakLength == 0)
+ lineBreak = Environment.NewLine;
+ else {
+ lineBreak = ReadOnlySpan.GetLineBreak (lines[0]).ToString ();
+ for (int i = 1; i < lines.Count; i++) {
+ if (!ReadOnlySpan.GetLineBreak (lines[i]).SequenceEqual (lineBreak)) {
+ mixedLineBreak = true;
+ break;
+ }
+ }
+ }
+ }
+ return lineBreak;
+ }
+ public CharLocation GetLocation (int absolutePosition) => lines.GetLocation (absolutePosition);
+ public TextLine GetLine (int index) => lines[index];
+ public void SetLine (int index, TextLine line) => lines[index] = line;
+ public ReadOnlySpan<char> GetText (TextLine line) => GetText(line.Span);
+ public ReadOnlySpan<char> GetText (TextSpan textSpan) => buffer.Span.Slice(textSpan.Start, textSpan.Length);
+ public int GetAbsolutePosition (CharLocation loc) => lines.GetAbsolutePosition (loc);
+ public CharLocation EndLocation => new CharLocation (lines.Count - 1, lines[lines.Count - 1].Length);
+ public override string ToString() => ReadOnlySpan.ToString();
+ }
+}
\ No newline at end of file
--- /dev/null
+// Copyright (c) 2013-2021 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Crow.Text
+{
+ public struct TextChange
+ {
+ public readonly int Start;
+ public readonly int Length;
+ public string ChangedText;
+
+ public int End => Start + Length;
+ public int End2 => End + CharDiff;
+
+ public int CharDiff => string.IsNullOrEmpty (ChangedText) ? - Length : ChangedText.Length - Length;
+ public bool IsEmpty => string.IsNullOrEmpty (ChangedText) && Length == 0;
+ public bool HasNewText => !string.IsNullOrEmpty (ChangedText);
+
+ public bool HasNoEffect(ReadOnlySpan<char> src)
+ => src.Slice(Start, Length).Equals(ChangedText, StringComparison.Ordinal);
+
+ public TextChange (int position, int length, string changedText = null) {
+ Start = position;
+ Length = length;
+ ChangedText = changedText;
+ }
+ public TextChange (int position, int length, ReadOnlySpan<char> changedText) {
+ Start = position;
+ Length = length;
+ ChangedText = changedText.ToString();
+ }
+ public TextChange Inverse (ReadOnlySpan<char> src)
+ => new TextChange (Start, string.IsNullOrEmpty (ChangedText) ? 0 : ChangedText.Length,
+ Length == 0 ? "" : src.Slice (Start, Length).ToString ());
+ public override string ToString() => $"{Start},{ChangedText}";
+ }
+}
--- /dev/null
+// Copyright (c) 2013-2021 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+
+namespace Crow.Text
+{
+ public enum LineBreakKind
+ {
+ Undefined,
+ Unix,
+ Windows,
+ Other
+ }
+ /// <summary>
+ /// represent a single line span with end of line handling.
+ /// </summary>
+ [DebuggerDisplay ("{Start}, {Length}, {LengthInPixel}")]
+ public struct TextLine : IComparable<TextLine>
+ {
+ /// <summary>
+ /// Line start absolute character position.
+ /// </summary>
+ public int Start;
+ /// <summary>
+ /// Line's character count not including linebreak if any.
+ /// </summary>
+ public int Length;
+ /// <summary>
+ /// Total line's Character count including linebreak characters if any.
+ /// </summary>
+ public int LengthIncludingLineBreak;
+ /// <summary>
+ /// Cached line's length in pixel. If not yet computed by renderer, value is '-1'
+ /// </summary>
+ public int LengthInPixel;
+ /// <summary>
+ /// Absolute end character position just before linebreak if any.
+ /// </summary>
+ public int End => Start + Length;
+ public TextSpan Span => new TextSpan (Start, End);
+ public TextSpan SpanIncludingLineBreak => new TextSpan (Start, EndIncludingLineBreak);
+ /// <summary>
+ /// Absolute line's end position after linebreak if any.
+ /// </summary>
+ public int EndIncludingLineBreak => Start + LengthIncludingLineBreak;
+ /// <summary>
+ /// Character count of the linebreak, 0 if no linebreak.
+ /// </summary>
+ public int LineBreakLength => LengthIncludingLineBreak - Length;
+ /// <summary>
+ /// True line has a linebreak, false otherwise.
+ /// </summary>
+ public bool HasLineBreak => LineBreakLength > 0;
+ /// <summary>
+ /// Create a new TextLine span using the absolute start and end character positions.
+ /// </summary>
+ public TextLine (int start, int end, int endIncludingLineBreak) {
+ Start = start;
+ Length = end - start;
+ LengthIncludingLineBreak = endIncludingLineBreak - start;
+ LengthInPixel = -1;
+ }
+ /// <summary>
+ /// Create an empty line span without linebreak starting at absolute charater position given in argument.
+ /// </summary>
+ /// <param name="start"></param>
+ public TextLine (int start) {
+ Start = start;
+ Length = 0;
+ LengthIncludingLineBreak = 0;
+ LengthInPixel = -1;
+ }
+ /// <summary>
+ /// Set a new line's length and reset computed length in pixel to '-1'.
+ /// </summary>
+ /// <param name="newLength"></param>
+ public void SetLength (int newLength) {
+ LengthInPixel = -1;
+ Length = newLength;
+ }
+ /// <summary>
+ /// Create a new TextLine span with a start offset, length in pixel is reseted.
+ /// </summary>
+ /// <param name="start"></param>
+ /// <returns></returns>
+ public TextLine WithStartOffset (int start) => new TextLine (Start + start, End, EndIncludingLineBreak);
+ public int CompareTo (TextLine other) => Start - other.Start;
+ }
+}
--- /dev/null
+// Copyright (c) 2013-2021 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Crow.Text
+{
+ public class LineCollection : IList<TextLine>
+ {
+ TextLine[] lines;
+ int length;
+
+ #region CTOR
+ public LineCollection (int capacity) {
+ lines = new TextLine[capacity];
+ length = 0;
+ }
+ public LineCollection (TextLine[] _lines, int capacity = -1) {
+ if (capacity >= _lines.Length) {
+ lines = new TextLine[capacity];
+ _lines.AsSpan ().CopyTo (lines);
+ } else
+ lines =_lines;
+
+ length = _lines.Length;
+ }
+
+ public LineCollection (string _text, int capacity = 4) : this (capacity) {
+ Update (_text.AsSpan ());
+ }
+ #endregion
+
+ public void Update (ReadOnlySpan<char> _text) {
+ length = 0;
+ int start = 0, i = 0;
+ while (i < _text.Length) {
+ char c = _text[i];
+ if (c == '\r') {
+ if (++i < _text.Length) {
+ if (_text[i] == '\n')
+ Add (new TextLine (start, i - 1, ++i));
+ else
+ Add (new TextLine (start, i - 1, i));
+ } else
+ Add (new TextLine (start, i - 1, i));
+ start = i;
+ } else if (c == '\n') {
+ if (++i < _text.Length) {
+ if (_text[i] == '\r')
+ Add (new TextLine (start, i - 1, ++i));
+ else
+ Add (new TextLine (start, i - 1, i));
+ } else
+ Add (new TextLine (start, i - 1, i));
+ start = i;
+
+ } else if (c == '\u0085' || c == '\u2028' || c == '\u2029')
+ Add (new TextLine (start, i - 1, i));
+ else
+ i++;
+ }
+
+ if (start < i)
+ Add (new TextLine (start, _text.Length, _text.Length));
+ else
+ Add (new TextLine (_text.Length, _text.Length, _text.Length));
+ }
+
+ public void Update (TextChange change) {
+ CharLocation locStart = GetLocation (change.Start);
+ int charsDiff = change.ChangedText.Length - change.Length;
+ int lineEnd = locStart.Line;
+ while (lineEnd < length - 1 && change.End >= lines[lineEnd + 1].Start)
+ lineEnd++;
+ int columnEnd = change.End - lines[lineEnd].Start;
+ int lineEndLineBreakLength = lines[lineEnd].LineBreakLength;
+
+ LineCollection newLines = new LineCollection (change.ChangedText);
+ int linesDiff = newLines.length - 1 - (lineEnd - locStart.Line);
+ TextLine endTl = lines[lineEnd];
+
+ if (linesDiff < 0)
+ RemoveAt (locStart.Line + 1, -linesDiff);
+ else if (linesDiff > 0) {
+ for (int i = 0; i < linesDiff; i++)
+ Insert (locStart.Line + 1, default);
+ }
+
+ int remainingColumns = endTl.Length - columnEnd;
+ lineEnd += linesDiff;
+ lines[lineEnd].SetLength (0);
+ lines[locStart.Line].SetLength (locStart.Column + newLines[0].Length);
+ lines[lineEnd].Length += remainingColumns;
+ if (newLines.Count > 1) {
+ lines[lineEnd].Length += newLines[newLines.Count - 1].Length;
+ lines[locStart.Line].LengthIncludingLineBreak = lines[locStart.Line].Length + newLines[0].LineBreakLength;
+ }
+ lines[lineEnd].LengthIncludingLineBreak = lines[lineEnd].Length + endTl.LineBreakLength;
+
+ for (int i = 1; i < newLines.Count - 1; i++) {
+ int l = locStart.Line + i;
+ lines[l] = newLines[i];
+ lines[l].Start = lines[l - 1].EndIncludingLineBreak;
+ }
+ if (lineEnd > 0)
+ lines[lineEnd].Start = lines[lineEnd - 1].EndIncludingLineBreak;
+
+ //shift start for remaining lines
+ for (int i = lineEnd + 1; i < length; i++)
+ lines[i].Start += charsDiff;
+ }
+ public int GetAbsolutePosition (CharLocation loc) => lines[loc.Line].Start + loc.Column;
+ public CharLocation GetLocation (int absolutePosition) {
+ TextLine tl = new TextLine (absolutePosition);
+ int result = lines.AsSpan (0, length).BinarySearch (tl);
+ if (result < 0) {
+ result = ~result;
+ return new CharLocation (result - 1, absolutePosition - lines[result - 1].Start);
+ }
+ return new CharLocation (result, absolutePosition - lines[result].Start);
+ }
+ [Obsolete]
+ public void UpdateLineLengthInPixel (int index, int lengthInPixel) {
+ lines[index].LengthInPixel = lengthInPixel;
+ }
+ public int Count => length;
+ public bool IsReadOnly => false;
+ public bool IsEmpty => length == 0;
+
+ public TextLine this[int index] { get => lines[index]; set => lines[index] = value; }
+
+ public void Add (TextLine item) {
+ if (lines.Length < length + 1) {
+ TextLine[] tmp = new TextLine[length * 2];
+ lines.AsSpan ().CopyTo (tmp);
+ lines = tmp;
+ }
+ lines[length] = item;
+ length++;
+ }
+
+ public void Clear () {
+ length = 0;
+ }
+
+ public bool Contains (TextLine item) => Array.IndexOf<TextLine> (lines, item) >= 0;
+
+ public void CopyTo (TextLine[] array, int arrayIndex) {
+ lines.AsSpan (0, length).CopyTo (array.AsSpan (arrayIndex));
+ }
+
+ public bool Remove (TextLine item) {
+ int idx = Array.IndexOf<TextLine> (lines, item);
+ if (idx < 0)
+ return false;
+ if (idx + 1 < length)
+ lines.AsSpan (idx + 1, length - idx - 1).CopyTo (lines.AsSpan (idx));
+ length--;
+ return true;
+ }
+ public IEnumerator<TextLine> GetEnumerator () => new Enumerator (this);
+ IEnumerator IEnumerable.GetEnumerator () => GetEnumerator ();
+
+ public int IndexOf (TextLine item) => Array.IndexOf (lines, item);
+
+ public void Insert (int index, TextLine item) {
+ if (lines.Length < length + 1) {
+ TextLine[] tmp = new TextLine[length * 2];
+ lines.AsSpan (0, index).CopyTo (tmp);
+ lines.AsSpan (index).CopyTo (tmp.AsSpan (index + 1));
+ lines = tmp;
+ }else
+ lines.AsSpan (index, length - index).CopyTo (lines.AsSpan (index + 1));
+ lines[index] = item;
+ length++;
+ }
+
+ public void RemoveAt (int index) {
+ if (index + 1 < length)
+ lines.AsSpan (index + 1, length - index - 1).CopyTo (lines.AsSpan (index));
+ length--;
+ }
+ public void RemoveAt (int index, int count) {
+ if (index + count < length)
+ lines.AsSpan (index + count, length - index - count).CopyTo (lines.AsSpan (index));
+ length -= count;
+ }
+
+ public class Enumerator : IEnumerator<TextLine>
+ {
+ TextLine[] lines;
+ int length, position = -1;
+ public Enumerator (LineCollection coll) {
+ lines = coll.lines;
+ length = coll.length;
+ }
+ public TextLine Current => lines[position];
+ object IEnumerator.Current => Current;
+ public void Dispose () { }
+ public bool MoveNext () => ++position < length;
+ public void Reset () {
+ position = -1;
+ }
+ }
+ }
+}
--- /dev/null
+// Copyright (c) 2013-2021 Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Crow.Text
+{
+ public struct TextSpan : IEquatable<TextSpan>
+ {
+ public readonly int Start;
+ public readonly int End;
+ public TextSpan (int start, int end) {
+ Start = start;
+ End = end;
+ }
+ public static TextSpan FromStartAndLength (int start, int length = 0) => new TextSpan (start, start + length);
+ public bool IsEmpty => Start == End;
+ public int Length => End - Start;
+
+ public bool Equals(TextSpan other)
+ => Start == other.Start && End == other.End;
+ public override bool Equals(object obj)
+ => obj is TextSpan ts ? Equals(ts) : false;
+
+ public override int GetHashCode()
+ => HashCode.Combine(Start, End);
+ public static bool operator ==(TextSpan left, TextSpan right)
+ => left.Equals (right);
+ public static bool operator !=(TextSpan left, TextSpan right)
+ => !left.Equals (right);
+ public override string ToString() => $"{Start},{End}";
+ public bool Contains (int absolutePosition)
+ => absolutePosition >= Start && absolutePosition < End;
+ public bool Contains (TextSpan span)
+ => Start <= span.Start && End >= span.End;
+ }
+}