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: u32) => 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: u32) => 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(init:0.into(), f:Add::add)
172 }
173}
174