1#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub enum InputRequest {
7 SetCursor(usize),
8 InsertChar(char),
9 GoToPrevChar,
10 GoToNextChar,
11 GoToPrevWord,
12 GoToNextWord,
13 GoToStart,
14 GoToEnd,
15 DeletePrevChar,
16 DeleteNextChar,
17 DeletePrevWord,
18 DeleteNextWord,
19 DeleteLine,
20 DeleteTillEnd,
21}
22
23#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub struct StateChanged {
26 pub value: bool,
27 pub cursor: bool,
28}
29
30pub type InputResponse = Option<StateChanged>;
31
32#[derive(Default, Debug, Clone)]
45#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
46pub struct Input {
47 value: String,
48 cursor: usize,
49}
50
51impl Input {
52 pub fn new(value: String) -> Self {
55 let len = value.chars().count();
56 Self { value, cursor: len }
57 }
58
59 pub fn with_value(mut self, value: String) -> Self {
62 self.cursor = value.chars().count();
63 self.value = value;
64 self
65 }
66
67 pub fn with_cursor(mut self, cursor: usize) -> Self {
70 self.cursor = cursor.min(self.value.chars().count());
71 self
72 }
73
74 pub fn reset(&mut self) {
76 self.cursor = Default::default();
77 self.value = Default::default();
78 }
79
80 pub fn handle(&mut self, req: InputRequest) -> InputResponse {
82 use InputRequest::*;
83 match req {
84 SetCursor(pos) => {
85 let pos = pos.min(self.value.chars().count());
86 if self.cursor == pos {
87 None
88 } else {
89 self.cursor = pos;
90 Some(StateChanged {
91 value: false,
92 cursor: true,
93 })
94 }
95 }
96 InsertChar(c) => {
97 if self.cursor == self.value.chars().count() {
98 self.value.push(c);
99 } else {
100 self.value = self
101 .value
102 .chars()
103 .take(self.cursor)
104 .chain(
105 std::iter::once(c)
106 .chain(self.value.chars().skip(self.cursor)),
107 )
108 .collect();
109 }
110 self.cursor += 1;
111 Some(StateChanged {
112 value: true,
113 cursor: true,
114 })
115 }
116
117 DeletePrevChar => {
118 if self.cursor == 0 {
119 None
120 } else {
121 self.cursor -= 1;
122 self.value = self
123 .value
124 .chars()
125 .enumerate()
126 .filter(|(i, _)| i != &self.cursor)
127 .map(|(_, c)| c)
128 .collect();
129
130 Some(StateChanged {
131 value: true,
132 cursor: true,
133 })
134 }
135 }
136
137 DeleteNextChar => {
138 if self.cursor == self.value.chars().count() {
139 None
140 } else {
141 self.value = self
142 .value
143 .chars()
144 .enumerate()
145 .filter(|(i, _)| i != &self.cursor)
146 .map(|(_, c)| c)
147 .collect();
148 Some(StateChanged {
149 value: true,
150 cursor: false,
151 })
152 }
153 }
154
155 GoToPrevChar => {
156 if self.cursor == 0 {
157 None
158 } else {
159 self.cursor -= 1;
160 Some(StateChanged {
161 value: false,
162 cursor: true,
163 })
164 }
165 }
166
167 GoToPrevWord => {
168 if self.cursor == 0 {
169 None
170 } else {
171 self.cursor = self
172 .value
173 .chars()
174 .rev()
175 .skip(self.value.chars().count().max(self.cursor) - self.cursor)
176 .skip_while(|c| !c.is_alphanumeric())
177 .skip_while(|c| c.is_alphanumeric())
178 .count();
179 Some(StateChanged {
180 value: false,
181 cursor: true,
182 })
183 }
184 }
185
186 GoToNextChar => {
187 if self.cursor == self.value.chars().count() {
188 None
189 } else {
190 self.cursor += 1;
191 Some(StateChanged {
192 value: false,
193 cursor: true,
194 })
195 }
196 }
197
198 GoToNextWord => {
199 if self.cursor == self.value.chars().count() {
200 None
201 } else {
202 self.cursor = self
203 .value
204 .chars()
205 .enumerate()
206 .skip(self.cursor)
207 .skip_while(|(_, c)| c.is_alphanumeric())
208 .find(|(_, c)| c.is_alphanumeric())
209 .map(|(i, _)| i)
210 .unwrap_or_else(|| self.value.chars().count());
211
212 Some(StateChanged {
213 value: false,
214 cursor: true,
215 })
216 }
217 }
218
219 DeleteLine => {
220 if self.value.is_empty() {
221 None
222 } else {
223 let cursor = self.cursor;
224 self.value = "".into();
225 self.cursor = 0;
226 Some(StateChanged {
227 value: true,
228 cursor: self.cursor == cursor,
229 })
230 }
231 }
232
233 DeletePrevWord => {
234 if self.cursor == 0 {
235 None
236 } else {
237 let remaining = self.value.chars().skip(self.cursor);
238 let rev = self
239 .value
240 .chars()
241 .rev()
242 .skip(self.value.chars().count().max(self.cursor) - self.cursor)
243 .skip_while(|c| !c.is_alphanumeric())
244 .skip_while(|c| c.is_alphanumeric())
245 .collect::<Vec<char>>();
246 let rev_len = rev.len();
247 self.value = rev.into_iter().rev().chain(remaining).collect();
248 self.cursor = rev_len;
249 Some(StateChanged {
250 value: true,
251 cursor: true,
252 })
253 }
254 }
255
256 DeleteNextWord => {
257 if self.cursor == self.value.chars().count() {
258 None
259 } else {
260 self.value = self
261 .value
262 .chars()
263 .take(self.cursor)
264 .chain(
265 self.value
266 .chars()
267 .skip(self.cursor)
268 .skip_while(|c| c.is_alphanumeric())
269 .skip_while(|c| !c.is_alphanumeric()),
270 )
271 .collect();
272
273 Some(StateChanged {
274 value: true,
275 cursor: false,
276 })
277 }
278 }
279
280 GoToStart => {
281 if self.cursor == 0 {
282 None
283 } else {
284 self.cursor = 0;
285 Some(StateChanged {
286 value: false,
287 cursor: true,
288 })
289 }
290 }
291
292 GoToEnd => {
293 let count = self.value.chars().count();
294 if self.cursor == count {
295 None
296 } else {
297 self.cursor = count;
298 Some(StateChanged {
299 value: false,
300 cursor: true,
301 })
302 }
303 }
304
305 DeleteTillEnd => {
306 self.value = self.value.chars().take(self.cursor).collect();
307 Some(StateChanged {
308 value: true,
309 cursor: false,
310 })
311 }
312 }
313 }
314
315 pub fn value(&self) -> &str {
317 self.value.as_str()
318 }
319
320 pub fn cursor(&self) -> usize {
322 self.cursor
323 }
324
325 pub fn visual_cursor(&self) -> usize {
327 if self.cursor == 0 {
328 return 0;
329 }
330
331 unicode_width::UnicodeWidthStr::width(unsafe {
333 self.value.get_unchecked(
334 0..self
335 .value
336 .char_indices()
337 .nth(self.cursor)
338 .map_or_else(|| self.value.len(), |(index, _)| index),
339 )
340 })
341 }
342
343 pub fn visual_scroll(&self, width: usize) -> usize {
345 let scroll = (self.visual_cursor()).max(width) - width;
346 let mut uscroll = 0;
347 let mut chars = self.value().chars();
348
349 while uscroll < scroll {
350 match chars.next() {
351 Some(c) => {
352 uscroll += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
353 }
354 None => break,
355 }
356 }
357 uscroll
358 }
359}
360
361impl From<Input> for String {
362 fn from(input: Input) -> Self {
363 input.value
364 }
365}
366
367impl From<String> for Input {
368 fn from(value: String) -> Self {
369 Self::new(value)
370 }
371}
372
373impl From<&str> for Input {
374 fn from(value: &str) -> Self {
375 Self::new(value.into())
376 }
377}
378
379impl std::fmt::Display for Input {
380 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
381 self.value.fmt(f)
382 }
383}
384
385#[cfg(test)]
386mod tests {
387
388 const TEXT: &str = "first second, third.";
389
390 use super::*;
391
392 #[test]
393 fn format() {
394 let input: Input = TEXT.into();
395 println!("{}", input);
396 println!("{}", input);
397 }
398
399 #[test]
400 fn set_cursor() {
401 let mut input: Input = TEXT.into();
402
403 let req = InputRequest::SetCursor(3);
404 let resp = input.handle(req);
405
406 assert_eq!(
407 resp,
408 Some(StateChanged {
409 value: false,
410 cursor: true,
411 })
412 );
413
414 assert_eq!(input.value(), "first second, third.");
415 assert_eq!(input.cursor(), 3);
416
417 let req = InputRequest::SetCursor(30);
418 let resp = input.handle(req);
419
420 assert_eq!(input.cursor(), TEXT.chars().count());
421 assert_eq!(
422 resp,
423 Some(StateChanged {
424 value: false,
425 cursor: true,
426 })
427 );
428
429 let req = InputRequest::SetCursor(TEXT.chars().count());
430 let resp = input.handle(req);
431
432 assert_eq!(input.cursor(), TEXT.chars().count());
433 assert_eq!(resp, None);
434 }
435
436 #[test]
437 fn insert_char() {
438 let mut input: Input = TEXT.into();
439
440 let req = InputRequest::InsertChar('x');
441 let resp = input.handle(req);
442
443 assert_eq!(
444 resp,
445 Some(StateChanged {
446 value: true,
447 cursor: true,
448 })
449 );
450
451 assert_eq!(input.value(), "first second, third.x");
452 assert_eq!(input.cursor(), TEXT.chars().count() + 1);
453 input.handle(req);
454 assert_eq!(input.value(), "first second, third.xx");
455 assert_eq!(input.cursor(), TEXT.chars().count() + 2);
456
457 let mut input = input.with_cursor(3);
458 input.handle(req);
459 assert_eq!(input.value(), "firxst second, third.xx");
460 assert_eq!(input.cursor(), 4);
461
462 input.handle(req);
463 assert_eq!(input.value(), "firxxst second, third.xx");
464 assert_eq!(input.cursor(), 5);
465 }
466
467 #[test]
468 fn go_to_prev_char() {
469 let mut input: Input = TEXT.into();
470
471 let req = InputRequest::GoToPrevChar;
472 let resp = input.handle(req);
473
474 assert_eq!(
475 resp,
476 Some(StateChanged {
477 value: false,
478 cursor: true,
479 })
480 );
481
482 assert_eq!(input.value(), "first second, third.");
483 assert_eq!(input.cursor(), TEXT.chars().count() - 1);
484
485 let mut input = input.with_cursor(3);
486 input.handle(req);
487 assert_eq!(input.value(), "first second, third.");
488 assert_eq!(input.cursor(), 2);
489
490 input.handle(req);
491 assert_eq!(input.value(), "first second, third.");
492 assert_eq!(input.cursor(), 1);
493 }
494
495 #[test]
496 fn remove_unicode_chars() {
497 let mut input: Input = "¡test¡".into();
498
499 let req = InputRequest::DeletePrevChar;
500 let resp = input.handle(req);
501
502 assert_eq!(
503 resp,
504 Some(StateChanged {
505 value: true,
506 cursor: true,
507 })
508 );
509
510 assert_eq!(input.value(), "¡test");
511 assert_eq!(input.cursor(), 5);
512
513 input.handle(InputRequest::GoToStart);
514
515 let req = InputRequest::DeleteNextChar;
516 let resp = input.handle(req);
517
518 assert_eq!(
519 resp,
520 Some(StateChanged {
521 value: true,
522 cursor: false,
523 })
524 );
525
526 assert_eq!(input.value(), "test");
527 assert_eq!(input.cursor(), 0);
528 }
529
530 #[test]
531 fn insert_unicode_chars() {
532 let mut input = Input::from("¡test¡").with_cursor(5);
533
534 let req = InputRequest::InsertChar('☆');
535 let resp = input.handle(req);
536
537 assert_eq!(
538 resp,
539 Some(StateChanged {
540 value: true,
541 cursor: true,
542 })
543 );
544
545 assert_eq!(input.value(), "¡test☆¡");
546 assert_eq!(input.cursor(), 6);
547
548 input.handle(InputRequest::GoToStart);
549 input.handle(InputRequest::GoToNextChar);
550
551 let req = InputRequest::InsertChar('☆');
552 let resp = input.handle(req);
553
554 assert_eq!(
555 resp,
556 Some(StateChanged {
557 value: true,
558 cursor: true,
559 })
560 );
561
562 assert_eq!(input.value(), "¡☆test☆¡");
563 assert_eq!(input.cursor(), 2);
564 }
565
566 #[test]
567 fn multispace_characters() {
568 let input: Input = "Hello, world!".into();
569 assert_eq!(input.cursor(), 13);
570 assert_eq!(input.visual_cursor(), 23);
571 assert_eq!(input.visual_scroll(6), 18);
572 }
573}