tui_input/
input.rs

1//! Core logic for handling input.
2//!
3//! # Example: Without any backend
4//!
5//! ```
6//! use tui_input::{Input, InputRequest, StateChanged};
7//!
8//! let mut input: Input = "Hello Worl".into();
9//!
10//! let req = InputRequest::InsertChar('d');
11//! let resp = input.handle(req);
12//!
13//! assert_eq!(resp, Some(StateChanged { value: true, cursor: true }));
14//! assert_eq!(input.cursor(), 11);
15//! assert_eq!(input.to_string(), "Hello World");
16//! ```
17
18enum Side {
19    Left,
20    Right,
21}
22
23/// Input requests are used to change the input state.
24///
25/// Different backends can be used to convert events into requests.
26#[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/// The input buffer with cursor support.
56///
57/// Example:
58///
59/// ```
60/// use tui_input::Input;
61///
62/// let input: Input = "Hello World".into();
63///
64/// assert_eq!(input.cursor(), 11);
65/// assert_eq!(input.to_string(), "Hello World");
66/// ```
67#[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    /// Initialize a new instance with a given value
78    /// Cursor will be set to the given value's length.
79    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    /// Set the value manually.
90    /// Cursor will be set to the given value's length.
91    pub fn with_value(mut self, value: String) -> Self {
92        self.cursor = value.chars().count();
93        self.value = value;
94        self
95    }
96
97    /// Set the cursor manually.
98    /// If the input is larger than the value length, it'll be auto adjusted.
99    pub fn with_cursor(mut self, cursor: usize) -> Self {
100        self.cursor = cursor.min(self.value.chars().count());
101        self
102    }
103
104    // Reset the cursor and value to default
105    pub fn reset(&mut self) {
106        self.cursor = Default::default();
107        self.value = Default::default();
108    }
109
110    // Reset the cursor and value to default, returning the previous value
111    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    /// Handle request and emit response.
137    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    /// Get a reference to the current value.
420    pub fn value(&self) -> &str {
421        self.value.as_str()
422    }
423
424    /// Get the current cursor placement.
425    pub fn cursor(&self) -> usize {
426        self.cursor
427    }
428
429    /// Get the current cursor position with account for multispace characters.
430    pub fn visual_cursor(&self) -> usize {
431        if self.cursor == 0 {
432            return 0;
433        }
434
435        // Safe, because the end index will always be within bounds
436        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    /// Get the scroll position with account for multispace characters.
448    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}