tui_input/
input.rs

1/// Input requests are used to change the input state.
2///
3/// Different backends can be used to convert events into requests.
4#[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/// The input buffer with cursor support.
33///
34/// Example:
35///
36/// ```
37/// use tui_input::Input;
38///
39/// let input: Input = "Hello World".into();
40///
41/// assert_eq!(input.cursor(), 11);
42/// assert_eq!(input.to_string(), "Hello World");
43/// ```
44#[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    /// Initialize a new instance with a given value
53    /// Cursor will be set to the given value's length.
54    pub fn new(value: String) -> Self {
55        let len = value.chars().count();
56        Self { value, cursor: len }
57    }
58
59    /// Set the value manually.
60    /// Cursor will be set to the given value's length.
61    pub fn with_value(mut self, value: String) -> Self {
62        self.cursor = value.chars().count();
63        self.value = value;
64        self
65    }
66
67    /// Set the cursor manually.
68    /// If the input is larger than the value length, it'll be auto adjusted.
69    pub fn with_cursor(mut self, cursor: usize) -> Self {
70        self.cursor = cursor.min(self.value.chars().count());
71        self
72    }
73
74    // Reset the cursor and value to default
75    pub fn reset(&mut self) {
76        self.cursor = Default::default();
77        self.value = Default::default();
78    }
79
80    /// Handle request and emit response.
81    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    /// Get a reference to the current value.
316    pub fn value(&self) -> &str {
317        self.value.as_str()
318    }
319
320    /// Get the currect cursor placement.
321    pub fn cursor(&self) -> usize {
322        self.cursor
323    }
324
325    /// Get the current cursor position with account for multispace characters.
326    pub fn visual_cursor(&self) -> usize {
327        if self.cursor == 0 {
328            return 0;
329        }
330
331        // Safe, because the end index will always be within bounds
332        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    /// Get the scroll position with account for multispace characters.
344    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}