1enum Side {
19 Left,
20 Right,
21}
22
23#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub enum InputRequest {
29 SetCursor(usize),
30 InsertChar(char),
31 GoToPrevChar,
32 GoToNextChar,
33 GoToPrevWord,
34 GoToNextWord,
35 GoToStart,
36 GoToEnd,
37 DeletePrevChar,
38 DeleteNextChar,
39 DeletePrevWord,
40 DeleteNextWord,
41 DeleteLine,
42 DeleteTillEnd,
43 Yank,
44}
45
46#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48pub struct StateChanged {
49 pub value: bool,
50 pub cursor: bool,
51}
52
53pub type InputResponse = Option<StateChanged>;
54
55#[derive(Default, Debug, Clone)]
68#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
69pub struct Input {
70 value: String,
71 cursor: usize,
72 yank: String,
73 last_was_cut: bool,
74}
75
76impl Input {
77 pub fn new(value: String) -> Self {
80 let len = value.chars().count();
81 Self {
82 value,
83 cursor: len,
84 yank: String::new(),
85 last_was_cut: false,
86 }
87 }
88
89 pub fn with_value(mut self, value: String) -> Self {
92 self.cursor = value.chars().count();
93 self.value = value;
94 self
95 }
96
97 pub fn with_cursor(mut self, cursor: usize) -> Self {
100 self.cursor = cursor.min(self.value.chars().count());
101 self
102 }
103
104 pub fn reset(&mut self) {
106 self.cursor = Default::default();
107 self.value = Default::default();
108 }
109
110 pub fn value_and_reset(&mut self) -> String {
112 let val = self.value.clone();
113 self.reset();
114 val
115 }
116
117 fn add_to_yank(&mut self, deleted: String, side: Side) {
118 if self.last_was_cut {
119 match side {
120 Side::Left => self.yank.insert_str(0, &deleted),
121 Side::Right => self.yank.push_str(&deleted),
122 }
123 } else {
124 self.yank = deleted;
125 }
126 }
127
128 fn set_last_was_cut(&mut self, req: InputRequest) {
129 use InputRequest::*;
130 self.last_was_cut = matches!(
131 req,
132 DeleteLine | DeletePrevWord | DeleteNextWord | DeleteTillEnd
133 );
134 }
135
136 pub fn handle(&mut self, req: InputRequest) -> InputResponse {
138 use InputRequest::*;
139 let result = match req {
140 SetCursor(pos) => {
141 let pos = pos.min(self.value.chars().count());
142 if self.cursor == pos {
143 None
144 } else {
145 self.cursor = pos;
146 Some(StateChanged {
147 value: false,
148 cursor: true,
149 })
150 }
151 }
152 InsertChar(c) => {
153 if self.cursor == self.value.chars().count() {
154 self.value.push(c);
155 } else {
156 self.value = self
157 .value
158 .chars()
159 .take(self.cursor)
160 .chain(
161 std::iter::once(c)
162 .chain(self.value.chars().skip(self.cursor)),
163 )
164 .collect();
165 }
166 self.cursor += 1;
167 Some(StateChanged {
168 value: true,
169 cursor: true,
170 })
171 }
172
173 DeletePrevChar => {
174 if self.cursor == 0 {
175 None
176 } else {
177 self.cursor -= 1;
178 self.value = self
179 .value
180 .chars()
181 .enumerate()
182 .filter(|(i, _)| i != &self.cursor)
183 .map(|(_, c)| c)
184 .collect();
185
186 Some(StateChanged {
187 value: true,
188 cursor: true,
189 })
190 }
191 }
192
193 DeleteNextChar => {
194 if self.cursor == self.value.chars().count() {
195 None
196 } else {
197 self.value = self
198 .value
199 .chars()
200 .enumerate()
201 .filter(|(i, _)| i != &self.cursor)
202 .map(|(_, c)| c)
203 .collect();
204 Some(StateChanged {
205 value: true,
206 cursor: false,
207 })
208 }
209 }
210
211 GoToPrevChar => {
212 if self.cursor == 0 {
213 None
214 } else {
215 self.cursor -= 1;
216 Some(StateChanged {
217 value: false,
218 cursor: true,
219 })
220 }
221 }
222
223 GoToPrevWord => {
224 if self.cursor == 0 {
225 None
226 } else {
227 self.cursor = self
228 .value
229 .chars()
230 .rev()
231 .skip(self.value.chars().count().max(self.cursor) - self.cursor)
232 .skip_while(|c| !c.is_alphanumeric())
233 .skip_while(|c| c.is_alphanumeric())
234 .count();
235 Some(StateChanged {
236 value: false,
237 cursor: true,
238 })
239 }
240 }
241
242 GoToNextChar => {
243 if self.cursor == self.value.chars().count() {
244 None
245 } else {
246 self.cursor += 1;
247 Some(StateChanged {
248 value: false,
249 cursor: true,
250 })
251 }
252 }
253
254 GoToNextWord => {
255 if self.cursor == self.value.chars().count() {
256 None
257 } else {
258 self.cursor = self
259 .value
260 .chars()
261 .enumerate()
262 .skip(self.cursor)
263 .skip_while(|(_, c)| c.is_alphanumeric())
264 .find(|(_, c)| c.is_alphanumeric())
265 .map(|(i, _)| i)
266 .unwrap_or_else(|| self.value.chars().count());
267
268 Some(StateChanged {
269 value: false,
270 cursor: true,
271 })
272 }
273 }
274
275 DeleteLine => {
276 if self.value.is_empty() {
277 None
278 } else {
279 let side = if self.cursor == self.value.chars().count() {
280 Side::Left
281 } else {
282 Side::Right
283 };
284 self.add_to_yank(self.value.clone(), side);
285 self.value = "".into();
286 self.cursor = 0;
287 Some(StateChanged {
288 value: true,
289 cursor: true,
290 })
291 }
292 }
293
294 DeletePrevWord => {
295 if self.cursor == 0 {
296 None
297 } else {
298 let rev = self
299 .value
300 .chars()
301 .rev()
302 .skip(self.value.chars().count().max(self.cursor) - self.cursor)
303 .skip_while(|c| !c.is_alphanumeric())
304 .skip_while(|c| c.is_alphanumeric())
305 .collect::<Vec<char>>();
306 let rev_len = rev.len();
307 let deleted: String = self
308 .value
309 .chars()
310 .skip(rev_len)
311 .take(self.cursor - rev_len)
312 .collect();
313 self.add_to_yank(deleted, Side::Left);
314 let remaining = self.value.chars().skip(self.cursor);
315 self.value = rev.into_iter().rev().chain(remaining).collect();
316 self.cursor = rev_len;
317 Some(StateChanged {
318 value: true,
319 cursor: true,
320 })
321 }
322 }
323
324 DeleteNextWord => {
325 if self.cursor == self.value.chars().count() {
326 None
327 } else {
328 let after: Vec<_> = self
329 .value
330 .chars()
331 .skip(self.cursor)
332 .skip_while(|c| c.is_alphanumeric())
333 .skip_while(|c| !c.is_alphanumeric())
334 .collect();
335 let deleted_count =
336 self.value.chars().count() - self.cursor - after.len();
337 let deleted: String = self
338 .value
339 .chars()
340 .skip(self.cursor)
341 .take(deleted_count)
342 .collect();
343 self.add_to_yank(deleted, Side::Right);
344 self.value =
345 self.value.chars().take(self.cursor).chain(after).collect();
346
347 Some(StateChanged {
348 value: true,
349 cursor: false,
350 })
351 }
352 }
353
354 GoToStart => {
355 if self.cursor == 0 {
356 None
357 } else {
358 self.cursor = 0;
359 Some(StateChanged {
360 value: false,
361 cursor: true,
362 })
363 }
364 }
365
366 GoToEnd => {
367 let count = self.value.chars().count();
368 if self.cursor == count {
369 None
370 } else {
371 self.cursor = count;
372 Some(StateChanged {
373 value: false,
374 cursor: true,
375 })
376 }
377 }
378
379 DeleteTillEnd => {
380 let deleted: String = self.value.chars().skip(self.cursor).collect();
381 self.add_to_yank(deleted, Side::Right);
382 self.value = self.value.chars().take(self.cursor).collect();
383 Some(StateChanged {
384 value: true,
385 cursor: false,
386 })
387 }
388
389 Yank => {
390 if self.yank.is_empty() {
391 None
392 } else if self.cursor == self.value.chars().count() {
393 self.value.push_str(&self.yank);
394 self.cursor += self.yank.chars().count();
395 Some(StateChanged {
396 value: true,
397 cursor: true,
398 })
399 } else {
400 self.value = self
401 .value
402 .chars()
403 .take(self.cursor)
404 .chain(self.yank.chars())
405 .chain(self.value.chars().skip(self.cursor))
406 .collect();
407 self.cursor += self.yank.chars().count();
408 Some(StateChanged {
409 value: true,
410 cursor: true,
411 })
412 }
413 }
414 };
415 self.set_last_was_cut(req);
416 result
417 }
418
419 pub fn value(&self) -> &str {
421 self.value.as_str()
422 }
423
424 pub fn cursor(&self) -> usize {
426 self.cursor
427 }
428
429 pub fn visual_cursor(&self) -> usize {
431 if self.cursor == 0 {
432 return 0;
433 }
434
435 unicode_width::UnicodeWidthStr::width(unsafe {
437 self.value.get_unchecked(
438 0..self
439 .value
440 .char_indices()
441 .nth(self.cursor)
442 .map_or_else(|| self.value.len(), |(index, _)| index),
443 )
444 })
445 }
446
447 pub fn visual_scroll(&self, width: usize) -> usize {
449 let scroll = (self.visual_cursor()).max(width) - width;
450 let mut uscroll = 0;
451 let mut chars = self.value().chars();
452
453 while uscroll < scroll {
454 match chars.next() {
455 Some(c) => {
456 uscroll += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
457 }
458 None => break,
459 }
460 }
461 uscroll
462 }
463}
464
465impl From<Input> for String {
466 fn from(input: Input) -> Self {
467 input.value
468 }
469}
470
471impl From<String> for Input {
472 fn from(value: String) -> Self {
473 Self::new(value)
474 }
475}
476
477impl From<&str> for Input {
478 fn from(value: &str) -> Self {
479 Self::new(value.into())
480 }
481}
482
483impl std::fmt::Display for Input {
484 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
485 self.value.fmt(f)
486 }
487}
488
489#[cfg(test)]
490mod tests {
491
492 const TEXT: &str = "first second, third.";
493
494 use super::*;
495
496 #[test]
497 fn format() {
498 let input: Input = TEXT.into();
499 println!("{}", input);
500 println!("{}", input);
501 }
502
503 #[test]
504 fn set_cursor() {
505 let mut input: Input = TEXT.into();
506
507 let req = InputRequest::SetCursor(3);
508 let resp = input.handle(req);
509
510 assert_eq!(
511 resp,
512 Some(StateChanged {
513 value: false,
514 cursor: true,
515 })
516 );
517
518 assert_eq!(input.value(), "first second, third.");
519 assert_eq!(input.cursor(), 3);
520
521 let req = InputRequest::SetCursor(30);
522 let resp = input.handle(req);
523
524 assert_eq!(input.cursor(), TEXT.chars().count());
525 assert_eq!(
526 resp,
527 Some(StateChanged {
528 value: false,
529 cursor: true,
530 })
531 );
532
533 let req = InputRequest::SetCursor(TEXT.chars().count());
534 let resp = input.handle(req);
535
536 assert_eq!(input.cursor(), TEXT.chars().count());
537 assert_eq!(resp, None);
538 }
539
540 #[test]
541 fn insert_char() {
542 let mut input: Input = TEXT.into();
543
544 let req = InputRequest::InsertChar('x');
545 let resp = input.handle(req);
546
547 assert_eq!(
548 resp,
549 Some(StateChanged {
550 value: true,
551 cursor: true,
552 })
553 );
554
555 assert_eq!(input.value(), "first second, third.x");
556 assert_eq!(input.cursor(), TEXT.chars().count() + 1);
557 input.handle(req);
558 assert_eq!(input.value(), "first second, third.xx");
559 assert_eq!(input.cursor(), TEXT.chars().count() + 2);
560
561 let mut input = input.with_cursor(3);
562 input.handle(req);
563 assert_eq!(input.value(), "firxst second, third.xx");
564 assert_eq!(input.cursor(), 4);
565
566 input.handle(req);
567 assert_eq!(input.value(), "firxxst second, third.xx");
568 assert_eq!(input.cursor(), 5);
569 }
570
571 #[test]
572 fn go_to_prev_char() {
573 let mut input: Input = TEXT.into();
574
575 let req = InputRequest::GoToPrevChar;
576 let resp = input.handle(req);
577
578 assert_eq!(
579 resp,
580 Some(StateChanged {
581 value: false,
582 cursor: true,
583 })
584 );
585
586 assert_eq!(input.value(), "first second, third.");
587 assert_eq!(input.cursor(), TEXT.chars().count() - 1);
588
589 let mut input = input.with_cursor(3);
590 input.handle(req);
591 assert_eq!(input.value(), "first second, third.");
592 assert_eq!(input.cursor(), 2);
593
594 input.handle(req);
595 assert_eq!(input.value(), "first second, third.");
596 assert_eq!(input.cursor(), 1);
597 }
598
599 #[test]
600 fn remove_unicode_chars() {
601 let mut input: Input = "¡test¡".into();
602
603 let req = InputRequest::DeletePrevChar;
604 let resp = input.handle(req);
605
606 assert_eq!(
607 resp,
608 Some(StateChanged {
609 value: true,
610 cursor: true,
611 })
612 );
613
614 assert_eq!(input.value(), "¡test");
615 assert_eq!(input.cursor(), 5);
616
617 input.handle(InputRequest::GoToStart);
618
619 let req = InputRequest::DeleteNextChar;
620 let resp = input.handle(req);
621
622 assert_eq!(
623 resp,
624 Some(StateChanged {
625 value: true,
626 cursor: false,
627 })
628 );
629
630 assert_eq!(input.value(), "test");
631 assert_eq!(input.cursor(), 0);
632 }
633
634 #[test]
635 fn insert_unicode_chars() {
636 let mut input = Input::from("¡test¡").with_cursor(5);
637
638 let req = InputRequest::InsertChar('☆');
639 let resp = input.handle(req);
640
641 assert_eq!(
642 resp,
643 Some(StateChanged {
644 value: true,
645 cursor: true,
646 })
647 );
648
649 assert_eq!(input.value(), "¡test☆¡");
650 assert_eq!(input.cursor(), 6);
651
652 input.handle(InputRequest::GoToStart);
653 input.handle(InputRequest::GoToNextChar);
654
655 let req = InputRequest::InsertChar('☆');
656 let resp = input.handle(req);
657
658 assert_eq!(
659 resp,
660 Some(StateChanged {
661 value: true,
662 cursor: true,
663 })
664 );
665
666 assert_eq!(input.value(), "¡☆test☆¡");
667 assert_eq!(input.cursor(), 2);
668 }
669
670 #[test]
671 fn multispace_characters() {
672 let input: Input = "Hello, world!".into();
673 assert_eq!(input.cursor(), 13);
674 assert_eq!(input.visual_cursor(), 23);
675 assert_eq!(input.visual_scroll(6), 18);
676 }
677
678 #[test]
679 fn yank_delete_line() {
680 let mut input: Input = TEXT.into();
681 input.handle(InputRequest::DeleteLine);
682 assert_eq!(input.value(), "");
683 assert_eq!(input.cursor(), 0);
684 assert_eq!(input.yank, TEXT);
685
686 input.handle(InputRequest::Yank);
687 assert_eq!(input.value(), TEXT);
688 assert_eq!(input.cursor(), TEXT.chars().count());
689 assert_eq!(input.yank, TEXT);
690 }
691
692 #[test]
693 fn yank_delete_till_end() {
694 let mut input = Input::from(TEXT).with_cursor(6);
695 input.handle(InputRequest::DeleteTillEnd);
696 assert_eq!(input.value(), "first ");
697 assert_eq!(input.cursor(), 6);
698 assert_eq!(input.yank, "second, third.");
699
700 input.handle(InputRequest::Yank);
701 assert_eq!(input.value(), "first second, third.");
702 assert_eq!(input.cursor(), TEXT.chars().count());
703 assert_eq!(input.yank, "second, third.");
704 }
705
706 #[test]
707 fn yank_delete_prev_word() {
708 let mut input = Input::from(TEXT).with_cursor(12);
709 input.handle(InputRequest::DeletePrevWord);
710 assert_eq!(input.value(), "first , third.");
711 assert_eq!(input.yank, "second");
712
713 input.handle(InputRequest::Yank);
714 assert_eq!(input.value(), "first second, third.");
715 assert_eq!(input.yank, "second");
716 }
717
718 #[test]
719 fn yank_delete_next_word() {
720 let mut input = Input::from(TEXT).with_cursor(6);
721 input.handle(InputRequest::DeleteNextWord);
722 assert_eq!(input.value(), "first third.");
723 assert_eq!(input.yank, "second, ");
724
725 input.handle(InputRequest::Yank);
726 assert_eq!(input.value(), "first second, third.");
727 assert_eq!(input.yank, "second, ");
728 }
729
730 #[test]
731 fn yank_empty() {
732 let mut input: Input = TEXT.into();
733 let result = input.handle(InputRequest::Yank);
734 assert_eq!(result, None);
735 assert_eq!(input.value(), TEXT);
736 assert_eq!(input.yank, "");
737 }
738
739 #[test]
740 fn yank_at_middle() {
741 let mut input = Input::from(TEXT).with_cursor(6);
742 input.handle(InputRequest::DeleteTillEnd);
743 assert_eq!(input.value(), "first ");
744 assert_eq!(input.yank, "second, third.");
745 input.handle(InputRequest::GoToStart);
746 input.handle(InputRequest::Yank);
747 assert_eq!(input.value(), "second, third.first ");
748 assert_eq!(input.cursor(), 14);
749 assert_eq!(input.yank, "second, third.");
750 }
751
752 #[test]
753 fn yank_consecutive_delete_prev_word() {
754 let mut input = Input::from(TEXT).with_cursor(TEXT.chars().count());
755 input.handle(InputRequest::DeletePrevWord);
756 assert_eq!(input.value(), "first second, ");
757 assert_eq!(input.yank, "third.");
758 input.handle(InputRequest::DeletePrevWord);
759 assert_eq!(input.value(), "first ");
760 assert_eq!(input.yank, "second, third.");
761 input.handle(InputRequest::Yank);
762 assert_eq!(input.value(), "first second, third.");
763 }
764
765 #[test]
766 fn yank_consecutive_delete_next_word() {
767 let mut input = Input::from(TEXT).with_cursor(0);
768 input.handle(InputRequest::DeleteNextWord);
769 assert_eq!(input.value(), "second, third.");
770 assert_eq!(input.yank, "first ");
771 input.handle(InputRequest::DeleteNextWord);
772 assert_eq!(input.value(), "third.");
773 assert_eq!(input.yank, "first second, ");
774 input.handle(InputRequest::Yank);
775 assert_eq!(input.value(), "first second, third.");
776 }
777
778 #[test]
779 fn yank_insert_breaks_cut_sequence() {
780 let mut input = Input::from(TEXT).with_cursor(TEXT.chars().count());
781 input.handle(InputRequest::DeletePrevWord);
782 assert_eq!(input.yank, "third.");
783 input.handle(InputRequest::InsertChar('x'));
784 input.handle(InputRequest::DeletePrevChar);
785 input.handle(InputRequest::DeletePrevWord);
786 assert_eq!(input.yank, "second, ");
787 }
788
789 #[test]
790 fn yank_mixed_delete_word_and_line() {
791 let mut input = Input::from(TEXT).with_cursor(6);
792 input.handle(InputRequest::DeletePrevWord);
793 assert_eq!(input.value(), "second, third.");
794 assert_eq!(input.yank, "first ");
795 input.handle(InputRequest::DeleteLine);
796 assert_eq!(input.value(), "");
797 assert_eq!(input.yank, "first second, third.");
798 input.handle(InputRequest::Yank);
799 assert_eq!(input.value(), "first second, third.");
800 }
801
802 #[test]
803 fn yank_mixed_delete_word_and_line_from_end() {
804 let mut input = Input::from(TEXT).with_cursor(TEXT.chars().count());
805 input.handle(InputRequest::DeletePrevWord);
806 assert_eq!(input.value(), "first second, ");
807 assert_eq!(input.yank, "third.");
808 input.handle(InputRequest::DeleteLine);
809 assert_eq!(input.value(), "");
810 assert_eq!(input.yank, "first second, third.");
811 input.handle(InputRequest::Yank);
812 assert_eq!(input.value(), "first second, third.");
813 }
814}