1pub mod helpers;
47pub mod memoization;
48
49#[cfg(test)]
50mod tests;
51
52use helpers::*;
53use memoization::MemoizedWrap;
54
55use crate::{cursor, viewport, Component};
56use bubbletea_rs::{Cmd, Model as BubbleTeaModel};
57use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
58
59const MIN_HEIGHT: usize = 1;
61const DEFAULT_HEIGHT: usize = 6;
62const DEFAULT_WIDTH: usize = 40;
63const DEFAULT_CHAR_LIMIT: usize = 0; const DEFAULT_MAX_HEIGHT: usize = 99;
65const DEFAULT_MAX_WIDTH: usize = 500;
66const MAX_LINES: usize = 10000;
67
68#[derive(Debug, Clone)]
70pub struct PasteMsg(pub String);
71
72#[derive(Debug, Clone)]
74pub struct PasteErrMsg(pub String);
75
76#[derive(Debug, Clone, Default)]
79pub struct LineInfo {
80 pub width: usize,
82 pub char_width: usize,
84 pub height: usize,
86 pub start_column: usize,
88 pub column_offset: usize,
90 pub row_offset: usize,
92 pub char_offset: usize,
94}
95
96#[derive(Debug)]
99pub struct Model {
100 pub err: Option<String>,
103
104 cache: MemoizedWrap,
106
107 pub prompt: String,
110 pub placeholder: String,
112 pub show_line_numbers: bool,
114 pub end_of_buffer_character: char,
116
117 pub key_map: TextareaKeyMap,
120
121 pub focused_style: TextareaStyle,
124 pub blurred_style: TextareaStyle,
126 current_style: TextareaStyle,
128
129 pub cursor: cursor::Model,
132
133 pub char_limit: usize,
136 pub max_height: usize,
138 pub max_width: usize,
140
141 prompt_func: Option<fn(usize) -> String>,
143 prompt_width: usize,
145
146 width: usize,
149 height: usize,
151
152 value: Vec<Vec<char>>,
155
156 focus: bool,
159 col: usize,
161 row: usize,
163 last_char_offset: usize,
165
166 viewport: viewport::Model,
168}
169
170impl Model {
171 pub fn new() -> Self {
173 let vp = viewport::Model::new(0, 0);
174 let cur = cursor::Model::new();
177
178 let (focused_style, blurred_style) = default_styles();
179
180 let mut model = Self {
181 err: None,
182 cache: MemoizedWrap::new(),
183 prompt: format!("{} ", lipgloss::thick_border().left),
184 placeholder: String::new(),
185 show_line_numbers: true,
186 end_of_buffer_character: ' ',
187 key_map: TextareaKeyMap::default(),
188 focused_style: focused_style.clone(),
189 blurred_style: blurred_style.clone(),
190 current_style: blurred_style, cursor: cur,
192 char_limit: DEFAULT_CHAR_LIMIT,
193 max_height: DEFAULT_MAX_HEIGHT,
194 max_width: DEFAULT_MAX_WIDTH,
195 prompt_func: None,
196 prompt_width: 0,
197 width: DEFAULT_WIDTH,
198 height: DEFAULT_HEIGHT,
199 value: vec![vec![]; MIN_HEIGHT],
200 focus: false,
201 col: 0,
202 row: 0,
203 last_char_offset: 0,
204 viewport: vp,
205 };
206
207 model.value.reserve(MAX_LINES);
209 model.set_height(DEFAULT_HEIGHT);
210 model.set_width(DEFAULT_WIDTH);
211
212 model
213 }
214
215 pub fn set_value(&mut self, s: impl Into<String>) {
217 self.reset();
218 self.insert_string(s.into());
219 self.row = self.value.len().saturating_sub(1);
221 if let Some(line) = self.value.get(self.row) {
222 self.set_cursor(line.len());
223 }
224 }
225
226 pub fn insert_string(&mut self, s: impl Into<String>) {
228 let s = s.into();
229 let runes: Vec<char> = s.chars().collect();
230 self.insert_runes_from_user_input(runes);
231 }
232
233 pub fn insert_rune(&mut self, r: char) {
235 self.insert_runes_from_user_input(vec![r]);
236 }
237
238 pub fn value(&self) -> String {
240 if self.value.is_empty() {
241 return String::new();
242 }
243
244 let mut result = String::new();
245 for (i, line) in self.value.iter().enumerate() {
246 if i > 0 {
247 result.push('\n');
248 }
249 result.extend(line.iter());
250 }
251 result
252 }
253
254 pub fn length(&self) -> usize {
256 let mut l = 0;
257 for row in &self.value {
258 l += row
259 .iter()
260 .map(|&ch| UnicodeWidthChar::width(ch).unwrap_or(0))
261 .sum::<usize>();
262 }
263 l + self.value.len().saturating_sub(1)
265 }
266
267 pub fn line_count(&self) -> usize {
269 self.value.len()
270 }
271
272 pub fn line(&self) -> usize {
274 self.row
275 }
276
277 pub fn focused(&self) -> bool {
279 self.focus
280 }
281
282 pub fn reset(&mut self) {
284 self.value = vec![vec![]; MIN_HEIGHT];
285 self.value.reserve(MAX_LINES);
286 self.col = 0;
287 self.row = 0;
288 self.viewport.goto_top();
289 self.set_cursor(0);
290 }
291
292 pub fn width(&self) -> usize {
294 self.width
295 }
296
297 pub fn height(&self) -> usize {
299 self.height
300 }
301
302 pub fn set_width(&mut self, w: usize) {
304 if self.prompt_func.is_none() {
306 self.prompt_width = self.prompt.width();
307 }
308
309 let reserved_outer = 0; let mut reserved_inner = self.prompt_width;
314
315 if self.show_line_numbers {
317 let ln_width = 4; reserved_inner += ln_width;
319 }
320
321 let min_width = reserved_inner + reserved_outer + 1;
323 let mut input_width = w.max(min_width);
324
325 if self.max_width > 0 {
327 input_width = input_width.min(self.max_width);
328 }
329
330 self.viewport.width = input_width.saturating_sub(reserved_outer);
331 self.width = input_width
332 .saturating_sub(reserved_outer)
333 .saturating_sub(reserved_inner);
334 }
335
336 pub fn set_height(&mut self, h: usize) {
338 if self.max_height > 0 {
339 self.height = clamp(h, MIN_HEIGHT, self.max_height);
340 self.viewport.height = clamp(h, MIN_HEIGHT, self.max_height);
341 } else {
342 self.height = h.max(MIN_HEIGHT);
343 self.viewport.height = h.max(MIN_HEIGHT);
344 }
345 }
346
347 pub fn set_prompt_func(&mut self, prompt_width: usize, func: fn(usize) -> String) {
350 self.prompt_func = Some(func);
351 self.prompt_width = prompt_width;
352 }
353
354 pub fn set_cursor(&mut self, col: usize) {
356 self.col = clamp(
357 col,
358 0,
359 self.value.get(self.row).map_or(0, |line| line.len()),
360 );
361 self.last_char_offset = 0;
363 }
364
365 pub fn cursor_start(&mut self) {
367 self.set_cursor(0);
368 }
369
370 pub fn cursor_end(&mut self) {
372 if let Some(line) = self.value.get(self.row) {
373 self.set_cursor(line.len());
374 }
375 }
376
377 pub fn cursor_down(&mut self) {
379 let li = self.line_info();
380 let char_offset = self.last_char_offset.max(li.char_offset);
381 self.last_char_offset = char_offset;
382
383 if li.row_offset + 1 >= li.height && self.row < self.value.len().saturating_sub(1) {
384 self.row += 1;
385 self.col = 0;
386 } else {
387 const TRAILING_SPACE: usize = 2;
389 if let Some(line) = self.value.get(self.row) {
390 self.col =
391 (li.start_column + li.width + TRAILING_SPACE).min(line.len().saturating_sub(1));
392 }
393 }
394
395 let nli = self.line_info();
396 self.col = nli.start_column;
397
398 if nli.width == 0 {
399 return;
400 }
401
402 let mut offset = 0;
403 while offset < char_offset {
404 if self.row >= self.value.len()
405 || self.col >= self.value.get(self.row).map_or(0, |line| line.len())
406 || offset >= nli.char_width.saturating_sub(1)
407 {
408 break;
409 }
410 if let Some(line) = self.value.get(self.row) {
411 if let Some(&ch) = line.get(self.col) {
412 offset += UnicodeWidthChar::width(ch).unwrap_or(0);
413 }
414 }
415 self.col += 1;
416 }
417 }
418
419 pub fn cursor_up(&mut self) {
421 let li = self.line_info();
422 let char_offset = self.last_char_offset.max(li.char_offset);
423 self.last_char_offset = char_offset;
424
425 if li.row_offset == 0 && self.row > 0 {
426 self.row -= 1;
427 if let Some(line) = self.value.get(self.row) {
428 self.col = line.len();
429 }
430 } else {
431 const TRAILING_SPACE: usize = 2;
433 self.col = li.start_column.saturating_sub(TRAILING_SPACE);
434 }
435
436 let nli = self.line_info();
437 self.col = nli.start_column;
438
439 if nli.width == 0 {
440 return;
441 }
442
443 let mut offset = 0;
444 while offset < char_offset {
445 if let Some(line) = self.value.get(self.row) {
446 if self.col >= line.len() || offset >= nli.char_width.saturating_sub(1) {
447 break;
448 }
449 if let Some(&ch) = line.get(self.col) {
450 offset += UnicodeWidthChar::width(ch).unwrap_or(0);
451 }
452 self.col += 1;
453 } else {
454 break;
455 }
456 }
457 }
458
459 pub fn move_to_begin(&mut self) {
461 self.row = 0;
462 self.set_cursor(0);
463 }
464
465 pub fn move_to_end(&mut self) {
467 self.row = self.value.len().saturating_sub(1);
468 if let Some(line) = self.value.get(self.row) {
469 self.set_cursor(line.len());
470 }
471 }
472
473 fn insert_runes_from_user_input(&mut self, mut runes: Vec<char>) {
477 runes = self.sanitize_runes(runes);
479
480 if self.char_limit > 0 {
481 let avail_space = self.char_limit.saturating_sub(self.length());
482 if avail_space == 0 {
483 return;
484 }
485 if avail_space < runes.len() {
486 runes.truncate(avail_space);
487 }
488 }
489
490 let mut lines = Vec::new();
492 let mut lstart = 0;
493
494 for (i, &r) in runes.iter().enumerate() {
495 if r == '\n' {
496 lines.push(runes[lstart..i].to_vec());
497 lstart = i + 1;
498 }
499 }
500
501 if lstart <= runes.len() {
502 lines.push(runes[lstart..].to_vec());
503 }
504
505 if MAX_LINES > 0 && self.value.len() + lines.len() - 1 > MAX_LINES {
507 let allowed_height = (MAX_LINES - self.value.len() + 1).max(0);
508 lines.truncate(allowed_height);
509 }
510
511 if lines.is_empty() {
512 return;
513 }
514
515 while self.row >= self.value.len() {
517 self.value.push(Vec::new());
518 }
519
520 let tail = if self.col < self.value[self.row].len() {
522 self.value[self.row][self.col..].to_vec()
523 } else {
524 Vec::new()
525 };
526
527 if self.col <= self.value[self.row].len() {
529 self.value[self.row].truncate(self.col);
530 }
531 self.value[self.row].extend_from_slice(&lines[0]);
532 self.col += lines[0].len();
533
534 if lines.len() > 1 {
535 for (i, line) in lines[1..].iter().enumerate() {
537 self.value.insert(self.row + 1 + i, line.clone());
538 }
539 self.row += lines.len() - 1;
541 self.col = lines.last().map(|l| l.len()).unwrap_or(0);
542 self.value[self.row].extend_from_slice(&tail);
544 } else {
545 self.value[self.row].extend_from_slice(&tail);
547 }
548
549 self.set_cursor(self.col);
550 }
551
552 fn sanitize_runes(&self, runes: Vec<char>) -> Vec<char> {
554 runes
556 }
557
558 pub fn line_info(&mut self) -> LineInfo {
561 if self.row >= self.value.len() {
562 return LineInfo::default();
563 }
564
565 let current_line = self.value[self.row].clone();
567 let width = self.width;
568 let grid = self.cache.wrap(¤t_line, width);
569
570 let mut counter = 0;
572 for (i, line) in grid.iter().enumerate() {
573 if counter + line.len() == self.col && i + 1 < grid.len() {
575 return LineInfo {
577 char_offset: 0,
578 column_offset: 0,
579 height: grid.len(),
580 row_offset: i + 1,
581 start_column: self.col,
582 width: grid.get(i + 1).map_or(0, |l| l.len()),
583 char_width: line
584 .iter()
585 .map(|&ch| UnicodeWidthChar::width(ch).unwrap_or(0))
586 .sum(),
587 };
588 }
589
590 if counter + line.len() >= self.col {
591 let col_in_line = self.col.saturating_sub(counter);
592 let char_off: usize = line[..col_in_line.min(line.len())]
593 .iter()
594 .map(|&ch| UnicodeWidthChar::width(ch).unwrap_or(0))
595 .sum();
596 return LineInfo {
597 char_offset: char_off,
598 column_offset: col_in_line, height: grid.len(),
600 row_offset: i,
601 start_column: counter,
602 width: line.len(),
603 char_width: line
604 .iter()
605 .map(|&ch| UnicodeWidthChar::width(ch).unwrap_or(0))
606 .sum(),
607 };
608 }
609
610 counter += line.len();
611 }
612
613 LineInfo::default()
614 }
615
616 pub fn delete_before_cursor(&mut self) {
618 if let Some(line) = self.value.get_mut(self.row) {
619 let tail = if self.col <= line.len() {
620 line[self.col..].to_vec()
621 } else {
622 Vec::new()
623 };
624 *line = tail;
625 }
626 self.set_cursor(0);
627 }
628
629 pub fn delete_after_cursor(&mut self) {
631 if let Some(line) = self.value.get_mut(self.row) {
632 line.truncate(self.col);
633 let line_len = line.len();
634 self.set_cursor(line_len);
635 }
636 }
637
638 pub fn delete_character_backward(&mut self) {
640 self.col = clamp(
641 self.col,
642 0,
643 self.value.get(self.row).map_or(0, |line| line.len()),
644 );
645 if self.col == 0 {
646 self.merge_line_above(self.row);
647 return;
648 }
649
650 if let Some(line) = self.value.get_mut(self.row) {
651 if !line.is_empty() && self.col > 0 {
652 line.remove(self.col - 1);
653 self.set_cursor(self.col - 1);
654 }
655 }
656 }
657
658 pub fn delete_character_forward(&mut self) {
660 if let Some(line) = self.value.get_mut(self.row) {
661 if !line.is_empty() && self.col < line.len() {
662 line.remove(self.col);
663 }
664 }
665
666 if self.col >= self.value.get(self.row).map_or(0, |line| line.len()) {
667 self.merge_line_below(self.row);
668 }
669 }
670
671 pub fn delete_word_backward(&mut self) {
673 if self.col == 0 {
674 self.merge_line_above(self.row);
675 return;
676 }
677
678 let line = if let Some(line) = self.value.get(self.row) {
679 line.clone()
680 } else {
681 return;
682 };
683
684 if line.is_empty() {
685 return;
686 }
687
688 let mut start = self.col;
690 let mut end = self.col;
691
692 while end < line.len() && line.get(end).is_some_and(|&c| !c.is_whitespace()) {
694 end += 1;
695 }
696
697 while start > 0 && line.get(start - 1).is_some_and(|&c| !c.is_whitespace()) {
699 start -= 1;
700 }
701
702 if self.col < line.len() && line.get(self.col).is_some_and(|&c| !c.is_whitespace()) {
704 if start > 0 && line.get(start - 1).is_some_and(|&c| c.is_whitespace()) {
706 start -= 1;
707 }
708 }
709
710 if let Some(line_mut) = self.value.get_mut(self.row) {
711 let end_clamped = end.min(line_mut.len());
712 let start_clamped = start.min(end_clamped);
713 line_mut.drain(start_clamped..end_clamped);
714 }
715
716 self.set_cursor(start);
717 }
718
719 pub fn delete_word_forward(&mut self) {
721 let line = if let Some(line) = self.value.get(self.row) {
722 line.clone()
723 } else {
724 return;
725 };
726
727 if self.col >= line.len() || line.is_empty() {
728 self.merge_line_below(self.row);
729 return;
730 }
731
732 let old_col = self.col;
733 let mut new_col = self.col;
734
735 while new_col < line.len() {
737 if let Some(&ch) = line.get(new_col) {
738 if ch.is_whitespace() {
739 new_col += 1;
740 } else {
741 break;
742 }
743 } else {
744 break;
745 }
746 }
747
748 while new_col < line.len() {
750 if let Some(&ch) = line.get(new_col) {
751 if !ch.is_whitespace() {
752 new_col += 1;
753 } else {
754 break;
755 }
756 } else {
757 break;
758 }
759 }
760
761 if let Some(line) = self.value.get_mut(self.row) {
763 if new_col > line.len() {
764 line.truncate(old_col);
765 } else {
766 line.drain(old_col..new_col);
767 }
768 }
769
770 self.set_cursor(old_col);
771 }
772
773 fn merge_line_below(&mut self, row: usize) {
775 if row >= self.value.len().saturating_sub(1) {
776 return;
777 }
778
779 if let Some(next_line) = self.value.get(row + 1).cloned() {
781 if let Some(current_line) = self.value.get_mut(row) {
782 current_line.extend_from_slice(&next_line);
783 }
784 }
785
786 self.value.remove(row + 1);
788 }
789
790 fn merge_line_above(&mut self, row: usize) {
792 if row == 0 {
793 return;
794 }
795
796 if let Some(prev_line) = self.value.get(row - 1) {
797 self.col = prev_line.len();
798 }
799 self.row = row - 1;
800
801 if let Some(current_line) = self.value.get(row).cloned() {
803 if let Some(prev_line) = self.value.get_mut(row - 1) {
804 prev_line.extend_from_slice(¤t_line);
805 }
806 }
807
808 self.value.remove(row);
810 }
811
812 fn split_line(&mut self, row: usize, col: usize) {
814 if let Some(line) = self.value.get(row) {
815 let head = line[..col].to_vec();
816 let tail = line[col..].to_vec();
817
818 self.value[row] = head;
820
821 self.value.insert(row + 1, tail);
823
824 self.col = 0;
825 self.row += 1;
826 }
827 }
828
829 pub fn insert_newline(&mut self) {
831 if self.max_height > 0 && self.value.len() >= self.max_height {
832 return;
833 }
834
835 self.col = clamp(
836 self.col,
837 0,
838 self.value.get(self.row).map_or(0, |line| line.len()),
839 );
840 self.split_line(self.row, self.col);
841 }
842
843 pub fn character_left(&mut self, inside_line: bool) {
845 if self.col == 0 && self.row != 0 {
846 self.row -= 1;
847 if let Some(line) = self.value.get(self.row) {
848 self.col = line.len();
849 if !inside_line {
850 return;
851 }
852 }
853 }
854 if self.col > 0 {
855 self.set_cursor(self.col - 1);
856 }
857 }
858
859 pub fn character_right(&mut self) {
861 if let Some(line) = self.value.get(self.row) {
862 if self.col < line.len() {
863 self.set_cursor(self.col + 1);
864 } else if self.row < self.value.len() - 1 {
865 self.row += 1;
866 self.cursor_start();
867 }
868 }
869 }
870
871 pub fn word_left(&mut self) {
873 while self.col > 0 {
875 if let Some(line) = self.value.get(self.row) {
876 if line.get(self.col - 1).is_some_and(|c| c.is_whitespace()) {
877 self.set_cursor(self.col - 1);
878 } else {
879 break;
880 }
881 } else {
882 break;
883 }
884 }
885 while self.col > 0 {
887 if let Some(line) = self.value.get(self.row) {
888 if line.get(self.col - 1).is_some_and(|c| !c.is_whitespace()) {
889 self.set_cursor(self.col - 1);
890 } else {
891 break;
892 }
893 } else {
894 break;
895 }
896 }
897 }
898
899 pub fn word_right(&mut self) {
901 self.do_word_right(|_, _| {});
902 }
903
904 fn do_word_right<F>(&mut self, mut func: F)
906 where
907 F: FnMut(usize, usize),
908 {
909 if self.row >= self.value.len() {
910 return;
911 }
912
913 let line = match self.value.get(self.row) {
914 Some(line) => line.clone(),
915 None => return,
916 };
917
918 if self.col >= line.len() {
919 return;
920 }
921
922 let mut pos = self.col;
923 let mut char_idx = 0;
924
925 while pos < line.len() && line[pos].is_whitespace() {
927 pos += 1;
928 }
929
930 while pos < line.len() && !line[pos].is_whitespace() {
932 func(char_idx, pos);
933 pos += 1;
934 char_idx += 1;
935 }
936
937 self.set_cursor(pos);
939 }
940
941 pub fn uppercase_right(&mut self) {
943 let start_col = self.col;
944 let start_row = self.row;
945
946 self.word_right(); let end_col = self.col;
949
950 if let Some(line) = self.value.get_mut(start_row) {
952 let end_idx = end_col.min(line.len());
953 if let Some(slice) = line.get_mut(start_col..end_idx) {
954 for ch in slice.iter_mut() {
955 *ch = ch.to_uppercase().next().unwrap_or(*ch);
956 }
957 }
958 }
959 }
960
961 pub fn lowercase_right(&mut self) {
963 let start_col = self.col;
964 let start_row = self.row;
965
966 self.word_right(); let end_col = self.col;
969
970 if let Some(line) = self.value.get_mut(start_row) {
972 let end_idx = end_col.min(line.len());
973 if let Some(slice) = line.get_mut(start_col..end_idx) {
974 for ch in slice.iter_mut() {
975 *ch = ch.to_lowercase().next().unwrap_or(*ch);
976 }
977 }
978 }
979 }
980
981 pub fn capitalize_right(&mut self) {
983 let start_col = self.col;
984 let start_row = self.row;
985
986 self.word_right(); let end_col = self.col;
989
990 if let Some(line) = self.value.get_mut(start_row) {
992 let end_idx = end_col.min(line.len());
993 if let Some(slice) = line.get_mut(start_col..end_idx) {
994 for (i, ch) in slice.iter_mut().enumerate() {
995 if i == 0 {
996 *ch = ch.to_uppercase().next().unwrap_or(*ch);
997 }
998 }
999 }
1000 }
1001 }
1002
1003 pub fn transpose_left(&mut self) {
1005 let row = self.row;
1006 let mut col = self.col;
1007
1008 if let Some(line) = self.value.get_mut(row) {
1009 if col == 0 || line.len() < 2 {
1010 return;
1011 }
1012
1013 if col >= line.len() {
1014 col -= 1;
1015 self.col = col;
1016 }
1017
1018 if col > 0 && col < line.len() {
1019 line.swap(col - 1, col);
1020 if col < line.len() {
1021 self.col = col + 1;
1022 }
1023 }
1024 }
1025 }
1026
1027 pub fn view(&self) -> String {
1029 if self.value.is_empty() || (self.value.len() == 1 && self.value[0].is_empty()) {
1031 return self.placeholder_view();
1032 }
1033
1034 let mut lines = Vec::new();
1035 let style = &self.current_style;
1036
1037 let start_line = self.viewport.y_offset;
1039 let end_line = (start_line + self.height).min(self.value.len());
1040
1041 for (line_idx, line) in self
1042 .value
1043 .iter()
1044 .enumerate()
1045 .skip(start_line)
1046 .take(end_line - start_line)
1047 {
1048 let mut line_str = String::new();
1049
1050 if let Some(prompt_func) = self.prompt_func {
1052 line_str.push_str(&style.computed_prompt().render(&prompt_func(line_idx + 1)));
1053 } else {
1054 line_str.push_str(&style.computed_prompt().render(&self.prompt));
1055 }
1056
1057 if self.show_line_numbers {
1059 let line_num = format!("{:>3} ", line_idx + 1);
1060 line_str.push_str(&style.computed_line_number().render(&line_num));
1061 }
1062
1063 let mut cache = self.cache.clone();
1065 let wrapped_lines = cache.wrap(line, self.width);
1066
1067 for (wrap_idx, wrapped_line) in wrapped_lines.iter().enumerate() {
1068 let mut display_line = line_str.clone();
1069
1070 if wrap_idx > 0 {
1071 if self.show_line_numbers {
1073 display_line =
1074 format!("{} ", style.computed_prompt().render(&self.prompt));
1075 } else {
1076 display_line = style.computed_prompt().render(&self.prompt);
1077 }
1078 }
1079
1080 let wrapped_content: String = wrapped_line.iter().collect();
1081
1082 if line_idx == self.row {
1084 display_line.push_str(&style.computed_cursor_line().render(&wrapped_content));
1085 } else {
1086 display_line.push_str(&style.computed_text().render(&wrapped_content));
1087 }
1088
1089 lines.push(display_line);
1090 }
1091 }
1092
1093 while lines.len() < self.height {
1095 let mut empty_line = String::new();
1096
1097 empty_line.push_str(&style.computed_prompt().render(&self.prompt));
1099
1100 if self.end_of_buffer_character != ' ' {
1102 empty_line.push_str(
1103 &style
1104 .computed_end_of_buffer()
1105 .render(&self.end_of_buffer_character.to_string()),
1106 );
1107 }
1108
1109 lines.push(empty_line);
1110 }
1111
1112 let content = lines.join("\n");
1114 let styled = style.base.render(&content);
1115 lipgloss::strip_ansi(&styled)
1116 }
1117
1118 fn placeholder_view(&self) -> String {
1120 if self.placeholder.is_empty() {
1121 return String::new();
1122 }
1123
1124 let mut lines = Vec::new();
1125 let style = &self.current_style;
1126
1127 let placeholder_lines: Vec<&str> = self.placeholder.lines().collect();
1129
1130 for (line_idx, &placeholder_line) in placeholder_lines.iter().enumerate() {
1131 let mut line_str = String::new();
1132
1133 if let Some(prompt_func) = self.prompt_func {
1135 line_str.push_str(&style.computed_prompt().render(&prompt_func(line_idx + 1)));
1136 } else {
1137 line_str.push_str(&style.computed_prompt().render(&self.prompt));
1138 }
1139
1140 if self.show_line_numbers {
1142 if line_idx == 0 {
1143 line_str.push_str(&style.computed_line_number().render(" 1 "));
1144 } else {
1145 line_str.push_str(&style.computed_line_number().render(" "));
1146 }
1147 }
1148
1149 let mut cache = self.cache.clone();
1151 let wrapped = cache.wrap(&placeholder_line.chars().collect::<Vec<_>>(), self.width);
1152
1153 for (wrap_idx, wrapped_line) in wrapped.iter().enumerate() {
1154 let mut display_line = line_str.clone();
1155
1156 if wrap_idx > 0 {
1157 if self.show_line_numbers {
1159 display_line =
1160 format!("{} ", style.computed_prompt().render(&self.prompt));
1161 } else {
1162 display_line = style.computed_prompt().render(&self.prompt);
1163 }
1164 }
1165
1166 let wrapped_content: String = wrapped_line.iter().collect();
1167 display_line.push_str(&style.computed_placeholder().render(&wrapped_content));
1168
1169 lines.push(display_line);
1170
1171 if lines.len() >= self.height {
1172 break;
1173 }
1174 }
1175
1176 if lines.len() >= self.height {
1177 break;
1178 }
1179 }
1180
1181 while lines.len() < self.height {
1183 let mut empty_line = String::new();
1184
1185 empty_line.push_str(&style.computed_prompt().render(&self.prompt));
1187
1188 if self.end_of_buffer_character != ' ' {
1190 empty_line.push_str(
1191 &style
1192 .computed_end_of_buffer()
1193 .render(&self.end_of_buffer_character.to_string()),
1194 );
1195 }
1196
1197 lines.push(empty_line);
1198 }
1199
1200 let content = lines.join("\n");
1202 let styled = style.base.render(&content);
1203 lipgloss::strip_ansi(&styled)
1204 }
1205
1206 pub fn scroll_down(&mut self, lines: usize) {
1208 self.viewport.set_y_offset(self.viewport.y_offset + lines);
1209 }
1210
1211 pub fn scroll_up(&mut self, lines: usize) {
1213 self.viewport
1214 .set_y_offset(self.viewport.y_offset.saturating_sub(lines));
1215 }
1216
1217 pub fn cursor_line_number(&mut self) -> usize {
1219 if self.row >= self.value.len() {
1220 return 0;
1221 }
1222
1223 let mut line_count = 0;
1225 for i in 0..self.row {
1226 if let Some(line) = self.value.get(i).cloned() {
1227 let wrapped_lines = self.cache.wrap(&line, self.width);
1228 line_count += wrapped_lines.len();
1229 }
1230 }
1231
1232 line_count += self.line_info().row_offset;
1234 line_count
1235 }
1236
1237 pub fn update(&mut self, msg: Option<bubbletea_rs::Msg>) -> Option<bubbletea_rs::Cmd> {
1239 if !self.focus {
1240 return None;
1241 }
1242
1243 if let Some(msg) = msg {
1244 if let Some(paste_msg) = msg.downcast_ref::<PasteMsg>() {
1246 self.insert_string(paste_msg.0.clone());
1247 return None;
1248 }
1249
1250 if let Some(_paste_err) = msg.downcast_ref::<PasteErrMsg>() {
1251 return None;
1253 }
1254
1255 if let Some(key_msg) = msg.downcast_ref::<bubbletea_rs::KeyMsg>() {
1257 return self.handle_key_msg(key_msg);
1258 }
1259
1260 let cursor_cmd = self.cursor.update(&msg);
1262 let viewport_cmd = self.viewport.update(msg);
1263
1264 cursor_cmd.or(viewport_cmd)
1266 } else {
1267 None
1268 }
1269 }
1270
1271 fn handle_key_msg(&mut self, key_msg: &bubbletea_rs::KeyMsg) -> Option<bubbletea_rs::Cmd> {
1273 use crate::key::matches_binding;
1274
1275 if matches_binding(key_msg, &self.key_map.character_forward) {
1277 self.character_right();
1278 } else if matches_binding(key_msg, &self.key_map.character_backward) {
1279 self.character_left(false);
1280
1281 } else if matches_binding(key_msg, &self.key_map.word_forward) {
1283 self.word_right();
1284 } else if matches_binding(key_msg, &self.key_map.word_backward) {
1285 self.word_left();
1286
1287 } else if matches_binding(key_msg, &self.key_map.line_next) {
1289 self.cursor_down();
1290 } else if matches_binding(key_msg, &self.key_map.line_previous) {
1291 self.cursor_up();
1292 } else if matches_binding(key_msg, &self.key_map.line_start) {
1293 self.cursor_start();
1294 } else if matches_binding(key_msg, &self.key_map.line_end) {
1295 self.cursor_end();
1296
1297 } else if matches_binding(key_msg, &self.key_map.input_begin) {
1299 self.move_to_begin();
1300 } else if matches_binding(key_msg, &self.key_map.input_end) {
1301 self.move_to_end();
1302
1303 } else if matches_binding(key_msg, &self.key_map.delete_character_backward) {
1305 self.delete_character_backward();
1306 } else if matches_binding(key_msg, &self.key_map.delete_character_forward) {
1307 self.delete_character_forward();
1308 } else if matches_binding(key_msg, &self.key_map.delete_word_backward) {
1309 self.delete_word_backward();
1310 } else if matches_binding(key_msg, &self.key_map.delete_word_forward) {
1311 self.delete_word_forward();
1312 } else if matches_binding(key_msg, &self.key_map.delete_after_cursor) {
1313 self.delete_after_cursor();
1314 } else if matches_binding(key_msg, &self.key_map.delete_before_cursor) {
1315 self.delete_before_cursor();
1316
1317 } else if matches_binding(key_msg, &self.key_map.insert_newline) {
1319 self.insert_newline();
1320
1321 } else if matches_binding(key_msg, &self.key_map.paste) {
1323 return Some(self.paste_command());
1324
1325 } else if matches_binding(key_msg, &self.key_map.uppercase_word_forward) {
1327 self.uppercase_right();
1328 } else if matches_binding(key_msg, &self.key_map.lowercase_word_forward) {
1329 self.lowercase_right();
1330 } else if matches_binding(key_msg, &self.key_map.capitalize_word_forward) {
1331 self.capitalize_right();
1332 } else if matches_binding(key_msg, &self.key_map.transpose_character_backward) {
1333 self.transpose_left();
1334 } else {
1335 if let Some(ch) = self.extract_character_from_key_msg(key_msg) {
1337 if ch.is_control() {
1338 return None;
1340 }
1341 self.insert_rune(ch);
1342 }
1343 }
1344
1345 None
1346 }
1347
1348 fn extract_character_from_key_msg(&self, _key_msg: &bubbletea_rs::KeyMsg) -> Option<char> {
1350 None
1354 }
1355
1356 fn paste_command(&self) -> bubbletea_rs::Cmd {
1358 bubbletea_rs::tick(
1359 std::time::Duration::from_nanos(1),
1360 |_| match Self::read_clipboard() {
1361 Ok(content) => Box::new(PasteMsg(content)) as bubbletea_rs::Msg,
1362 Err(err) => Box::new(PasteErrMsg(err)) as bubbletea_rs::Msg,
1363 },
1364 )
1365 }
1366
1367 fn read_clipboard() -> Result<String, String> {
1369 #[cfg(feature = "clipboard-support")]
1370 {
1371 use clipboard::{ClipboardContext, ClipboardProvider};
1372
1373 let mut ctx: ClipboardContext = ClipboardProvider::new()
1374 .map_err(|e| format!("Failed to create clipboard context: {}", e))?;
1375
1376 ctx.get_contents()
1377 .map_err(|e| format!("Failed to read clipboard: {}", e))
1378 }
1379 #[cfg(not(feature = "clipboard-support"))]
1380 {
1381 Err("Clipboard support not enabled".to_string())
1382 }
1383 }
1384
1385 pub fn copy_to_clipboard(&self, text: &str) -> Result<(), String> {
1387 #[cfg(feature = "clipboard-support")]
1388 {
1389 use clipboard::{ClipboardContext, ClipboardProvider};
1390
1391 let mut ctx: ClipboardContext = ClipboardProvider::new()
1392 .map_err(|e| format!("Failed to create clipboard context: {}", e))?;
1393
1394 ctx.set_contents(text.to_string())
1395 .map_err(|e| format!("Failed to write to clipboard: {}", e))
1396 }
1397 #[cfg(not(feature = "clipboard-support"))]
1398 {
1399 let _ = text; Err("Clipboard support not enabled".to_string())
1401 }
1402 }
1403
1404 pub fn copy_selection(&self) -> Result<(), String> {
1406 let content = self.value();
1409 self.copy_to_clipboard(&content)
1410 }
1411
1412 pub fn cut_selection(&mut self) -> Result<(), String> {
1414 let content = self.value();
1417 self.copy_to_clipboard(&content)?;
1418 self.reset();
1419 Ok(())
1420 }
1421}
1422
1423impl Default for Model {
1424 fn default() -> Self {
1425 Self::new()
1426 }
1427}
1428
1429impl Component for Model {
1431 fn focus(&mut self) -> Option<Cmd> {
1432 self.focus = true;
1433 self.current_style = self.focused_style.clone();
1434 self.cursor.focus()
1435 }
1436
1437 fn blur(&mut self) {
1438 self.focus = false;
1439 self.current_style = self.blurred_style.clone();
1440 self.cursor.blur();
1441 }
1442
1443 fn focused(&self) -> bool {
1444 self.focus
1445 }
1446}
1447
1448pub fn default_styles() -> (TextareaStyle, TextareaStyle) {
1450 let focused = default_focused_style();
1451 let blurred = default_blurred_style();
1452 (focused, blurred)
1453}
1454
1455pub fn new() -> Model {
1457 Model::new()
1458}