1use 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#[derive(Debug)]
26pub struct PasteMsgInternal(pub String);
27
28#[derive(Debug)]
29pub struct PasteErrMsg(pub String);
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
33pub enum EchoMode {
34 #[default]
36 EchoNormal,
37
38 EchoPassword,
41
42 EchoNone,
45}
46
47pub type ValidateFunc = Box<dyn Fn(&str) -> Result<(), String> + Send + Sync>;
49
50#[derive(Debug, Clone)]
52pub struct KeyMap {
53 pub character_forward: Binding,
55 pub character_backward: Binding,
57 pub word_forward: Binding,
59 pub word_backward: Binding,
61 pub delete_word_backward: Binding,
63 pub delete_word_forward: Binding,
65 pub delete_after_cursor: Binding,
67 pub delete_before_cursor: Binding,
69 pub delete_character_backward: Binding,
71 pub delete_character_forward: Binding,
73 pub line_start: Binding,
75 pub line_end: Binding,
77 pub paste: Binding,
79 pub accept_suggestion: Binding,
81 pub next_suggestion: Binding,
83 pub prev_suggestion: Binding,
85}
86
87pub 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
110pub struct Model {
112 pub err: Option<String>,
114
115 pub prompt: String,
118 pub placeholder: String,
120 pub echo_mode: EchoMode,
122 pub echo_character: char,
124
125 pub use_virtual_cursor: bool,
128
129 pub virtual_cursor: cursor::Model,
131
132 pub char_limit: usize,
135
136 pub styles: Styles,
139
140 pub width: usize,
144
145 pub key_map: KeyMap,
147
148 value: Vec<char>,
150
151 pub focus: bool,
154
155 pos: usize,
157
158 offset: usize,
161 offset_right: usize,
162
163 pub validate: Option<ValidateFunc>,
167
168 rsan: Option<runeutil::Sanitizer_>,
170
171 pub show_suggestions: bool,
173
174 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
191pub 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 pub fn virtual_cursor(&self) -> bool {
224 self.use_virtual_cursor
225 }
226
227 pub fn set_virtual_cursor(&mut self, v: bool) {
229 self.use_virtual_cursor = v;
230 self.update_virtual_cursor_style();
231 }
232
233 pub fn styles(&self) -> &Styles {
235 &self.styles
236 }
237
238 pub fn set_styles(&mut self, s: Styles) {
240 self.styles = s;
241 self.update_virtual_cursor_style();
242 }
243
244 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 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 pub fn width(&self) -> usize {
271 self.width
272 }
273
274 pub fn set_width(&mut self, w: usize) {
276 self.width = w;
277 }
278
279 pub fn set_value(&mut self, s: &str) {
281 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 pub fn value(&self) -> String {
306 String::from_iter(self.value.iter())
307 }
308
309 pub fn position(&self) -> usize {
311 self.pos
312 }
313
314 pub fn set_cursor(&mut self, pos: usize) {
318 self.pos = clamp(pos, 0, self.value.len());
319 self.handle_overflow();
320 }
321
322 pub fn cursor_start(&mut self) {
324 self.set_cursor(0);
325 }
326
327 pub fn cursor_end(&mut self) {
329 self.set_cursor(self.value.len());
330 }
331
332 pub fn focused(&self) -> bool {
334 self.focus
335 }
336
337 pub fn focus(&mut self) -> Cmd {
340 self.focus = true;
341 self.virtual_cursor.focus()
342 }
343
344 pub fn blur(&mut self) {
347 self.focus = false;
348 self.virtual_cursor.blur();
349 }
350
351 pub fn reset(&mut self) {
353 self.value = vec![];
354 self.set_cursor(0);
355 }
356
357 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 fn san(&mut self) -> &runeutil::Sanitizer_ {
366 if self.rsan.is_none() {
367 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 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 avail_space == 0 {
388 return;
389 }
390
391 if avail_space < paste.len() {
394 paste.truncate(avail_space);
395 }
396 }
397
398 let mut head: Vec<char> = self.value[..self.pos].to_vec();
400 let tail: Vec<char> = self.value[self.pos..].to_vec();
401
402 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn update(&mut self, msg: &dyn Msg) -> Cmd {
655 if !self.focus {
656 return None;
657 }
658
659 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 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 let text: Vec<char> = kp.0.text.chars().collect();
734 self.insert_ranes_from_user_input(&text);
735 }
736
737 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 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 pub fn view(&self) -> String {
771 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 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(); v += &style_text
794 .render(&self.echo_transform(&String::from_iter(value[pos + 1..].iter()))); v += &self.completion_view(0); } 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 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 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 self.width() < 1 && p.len() <= 1 {
848 return styles.prompt.clone().render(&self.prompt) + &v;
849 }
850
851 if self.width() > 0 {
853 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 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 v += &render.render(&String::from_iter(p[1..min_width].iter()));
868 v += &render.render(&" ".repeat(avail_width));
869 } else {
870 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 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 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 pub fn current_suggestion_index(&self) -> usize {
915 self.current_suggestion_index
916 }
917
918 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 pub fn can_accept_suggestion(&self) -> bool {
930 !self.matched_suggestions.is_empty()
931 }
932
933 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 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 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 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 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 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
1026pub 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
1046pub 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
1073pub fn default_light_styles() -> Styles {
1075 default_styles(false)
1076}
1077
1078pub fn default_dark_styles() -> Styles {
1080 default_styles(true)
1081}
1082
1083#[derive(Debug, Clone)]
1087pub struct Styles {
1088 pub focused: StyleState,
1090 pub blurred: StyleState,
1092 pub cursor: CursorStyle,
1094}
1095
1096#[derive(Debug, Clone)]
1101pub struct StyleState {
1102 pub text: Style,
1104 pub placeholder: Style,
1106 pub suggestion: Style,
1108 pub prompt: Style,
1110}
1111
1112#[derive(Debug, Clone)]
1114pub struct CursorStyle {
1115 pub color: Color,
1117
1118 pub shape: CursorShape,
1124
1125 pub blink: bool,
1127
1128 pub blink_speed: Duration,
1132}