Skip to content

Commit b108d4a

Browse files
committed
Improve NSRange and NSComparisonResult
With a few ideas from https://github.com/nvzqz/fruity
1 parent efaf576 commit b108d4a

File tree

3 files changed

+142
-9
lines changed

3 files changed

+142
-9
lines changed

objc2-foundation/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1111
`NSArray`, which derefs to `NSObject`, which derefs to `Object`.
1212

1313
This allows more ergonomic usage.
14-
* Implement `PartialOrd` and `Ord` for `NSString`.
14+
* Implement `PartialOrd` and `Ord` for `NSString` and `NSRange`.
1515
* Added `NSString::has_prefix` and `NSString::has_suffix`.
16+
* Added `NSRange` methods `new`, `is_empty`, `contains` and `end`.
1617

1718
### Changed
1819
* **BREAKING**: Removed the following helper traits in favor of inherent

objc2-foundation/src/comparison_result.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,22 @@ use core::cmp::Ordering;
22

33
use objc2::{Encode, Encoding, RefEncode};
44

5+
/// Constants that indicate sort order.
6+
///
7+
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nscomparisonresult?language=objc).
58
#[repr(isize)] // NSInteger
69
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
710
pub enum NSComparisonResult {
11+
/// The left operand is smaller than the right operand.
812
Ascending = -1,
13+
/// The two operands are equal.
914
Same = 0,
15+
/// The left operand is greater than the right operand.
1016
Descending = 1,
1117
}
1218

