// RichTextKit // Copyright © 2019-2020 Topten Software. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this product except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. using System; using System.IO; using System.Xml.Schema; namespace Topten.RichTextKit { /// /// Represents a range of code points in a text document /// public struct TextRange { /// /// Initializes a TextRange /// /// The code point index of the start of the range /// The code point index of the end of the range /// Whether the caret at the end of the range should be displayed in its alternative position public TextRange(int start, int end, bool altPosition = false) { Start = start; End = end; AltPosition = altPosition; } /// /// Initializes a TextRange with a non-range position /// /// The code point index of the position /// Whether the caret should be displayed in its alternative position public TextRange(int position, bool altPosition = false) { Start = position; End = position; AltPosition = altPosition; } /// /// Initializes a TextRange from a caret position /// /// The code point index of the position public TextRange(CaretPosition position) { Start = position.CodePointIndex; End = position.CodePointIndex; AltPosition = position.AltPosition; } /// /// The code point index of the start of the range /// public int Start; /// /// The code point index of the end of the range /// public int End; /// /// True if the end of the range should be displayed /// with the caret in the alt position /// public bool AltPosition; /// /// Get the length of this range /// /// /// Will return negative if the range isn't normalized /// public int Length => End - Start; /// /// Offset this text range by the specified amount /// /// The number of code points to offset the range by /// A new TextRange public TextRange Offset(int delta) { return new TextRange(Start + delta, End + delta, AltPosition); } /// /// Returns the reversed text range /// public TextRange Reversed { get { return new TextRange(End, Start, false); } } /// /// Returns the normalized version of the text range /// public TextRange Normalized { get { if (Start > End) return Reversed; else return this; } } /// /// Compare this text range to another for equality /// /// The text range to compare to /// True if the text ranges are equal public bool IsEqual(TextRange other) { return other.Start == Start && other.End == End && other.AltPosition == AltPosition; } /// /// Check if this is actually a range /// public bool IsRange => Start != End; /// /// Get the end of the range closer to the start of the document /// public int Minimum => Math.Min(Start, End); /// /// Get the end of the range closer to the end of the document /// public int Maximum => Math.Max(Start, End); /// /// Gets the end of the range as a caret position /// public CaretPosition CaretPosition { get { return new CaretPosition(End, AltPosition); } } /// /// Clamp the text range to a document length /// /// The max code point index /// A clamped TextRange public TextRange Clamp(int maxCodePointIndex) { var newStart = Start; if (newStart < 0) newStart = 0; if (newStart > maxCodePointIndex) newStart = maxCodePointIndex; var newEnd = End; if (newEnd < 0) newEnd = 0; if (newEnd > maxCodePointIndex) newEnd = maxCodePointIndex; return new TextRange(newStart, newEnd, AltPosition); } /// /// Create an updated text range that tries to represent the same /// piece of text from before an edit to after the edit. /// /// The position of the edit /// The length of text deleted /// The length of text inserted /// An updated text range public TextRange UpdateForEdit(int codePointIndex, int oldLength, int newLength) { int delta = newLength - oldLength; // After this range? if (codePointIndex > Maximum) return this; // Before this range? if (codePointIndex + oldLength <= Minimum) return new TextRange(Start + delta, End + delta, AltPosition); // Entire range? if (codePointIndex <= Minimum && codePointIndex + oldLength >= Maximum) return new TextRange(codePointIndex, codePointIndex, AltPosition); // Inside this range? if (codePointIndex >= Minimum && codePointIndex + oldLength <= Maximum) { if (Start < End) return new TextRange(Start, End + delta, AltPosition); else return new TextRange(End, Start + delta, AltPosition); } // Overlap start of this range? if (codePointIndex < Minimum && codePointIndex + oldLength >= Minimum) { if (Start < End) return new TextRange(codePointIndex + newLength, End + delta, AltPosition); else return new TextRange(Start + delta, codePointIndex + newLength, AltPosition); } // Overlap end of this range? if (codePointIndex >= Minimum && codePointIndex + oldLength > Maximum) { if (Start < End) return new TextRange(Start, codePointIndex, AltPosition); else return new TextRange(codePointIndex, End, AltPosition); } // Should never get here. return new TextRange(); } /// /// Create a new range that is the union of two other ranges. The /// union is the smallest range that contains the other two other /// ranges. /// /// /// The returned range is configured such that the 'b' range /// is used for the end position (ie: the caret) /// /// The first text range /// The second text range /// A range that encompasses both ranges public static TextRange Union(TextRange a, TextRange b) { if (a.Minimum <= b.Minimum) { return new TextRange( Math.Min(a.Minimum, b.Minimum), Math.Max(a.Maximum, b.Maximum), b.AltPosition); } else { return new TextRange( Math.Max(a.Maximum, b.Maximum), Math.Min(a.Minimum, b.Minimum), b.AltPosition); } } /// public override string ToString() { return $"{Start} → {End} (len: {Length})"; } } }