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        u32,
9    },
10};
11
12/// A measure of text length. Also, equivalently, an index into text.
13///
14/// This is a UTF-8 bytes offset stored as `u32`, but
15/// most clients should treat it as an opaque measure.
16///
17/// For cases that need to escape `TextSize` and return to working directly
18/// with primitive integers, `TextSize` can be converted losslessly to/from
19/// `u32` via [`From`] conversions as well as losslessly be converted [`Into`]
20/// `usize`. The `usize -> TextSize` direction can be done via [`TryFrom`].
21///
22/// These escape hatches are primarily required for unit testing and when
23/// converting from UTF-8 size to another coordinate space, such as UTF-16.
24#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
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    /// Creates a new instance of `TextSize` from a raw `u32`.
37    #[inline]
38    pub const fn new(raw: u32) -> TextSize {
39        TextSize { raw }
40    }
41
42    /// The text size of some primitive text-like object.
43    ///
44    /// Accepts `char`, `&str`, and `&String`.
45    ///
46    /// # Examples
47    ///
48    /// ```rust
49    /// # use text_size::*;
50    /// let char_size = TextSize::of('🦀');
51    /// assert_eq!(char_size, TextSize::from(4));
52    ///
53    /// let str_size = TextSize::of("rust-analyzer");
54    /// assert_eq!(str_size, TextSize::from(13));
55    /// ```
56    #[inline]
57    pub fn of<T: TextLen>(text: T) -> TextSize {
58        text.text_len()
59    }
60}
61
62/// Methods to act like a primitive integer type, where reasonably applicable.
63//  Last updated for parity with Rust 1.42.0.
64impl TextSize {
65    /// Checked addition. Returns `None` if overflow occurred.
66    #[inline]
67    pub const fn checked_add(self, rhs: TextSize) -> Option<TextSize> {
68        match self.raw.checked_add(rhs.raw) {
69            Some(raw) => Some(TextSize { raw }),
70            None => None,
71        }
72    }
73
74    /// Checked subtraction. Returns `None` if overflow occurred.
75    #[inline]
76    pub const fn checked_sub(self, rhs: TextSize) -> Option<TextSize> {
77        match self.raw.checked_sub(rhs.raw) {
78            Some(raw) => Some(TextSize { raw }),
79            None => None,
80        }
81    }
82}
83
84impl From<u32> for TextSize {
85    #[inline]
86    fn from(raw: u32) -> Self {
87        TextSize { raw }
88    }
89}
90
91impl From<TextSize> for u32 {
92    #[inline]
93    fn from(value: TextSize) -> Self {
94        value.raw
95    }
96}
97
98impl TryFrom<usize> for TextSize {
99    type Error = TryFromIntError;
100    #[inline]
101    fn try_from(value: usize) -> Result<Self, TryFromIntError> {
102        Ok(u32::try_from(value)?.into())
103    }
104}
105
106impl From<TextSize> for usize {
107    #[inline]
108    fn from(value: TextSize) -> Self {
109        value.raw as usize
110    }
111}
112
113macro_rules! ops {
114    (impl $Op:ident for TextSize by fn $f:ident = $op:tt) => {
115        impl $Op<TextSize> for TextSize {
116            type Output = TextSize;
117            #[inline]
118            fn $f(self, other: TextSize) -> TextSize {
119                TextSize { raw: self.raw $op other.raw }
120            }
121        }
122        impl $Op<&TextSize> for TextSize {
123            type Output = TextSize;
124            #[inline]
125            fn $f(self, other: &TextSize) -> TextSize {
126                self $op *other
127            }
128        }
129        impl<T> $Op<T> for &TextSize
130        where
131            TextSize: $Op<T, Output=TextSize>,
132        {
133            type Output = TextSize;
134            #[inline]
135            fn $f(self, other: T) -> TextSize {
136                *self $op other
137            }
138        }
139    };
140}
141
142ops!(impl Add for TextSize by fn add = +);
143ops!(impl Sub for TextSize by fn sub = -);
144
145impl<A> AddAssign<A> for TextSize
146where
147    TextSize: Add<A, Output = TextSize>,
148{
149    #[inline]
150    fn add_assign(&mut self, rhs: A) {
151        *self = *self + rhs
152    }
153}
154
155impl<S> SubAssign<S> for TextSize
156where
157    TextSize: Sub<S, Output = TextSize>,
158{
159    #[inline]
160    fn sub_assign(&mut self, rhs: S) {
161        *self = *self - rhs
162    }
163}
164
165impl<A> iter::Sum<A> for TextSize
166where
167    TextSize: Add<A, Output = TextSize>,
168{
169    #[inline]
170    fn sum<I: Iterator<Item = A>>(iter: I) -> TextSize {
171        iter.fold(0.into(), Add::add)
172    }
173}