rustpython_ruff_text_size/range.rs
1use cmp::Ordering;
2
3use {
4 crate::TextSize,
5 std::{
6 cmp, fmt,
7 ops::{Add, AddAssign, Bound, Index, IndexMut, Range, RangeBounds, Sub, SubAssign},
8 },
9};
10
11/// A range in text, represented as a pair of [`TextSize`][struct@TextSize].
12///
13/// It is a logic error for `start` to be greater than `end`.
14#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
15#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
16pub struct TextRange {
17 // Invariant: start <= end
18 start: TextSize,
19 end: TextSize,
20}
21
22impl fmt::Debug for TextRange {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 write!(f, "{}..{}", self.start().raw, self.end().raw)
25 }
26}
27
28impl TextRange {
29 /// Creates a new `TextRange` with the given `start` and `end` (`start..end`).
30 ///
31 /// # Panics
32 ///
33 /// Panics if `end < start`.
34 ///
35 /// # Examples
36 ///
37 /// ```rust
38 /// # use ruff_text_size::*;
39 /// let start = TextSize::from(5);
40 /// let end = TextSize::from(10);
41 /// let range = TextRange::new(start, end);
42 ///
43 /// assert_eq!(range.start(), start);
44 /// assert_eq!(range.end(), end);
45 /// assert_eq!(range.len(), end - start);
46 /// ```
47 #[inline]
48 #[track_caller]
49 pub const fn new(start: TextSize, end: TextSize) -> TextRange {
50 assert!(start.raw <= end.raw);
51 TextRange { start, end }
52 }
53
54 /// Create a new `TextRange` with the given `offset` and `len` (`offset..offset + len`).
55 ///
56 /// # Examples
57 ///
58 /// ```rust
59 /// # use ruff_text_size::*;
60 /// let text = "0123456789";
61 ///
62 /// let offset = TextSize::from(2);
63 /// let length = TextSize::from(5);
64 /// let range = TextRange::at(offset, length);
65 ///
66 /// assert_eq!(range, TextRange::new(offset, offset + length));
67 /// assert_eq!(&text[range], "23456")
68 /// ```
69 #[inline]
70 pub fn at(offset: TextSize, len: TextSize) -> TextRange {
71 TextRange::new(offset, offset + len)
72 }
73
74 /// Create a zero-length range at the specified offset (`offset..offset`).
75 ///
76 /// # Examples
77 ///
78 /// ```rust
79 /// # use ruff_text_size::*;
80 /// let point: TextSize;
81 /// # point = TextSize::from(3);
82 /// let range = TextRange::empty(point);
83 /// assert!(range.is_empty());
84 /// assert_eq!(range, TextRange::new(point, point));
85 /// ```
86 #[inline]
87 pub fn empty(offset: TextSize) -> TextRange {
88 TextRange {
89 start: offset,
90 end: offset,
91 }
92 }
93
94 /// Create a range up to the given end (`..end`).
95 ///
96 /// # Examples
97 ///
98 /// ```rust
99 /// # use ruff_text_size::*;
100 /// let point: TextSize;
101 /// # point = TextSize::from(12);
102 /// let range = TextRange::up_to(point);
103 ///
104 /// assert_eq!(range.len(), point);
105 /// assert_eq!(range, TextRange::new(0.into(), point));
106 /// assert_eq!(range, TextRange::at(0.into(), point));
107 /// ```
108 #[inline]
109 pub fn up_to(end: TextSize) -> TextRange {
110 TextRange {
111 start: 0.into(),
112 end,
113 }
114 }
115}
116
117/// Identity methods.
118impl TextRange {
119 /// The start point of this range.
120 #[inline]
121 pub const fn start(self) -> TextSize {
122 self.start
123 }
124
125 /// The end point of this range.
126 #[inline]
127 pub const fn end(self) -> TextSize {
128 self.end
129 }
130
131 /// The size of this range.
132 #[inline]
133 pub const fn len(self) -> TextSize {
134 // HACK for const fn: math on primitives only
135 TextSize {
136 raw: self.end().raw - self.start().raw,
137 }
138 }
139
140 /// Check if this range is empty.
141 #[inline]
142 pub const fn is_empty(self) -> bool {
143 // HACK for const fn: math on primitives only
144 self.start().raw == self.end().raw
145 }
146}
147
148/// Manipulation methods.
149impl TextRange {
150 /// Check if this range contains an offset.
151 ///
152 /// The end index is considered excluded.
153 ///
154 /// # Examples
155 ///
156 /// ```rust
157 /// # use ruff_text_size::*;
158 /// let (start, end): (TextSize, TextSize);
159 /// # start = 10.into(); end = 20.into();
160 /// let range = TextRange::new(start, end);
161 /// assert!(range.contains(start));
162 /// assert!(!range.contains(end));
163 /// ```
164 #[inline]
165 pub fn contains(self, offset: TextSize) -> bool {
166 self.start() <= offset && offset < self.end()
167 }
168
169 /// Check if this range contains an offset.
170 ///
171 /// The end index is considered included.
172 ///
173 /// # Examples
174 ///
175 /// ```rust
176 /// # use ruff_text_size::*;
177 /// let (start, end): (TextSize, TextSize);
178 /// # start = 10.into(); end = 20.into();
179 /// let range = TextRange::new(start, end);
180 /// assert!(range.contains_inclusive(start));
181 /// assert!(range.contains_inclusive(end));
182 /// ```
183 #[inline]
184 pub fn contains_inclusive(self, offset: TextSize) -> bool {
185 self.start() <= offset && offset <= self.end()
186 }
187
188 /// Check if this range completely contains another range.
189 ///
190 /// # Examples
191 ///
192 /// ```rust
193 /// # use ruff_text_size::*;
194 /// let larger = TextRange::new(0.into(), 20.into());
195 /// let smaller = TextRange::new(5.into(), 15.into());
196 /// assert!(larger.contains_range(smaller));
197 /// assert!(!smaller.contains_range(larger));
198 ///
199 /// // a range always contains itself
200 /// assert!(larger.contains_range(larger));
201 /// assert!(smaller.contains_range(smaller));
202 /// ```
203 #[inline]
204 pub fn contains_range(self, other: TextRange) -> bool {
205 self.start() <= other.start() && other.end() <= self.end()
206 }
207
208 /// The range covered by both ranges, if it exists.
209 /// If the ranges touch but do not overlap, the output range is empty.
210 ///
211 /// # Examples
212 ///
213 /// ```rust
214 /// # use ruff_text_size::*;
215 /// assert_eq!(
216 /// TextRange::intersect(
217 /// TextRange::new(0.into(), 10.into()),
218 /// TextRange::new(5.into(), 15.into()),
219 /// ),
220 /// Some(TextRange::new(5.into(), 10.into())),
221 /// );
222 /// ```
223 #[inline]
224 pub fn intersect(self, other: TextRange) -> Option<TextRange> {
225 let start = cmp::max(self.start(), other.start());
226 let end = cmp::min(self.end(), other.end());
227 if end < start {
228 return None;
229 }
230 Some(TextRange::new(start, end))
231 }
232
233 /// Extends the range to cover `other` as well.
234 ///
235 /// # Examples
236 ///
237 /// ```rust
238 /// # use ruff_text_size::*;
239 /// assert_eq!(
240 /// TextRange::cover(
241 /// TextRange::new(0.into(), 5.into()),
242 /// TextRange::new(15.into(), 20.into()),
243 /// ),
244 /// TextRange::new(0.into(), 20.into()),
245 /// );
246 /// ```
247 #[inline]
248 #[must_use]
249 pub fn cover(self, other: TextRange) -> TextRange {
250 let start = cmp::min(self.start(), other.start());
251 let end = cmp::max(self.end(), other.end());
252 TextRange::new(start, end)
253 }
254
255 /// Extends the range to cover `other` offsets as well.
256 ///
257 /// # Examples
258 ///
259 /// ```rust
260 /// # use ruff_text_size::*;
261 /// assert_eq!(
262 /// TextRange::empty(0.into()).cover_offset(20.into()),
263 /// TextRange::new(0.into(), 20.into()),
264 /// )
265 /// ```
266 #[inline]
267 #[must_use]
268 pub fn cover_offset(self, offset: TextSize) -> TextRange {
269 self.cover(TextRange::empty(offset))
270 }
271
272 /// Add an offset to this range.
273 ///
274 /// Note that this is not appropriate for changing where a `TextRange` is
275 /// within some string; rather, it is for changing the reference anchor
276 /// that the `TextRange` is measured against.
277 ///
278 /// The unchecked version (`Add::add`) will _always_ panic on overflow,
279 /// in contrast to primitive integers, which check in debug mode only.
280 #[inline]
281 pub fn checked_add(self, offset: TextSize) -> Option<TextRange> {
282 Some(TextRange {
283 start: self.start.checked_add(offset)?,
284 end: self.end.checked_add(offset)?,
285 })
286 }
287
288 /// Subtract an offset from this range.
289 ///
290 /// Note that this is not appropriate for changing where a `TextRange` is
291 /// within some string; rather, it is for changing the reference anchor
292 /// that the `TextRange` is measured against.
293 ///
294 /// The unchecked version (`Sub::sub`) will _always_ panic on overflow,
295 /// in contrast to primitive integers, which check in debug mode only.
296 #[inline]
297 pub fn checked_sub(self, offset: TextSize) -> Option<TextRange> {
298 Some(TextRange {
299 start: self.start.checked_sub(offset)?,
300 end: self.end.checked_sub(offset)?,
301 })
302 }
303
304 /// Relative order of the two ranges (overlapping ranges are considered
305 /// equal).
306 ///
307 ///
308 /// This is useful when, for example, binary searching an array of disjoint
309 /// ranges.
310 ///
311 /// # Examples
312 ///
313 /// ```
314 /// # use ruff_text_size::*;
315 /// # use std::cmp::Ordering;
316 ///
317 /// let a = TextRange::new(0.into(), 3.into());
318 /// let b = TextRange::new(4.into(), 5.into());
319 /// assert_eq!(a.ordering(b), Ordering::Less);
320 ///
321 /// let a = TextRange::new(0.into(), 3.into());
322 /// let b = TextRange::new(3.into(), 5.into());
323 /// assert_eq!(a.ordering(b), Ordering::Less);
324 ///
325 /// let a = TextRange::new(0.into(), 3.into());
326 /// let b = TextRange::new(2.into(), 5.into());
327 /// assert_eq!(a.ordering(b), Ordering::Equal);
328 ///
329 /// let a = TextRange::new(0.into(), 3.into());
330 /// let b = TextRange::new(2.into(), 2.into());
331 /// assert_eq!(a.ordering(b), Ordering::Equal);
332 ///
333 /// let a = TextRange::new(2.into(), 3.into());
334 /// let b = TextRange::new(2.into(), 2.into());
335 /// assert_eq!(a.ordering(b), Ordering::Greater);
336 /// ```
337 #[inline]
338 pub fn ordering(self, other: TextRange) -> Ordering {
339 if self.end() <= other.start() {
340 Ordering::Less
341 } else if other.end() <= self.start() {
342 Ordering::Greater
343 } else {
344 Ordering::Equal
345 }
346 }
347
348 /// Returns a new range with the start offset set to the
349 /// value given and the end offset unchanged from this
350 /// range.
351 ///
352 /// ## Panics
353 ///
354 /// When `offset > self.end()`.
355 ///
356 /// ## Examples
357 ///
358 /// ```
359 /// use ruff_text_size::{Ranged, TextRange, TextSize};
360 ///
361 /// let range = TextRange::new(TextSize::from(5), TextSize::from(10));
362 /// let new = range.with_start(TextSize::from(8));
363 /// assert_eq!(new, TextRange::new(TextSize::from(8), TextSize::from(10)));
364 /// ```
365 #[inline]
366 #[must_use]
367 pub fn with_start(&self, offset: TextSize) -> TextRange {
368 TextRange::new(offset, self.end())
369 }
370
371 /// Returns a new range with the end offset set to the
372 /// value given and the start offset unchanged from this
373 /// range.
374 ///
375 /// ## Panics
376 ///
377 /// When `offset < self.start()`.
378 ///
379 /// ## Examples
380 ///
381 /// ```
382 /// use ruff_text_size::{Ranged, TextRange, TextSize};
383 ///
384 /// let range = TextRange::new(TextSize::from(5), TextSize::from(10));
385 /// let new = range.with_end(TextSize::from(8));
386 /// assert_eq!(new, TextRange::new(TextSize::from(5), TextSize::from(8)));
387 /// ```
388 #[inline]
389 #[must_use]
390 pub fn with_end(&self, offset: TextSize) -> TextRange {
391 TextRange::new(self.start(), offset)
392 }
393
394 /// Subtracts an offset from the start position.
395 ///
396 ///
397 /// ## Panics
398 /// If `start - amount` is less than zero.
399 ///
400 /// ## Examples
401 ///
402 /// ```
403 /// use ruff_text_size::{Ranged, TextRange, TextSize};
404 ///
405 /// let range = TextRange::new(TextSize::from(5), TextSize::from(10));
406 /// assert_eq!(range.sub_start(TextSize::from(2)), TextRange::new(TextSize::from(3), TextSize::from(10)));
407 /// ```
408 #[inline]
409 #[must_use]
410 pub fn sub_start(&self, amount: TextSize) -> TextRange {
411 TextRange::new(self.start() - amount, self.end())
412 }
413
414 /// Adds an offset to the start position.
415 ///
416 /// ## Panics
417 /// If `start + amount > end`
418 ///
419 /// ## Examples
420 ///
421 /// ```
422 /// use ruff_text_size::{Ranged, TextRange, TextSize};
423 ///
424 /// let range = TextRange::new(TextSize::from(5), TextSize::from(10));
425 /// assert_eq!(range.add_start(TextSize::from(3)), TextRange::new(TextSize::from(8), TextSize::from(10)));
426 /// ```
427 #[inline]
428 #[must_use]
429 pub fn add_start(&self, amount: TextSize) -> TextRange {
430 TextRange::new(self.start() + amount, self.end())
431 }
432
433 /// Subtracts an offset from the end position.
434 ///
435 ///
436 /// ## Panics
437 /// If `end - amount < 0` or `end - amount < start`
438 ///
439 /// ## Examples
440 ///
441 /// ```
442 /// use ruff_text_size::{Ranged, TextRange, TextSize};
443 ///
444 /// let range = TextRange::new(TextSize::from(5), TextSize::from(10));
445 /// assert_eq!(range.sub_end(TextSize::from(2)), TextRange::new(TextSize::from(5), TextSize::from(8)));
446 /// ```
447 #[inline]
448 #[must_use]
449 pub fn sub_end(&self, amount: TextSize) -> TextRange {
450 TextRange::new(self.start(), self.end() - amount)
451 }
452
453 /// Adds an offset to the end position.
454 ///
455 ///
456 /// ## Panics
457 /// If `end + amount > u32::MAX`
458 ///
459 /// ## Examples
460 ///
461 /// ```
462 /// use ruff_text_size::{Ranged, TextRange, TextSize};
463 ///
464 /// let range = TextRange::new(TextSize::from(5), TextSize::from(10));
465 /// assert_eq!(range.add_end(TextSize::from(2)), TextRange::new(TextSize::from(5), TextSize::from(12)));
466 /// ```
467 #[inline]
468 #[must_use]
469 pub fn add_end(&self, amount: TextSize) -> TextRange {
470 TextRange::new(self.start(), self.end() + amount)
471 }
472}
473
474/// Conversion methods.
475impl TextRange {
476 /// A convenience routine for converting this range to a
477 /// standard library range that satisfies the `RangeBounds`
478 /// trait.
479 ///
480 /// This is also available as a `From` trait implementation,
481 /// but this method avoids the need to specify types to help
482 /// inference.
483 #[inline]
484 #[must_use]
485 pub fn to_std_range(&self) -> Range<usize> {
486 (*self).into()
487 }
488}
489
490impl Index<TextRange> for str {
491 type Output = str;
492 #[inline]
493 fn index(&self, index: TextRange) -> &str {
494 &self[Range::<usize>::from(index)]
495 }
496}
497
498impl Index<TextRange> for String {
499 type Output = str;
500 #[inline]
501 fn index(&self, index: TextRange) -> &str {
502 &self[Range::<usize>::from(index)]
503 }
504}
505
506impl IndexMut<TextRange> for str {
507 #[inline]
508 fn index_mut(&mut self, index: TextRange) -> &mut str {
509 &mut self[Range::<usize>::from(index)]
510 }
511}
512
513impl IndexMut<TextRange> for String {
514 #[inline]
515 fn index_mut(&mut self, index: TextRange) -> &mut str {
516 &mut self[Range::<usize>::from(index)]
517 }
518}
519
520impl RangeBounds<TextSize> for TextRange {
521 fn start_bound(&self) -> Bound<&TextSize> {
522 Bound::Included(&self.start)
523 }
524
525 fn end_bound(&self) -> Bound<&TextSize> {
526 Bound::Excluded(&self.end)
527 }
528}
529
530impl From<Range<TextSize>> for TextRange {
531 #[inline]
532 fn from(r: Range<TextSize>) -> Self {
533 TextRange::new(r.start, r.end)
534 }
535}
536
537impl<T> From<TextRange> for Range<T>
538where
539 T: From<TextSize>,
540{
541 #[inline]
542 fn from(r: TextRange) -> Self {
543 r.start().into()..r.end().into()
544 }
545}
546
547macro_rules! ops {
548 (impl $Op:ident for TextRange by fn $f:ident = $op:tt) => {
549 impl $Op<&TextSize> for TextRange {
550 type Output = TextRange;
551 #[inline]
552 fn $f(self, other: &TextSize) -> TextRange {
553 self $op *other
554 }
555 }
556 impl<T> $Op<T> for &TextRange
557 where
558 TextRange: $Op<T, Output=TextRange>,
559 {
560 type Output = TextRange;
561 #[inline]
562 fn $f(self, other: T) -> TextRange {
563 *self $op other
564 }
565 }
566 };
567}
568
569impl Add<TextSize> for TextRange {
570 type Output = TextRange;
571 #[inline]
572 fn add(self, offset: TextSize) -> TextRange {
573 self.checked_add(offset)
574 .expect("TextRange +offset overflowed")
575 }
576}
577
578impl Sub<TextSize> for TextRange {
579 type Output = TextRange;
580 #[inline]
581 fn sub(self, offset: TextSize) -> TextRange {
582 self.checked_sub(offset)
583 .expect("TextRange -offset overflowed")
584 }
585}
586
587ops!(impl Add for TextRange by fn add = +);
588ops!(impl Sub for TextRange by fn sub = -);
589
590impl<A> AddAssign<A> for TextRange
591where
592 TextRange: Add<A, Output = TextRange>,
593{
594 #[inline]
595 fn add_assign(&mut self, rhs: A) {
596 *self = *self + rhs;
597 }
598}
599
600impl<S> SubAssign<S> for TextRange
601where
602 TextRange: Sub<S, Output = TextRange>,
603{
604 #[inline]
605 fn sub_assign(&mut self, rhs: S) {
606 *self = *self - rhs;
607 }
608}