Unnamed repository; edit this file 'description' to name the repository.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
use {
    crate::{TextSize, TextSized},
    std::{
        cmp,
        convert::{TryFrom, TryInto},
        fmt,
        num::TryFromIntError,
        ops::{
            Add, AddAssign, Bound, Index, IndexMut, Range, RangeBounds, RangeInclusive, RangeTo,
            RangeToInclusive, Sub, SubAssign,
        },
    },
};

/// A range in text, represented as a pair of [`TextSize`][struct@TextSize].
///
/// It is a logical error to have `end() < start()`, but
/// code must not assume this is true for `unsafe` guarantees.
///
/// # Translation from `text_unit`
///
/// - `TextRange::from_to(from, to)` ⟹ `TextRange::from(from..to)`
/// - `TextRange::offset_len(offset, size)` ⟹ `TextRange::at(offset).with_len(size)`
/// - `range.start()` ⟹ `range.start()`
/// - `range.end()` ⟹ `range.end()`
/// - `range.len()` ⟹ `range.len()`<sup>†</sup>
/// - `range.is_empty()` ⟹ `range.is_empty()`
/// - `a.is_subrange(b)` ⟹ `b.contains(a)`
/// - `a.intersection(b)` ⟹ `TextRange::intersection(a, b)`
/// - `a.extend_to(b)` ⟹ `TextRange::covering(a, b)`
/// - `range.contains(offset)` ⟹ `range.contains_point(point)`
/// - `range.contains_inclusive(offset)` ⟹ `range.contains_point_inclusive(point)`
///
/// † See the note on [`TextRange::len`] for differing behavior for incorrect reverse ranges.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct TextRange {
    start: TextSize,
    end: TextSize,
}

#[allow(non_snake_case)]
pub(crate) const fn TextRange(start: TextSize, end: TextSize) -> TextRange {
    TextRange { start, end }
}

impl fmt::Debug for TextRange {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(self, f)
    }
}

impl fmt::Display for TextRange {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "[{}..{})", self.start(), self.end())
    }
}

/// Identity methods.
impl TextRange {
    /// The start point of this range.
    pub const fn start(self) -> TextSize {
        self.start
    }

    /// The end point of this range.
    pub const fn end(self) -> TextSize {
        self.end
    }

    /// The size of this range.
    ///
    /// # Panics
    ///
    /// When `end() < start()`, triggers a subtraction overflow.
    /// This will panic with debug assertions, and overflow without.
    pub const fn len(self) -> TextSize {
        // HACK for const fn: math on primitives only
        TextSize(self.end().raw - self.start().raw)
    }

    /// Check if this range empty or reversed.
    ///
    /// When `end() < start()`, this returns false.
    /// Code should prefer `is_empty()` to `len() == 0`,
    /// as this safeguards against incorrect reverse ranges.
    pub const fn is_empty(self) -> bool {
        // HACK for const fn: math on primitives only
        self.start().raw >= self.end().raw
    }
}

/// Manipulation methods.
impl TextRange {
    /// A range covering the text size of some text-like object.
    pub fn of(size: impl TextSized) -> TextRange {
        TextRange(0.into(), size.text_size())
    }

    /// An empty range at some text size offset.
    pub fn at(size: impl Into<TextSize>) -> TextRange {
        let size = size.into();
        TextRange(size, size)
    }

    /// Set the length without changing the starting offset.
    pub fn with_len(self, len: impl Into<TextSize>) -> TextRange {
        TextRange(self.start(), self.start() + len.into())
    }

    /// Set the starting offset without changing the length.
    pub fn with_offset(self, offset: impl Into<TextSize>) -> TextRange {
        TextRange::at(offset).with_len(self.len())
    }

    /// Check if this range completely contains another range.
    pub fn contains(self, other: TextRange) -> bool {
        self.start() <= other.start() && other.end() <= self.end()
    }

