1use std::str::CharIndices;
2
3fn is_newline(c: char) -> bool {
4 c == '\n'
5}
6
7#[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 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 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}