Skip to main content

rusty_bubbles/
textinput.rs

1//! Cleanroom Rust port of upstream Go source file: `textinput/textinput.go`
2//! Cleanroom Rust port of upstream Go source file: `textinput/styles.go`
3//! Upstream Target Tag / Version: `v2.1.0`
4//!
5//! <public-docs>
6//! # TextInput
7//!
8//! A text input component for Bubble Tea applications.
9//! </public-docs>
10
11use crate::cursor;
12use crate::internal::clipboard;
13use crate::internal::runeutil::{self, Sanitizer};
14use crate::key::{self, Binding};
15use rusty_bubbletea::commands;
16use rusty_bubbletea::cursor::CursorShape;
17use rusty_bubbletea::key::{Key, KeyPressMsg};
18use rusty_bubbletea::model::{Cmd, Msg};
19use rusty_bubbletea::paste::PasteMsg;
20use rusty_lipgloss::{self, Color, Style};
21use std::time::Duration;
22use unicode_width::UnicodeWidthChar;
23
24/// Internal messages for clipboard operations.
25#[derive(Debug)]
26pub struct PasteMsgInternal(pub String);
27
28#[derive(Debug)]
29pub struct PasteErrMsg(pub String);
30
31/// EchoMode sets the input behavior of the text input field.
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
33pub enum EchoMode {
34    /// EchoNormal displays text as is. This is the default behavior.
35    #[default]
36    EchoNormal,
37
38    /// EchoPassword displays the EchoCharacter mask instead of actual
39    /// characters. This is commonly used for password fields.
40    EchoPassword,
41
42    /// EchoNone displays nothing as characters are entered. This is commonly
43    /// seen for password fields on the command line.
44    EchoNone,
45}
46
47/// ValidateFunc is a function that returns an error if the input is invalid.
48pub type ValidateFunc = Box<dyn Fn(&str) -> Result<(), String> + Send + Sync>;
49
50/// KeyMap is the key bindings for different actions within the textinput.
51#[derive(Debug, Clone)]
52pub struct KeyMap {
53    /// Move the cursor forward one character.
54    pub character_forward: Binding,
55    /// Move the cursor backward one character.
56    pub character_backward: Binding,
57    /// Move the cursor forward one word.
58    pub word_forward: Binding,
59    /// Move the cursor backward one word.
60    pub word_backward: Binding,
61    /// Delete the word backward.
62    pub delete_word_backward: Binding,
63    /// Delete the word forward.
64    pub delete_word_forward: Binding,
65    /// Delete after the cursor.
66    pub delete_after_cursor: Binding,
67    /// Delete before the cursor.
68    pub delete_before_cursor: Binding,
69    /// Delete the character backward.
70    pub delete_character_backward: Binding,
71    /// Delete the character forward.
72    pub delete_character_forward: Binding,
73    /// Go to the line start.
74    pub line_start: Binding,
75    /// Go to the line end.
76    pub line_end: Binding,
77    /// Paste from the clipboard.
78    pub paste: Binding,
79    /// Accept the current suggestion.
80    pub accept_suggestion: Binding,
81    /// Go to the next suggestion.
82    pub next_suggestion: Binding,
83    /// Go to the previous suggestion.
84    pub prev_suggestion: Binding,
85}
86
87/// DefaultKeyMap is the default set of key bindings for navigating and
88/// acting upon the textinput.
89pub fn default_key_map() -> KeyMap {
90    KeyMap {
91        character_forward: key::new_binding(vec![key::with_keys(&["right", "ctrl+f"])]),
92        character_backward: key::new_binding(vec![key::with_keys(&["left", "ctrl+b"])]),
93        word_forward: key::new_binding(vec![key::with_keys(&["alt+right", "ctrl+right", "alt+f"])]),
94        word_backward: key::new_binding(vec![key::with_keys(&["alt+left", "ctrl+left", "alt+b"])]),
95        delete_word_backward: key::new_binding(vec![key::with_keys(&["alt+backspace", "ctrl+w"])]),
96        delete_word_forward: key::new_binding(vec![key::with_keys(&["alt+delete", "alt+d"])]),
97        delete_after_cursor: key::new_binding(vec![key::with_keys(&["ctrl+k"])]),
98        delete_before_cursor: key::new_binding(vec![key::with_keys(&["ctrl+u"])]),
99        delete_character_backward: key::new_binding(vec![key::with_keys(&["backspace", "ctrl+h"])]),
100        delete_character_forward: key::new_binding(vec![key::with_keys(&["delete", "ctrl+d"])]),
101        line_start: key::new_binding(vec![key::with_keys(&["home", "ctrl+a"])]),
102        line_end: key::new_binding(vec![key::with_keys(&["end", "ctrl+e"])]),
103        paste: key::new_binding(vec![key::with_keys(&["ctrl+v"])]),
104        accept_suggestion: key::new_binding(vec![key::with_keys(&["tab"])]),
105        next_suggestion: key::new_binding(vec![key::with_keys(&["down", "ctrl+n"])]),
106        prev_suggestion: key::new_binding(vec![key::with_keys(&["up", "ctrl+p"])]),
107    }
108}
109
110/// Model is the Bubble Tea model for this text input element.
111pub struct Model {
112    /// The validation error, if any.
113    pub err: Option<String>,
114
115    /// General settings.
116    /// The prompt shown before the input.
117    pub prompt: String,
118    /// The placeholder shown when the input is empty.
119    pub placeholder: String,
120    /// The echo mode of the input.
121    pub echo_mode: EchoMode,
122    /// The character used for masking in [`EchoMode::EchoPassword`].
123    pub echo_character: char,
124
125    /// use_virtual_cursor determines whether or not to use the virtual
126    /// cursor.
127    pub use_virtual_cursor: bool,
128
129    /// Virtual cursor manager.
130    pub virtual_cursor: cursor::Model,
131
132    /// CharLimit is the maximum amount of characters this input element will
133    /// accept. If 0 or less, there's no limit.
134    pub char_limit: usize,
135
136    /// Styling. FocusedStyle and BlurredStyle are used to style the textarea
137    /// in focused and blurred states.
138    pub styles: Styles,
139
140    /// Width is the maximum number of characters that can be displayed at
141    /// once. It essentially treats the text field like a horizontally
142    /// scrolling viewport. If 0 or less this setting is ignored.
143    pub width: usize,
144
145    /// KeyMap encodes the keybindings recognized by the widget.
146    pub key_map: KeyMap,
147
148    /// Underlying text value.
149    value: Vec<char>,
150
151    /// focus indicates whether user input focus should be on this input
152    /// component. When false, ignore keyboard input and hide the cursor.
153    pub focus: bool,
154
155    /// Cursor position.
156    pos: usize,
157
158    /// Used to emulate a viewport when width is set and the content is
159    /// overflowing.
160    offset: usize,
161    offset_right: usize,
162
163    /// Validate is a function that checks whether or not the text within the
164    /// input is valid. If it is not valid, the `Err` field will be set to the
165    /// error returned by the function.
166    pub validate: Option<ValidateFunc>,
167
168    /// rune sanitizer for input.
169    rsan: Option<runeutil::Sanitizer_>,
170
171    /// Should the input suggest to complete.
172    pub show_suggestions: bool,
173
174    /// suggestions is a list of suggestions that may be used to complete the
175    /// input.
176    suggestions: Vec<Vec<char>>,
177    matched_suggestions: Vec<Vec<char>>,
178    current_suggestion_index: usize,
179}
180
181impl std::fmt::Debug for Model {
182    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183        f.debug_struct("textinput::Model")
184            .field("value", &String::from_iter(self.value.iter()))
185            .field("focus", &self.focus)
186            .field("pos", &self.pos)
187            .finish()
188    }
189}
190
191/// New creates a new model with default settings.
192pub fn new() -> Model {
193    let mut m = Model {
194        prompt: "> ".to_string(),
195        echo_character: '*',
196        char_limit: 0,
197        styles: default_dark_styles(),
198        show_suggestions: false,
199        use_virtual_cursor: true,
200        virtual_cursor: cursor::new(),
201        key_map: default_key_map(),
202        suggestions: vec![],
203        value: vec![],
204        focus: false,
205        pos: 0,
206        placeholder: String::new(),
207        echo_mode: EchoMode::EchoNormal,
208        err: None,
209        width: 0,
210        validate: None,
211        rsan: None,
212        offset: 0,
213        offset_right: 0,
214        matched_suggestions: vec![],
215        current_suggestion_index: 0,
216    };
217    m.update_virtual_cursor_style();
218    m
219}
220
221impl Model {
222    /// VirtualCursor returns whether the model is using a virtual cursor.
223    pub fn virtual_cursor(&self) -> bool {
224        self.use_virtual_cursor
225    }
226
227    /// SetVirtualCursor sets whether the model should use a virtual cursor.
228    pub fn set_virtual_cursor(&mut self, v: bool) {
229        self.use_virtual_cursor = v;
230        self.update_virtual_cursor_style();
231    }
232
233    /// Styles returns the current set of styles.
234    pub fn styles(&self) -> &Styles {
235        &self.styles
236    }
237
238    /// SetStyles sets the styles for the text input.
239    pub fn set_styles(&mut self, s: Styles) {
240        self.styles = s;
241        self.update_virtual_cursor_style();
242    }
243
244    /// Cursor returns a real cursor for rendering in a Bubble Tea program.
245    /// This requires that [`use_virtual_cursor`](Self::use_virtual_cursor) is
246    /// false.
247    pub fn cursor(&self) -> Option<rusty_bubbletea::cursor::Cursor> {
248        if self.use_virtual_cursor || !self.focus {
249            return None;
250        }
251
252        let prompt_width = rusty_lipgloss::size::width(&self.prompt_view());
253        let mut x_offset = self.pos + prompt_width;
254        if self.width > 0 {
255            x_offset = x_offset.min(self.width + prompt_width);
256        }
257
258        let style = &self.styles.cursor;
259        let mut c = rusty_bubbletea::cursor::Cursor::new(x_offset, 0);
260        c.blink = style.blink;
261        // The cursor color: upstream stores a color.Color; the bubbletea
262        // Cursor expects an RGBColor — convert from the style color.
263        let (r, g, b, _) = style.color.rgba_bytes();
264        c.color = Some(rusty_x_ansi::color::RGBColor { r, g, b });
265        c.shape = style.shape;
266        Some(c)
267    }
268
269    /// Width returns the width of the text input.
270    pub fn width(&self) -> usize {
271        self.width
272    }
273
274    /// SetWidth sets the width of the text input.
275    pub fn set_width(&mut self, w: usize) {
276        self.width = w;
277    }
278
279    /// SetValue sets the value of the text input.
280    pub fn set_value(&mut self, s: &str) {
281        // Clean up any special characters in the input provided by the
282        // caller. This avoids bugs due to e.g. tab characters and whatnot.
283        let runes = self.san().sanitize(&s.chars().collect::<Vec<char>>());
284        let err = self.validate(&runes);
285        self.set_value_internal(runes, err);
286    }
287
288    fn set_value_internal(&mut self, runes: Vec<char>, err: Option<String>) {
289        self.err = err;
290
291        let empty = self.value.is_empty();
292
293        if self.char_limit > 0 && runes.len() > self.char_limit {
294            self.value = runes[..self.char_limit].to_vec();
295        } else {
296            self.value = runes;
297        }
298        if (self.pos == 0 && empty) || self.pos > self.value.len() {
299            self.set_cursor(self.value.len());
300        }
301        self.handle_overflow();
302    }
303
304    /// Value returns the value of the text input.
305    pub fn value(&self) -> String {
306        String::from_iter(self.value.iter())
307    }
308
309    /// Position returns the cursor position.
310    pub fn position(&self) -> usize {
311        self.pos
312    }
313
314    /// SetCursor moves the cursor to the given position. If the position is
315    /// out of bounds the cursor will be moved to the start or end
316    /// accordingly.
317    pub fn set_cursor(&mut self, pos: usize) {
318        self.pos = clamp(pos, 0, self.value.len());
319        self.handle_overflow();
320    }
321
322    /// CursorStart moves the cursor to the start of the input field.
323    pub fn cursor_start(&mut self) {
324        self.set_cursor(0);
325    }
326
327    /// CursorEnd moves the cursor to the end of the input field.
328    pub fn cursor_end(&mut self) {
329        self.set_cursor(self.value.len());
330    }
331
332    /// Focused returns the focus state on the model.
333    pub fn focused(&self) -> bool {
334        self.focus
335    }
336
337    /// Focus sets the focus state on the model. When the model is in focus
338    /// it can receive keyboard input and the cursor will be shown.
339    pub fn focus(&mut self) -> Cmd {
340        self.focus = true;
341        self.virtual_cursor.focus()
342    }
343
344    /// Blur removes the focus state on the model. When the model is blurred
345    /// it can not receive keyboard input and the cursor will be hidden.
346    pub fn blur(&mut self) {
347        self.focus = false;
348        self.virtual_cursor.blur();
349    }
350
351    /// Reset sets the input to its default state with no input.
352    pub fn reset(&mut self) {
353        self.value = vec![];
354        self.set_cursor(0);
355    }
356
357    /// SetSuggestions sets the suggestions for the input.
358    pub fn set_suggestions(&mut self, suggestions: &[String]) {
359        self.suggestions = suggestions.iter().map(|s| s.chars().collect()).collect();
360
361        self.update_suggestions();
362    }
363
364    /// rsan initializes or retrieves the rune sanitizer.
365    fn san(&mut self) -> &runeutil::Sanitizer_ {
366        if self.rsan.is_none() {
367            // Textinput has all its input on a single line so collapse
368            // newlines/tabs to single spaces.
369            self.rsan = Some(runeutil::new_sanitizer(vec![
370                runeutil::replace_tabs(" "),
371                runeutil::replace_newlines(" "),
372            ]));
373        }
374        self.rsan.as_ref().unwrap()
375    }
376
377    fn insert_ranes_from_user_input(&mut self, v: &[char]) {
378        // Clean up any special characters in the input provided by the
379        // clipboard. This avoids bugs due to e.g. tab characters and whatnot.
380        let mut paste = self.san().sanitize(v);
381
382        let mut avail_space: usize = 0;
383        if self.char_limit > 0 {
384            avail_space = self.char_limit - self.value.len();
385
386            // If the char limit's been reached, cancel.
387            if avail_space == 0 {
388                return;
389            }
390
391            // If there's not enough space to paste the whole thing cut the
392            // pasted runes down so they'll fit.
393            if avail_space < paste.len() {
394                paste.truncate(avail_space);
395            }
396        }
397
398        // Stuff before and after the cursor
399        let mut head: Vec<char> = self.value[..self.pos].to_vec();
400        let tail: Vec<char> = self.value[self.pos..].to_vec();
401
402        // Insert pasted runes
403        for r in paste {
404            head.push(r);
405            self.pos += 1;
406            if self.char_limit > 0 {
407                avail_space -= 1;
408                if avail_space == 0 {
409                    break;
410                }
411            }
412        }
413
414        // Put it all back together
415        let mut value = head;
416        value.extend_from_slice(&tail);
417        let input_err = self.validate(&value);
418        self.set_value_internal(value, input_err);
419    }
420
421    /// If a max width is defined, perform some logic to treat the visible
422    /// area as a horizontally scrolling viewport.
423    fn handle_overflow(&mut self) {
424        if self.width() == 0 || string_width(&String::from_iter(self.value.iter())) <= self.width()
425        {
426            self.offset = 0;
427            self.offset_right = self.value.len();
428            return;
429        }
430
431        // Correct right offset if we've deleted characters
432        self.offset_right = self.offset_right.min(self.value.len());
433
434        if self.pos < self.offset {
435            self.offset = self.pos;
436
437            let mut w = 0;
438            let mut i = 0;
439            let runes = &self.value[self.offset..];
440
441            while i < runes.len() && w <= self.width() {
442                w += rune_width(runes[i]);
443                if w <= self.width() + 1 {
444                    i += 1;
445                }
446            }
447
448            self.offset_right = self.offset + i;
449        } else if self.pos >= self.offset_right {
450            self.offset_right = self.pos;
451
452            let mut w = 0;
453            let runes = &self.value[..self.offset_right];
454            let mut i = runes.len() - 1;
455
456            while i > 0 && w < self.width() {
457                w += rune_width(runes[i]);
458                if w <= self.width() {
459                    i -= 1;
460                }
461            }
462
463            self.offset = self.offset_right - (runes.len() - 1 - i);
464        }
465    }
466
467    /// deleteBeforeCursor deletes all text before the cursor.
468    fn delete_before_cursor(&mut self) {
469        self.value = self.value[self.pos..].to_vec();
470        self.err = self.validate(&self.value);
471        self.offset = 0;
472        self.set_cursor(0);
473    }
474
475    /// deleteAfterCursor deletes all text after the cursor. If input is
476    /// masked delete everything after the cursor so as not to reveal word
477    /// breaks in the masked input.
478    fn delete_after_cursor(&mut self) {
479        self.value = self.value[..self.pos].to_vec();
480        self.err = self.validate(&self.value);
481        self.set_cursor(self.value.len());
482    }
483
484    /// deleteWordBackward deletes the word left to the cursor.
485    fn delete_word_backward(&mut self) {
486        if self.pos == 0 || self.value.is_empty() {
487            return;
488        }
489
490        if self.echo_mode != EchoMode::EchoNormal {
491            self.delete_before_cursor();
492            return;
493        }
494
495        // Linter note: it's critical that we acquire the initial cursor
496        // position here prior to altering it via SetCursor() below.
497        let old_pos = self.pos;
498
499        self.set_cursor(self.pos - 1);
500        loop {
501            if self.pos == 0 {
502                break;
503            }
504            if !self.value[self.pos].is_whitespace() {
505                break;
506            }
507            // ignore series of whitespace before cursor
508            self.set_cursor(self.pos - 1);
509        }
510
511        while self.pos > 0 {
512            if !self.value[self.pos].is_whitespace() {
513                self.set_cursor(self.pos - 1);
514            } else {
515                if self.pos > 0 {
516                    // keep the previous space
517                    self.set_cursor(self.pos + 1);
518                }
519                break;
520            }
521        }
522
523        if old_pos > self.value.len() {
524            self.value = self.value[..self.pos].to_vec();
525        } else {
526            let mut v = self.value[..self.pos].to_vec();
527            v.extend_from_slice(&self.value[old_pos..]);
528            self.value = v;
529        }
530        self.err = self.validate(&self.value);
531    }
532
533    /// deleteWordForward deletes the word right to the cursor. If input is
534    /// masked delete everything after the cursor so as not to reveal word
535    /// breaks in the masked input.
536    fn delete_word_forward(&mut self) {
537        if self.pos >= self.value.len() || self.value.is_empty() {
538            return;
539        }
540
541        if self.echo_mode != EchoMode::EchoNormal {
542            self.delete_after_cursor();
543            return;
544        }
545
546        let old_pos = self.pos;
547        self.set_cursor(self.pos + 1);
548        loop {
549            // ignore series of whitespace after cursor
550            self.set_cursor(self.pos + 1);
551            if self.pos >= self.value.len() {
552                break;
553            }
554            if !self.value[self.pos].is_whitespace() {
555                break;
556            }
557        }
558
559        while self.pos < self.value.len() {
560            if !self.value[self.pos].is_whitespace() {
561                self.set_cursor(self.pos + 1);
562            } else {
563                break;
564            }
565        }
566
567        if self.pos > self.value.len() {
568            self.value = self.value[..old_pos].to_vec();
569        } else {
570            let mut v = self.value[..old_pos].to_vec();
571            v.extend_from_slice(&self.value[self.pos..]);
572            self.value = v;
573        }
574        self.err = self.validate(&self.value);
575
576        self.set_cursor(old_pos);
577    }
578
579    /// wordBackward moves the cursor one word to the left. If input is
580    /// masked, move input to the start so as not to reveal word breaks in
581    /// the masked input.
582    fn word_backward(&mut self) {
583        if self.pos == 0 || self.value.is_empty() {
584            return;
585        }
586
587        if self.echo_mode != EchoMode::EchoNormal {
588            self.cursor_start();
589            return;
590        }
591
592        let mut i = self.pos as isize - 1;
593        while i >= 0 {
594            if self.value[i as usize].is_whitespace() {
595                self.set_cursor(self.pos - 1);
596                i -= 1;
597            } else {
598                break;
599            }
600        }
601
602        while i >= 0 {
603            if !self.value[i as usize].is_whitespace() {
604                self.set_cursor(self.pos - 1);
605                i -= 1;
606            } else {
607                break;
608            }
609        }
610    }
611
612    /// wordForward moves the cursor one word to the right. If the input is
613    /// masked, move input to the end so as not to reveal word breaks in the
614    /// masked input.
615    fn word_forward(&mut self) {
616        if self.pos >= self.value.len() || self.value.is_empty() {
617            return;
618        }
619
620        if self.echo_mode != EchoMode::EchoNormal {
621            self.cursor_end();
622            return;
623        }
624
625        let mut i = self.pos;
626        while i < self.value.len() {
627            if self.value[i].is_whitespace() {
628                self.set_cursor(self.pos + 1);
629                i += 1;
630            } else {
631                break;
632            }
633        }
634
635        while i < self.value.len() {
636            if !self.value[i].is_whitespace() {
637                self.set_cursor(self.pos + 1);
638                i += 1;
639            } else {
640                break;
641            }
642        }
643    }
644
645    fn echo_transform(&self, v: &str) -> String {
646        match self.echo_mode {
647            EchoMode::EchoPassword => self.echo_character.to_string().repeat(string_width(v)),
648            EchoMode::EchoNone => String::new(),
649            EchoMode::EchoNormal => v.to_string(),
650        }
651    }
652
653    /// Update is the Bubble Tea update loop.
654    pub fn update(&mut self, msg: &dyn Msg) -> Cmd {
655        if !self.focus {
656            return None;
657        }
658
659        // Need to check for completion before, because key is configurable
660        // and might be double assigned.
661        let key_press = msg.as_any().downcast_ref::<KeyPressMsg>();
662        if let Some(kp) = key_press {
663            if key::matches(&kp.0, std::slice::from_ref(&self.key_map.accept_suggestion))
664                && self.can_accept_suggestion()
665            {
666                let suggestion = &self.matched_suggestions[self.current_suggestion_index];
667                let rest: Vec<char> = suggestion[self.value.len()..].to_vec();
668                self.value.extend_from_slice(&rest);
669                self.cursor_end();
670            }
671        }
672
673        // Let's remember where the position of the cursor currently is so
674        // that if the cursor position changes, we can reset the blink.
675        let old_pos = self.pos;
676
677        if let Some(kp) = key_press {
678            let k: &Key = &kp.0;
679            if key::matches(k, std::slice::from_ref(&self.key_map.delete_word_backward)) {
680                self.delete_word_backward();
681            } else if key::matches(
682                k,
683                std::slice::from_ref(&self.key_map.delete_character_backward),
684            ) {
685                self.err = None;
686                if !self.value.is_empty() {
687                    let mut v = self.value[..self.pos.max(1) - 1].to_vec();
688                    v.extend_from_slice(&self.value[self.pos..]);
689                    self.value = v;
690                    self.err = self.validate(&self.value);
691                    if self.pos > 0 {
692                        self.set_cursor(self.pos - 1);
693                    }
694                }
695            } else if key::matches(k, std::slice::from_ref(&self.key_map.word_backward)) {
696                self.word_backward();
697            } else if key::matches(k, std::slice::from_ref(&self.key_map.character_backward)) {
698                if self.pos > 0 {
699                    self.set_cursor(self.pos - 1);
700                }
701            } else if key::matches(k, std::slice::from_ref(&self.key_map.word_forward)) {
702                self.word_forward();
703            } else if key::matches(k, std::slice::from_ref(&self.key_map.character_forward)) {
704                if self.pos < self.value.len() {
705                    self.set_cursor(self.pos + 1);
706                }
707            } else if key::matches(k, std::slice::from_ref(&self.key_map.line_start)) {
708                self.cursor_start();
709            } else if key::matches(
710                k,
711                std::slice::from_ref(&self.key_map.delete_character_forward),
712            ) {
713                if !self.value.is_empty() && self.pos < self.value.len() {
714                    self.value.remove(self.pos);
715                    self.err = self.validate(&self.value);
716                }
717            } else if key::matches(k, std::slice::from_ref(&self.key_map.line_end)) {
718                self.cursor_end();
719            } else if key::matches(k, std::slice::from_ref(&self.key_map.delete_after_cursor)) {
720                self.delete_after_cursor();
721            } else if key::matches(k, std::slice::from_ref(&self.key_map.delete_before_cursor)) {
722                self.delete_before_cursor();
723            } else if key::matches(k, std::slice::from_ref(&self.key_map.paste)) {
724                return self.paste();
725            } else if key::matches(k, std::slice::from_ref(&self.key_map.delete_word_forward)) {
726                self.delete_word_forward();
727            } else if key::matches(k, std::slice::from_ref(&self.key_map.next_suggestion)) {
728                self.next_suggestion();
729            } else if key::matches(k, std::slice::from_ref(&self.key_map.prev_suggestion)) {
730                self.previous_suggestion();
731            } else {
732                // Input one or more regular characters.
733                let text: Vec<char> = kp.0.text.chars().collect();
734                self.insert_ranes_from_user_input(&text);
735            }
736
737            // Check again if can be completed because value might be
738            // something that does not match the completion prefix.
739            self.update_suggestions();
740        } else if let Some(pm) = msg.as_any().downcast_ref::<PasteMsg>() {
741            let content: Vec<char> = pm.content.chars().collect();
742            self.insert_ranes_from_user_input(&content);
743        } else if let Some(pm) = msg.as_any().downcast_ref::<PasteMsgInternal>() {
744            let content: Vec<char> = pm.0.chars().collect();
745            self.insert_ranes_from_user_input(&content);
746        } else if let Some(pm) = msg.as_any().downcast_ref::<PasteErrMsg>() {
747            self.err = Some(pm.0.clone());
748        }
749
750        let mut cmds: Vec<Cmd> = Vec::new();
751
752        if self.use_virtual_cursor {
753            let cmd = self.virtual_cursor.update(msg);
754            cmds.push(cmd);
755
756            // If the cursor position changed, reset the blink state. This is
757            // a small UX nuance that makes cursor movement obvious and feel
758            // snappy.
759            if old_pos != self.pos && self.virtual_cursor.mode() == cursor::Mode::Blink {
760                self.virtual_cursor.is_blinked = false;
761                cmds.push(self.virtual_cursor.blink());
762            }
763        }
764
765        self.handle_overflow();
766        commands::batch(cmds)
767    }
768
769    /// View renders the textinput in its current state.
770    pub fn view(&self) -> String {
771        // Placeholder text
772        if self.value.is_empty() && !self.placeholder.is_empty() {
773            return self.placeholder_view();
774        }
775
776        let styles = self.active_style();
777
778        let style_text = styles.text.clone().inline(true);
779
780        let value = &self.value[self.offset..self.offset_right];
781        let pos = self.pos - self.offset;
782        let mut v =
783            style_text.render(&self.echo_transform(&String::from_iter(value[..pos].iter())));
784
785        // The upstream View() operates on a copy of the model, so cursor
786        // mutations are applied to a local clone here.
787        let mut vc = self.virtual_cursor.clone();
788
789        if pos < value.len() {
790            let char = self.echo_transform(&String::from_iter(value[pos..pos + 1].iter()));
791            vc.set_char(&char);
792            v += &vc.view(); // cursor and text under it
793            v += &style_text
794                .render(&self.echo_transform(&String::from_iter(value[pos + 1..].iter()))); // text after cursor
795            v += &self.completion_view(0); // suggested completion
796        } else if self.focus && self.can_accept_suggestion() {
797            let suggestion = &self.matched_suggestions[self.current_suggestion_index];
798            if value.len() < suggestion.len() {
799                vc.text_style = styles.suggestion.clone();
800                vc.set_char(
801                    &self.echo_transform(&String::from_iter(suggestion[pos..pos + 1].iter())),
802                );
803                v += &vc.view();
804                v += &self.completion_view(1);
805            } else {
806                vc.set_char(" ");
807                v += &vc.view();
808            }
809        } else {
810            vc.set_char(" ");
811            v += &vc.view();
812        }
813
814        // If a max width and background color were set fill the empty spaces
815        // with the background color.
816        let val_width = string_width(&String::from_iter(value.iter()));
817        if self.width() > 0 && val_width <= self.width() {
818            let mut padding = self.width() - val_width;
819            if val_width + padding <= self.width() && pos < value.len() {
820                padding += 1;
821            }
822            v += &style_text.render(&" ".repeat(padding));
823        }
824
825        self.prompt_view() + &v
826    }
827
828    fn prompt_view(&self) -> String {
829        self.active_style().prompt.clone().render(&self.prompt)
830    }
831
832    /// placeholderView returns the prompt and placeholder view, if any.
833    fn placeholder_view(&self) -> String {
834        let styles = self.active_style();
835        let render = styles.placeholder.clone();
836
837        let mut p: Vec<char> = self.placeholder.chars().collect();
838        p.resize(self.width() + 1, '\0');
839
840        let mut vc = self.virtual_cursor.clone();
841        vc.text_style = styles.placeholder.clone();
842        vc.set_char(&p[..1].iter().collect::<String>());
843        let mut v = vc.view();
844
845        // If the entire placeholder is already set and no padding is needed,
846        // finish.
847        if self.width() < 1 && p.len() <= 1 {
848            return styles.prompt.clone().render(&self.prompt) + &v;
849        }
850
851        // If Width is set then size placeholder accordingly.
852        if self.width() > 0 {
853            // available width is width - len + cursor offset of 1
854            let mut min_width = rusty_lipgloss::size::width(&self.placeholder);
855            let avail = (self.width() as i64) - (min_width as i64) + 1;
856            let avail_width: usize;
857
858            // if width < len, 'subtract'(add) number to len and dont add
859            // padding
860            if avail < 0 {
861                min_width = (min_width as i64 + avail).max(0) as usize;
862                avail_width = 0;
863            } else {
864                avail_width = avail as usize;
865            }
866            // append placeholder[len] - cursor, append padding
867            v += &render.render(&String::from_iter(p[1..min_width].iter()));
868            v += &render.render(&" ".repeat(avail_width));
869        } else {
870            // if there is no width, the placeholder can be any length
871            v += &render.render(&String::from_iter(p[1..].iter()));
872        }
873
874        styles.prompt.clone().render(&self.prompt) + &v
875    }
876
877    fn completion_view(&self, offset: usize) -> String {
878        if !self.can_accept_suggestion() {
879            return String::new();
880        }
881        let value = &self.value;
882        let suggestion = &self.matched_suggestions[self.current_suggestion_index];
883        if value.len() < suggestion.len() {
884            return self
885                .active_style()
886                .suggestion
887                .clone()
888                .inline(true)
889                .render(&String::from_iter(
890                    suggestion[value.len() + offset..].iter(),
891                ));
892        }
893        String::new()
894    }
895
896    /// AvailableSuggestions returns the list of available suggestions.
897    pub fn available_suggestions(&self) -> Vec<String> {
898        self.suggestions
899            .iter()
900            .map(|s| String::from_iter(s.iter()))
901            .collect()
902    }
903
904    /// MatchedSuggestions returns the list of matched suggestions.
905    pub fn matched_suggestions(&self) -> Vec<String> {
906        self.matched_suggestions
907            .iter()
908            .map(|s| String::from_iter(s.iter()))
909            .collect()
910    }
911
912    /// CurrentSuggestionIndex returns the currently selected suggestion
913    /// index.
914    pub fn current_suggestion_index(&self) -> usize {
915        self.current_suggestion_index
916    }
917
918    /// CurrentSuggestion returns the currently selected suggestion.
919    pub fn current_suggestion(&self) -> String {
920        if self.current_suggestion_index >= self.matched_suggestions.len() {
921            return String::new();
922        }
923
924        String::from_iter(self.matched_suggestions[self.current_suggestion_index].iter())
925    }
926
927    /// canAcceptSuggestion returns whether there is an acceptable suggestion
928    /// to autocomplete the current value.
929    pub fn can_accept_suggestion(&self) -> bool {
930        !self.matched_suggestions.is_empty()
931    }
932
933    /// updateSuggestions refreshes the list of matching suggestions.
934    fn update_suggestions(&mut self) {
935        if !self.show_suggestions {
936            return;
937        }
938
939        if self.value.is_empty() || self.suggestions.is_empty() {
940            self.matched_suggestions = vec![];
941            return;
942        }
943
944        let mut matches: Vec<Vec<char>> = Vec::new();
945        for s in &self.suggestions {
946            let suggestion = String::from_iter(s.iter());
947
948            let lower_suggestion = suggestion.to_lowercase();
949            let lower_value = String::from_iter(self.value.iter()).to_lowercase();
950            if lower_suggestion.starts_with(&lower_value) {
951                matches.push(s.clone());
952            }
953        }
954        if matches != self.matched_suggestions {
955            self.current_suggestion_index = 0;
956        }
957
958        self.matched_suggestions = matches;
959    }
960
961    /// nextSuggestion selects the next suggestion.
962    fn next_suggestion(&mut self) {
963        self.current_suggestion_index += 1;
964        if self.current_suggestion_index >= self.matched_suggestions.len() {
965            self.current_suggestion_index = 0;
966        }
967    }
968
969    /// previousSuggestion selects the previous suggestion.
970    fn previous_suggestion(&mut self) {
971        if self.current_suggestion_index == 0 {
972            self.current_suggestion_index = self.matched_suggestions.len() - 1;
973        } else {
974            self.current_suggestion_index -= 1;
975        }
976    }
977
978    fn validate(&self, v: &[char]) -> Option<String> {
979        match &self.validate {
980            Some(f) => {
981                let s = String::from_iter(v.iter());
982                f(&s).err()
983            }
984            None => None,
985        }
986    }
987
988    fn update_virtual_cursor_style(&mut self) {
989        if !self.use_virtual_cursor {
990            // Hide the virtual cursor if we're using a real cursor.
991            self.virtual_cursor.set_mode(cursor::Mode::Hide);
992            return;
993        }
994
995        self.virtual_cursor.style = Style::new().foreground_color(self.styles.cursor.color.clone());
996
997        // By default, the blink speed of the cursor is set to a default
998        // internally.
999        if self.styles.cursor.blink {
1000            if !self.styles.cursor.blink_speed.is_zero() {
1001                self.virtual_cursor.blink_speed = self.styles.cursor.blink_speed;
1002            }
1003            self.virtual_cursor.set_mode(cursor::Mode::Blink);
1004            return;
1005        }
1006        self.virtual_cursor.set_mode(cursor::Mode::Static);
1007    }
1008
1009    fn active_style(&self) -> StyleState {
1010        if self.focus {
1011            self.styles.focused.clone()
1012        } else {
1013            self.styles.blurred.clone()
1014        }
1015    }
1016
1017    /// Paste is a command for pasting from the clipboard into the text input.
1018    fn paste(&self) -> Cmd {
1019        Some(Box::new(|| match clipboard::read_all() {
1020            Ok(str) => Some(Box::new(PasteMsgInternal(str))),
1021            Err(err) => Some(Box::new(PasteErrMsg(err))),
1022        }))
1023    }
1024}
1025
1026/// Blink is a command used to initialize cursor blinking.
1027pub fn blink() -> Box<dyn Msg> {
1028    crate::cursor::blink()
1029}
1030
1031fn clamp(v: usize, low: usize, high: usize) -> usize {
1032    if high < low {
1033        return low;
1034    }
1035    high.min(low.max(v))
1036}
1037
1038fn rune_width(c: char) -> usize {
1039    UnicodeWidthChar::width(c).unwrap_or(0)
1040}
1041
1042fn string_width(s: &str) -> usize {
1043    s.chars().map(rune_width).sum()
1044}
1045
1046/// DefaultStyles returns the default styles for focused and blurred states
1047/// for the textarea.
1048pub fn default_styles(is_dark: bool) -> Styles {
1049    let light_dark = rusty_lipgloss::color::light_dark(is_dark);
1050
1051    Styles {
1052        focused: StyleState {
1053            placeholder: Style::new().foreground("240"),
1054            suggestion: Style::new().foreground("240"),
1055            prompt: Style::new().foreground("7"),
1056            text: Style::new(),
1057        },
1058        blurred: StyleState {
1059            placeholder: Style::new().foreground("240"),
1060            suggestion: Style::new().foreground("240"),
1061            prompt: Style::new().foreground("7"),
1062            text: Style::new().foreground_color(light_dark(Color::parse("245"), Color::parse("7"))),
1063        },
1064        cursor: CursorStyle {
1065            color: Color::parse("7"),
1066            shape: CursorShape::CursorBlock,
1067            blink: true,
1068            blink_speed: Duration::from_millis(530),
1069        },
1070    }
1071}
1072
1073/// DefaultLightStyles returns the default styles for a light background.
1074pub fn default_light_styles() -> Styles {
1075    default_styles(false)
1076}
1077
1078/// DefaultDarkStyles returns the default styles for a dark background.
1079pub fn default_dark_styles() -> Styles {
1080    default_styles(true)
1081}
1082
1083/// Styles are the styles for the textarea, separated into focused and
1084/// blurred states. The appropriate styles will be chosen based on the focus
1085/// state of the textarea.
1086#[derive(Debug, Clone)]
1087pub struct Styles {
1088    /// The styles used when focused.
1089    pub focused: StyleState,
1090    /// The styles used when blurred.
1091    pub blurred: StyleState,
1092    /// The cursor style.
1093    pub cursor: CursorStyle,
1094}
1095
1096/// StyleState that will be applied to the text area.
1097///
1098/// StyleState can be applied to focused and unfocused states to change the
1099/// styles depending on the focus state.
1100#[derive(Debug, Clone)]
1101pub struct StyleState {
1102    /// Style for the text.
1103    pub text: Style,
1104    /// Style for the placeholder.
1105    pub placeholder: Style,
1106    /// Style for the suggestion.
1107    pub suggestion: Style,
1108    /// Style for the prompt.
1109    pub prompt: Style,
1110}
1111
1112/// CursorStyle is the style for real and virtual cursors.
1113#[derive(Debug, Clone)]
1114pub struct CursorStyle {
1115    /// Style styles the cursor block.
1116    pub color: Color,
1117
1118    /// Shape is the cursor shape. The following shapes are available:
1119    ///
1120    /// - [`CursorShape::CursorBlock`]
1121    /// - [`CursorShape::CursorUnderline`]
1122    /// - [`CursorShape::CursorBar`]
1123    pub shape: CursorShape,
1124
1125    /// CursorBlink determines whether or not the cursor should blink.
1126    pub blink: bool,
1127
1128    /// BlinkSpeed is the speed at which the virtual cursor blinks. This has
1129    /// no effect on real cursors as well as no effect if the cursor is set
1130    /// not to blink.
1131    pub blink_speed: Duration,
1132}