    /// The range covered by both ranges, if it exists.
    /// If the ranges touch but do not overlap, the output range is empty.
    pub fn intersection(lhs: TextRange, rhs: TextRange) -> Option<TextRange> {
        let start = cmp::max(lhs.start(), rhs.start());
        let end = cmp::min(lhs.end(), rhs.end());
        Some(TextRange(start, end)).filter(|_| start <= end)
    }

    /// The smallest range that completely contains both ranges.
    pub fn covering(lhs: TextRange, rhs: TextRange) -> TextRange {
        let start = cmp::min(lhs.start(), rhs.start());
        let end = cmp::max(lhs.end(), rhs.end());
        TextRange(start, end)
    }

    /// Check if this range contains a point.
    ///
    /// The end index is considered excluded.
    pub fn contains_point(self, point: impl Into<TextSize>) -> bool {
        let point = point.into();
        self.start() <= point && point < self.end()
    }

    /// Check if this range contains a point.
    ///
    /// The end index is considered included.
    pub fn contains_point_inclusive(self, point: impl Into<TextSize>) -> bool {
        let point = point.into();
        self.start() <= point && point <= self.end()
    }

    /// Offset the entire range by some text size.
    pub fn checked_add(self, rhs: impl TryInto<TextSize>) -> Option<TextRange> {
        let rhs = rhs.try_into().ok()?;
        Some(TextRange(
            self.start().checked_add(rhs)?,
            self.end().checked_add(rhs)?,
        ))
    }

    /// Offset the entire range by some text size.
    pub fn checked_sub(self, rhs: impl TryInto<TextSize>) -> Option<TextRange> {
        let rhs = rhs.try_into().ok()?;
        Some(TextRange(
            self.start().checked_sub(rhs)?,
            self.end().checked_sub(rhs)?,
        ))
    }
}

impl Index<TextRange> for str {
    type Output = str;
    fn index(&self, index: TextRange) -> &Self::Output {
        &self[index.start().ix()..index.end().ix()]
    }
}

impl IndexMut<TextRange> for str {
    fn index_mut(&mut self, index: TextRange) -> &mut Self::Output {
        &mut self[index.start().ix()..index.end().ix()]
    }
}

impl RangeBounds<TextSize> for TextRange {
    fn start_bound(&self) -> Bound<&TextSize> {
        Bound::Included(&self.start)
    }

    fn end_bound(&self) -> Bound<&TextSize> {
        Bound::Excluded(&self.end)
    }
}

