cursor_iter/
lib.rs

1use std::str::CharIndices;
2
3fn is_newline(c: char) -> bool {
4    c == '\n'
5}
6
7/// A cursor that can move both forward and backward through a string.
8#[derive(Debug, Clone)]
9pub struct Cursor<'a> {
10    data: &'a str,
11    offset: usize,
12    line: usize,
13    forward: CharIndices<'a>,
14}
15
16impl<'a> Cursor<'a> {
17    pub fn new(data: &'a str) -> Self {
18        Self {
19            data,
20            offset: 0,
21            line: 0,
22            forward: data.char_indices(),
23        }
24    }
25
26    fn backward(&self) -> CharIndices<'a> {
27        self.data[..self.offset].char_indices()
28    }
29
30    pub fn next_char(&mut self) -> Option<char> {
31        self.next().map(|(_, c)| c)
32    }
33
34    pub fn next_word(&mut self) -> Option<(usize, &'a str)> {
35        let start = self.offset;
36        for (_, c) in self.by_ref() {
37            if c.is_whitespace() {
38                break;
39            }
40        }
41        let end = self.offset;
42        if start < end {
43            Some((start, &self.data[start..end]))
44        } else {
45            None
46        }
47    }
48
49    pub fn next_line(&mut self) -> Option<(usize, &'a str)> {
50        let start = self.offset;
51        for (_, c) in self.by_ref() {
52            if is_newline(c) {
53                break;
54            }
55        }
56        let end = self.offset;
57        if start < end {
58            Some((start, &self.data[start..end]))
59        } else {
60            None
61        }
62    }
63
64    pub fn peek_line(&mut self) -> Option<(usize, &'a str)> {
65        let start = self.offset;
66        for (_, c) in self.forward.clone() {
67            if is_newline(c) {
68                break;
69            }
70        }
71        let end = self.offset;
72        if start < end {
73            Some((start, &self.data[start..end]))
74        } else {
75            None
76        }
77    }
78
79    fn skip_whitespace(&mut self) {
80        while let Some((_, c)) = self.peek() {
81            if !c.is_whitespace() {
82                break;
83            }
84            self.next();
85        }
86    }
87
88    pub fn prev(&mut self) -> Option<(usize, char)> {
89        let mut backward = self.backward();
90
91        let last_byte_len = backward.as_str().as_bytes().len();
92        let (pos, c) = backward.next_back()?;
93        let cur_byte_len = backward.as_str().as_bytes().len();
94        self.offset -= last_byte_len - cur_byte_len;
95
96        self.forward = self.data[self.offset..].char_indices();
97
98        if is_newline(c) {
99            self.line -= 1;
100        }
101
102        Some((pos, c))
103    }
104
105    pub fn prev_char(&mut self) -> Option<char> {
106        self.prev().map(|(_, c)| c)
107    }
108
109    pub fn peek(&self) -> Option<(usize, char)> {
110        self.forward.clone().next()
111    }
112
113    pub fn peek_char(&self) -> Option<char> {
114        self.peek().map(|(_, c)| c)
115    }
116
117    pub fn lookback(&self) -> Option<(usize, char)> {
118        self.backward().next_back()
119    }
120
121    pub fn lookback_char(&self) -> Option<char> {
122        self.lookback().map(|(_, c)| c)
123    }
124
125    pub const fn line(&self) -> usize {
126        self.line
127    }
128
129    pub const fn words(&mut self) -> CursorWords<'a, '_> {
130        CursorWords::new(self)
131    }
132
133    pub const fn words_with_lines(&mut self) -> CursorWords<'a, '_, true> {
134        CursorWords::with_lines(self)
135    }
136
137    pub const fn lines(&mut self) -> CursorLines<'a, '_> {
138        CursorLines::new(self)
139    }
140}
141
142impl Iterator for Cursor<'_> {
143    type Item = (usize, char);
144
145    fn next(&mut self) -> Option<Self::Item> {
146        let last_byte_len = self.forward.as_str().as_bytes().len();
147        let (pos, c) = self.forward.next()?;
148        let cur_byte_len = self.forward.as_str().as_bytes().len();
149        self.offset += last_byte_len - cur_byte_len;
150
151        if is_newline(c) {
152            self.line += 1;
153        }
154
155        Some((pos, c))
156    }
157}
158
159pub struct CursorWords<'a, 'b, const LINES: bool = false> {
160    cursor: &'b mut Cursor<'a>,
161}
162
163impl<'a, 'b> CursorWords<'a, 'b> {
164    pub const fn new(cursor: &'b mut Cursor<'a>) -> Self {
165        Self { cursor }
166    }
167}
168
169impl<'a, 'b> CursorWords<'a, 'b, true> {
170    pub const fn with_lines(cursor: &'b mut Cursor<'a>) -> Self {
171        Self { cursor }
172    }
173}
174
175impl<'a> Iterator for CursorWords<'a, '_, false> {
176    type Item = (usize, &'a str);
177
178    fn next(&mut self) -> Option<Self::Item> {
179        let ret = self.cursor.next_word()?;
180        self.cursor.skip_whitespace();
181        Some(ret)
182    }
183}
184
185impl<'a> Iterator for CursorWords<'a, '_, true> {
186    type Item = (usize, usize, &'a str);
187
188    fn next(&mut self) -> Option<Self::Item> {
189        let line = self.cursor.line();
190        let (offset, word) = self.cursor.next_word()?;
191        self.cursor.skip_whitespace();
192        Some((offset, line, word))
193    }
194}
195
196pub struct CursorLines<'a, 'b> {
197    cursor: &'b mut Cursor<'a>,
198}
199
200impl<'a, 'b> CursorLines<'a, 'b> {
201    pub const fn new(cursor: &'b mut Cursor<'a>) -> Self {
202        Self { cursor }
203    }
204}
205
206impl<'a> Iterator for CursorLines<'a, '_> {
207    type Item = (usize, usize, &'a str);
208
209    fn next(&mut self) -> Option<Self::Item> {
210        let line = self.cursor.line();
211        let (off, ret) = self.cursor.next_line()?;
212        Some((off, line, ret))
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219
220    #[test]
221    fn test_new_cursor() {
222        let cursor = Cursor::new("hello");
223        assert_eq!(cursor.peek_char(), Some('h'));
224        assert_eq!(cursor.lookback_char(), None);
225    }
226
227    #[test]
228    fn test_empty_string() {
229        let mut cursor = Cursor::new("");
230        assert_eq!(cursor.next_char(), None);
231        assert_eq!(cursor.prev_char(), None);
232        assert_eq!(cursor.peek_char(), None);
233        assert_eq!(cursor.lookback_char(), None);
234    }
235
236    #[test]
237    fn test_advance() {
238        let mut cursor = Cursor::new("abc");
239
240        assert_eq!(cursor.next(), Some((0, 'a')));
241        assert_eq!(cursor.next(), Some((1, 'b')));
242        assert_eq!(cursor.next(), Some((2, 'c')));
243        assert_eq!(cursor.next(), None);
244    }
245
246    #[test]
247    fn test_prev() {
248        let mut cursor = Cursor::new("abc");
249
250        // Advance to end
251        cursor.next();
252        cursor.next();
253        cursor.next();
254
255        assert_eq!(cursor.prev(), Some((2, 'c')));
256        assert_eq!(cursor.prev(), Some((1, 'b')));
257        assert_eq!(cursor.prev(), Some((0, 'a')));
258        assert_eq!(cursor.prev(), None);
259    }
260
261    #[test]
262    fn test_peek() {
263        let mut cursor = Cursor::new("abc");
264
265        assert_eq!(cursor.peek(), Some((0, 'a')));
266        cursor.next();
267        assert_eq!(cursor.peek(), Some((1, 'b')));
268        cursor.next();
269        assert_eq!(cursor.peek(), Some((2, 'c')));
270        cursor.next();
271        assert_eq!(cursor.peek(), None);
272    }
273
274    #[test]
275    fn test_lookback() {
276        let mut cursor = Cursor::new("abc");
277
278        assert_eq!(cursor.lookback(), None);
279        cursor.next();
280        assert_eq!(cursor.lookback(), Some((0, 'a')));
281        cursor.next();
282        assert_eq!(cursor.lookback(), Some((1, 'b')));
283    }
284
285    #[test]
286    fn test_bidirectional_movement() {
287        let mut cursor = Cursor::new("hello");
288
289        assert_eq!(cursor.next_char(), Some('h'));
290        assert_eq!(cursor.next_char(), Some('e'));
291        assert_eq!(cursor.prev_char(), Some('e'));
292        assert_eq!(cursor.prev_char(), Some('h'));
293        assert_eq!(cursor.next_char(), Some('h'));
294    }
295
296    #[test]
297    fn test_unicode() {
298        let mut cursor = Cursor::new("hello 👋 world");
299
300        // Advance to emoji
301        for _ in 0..6 {
302            cursor.next();
303        }
304
305        assert_eq!(cursor.next_char(), Some('👋'));
306        assert_eq!(cursor.prev_char(), Some('👋'));
307    }
308
309    #[test]
310    fn test_iterator_implementation() {
311        let cursor = Cursor::new("abc");
312        let collected: Vec<(usize, char)> = cursor.collect();
313
314        assert_eq!(collected, vec![(0, 'a'), (1, 'b'), (2, 'c'),]);
315    }
316
317    #[test]
318    fn test_mixed_operations() {
319        let mut cursor = Cursor::new("test");
320
321        assert_eq!(cursor.next_char(), Some('t'));
322        assert_eq!(cursor.peek_char(), Some('e'));
323        assert_eq!(cursor.prev_char(), Some('t'));
324        assert_eq!(cursor.lookback_char(), None);
325        assert_eq!(cursor.next_char(), Some('t'));
326        assert_eq!(cursor.next_char(), Some('e'));
327        assert_eq!(cursor.lookback_char(), Some('e'));
328    }
329}