Skip to main content

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}