1 | use {
|
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)]
|
25 | pub struct TextSize {
|
26 | pub(crate) raw: u32,
|
27 | }
|
28 |
|
29 | impl fmt::Debug for TextSize {
|
30 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
31 | write!(f, " {}" , self.raw)
|
32 | }
|
33 | }
|
34 |
|
35 | impl 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.
|
64 | impl 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 |
|
84 | impl From<u32> for TextSize {
|
85 | #[inline ]
|
86 | fn from(raw: u32) -> Self {
|
87 | TextSize { raw }
|
88 | }
|
89 | }
|
90 |
|
91 | impl From<TextSize> for u32 {
|
92 | #[inline ]
|
93 | fn from(value: TextSize) -> Self {
|
94 | value.raw
|
95 | }
|
96 | }
|
97 |
|
98 | impl 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 |
|
106 | impl From<TextSize> for usize {
|
107 | #[inline ]
|
108 | fn from(value: TextSize) -> Self {
|
109 | value.raw as usize
|
110 | }
|
111 | }
|
112 |
|
113 | macro_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 |
|
142 | ops!(impl Add for TextSize by fn add = +);
|
143 | ops!(impl Sub for TextSize by fn sub = -);
|
144 |
|
145 | impl<A> AddAssign<A> for TextSize
|
146 | where
|
147 | TextSize: Add<A, Output = TextSize>,
|
148 | {
|
149 | #[inline ]
|
150 | fn add_assign(&mut self, rhs: A) {
|
151 | *self = *self + rhs
|
152 | }
|
153 | }
|
154 |
|
155 | impl<S> SubAssign<S> for TextSize
|
156 | where
|
157 | TextSize: Sub<S, Output = TextSize>,
|
158 | {
|
159 | #[inline ]
|
160 | fn sub_assign(&mut self, rhs: S) {
|
161 | *self = *self - rhs
|
162 | }
|
163 | }
|
164 |
|
165 | impl<A> iter::Sum<A> for TextSize
|
166 | where
|
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 | |