Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

minor improvements everywhere #18

Merged
merged 9 commits into from
Mar 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
51 changes: 31 additions & 20 deletions src/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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)]
}
Expand All @@ -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()
}
Expand Down
41 changes: 29 additions & 12 deletions src/size.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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()
}
Expand All @@ -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)
}
Expand All @@ -59,57 +69,60 @@ 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
}
}

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
}
}

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
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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)
}
Expand Down
2 changes: 2 additions & 0 deletions src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub trait TextSized: Copy {
}

impl TextSized for &'_ str {
#[inline]
fn text_size(self) -> TextSize {
self.len()
.try_into()
Expand All @@ -15,6 +16,7 @@ impl TextSized for &'_ str {
}

impl TextSized for char {
#[inline]
fn text_size(self) -> TextSize {
TextSize(self.len_utf8() as u32)
}
Expand Down