Skip to content

Commit 8d19701

Browse files
committed
minor improvements everywhere
1 parent e523b86 commit 8d19701

File tree

3 files changed

+66
-31
lines changed

3 files changed

+66
-31
lines changed

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@ mod traits;
1313
mod serde_impls;
1414

1515
pub use crate::{range::TextRange, size::TextSize, traits::TextSized};
16+
17+
#[cfg(target_pointer_width = "16")]
18+
compile_error!("text-size assumes usize >= u32 and does not work on 16-bit targets");

src/range.rs

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,25 @@ use {
22
crate::TextSize,
33
std::{
44
cmp, fmt,
5-
ops::{Bound, Index, IndexMut, Range, RangeBounds},
5+
ops::{Bound, Index, IndexMut, Range, RangeBounds, RangeFrom},
66
},
77
};
88

99
/// A range in text, represented as a pair of [`TextSize`][struct@TextSize].
1010
///
11-
/// It is a logical error to have `end() < start()`, but
12-
/// code must not assume this is true for `unsafe` guarantees.
13-
///
1411
/// # Translation from `text_unit`
1512
///
16-
/// - `TextRange::from_to(from, to)` ⟹ `TextRange::from(from..to)`
17-
/// - `TextRange::offset_len(offset, size)` ⟹ `TextRange::from(offset..offset + size)`
13+
/// - `TextRange::from_to(from, to)` ⟹ `TextRange(from, to)`
14+
/// - `TextRange::offset_len(offset, size)` ⟹ `TextRange::to(size).offset(offset)`
1815
/// - `range.start()` ⟹ `range.start()`
1916
/// - `range.end()` ⟹ `range.end()`
20-
/// - `range.len()` ⟹ `range.len()`<sup>†</sup>
17+
/// - `range.len()` ⟹ `range.len()`
2118
/// - `range.is_empty()` ⟹ `range.is_empty()`
22-
/// - `a.is_subrange(b)` ⟹ `b.contains(a)`
19+
/// - `a.is_subrange(b)` ⟹ `b.contains_range(a)`
2320
/// - `a.intersection(b)` ⟹ `TextRange::intersection(a, b)`
2421
/// - `a.extend_to(b)` ⟹ `TextRange::covering(a, b)`
25-
/// - `range.contains(offset)` ⟹ `range.contains_exclusive(point)`
22+
/// - `range.contains(offset)` ⟹ `range.contains(point)`
2623
/// - `range.contains_inclusive(offset)` ⟹ `range.contains_inclusive(point)`
27-
///
28-
/// † See the note on [`TextRange::len`] for differing behavior for incorrect reverse ranges.
2924
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
3025
pub struct TextRange {
3126
// Invariant: start <= end
@@ -39,7 +34,7 @@ impl fmt::Debug for TextRange {
3934
}
4035
}
4136

42-
/// Creates a new `TextRange` with given `start` and `end.
37+
/// Creates a new `TextRange` with the given `start` and `end` (`start..end`).
4338
///
4439
/// # Panics
4540
///
@@ -50,16 +45,47 @@ pub fn TextRange(start: TextSize, end: TextSize) -> TextRange {
5045
TextRange { start, end }
5146
}
5247

