Skip to main content

rustpython_ruff_text_size/
size.rs

1use {
2    crate::TextLen,
3    std::{
4        convert::TryFrom,
5        fmt, iter,
6        num::TryFromIntError,
7        ops::{Add, AddAssign, Sub, SubAssign},
8    },
9};
10
11/// A measure of text length. Also, equivalently, an index into text.
12///
13/// This is a UTF-8 bytes offset stored as `u32`, but
14/// most clients should treat it as an opaque measure.
15///
16/// For cases that need to escape `TextSize` and return to working directly
17/// with primitive integers, `TextSize` can be converted losslessly to/from
18/// `u32` via [`From`] conversions as well as losslessly be converted [`Into`]
19/// `usize`. The `usize -> TextSize` direction can be done via [`TryFrom`].
20///
21/// These escape hatches are primarily required for unit testing and when
22/// converting from UTF-8 size to another coordinate space, such as UTF-16.
23#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
24#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
25pub struct TextSize {
26    pub(crate) raw: u32,
27}
28
29impl fmt::Debug for TextSize {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        write!(f, "{}", self.raw)
32    }
33}
34
35impl TextSize {
36    /// A `TextSize` of zero.
37    pub const ZERO: TextSize = TextSize::new(0);
38
39    /// Creates a new `TextSize` at the given `offset`.
40    ///
41    /// # Examples
42    ///
43    /// ```rust
44    /// # use ruff_text_size::*;
45    /// assert_eq!(TextSize::from(4), TextSize::new(4));
46    /// ```
47    pub const fn new(offset: u32) -> Self {
48        Self { raw: offset }
49    }
50
51    /// The text size of some primitive text-like object.
52    ///
53    /// Accepts `char`, `&str`, and `&String`.
54    ///
55    /// # Examples
56    ///
57    /// ```rust
58    /// # use ruff_text_size::*;
59    /// let char_size = TextSize::of('🦀');
60    /// assert_eq!(char_size, TextSize::from(4));
61    ///
62    /// let str_size = TextSize::of("rust-analyzer");
63    /// assert_eq!(str_size, TextSize::from(13));
64    /// ```
65    #[inline]
66    pub fn of<T: TextLen>(text: T) -> TextSize {
67        text.text_len()
68    }
69
70    /// Returns current raw `offset` as u32.
71    ///
72    /// # Examples
73    ///
74    /// ```rust
75    /// # use ruff_text_size::*;
76    /// assert_eq!(TextSize::from(4).to_u32(), 4);
77    /// ```
78    pub const fn to_u32(&self) -> u32 {
79        self.raw
80    }
81
82    /// Returns current raw `offset` as usize.
83    ///
84    /// # Examples
85    ///
86    /// ```rust
87    /// # use ruff_text_size::*;
88    /// assert_eq!(TextSize::from(4).to_usize(), 4);
89    /// ```
90    pub const fn to_usize(&self) -> usize {
91        self.raw as usize
92    }
93}
94
95/// Methods to act like a primitive integer type, where reasonably applicable.
96//  Last updated for parity with Rust 1.42.0.
97impl TextSize {
98    /// Checked addition. Returns `None` if overflow occurred.
99    #[inline]
100    pub fn checked_add(self, rhs: TextSize) -> Option<TextSize> {
101        self.raw.checked_add(rhs.raw).map(|raw| TextSize { raw })
102    }
103
104    /// Checked subtraction. Returns `None` if overflow occurred.
105    #[inline]
106    pub fn checked_sub(self, rhs: TextSize) -> Option<TextSize> {
107        self.raw.checked_sub(rhs.raw).map(|raw| TextSize { raw })
108    }
109
110    /// Saturating addition. Returns maximum `TextSize` if overflow occurred.
111    #[inline]
112    #[must_use]
113    pub fn saturating_add(self, rhs: TextSize) -> TextSize {
114        TextSize {
115            raw: self.raw.saturating_add(rhs.raw),
116        }
117    }
118
119    /// Saturating subtraction. Returns minimum `TextSize` if overflow
120    /// occurred.
121    #[inline]
122    #[must_use]
123    pub fn saturating_sub(self, rhs: TextSize) -> TextSize {
124        TextSize {
125            raw: self.raw.saturating_sub(rhs.raw),
126        }
127    }
128}
129
130impl From<u32> for TextSize {
131    #[inline]
132    fn from(raw: u32) -> Self {
133        TextSize::new(raw)
134    }
135}
136
137impl From<TextSize> for u32 {
138    #[inline]
139    fn from(value: TextSize) -> Self {
140        value.to_u32()
141    }
142}
143
144impl TryFrom<usize> for TextSize {
145    type Error = TryFromIntError;
146    #[inline]
147    fn try_from(value: usize) -> Result<Self, TryFromIntError> {
148        Ok(u32::try_from(value)?.into())
149    }
150}
151
152impl From<TextSize> for usize {
153    #[inline]
154    fn from(value: TextSize) -> Self {
155        value.to_usize()
156    }
157}
158
159macro_rules! ops {
160    (impl $Op:ident for TextSize by fn $f:ident = $op:tt) => {
161        impl $Op<TextSize> for TextSize {
162            type Output = TextSize;
163            #[inline]
164            fn $f(self, other: TextSize) -> TextSize {
165                TextSize { raw: self.raw $op other.raw }
166            }
167        }
168        impl $Op<&TextSize> for TextSize {
169            type Output = TextSize;
170            #[inline]
171            fn $f(self, other: &TextSize) -> TextSize {
172                self $op *other
173            }
174        }
175        impl<T> $Op<T> for &TextSize
176        where
177            TextSize: $Op<T, Output=TextSize>,
178        {
179            type Output = TextSize;
180            #[inline]
181            fn $f(self, other: T) -> TextSize {
182                *self $op other
183            }
184        }
185    };
186}
187
188ops!(impl Add for TextSize by fn add = +);
189ops!(impl Sub for TextSize by fn sub = -);
190
191impl<A> AddAssign<A> for TextSize
192where
193    TextSize: Add<A, Output = TextSize>,
194{
195    #[inline]
196    fn add_assign(&mut self, rhs: A) {
197        *self = *self + rhs;
198    }
199}
200
201impl<S> SubAssign<S> for TextSize
202where
203    TextSize: Sub<S, Output = TextSize>,
204{
205    #[inline]
206    fn sub_assign(&mut self, rhs: S) {
207        *self = *self - rhs;
208    }
209}
210
211impl<A> iter::Sum<A> for TextSize
212where
213    TextSize: Add<A, Output = TextSize>,
214{
215    #[inline]
216    fn sum<I: Iterator<Item = A>>(iter: I) -> TextSize {
217        iter.fold(0.into(), Add::add)
218    }
219}