123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- // 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
- {
- /// <summary>
- /// Represents a range of code points in a text document
- /// </summary>
- public struct TextRange
- {
- /// <summary>
- /// Initializes a TextRange
- /// </summary>
- /// <param name="start">The code point index of the start of the range</param>
- /// <param name="end">The code point index of the end of the range</param>
- /// <param name="altPosition">Whether the caret at the end of the range should be displayed in its alternative position</param>
- public TextRange(int start, int end, bool altPosition = false)
- {
- Start = start;
- End = end;
- AltPosition = altPosition;
- }
- /// <summary>
- /// Initializes a TextRange with a non-range position
- /// </summary>
- /// <param name="position">The code point index of the position</param>
- /// <param name="altPosition">Whether the caret should be displayed in its alternative position</param>
- public TextRange(int position, bool altPosition = false)
- {
- Start = position;
- End = position;
- AltPosition = altPosition;
- }
- /// <summary>
- /// Initializes a TextRange from a caret position
- /// </summary>
- /// <param name="position">The code point index of the position</param>
- public TextRange(CaretPosition position)
- {
- Start = position.CodePointIndex;
- End = position.CodePointIndex;
- AltPosition = position.AltPosition;
- }
- /// <summary>
- /// The code point index of the start of the range
- /// </summary>
- public int Start;
- /// <summary>
- /// The code point index of the end of the range
- /// </summary>
- public int End;
- /// <summary>
- /// True if the end of the range should be displayed
- /// with the caret in the alt position
- /// </summary>
- public bool AltPosition;
- /// <summary>
- /// Get the length of this range
- /// </summary>
- /// <remarks>
- /// Will return negative if the range isn't normalized
- /// </remarks>
- public int Length => End - Start;
- /// <summary>
- /// Offset this text range by the specified amount
- /// </summary>
- /// <param name="delta">The number of code points to offset the range by</param>
- /// <returns>A new TextRange</returns>
- public TextRange Offset(int delta)
- {
- return new TextRange(Start + delta, End + delta, AltPosition);
- }
- /// <summary>
- /// Returns the reversed text range
- /// </summary>
- public TextRange Reversed
- {
- get
- {
- return new TextRange(End, Start, false);
- }
- }
- /// <summary>
- /// Returns the normalized version of the text range
- /// </summary>
- public TextRange Normalized
- {
- get
- {
- if (Start > End)
- return Reversed;
- else
- return this;
- }
- }
- /// <summary>
- /// Compare this text range to another for equality
- /// </summary>
- /// <param name="other">The text range to compare to</param>
- /// <returns>True if the text ranges are equal</returns>
- public bool IsEqual(TextRange other)
- {
- return other.Start == Start &&
- other.End == End &&
- other.AltPosition == AltPosition;
- }
- /// <summary>
- /// Check if this is actually a range
- /// </summary>
- public bool IsRange => Start != End;
- /// <summary>
- /// Get the end of the range closer to the start of the document
- /// </summary>
- public int Minimum => Math.Min(Start, End);
- /// <summary>
- /// Get the end of the range closer to the end of the document
- /// </summary>
- public int Maximum => Math.Max(Start, End);
- /// <summary>
- /// Gets the end of the range as a caret position
- /// </summary>
- public CaretPosition CaretPosition
- {
- get
- {
- return new CaretPosition(End, AltPosition);
- }
- }
- /// <summary>
- /// Clamp the text range to a document length
- /// </summary>
- /// <param name="maxCodePointIndex">The max code point index</param>
- /// <returns>A clamped TextRange</returns>
- 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);
- }
- /// <summary>
- /// Create an updated text range that tries to represent the same
- /// piece of text from before an edit to after the edit.
- /// </summary>
- /// <param name="codePointIndex">The position of the edit</param>
- /// <param name="oldLength">The length of text deleted</param>
- /// <param name="newLength">The length of text inserted</param>
- /// <returns>An updated text range</returns>
- 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();
- }
- /// <summary>
- /// 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.
- /// </summary>
- /// <remarks>
- /// The returned range is configured such that the 'b' range
- /// is used for the end position (ie: the caret)
- /// </remarks>
- /// <param name="a">The first text range</param>
- /// <param name="b">The second text range</param>
- /// <returns>A range that encompasses both ranges</returns>
- 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);
- }
- }
- /// <inheritdoc />
- public override string ToString()
- {
- return $"{Start} → {End} (len: {Length})";
- }
- }
- }
|