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