Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'lib/text-size/src/size.rs')
| -rw-r--r-- | lib/text-size/src/size.rs | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/lib/text-size/src/size.rs b/lib/text-size/src/size.rs new file mode 100644 index 0000000000..80dba0aba6 --- /dev/null +++ b/lib/text-size/src/size.rs @@ -0,0 +1,246 @@ +use { + crate::TextSized, + std::{ + convert::{TryFrom, TryInto}, + fmt, iter, + num::TryFromIntError, + ops::{Add, AddAssign, Sub, SubAssign}, + u32, + }, +}; + +/// A measure of text length. Also, equivalently, an index into text. +/// +/// This is a utf8-bytes-offset stored as `u32`, but +/// most clients should treat it as an opaque measure. +/// +/// # Translation from `text_unit` +/// +/// - `TextUnit::of_char(c)` ⟹ `TextSize::of(c)` +/// - `TextUnit::of_str(s)` ⟹ `TextSize:of(s)` +/// - `TextUnit::from_usize(size)` ⟹ `TextSize::new(size)` +/// - `unit.to_usize()` ⟹ `size.ix()` +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TextSize { + pub(crate) raw: u32, +} + +#[allow(non_snake_case)] +pub(crate) const fn TextSize(raw: u32) -> TextSize { + TextSize { raw } +} + +impl fmt::Debug for TextSize { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl fmt::Display for TextSize { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.raw, f) + } +} + +impl TextSize { + /// The text size of some text-like object. + pub fn of(text: &impl TextSized) -> TextSize { + text.text_size() + } + + /// A text size for some `usize`. + /// + /// # Panics + /// + /// Panics if the size is greater than `u32::MAX` and debug assertions are + /// enabled. If debug assertions are not enabled, wraps into `u32` space. + pub fn new(size: usize) -> TextSize { + if let Ok(size) = size.try_into() { + size + } else if cfg!(debug_assertions) { + panic!("overflow when converting to TextSize"); + } else { + TextSize(size as u32) + } + } + + /// Convert this text size into the standard indexing type. + /// + /// # Panics + /// + /// Panics if the size is greater than `usize::MAX`. This can only + /// occur on targets where `size_of::<usize>() < size_of::<u32>()`. + pub fn ix(self) -> usize { + if let Ok(ix) = self.try_into() { + ix + } else { + panic!("overflow when converting TextSize to usize index") + } + } +} + +/// Methods to act like a primitive integer type, where reasonably applicable. +// Last updated for parity with Rust 1.42.0. +impl TextSize { + /// The smallest representable text size. (`u32::MIN`) + pub const MIN: TextSize = TextSize(u32::MIN); + /// The largest representable text size. (`u32::MAX`) + pub const MAX: TextSize = TextSize(u32::MAX); + + #[allow(missing_docs)] + pub fn checked_add(self, rhs: impl TryInto<TextSize>) -> Option<TextSize> { + let rhs = rhs.try_into().ok()?; + self.raw.checked_add(rhs.raw).map(TextSize) + } + + #[allow(missing_docs)] + pub fn checked_sub(self, rhs: impl TryInto<TextSize>) -> Option<TextSize> { + let rhs = rhs.try_into().ok()?; + self.raw.checked_sub(rhs.raw).map(TextSize) + } +} + +macro_rules! conversions { + (From<TextSize> for $gte:ident) => { + impl From<TextSize> for $gte { + fn from(value: TextSize) -> $gte { + value.raw.into() + } + } + }; + (From<$lte:ident> for TextSize) => { + impl From<$lte> for TextSize { + fn from(value: $lte) -> TextSize { + TextSize(value.into()) + } + } + }; + (TryFrom<TextSize> for $lt:ident) => { + impl TryFrom<TextSize> for $lt { + type Error = TryFromIntError; + fn try_from(value: TextSize) -> Result<$lt, Self::Error> { + value.raw.try_into() + } + } + }; + (TryFrom<$gt:ident> for TextSize) => { + impl TryFrom<$gt> for TextSize { + type Error = <$gt as TryInto<u32>>::Error; + fn try_from(value: $gt) -> Result<TextSize, Self::Error> { + value.try_into().map(TextSize) + } + } + }; + { + lt u32 [$($lt:ident)*] + eq u32 [$($eq:ident)*] + gt u32 [$($gt:ident)*] + varries [$($var:ident)*] + } => { + $( + // Not `From` yet because of integer type fallback. We want e.g. + // `TextSize::from(0)` and `size + 1` to work, and more `From` + // impls means that this will try (and fail) to use i32 rather + // than one of the unsigned integer types that actually work. + conversions!(TryFrom<$lt> for TextSize); + conversions!(TryFrom<TextSize> for $lt); + )* + + $( + conversions!(From<$eq> for TextSize); + conversions!(From<TextSize> for $eq); + )* + + $( + conversions!(TryFrom<$gt> for TextSize); + conversions!(From<TextSize> for $gt); + )* + + $( + conversions!(TryFrom<$var> for TextSize); + conversions!(TryFrom<TextSize> for $var); + )* + }; +} + +conversions! { + lt u32 [u8 u16] + eq u32 [u32] + gt u32 [u64] + varries [usize i32] // i32 so that `checked_add($lit)` (`try_from($lit)`) can work + // this will unfortunately have to hang around even if integer literal type fallback improves +} + +impl Into<TextSize> for &'_ TextSize { + fn into(self) -> TextSize { + *self + } +} + +impl Into<TextSize> for &'_ mut TextSize { + fn into(self) -> TextSize { + *self + } +} + +macro_rules! op { + (impl $Op:ident for TextSize by fn $f:ident = $op:tt) => { + impl<IntoSize: Into<TextSize>> $Op<IntoSize> for TextSize { + type Output = TextSize; + fn $f(self, rhs: IntoSize) -> TextSize { + TextSize(self.raw $op rhs.into().raw) + } + } + impl<IntoSize> $Op<IntoSize> for &'_ TextSize + where + TextSize: $Op<IntoSize, Output = TextSize>, + { + type Output = TextSize; + fn $f(self, rhs: IntoSize) -> TextSize { + *self $op rhs + } + } + impl<IntoSize> $Op<IntoSize> for &'_ mut TextSize + where + TextSize: $Op<IntoSize, Output = TextSize>, + { + type Output = TextSize; + fn $f(self, rhs: IntoSize) -> TextSize { + *self $op rhs + } + } + }; +} + +op!(impl Add for TextSize by fn add = +); +op!(impl Sub for TextSize by fn sub = -); + +impl<A> AddAssign<A> for TextSize +where + TextSize: Add<A, Output = TextSize>, +{ + fn add_assign(&mut self, rhs: A) { + *self = *self + rhs + } +} + +impl<S> SubAssign<S> for TextSize +where + TextSize: Sub<S, Output = TextSize>, +{ + fn sub_assign(&mut self, rhs: S) { + *self = *self - rhs + } +} + +impl iter::Sum for TextSize { + fn sum<I: Iterator<Item = TextSize>>(iter: I) -> TextSize { + iter.fold(TextSize::default(), Add::add) + } +} + +impl<'a> iter::Sum<&'a Self> for TextSize { + fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self { + iter.fold(TextSize::default(), Add::add) + } +} |