objc2_foundation/
range.rs1use core::ops::Range;
2
3use objc2::encode::{Encode, Encoding, RefEncode};
4
5use crate::NSUInteger;
6
7#[repr(C)]
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
13pub struct NSRange {
14 pub location: NSUInteger,
16 pub length: NSUInteger,
18}
19
20impl NSRange {
21 #[inline]
30 #[doc(alias = "NSMakeRange")]
31 pub const fn new(location: usize, length: usize) -> Self {
32 Self { location, length }
34 }
35
36 #[inline]
47 pub fn is_empty(&self) -> bool {
48 self.length == 0
49 }
50
51 #[inline]
66 #[doc(alias = "NSLocationInRange")]
67 pub fn contains(&self, index: usize) -> bool {
68 if let Some(len) = index.checked_sub(self.location) {
70 len < self.length
71 } else {
72 false
74 }
75 }
76
77 #[inline]
79 #[doc(alias = "NSMaxRange")]
80 pub fn end(&self) -> usize {
81 self.location
82 .checked_add(self.length)
83 .expect("NSRange too large")
84 }
85
86 }
90
91impl From<Range<usize>> for NSRange {
102 fn from(range: Range<usize>) -> Self {
103 let length = range
104 .end
105 .checked_sub(range.start)
106 .expect("Range end < start");
107 Self {
108 location: range.start,
109 length,
110 }
111 }
112}
113
114impl From<NSRange> for Range<usize> {
115 #[inline]
116 fn from(nsrange: NSRange) -> Self {
117 Self {
118 start: nsrange.location,
119 end: nsrange.end(),
120 }
121 }
122}
123
124unsafe impl Encode for NSRange {
125 const ENCODING: Encoding = Encoding::Struct("_NSRange", &[usize::ENCODING, usize::ENCODING]);
126}
127
128unsafe impl RefEncode for NSRange {
129 const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn test_from_range() {
138 let cases: &[(Range<usize>, NSRange)] = &[
139 (0..0, NSRange::new(0, 0)),
140 (0..10, NSRange::new(0, 10)),
141 (10..10, NSRange::new(10, 0)),
142 (10..20, NSRange::new(10, 10)),
143 ];
144
145 for (range, expected) in cases {
146 assert_eq!(NSRange::from(range.clone()), *expected);
147 }
148 }
149
150 #[test]
151 #[should_panic = "Range end < start"]
152 #[allow(clippy::reversed_empty_ranges)]
153 fn test_from_range_inverted() {
154 let _ = NSRange::from(10..0);
155 }
156
157 #[test]
158 fn test_contains() {
159 let range = NSRange::from(10..20);
160 assert!(!range.contains(0));
161 assert!(!range.contains(9));
162 assert!(range.contains(10));
163 assert!(range.contains(11));
164 assert!(!range.contains(20));
165 assert!(!range.contains(21));
166 }
167
168 #[test]
169 fn test_end() {
170 let range = NSRange::from(10..20);
171 assert!(!range.contains(0));
172 assert!(!range.contains(9));
173 assert!(range.contains(10));
174 assert!(range.contains(11));
175 assert!(!range.contains(20));
176 assert!(!range.contains(21));
177 }
178
179 #[test]
180 #[should_panic = "NSRange too large"]
181 fn test_end_large() {
182 let _ = NSRange::new(usize::MAX, usize::MAX).end();
183 }
184}