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 fn skip_whitespace(&mut self) {
65 while let Some((_, c)) = self.peek() {
66 if !c.is_whitespace() {
67 break;
68 }
69 self.next();
70 }
71 }
72
73 pub fn prev(&mut self) -> Option<(usize, char)> {
74 let mut backward = self.backward();
75
76 let last_byte_len = backward.as_str().as_bytes().len();
77 let (pos, c) = backward.next_back()?;
78 let cur_byte_len = backward.as_str().as_bytes().len();
79 self.offset -= last_byte_len - cur_byte_len;
80
81 self.forward = self.data[self.offset..].char_indices();
82
83 if is_newline(c) {
84 self.line -= 1;
85 }
86
87 Some((pos, c))
88 }
89
90 pub fn prev_char(&mut self) -> Option<char> {
91 self.prev().map(|(_, c)| c)
92 }
93
94 pub fn peek(&self) -> Option<(usize, char)> {
95 self.forward.clone().next()
96 }
97
98 pub fn peek_char(&self) -> Option<char> {
99 self.peek().map(|(_, c)| c)
100 }
101
102 pub fn lookback(&self) -> Option<(usize, char)> {
103 self.backward().next_back()
104 }
105
106 pub fn lookback_char(&self) -> Option<char> {
107 self.lookback().map(|(_, c)| c)
108 }
109
110 pub const fn line(&self) -> usize {
111 self.line
112 }
113
114 pub const fn words(&mut self) -> CursorWords<'a, '_> {
115 CursorWords::new(self)
116 }
117
118 pub const fn words_with_lines(&mut self) -> CursorWords<'a, '_, true> {
119 CursorWords::with_lines(self)
120 }
121
122 pub const fn lines(&mut self) -> CursorLines<'a, '_> {
123 CursorLines::new(self)
124 }
125}
126
127impl Iterator for Cursor<'_> {
128 type Item = (usize, char);
129
130 fn next(&mut self) -> Option<Self::Item> {
131 let last_byte_len = self.forward.as_str().as_bytes().len();
132 let (pos, c) = self.forward.next()?;
133 let cur_byte_len = self.forward.as_str().as_bytes().len();
134 self.offset += last_byte_len - cur_byte_len;
135
136 if is_newline(c) {
137 self.line += 1;
138 }
139
140 Some((pos, c))
141 }
142}
143
144pub struct CursorWords<'a, 'b, const LINES: bool = false> {
145 cursor: &'b mut Cursor<'a>,
146}
147
148impl<'a, 'b> CursorWords<'a, 'b> {
149 pub const fn new(cursor: &'b mut Cursor<'a>) -> Self {
150 Self { cursor }
151 }
152}
153
154impl<'a, 'b> CursorWords<'a, 'b, true> {
155 pub const fn with_lines(cursor: &'b mut Cursor<'a>) -> Self {
156 Self { cursor }
157 }
158}
159
160impl<'a> Iterator for CursorWords<'a, '_, false> {
161 type Item = (usize, &'a str);
162
163 fn next(&mut self) -> Option<Self::Item> {
164 let ret = self.cursor.next_word()?;
165 self.cursor.skip_whitespace();
166 Some(ret)
167 }
168}
169
170impl<'a> Iterator for CursorWords<'a, '_, true> {
171 type Item = (usize, usize, &'a str);
172
173 fn next(&mut self) -> Option<Self::Item> {
174 let line = self.cursor.line();
175 let (offset, word) = self.cursor.next_word()?;
176 self.cursor.skip_whitespace();
177 Some((offset, line, word))
178 }
179}
180
181pub struct CursorLines<'a, 'b> {
182 cursor: &'b mut Cursor<'a>,
183}
184
185impl<'a, 'b> CursorLines<'a, 'b> {
186 pub const fn new(cursor: &'b mut Cursor<'a>) -> Self {
187 Self { cursor }
188 }
189}
190
191impl<'a> Iterator for CursorLines<'a, '_> {
192 type Item = (usize, usize, &'a str);
193
194 fn next(&mut self) -> Option<Self::Item> {
195 let line = self.cursor.line();
196 let (off, ret) = self.cursor.next_line()?;
197 Some((off, line, ret))
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn test_new_cursor() {
207 let cursor = Cursor::new("hello");
208 assert_eq!(cursor.peek_char(), Some('h'));
209 assert_eq!(cursor.lookback_char(), None);
210 }
211
212 #[test]
213 fn test_empty_string() {
214 let mut cursor = Cursor::new("");
215 assert_eq!(cursor.next_char(), None);
216 assert_eq!(cursor.prev_char(), None);
217 assert_eq!(cursor.peek_char(), None);
218 assert_eq!(cursor.lookback_char(), None);
219 }
220
221 #[test]
222 fn test_advance() {
223 let mut cursor = Cursor::new("abc");
224
225 assert_eq!(cursor.next(), Some((0, 'a')));
226 assert_eq!(cursor.next(), Some((1, 'b')));
227 assert_eq!(cursor.next(), Some((2, 'c')));
228 assert_eq!(cursor.next(), None);
229 }
230
231 #[test]
232 fn test_prev() {
233 let mut cursor = Cursor::new("abc");
234
235 cursor.next();
237 cursor.next();
238 cursor.next();
239
240 assert_eq!(cursor.prev(), Some((2, 'c')));
241 assert_eq!(cursor.prev(), Some((1, 'b')));
242 assert_eq!(cursor.prev(), Some((0, 'a')));
243 assert_eq!(cursor.prev(), None);
244 }
245
246 #[test]
247 fn test_peek() {
248 let mut cursor = Cursor::new("abc");
249
250 assert_eq!(cursor.peek(), Some((0, 'a')));
251 cursor.next();
252 assert_eq!(cursor.peek(), Some((1, 'b')));
253 cursor.next();
254 assert_eq!(cursor.peek(), Some((2, 'c')));
255 cursor.next();
256 assert_eq!(cursor.peek(), None);
257 }
258
259 #[test]
260 fn test_lookback() {
261 let mut cursor = Cursor::new("abc");
262
263 assert_eq!(cursor.lookback(), None);
264 cursor.next();
265 assert_eq!(cursor.lookback(), Some((0, 'a')));
266 cursor.next();
267 assert_eq!(cursor.lookback(), Some((1, 'b')));
268 }
269
270 #[test]
271 fn test_bidirectional_movement() {
272 let mut cursor = Cursor::new("hello");
273
274 assert_eq!(cursor.next_char(), Some('h'));
275 assert_eq!(cursor.next_char(), Some('e'));
276 assert_eq!(cursor.prev_char(), Some('e'));
277 assert_eq!(cursor.prev_char(), Some('h'));
278 assert_eq!(cursor.next_char(), Some('h'));
279 }
280
281 #[test]
282 fn test_unicode() {
283 let mut cursor = Cursor::new("hello 👋 world");
284
285 for _ in 0..6 {
287 cursor.next();
288 }
289
290 assert_eq!(cursor.next_char(), Some('👋'));
291 assert_eq!(cursor.prev_char(), Some('👋'));
292 }
293
294 #[test]
295 fn test_iterator_implementation() {
296 let cursor = Cursor::new("abc");
297 let collected: Vec<(usize, char)> = cursor.collect();
298
299 assert_eq!(collected, vec![(0, 'a'), (1, 'b'), (2, 'c'),]);
300 }
301
302 #[test]
303 fn test_mixed_operations() {
304 let mut cursor = Cursor::new("test");
305
306 assert_eq!(cursor.next_char(), Some('t'));
307 assert_eq!(cursor.peek_char(), Some('e'));
308 assert_eq!(cursor.prev_char(), Some('t'));
309 assert_eq!(cursor.lookback_char(), None);
310 assert_eq!(cursor.next_char(), Some('t'));
311 assert_eq!(cursor.next_char(), Some('e'));
312 assert_eq!(cursor.lookback_char(), Some('e'));
313 }
314}