1use crate::cursor::{Cursor, Mode as CursorMode, blink_cmd};
19use crate::key::{Binding, matches};
20use crate::runeutil::Sanitizer;
21use crate::viewport::Viewport;
22use bubbletea::{Cmd, KeyMsg, Message, Model};
23use lipgloss::{Color, Style};
24
25const MIN_HEIGHT: usize = 1;
26const DEFAULT_HEIGHT: usize = 6;
27const DEFAULT_WIDTH: usize = 40;
28const DEFAULT_MAX_HEIGHT: usize = 99;
29const DEFAULT_MAX_WIDTH: usize = 500;
30const MAX_LINES: usize = 10000;
31
32#[derive(Debug, Clone)]
34pub struct KeyMap {
35 pub character_forward: Binding,
37 pub character_backward: Binding,
39 pub delete_after_cursor: Binding,
41 pub delete_before_cursor: Binding,
43 pub delete_character_backward: Binding,
45 pub delete_character_forward: Binding,
47 pub delete_word_backward: Binding,
49 pub delete_word_forward: Binding,
51 pub insert_newline: Binding,
53 pub line_end: Binding,
55 pub line_next: Binding,
57 pub line_previous: Binding,
59 pub line_start: Binding,
61 pub paste: Binding,
63 pub word_backward: Binding,
65 pub word_forward: Binding,
67 pub input_begin: Binding,
69 pub input_end: Binding,
71 pub uppercase_word_forward: Binding,
73 pub lowercase_word_forward: Binding,
75 pub capitalize_word_forward: Binding,
77 pub transpose_character_backward: Binding,
79}
80
81impl Default for KeyMap {
82 fn default() -> Self {
83 Self {
84 character_forward: Binding::new()
85 .keys(&["right", "ctrl+f"])
86 .help("right", "character forward"),
87 character_backward: Binding::new()
88 .keys(&["left", "ctrl+b"])
89 .help("left", "character backward"),
90 word_forward: Binding::new()
91 .keys(&["alt+right", "alt+f"])
92 .help("alt+right", "word forward"),
93 word_backward: Binding::new()
94 .keys(&["alt+left", "alt+b"])
95 .help("alt+left", "word backward"),
96 line_next: Binding::new()
97 .keys(&["down", "ctrl+n"])
98 .help("down", "next line"),
99 line_previous: Binding::new()
100 .keys(&["up", "ctrl+p"])
101 .help("up", "previous line"),
102 delete_word_backward: Binding::new()
103 .keys(&["alt+backspace", "ctrl+w"])
104 .help("alt+backspace", "delete word backward"),
105 delete_word_forward: Binding::new()
106 .keys(&["alt+delete", "alt+d"])
107 .help("alt+delete", "delete word forward"),
108 delete_after_cursor: Binding::new()
109 .keys(&["ctrl+k"])
110 .help("ctrl+k", "delete after cursor"),
111 delete_before_cursor: Binding::new()
112 .keys(&["ctrl+u"])
113 .help("ctrl+u", "delete before cursor"),
114 insert_newline: Binding::new()
115 .keys(&["enter", "ctrl+m"])
116 .help("enter", "insert newline"),
117 delete_character_backward: Binding::new()
118 .keys(&["backspace", "ctrl+h"])
119 .help("backspace", "delete character backward"),
120 delete_character_forward: Binding::new()
121 .keys(&["delete", "ctrl+d"])
122 .help("delete", "delete character forward"),
123 line_start: Binding::new()
124 .keys(&["home", "ctrl+a"])
125 .help("home", "line start"),
126 line_end: Binding::new()
127 .keys(&["end", "ctrl+e"])
128 .help("end", "line end"),
129 paste: Binding::new().keys(&["ctrl+v"]).help("ctrl+v", "paste"),
130 input_begin: Binding::new()
131 .keys(&["alt+<", "ctrl+home"])
132 .help("alt+<", "input begin"),
133 input_end: Binding::new()
134 .keys(&["alt+>", "ctrl+end"])
135 .help("alt+>", "input end"),
136 capitalize_word_forward: Binding::new()
137 .keys(&["alt+c"])
138 .help("alt+c", "capitalize word forward"),
139 lowercase_word_forward: Binding::new()
140 .keys(&["alt+l"])
141 .help("alt+l", "lowercase word forward"),
142 uppercase_word_forward: Binding::new()
143 .keys(&["alt+u"])
144 .help("alt+u", "uppercase word forward"),
145 transpose_character_backward: Binding::new()
146 .keys(&["ctrl+t"])
147 .help("ctrl+t", "transpose character backward"),
148 }
149 }
150}
151
152#[derive(Debug, Clone)]
154pub struct Styles {
155 pub base: Style,
157 pub cursor_line: Style,
159 pub cursor_line_number: Style,
161 pub end_of_buffer: Style,
163 pub line_number: Style,
165 pub placeholder: Style,
167 pub prompt: Style,
169 pub text: Style,
171}
172
173impl Default for Styles {
174 fn default() -> Self {
175 Self {
176 base: Style::new(),
177 cursor_line: Style::new(),
178 cursor_line_number: Style::new().foreground_color(Color::from("240")),
179 end_of_buffer: Style::new().foreground_color(Color::from("240")),
180 line_number: Style::new().foreground_color(Color::from("240")),
181 placeholder: Style::new().foreground_color(Color::from("240")),
182 prompt: Style::new().foreground_color(Color::from("7")),
183 text: Style::new(),
184 }
185 }
186}
187
188#[derive(Debug, Clone)]
190pub struct PasteMsg(pub String);
191
192#[derive(Debug, Clone)]
194pub struct PasteErrMsg(pub String);
195
196#[derive(Debug, Clone)]
198pub struct TextArea {
199 pub err: Option<String>,
201 pub prompt: String,
203 pub placeholder: String,
205 pub show_line_numbers: bool,
207 pub end_of_buffer_character: char,
209 pub key_map: KeyMap,
211 pub focused_style: Styles,
213 pub blurred_style: Styles,
215 pub cursor: Cursor,
217 pub char_limit: usize,
219 pub max_height: usize,
221 pub max_width: usize,
223 use_focused_style: bool,
225 prompt_width: usize,
227 width: usize,
229 height: usize,
231 value: Vec<Vec<char>>,
233 focus: bool,
235 col: usize,
237 row: usize,
239 last_char_offset: usize,
241 viewport: Viewport,
243 sanitizer: Sanitizer,
245}
246
247impl Default for TextArea {
248 fn default() -> Self {
249 Self::new()
250 }
251}
252
253impl TextArea {
254 #[must_use]
256 pub fn new() -> Self {
257 let viewport = Viewport::new(0, 0);
258
259 let mut ta = Self {
260 err: None,
261 prompt: "β ".to_string(),
262 placeholder: String::new(),
263 show_line_numbers: true,
264 end_of_buffer_character: ' ',
265 key_map: KeyMap::default(),
266 focused_style: Styles::default(),
267 blurred_style: Styles::default(),
268 use_focused_style: false,
269 cursor: Cursor::new(),
270 char_limit: 0,
271 max_height: DEFAULT_MAX_HEIGHT,
272 max_width: DEFAULT_MAX_WIDTH,
273 prompt_width: 2, width: DEFAULT_WIDTH,
275 height: DEFAULT_HEIGHT,
276 value: vec![Vec::new()],
277 focus: false,
278 col: 0,
279 row: 0,
280 last_char_offset: 0,
281 viewport,
282 sanitizer: Sanitizer::new(),
283 };
284
285 ta.set_height(DEFAULT_HEIGHT);
286 ta.set_width(DEFAULT_WIDTH);
287 ta
288 }
289
290 pub fn set_value(&mut self, s: &str) {
292 self.reset();
293 self.insert_string(s);
294 }
295
296 pub fn insert_string(&mut self, s: &str) {
298 self.insert_runes_from_user_input(&s.chars().collect::<Vec<_>>());
299 }
300
301 pub fn insert_rune(&mut self, r: char) {
303 self.insert_runes_from_user_input(&[r]);
304 }
305
306 fn insert_runes_from_user_input(&mut self, runes: &[char]) {
307 let runes = self.sanitizer.sanitize(runes);
308
309 let runes = if self.char_limit > 0 {
310 let current_len = self.length();
311 let avail = self.char_limit.saturating_sub(current_len);
312 if avail == 0 {
313 return;
314 }
315 if runes.len() > avail {
316 runes[..avail].to_vec()
317 } else {
318 runes
319 }
320 } else {
321 runes
322 };
323
324 let mut lines: Vec<Vec<char>> = Vec::new();
326 let mut current_line = Vec::new();
327
328 for c in &runes {
329 if *c == '\n' {
330 lines.push(current_line);
331 current_line = Vec::new();
332 } else {
333 current_line.push(*c);
334 }
335 }
336 lines.push(current_line);
337
338 if MAX_LINES > 0 && self.value.len() + lines.len() - 1 > MAX_LINES {
340 let allowed = MAX_LINES.saturating_sub(self.value.len()) + 1;
341 lines.truncate(allowed);
342 }
343
344 if lines.is_empty() {
345 return;
346 }
347
348 let tail: Vec<char> = self.value[self.row][self.col..].to_vec();
350
351 self.value[self.row].truncate(self.col);
353 self.value[self.row].extend_from_slice(&lines[0]);
354 self.col += lines[0].len();
355
356 if lines.len() > 1 {
358 for line in lines.into_iter().skip(1) {
360 self.row += 1;
361 self.value.insert(self.row, line.clone());
362 self.col = line.len();
363 }
364 }
365
366 self.value[self.row].extend_from_slice(&tail);
368 self.set_cursor_col(self.col);
369 }
370
371 #[must_use]
373 pub fn value(&self) -> String {
374 self.value
375 .iter()
376 .map(|line| line.iter().collect::<String>())
377 .collect::<Vec<_>>()
378 .join("\n")
379 }
380
381 #[must_use]
383 pub fn length(&self) -> usize {
384 let char_count: usize = self.value.iter().map(|line| line.len()).sum();
385 char_count + self.value.len().saturating_sub(1)
387 }
388
389 #[must_use]
391 pub fn line_count(&self) -> usize {
392 self.value.len()
393 }
394
395 #[must_use]
397 pub fn line(&self) -> usize {
398 self.row
399 }
400
401 #[must_use]
403 pub fn cursor_col(&self) -> usize {
404 self.col
405 }
406
407 #[must_use]
409 pub fn cursor_pos(&self) -> (usize, usize) {
410 (self.row, self.col)
411 }
412
413 #[must_use]
415 pub fn cursor_byte_offset(&self) -> usize {
416 if self.value.is_empty() {
417 return 0;
418 }
419
420 let row = self.row.min(self.value.len().saturating_sub(1));
421 let col = self.col.min(self.value[row].len());
422
423 let mut offset = 0usize;
424
425 for line in &self.value[..row] {
426 offset = offset.saturating_add(line.iter().map(|c| c.len_utf8()).sum::<usize>());
427 offset = offset.saturating_add(1); }
429
430 offset.saturating_add(
431 self.value[row][..col]
432 .iter()
433 .map(|c| c.len_utf8())
434 .sum::<usize>(),
435 )
436 }
437
438 pub fn set_cursor_byte_offset(&mut self, offset: usize) {
443 if self.value.is_empty() {
444 self.value = vec![Vec::new()];
445 }
446
447 fn col_for_byte_offset(line: &[char], byte_offset: usize) -> usize {
448 let mut col = 0usize;
449 let mut used = 0usize;
450
451 for c in line {
452 let len = c.len_utf8();
453 if used.saturating_add(len) > byte_offset {
454 break;
455 }
456 used = used.saturating_add(len);
457 col = col.saturating_add(1);
458 }
459
460 col
461 }
462
463 let mut remaining = offset;
464
465 for (idx, line) in self.value.iter().enumerate() {
466 let line_bytes = line.iter().map(|c| c.len_utf8()).sum::<usize>();
467
468 if remaining <= line_bytes {
469 self.row = idx;
470 let col = col_for_byte_offset(line, remaining);
471 self.set_cursor_col(col);
472 return;
473 }
474
475 remaining = remaining.saturating_sub(line_bytes);
476
477 if idx + 1 < self.value.len() {
478 if remaining == 0 {
480 self.row = idx;
481 self.set_cursor_col(line.len());
482 return;
483 }
484
485 remaining = remaining.saturating_sub(1);
486 if remaining == 0 {
487 self.row = idx + 1;
488 self.set_cursor_col(0);
489 return;
490 }
491 }
492 }
493
494 self.row = self.value.len().saturating_sub(1);
496 let last_len = self.value[self.row].len();
497 self.set_cursor_col(last_len);
498 }
499
500 pub fn cursor_down(&mut self) {
502 if self.row < self.value.len() - 1 {
503 self.row += 1;
504 self.col = self.col.min(self.value[self.row].len());
505 }
506 }
507
508 pub fn cursor_up(&mut self) {
510 if self.row > 0 {
511 self.row -= 1;
512 self.col = self.col.min(self.value[self.row].len());
513 }
514 }
515
516 pub fn set_cursor_col(&mut self, col: usize) {
518 self.col = col.min(self.value[self.row].len());
519 self.last_char_offset = 0;
520 }
521
522 pub fn cursor_start(&mut self) {
524 self.set_cursor_col(0);
525 }
526
527 pub fn cursor_end(&mut self) {
529 self.set_cursor_col(self.value[self.row].len());
530 }
531
532 pub fn cursor_left(&mut self) {
534 self.character_left(false);
535 }
536
537 pub fn cursor_right(&mut self) {
539 self.character_right();
540 }
541
542 #[must_use]
544 pub fn focused(&self) -> bool {
545 self.focus
546 }
547
548 pub fn focus(&mut self) -> Option<Cmd> {
550 self.focus = true;
551 self.use_focused_style = true;
552 self.cursor.focus()
553 }
554
555 pub fn blur(&mut self) {
557 self.focus = false;
558 self.use_focused_style = false;
559 self.cursor.blur();
560 }
561
562 pub fn reset(&mut self) {
564 self.value = vec![Vec::new()];
565 self.col = 0;
566 self.row = 0;
567 self.viewport.goto_top();
568 self.set_cursor_col(0);
569 }
570
571 fn current_style(&self) -> &Styles {
572 if self.use_focused_style {
573 &self.focused_style
574 } else {
575 &self.blurred_style
576 }
577 }
578
579 fn delete_before_cursor(&mut self) {
580 self.value[self.row] = self.value[self.row][self.col..].to_vec();
581 self.set_cursor_col(0);
582 }
583
584 fn delete_after_cursor(&mut self) {
585 self.value[self.row].truncate(self.col);
586 self.set_cursor_col(self.value[self.row].len());
587 }
588
589 fn transpose_left(&mut self) {
590 let len = self.value[self.row].len();
591 if self.col == 0 || len < 2 {
592 return;
593 }
594 if self.col >= len {
596 self.set_cursor_col(len - 1);
597 }
598 self.value[self.row].swap(self.col - 1, self.col);
599 if self.col < self.value[self.row].len() {
600 self.set_cursor_col(self.col + 1);
601 }
602 }
603
604 fn delete_word_left(&mut self) {
605 if self.col == 0 || self.value[self.row].is_empty() {
606 return;
607 }
608
609 let old_col = self.col;
610 self.set_cursor_col(self.col.saturating_sub(1));
611
612 while self.col > 0
614 && self.value[self.row]
615 .get(self.col)
616 .is_some_and(|c| c.is_whitespace())
617 {
618 self.set_cursor_col(self.col.saturating_sub(1));
619 }
620
621 while self.col > 0 {
623 if !self.value[self.row]
624 .get(self.col)
625 .is_some_and(|c| c.is_whitespace())
626 {
627 self.set_cursor_col(self.col.saturating_sub(1));
628 } else {
629 if self.col > 0 {
630 self.set_cursor_col(self.col + 1);
631 }
632 break;
633 }
634 }
635
636 let mut new_line = self.value[self.row][..self.col].to_vec();
637 if old_col <= self.value[self.row].len() {
638 new_line.extend_from_slice(&self.value[self.row][old_col..]);
639 }
640 self.value[self.row] = new_line;
641 }
642
643 fn delete_word_right(&mut self) {
644 if self.col >= self.value[self.row].len() || self.value[self.row].is_empty() {
645 return;
646 }
647
648 let old_col = self.col;
649
650 while self.col < self.value[self.row].len()
652 && self.value[self.row]
653 .get(self.col)
654 .is_some_and(|c| c.is_whitespace())
655 {
656 self.set_cursor_col(self.col + 1);
657 }
658
659 while self.col < self.value[self.row].len() {
661 if !self.value[self.row]
662 .get(self.col)
663 .is_some_and(|c| c.is_whitespace())
664 {
665 self.set_cursor_col(self.col + 1);
666 } else {
667 break;
668 }
669 }
670
671 let mut new_line = self.value[self.row][..old_col].to_vec();
672 if self.col <= self.value[self.row].len() {
673 new_line.extend_from_slice(&self.value[self.row][self.col..]);
674 }
675 self.value[self.row] = new_line;
676 self.set_cursor_col(old_col);
677 }
678
679 fn character_right(&mut self) {
680 if self.col < self.value[self.row].len() {
681 self.set_cursor_col(self.col + 1);
682 } else if self.row < self.value.len() - 1 {
683 self.row += 1;
684 self.cursor_start();
685 }
686 }
687
688 fn character_left(&mut self, inside_line: bool) {
689 if self.col == 0 && self.row > 0 {
690 self.row -= 1;
691 self.cursor_end();
692 if !inside_line {
693 return;
694 }
695 }
696 if self.col > 0 {
697 self.set_cursor_col(self.col - 1);
698 }
699 }
700
701 fn word_left(&mut self) {
702 loop {
703 self.character_left(true);
704 if self.col < self.value[self.row].len()
705 && !self.value[self.row]
706 .get(self.col)
707 .is_some_and(|c| c.is_whitespace())
708 {
709 break;
710 }
711 if self.col == 0 && self.row == 0 {
712 break;
713 }
714 }
715
716 while self.col > 0 {
717 if self.value[self.row]
718 .get(self.col - 1)
719 .is_some_and(|c| c.is_whitespace())
720 {
721 break;
722 }
723 self.set_cursor_col(self.col - 1);
724 }
725 }
726
727 fn word_right(&mut self) {
728 while self.col >= self.value[self.row].len()
730 || self.value[self.row]
731 .get(self.col)
732 .is_some_and(|c| c.is_whitespace())
733 {
734 if self.row == self.value.len() - 1 && self.col == self.value[self.row].len() {
735 break;
736 }
737 self.character_right();
738 }
739
740 while self.col < self.value[self.row].len() {
742 if self.value[self.row]
743 .get(self.col)
744 .is_some_and(|c| c.is_whitespace())
745 {
746 break;
747 }
748 self.set_cursor_col(self.col + 1);
749 }
750 }
751
752 fn uppercase_right(&mut self) {
753 self.do_word_right(|line, i| {
754 line[i] = line[i].to_uppercase().next().unwrap_or(line[i]);
755 });
756 }
757
758 fn lowercase_right(&mut self) {
759 self.do_word_right(|line, i| {
760 line[i] = line[i].to_lowercase().next().unwrap_or(line[i]);
761 });
762 }
763
764 fn capitalize_right(&mut self) {
765 let mut char_idx = 0;
766 self.do_word_right(|line, i| {
767 if char_idx == 0 {
768 line[i] = line[i].to_uppercase().next().unwrap_or(line[i]);
769 }
770 char_idx += 1;
771 });
772 }
773
774 fn do_word_right<F>(&mut self, mut f: F)
775 where
776 F: FnMut(&mut Vec<char>, usize),
777 {
778 while self.col >= self.value[self.row].len()
780 || self.value[self.row]
781 .get(self.col)
782 .is_some_and(|c| c.is_whitespace())
783 {
784 if self.row == self.value.len() - 1 && self.col == self.value[self.row].len() {
785 break;
786 }
787 self.character_right();
788 }
789
790 while self.col < self.value[self.row].len() {
791 if self.value[self.row]
792 .get(self.col)
793 .is_some_and(|c| c.is_whitespace())
794 {
795 break;
796 }
797 f(&mut self.value[self.row], self.col);
798 self.set_cursor_col(self.col + 1);
799 }
800 }
801
802 fn move_to_begin(&mut self) {
803 self.row = 0;
804 self.set_cursor_col(0);
805 }
806
807 fn move_to_end(&mut self) {
808 self.row = self.value.len().saturating_sub(1);
809 self.set_cursor_col(self.value[self.row].len());
810 }
811
812 pub fn set_width(&mut self, w: usize) {
814 self.prompt_width = self.prompt.chars().count();
815
816 let reserved_outer = 0; let mut reserved_inner = self.prompt_width;
818
819 if self.show_line_numbers {
820 reserved_inner += 4; }
822
823 let min_width = reserved_inner + reserved_outer + 1;
824 let mut input_width = w.max(min_width);
825
826 if self.max_width > 0 {
827 input_width = input_width.min(self.max_width);
828 }
829
830 self.viewport.width = input_width.saturating_sub(reserved_outer);
831 self.width = input_width
832 .saturating_sub(reserved_outer)
833 .saturating_sub(reserved_inner);
834 }
835
836 #[must_use]
838 pub fn width(&self) -> usize {
839 self.width
840 }
841
842 pub fn set_height(&mut self, h: usize) {
844 if self.max_height > 0 {
845 self.height = h.clamp(MIN_HEIGHT, self.max_height);
846 self.viewport.height = h.clamp(MIN_HEIGHT, self.max_height);
847 } else {
848 self.height = h.max(MIN_HEIGHT);
849 self.viewport.height = h.max(MIN_HEIGHT);
850 }
851 }
852
853 #[must_use]
855 pub fn height(&self) -> usize {
856 self.height
857 }
858
859 fn merge_line_below(&mut self, row: usize) {
860 if row >= self.value.len() - 1 {
861 return;
862 }
863
864 let below = self.value.remove(row + 1);
865 self.value[row].extend(below);
866 }
867
868 fn merge_line_above(&mut self, row: usize) {
869 if row == 0 {
870 return;
871 }
872
873 self.col = self.value[row - 1].len();
874 let current = self.value.remove(row);
875 self.value[row - 1].extend(current);
876 self.row -= 1;
877 }
878
879 fn split_line(&mut self, row: usize, col: usize) {
880 let tail = self.value[row][col..].to_vec();
881 self.value[row].truncate(col);
882 self.value.insert(row + 1, tail);
883 self.col = 0;
884 self.row += 1;
885 }
886
887 fn reposition_view(&mut self) {
888 let minimum = self.viewport.y_offset();
889 let maximum = minimum + self.viewport.height.saturating_sub(1);
890
891 if self.row < minimum {
892 self.viewport.scroll_up(minimum - self.row);
893 } else if self.row > maximum {
894 self.viewport.scroll_down(self.row - maximum);
895 }
896 }
897
898 pub fn update(&mut self, msg: Message) -> Option<Cmd> {
900 if !self.focus {
901 self.cursor.blur();
902 return None;
903 }
904
905 let old_row = self.row;
906 let old_col = self.col;
907
908 if let Some(paste) = msg.downcast_ref::<PasteMsg>() {
910 self.insert_runes_from_user_input(&paste.0.chars().collect::<Vec<_>>());
911 }
912
913 if let Some(paste_err) = msg.downcast_ref::<PasteErrMsg>() {
914 self.err = Some(paste_err.0.clone());
915 }
916
917 if let Some(key) = msg.downcast_ref::<KeyMsg>() {
918 let key_str = key.to_string();
919
920 if matches(&key_str, &[&self.key_map.delete_after_cursor]) {
921 self.col = self.col.min(self.value[self.row].len());
922 if self.col >= self.value[self.row].len() {
923 self.merge_line_below(self.row);
924 } else {
925 self.delete_after_cursor();
926 }
927 } else if matches(&key_str, &[&self.key_map.delete_before_cursor]) {
928 self.col = self.col.min(self.value[self.row].len());
929 if self.col == 0 {
930 self.merge_line_above(self.row);
931 } else {
932 self.delete_before_cursor();
933 }
934 } else if matches(&key_str, &[&self.key_map.delete_character_backward]) {
935 self.col = self.col.min(self.value[self.row].len());
936 if self.col == 0 {
937 self.merge_line_above(self.row);
938 } else if !self.value[self.row].is_empty() {
939 self.value[self.row].remove(self.col - 1);
940 self.set_cursor_col(self.col.saturating_sub(1));
941 }
942 } else if matches(&key_str, &[&self.key_map.delete_character_forward]) {
943 if !self.value[self.row].is_empty() && self.col < self.value[self.row].len() {
944 self.value[self.row].remove(self.col);
945 }
946 if self.col >= self.value[self.row].len() {
947 self.merge_line_below(self.row);
948 }
949 } else if matches(&key_str, &[&self.key_map.delete_word_backward]) {
950 if self.col == 0 {
951 self.merge_line_above(self.row);
952 } else {
953 self.delete_word_left();
954 }
955 } else if matches(&key_str, &[&self.key_map.delete_word_forward]) {
956 self.col = self.col.min(self.value[self.row].len());
957 if self.col >= self.value[self.row].len() {
958 self.merge_line_below(self.row);
959 } else {
960 self.delete_word_right();
961 }
962 } else if matches(&key_str, &[&self.key_map.insert_newline]) {
963 if self.max_height == 0 || self.value.len() < self.max_height {
964 self.col = self.col.min(self.value[self.row].len());
965 self.split_line(self.row, self.col);
966 }
967 } else if matches(&key_str, &[&self.key_map.line_end]) {
968 self.cursor_end();
969 } else if matches(&key_str, &[&self.key_map.line_start]) {
970 self.cursor_start();
971 } else if matches(&key_str, &[&self.key_map.character_forward]) {
972 self.character_right();
973 } else if matches(&key_str, &[&self.key_map.line_next]) {
974 self.cursor_down();
975 } else if matches(&key_str, &[&self.key_map.word_forward]) {
976 self.word_right();
977 } else if matches(&key_str, &[&self.key_map.character_backward]) {
978 self.character_left(false);
979 } else if matches(&key_str, &[&self.key_map.line_previous]) {
980 self.cursor_up();
981 } else if matches(&key_str, &[&self.key_map.word_backward]) {
982 self.word_left();
983 } else if matches(&key_str, &[&self.key_map.input_begin]) {
984 self.move_to_begin();
985 } else if matches(&key_str, &[&self.key_map.input_end]) {
986 self.move_to_end();
987 } else if matches(&key_str, &[&self.key_map.lowercase_word_forward]) {
988 self.lowercase_right();
989 } else if matches(&key_str, &[&self.key_map.uppercase_word_forward]) {
990 self.uppercase_right();
991 } else if matches(&key_str, &[&self.key_map.capitalize_word_forward]) {
992 self.capitalize_right();
993 } else if matches(&key_str, &[&self.key_map.transpose_character_backward]) {
994 self.transpose_left();
995 } else if !matches(&key_str, &[&self.key_map.paste]) {
996 let runes: Vec<char> = key.runes.clone();
998 if !runes.is_empty() {
999 self.insert_runes_from_user_input(&runes);
1000 }
1001 }
1002 }
1003
1004 self.viewport.update(&msg);
1005
1006 let mut cmds: Vec<Option<Cmd>> = Vec::new();
1007
1008 if let Some(cmd) = self.cursor.update(msg) {
1009 cmds.push(Some(cmd));
1010 }
1011
1012 if (self.row != old_row || self.col != old_col) && self.cursor.mode() == CursorMode::Blink {
1013 cmds.push(Some(blink_cmd()));
1015 }
1016
1017 self.reposition_view();
1018
1019 bubbletea::batch(cmds)
1020 }
1021
1022 #[must_use]
1024 pub fn view(&self) -> String {
1025 if self.value() == "" && self.row == 0 && self.col == 0 && !self.placeholder.is_empty() {
1026 return self.placeholder_view();
1027 }
1028
1029 let style = self.current_style();
1030 let mut lines = Vec::new();
1031
1032 for (l, line) in self.value.iter().enumerate() {
1033 let is_cursor_line = self.row == l;
1034 let line_style = if is_cursor_line {
1035 &style.cursor_line
1036 } else {
1037 &style.text
1038 };
1039
1040 let mut s = String::new();
1041
1042 s.push_str(&style.prompt.render(&self.prompt));
1044
1045 if self.show_line_numbers {
1047 let ln_style = if is_cursor_line {
1048 &style.cursor_line_number
1049 } else {
1050 &style.line_number
1051 };
1052 s.push_str(&ln_style.render(&format!("{:>3} ", l + 1)));
1053 }
1054
1055 let line_str: String = line.iter().collect();
1057 if is_cursor_line {
1058 let before: String = line[..self.col.min(line.len())].iter().collect();
1059 s.push_str(&line_style.render(&before));
1060
1061 if self.col < line.len() {
1062 let cursor_char: String = line[self.col..self.col + 1].iter().collect();
1063 let mut cursor = self.cursor.clone();
1064 cursor.set_char(&cursor_char);
1065 s.push_str(&cursor.view());
1066
1067 let after: String = line[self.col + 1..].iter().collect();
1068 s.push_str(&line_style.render(&after));
1069 } else {
1070 let mut cursor = self.cursor.clone();
1071 cursor.set_char(" ");
1072 s.push_str(&cursor.view());
1073 }
1074 } else {
1075 s.push_str(&line_style.render(&line_str));
1076 }
1077
1078 let mut current_line_width: usize = line
1080 .iter()
1081 .map(|c| unicode_width::UnicodeWidthChar::width(*c).unwrap_or(0))
1082 .sum();
1083 if is_cursor_line && self.col >= line.len() {
1084 current_line_width += 1; }
1086
1087 let padding = self.width.saturating_sub(current_line_width);
1088 if padding > 0 {
1089 s.push_str(&line_style.render(&" ".repeat(padding)));
1090 }
1091
1092 lines.push(s);
1093 }
1094
1095 while lines.len() < self.height {
1097 let mut s = String::new();
1098 s.push_str(&style.prompt.render(&self.prompt));
1099 if self.show_line_numbers {
1100 s.push_str(&style.line_number.render(" "));
1101 }
1102 s.push_str(
1103 &style
1104 .end_of_buffer
1105 .render(&format!("{}", self.end_of_buffer_character)),
1106 );
1107 let padding = self.width.saturating_sub(1);
1108 s.push_str(&" ".repeat(padding));
1109 lines.push(s);
1110 }
1111
1112 let start = self.viewport.y_offset();
1114 let end = (start + self.height).min(lines.len());
1115 let visible: String = lines[start..end].join("\n");
1116
1117 style.base.render(&visible)
1118 }
1119
1120 fn placeholder_view(&self) -> String {
1121 let style = self.current_style();
1122 let mut lines = Vec::new();
1123
1124 let placeholder_lines: Vec<&str> = self.placeholder.lines().collect();
1125
1126 for i in 0..self.height {
1127 let mut s = String::new();
1128
1129 s.push_str(&style.prompt.render(&self.prompt));
1131
1132 if self.show_line_numbers {
1134 let ln_style = if i == 0 {
1135 &style.cursor_line_number
1136 } else {
1137 &style.line_number
1138 };
1139 if i == 0 {
1140 s.push_str(&ln_style.render(&format!("{:>3} ", 1)));
1141 } else {
1142 s.push_str(&ln_style.render(" "));
1143 }
1144 }
1145
1146 if i < placeholder_lines.len() {
1147 let line = placeholder_lines[i];
1148 if i == 0 && !line.is_empty() {
1149 let first: String = line.chars().take(1).collect();
1151 let rest: String = line.chars().skip(1).collect();
1152
1153 let mut cursor = self.cursor.clone();
1154 cursor.text_style = style.placeholder.clone();
1155 cursor.set_char(&first);
1156 s.push_str(&cursor.view());
1157 s.push_str(&style.placeholder.render(&rest));
1158 } else {
1159 s.push_str(&style.placeholder.render(line));
1160 }
1161 } else {
1162 s.push_str(
1163 &style
1164 .end_of_buffer
1165 .render(&format!("{}", self.end_of_buffer_character)),
1166 );
1167 }
1168
1169 lines.push(s);
1170 }
1171
1172 style.base.render(&lines.join("\n"))
1173 }
1174}
1175
1176impl Model for TextArea {
1177 fn init(&self) -> Option<Cmd> {
1179 if self.focus { Some(blink_cmd()) } else { None }
1180 }
1181
1182 fn update(&mut self, msg: Message) -> Option<Cmd> {
1184 TextArea::update(self, msg)
1185 }
1186
1187 fn view(&self) -> String {
1189 TextArea::view(self)
1190 }
1191}
1192
1193#[cfg(test)]
1194mod tests {
1195 use super::*;
1196
1197 #[test]
1198 fn test_textarea_new() {
1199 let ta = TextArea::new();
1200 assert_eq!(ta.height, DEFAULT_HEIGHT);
1201 assert!(ta.show_line_numbers);
1202 assert!(!ta.focused());
1203 }
1204
1205 #[test]
1206 fn test_textarea_set_value() {
1207 let mut ta = TextArea::new();
1208 ta.set_value("Hello\nWorld");
1209 assert_eq!(ta.value(), "Hello\nWorld");
1210 assert_eq!(ta.line_count(), 2);
1211 }
1212
1213 #[test]
1214 fn test_textarea_cursor_navigation() {
1215 let mut ta = TextArea::new();
1216 ta.set_value("Line 1\nLine 2\nLine 3");
1217
1218 assert_eq!(ta.row, 2); ta.move_to_begin();
1220 assert_eq!(ta.row, 0);
1221 assert_eq!(ta.col, 0);
1222
1223 ta.cursor_end();
1224 assert_eq!(ta.col, 6);
1225
1226 ta.cursor_down();
1227 assert_eq!(ta.row, 1);
1228 }
1229
1230 #[test]
1231 fn test_textarea_focus_blur() {
1232 let mut ta = TextArea::new();
1233 assert!(!ta.focused());
1234
1235 ta.focus();
1236 assert!(ta.focused());
1237
1238 ta.blur();
1239 assert!(!ta.focused());
1240 }
1241
1242 #[test]
1243 fn test_textarea_reset() {
1244 let mut ta = TextArea::new();
1245 ta.set_value("Hello\nWorld");
1246 ta.reset();
1247 assert_eq!(ta.value(), "");
1248 assert_eq!(ta.line_count(), 1);
1249 }
1250
1251 #[test]
1252 fn test_textarea_insert_newline() {
1253 let mut ta = TextArea::new();
1254 ta.set_value("Hello");
1255 ta.move_to_begin();
1256 ta.set_cursor_col(2); ta.split_line(0, 2);
1258
1259 assert_eq!(ta.line_count(), 2);
1260 assert_eq!(ta.value(), "He\nllo");
1261 }
1262
1263 #[test]
1264 fn test_textarea_delete_line() {
1265 let mut ta = TextArea::new();
1266 ta.set_value("Line 1\nLine 2\nLine 3");
1267 ta.move_to_begin();
1268 ta.row = 1;
1269 ta.col = 0;
1270 ta.merge_line_above(1);
1271
1272 assert_eq!(ta.line_count(), 2);
1273 assert_eq!(ta.value(), "Line 1Line 2\nLine 3");
1274 }
1275
1276 #[test]
1277 fn test_textarea_char_limit() {
1278 let mut ta = TextArea::new();
1279 ta.char_limit = 10;
1280 ta.set_value("This is a very long string");
1281 assert!(ta.length() <= 10);
1282 }
1283
1284 #[test]
1285 fn test_textarea_dimensions() {
1286 let mut ta = TextArea::new();
1287 ta.set_width(80);
1288 ta.set_height(24);
1289
1290 assert_eq!(ta.height(), 24);
1291 }
1292
1293 #[test]
1294 fn test_textarea_view() {
1295 let mut ta = TextArea::new();
1296 ta.set_value("Hello\nWorld");
1297 let view = ta.view();
1298
1299 assert!(view.contains("Hello"));
1300 assert!(view.contains("World"));
1301 }
1302
1303 #[test]
1304 fn test_textarea_placeholder() {
1305 let mut ta = TextArea::new();
1306 ta.placeholder = "Enter text...".to_string();
1307 let view = ta.view();
1308 assert!(view.contains("E"), "View should contain cursor char 'E'");
1311 assert!(
1312 view.contains("nter text..."),
1313 "View should contain rest of placeholder"
1314 );
1315 }
1316
1317 #[test]
1318 fn test_keymap_default() {
1319 let km = KeyMap::default();
1320 assert!(!km.character_forward.get_keys().is_empty());
1321 assert!(!km.insert_newline.get_keys().is_empty());
1322 }
1323
1324 #[test]
1326 fn test_model_init_unfocused() {
1327 let ta = TextArea::new();
1328 let cmd = Model::init(&ta);
1330 assert!(cmd.is_none());
1331 }
1332
1333 #[test]
1334 fn test_model_init_focused() {
1335 let mut ta = TextArea::new();
1336 ta.focus();
1337 let cmd = Model::init(&ta);
1339 assert!(cmd.is_some());
1340 }
1341
1342 #[test]
1343 fn test_model_view() {
1344 let mut ta = TextArea::new();
1345 ta.set_value("Test content");
1346 let model_view = Model::view(&ta);
1348 let textarea_view = TextArea::view(&ta);
1349 assert_eq!(model_view, textarea_view);
1350 }
1351
1352 #[test]
1353 fn test_model_update_handles_paste_msg() {
1354 use bubbletea::Message;
1355
1356 let mut ta = TextArea::new();
1357 ta.focus();
1358 assert_eq!(ta.value(), "");
1359
1360 let paste_msg = Message::new(PasteMsg("hello world".to_string()));
1361 let _ = Model::update(&mut ta, paste_msg);
1362
1363 assert_eq!(
1364 ta.value(),
1365 "hello world",
1366 "TextArea should insert pasted text"
1367 );
1368 }
1369
1370 #[test]
1371 fn test_model_update_unfocused_ignores_input() {
1372 use bubbletea::{KeyMsg, Message};
1373
1374 let mut ta = TextArea::new();
1375 assert!(!ta.focused());
1377 assert_eq!(ta.value(), "");
1378
1379 let key_msg = Message::new(KeyMsg::from_char('a'));
1380 let _ = Model::update(&mut ta, key_msg);
1381
1382 assert_eq!(ta.value(), "", "Unfocused textarea should ignore key input");
1383 }
1384
1385 #[test]
1386 fn test_model_update_handles_key_input() {
1387 use bubbletea::{KeyMsg, Message};
1388
1389 let mut ta = TextArea::new();
1390 ta.focus();
1391 assert_eq!(ta.value(), "");
1392
1393 let key_msg = Message::new(KeyMsg::from_char('H'));
1394 let _ = Model::update(&mut ta, key_msg);
1395
1396 assert_eq!(
1397 ta.value(),
1398 "H",
1399 "Focused textarea should insert typed character"
1400 );
1401 }
1402
1403 #[test]
1404 fn test_model_update_handles_navigation() {
1405 use bubbletea::{KeyMsg, KeyType, Message};
1406
1407 let mut ta = TextArea::new();
1408 ta.focus();
1409 ta.set_value("Hello\nWorld");
1410 ta.move_to_begin();
1411 assert_eq!(ta.row, 0);
1412 assert_eq!(ta.col, 0);
1413
1414 let down_msg = Message::new(KeyMsg::from_type(KeyType::Down));
1416 let _ = Model::update(&mut ta, down_msg);
1417
1418 assert_eq!(ta.row, 1, "TextArea should navigate down on Down key");
1419 }
1420
1421 #[test]
1422 fn test_textarea_satisfies_model_bounds() {
1423 fn requires_model<T: Model + Send + 'static>() {}
1424 requires_model::<TextArea>();
1425 }
1426
1427 #[test]
1432 fn test_model_update_backspace_deletes_char() {
1433 use bubbletea::{KeyMsg, KeyType, Message};
1434
1435 let mut ta = TextArea::new();
1436 ta.focus();
1437 ta.set_value("Hello");
1438 ta.move_to_begin();
1439 ta.col = 5; let backspace_msg = Message::new(KeyMsg::from_type(KeyType::Backspace));
1442 let _ = Model::update(&mut ta, backspace_msg);
1443
1444 assert_eq!(
1445 ta.value(),
1446 "Hell",
1447 "Backspace should delete character before cursor"
1448 );
1449 }
1450
1451 #[test]
1452 fn test_model_update_backspace_at_start_noop() {
1453 use bubbletea::{KeyMsg, KeyType, Message};
1454
1455 let mut ta = TextArea::new();
1456 ta.focus();
1457 ta.set_value("Hello");
1458 ta.move_to_begin();
1459 assert_eq!(ta.row, 0);
1460 assert_eq!(ta.col, 0);
1461
1462 let backspace_msg = Message::new(KeyMsg::from_type(KeyType::Backspace));
1463 let _ = Model::update(&mut ta, backspace_msg);
1464
1465 assert_eq!(ta.value(), "Hello", "Backspace at start should do nothing");
1466 assert_eq!(ta.col, 0);
1467 }
1468
1469 #[test]
1470 fn test_model_update_delete_forward() {
1471 use bubbletea::{KeyMsg, KeyType, Message};
1472
1473 let mut ta = TextArea::new();
1474 ta.focus();
1475 ta.set_value("Hello");
1476 ta.move_to_begin();
1477 ta.col = 0;
1478
1479 let delete_msg = Message::new(KeyMsg::from_type(KeyType::Delete));
1480 let _ = Model::update(&mut ta, delete_msg);
1481
1482 assert_eq!(
1483 ta.value(),
1484 "ello",
1485 "Delete should remove character at cursor"
1486 );
1487 }
1488
1489 #[test]
1490 fn test_model_update_cursor_left() {
1491 use bubbletea::{KeyMsg, KeyType, Message};
1492
1493 let mut ta = TextArea::new();
1494 ta.focus();
1495 ta.set_value("Hello");
1496 ta.move_to_begin();
1497 ta.col = 3;
1498
1499 let left_msg = Message::new(KeyMsg::from_type(KeyType::Left));
1500 let _ = Model::update(&mut ta, left_msg);
1501
1502 assert_eq!(ta.col, 2, "Left arrow should move cursor left");
1503 }
1504
1505 #[test]
1506 fn test_model_update_cursor_right() {
1507 use bubbletea::{KeyMsg, KeyType, Message};
1508
1509 let mut ta = TextArea::new();
1510 ta.focus();
1511 ta.set_value("Hello");
1512 ta.move_to_begin();
1513 ta.col = 0;
1514
1515 let right_msg = Message::new(KeyMsg::from_type(KeyType::Right));
1516 let _ = Model::update(&mut ta, right_msg);
1517
1518 assert_eq!(ta.col, 1, "Right arrow should move cursor right");
1519 }
1520
1521 #[test]
1522 fn test_model_update_cursor_up() {
1523 use bubbletea::{KeyMsg, KeyType, Message};
1524
1525 let mut ta = TextArea::new();
1526 ta.focus();
1527 ta.set_value("Line1\nLine2\nLine3");
1528 ta.row = 2;
1529 ta.col = 0;
1530
1531 let up_msg = Message::new(KeyMsg::from_type(KeyType::Up));
1532 let _ = Model::update(&mut ta, up_msg);
1533
1534 assert_eq!(ta.row, 1, "Up arrow should move cursor up");
1535 }
1536
1537 #[test]
1538 fn test_model_update_enter_splits_line() {
1539 use bubbletea::{KeyMsg, KeyType, Message};
1540
1541 let mut ta = TextArea::new();
1542 ta.focus();
1543 ta.set_value("Hello World");
1544 ta.move_to_begin();
1545 ta.col = 5;
1546
1547 let enter_msg = Message::new(KeyMsg::from_type(KeyType::Enter));
1548 let _ = Model::update(&mut ta, enter_msg);
1549
1550 assert_eq!(ta.line_count(), 2, "Enter should split into two lines");
1551 assert!(ta.value().contains('\n'), "Value should contain newline");
1552 }
1553
1554 #[test]
1555 fn test_textarea_view_shows_line_numbers() {
1556 let mut ta = TextArea::new();
1557 ta.show_line_numbers = true;
1558 ta.set_value("Line 1\nLine 2\nLine 3");
1559
1560 let view = ta.view();
1561
1562 assert!(
1564 view.contains('1') && view.contains('2') && view.contains('3'),
1565 "View should contain line numbers"
1566 );
1567 }
1568
1569 #[test]
1570 fn test_textarea_view_hides_line_numbers() {
1571 let mut ta = TextArea::new();
1572 ta.show_line_numbers = false;
1573 ta.set_value("A\nB\nC");
1574
1575 let view = ta.view();
1576
1577 assert!(
1579 view.contains('A') && view.contains('B') && view.contains('C'),
1580 "View should contain content"
1581 );
1582 }
1583
1584 #[test]
1585 fn test_textarea_empty_operations() {
1586 let mut ta = TextArea::new();
1587 ta.focus();
1588 assert_eq!(ta.value(), "");
1589
1590 ta.cursor_up();
1592 ta.cursor_down();
1593 ta.cursor_start();
1594 ta.cursor_end();
1595 ta.move_to_begin();
1596 ta.move_to_end();
1597
1598 assert_eq!(
1599 ta.value(),
1600 "",
1601 "Empty textarea should remain empty after navigation"
1602 );
1603 assert_eq!(ta.row, 0);
1604 assert_eq!(ta.col, 0);
1605 }
1606
1607 #[test]
1608 fn test_textarea_unicode_characters() {
1609 let mut ta = TextArea::new();
1610 ta.focus();
1611 ta.set_value("Hello δΈη π¦");
1612
1613 assert_eq!(ta.value(), "Hello δΈη π¦");
1614 let view = ta.view();
1615 assert!(view.contains("δΈη"), "View should render CJK characters");
1616 assert!(view.contains("π¦"), "View should render emoji");
1617 }
1618
1619 #[test]
1620 fn test_textarea_unicode_cursor_navigation() {
1621 use bubbletea::{KeyMsg, KeyType, Message};
1622
1623 let mut ta = TextArea::new();
1624 ta.focus();
1625 ta.set_value("ζ₯ζ¬θͺ");
1626 ta.move_to_begin();
1627
1628 let right_msg = Message::new(KeyMsg::from_type(KeyType::Right));
1630 let _ = Model::update(&mut ta, right_msg);
1631
1632 assert!(ta.col > 0, "Cursor should advance through unicode");
1633 }
1634
1635 #[test]
1636 fn test_textarea_very_long_line() {
1637 let mut ta = TextArea::new();
1638 ta.set_width(20);
1639 let long_line = "A".repeat(100);
1640 ta.set_value(&long_line);
1641
1642 let view = ta.view();
1644 assert!(!view.is_empty(), "View should render long line");
1645 }
1646
1647 #[test]
1648 fn test_textarea_max_height_enforced() {
1649 let mut ta = TextArea::new();
1650 ta.max_height = 3;
1651
1652 ta.set_value("1\n2\n3\n4\n5\n6\n7\n8\n9\n10");
1654
1655 assert!(ta.line_count() >= 3, "Content should be stored");
1658 }
1659
1660 #[test]
1661 fn test_textarea_width_set_propagates() {
1662 let mut ta = TextArea::new();
1663 ta.set_width(80);
1664
1665 let view = ta.view();
1667 assert!(!view.is_empty(), "View should work after width set");
1668 }
1669
1670 #[test]
1671 fn test_model_init_returns_blink_when_focused() {
1672 let mut ta = TextArea::new();
1673 ta.focus();
1674
1675 let cmd = Model::init(&ta);
1676 assert!(
1677 cmd.is_some(),
1678 "Focused textarea init should return blink command"
1679 );
1680 }
1681
1682 #[test]
1683 fn test_model_init_returns_none_when_unfocused() {
1684 let ta = TextArea::new();
1685
1686 let cmd = Model::init(&ta);
1687 assert!(cmd.is_none(), "Unfocused textarea init should return None");
1688 }
1689
1690 #[test]
1695 fn test_bracketed_paste_basic() {
1696 use bubbletea::{KeyMsg, KeyType, Message};
1697
1698 let mut ta = TextArea::new();
1699 ta.focus();
1700
1701 let key_msg = Message::new(KeyMsg {
1702 key_type: KeyType::Runes,
1703 runes: vec!['h', 'e', 'l', 'l', 'o'],
1704 alt: false,
1705 paste: true,
1706 });
1707 let _ = Model::update(&mut ta, key_msg);
1708
1709 assert_eq!(ta.value(), "hello");
1710 }
1711
1712 #[test]
1713 fn test_bracketed_paste_multiline_preserves_newlines() {
1714 use bubbletea::{KeyMsg, KeyType, Message};
1715
1716 let mut ta = TextArea::new();
1717 ta.focus();
1718
1719 let key_msg = Message::new(KeyMsg {
1721 key_type: KeyType::Runes,
1722 runes: "line1\nline2\nline3".chars().collect(),
1723 alt: false,
1724 paste: true,
1725 });
1726 let _ = Model::update(&mut ta, key_msg);
1727
1728 assert_eq!(
1729 ta.value(),
1730 "line1\nline2\nline3",
1731 "TextArea should preserve newlines in paste"
1732 );
1733 assert_eq!(ta.line_count(), 3, "Should have 3 lines after paste");
1734 }
1735
1736 #[test]
1737 fn test_bracketed_paste_crlf_normalized() {
1738 use bubbletea::{KeyMsg, KeyType, Message};
1739
1740 let mut ta = TextArea::new();
1741 ta.focus();
1742
1743 let key_msg = Message::new(KeyMsg {
1745 key_type: KeyType::Runes,
1746 runes: "line1\r\nline2".chars().collect(),
1747 alt: false,
1748 paste: true,
1749 });
1750 let _ = Model::update(&mut ta, key_msg);
1751
1752 assert_eq!(
1753 ta.value(),
1754 "line1\nline2",
1755 "CRLF should be normalized to LF"
1756 );
1757 assert_eq!(ta.line_count(), 2);
1758 }
1759
1760 #[test]
1761 fn test_bracketed_paste_respects_char_limit() {
1762 use bubbletea::{KeyMsg, KeyType, Message};
1763
1764 let mut ta = TextArea::new();
1765 ta.focus();
1766 ta.char_limit = 10;
1767
1768 let key_msg = Message::new(KeyMsg {
1769 key_type: KeyType::Runes,
1770 runes: "this is a very long paste".chars().collect(),
1771 alt: false,
1772 paste: true,
1773 });
1774 let _ = Model::update(&mut ta, key_msg);
1775
1776 assert_eq!(ta.length(), 10, "Paste should respect char_limit");
1777 }
1778
1779 #[test]
1780 fn test_bracketed_paste_unfocused_ignored() {
1781 use bubbletea::{KeyMsg, KeyType, Message};
1782
1783 let mut ta = TextArea::new();
1784 let key_msg = Message::new(KeyMsg {
1787 key_type: KeyType::Runes,
1788 runes: "ignored".chars().collect(),
1789 alt: false,
1790 paste: true,
1791 });
1792 let _ = Model::update(&mut ta, key_msg);
1793
1794 assert_eq!(ta.value(), "", "Unfocused textarea should ignore paste");
1795 }
1796
1797 #[test]
1798 fn test_bracketed_paste_inserts_at_cursor() {
1799 use bubbletea::{KeyMsg, KeyType, Message};
1800
1801 let mut ta = TextArea::new();
1802 ta.focus();
1803 ta.set_value("helloworld");
1804 ta.move_to_begin();
1805 ta.col = 5; let key_msg = Message::new(KeyMsg {
1808 key_type: KeyType::Runes,
1809 runes: " ".chars().collect(),
1810 alt: false,
1811 paste: true,
1812 });
1813 let _ = Model::update(&mut ta, key_msg);
1814
1815 assert_eq!(ta.value(), "hello world");
1816 }
1817
1818 #[test]
1819 fn test_bracketed_paste_unicode() {
1820 use bubbletea::{KeyMsg, KeyType, Message};
1821
1822 let mut ta = TextArea::new();
1823 ta.focus();
1824
1825 let key_msg = Message::new(KeyMsg {
1826 key_type: KeyType::Runes,
1827 runes: "hello δΈη π".chars().collect(),
1828 alt: false,
1829 paste: true,
1830 });
1831 let _ = Model::update(&mut ta, key_msg);
1832
1833 assert_eq!(ta.value(), "hello δΈη π");
1834 }
1835
1836 #[test]
1837 fn test_bracketed_paste_large_content() {
1838 use bubbletea::{KeyMsg, KeyType, Message};
1839
1840 let mut ta = TextArea::new();
1841 ta.focus();
1842
1843 let large_text: String = "a".repeat(1000);
1845 let key_msg = Message::new(KeyMsg {
1846 key_type: KeyType::Runes,
1847 runes: large_text.chars().collect(),
1848 alt: false,
1849 paste: true,
1850 });
1851 let _ = Model::update(&mut ta, key_msg);
1852
1853 assert_eq!(
1854 ta.value().len(),
1855 1000,
1856 "Large paste should work without issues"
1857 );
1858 }
1859
1860 #[test]
1861 fn test_bracketed_paste_multiline_cursor_position() {
1862 use bubbletea::{KeyMsg, KeyType, Message};
1863
1864 let mut ta = TextArea::new();
1865 ta.focus();
1866
1867 let key_msg = Message::new(KeyMsg {
1868 key_type: KeyType::Runes,
1869 runes: "line1\nline2\nline3".chars().collect(),
1870 alt: false,
1871 paste: true,
1872 });
1873 let _ = Model::update(&mut ta, key_msg);
1874
1875 assert_eq!(ta.row, 2, "Cursor should be on line 3 (index 2)");
1877 assert_eq!(ta.col, 5, "Cursor should be at end of 'line3'");
1878 }
1879}