1319
impl Default for NSComparisonResult {
20+
#[inline]
1421
fn default() -> Self {
1522
Self::Same
1623
}
@@ -25,6 +32,7 @@ unsafe impl RefEncode for NSComparisonResult {
2532
}
2633

2734
impl From<Ordering> for NSComparisonResult {
35+
#[inline]
2836
fn from(order: Ordering) -> Self {
2937
match order {
3038
Ordering::Less => Self::Ascending,
@@ -35,6 +43,7 @@ impl From<Ordering> for NSComparisonResult {
3543
}
3644

3745
impl From<NSComparisonResult> for Ordering {
46+
#[inline]
3847
fn from(comparison_result: NSComparisonResult) -> Self {
3948
match comparison_result {
4049
NSComparisonResult::Ascending => Self::Less,

objc2-foundation/src/range.rs

Lines changed: 131 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,90 @@ use core::ops::Range;
22

33
use objc2::{Encode, Encoding, RefEncode};
44

5+
/// TODO.
6+
///
7+
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsrange?language=objc).
58
#[repr(C)]
69
// PartialEq is same as NSEqualRanges
7-
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
10+
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
811
pub struct NSRange {
12+
/// The lower bound of the range (inclusive).
13+
// NSUInteger
914
pub location: usize,
15+
/// The number of items in the range, starting from `location`.
16+
// NSUInteger
1017
pub length: usize,
1118
}
1219

13-
// impl NSRange {
14-
// pub fn contains(&self, index: usize) -> bool {
15-
// // Same as NSLocationInRange
16-
// <Self as RangeBounds<usize>>::contains(self, &index)
17-
// }
18-
// }
20+
impl NSRange {
21+
/// Create a new range with the given values.
22+
///
23+
/// # Examples
24+
///
25+
/// ```
26+
/// use objc2_foundation::NSRange;
27+
/// assert_eq!(NSRange::new(3, 2), NSRange::from(3..5));
28+
/// ```
29+
#[inline]
30+
pub const fn new(location: usize, length: usize) -> Self {
31+
// Equivalent to NSMakeRange
32+
Self { location, length }
33+
}
1934

35+
/// Returns `true` if the range contains no items.
36+
///
37+
/// # Examples
38+
///
39+
/// ```
40+
/// use objc2_foundation::NSRange;
41+
///
42+
/// assert!(!NSRange::from(3..5).is_empty());
43+
/// assert!( NSRange::from(3..3).is_empty());
44+
/// ```
45+
#[inline]
46+
pub fn is_empty(&self) -> bool {
47+
self.length == 0
48+
}
49+
50+
/// Returns `true` if the index is within the range.
51+
///
52+
/// # Examples
53+
///
54+
/// ```
55+
/// use objc2_foundation::NSRange;
56+
///
57+
/// assert!(!NSRange::from(3..5).contains(2));
58+
/// assert!( NSRange::from(3..5).contains(3));
59+
/// assert!( NSRange::from(3..5).contains(4));
60+
/// assert!(!NSRange::from(3..5).contains(5));
61+
///
62+
/// assert!(!NSRange::from(3..3).contains(3));
63+
/// ```
64+
#[inline]
65+
pub fn contains(&self, index: usize) -> bool {
66+
// Same as NSLocationInRange
67+
if let Some(len) = index.checked_sub(self.location) {
68+
len < self.length
69+
} else {
70+
// index < self.location
71+
false
72+
}
73+
}
74+
75+
/// Returns the upper bound of the range (exclusive).
76+
#[inline]
77+
pub fn end(&self) -> usize {
78+
self.location
79+
.checked_add(self.length)
80+
.expect("NSRange too large")
81+
}
82+
83+
// TODO: https://developer.apple.com/documentation/foundation/1408420-nsrangefromstring
84+
// TODO: NSUnionRange
85+
// TODO: NSIntersectionRange
86+
}
87+
88+
// Sadly, we can't do this:
2089
// impl RangeBounds<usize> for NSRange {
2190
// fn start_bound(&self) -> Bound<&usize> {
2291
// Bound::Included(&self.location)
@@ -40,10 +109,11 @@ impl From<Range<usize>> for NSRange {
40109
}
41110

42111
impl From<NSRange> for Range<usize> {
112+
#[inline]
43113
fn from(nsrange: NSRange) -> Self {
44114
Self {
45115
start: nsrange.location,
46-
end: nsrange.location + nsrange.length,
116+
end: nsrange.end(),
47117
}
48118
}
49119
}
@@ -56,3 +126,56 @@ unsafe impl Encode for NSRange {
56126
unsafe impl RefEncode for NSRange {
57127
const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Self::ENCODING);
58128
}
129+
130+
#[cfg(test)]
131+
mod tests {
132+
use super::*;
133+
134+
#[test]
135+
fn test_from_range() {
136+
let cases: &[(Range<usize>, NSRange)] = &[
137+
(0..0, NSRange::new(0, 0)),
138+
(0..10, NSRange::new(0, 10)),
139+
(10..10, NSRange::new(10, 0)),
140+
(10..20, NSRange::new(10, 10)),
141+
];
142+
143+
for (range, expected) in cases {
144+
assert_eq!(NSRange::from(range.clone()), *expected);
145+
}
146+
}
147+
148+
#[test]
149+
#[should_panic = "Range end < start"]
150+
fn test_from_range_inverted() {
151+
let _ = NSRange::from(10..0);
152+
}
153+
154+
#[test]
155+
fn test_contains() {
156+
let range = NSRange::from(10..20);
157+
assert!(!range.contains(0));
158+
assert!(!range.contains(9));
159+
assert!(range.contains(10));
160+
assert!(range.contains(11));
161+
assert!(!range.contains(20));
162+
assert!(!range.contains(21));
163+
}
164+
165+
#[test]
166+
fn test_end() {
167+
let range = NSRange::from(10..20);
168+
assert!(!range.contains(0));
169+
assert!(!range.contains(9));
170+
assert!(range.contains(10));
171+
assert!(range.contains(11));
172+
assert!(!range.contains(20));
173+
assert!(!range.contains(21));
174+
}
175+
176+
#[test]
177+
#[should_panic = "NSRange too large"]
178+
fn test_end_large() {
179+
let _ = NSRange::new(usize::MAX, usize::MAX).end();
180+
}
181+
}

0 commit comments

Comments
 (0)