Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--lib/text-size/src/lib.rs3
-rw-r--r--lib/text-size/src/range.rs51
-rw-r--r--lib/text-size/src/size.rs41
-rw-r--r--lib/text-size/src/traits.rs2
4 files changed, 65 insertions, 32 deletions
diff --git a/lib/text-size/src/lib.rs b/lib/text-size/src/lib.rs
index 32262de12c..e194e2317b 100644
--- a/lib/text-size/src/lib.rs
+++ b/lib/text-size/src/lib.rs
@@ -13,3 +13,6 @@ mod traits;
mod serde_impls;
pub use crate::{range::TextRange, size::TextSize, traits::TextSized};
+
+#[cfg(target_pointer_width = "16")]
+compile_error!("text-size assumes usize >= u32 and does not work on 16-bit targets");
diff --git a/lib/text-size/src/range.rs b/lib/text-size/src/range.rs
index df5296af14..789d300665 100644
--- a/lib/text-size/src/range.rs
+++ b/lib/text-size/src/range.rs
@@ -8,24 +8,19 @@ use {
/// A range in text, represented as a pair of [`TextSize`][struct@TextSize].
///
-/// It is a logical error to have `end() < start()`, but
-/// code must not assume this is true for `unsafe` guarantees.
-///
/// # Translation from `text_unit`
///
-/// - `TextRange::from_to(from, to)` ⟹ `TextRange::from(from..to)`
-/// - `TextRange::offset_len(offset, size)` ⟹ `TextRange::from(offset..offset + size)`
+/// - `TextRange::from_to(from, to)` ⟹ `TextRange(from, to)`
+/// - `TextRange::offset_len(offset, size)` ⟹ `TextRange::up_to(size).offset(offset)`
/// - `range.start()` ⟹ `range.start()`
/// - `range.end()` ⟹ `range.end()`
-/// - `range.len()` ⟹ `range.len()`<sup>†</sup>
+/// - `range.len()` ⟹ `range.len()`
/// - `range.is_empty()` ⟹ `range.is_empty()`
-/// - `a.is_subrange(b)` ⟹ `b.contains(a)`
+/// - `a.is_subrange(b)` ⟹ `b.contains_range(a)`
/// - `a.intersection(b)` ⟹ `TextRange::intersection(a, b)`
/// - `a.extend_to(b)` ⟹ `TextRange::covering(a, b)`
-/// - `range.contains(offset)` ⟹ `range.contains_exclusive(point)`
+/// - `range.contains(offset)` ⟹ `range.contains(point)`
/// - `range.contains_inclusive(offset)` ⟹ `range.contains_inclusive(point)`
-///
-/// † See the note on [`TextRange::len`] for differing behavior for incorrect reverse ranges.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct TextRange {
// Invariant: start <= end
@@ -39,47 +34,61 @@ impl fmt::Debug for TextRange {
}
}
-/// Creates a new `TextRange` with given `start` and `end.
+/// Creates a new `TextRange` with the given `start` and `end` (`start..end`).
///
/// # Panics
///
/// Panics if `end < start`.
#[allow(non_snake_case)]
+#[inline]
pub fn TextRange(start: TextSize, end: TextSize) -> TextRange {
assert!(start <= end);
TextRange { start, end }
}
-/// Identity methods.
impl TextRange {
- /// Creates a zero-length range at the specified offset.
- pub const fn empty(self, offset: TextSize) -> TextRange {
+ /// Create a zero-length range at the specified offset (`offset..offset`).
+ #[inline]
+ pub const fn empty(offset: TextSize) -> TextRange {
TextRange {
start: offset,
end: offset,
}
}
+ /// Create a range up to the given end (`..end`).
+ #[inline]
+ pub const fn up_to(end: TextSize) -> TextRange {
+ TextRange {
+ start: TextSize::zero(),
+ end,
+ }
+ }
+}
+
+/// Identity methods.
+impl TextRange {
/// The start point of this range.
+ #[inline]
pub const fn start(self) -> TextSize {
self.start
}
/// The end point of this range.
+ #[inline]
pub const fn end(self) -> TextSize {
self.end
}
/// The size of this range.
+ #[inline]
pub const fn len(self) -> TextSize {
// HACK for const fn: math on primitives only
TextSize(self.end().raw - self.start().raw)
}
- /// Check if this range empty or reversed.
- ///
- /// When `end() < start()`, this returns false.
- /// Code should prefer `is_empty()` to `len() == 0`.
+ /// Check if this range is empty.
+ #[inline]
pub const fn is_empty(self) -> bool {
// HACK for const fn: math on primitives only
self.start().raw == self.end().raw
@@ -99,8 +108,7 @@ impl TextRange {
///
/// The end index is considered included.
pub fn contains_inclusive(self, offset: TextSize) -> bool {
- let point = offset.into();
- self.start() <= point && point <= self.end()
+ self.start() <= offset && offset <= self.end()
}
/// Check if this range completely contains another range.
@@ -129,12 +137,14 @@ impl TextRange {
impl Index<TextRange> for str {
type Output = str;
+ #[inline]
fn index(&self, index: TextRange) -> &Self::Output {
&self[Range::<usize>::from(index)]
}
}
impl IndexMut<TextRange> for str {
+ #[inline]
fn index_mut(&mut self, index: TextRange) -> &mut Self::Output {
&mut self[Range::<usize>::from(index)]
}
@@ -154,6 +164,7 @@ impl<T> From<TextRange> for Range<T>
where
T: From<TextSize>,
{
+ #[inline]
fn from(r: TextRange) -> Self {
r.start().into()..r.end().into()
}
diff --git a/lib/text-size/src/size.rs b/lib/text-size/src/size.rs
index 454327a015..5239ea6691 100644
--- a/lib/text-size/src/size.rs
+++ b/lib/text-size/src/size.rs
@@ -11,15 +11,23 @@ use {
/// A measure of text length. Also, equivalently, an index into text.
///
-/// This is a utf8-bytes-offset stored as `u32`, but
+/// 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.
+///
/// # Translation from `text_unit`
///
/// - `TextUnit::of_char(c)` ⟹ `TextSize::of(c)`
-/// - `TextUnit::of_str(s)` ⟹ `TextSize:of(s)`
+/// - `TextUnit::of_str(s)` ⟹ `TextSize::of(s)`
/// - `TextUnit::from_usize(size)` ⟹ `TextSize::try_from(size).unwrap_or_else(|| panic!(_))`
-/// - `unit.to_usize()` ⟹ `usize::try_from(size).unwrap_or_else(|| panic!(_))`
+/// - `unit.to_usize()` ⟹ `usize::from(size)`
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TextSize {
pub(crate) raw: u32,
@@ -38,6 +46,7 @@ impl fmt::Debug for TextSize {
impl TextSize {
/// The text size of some text-like object.
+ #[inline]
pub fn of(text: impl TextSized) -> TextSize {
text.text_size()
}
@@ -46,6 +55,7 @@ impl TextSize {
///
/// This is equivalent to `TextSize::default()` or [`TextSize::MIN`],
/// but is more explicit on intent.
+ #[inline]
pub const fn zero() -> TextSize {
TextSize(0)
}
@@ -59,24 +69,28 @@ impl TextSize {
/// The largest representable text size. (`u32::MAX`)
pub const MAX: TextSize = TextSize(u32::MAX);
- #[allow(missing_docs)]
+ /// Checked addition. Returns `None` if overflow occurred.
+ #[inline]
pub fn checked_add(self, rhs: TextSize) -> Option<TextSize> {
self.raw.checked_add(rhs.raw).map(TextSize)
}
- #[allow(missing_docs)]
+ /// Checked subtraction. Returns `None` if overflow occurred.
+ #[inline]
pub fn checked_sub(self, rhs: TextSize) -> Option<TextSize> {
self.raw.checked_sub(rhs.raw).map(TextSize)
}
}
impl From<u32> for TextSize {
+ #[inline]
fn from(raw: u32) -> Self {
- TextSize { raw }
+ TextSize(raw)
}
}
impl From<TextSize> for u32 {
+ #[inline]
fn from(value: TextSize) -> Self {
value.raw
}
@@ -84,19 +98,16 @@ impl From<TextSize> for u32 {
impl TryFrom<usize> for TextSize {
type Error = TryFromIntError;
+ #[inline]
fn try_from(value: usize) -> Result<Self, TryFromIntError> {
Ok(u32::try_from(value)?.into())
}
}
impl From<TextSize> for usize {
+ #[inline]
fn from(value: TextSize) -> Self {
- assert_lossless_conversion();
- return value.raw as usize;
-
- const fn assert_lossless_conversion() {
- [()][(std::mem::size_of::<usize>() < std::mem::size_of::<u32>()) as usize]
- }
+ value.raw as usize
}
}
@@ -104,12 +115,14 @@ macro_rules! ops {
(impl $Op:ident for TextSize by fn $f:ident = $op:tt) => {
impl $Op<TextSize> for TextSize {
type Output = TextSize;
+ #[inline]
fn $f(self, other: TextSize) -> TextSize {
TextSize(self.raw $op other.raw)
}
}
impl $Op<&TextSize> for TextSize {
type Output = TextSize;
+ #[inline]
fn $f(self, other: &TextSize) -> TextSize {
self $op *other
}
@@ -119,6 +132,7 @@ macro_rules! ops {
TextSize: $Op<T, Output=TextSize>,
{
type Output = TextSize;
+ #[inline]
fn $f(self, other: T) -> TextSize {
*self $op other
}
@@ -133,6 +147,7 @@ impl<A> AddAssign<A> for TextSize
where
TextSize: Add<A, Output = TextSize>,
{
+ #[inline]
fn add_assign(&mut self, rhs: A) {
*self = *self + rhs
}
@@ -142,6 +157,7 @@ impl<S> SubAssign<S> for TextSize
where
TextSize: Sub<S, Output = TextSize>,
{
+ #[inline]
fn sub_assign(&mut self, rhs: S) {
*self = *self - rhs
}
@@ -151,6 +167,7 @@ impl<A> iter::Sum<A> for TextSize
where
TextSize: Add<A, Output = TextSize>,
{
+ #[inline]
fn sum<I: Iterator<Item = A>>(iter: I) -> TextSize {
iter.fold(TextSize::zero(), Add::add)
}
diff --git a/lib/text-size/src/traits.rs b/lib/text-size/src/traits.rs
index ca4b7d9b42..8d197db8c1 100644
--- a/lib/text-size/src/traits.rs
+++ b/lib/text-size/src/traits.rs
@@ -7,6 +7,7 @@ pub trait TextSized: Copy {
}
impl TextSized for &'_ str {
+ #[inline]
fn text_size(self) -> TextSize {
self.len()
.try_into()
@@ -15,6 +16,7 @@ impl TextSized for &'_ str {
}
impl TextSized for char {
+ #[inline]
fn text_size(self) -> TextSize {
TextSize(self.len_utf8() as u32)
}