1pub struct InputState {
2 buffer: String,
3 cursor_byte_offset: usize,
4}
5
6impl InputState {
7 pub fn new() -> Self {
8 Self {
9 buffer: String::new(),
10 cursor_byte_offset: 0,
11 }
12 }
13
14 pub fn insert_char(&mut self, c: char) {
15 self.buffer.insert(self.cursor_byte_offset, c);
16 self.cursor_byte_offset += c.len_utf8();
17 }
18
19 pub fn backspace(&mut self) {
20 if self.cursor_byte_offset > 0 {
21 let prev = self.buffer[..self.cursor_byte_offset]
22 .chars()
23 .next_back()
24 .unwrap();
25 self.cursor_byte_offset -= prev.len_utf8();
26 self.buffer.remove(self.cursor_byte_offset);
27 }
28 }
29
30 pub fn delete(&mut self) {
31 if self.cursor_byte_offset < self.buffer.len() {
32 self.buffer.remove(self.cursor_byte_offset);
33 }
34 }
35
36 pub fn move_left(&mut self) {
37 if self.cursor_byte_offset > 0 {
38 let prev = self.buffer[..self.cursor_byte_offset]
39 .chars()
40 .next_back()
41 .unwrap();
42 self.cursor_byte_offset -= prev.len_utf8();
43 }
44 }
45
46 pub fn move_right(&mut self) {
47 if self.cursor_byte_offset < self.buffer.len() {
48 let next = self.buffer[self.cursor_byte_offset..]
49 .chars()
50 .next()
51 .unwrap();
52 self.cursor_byte_offset += next.len_utf8();
53 }
54 }
55
56 pub fn move_home(&mut self) {
57 self.cursor_byte_offset = 0;
58 }
59
60 pub fn move_end(&mut self) {
61 self.cursor_byte_offset = self.buffer.len();
62 }
63
64 pub fn clear(&mut self) {
65 self.buffer.clear();
66 self.cursor_byte_offset = 0;
67 }
68
69 pub fn set(&mut self, text: String) {
70 self.buffer = text;
71 self.cursor_byte_offset = self.buffer.len();
72 }
73
74 pub fn is_empty(&self) -> bool {
75 self.buffer.is_empty()
76 }
77
78 pub fn as_str(&self) -> &str {
79 &self.buffer
80 }
81
82 pub fn cursor_byte_offset(&self) -> usize {
83 self.cursor_byte_offset
84 }
85}
86
87impl Default for InputState {
88 fn default() -> Self {
89 Self::new()
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn test_new_is_empty() {
99 let input = InputState::new();
100 assert!(input.is_empty());
101 assert_eq!(input.cursor_byte_offset(), 0);
102 assert_eq!(input.as_str(), "");
103 }
104
105 #[test]
106 fn test_insert_char_at_end() {
107 let mut input = InputState::new();
108 input.insert_char('a');
109 input.insert_char('b');
110 input.insert_char('c');
111 assert_eq!(input.as_str(), "abc");
112 assert_eq!(input.cursor_byte_offset(), 3);
113 }
114
115 #[test]
116 fn test_insert_char_at_beginning() {
117 let mut input = InputState::new();
118 input.insert_char('b');
119 input.move_home();
120 input.insert_char('a');
121 assert_eq!(input.as_str(), "ab");
122 assert_eq!(input.cursor_byte_offset(), 1);
123 }
124
125 #[test]
126 fn test_insert_char_at_middle() {
127 let mut input = InputState::new();
128 input.insert_char('a');
129 input.insert_char('c');
130 input.move_left();
131 input.insert_char('b');
132 assert_eq!(input.as_str(), "abc");
133 assert_eq!(input.cursor_byte_offset(), 2);
134 }
135
136 #[test]
137 fn test_backspace_at_start_is_noop() {
138 let mut input = InputState::new();
139 input.backspace();
140 assert_eq!(input.as_str(), "");
141 assert_eq!(input.cursor_byte_offset(), 0);
142
143 input.insert_char('a');
144 input.move_home();
145 input.backspace();
146 assert_eq!(input.as_str(), "a");
147 assert_eq!(input.cursor_byte_offset(), 0);
148 }
149
150 #[test]
151 fn test_backspace_removes_previous_char() {
152 let mut input = InputState::new();
153 input.insert_char('a');
154 input.insert_char('b');
155 input.insert_char('c');
156 input.move_left();
157 input.backspace();
158 assert_eq!(input.as_str(), "ac");
159 assert_eq!(input.cursor_byte_offset(), 1);
160 }
161
162 #[test]
163 fn test_backspace_at_end() {
164 let mut input = InputState::new();
165 input.insert_char('a');
166 input.insert_char('b');
167 input.backspace();
168 assert_eq!(input.as_str(), "a");
169 assert_eq!(input.cursor_byte_offset(), 1);
170 }
171
172 #[test]
173 fn test_delete_at_end_is_noop() {
174 let mut input = InputState::new();
175 input.delete();
176 assert_eq!(input.as_str(), "");
177
178 input.insert_char('a');
179 input.delete();
180 assert_eq!(input.as_str(), "a");
181 assert_eq!(input.cursor_byte_offset(), 1);
182 }
183
184 #[test]
185 fn test_delete_removes_char_at_cursor() {
186 let mut input = InputState::new();
187 input.insert_char('a');
188 input.insert_char('b');
189 input.insert_char('c');
190 input.move_home();
191 input.delete();
192 assert_eq!(input.as_str(), "bc");
193 assert_eq!(input.cursor_byte_offset(), 0);
194 }
195
196 #[test]
197 fn test_move_left_at_zero_is_noop() {
198 let input = InputState::new();
199 assert_eq!(input.cursor_byte_offset(), 0);
200
201 let mut input = InputState::new();
202 input.insert_char('a');
203 input.move_home();
204 input.move_left();
205 assert_eq!(input.cursor_byte_offset(), 0);
206 }
207
208 #[test]
209 fn test_move_left() {
210 let mut input = InputState::new();
211 input.insert_char('a');
212 input.insert_char('b');
213 input.move_left();
214 assert_eq!(input.cursor_byte_offset(), 1);
215 input.move_left();
216 assert_eq!(input.cursor_byte_offset(), 0);
217 }
218
219 #[test]
220 fn test_move_right_at_end_is_noop() {
221 let mut input = InputState::new();
222 input.move_right();
223 assert_eq!(input.cursor_byte_offset(), 0);
224
225 input.insert_char('a');
226 input.move_right();
227 assert_eq!(input.cursor_byte_offset(), 1);
228 }
229
230 #[test]
231 fn test_move_right() {
232 let mut input = InputState::new();
233 input.insert_char('a');
234 input.insert_char('b');
235 input.move_home();
236 input.move_right();
237 assert_eq!(input.cursor_byte_offset(), 1);
238 input.move_right();
239 assert_eq!(input.cursor_byte_offset(), 2);
240 }
241
242 #[test]
243 fn test_move_home() {
244 let mut input = InputState::new();
245 input.insert_char('a');
246 input.insert_char('b');
247 input.insert_char('c');
248 assert_eq!(input.cursor_byte_offset(), 3);
249 input.move_home();
250 assert_eq!(input.cursor_byte_offset(), 0);
251 }
252
253 #[test]
254 fn test_move_end() {
255 let mut input = InputState::new();
256 input.insert_char('a');
257 input.insert_char('b');
258 input.move_home();
259 assert_eq!(input.cursor_byte_offset(), 0);
260 input.move_end();
261 assert_eq!(input.cursor_byte_offset(), 2);
262 }
263
264 #[test]
265 fn test_clear() {
266 let mut input = InputState::new();
267 input.insert_char('a');
268 input.insert_char('b');
269 input.clear();
270 assert!(input.is_empty());
271 assert_eq!(input.cursor_byte_offset(), 0);
272 assert_eq!(input.as_str(), "");
273 }
274
275 #[test]
276 fn test_set() {
277 let mut input = InputState::new();
278 input.set("hello".to_string());
279 assert_eq!(input.as_str(), "hello");
280 assert_eq!(input.cursor_byte_offset(), 5);
281 }
282
283 #[test]
284 fn test_is_empty() {
285 let mut input = InputState::new();
286 assert!(input.is_empty());
287 input.insert_char('x');
288 assert!(!input.is_empty());
289 input.backspace();
290 assert!(input.is_empty());
291 }
292
293 #[test]
294 fn test_as_str() {
295 let mut input = InputState::new();
296 assert_eq!(input.as_str(), "");
297 input.set("test".to_string());
298 assert_eq!(input.as_str(), "test");
299 }
300
301 #[test]
304 fn test_insert_multibyte_char() {
305 let mut input = InputState::new();
306 input.insert_char('a');
307 input.insert_char('\u{00e9}'); input.insert_char('b');
309 assert_eq!(input.as_str(), "a\u{00e9}b");
310 assert_eq!(input.cursor_byte_offset(), 4); let mut input = InputState::new();
313 input.insert_char('\u{4e16}'); assert_eq!(input.cursor_byte_offset(), 3);
315
316 let mut input = InputState::new();
317 input.insert_char('\u{1f600}'); assert_eq!(input.cursor_byte_offset(), 4);
319 }
320
321 #[test]
322 fn test_backspace_multibyte() {
323 let mut input = InputState::new();
324 input.insert_char('a');
325 input.insert_char('\u{00e9}');
326 input.insert_char('b');
327 input.backspace();
328 assert_eq!(input.as_str(), "a\u{00e9}");
329 assert_eq!(input.cursor_byte_offset(), 3);
330 input.backspace();
331 assert_eq!(input.as_str(), "a");
332 assert_eq!(input.cursor_byte_offset(), 1);
333 }
334
335 #[test]
336 fn test_delete_multibyte() {
337 let mut input = InputState::new();
338 input.insert_char('a');
339 input.insert_char('\u{00e9}');
340 input.insert_char('b');
341 input.move_home();
342 input.move_right(); input.delete(); assert_eq!(input.as_str(), "ab");
345 assert_eq!(input.cursor_byte_offset(), 1);
346 }
347
348 #[test]
349 fn test_move_left_multibyte() {
350 let mut input = InputState::new();
351 input.insert_char('a');
352 input.insert_char('\u{00e9}'); input.insert_char('b');
354 input.move_left(); assert_eq!(input.cursor_byte_offset(), 3);
357 input.move_left(); assert_eq!(input.cursor_byte_offset(), 1);
359 input.move_left(); assert_eq!(input.cursor_byte_offset(), 0);
361 }
362
363 #[test]
364 fn test_move_right_multibyte() {
365 let mut input = InputState::new();
366 input.insert_char('a');
367 input.insert_char('\u{00e9}'); input.insert_char('b');
369 input.move_home();
370 input.move_right(); assert_eq!(input.cursor_byte_offset(), 1);
372 input.move_right(); assert_eq!(input.cursor_byte_offset(), 3);
374 input.move_right(); assert_eq!(input.cursor_byte_offset(), 4);
376 }
377
378 #[test]
379 fn test_mixed_ascii_and_multibyte() {
380 let mut input = InputState::new();
381 input.insert_char('h');
383 input.insert_char('l');
384 input.insert_char('l');
385 input.insert_char('o');
386 input.move_home();
388 input.move_right();
389 input.insert_char('\u{00e9}');
390 assert_eq!(input.as_str(), "h\u{00e9}llo");
391 input.move_end();
393 input.insert_char('\u{1f600}');
394 assert_eq!(input.as_str(), "h\u{00e9}llo\u{1f600}");
395 input.move_home();
397 input.move_right(); input.delete(); assert_eq!(input.as_str(), "hllo\u{1f600}");
400 input.move_end();
402 input.backspace();
403 assert_eq!(input.as_str(), "hllo");
404 }
405}