duat_core/text/
ops.rs

1//! Convenience operations for the [`Text`]
2//!
3//! These include the [`Point`] struct and traits that are meant to
4//! take many kinds of inputs, like the [`TwoPoints`], which is meant
5//! to interpret up to 2 [`Point`]s as a real and ghost position in
6//! the [`Text`].
7//!
8//! [`Text`]: super::Text
9use std::ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive};
10
11use bincode::{Decode, Encode};
12
13use super::Item;
14
15/// A position in [`Text`]
16///
17/// [`Text`]: super::Text
18#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Decode)]
19pub struct Point {
20    b: u32,
21    c: u32,
22    l: u32,
23}
24
25impl Point {
26    ////////// Creation of a Point
27
28    /// Returns a new [`Point`], at the first byte
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    /// Internal function to create [`Point`]s
34    pub(super) fn from_raw(b: usize, c: usize, l: usize) -> Self {
35        let (b, c, l) = (b as u32, c as u32, l as u32);
36        Self { b, c, l }
37    }
38
39    ////////// Querying functions
40
41    /// The len [`Point`] of a [`&str`]
42    ///
43    /// This is the equivalent of [`Text::len`], but for types
44    /// other than [`Text`]
45    ///
46    /// [`&str`]: str
47    /// [`Text::len`]: super::Text::len
48    /// [`Text`]: super::Text
49    pub fn len_of(str: impl AsRef<str>) -> Self {
50        let str = str.as_ref();
51        Self {
52            b: str.len() as u32,
53            c: str.chars().count() as u32,
54            l: str.bytes().filter(|c| *c == b'\n').count() as u32,
55        }
56    }
57
58    /// Returns the byte (relative to the beginning of the file)
59    /// of self. Indexed at 0
60    pub fn byte(&self) -> usize {
61        self.b as usize
62    }
63
64    /// Returns the char index (relative to the beginning of the
65    /// file). Indexed at 0
66    pub fn char(&self) -> usize {
67        self.c as usize
68    }
69
70    /// Returns the line. Indexed at 0
71    pub fn line(&self) -> usize {
72        self.l as usize
73    }
74
75    /// Checked [`Point`] subtraction
76    pub fn checked_sub(self, rhs: Point) -> Option<Point> {
77        Some(Self {
78            b: self.b.checked_sub(rhs.b)?,
79            c: self.c.checked_sub(rhs.c)?,
80            l: self.l.checked_sub(rhs.l)?,
81        })
82    }
83
84    ////////// Shifting functions
85
86    /// Moves a [`Point`] forward by one character
87    #[inline(always)]
88    pub(crate) fn fwd(self, char: char) -> Self {
89        Self {
90            b: self.b + char.len_utf8() as u32,
91            c: self.c + 1,
92            l: self.l + (char == '\n') as u32,
93        }
94    }
95
96    /// Moves a [`Point`] in reverse by one character
97    #[inline(always)]
98    pub(crate) fn rev(self, char: char) -> Self {
99        Self {
100            b: self.b - char.len_utf8() as u32,
101            c: self.c - 1,
102            l: self.l - (char == '\n') as u32,
103        }
104    }
105
106    /// Shifts the [`Point`] by a "signed point"
107    ///
108    /// This assumes that no overflow is going to happen
109    pub(crate) fn shift_by(self, [b, c, l]: [i32; 3]) -> Self {
110        Self {
111            b: (self.b as i32 + b) as u32,
112            c: (self.c as i32 + c) as u32,
113            l: (self.l as i32 + l) as u32,
114        }
115    }
116}
117
118impl std::fmt::Debug for Point {
119    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120        write!(f, "Point {{ b: {}, c: {}, l: {} }}", self.b, self.c, self.l)
121    }
122}
123
124impl std::fmt::Display for Point {
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        write!(f, "{}, {}, {}", self.b, self.c, self.l)
127    }
128}
129
130impl std::ops::Add for Point {
131    type Output = Self;
132
133    fn add(self, rhs: Self) -> Self::Output {
134        Self {
135            b: self.b + rhs.b,
136            c: self.c + rhs.c,
137            l: self.l + rhs.l,
138        }
139    }
140}
141
142impl std::ops::AddAssign for Point {
143    fn add_assign(&mut self, rhs: Self) {
144        *self = *self + rhs;
145    }
146}
147
148impl std::ops::Sub for Point {
149    type Output = Self;
150
151    fn sub(self, rhs: Self) -> Self::Output {
152        Self {
153            b: self.b - rhs.b,
154            c: self.c - rhs.c,
155            l: self.l - rhs.l,
156        }
157    }
158}
159
160impl std::ops::SubAssign for Point {
161    fn sub_assign(&mut self, rhs: Self) {
162        *self = *self - rhs;
163    }
164}
165
166/// Given a first byte, determines how many bytes are in this
167/// UTF-8 character
168#[inline]
169pub const fn utf8_char_width(b: u8) -> u32 {
170    // https://tools.ietf.org/html/rfc3629
171    const UTF8_CHAR_WIDTH: &[u8; 256] = &[
172        // 1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
173        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
174        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
175        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
176        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3
177        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
178        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
179        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
180        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
181        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
182        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
183        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
184        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
185        0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C
186        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D
187        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E
188        4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F
189    ];
190
191    UTF8_CHAR_WIDTH[b as usize] as u32
192}
193
194/// Ranges that can be used to index the [`Text`]
195///
196/// All of the [ranges] in [`std`] that implement either
197/// [`RangeBounds<usize>`] or [`RangeBounds<Point>`] should work as an
198/// argument. If it implements [`RangeBounds<usize>`], then the
199/// `usize` represents the a byte index in the [`Text`].
200///
201/// [`Text`]: super::Text
202/// [ranges]: std::range
203/// [`RangeBounds<usize>`]: std::ops::RangeBounds
204/// [`RangeBounds<Point>`]: std::ops::RangeBounds
205pub trait TextRange: Clone {
206    /// A "forward facing range"
207    ///
208    /// If given a single [`usize`]/[`Point`], acts like [`RangeFrom`]
209    fn to_range(self, max: usize) -> Range<usize>;
210}
211
212implTextRange!(Range, r, r.start, r.end, r.start.byte(), r.end.byte());
213implTextRange!(
214    RangeInclusive,
215    r,
216    *r.start(),
217    r.end() + 1,
218    r.start().byte(),
219    r.end().byte() + 1
220);
221implTextRange!(RangeTo, r, 0, r.end, 0, r.end.byte());
222implTextRange!(RangeToInclusive, r, 0, r.end, 0, r.end.byte());
223implTextRange!(RangeFrom, r, r.start, MAX, r.start.byte(), MAX);
224
225impl TextRange for RangeFull {
226    fn to_range(self, max: usize) -> Range<usize> {
227        0..max
228    }
229}
230
231/// Either a [`TextRange`], a [`usize`] or a [`Point`]
232///
233/// This trait's purpose is to be used for [`Tag`] removal in the
234/// [`MutTags::remove`] and [`Text::remove_tags`] functions. This is
235/// useful in order to reduce the number of functions exposed to API
236/// users.
237///
238/// [`Tag`]: super::Tag
239/// [`MutTags::remove`]: super::MutTags::remove
240/// [`Text::remove_tags`]: super::Text::remove_tags
241pub trait TextRangeOrPoint {
242    /// Transforms `self` into a [`Range<usize>`]
243    fn to_range(self, max: usize) -> Range<usize>;
244}
245
246impl TextRangeOrPoint for usize {
247    fn to_range(self, max: usize) -> Range<usize> {
248        max.min(self)..max.min(self + 1)
249    }
250}
251
252impl TextRangeOrPoint for Point {
253    fn to_range(self, max: usize) -> Range<usize> {
254        max.min(self.byte())..max.min(self.byte() + 1)
255    }
256}
257
258impl TextRangeOrPoint for RangeFull {
259    fn to_range(self, max: usize) -> Range<usize> {
260        TextRange::to_range(self, max)
261    }
262}
263
264implTextRangeOrPoint!(Range);
265implTextRangeOrPoint!(RangeInclusive);
266implTextRangeOrPoint!(RangeTo);
267implTextRangeOrPoint!(RangeToInclusive);
268implTextRangeOrPoint!(RangeFrom);
269
270/// Two positions, one for the [`Text`], and one for [ghost text]
271///
272/// This can either be a [`Point`] or `(Point, Option<Point>)` or
273/// even `(Point, Point)`. If a second [`Point`] is excluded, it
274/// is assumed to be [`Point::default()`], i.e., this
275/// [`TwoPoints`] represents the beginning of a [ghost text].
276///
277/// [`Text`]: super::Text
278/// [ghost text]: super::Ghost
279pub trait TwoPoints: Clone + Copy {
280    /// Returns two [`Point`]s, for `Text` and ghosts
281    fn to_points(self) -> (Point, Option<Point>);
282}
283
284impl TwoPoints for Point {
285    fn to_points(self) -> (Point, Option<Point>) {
286        (self, None)
287    }
288}
289
290impl TwoPoints for (Point, Point) {
291    fn to_points(self) -> (Point, Option<Point>) {
292        (self.0, Some(self.1))
293    }
294}
295
296impl TwoPoints for (Point, Option<Point>) {
297    fn to_points(self) -> (Point, Option<Point>) {
298        self
299    }
300}
301
302impl TwoPoints for Item {
303    fn to_points(self) -> (Point, Option<Point>) {
304        (self.real, self.ghost)
305    }
306}
307
308const MAX: usize = usize::MAX;
309
310macro implTextRange($range:ident, $r:ident, $sb:expr, $eb:expr, $sp:expr, $ep:expr) {
311    impl TextRange for $range<usize> {
312        fn to_range(self, max: usize) -> Range<usize> {
313            let $r = self;
314            max.min($sb)..max.min($eb)
315        }
316    }
317
318    impl TextRange for $range<Point> {
319        fn to_range(self, max: usize) -> Range<usize> {
320            let $r = self;
321            max.min($sp)..max.min($ep)
322        }
323    }
324}
325
326macro implTextRangeOrPoint($range:ident) {
327    impl TextRangeOrPoint for $range<usize> {
328        fn to_range(self, max: usize) -> Range<usize> {
329            TextRange::to_range(self, max)
330        }
331    }
332
333    impl TextRangeOrPoint for $range<Point> {
334        fn to_range(self, max: usize) -> Range<usize> {
335            TextRange::to_range(self, max)
336        }
337    }
338}