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