use { crate::TextLen, std::{ convert::TryFrom, fmt, iter, num::TryFromIntError, ops::{Add, AddAssign, Sub, SubAssign}, u32, }, }; /// A measure of text length. Also, equivalently, an index into text. /// /// This is a UTF-8 bytes offset stored as `u32`, but /// most clients should treat it as an opaque measure. /// /// For cases that need to escape `TextSize` and return to working directly /// with primitive integers, `TextSize` can be converted losslessly to/from /// `u32` via [`From`] conversions as well as losslessly be converted [`Into`] /// `usize`. The `usize -> TextSize` direction can be done via [`TryFrom`]. /// /// These escape hatches are primarily required for unit testing and when /// converting from UTF-8 size to another coordinate space, such as UTF-16. #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct TextSize { pub(crate) raw: u32, } impl fmt::Debug for TextSize { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.raw) } } impl TextSize { /// The text size of some primitive text-like object. /// /// Accepts `char`, `&str`, and `&String`. /// /// # Examples /// /// ```rust /// # use text_size::*; /// let char_size = TextSize::of('🦀'); /// assert_eq!(char_size, TextSize::from(4)); /// /// let str_size = TextSize::of("rust-analyzer"); /// assert_eq!(str_size, TextSize::from(13)); /// ``` #[inline] pub fn of(text: T) -> TextSize { text.text_len() } } /// Methods to act like a primitive integer type, where reasonably applicable. // Last updated for parity with Rust 1.42.0. impl TextSize { /// Checked addition. Returns `None` if overflow occurred. #[inline] pub fn checked_add(self, rhs: TextSize) -> Option { self.raw.checked_add(rhs.raw).map(|raw| TextSize { raw }) } /// Checked subtraction. Returns `None` if overflow occurred. #[inline] pub fn checked_sub(self, rhs: TextSize) -> Option { self.raw.checked_sub(rhs.raw).map(|raw| TextSize { raw }) } } impl From for TextSize { #[inline] fn from(raw: u32) -> Self { TextSize { raw } } } impl From for u32 { #[inline] fn from(value: TextSize) -> Self { value.raw } } impl TryFrom for TextSize { type Error = TryFromIntError; #[inline] fn try_from(value: usize) -> Result { Ok(u32::try_from(value)?.into()) } } impl From for usize { #[inline] fn from(value: TextSize) -> Self { value.raw as usize } } macro_rules! ops { (impl $Op:ident for TextSize by fn $f:ident = $op:tt) => { impl $Op for TextSize { type Output = TextSize; #[inline] fn $f(self, other: TextSize) -> TextSize { TextSize { raw: self.raw $op other.raw } } } impl $Op<&TextSize> for TextSize { type Output = TextSize; #[inline] fn $f(self, other: &TextSize) -> TextSize { self $op *other } } impl $Op for &TextSize where TextSize: $Op, { type Output = TextSize; #[inline] fn $f(self, other: T) -> TextSize { *self $op other } } }; } ops!(impl Add for TextSize by fn add = +); ops!(impl Sub for TextSize by fn sub = -); impl AddAssign for TextSize where TextSize: Add, { #[inline] fn add_assign(&mut self, rhs: A) { *self = *self + rhs } } impl SubAssign for TextSize where TextSize: Sub, { #[inline] fn sub_assign(&mut self, rhs: S) { *self = *self - rhs } } impl iter::Sum for TextSize where TextSize: Add, { #[inline] fn sum>(iter: I) -> TextSize { iter.fold(0.into(), Add::add) } }