Skip to main content

tui_input/
input.rs

1//! Core logic for handling input.
2//!
3//! # Units
4//!
5//! A string has four different possible notions of length or position:
6//!
7//! - **bytes**:  indices into the UTF-8 encoding, used only internally.
8//! - **codepoints**:  Unicode scalar values (what [`str::chars`] yields).
9//!   This is what [`Input::cursor`] returns and what
10//!   [`InputRequest::SetCursor`] accepts.
11//! - **graphemes**:  user-perceived characters (per `unicode-segmentation`).
12//!   Movement and deletion ([`InputRequest::GoToPrevChar`],
13//!   [`InputRequest::GoToNextChar`], [`InputRequest::DeletePrevChar`],
14//!   [`InputRequest::DeleteNextChar`], [`InputRequest::GoToPrevWord`],
15//!   [`InputRequest::GoToNextWord`], [`InputRequest::DeletePrevWord`],
16//!   [`InputRequest::DeleteNextWord`]) step one *grapheme* or *word*
17//!   at a time, which may span multiple codepoints.
18//! - **display columns**:  terminal cell width (per `unicode-width`).
19//!   Returned by [`Input::visual_cursor`] and [`Input::visual_scroll`].
20//!
21//! All four can differ for one string.  For example, `🤦🏼‍♂️` is
22//! actually `"🤦🏼\u{200D}♂\u{FE0F}"`, which is 17 bytes, 5 codepoints,
23//! 1 grapheme, 2 display columns.
24//!
25//! # Example: Without any backend
26//!
27//! ```
28//! use tui_input::{Input, InputRequest, StateChanged};
29//!
30//! let mut input: Input = "Hello Worl".into();
31//!
32//! let req = InputRequest::InsertChar('d');
33//! let resp = input.handle(req);
34//!
35//! assert_eq!(resp, Some(StateChanged { value: true, cursor: true }));
36//! assert_eq!(input.cursor(), 11);
37//! assert_eq!(input.to_string(), "Hello World");
38//! ```
39
40mod value;
41
42use unicode_segmentation::{GraphemeCursor, UnicodeSegmentation};
43
44use self::value::Value;
45
46fn prev_grapheme(s: &str, byte: usize) -> Option<usize> {
47    GraphemeCursor::new(byte, s.len(), true)
48        .prev_boundary(s, 0)
49        .ok()
50        .flatten()
51}
52
53fn next_grapheme(s: &str, byte: usize) -> Option<usize> {
54    GraphemeCursor::new(byte, s.len(), true)
55        .next_boundary(s, 0)
56        .ok()
57        .flatten()
58}
59
60fn is_word(s: &str) -> bool {
61    s.chars()
62        .any(|c| !c.is_whitespace() && !c.is_ascii_punctuation())
63}
64
65fn prev_word_byte(s: &str, byte: usize) -> usize {
66    let words = s
67        .split_word_bound_indices()
68        .filter(|(i, _)| *i < byte)
69        .rev();
70    for (i, word) in words {
71        if is_word(word) {
72            return i;
73        }
74    }
75    0
76}
77
78fn next_word_byte(s: &str, byte: usize) -> usize {
79    let words = s.split_word_bound_indices().filter(|(i, _)| *i > byte);
80    for (i, word) in words {
81        if is_word(word) {
82            return i;
83        }
84    }
85    s.len()
86}
87
88fn codepoint_to_byte(s: &str, n: usize) -> usize {
89    s.char_indices().nth(n).map_or(s.len(), |(i, _)| i)
90}
91
92fn byte_to_codepoint(s: &str, byte: usize) -> usize {
93    s[..byte].chars().count()
94}
95
96enum Side {
97    Left,
98    Right,
99}
100
101/// Input requests are used to change the input state.
102///
103/// Different backends can be used to convert events into requests.
104#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
106pub enum InputRequest {
107    SetCursor(usize),
108    InsertChar(char),
109    GoToPrevChar,
110    GoToNextChar,
111    GoToPrevWord,
112    GoToNextWord,
113    GoToStart,
114    GoToEnd,
115    DeletePrevChar,
116    DeleteNextChar,
117    DeletePrevWord,
118    DeleteNextWord,
119    DeleteLine,
120    DeleteTillEnd,
121    Yank,
122}
123
124#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
125#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
126pub struct StateChanged {
127    pub value: bool,
128    pub cursor: bool,
129}
130
131pub type InputResponse = Option<StateChanged>;
132
133/// The input buffer with cursor support.
134///
135/// Example:
136///
137/// ```
138/// use tui_input::Input;
139///
140/// let input: Input = "Hello World".into();
141///
142/// assert_eq!(input.cursor(), 11);
143/// assert_eq!(input.to_string(), "Hello World");
144/// ```
145#[derive(Default, Debug, Clone)]
146#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
147pub struct Input {
148    value: Value,
149    /// Codepoints preceding the cursor.  See the module-level `Units` section.
150    cursor: usize,
151    yank: Value,
152    last_was_cut: bool,
153}
154
155impl Input {
156    /// Initialize a new instance with a given value
157    /// Cursor will be set to the given value's length.
158    pub fn new(value: String) -> Self {
159        let value = Value::new(value);
160        let cursor = value.chars();
161        Self {
162            value,
163            cursor,
164            yank: Value::default(),
165            last_was_cut: false,
166        }
167    }
168
169    /// Set the value manually.
170    /// Cursor will be set to the given value's length.
171    pub fn with_value(mut self, value: String) -> Self {
172        self.value = Value::new(value);
173        self.cursor = self.value.chars();
174        self
175    }
176
177    /// Set the cursor manually.
178    /// If the input is larger than the value length, it'll be auto adjusted.
179    pub fn with_cursor(mut self, cursor: usize) -> Self {
180        self.cursor = cursor.min(self.value.chars());
181        self
182    }
183
184    // Reset the cursor and value to default
185    pub fn reset(&mut self) {
186        self.cursor = Default::default();
187        self.value = Default::default();
188    }
189
190    // Reset the cursor and value to default, returning the previous value
191    pub fn value_and_reset(&mut self) -> String {
192        let val = self.value.as_str().to_owned();
193        self.reset();
194        val
195    }
196
197    fn add_to_yank(&mut self, deleted: String, side: Side) {
198        if self.last_was_cut {
199            match side {
200                Side::Left => self.yank.edit().insert_str(0, &deleted),
201                Side::Right => self.yank.edit().push_str(&deleted),
202            }
203        } else {
204            self.yank = Value::new(deleted);
205        }
206    }
207
208    fn set_last_was_cut(&mut self, req: InputRequest) {
209        use InputRequest::*;
210        self.last_was_cut = matches!(
211            req,
212            DeleteLine | DeletePrevWord | DeleteNextWord | DeleteTillEnd
213        );
214    }
215
216    /// Handle request and emit response.
217    pub fn handle(&mut self, req: InputRequest) -> InputResponse {
218        use InputRequest::*;
219        let result = match req {
220            SetCursor(pos) => {
221                let pos = pos.min(self.value.chars());
222                if self.cursor == pos {
223                    None
224                } else {
225                    self.cursor = pos;
226                    Some(StateChanged {
227                        value: false,
228                        cursor: true,
229                    })
230                }
231            }
232            InsertChar(c) => {
233                let byte = codepoint_to_byte(self.value.as_str(), self.cursor);
234                self.value.edit().insert(byte, c);
235                self.cursor += 1;
236                Some(StateChanged {
237                    value: true,
238                    cursor: true,
239                })
240            }
241
242            DeletePrevChar => {
243                let s = self.value.as_str();
244                let byte = codepoint_to_byte(s, self.cursor);
245                let prev = prev_grapheme(s, byte)?;
246                let removed = s[prev..byte].chars().count();
247                self.value.edit().replace_range(prev..byte, "");
248                self.cursor -= removed;
249                Some(StateChanged {
250                    value: true,
251                    cursor: true,
252                })
253            }
254
255            DeleteNextChar => {
256                let s = self.value.as_str();
257                let byte = codepoint_to_byte(s, self.cursor);
258                let next = next_grapheme(s, byte)?;
259                self.value.edit().replace_range(byte..next, "");
260                Some(StateChanged {
261                    value: true,
262                    cursor: false,
263                })
264            }
265
266            GoToPrevChar => {
267                let s = self.value.as_str();
268                let byte = codepoint_to_byte(s, self.cursor);
269                let prev = prev_grapheme(s, byte)?;
270                self.cursor -= s[prev..byte].chars().count();
271                Some(StateChanged {
272                    value: false,
273                    cursor: true,
274                })
275            }
276
277            GoToPrevWord => {
278                let s = self.value.as_str();
279                let byte = codepoint_to_byte(s, self.cursor);
280                let prev = prev_word_byte(s, byte);
281                if self.cursor == 0 {
282                    None
283                } else {
284                    self.cursor = byte_to_codepoint(s, prev);
285                    Some(StateChanged {
286                        value: false,
287                        cursor: true,
288                    })
289                }
290            }
291
292            GoToNextChar => {
293                let s = self.value.as_str();
294                let byte = codepoint_to_byte(s, self.cursor);
295                let next = next_grapheme(s, byte)?;
296                self.cursor += s[byte..next].chars().count();
297                Some(StateChanged {
298                    value: false,
299                    cursor: true,
300                })
301            }
302
303            GoToNextWord => {
304                let s = self.value.as_str();
305                let byte = codepoint_to_byte(s, self.cursor);
306                let next = next_word_byte(s, byte);
307                if self.cursor == self.value.chars() {
308                    None
309                } else {
310                    self.cursor = byte_to_codepoint(s, next);
311                    Some(StateChanged {
312                        value: false,
313                        cursor: true,
314                    })
315                }
316            }
317
318            DeleteLine => {
319                if self.value.as_str().is_empty() {
320                    None
321                } else {
322                    let side = if self.cursor == self.value.chars() {
323                        Side::Left
324                    } else {
325                        Side::Right
326                    };
327                    self.add_to_yank(self.value.as_str().to_owned(), side);
328                    self.value.edit().clear();
329                    self.cursor = 0;
330                    Some(StateChanged {
331                        value: true,
332                        cursor: true,
333                    })
334                }
335            }
336
337            DeletePrevWord => {
338                if self.cursor == 0 {
339                    None
340                } else {
341                    let s = self.value.as_str();
342                    let byte = codepoint_to_byte(s, self.cursor);
343                    let prev = prev_word_byte(s, byte);
344                    let deleted = s[prev..byte].to_string();
345                    self.add_to_yank(deleted, Side::Left);
346                    self.value.edit().replace_range(prev..byte, "");
347                    self.cursor = byte_to_codepoint(self.value.as_str(), prev);
348                    Some(StateChanged {
349                        value: true,
350                        cursor: true,
351                    })
352                }
353            }
354
355            DeleteNextWord => {
356                let s = self.value.as_str();
357                let byte = codepoint_to_byte(s, self.cursor);
358                let next = next_word_byte(s, byte);
359                if self.cursor == self.value.chars() {
360                    None
361                } else {
362                    let deleted = s[byte..next].to_string();
363                    self.add_to_yank(deleted, Side::Right);
364                    self.value.edit().replace_range(byte..next, "");
365                    Some(StateChanged {
366                        value: true,
367                        cursor: false,
368                    })
369                }
370            }
371
372            GoToStart => {
373                if self.cursor == 0 {
374                    None
375                } else {
376                    self.cursor = 0;
377                    Some(StateChanged {
378                        value: false,
379                        cursor: true,
380                    })
381                }
382            }
383
384            GoToEnd => {
385                let count = self.value.chars();
386                if self.cursor == count {
387                    None
388                } else {
389                    self.cursor = count;
390                    Some(StateChanged {
391                        value: false,
392                        cursor: true,
393                    })
394                }
395            }
396
397            DeleteTillEnd => {
398                let byte = codepoint_to_byte(self.value.as_str(), self.cursor);
399                let deleted = self.value.as_str()[byte..].to_string();
400                self.add_to_yank(deleted, Side::Right);
401                self.value.edit().truncate(byte);
402                Some(StateChanged {
403                    value: true,
404                    cursor: false,
405                })
406            }
407
408            Yank => {
409                if self.yank.as_str().is_empty() {
410                    None
411                } else {
412                    let byte = codepoint_to_byte(self.value.as_str(), self.cursor);
413                    self.value.edit().insert_str(byte, self.yank.as_str());
414                    self.cursor += self.yank.chars();
415                    Some(StateChanged {
416                        value: true,
417                        cursor: true,
418                    })
419                }
420            }
421        };
422        self.set_last_was_cut(req);
423        result
424    }
425
426    /// Get a reference to the current value.
427    pub fn value(&self) -> &str {
428        self.value.as_str()
429    }
430
431    /// Returns the number of **codepoints** preceding the cursor.  Movement
432    /// and deletion operations step one *grapheme* at a time, so a single
433    /// [`InputRequest::GoToNextChar`] or [`InputRequest::DeletePrevChar`]
434    /// may change this count by more than one.
435    pub fn cursor(&self) -> usize {
436        self.cursor
437    }
438
439    /// Returns the cursor's position in **display columns** (per
440    /// `unicode-width`).
441    pub fn visual_cursor(&self) -> usize {
442        if self.cursor == 0 {
443            return 0;
444        }
445
446        let s = self.value.as_str();
447        // Safe, because the end index will always be within bounds
448        unicode_width::UnicodeWidthStr::width(unsafe {
449            s.get_unchecked(
450                0..s.char_indices()
451                    .nth(self.cursor)
452                    .map_or_else(|| s.len(), |(index, _)| index),
453            )
454        })
455    }
456
457    /// Get the scroll position with account for multispace characters.
458    pub fn visual_scroll(&self, width: usize) -> usize {
459        let scroll = (self.visual_cursor()).max(width) - width;
460        let mut uscroll = 0;
461        let mut chars = self.value().chars();
462
463        while uscroll < scroll {
464            match chars.next() {
465                Some(c) => {
466                    uscroll += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
467                }
468                None => break,
469            }
470        }
471        uscroll
472    }
473}
474
475impl From<Input> for String {
476    fn from(input: Input) -> Self {
477        input.value.into()
478    }
479}
480
481impl From<String> for Input {
482    fn from(value: String) -> Self {
483        Self::new(value)
484    }
485}
486
487impl From<&str> for Input {
488    fn from(value: &str) -> Self {
489        Self::new(value.into())
490    }
491}
492
493impl std::fmt::Display for Input {
494    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
495        self.value.as_str().fmt(f)
496    }
497}
498
499#[cfg(test)]
500mod tests;