@@ -2,21 +2,90 @@ use core::ops::Range;
2
2
3
3
use objc2:: { Encode , Encoding , RefEncode } ;
4
4
5
+ /// TODO.
6
+ ///
7
+ /// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsrange?language=objc).
5
8
#[ repr( C ) ]
6
9
// 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 ) ]
8
11
pub struct NSRange {
12
+ /// The lower bound of the range (inclusive).
13
+ // NSUInteger
9
14
pub location : usize ,
15
+ /// The number of items in the range, starting from `location`.
16
+ // NSUInteger
10
17
pub length : usize ,
11
18
}
12
19
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
+ }
19
34
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:
20
89
// impl RangeBounds<usize> for NSRange {
21
90
// fn start_bound(&self) -> Bound<&usize> {
22
91
// Bound::Included(&self.location)
@@ -40,10 +109,11 @@ impl From<Range<usize>> for NSRange {
40
109
}
41
110
42
111
impl From < NSRange > for Range < usize > {
112
+ #[ inline]
43
113
fn from ( nsrange : NSRange ) -> Self {
44
114
Self {
45
115
start : nsrange. location ,
46
- end : nsrange. location + nsrange . length ,
116
+ end : nsrange. end ( ) ,
47
117
}
48
118
}
49
119
}
@@ -56,3 +126,56 @@ unsafe impl Encode for NSRange {
56
126
unsafe impl RefEncode for NSRange {
57
127
const ENCODING_REF : Encoding < ' static > = Encoding :: Pointer ( & Self :: ENCODING ) ;
58
128
}
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