53-
/// Identity methods.
5448
impl TextRange {
55-
/// Creates a zero-length range at the specified offset.
56-
pub const fn empty(self, offset: TextSize) -> TextRange {
49+
/// Create a zero-length range at the specified offset (`offset..offset`).
50+
pub const fn empty(offset: TextSize) -> TextRange {
5751
TextRange {
5852
start: offset,
5953
end: offset,
6054
}
6155
}
6256

57+
/// Create a range up to the given end (`..end`).
58+
pub const fn before(end: TextSize) -> TextRange {
59+
TextRange {
60+
start: TextSize::zero(),
61+
end,
62+
}
63+
}
64+
65+
/// Create a range after the given start (`start..`).
66+
///
67+
/// This returns a std [`RangeFrom`] rather than `TextRange` because
68+
/// `TextRange` does not support right-unbounded ranges. As such, this
69+
/// should only be used for direct indexing, and bounded ranges should be
70+
/// used for persistent ranges (`TextRange(start, TextSize::of(text))`).
71+
pub const fn after(start: TextSize) -> RangeFrom<usize> {
72+
start.raw as usize..
73+
}
74+
75+
/// Offset this range by some amount.
76+
///
77+
/// This is typically used to convert a range from one coordinate space to
78+
/// another, such as from within a substring to within an entire document.
79+
pub fn offset(self, offset: TextSize) -> TextRange {
80+
TextRange(
81+
self.start().checked_add(offset).unwrap(),
82+
self.end().checked_add(offset).unwrap(),
83+
)
84+
}
85+
}
86+
87+
/// Identity methods.
88+
impl TextRange {
6389
/// The start point of this range.
6490
pub const fn start(self) -> TextSize {
6591
self.start
@@ -76,10 +102,7 @@ impl TextRange {
76102
TextSize(self.end().raw - self.start().raw)
77103
}
78104

79-
/// Check if this range empty or reversed.
80-
///
81-
/// When `end() < start()`, this returns false.
82-
/// Code should prefer `is_empty()` to `len() == 0`.
105+
/// Check if this range is empty.
83106
pub const fn is_empty(self) -> bool {
84107
// HACK for const fn: math on primitives only
85108
self.start().raw == self.end().raw
@@ -99,8 +122,7 @@ impl TextRange {
99122
///
100123
/// The end index is considered included.
101124
pub fn contains_inclusive(self, offset: TextSize) -> bool {
102-
let point = offset.into();
103-
self.start() <= point && point <= self.end()
125+
self.start() <= offset && offset <= self.end()
104126
}
105127

106128
/// Check if this range completely contains another range.

src/size.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,23 @@ use {
1111

1212
/// A measure of text length. Also, equivalently, an index into text.
1313
///
14-
/// This is a utf8-bytes-offset stored as `u32`, but
14+
/// This is a UTF-8 bytes offset stored as `u32`, but
1515
/// most clients should treat it as an opaque measure.
1616
///
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+
///
1725
/// # Translation from `text_unit`
1826
///
1927
/// - `TextUnit::of_char(c)` ⟹ `TextSize::of(c)`
20-
/// - `TextUnit::of_str(s)` ⟹ `TextSize:of(s)`
28+
/// - `TextUnit::of_str(s)` ⟹ `TextSize::of(s)`
2129
/// - `TextUnit::from_usize(size)` ⟹ `TextSize::try_from(size).unwrap_or_else(|| panic!(_))`
22-
/// - `unit.to_usize()` ⟹ `usize::try_from(size).unwrap_or_else(|| panic!(_))`
30+
/// - `unit.to_usize()` ⟹ `usize::from(size)`
2331
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
2432
pub struct TextSize {
2533
pub(crate) raw: u32,
@@ -49,6 +57,11 @@ impl TextSize {
4957
pub const fn zero() -> TextSize {
5058
TextSize(0)
5159
}
60+
61+
/// A size of one.
62+
pub const fn one() -> TextSize {
63+
TextSize(1)
64+
}
5265
}
5366

5467
/// Methods to act like a primitive integer type, where reasonably applicable.
@@ -58,6 +71,8 @@ impl TextSize {
5871
pub const MIN: TextSize = TextSize(u32::MIN);
5972
/// The largest representable text size. (`u32::MAX`)
6073
pub const MAX: TextSize = TextSize(u32::MAX);
74+
/// The text size of a single ASCII character.
75+
pub const ONE: TextSize = TextSize(1);
6176

6277
#[allow(missing_docs)]
6378
pub fn checked_add(self, rhs: TextSize) -> Option<TextSize> {
@@ -72,7 +87,7 @@ impl TextSize {
7287

7388
impl From<u32> for TextSize {
7489
fn from(raw: u32) -> Self {
75-
TextSize { raw }
90+
TextSize(raw)
7691
}
7792
}
7893

@@ -91,12 +106,7 @@ impl TryFrom<usize> for TextSize {
91106

92107
impl From<TextSize> for usize {
93108
fn from(value: TextSize) -> Self {
94-
assert_lossless_conversion();
95-
return value.raw as usize;
96-
97-
const fn assert_lossless_conversion() {
98-
[()][(std::mem::size_of::<usize>() < std::mem::size_of::<u32>()) as usize]
99-
}
109+
value.raw as usize
100110
}
101111
}
102112

0 commit comments

Comments
 (0)