macro_rules! conversions {
    (From<$lte:ident> for TextRange) => {
        impl From<Range<$lte>> for TextRange {
            fn from(value: Range<$lte>) -> TextRange {
                TextRange(value.start.into(), value.end.into())
            }
        }
        impl TryFrom<RangeInclusive<$lte>> for TextRange {
            type Error = TryFromIntError;
            fn try_from(value: RangeInclusive<$lte>) -> Result<TextRange, Self::Error> {
                let (start, end) = value.into_inner();
                let end: TextSize = end.into();
                // This is the only way to get a TryFromIntError currently.
                let end = end.checked_add(1).ok_or_else(|| u8::try_from(-1).unwrap_err())?;
                Ok(TextRange(start.into(), end))
            }
        }
        impl From<RangeTo<$lte>> for TextRange {
            fn from(value: RangeTo<$lte>) -> TextRange {
                TextRange(0.into(), value.end.into())
            }
        }
        impl TryFrom<RangeToInclusive<$lte>> for TextRange {
            type Error = TryFromIntError;
            fn try_from(value: RangeToInclusive<$lte>) -> Result<TextRange, Self::Error> {
                let start: TextSize = 0.into();
                let end: TextSize = value.end.into();
                TextRange::try_from(start..=end)
            }
        }
    };
    (TryFrom<$gt:ident> for TextRange) => {
        impl TryFrom<Range<$gt>> for TextRange {
            type Error = <$gt as TryInto<u32>>::Error;
            fn try_from(value: Range<$gt>) -> Result<TextRange, Self::Error> {
                Ok(TextRange(value.start.try_into()?, value.end.try_into()?))
            }
        }
        impl TryFrom<RangeInclusive<$gt>> for TextRange {
            type Error = TryFromIntError;
            fn try_from(value: RangeInclusive<$gt>) -> Result<TextRange, Self::Error> {
                let (start, end) = value.into_inner();
                let end: TextSize = end.try_into()?;
                // This is the only way to get a TryFromIntError currently.
                let end = end.checked_add(1).ok_or_else(|| u8::try_from(-1).unwrap_err())?;
                Ok(TextRange(start.try_into()?, end))
            }
        }
        impl TryFrom<RangeTo<$gt>> for TextRange {
            type Error = TryFromIntError;
            fn try_from(value: RangeTo<$gt>) -> Result<TextRange, Self::Error> {
                Ok(TextRange(0.into(), value.end.try_into()?))
            }
        }
        impl TryFrom<RangeToInclusive<$gt>> for TextRange {
            type Error = TryFromIntError;
            fn try_from(value: RangeToInclusive<$gt>) -> Result<TextRange, Self::Error> {
                let start: TextSize = 0.into();
                let end: TextSize = value.end.try_into()?;
                TextRange::try_from(start..=end)
            }
        }
    };
    {
        lt TextSize [$($lt:ident)*]
        eq TextSize [$($eq:ident)*]
        gt TextSize [$($gt:ident)*]
        varries     [$($var:ident)*]
    } => {
        $(
            // Not `From` yet because of integer type fallback. We want e.g.
            // `TextRange::from(0)` and `range + 1` to work, and more `From`
            // impls means that this will try (and fail) to use i32 rather
            // than one of the unsigned integer types that actually work.
            conversions!(TryFrom<$lt> for TextRange);
        )*

        $(
            conversions!(From<$eq> for TextRange);
        )*

        $(
            conversions!(TryFrom<$gt> for TextRange);
        )*

        $(
            conversions!(TryFrom<$var> for TextRange);
        )*
    };
}

// FIXME: when `default impl` is usable, change to blanket impls for [Try]Into<TextSize> instead
conversions! {
    lt TextSize [u8 u16]
    eq TextSize [u32 TextSize]
    gt TextSize [u64]
    varries     [usize]
}

impl Into<TextRange> for &'_ TextRange {
    fn into(self) -> TextRange {
        *self
    }
}

impl Into<TextRange> for &'_ mut TextRange {
    fn into(self) -> TextRange {
        *self
    }
}

macro_rules! op {
    (impl $Op:ident for TextRange by fn $f:ident = $op:tt) => {
        impl<IntoSize: Copy> $Op<IntoSize> for TextRange
        where
            TextSize: $Op<IntoSize, Output = TextSize>,
        {
            type Output = TextRange;
            fn $f(self, rhs: IntoSize) -> TextRange {
                TextRange(self.start() $op rhs, self.end() $op rhs)
            }
        }
        impl<IntoSize> $Op<IntoSize> for &'_ TextRange
        where
            TextRange: $Op<IntoSize, Output = TextRange>,
        {
            type Output = TextRange;
            fn $f(self, rhs: IntoSize) -> TextRange {
                *self $op rhs
            }
        }
        impl<IntoSize> $Op<IntoSize> for &'_ mut TextRange
        where
            TextRange: $Op<IntoSize, Output = TextRange>,
        {
            type Output = TextRange;
            fn $f(self, rhs: IntoSize) -> TextRange {
                *self $op rhs
            }
        }
    };
}

op!(impl Add for TextRange by fn add = +);
op!(impl Sub for TextRange by fn sub = -);

impl<A> AddAssign<A> for TextRange
where
    TextRange: Add<A, Output = TextRange>,
{
    fn add_assign(&mut self, rhs: A) {
        *self = *self + rhs
    }
}

impl<S> SubAssign<S> for TextRange
where
    TextRange: Sub<S, Output = TextRange>,
{
    fn sub_assign(&mut self, rhs: S) {
        *self = *self - rhs
    }
}