TextRange.cs 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. // RichTextKit
  2. // Copyright © 2019-2020 Topten Software. All Rights Reserved.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License"); you may
  5. // not use this product except in compliance with the License. You may obtain
  6. // a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. // License for the specific language governing permissions and limitations
  14. // under the License.
  15. using System;
  16. using System.IO;
  17. using System.Xml.Schema;
  18. namespace Topten.RichTextKit
  19. {
  20. /// <summary>
  21. /// Represents a range of code points in a text document
  22. /// </summary>
  23. public struct TextRange
  24. {
  25. /// <summary>
  26. /// Initializes a TextRange
  27. /// </summary>
  28. /// <param name="start">The code point index of the start of the range</param>
  29. /// <param name="end">The code point index of the end of the range</param>
  30. /// <param name="altPosition">Whether the caret at the end of the range should be displayed in its alternative position</param>
  31. public TextRange(int start, int end, bool altPosition = false)
  32. {
  33. Start = start;
  34. End = end;
  35. AltPosition = altPosition;
  36. }
  37. /// <summary>
  38. /// Initializes a TextRange with a non-range position
  39. /// </summary>
  40. /// <param name="position">The code point index of the position</param>
  41. /// <param name="altPosition">Whether the caret should be displayed in its alternative position</param>
  42. public TextRange(int position, bool altPosition = false)
  43. {
  44. Start = position;
  45. End = position;
  46. AltPosition = altPosition;
  47. }
  48. /// <summary>
  49. /// Initializes a TextRange from a caret position
  50. /// </summary>
  51. /// <param name="position">The code point index of the position</param>
  52. public TextRange(CaretPosition position)
  53. {
  54. Start = position.CodePointIndex;
  55. End = position.CodePointIndex;
  56. AltPosition = position.AltPosition;
  57. }
  58. /// <summary>
  59. /// The code point index of the start of the range
  60. /// </summary>
  61. public int Start;
  62. /// <summary>
  63. /// The code point index of the end of the range
  64. /// </summary>
  65. public int End;
  66. /// <summary>
  67. /// True if the end of the range should be displayed
  68. /// with the caret in the alt position
  69. /// </summary>
  70. public bool AltPosition;
  71. /// <summary>
  72. /// Get the length of this range
  73. /// </summary>
  74. /// <remarks>
  75. /// Will return negative if the range isn't normalized
  76. /// </remarks>
  77. public int Length => End - Start;
  78. /// <summary>
  79. /// Offset this text range by the specified amount
  80. /// </summary>
  81. /// <param name="delta">The number of code points to offset the range by</param>
  82. /// <returns>A new TextRange</returns>
  83. public TextRange Offset(int delta)
  84. {
  85. return new TextRange(Start + delta, End + delta, AltPosition);
  86. }
  87. /// <summary>
  88. /// Returns the reversed text range
  89. /// </summary>
  90. public TextRange Reversed
  91. {
  92. get
  93. {
  94. return new TextRange(End, Start, false);
  95. }
  96. }
  97. /// <summary>
  98. /// Returns the normalized version of the text range
  99. /// </summary>
  100. public TextRange Normalized
  101. {
  102. get
  103. {
  104. if (Start > End)
  105. return Reversed;
  106. else
  107. return this;
  108. }
  109. }
  110. /// <summary>
  111. /// Compare this text range to another for equality
  112. /// </summary>
  113. /// <param name="other">The text range to compare to</param>
  114. /// <returns>True if the text ranges are equal</returns>
  115. public bool IsEqual(TextRange other)
  116. {
  117. return other.Start == Start &&
  118. other.End == End &&
  119. other.AltPosition == AltPosition;
  120. }
  121. /// <summary>
  122. /// Check if this is actually a range
  123. /// </summary>
  124. public bool IsRange => Start != End;
  125. /// <summary>
  126. /// Get the end of the range closer to the start of the document
  127. /// </summary>
  128. public int Minimum => Math.Min(Start, End);
  129. /// <summary>
  130. /// Get the end of the range closer to the end of the document
  131. /// </summary>
  132. public int Maximum => Math.Max(Start, End);
  133. /// <summary>
  134. /// Gets the end of the range as a caret position
  135. /// </summary>
  136. public CaretPosition CaretPosition
  137. {
  138. get
  139. {
  140. return new CaretPosition(End, AltPosition);
  141. }
  142. }
  143. /// <summary>
  144. /// Clamp the text range to a document length
  145. /// </summary>
  146. /// <param name="maxCodePointIndex">The max code point index</param>
  147. /// <returns>A clamped TextRange</returns>
  148. public TextRange Clamp(int maxCodePointIndex)
  149. {
  150. var newStart = Start;
  151. if (newStart < 0)
  152. newStart = 0;
  153. if (newStart > maxCodePointIndex)
  154. newStart = maxCodePointIndex;
  155. var newEnd = End;
  156. if (newEnd < 0)
  157. newEnd = 0;
  158. if (newEnd > maxCodePointIndex)
  159. newEnd = maxCodePointIndex;
  160. return new TextRange(newStart, newEnd, AltPosition);
  161. }
  162. /// <summary>
  163. /// Create an updated text range that tries to represent the same
  164. /// piece of text from before an edit to after the edit.
  165. /// </summary>
  166. /// <param name="codePointIndex">The position of the edit</param>
  167. /// <param name="oldLength">The length of text deleted</param>
  168. /// <param name="newLength">The length of text inserted</param>
  169. /// <returns>An updated text range</returns>
  170. public TextRange UpdateForEdit(int codePointIndex, int oldLength, int newLength)
  171. {
  172. int delta = newLength - oldLength;
  173. // After this range?
  174. if (codePointIndex > Maximum)
  175. return this;
  176. // Before this range?
  177. if (codePointIndex + oldLength <= Minimum)
  178. return new TextRange(Start + delta, End + delta, AltPosition);
  179. // Entire range?
  180. if (codePointIndex <= Minimum && codePointIndex + oldLength >= Maximum)
  181. return new TextRange(codePointIndex, codePointIndex, AltPosition);
  182. // Inside this range?
  183. if (codePointIndex >= Minimum && codePointIndex + oldLength <= Maximum)
  184. {
  185. if (Start < End)
  186. return new TextRange(Start, End + delta, AltPosition);
  187. else
  188. return new TextRange(End, Start + delta, AltPosition);
  189. }
  190. // Overlap start of this range?
  191. if (codePointIndex < Minimum && codePointIndex + oldLength >= Minimum)
  192. {
  193. if (Start < End)
  194. return new TextRange(codePointIndex + newLength, End + delta, AltPosition);
  195. else
  196. return new TextRange(Start + delta, codePointIndex + newLength, AltPosition);
  197. }
  198. // Overlap end of this range?
  199. if (codePointIndex >= Minimum && codePointIndex + oldLength > Maximum)
  200. {
  201. if (Start < End)
  202. return new TextRange(Start, codePointIndex, AltPosition);
  203. else
  204. return new TextRange(codePointIndex, End, AltPosition);
  205. }
  206. // Should never get here.
  207. return new TextRange();
  208. }
  209. /// <summary>
  210. /// Create a new range that is the union of two other ranges. The
  211. /// union is the smallest range that contains the other two other
  212. /// ranges.
  213. /// </summary>
  214. /// <remarks>
  215. /// The returned range is configured such that the 'b' range
  216. /// is used for the end position (ie: the caret)
  217. /// </remarks>
  218. /// <param name="a">The first text range</param>
  219. /// <param name="b">The second text range</param>
  220. /// <returns>A range that encompasses both ranges</returns>
  221. public static TextRange Union(TextRange a, TextRange b)
  222. {
  223. if (a.Minimum <= b.Minimum)
  224. {
  225. return new TextRange(
  226. Math.Min(a.Minimum, b.Minimum),
  227. Math.Max(a.Maximum, b.Maximum), b.AltPosition);
  228. }
  229. else
  230. {
  231. return new TextRange(
  232. Math.Max(a.Maximum, b.Maximum),
  233. Math.Min(a.Minimum, b.Minimum), b.AltPosition);
  234. }
  235. }
  236. /// <inheritdoc />
  237. public override string ToString()
  238. {
  239. return $"{Start} → {End} (len: {Length})";
  240. }
  241. }
  242. }