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 lipgloss_extras::lipgloss;
58use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
59
60const MIN_HEIGHT: usize = 1;
62const DEFAULT_HEIGHT: usize = 6;
63const DEFAULT_WIDTH: usize = 40;
64const DEFAULT_CHAR_LIMIT: usize = 0; const DEFAULT_MAX_HEIGHT: usize = 99;
66const DEFAULT_MAX_WIDTH: usize = 500;
67const MAX_LINES: usize = 10000;
68
69#[derive(Debug, Clone)]
71pub struct PasteMsg(pub String);
72
73#[derive(Debug, Clone)]
75pub struct PasteErrMsg(pub String);
76
77#[derive(Debug, Clone, Default)]
80pub struct LineInfo {
81 pub width: usize,
83 pub char_width: usize,
85 pub height: usize,
87 pub start_column: usize,
89 pub column_offset: usize,
91 pub row_offset: usize,
93 pub char_offset: usize,
95}
96
97#[derive(Debug)]
100pub struct Model {
101 pub err: Option<String>,
104
105 cache: MemoizedWrap,
107
108 pub prompt: String,
111 pub placeholder: String,
113 pub show_line_numbers: bool,
115 pub end_of_buffer_character: char,
117
118 pub key_map: TextareaKeyMap,
121
122 pub focused_style: TextareaStyle,
125 pub blurred_style: TextareaStyle,
127 current_style: TextareaStyle,
129
130 pub cursor: cursor::Model,
133
134 pub char_limit: usize,
137 pub max_height: usize,
139 pub max_width: usize,
141
142 prompt_func: Option<fn(usize) -> String>,
144 prompt_width: usize,
146
147 width: usize,
150 height: usize,
152
153 value: Vec<Vec<char>>,
156
157 focus: bool,
160 col: usize,
162 row: usize,
164 last_char_offset: usize,
166
167 viewport: viewport::Model,
169}
170
171impl Model {
172 pub fn new() -> Self {
174 let vp = viewport::Model::new(0, 0);
175 let cur = cursor::Model::new();
178
179 let (focused_style, blurred_style) = default_styles();
180
181 let mut model = Self {
182 err: None,
183 cache: MemoizedWrap::new(),
184 prompt: format!("{} ", lipgloss::thick_border().left),
185 placeholder: String::new(),
186 show_line_numbers: true,
187 end_of_buffer_character: ' ',
188 key_map: TextareaKeyMap::default(),
189 focused_style: focused_style.clone(),
190 blurred_style: blurred_style.clone(),
191 current_style: blurred_style, cursor: cur,
193 char_limit: DEFAULT_CHAR_LIMIT,
194 max_height: DEFAULT_MAX_HEIGHT,
195 max_width: DEFAULT_MAX_WIDTH,
196 prompt_func: None,
197 prompt_width: 0,
198 width: DEFAULT_WIDTH,
199 height: DEFAULT_HEIGHT,
200 value: vec![vec![]; MIN_HEIGHT],
201 focus: false,
202 col: 0,
203 row: 0,
204 last_char_offset: 0,
205 viewport: vp,
206 };
207
208 model.value.reserve(MAX_LINES);
210 model.set_height(DEFAULT_HEIGHT);
211 model.set_width(DEFAULT_WIDTH);
212
213 model
214 }
215
216 pub fn set_value(&mut self, s: impl Into<String>) {
218 self.reset();
219 self.insert_string(s.into());
220 self.row = self.value.len().saturating_sub(1);
222 if let Some(line) = self.value.get(self.row) {
223 self.set_cursor(line.len());
224 }
225 }
226
227 pub fn insert_string(&mut self, s: impl Into<String>) {
229 let s = s.into();
230 let runes: Vec<char> = s.chars().collect();
231 self.insert_runes_from_user_input(runes);
232 }
233
234 pub fn insert_rune(&mut self, r: char) {
236 self.insert_runes_from_user_input(vec![r]);
237 }
238
239 pub fn value(&self) -> String {
241 if self.value.is_empty() {
242 return String::new();
243 }
244
245 let mut result = String::new();
246 for (i, line) in self.value.iter().enumerate() {
247 if i > 0 {
248 result.push('\n');
249 }
250 result.extend(line.iter());
251 }
252 result
253 }
254
255 pub fn length(&self) -> usize {
257 let mut l = 0;
258 for row in &self.value {
259 l += row
260 .iter()
261 .map(|&ch| UnicodeWidthChar::width(ch).unwrap_or(0))
262 .sum::<usize>();
263 }
264 l + self.value.len().saturating_sub(1)
266 }
267
268 pub fn line_count(&self) -> usize {
270 self.value.len()
271 }
272
273 pub fn line(&self) -> usize {
275 self.row
276 }
277
278 pub fn focused(&self) -> bool {
280 self.focus
281 }
282
283 pub fn reset(&mut self) {
285 self.value = vec![vec![]; MIN_HEIGHT];
286 self.value.reserve(MAX_LINES);
287 self.col = 0;
288 self.row = 0;
289 self.viewport.goto_top();
290 self.set_cursor(0);
291 }
292
293 pub fn width(&self) -> usize {
295 self.width
296 }
297
298 pub fn height(&self) -> usize {
300 self.height
301 }
302
303 pub fn set_width(&mut self, w: usize) {
305 if self.prompt_func.is_none() {
307 self.prompt_width = self.prompt.width();
308 }
309
310 let reserved_outer = 0; let mut reserved_inner = self.prompt_width;
315
316 if self.show_line_numbers {
318 let ln_width = 4; reserved_inner += ln_width;
320 }
321
322 let min_width = reserved_inner + reserved_outer + 1;
324 let mut input_width = w.max(min_width);
325
326 if self.max_width > 0 {
328 input_width = input_width.min(self.max_width);
329 }
330
331 self.viewport.width = input_width.saturating_sub(reserved_outer);
332 self.width = input_width
333 .saturating_sub(reserved_outer)
334 .saturating_sub(reserved_inner);
335 }
336
337 pub fn set_height(&mut self, h: usize) {
339 if self.max_height > 0 {
340 self.height = clamp(h, MIN_HEIGHT, self.max_height);
341 self.viewport.height = clamp(h, MIN_HEIGHT, self.max_height);
342 } else {
343 self.height = h.max(MIN_HEIGHT);
344 self.viewport.height = h.max(MIN_HEIGHT);
345 }
346 }
347
348 pub fn set_prompt_func(&mut self, prompt_width: usize, func: fn(usize) -> String) {
351 self.prompt_func = Some(func);
352 self.prompt_width = prompt_width;
353 }
354
355 pub fn set_cursor(&mut self, col: usize) {
357 self.col = clamp(
358 col,
359 0,
360 self.value.get(self.row).map_or(0, |line| line.len()),
361 );
362 self.last_char_offset = 0;
364 }
365
366 pub fn cursor_start(&mut self) {
368 self.set_cursor(0);
369 }
370
371 pub fn cursor_end(&mut self) {
373 if let Some(line) = self.value.get(self.row) {
374 self.set_cursor(line.len());
375 }
376 }
377
378 pub fn cursor_down(&mut self) {
380 let li = self.line_info();
381 let char_offset = self.last_char_offset.max(li.char_offset);
382 self.last_char_offset = char_offset;
383
384 if li.row_offset + 1 >= li.height && self.row < self.value.len().saturating_sub(1) {
385 self.row += 1;
386 self.col = 0;
387 } else {
388 const TRAILING_SPACE: usize = 2;
390 if let Some(line) = self.value.get(self.row) {
391 self.col =
392 (li.start_column + li.width + TRAILING_SPACE).min(line.len().saturating_sub(1));
393 }
394 }
395
396 let nli = self.line_info();
397 self.col = nli.start_column;
398
399 if nli.width == 0 {
400 return;
401 }
402
403 let mut offset = 0;
404 while offset < char_offset {
405 if self.row >= self.value.len()
406 || self.col >= self.value.get(self.row).map_or(0, |line| line.len())
407 || offset >= nli.char_width.saturating_sub(1)
408 {
409 break;
410 }
411 if let Some(line) = self.value.get(self.row) {
412 if let Some(&ch) = line.get(self.col) {
413 offset += UnicodeWidthChar::width(ch).unwrap_or(0);
414 }
415 }
416 self.col += 1;
417 }
418 }
419
420 pub fn cursor_up(&mut self) {
422 let li = self.line_info();
423 let char_offset = self.last_char_offset.max(li.char_offset);
424 self.last_char_offset = char_offset;
425
426 if li.row_offset == 0 && self.row > 0 {
427 self.row -= 1;
428 if let Some(line) = self.value.get(self.row) {
429 self.col = line.len();
430 }
431 } else {
432 const TRAILING_SPACE: usize = 2;
434 self.col = li.start_column.saturating_sub(TRAILING_SPACE);
435 }
436
437 let nli = self.line_info();
438 self.col = nli.start_column;
439
440 if nli.width == 0 {
441 return;
442 }
443
444 let mut offset = 0;
445 while offset < char_offset {
446 if let Some(line) = self.value.get(self.row) {
447 if self.col >= line.len() || offset >= nli.char_width.saturating_sub(1) {
448 break;
449 }
450 if let Some(&ch) = line.get(self.col) {
451 offset += UnicodeWidthChar::width(ch).unwrap_or(0);
452 }
453 self.col += 1;
454 } else {
455 break;
456 }
457 }
458 }
459
460 pub fn move_to_begin(&mut self) {
462 self.row = 0;
463 self.set_cursor(0);
464 }
465
466 pub fn move_to_end(&mut self) {
468 self.row = self.value.len().saturating_sub(1);
469 if let Some(line) = self.value.get(self.row) {
470 self.set_cursor(line.len());
471 }
472 }
473
474 fn insert_runes_from_user_input(&mut self, mut runes: Vec<char>) {
478 runes = self.sanitize_runes(runes);
480
481 if self.char_limit > 0 {
482 let avail_space = self.char_limit.saturating_sub(self.length());
483 if avail_space == 0 {
484 return;
485 }
486 if avail_space < runes.len() {
487 runes.truncate(avail_space);
488 }
489 }
490
491 let mut lines = Vec::new();
493 let mut lstart = 0;
494
495 for (i, &r) in runes.iter().enumerate() {
496 if r == '\n' {
497 lines.push(runes[lstart..i].to_vec());
498 lstart = i + 1;
499 }
500 }
501
502 if lstart <= runes.len() {
503 lines.push(runes[lstart..].to_vec());
504 }
505
506 if MAX_LINES > 0 && self.value.len() + lines.len() - 1 > MAX_LINES {
508 let allowed_height = (MAX_LINES - self.value.len() + 1).max(0);
509 lines.truncate(allowed_height);
510 }
511
512 if lines.is_empty() {
513 return;
514 }
515
516 while self.row >= self.value.len() {
518 self.value.push(Vec::new());
519 }
520
521 let tail = if self.col < self.value[self.row].len() {
523 self.value[self.row][self.col..].to_vec()
524 } else {
525 Vec::new()
526 };
527
528 if self.col <= self.value[self.row].len() {
530 self.value[self.row].truncate(self.col);
531 }
532 self.value[self.row].extend_from_slice(&lines[0]);
533 self.col += lines[0].len();
534
535 if lines.len() > 1 {
536 for (i, line) in lines[1..].iter().enumerate() {
538 self.value.insert(self.row + 1 + i, line.clone());
539 }
540 self.row += lines.len() - 1;
542 self.col = lines.last().map(|l| l.len()).unwrap_or(0);
543 self.value[self.row].extend_from_slice(&tail);
545 } else {
546 self.value[self.row].extend_from_slice(&tail);
548 }
549
550 self.set_cursor(self.col);
551 }
552
553 fn sanitize_runes(&self, runes: Vec<char>) -> Vec<char> {
555 runes
557 }
558
559 pub fn line_info(&mut self) -> LineInfo {
562 if self.row >= self.value.len() {
563 return LineInfo::default();
564 }
565
566 let current_line = self.value[self.row].clone();
568 let width = self.width;
569 let grid = self.cache.wrap(¤t_line, width);
570
571 let mut counter = 0;
573 for (i, line) in grid.iter().enumerate() {
574 if counter + line.len() == self.col && i + 1 < grid.len() {
576 let next_line = &grid[i + 1];
578 return LineInfo {
579 char_offset: 0,
580 column_offset: 0,
581 height: grid.len(),
582 row_offset: i + 1,
583 start_column: self.col,
584 width: next_line.len(),
585 char_width: next_line
586 .iter()
587 .map(|&ch| UnicodeWidthChar::width(ch).unwrap_or(0))
588 .sum(),
589 };
590 }
591
592 if counter + line.len() >= self.col {
594 let col_in_line = self.col.saturating_sub(counter);
595 let char_off: usize = line
596 .iter()
597 .take(col_in_line.min(line.len()))
598 .map(|&ch| UnicodeWidthChar::width(ch).unwrap_or(0))
599 .sum();
600
601 return LineInfo {
602 char_offset: char_off,
603 column_offset: col_in_line,
604 height: grid.len(),
605 row_offset: i,
606 start_column: counter,
607 width: line.len(),
608 char_width: line
609 .iter()
610 .map(|&ch| UnicodeWidthChar::width(ch).unwrap_or(0))
611 .sum(),
612 };
613 }
614
615 counter += line.len();
616 }
617
618 if let Some(last_line) = grid.last() {
620 let last_counter = counter - last_line.len();
621 return LineInfo {
622 char_offset: last_line
623 .iter()
624 .map(|&ch| UnicodeWidthChar::width(ch).unwrap_or(0))
625 .sum(),
626 column_offset: last_line.len(),
627 height: grid.len(),
628 row_offset: grid.len().saturating_sub(1),
629 start_column: last_counter,
630 width: last_line.len(),
631 char_width: last_line
632 .iter()
633 .map(|&ch| UnicodeWidthChar::width(ch).unwrap_or(0))
634 .sum(),
635 };
636 }
637
638 LineInfo::default()
639 }
640
641 pub fn delete_before_cursor(&mut self) {
643 if let Some(line) = self.value.get_mut(self.row) {
644 let tail = if self.col <= line.len() {
645 line[self.col..].to_vec()
646 } else {
647 Vec::new()
648 };
649 *line = tail;
650 }
651 self.set_cursor(0);
652 }
653
654 pub fn delete_after_cursor(&mut self) {
656 if let Some(line) = self.value.get_mut(self.row) {
657 line.truncate(self.col);
658 let line_len = line.len();
659 self.set_cursor(line_len);
660 }
661 }
662
663 pub fn delete_character_backward(&mut self) {
665 self.col = clamp(
666 self.col,
667 0,
668 self.value.get(self.row).map_or(0, |line| line.len()),
669 );
670 if self.col == 0 {
671 self.merge_line_above(self.row);
672 return;
673 }
674
675 if let Some(line) = self.value.get_mut(self.row) {
676 if !line.is_empty() && self.col > 0 {
677 line.remove(self.col - 1);
678 self.set_cursor(self.col - 1);
679 }
680 }
681 }
682
683 pub fn delete_character_forward(&mut self) {
685 if let Some(line) = self.value.get_mut(self.row) {
686 if !line.is_empty() && self.col < line.len() {
687 line.remove(self.col);
688 }
689 }
690
691 if self.col >= self.value.get(self.row).map_or(0, |line| line.len()) {
692 self.merge_line_below(self.row);
693 }
694 }
695
696 pub fn delete_word_backward(&mut self) {
698 if self.col == 0 {
699 self.merge_line_above(self.row);
700 return;
701 }
702
703 let line = if let Some(line) = self.value.get(self.row) {
704 line.clone()
705 } else {
706 return;
707 };
708
709 if line.is_empty() {
710 return;
711 }
712
713 let mut start = self.col;
715 let mut end = self.col;
716
717 while end < line.len() && line.get(end).is_some_and(|&c| !c.is_whitespace()) {
719 end += 1;
720 }
721
722 while start > 0 && line.get(start - 1).is_some_and(|&c| !c.is_whitespace()) {
724 start -= 1;
725 }
726
727 if self.col < line.len() && line.get(self.col).is_some_and(|&c| !c.is_whitespace()) {
729 if start > 0 && line.get(start - 1).is_some_and(|&c| c.is_whitespace()) {
731 start -= 1;
732 }
733 }
734
735 if let Some(line_mut) = self.value.get_mut(self.row) {
736 let end_clamped = end.min(line_mut.len());
737 let start_clamped = start.min(end_clamped);
738 line_mut.drain(start_clamped..end_clamped);
739 }
740
741 self.set_cursor(start);
742 }
743
744 pub fn delete_word_forward(&mut self) {
746 let line = if let Some(line) = self.value.get(self.row) {
747 line.clone()
748 } else {
749 return;
750 };
751
752 if self.col >= line.len() || line.is_empty() {
753 self.merge_line_below(self.row);
754 return;
755 }
756
757 let old_col = self.col;
758 let mut new_col = self.col;
759
760 while new_col < line.len() {
762 if let Some(&ch) = line.get(new_col) {
763 if ch.is_whitespace() {
764 new_col += 1;
765 } else {
766 break;
767 }
768 } else {
769 break;
770 }
771 }
772
773 while new_col < line.len() {
775 if let Some(&ch) = line.get(new_col) {
776 if !ch.is_whitespace() {
777 new_col += 1;
778 } else {
779 break;
780 }
781 } else {
782 break;
783 }
784 }
785
786 if let Some(line) = self.value.get_mut(self.row) {
788 if new_col > line.len() {
789 line.truncate(old_col);
790 } else {
791 line.drain(old_col..new_col);
792 }
793 }
794
795 self.set_cursor(old_col);
796 }
797
798 fn merge_line_below(&mut self, row: usize) {
800 if row >= self.value.len().saturating_sub(1) {
801 return;
802 }
803
804 if let Some(next_line) = self.value.get(row + 1).cloned() {
806 if let Some(current_line) = self.value.get_mut(row) {
807 current_line.extend_from_slice(&next_line);
808 }
809 }
810
811 self.value.remove(row + 1);
813 }
814
815 fn merge_line_above(&mut self, row: usize) {
817 if row == 0 {
818 return;
819 }
820
821 if let Some(prev_line) = self.value.get(row - 1) {
822 self.col = prev_line.len();
823 }
824 self.row = row - 1;
825
826 if let Some(current_line) = self.value.get(row).cloned() {
828 if let Some(prev_line) = self.value.get_mut(row - 1) {
829 prev_line.extend_from_slice(¤t_line);
830 }
831 }
832
833 self.value.remove(row);
835 }
836
837 fn split_line(&mut self, row: usize, col: usize) {
839 if let Some(line) = self.value.get(row) {
840 let head = line[..col].to_vec();
841 let tail = line[col..].to_vec();
842
843 self.value[row] = head;
845
846 self.value.insert(row + 1, tail);
848
849 self.col = 0;
850 self.row += 1;
851 }
852 }
853
854 pub fn insert_newline(&mut self) {
856 if self.max_height > 0 && self.value.len() >= self.max_height {
857 return;
858 }
859
860 self.col = clamp(
861 self.col,
862 0,
863 self.value.get(self.row).map_or(0, |line| line.len()),
864 );
865 self.split_line(self.row, self.col);
866 }
867
868 pub fn character_left(&mut self, inside_line: bool) {
870 if self.col == 0 && self.row != 0 {
871 self.row -= 1;
872 if let Some(line) = self.value.get(self.row) {
873 self.col = line.len();
874 if !inside_line {
875 return;
876 }
877 }
878 }
879 if self.col > 0 {
880 self.set_cursor(self.col - 1);
881 }
882 }
883
884 pub fn character_right(&mut self) {
886 if let Some(line) = self.value.get(self.row) {
887 if self.col < line.len() {
888 self.set_cursor(self.col + 1);
889 } else if self.row < self.value.len() - 1 {
890 self.row += 1;
891 self.cursor_start();
892 }
893 }
894 }
895
896 pub fn word_left(&mut self) {
898 while self.col > 0 {
900 if let Some(line) = self.value.get(self.row) {
901 if line.get(self.col - 1).is_some_and(|c| c.is_whitespace()) {
902 self.set_cursor(self.col - 1);
903 } else {
904 break;
905 }
906 } else {
907 break;
908 }
909 }
910 while self.col > 0 {
912 if let Some(line) = self.value.get(self.row) {
913 if line.get(self.col - 1).is_some_and(|c| !c.is_whitespace()) {
914 self.set_cursor(self.col - 1);
915 } else {
916 break;
917 }
918 } else {
919 break;
920 }
921 }
922 }
923
924 pub fn word_right(&mut self) {
926 self.do_word_right(|_, _| {});
927 }
928
929 fn do_word_right<F>(&mut self, mut func: F)
931 where
932 F: FnMut(usize, usize),
933 {
934 if self.row >= self.value.len() {
935 return;
936 }
937
938 let line = match self.value.get(self.row) {
939 Some(line) => line.clone(),
940 None => return,
941 };
942
943 if self.col >= line.len() {
944 return;
945 }
946
947 let mut pos = self.col;
948 let mut char_idx = 0;
949
950 while pos < line.len() && line[pos].is_whitespace() {
952 pos += 1;
953 }
954
955 while pos < line.len() && !line[pos].is_whitespace() {
957 func(char_idx, pos);
958 pos += 1;
959 char_idx += 1;
960 }
961
962 self.set_cursor(pos);
964 }
965
966 pub fn uppercase_right(&mut self) {
968 let start_col = self.col;
969 let start_row = self.row;
970
971 self.word_right(); let end_col = self.col;
974
975 if let Some(line) = self.value.get_mut(start_row) {
977 let end_idx = end_col.min(line.len());
978 if let Some(slice) = line.get_mut(start_col..end_idx) {
979 for ch in slice.iter_mut() {
980 *ch = ch.to_uppercase().next().unwrap_or(*ch);
981 }
982 }
983 }
984 }
985
986 pub fn lowercase_right(&mut self) {
988 let start_col = self.col;
989 let start_row = self.row;
990
991 self.word_right(); let end_col = self.col;
994
995 if let Some(line) = self.value.get_mut(start_row) {
997 let end_idx = end_col.min(line.len());
998 if let Some(slice) = line.get_mut(start_col..end_idx) {
999 for ch in slice.iter_mut() {
1000 *ch = ch.to_lowercase().next().unwrap_or(*ch);
1001 }
1002 }
1003 }
1004 }
1005
1006 pub fn capitalize_right(&mut self) {
1008 let start_col = self.col;
1009 let start_row = self.row;
1010
1011 self.word_right(); let end_col = self.col;
1014
1015 if let Some(line) = self.value.get_mut(start_row) {
1017 let end_idx = end_col.min(line.len());
1018 if let Some(slice) = line.get_mut(start_col..end_idx) {
1019 for (i, ch) in slice.iter_mut().enumerate() {
1020 if i == 0 {
1021 *ch = ch.to_uppercase().next().unwrap_or(*ch);
1022 }
1023 }
1024 }
1025 }
1026 }
1027
1028 pub fn transpose_left(&mut self) {
1030 let row = self.row;
1031 let mut col = self.col;
1032
1033 if let Some(line) = self.value.get_mut(row) {
1034 if col == 0 || line.len() < 2 {
1035 return;
1036 }
1037
1038 if col >= line.len() {
1039 col -= 1;
1040 self.col = col;
1041 }
1042
1043 if col > 0 && col < line.len() {
1044 line.swap(col - 1, col);
1045 if col < line.len() {
1046 self.col = col + 1;
1047 }
1048 }
1049 }
1050 }
1051
1052 pub fn view(&mut self) -> String {
1054 if self.value.is_empty() || (self.value.len() == 1 && self.value[0].is_empty()) {
1056 return self.placeholder_view();
1057 }
1058
1059 self.cursor.text_style = self.current_style.computed_cursor_line();
1061
1062 let mut s = String::new();
1063 let line_info = self.line_info();
1064 let style = &self.current_style;
1065
1066 let mut display_line = 0;
1068 let mut widest_line_number = 0;
1069
1070 for (doc_line_idx, line) in self.value.iter().enumerate() {
1072 let wrapped_lines = self.cache.wrap(line, self.width);
1073 let is_current_doc_line = doc_line_idx == self.row;
1074
1075 for (wrap_idx, wrapped_line) in wrapped_lines.iter().enumerate() {
1076 let prompt = self.get_prompt_string(display_line);
1077 s.push_str(&style.computed_prompt().render(&prompt));
1078 display_line += 1;
1079
1080 let mut ln = String::new();
1082 if self.show_line_numbers {
1083 if wrap_idx == 0 {
1084 if is_current_doc_line {
1085 ln = style
1086 .computed_cursor_line_number()
1087 .render(&self.format_line_number(doc_line_idx + 1));
1088 } else {
1089 ln = style
1090 .computed_line_number()
1091 .render(&self.format_line_number(doc_line_idx + 1));
1092 }
1093 } else if is_current_doc_line {
1094 ln = style
1095 .computed_cursor_line_number()
1096 .render(&self.format_line_number(""));
1097 } else {
1098 ln = style
1099 .computed_line_number()
1100 .render(&self.format_line_number(""));
1101 }
1102 s.push_str(&ln);
1103 }
1104
1105 let lnw = lipgloss::width(&ln);
1107 if lnw > widest_line_number {
1108 widest_line_number = lnw;
1109 }
1110
1111 let strwidth = wrapped_line
1112 .iter()
1113 .map(|&ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0))
1114 .sum::<usize>();
1115 let mut padding = self.width.saturating_sub(strwidth);
1116
1117 if strwidth > self.width {
1119 let content: String = wrapped_line
1121 .iter()
1122 .collect::<String>()
1123 .trim_end()
1124 .to_string();
1125 let new_wrapped_line: Vec<char> = content.chars().collect();
1126 let new_strwidth = new_wrapped_line
1127 .iter()
1128 .map(|&ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0))
1129 .sum::<usize>();
1130 padding = self.width.saturating_sub(new_strwidth);
1131 }
1132
1133 if is_current_doc_line && line_info.row_offset == wrap_idx {
1135 let col_offset = line_info.column_offset;
1136
1137 let before: String = wrapped_line.iter().take(col_offset).collect();
1139 s.push_str(&style.computed_cursor_line().render(&before));
1140
1141 if self.col >= line.len() && line_info.char_offset >= self.width {
1143 self.cursor.set_char(" ");
1144 s.push_str(&self.cursor.view());
1145 } else {
1146 let cursor_char = wrapped_line.get(col_offset).unwrap_or(&' ');
1147 self.cursor.set_char(&cursor_char.to_string());
1148 s.push_str(&self.cursor.view());
1149
1150 let after: String = wrapped_line.iter().skip(col_offset + 1).collect();
1152 s.push_str(&style.computed_cursor_line().render(&after));
1153 }
1154 } else {
1155 let content: String = wrapped_line.iter().collect();
1157 let line_style = if is_current_doc_line {
1158 style.computed_cursor_line()
1159 } else {
1160 style.computed_text()
1161 };
1162 s.push_str(&line_style.render(&content));
1163 }
1164
1165 s.push_str(&style.computed_text().render(&" ".repeat(padding.max(0))));
1167 s.push('\n');
1168 }
1169 }
1170
1171 for _ in 0..(self.height.saturating_sub(display_line)) {
1173 let prompt = self.get_prompt_string(display_line);
1174 s.push_str(&style.computed_prompt().render(&prompt));
1175 display_line += 1;
1176
1177 let left_gutter = self.end_of_buffer_character.to_string();
1178 let right_gap_width =
1179 self.width().saturating_sub(lipgloss::width(&left_gutter)) + widest_line_number;
1180 let right_gap = " ".repeat(right_gap_width.max(0));
1181 s.push_str(
1182 &style
1183 .computed_end_of_buffer()
1184 .render(&(left_gutter + &right_gap)),
1185 );
1186 s.push('\n');
1187 }
1188
1189 s
1190 }
1191
1192 fn get_prompt_string(&self, display_line: usize) -> String {
1194 if let Some(prompt_func) = self.prompt_func {
1195 let prompt = prompt_func(display_line);
1196 let pl = prompt.len();
1197 if pl < self.prompt_width {
1198 format!("{}{}", " ".repeat(self.prompt_width - pl), prompt)
1199 } else {
1200 prompt
1201 }
1202 } else {
1203 self.prompt.clone()
1204 }
1205 }
1206
1207 fn format_line_number(&self, x: impl std::fmt::Display) -> String {
1209 let digits = if self.max_height > 0 {
1211 self.max_height.to_string().len()
1212 } else {
1213 3 };
1215 format!(" {:width$} ", x, width = digits)
1216 }
1217
1218 fn placeholder_view(&mut self) -> String {
1220 if self.placeholder.is_empty() {
1221 return String::new();
1222 }
1223
1224 let mut s = String::new();
1225
1226 let placeholder_lines: Vec<&str> = self.placeholder.lines().collect();
1228
1229 for i in 0..self.height {
1230 let prompt = self.get_prompt_string(i);
1232 s.push_str(&prompt);
1233
1234 if self.show_line_numbers {
1236 let ln = if i == 0 {
1237 self.format_line_number(1)
1238 } else {
1239 self.format_line_number("")
1240 };
1241 s.push_str(&ln);
1242 }
1243
1244 if i < placeholder_lines.len() {
1246 s.push_str(placeholder_lines[i]);
1248 } else {
1249 if self.end_of_buffer_character != ' ' {
1251 s.push(self.end_of_buffer_character);
1252 }
1253 }
1254
1255 s.push('\n');
1256 }
1257
1258 s.trim_end_matches('\n').to_string()
1260 }
1261
1262 pub fn scroll_down(&mut self, lines: usize) {
1264 self.viewport.set_y_offset(self.viewport.y_offset + lines);
1265 }
1266
1267 pub fn scroll_up(&mut self, lines: usize) {
1269 self.viewport
1270 .set_y_offset(self.viewport.y_offset.saturating_sub(lines));
1271 }
1272
1273 pub fn cursor_line_number(&mut self) -> usize {
1275 if self.row >= self.value.len() {
1276 return 0;
1277 }
1278
1279 let mut line_count = 0;
1281 for i in 0..self.row {
1282 if let Some(line) = self.value.get(i).cloned() {
1283 let wrapped_lines = self.cache.wrap(&line, self.width);
1284 line_count += wrapped_lines.len();
1285 }
1286 }
1287
1288 line_count += self.line_info().row_offset;
1290 line_count
1291 }
1292
1293 fn reposition_view(&mut self) {
1295 let cursor_line = self.cursor_line_number();
1296 let minimum = self.viewport.y_offset;
1297 let maximum = minimum + self.viewport.height.saturating_sub(1);
1298
1299 if cursor_line < minimum {
1300 self.viewport.set_y_offset(cursor_line);
1302 } else if cursor_line > maximum {
1303 let new_offset = cursor_line.saturating_sub(self.viewport.height.saturating_sub(1));
1305 self.viewport.set_y_offset(new_offset);
1306 }
1307 }
1308
1309 pub fn update(&mut self, msg: Option<bubbletea_rs::Msg>) -> Option<bubbletea_rs::Cmd> {
1311 if !self.focus {
1312 return None;
1313 }
1314
1315 if let Some(msg) = msg {
1316 if let Some(paste_msg) = msg.downcast_ref::<PasteMsg>() {
1318 self.insert_string(paste_msg.0.clone());
1319 return None;
1320 }
1321
1322 if let Some(_paste_err) = msg.downcast_ref::<PasteErrMsg>() {
1323 return None;
1325 }
1326
1327 if let Some(key_msg) = msg.downcast_ref::<bubbletea_rs::KeyMsg>() {
1329 return self.handle_key_msg(key_msg);
1330 }
1331
1332 let cursor_cmd = self.cursor.update(&msg);
1334 let viewport_cmd = self.viewport.update(msg);
1335
1336 cursor_cmd.or(viewport_cmd)
1338 } else {
1339 None
1340 }
1341 }
1342
1343 fn handle_key_msg(&mut self, key_msg: &bubbletea_rs::KeyMsg) -> Option<bubbletea_rs::Cmd> {
1345 let old_row = self.row;
1347 let old_col = self.col;
1348
1349 if let Some(cmd) = self.handle_clipboard_keys(key_msg) {
1351 return Some(cmd);
1352 }
1353
1354 self.handle_movement_keys(key_msg);
1355 self.handle_deletion_keys(key_msg);
1356 self.handle_text_operations(key_msg);
1357 self.handle_text_insertion(key_msg);
1358 self.handle_character_input(key_msg);
1359
1360 if self.row != old_row || self.col != old_col {
1362 self.reposition_view();
1363 }
1364
1365 None
1366 }
1367
1368 fn handle_clipboard_keys(
1370 &mut self,
1371 key_msg: &bubbletea_rs::KeyMsg,
1372 ) -> Option<bubbletea_rs::Cmd> {
1373 use crate::key::matches_binding;
1374
1375 if matches_binding(key_msg, &self.key_map.paste) {
1376 return Some(self.paste_command());
1377 }
1378
1379 None
1380 }
1381
1382 fn handle_movement_keys(&mut self, key_msg: &bubbletea_rs::KeyMsg) {
1384 use crate::key::matches_binding;
1385
1386 if matches_binding(key_msg, &self.key_map.character_forward) {
1388 self.character_right();
1389 } else if matches_binding(key_msg, &self.key_map.character_backward) {
1390 self.character_left(false);
1391 } else if matches_binding(key_msg, &self.key_map.word_forward) {
1393 self.word_right();
1394 } else if matches_binding(key_msg, &self.key_map.word_backward) {
1395 self.word_left();
1396 } else if matches_binding(key_msg, &self.key_map.line_next) {
1398 self.cursor_down();
1399 } else if matches_binding(key_msg, &self.key_map.line_previous) {
1400 self.cursor_up();
1401 } else if matches_binding(key_msg, &self.key_map.line_start) {
1402 self.cursor_start();
1403 } else if matches_binding(key_msg, &self.key_map.line_end) {
1404 self.cursor_end();
1405 } else if matches_binding(key_msg, &self.key_map.input_begin) {
1407 self.move_to_begin();
1408 } else if matches_binding(key_msg, &self.key_map.input_end) {
1409 self.move_to_end();
1410 }
1411 }
1412
1413 fn handle_deletion_keys(&mut self, key_msg: &bubbletea_rs::KeyMsg) {
1415 use crate::key::matches_binding;
1416
1417 if matches_binding(key_msg, &self.key_map.delete_character_backward) {
1418 self.delete_character_backward();
1419 } else if matches_binding(key_msg, &self.key_map.delete_character_forward) {
1420 self.delete_character_forward();
1421 } else if matches_binding(key_msg, &self.key_map.delete_word_backward) {
1422 self.delete_word_backward();
1423 } else if matches_binding(key_msg, &self.key_map.delete_word_forward) {
1424 self.delete_word_forward();
1425 } else if matches_binding(key_msg, &self.key_map.delete_after_cursor) {
1426 self.delete_after_cursor();
1427 } else if matches_binding(key_msg, &self.key_map.delete_before_cursor) {
1428 self.delete_before_cursor();
1429 }
1430 }
1431
1432 fn handle_text_operations(&mut self, key_msg: &bubbletea_rs::KeyMsg) {
1434 use crate::key::matches_binding;
1435
1436 if matches_binding(key_msg, &self.key_map.uppercase_word_forward) {
1437 self.uppercase_right();
1438 } else if matches_binding(key_msg, &self.key_map.lowercase_word_forward) {
1439 self.lowercase_right();
1440 } else if matches_binding(key_msg, &self.key_map.capitalize_word_forward) {
1441 self.capitalize_right();
1442 } else if matches_binding(key_msg, &self.key_map.transpose_character_backward) {
1443 self.transpose_left();
1444 }
1445 }
1446
1447 fn handle_text_insertion(&mut self, key_msg: &bubbletea_rs::KeyMsg) {
1449 use crate::key::matches_binding;
1450
1451 if matches_binding(key_msg, &self.key_map.insert_newline) {
1452 self.insert_newline();
1453 }
1454 }
1455
1456 fn handle_character_input(&mut self, key_msg: &bubbletea_rs::KeyMsg) {
1458 if let Some(ch) = self.extract_character_from_key_msg(key_msg) {
1460 if ch.is_control() {
1461 return;
1463 }
1464 self.insert_rune(ch);
1465 }
1466 }
1467
1468 fn extract_character_from_key_msg(&self, key_msg: &bubbletea_rs::KeyMsg) -> Option<char> {
1470 use crossterm::event::{KeyCode, KeyModifiers};
1471
1472 if key_msg.modifiers.contains(KeyModifiers::CONTROL)
1475 || key_msg.modifiers.contains(KeyModifiers::ALT)
1476 {
1477 return None;
1478 }
1479
1480 match key_msg.key {
1481 KeyCode::Char(c) => {
1482 if c.is_control() {
1484 None
1485 } else {
1486 Some(c)
1487 }
1488 }
1489 KeyCode::Tab => Some('\t'),
1490 _ => None,
1491 }
1492 }
1493
1494 fn paste_command(&self) -> bubbletea_rs::Cmd {
1496 bubbletea_rs::tick(
1498 std::time::Duration::from_millis(1),
1499 |_| match Self::read_clipboard() {
1500 Ok(content) => Box::new(PasteMsg(content)) as bubbletea_rs::Msg,
1501 Err(err) => Box::new(PasteErrMsg(err)) as bubbletea_rs::Msg,
1502 },
1503 )
1504 }
1505
1506 fn read_clipboard() -> Result<String, String> {
1508 #[cfg(feature = "clipboard-support")]
1509 {
1510 use clipboard::{ClipboardContext, ClipboardProvider};
1511
1512 let res: Result<String, String> = (|| {
1513 let mut ctx: ClipboardContext = ClipboardProvider::new()
1514 .map_err(|e| format!("Failed to create clipboard context: {}", e))?;
1515 ctx.get_contents()
1516 .map_err(|e| format!("Failed to read clipboard: {}", e))
1517 })();
1518 res
1519 }
1520 #[cfg(not(feature = "clipboard-support"))]
1521 {
1522 Ok(String::new())
1524 }
1525 }
1526
1527 pub fn copy_to_clipboard(&self, text: &str) -> Result<(), String> {
1529 #[cfg(feature = "clipboard-support")]
1530 {
1531 use clipboard::{ClipboardContext, ClipboardProvider};
1532
1533 let mut ctx: ClipboardContext = ClipboardProvider::new()
1534 .map_err(|e| format!("Failed to create clipboard context: {}", e))?;
1535
1536 ctx.set_contents(text.to_string())
1537 .map_err(|e| format!("Failed to write to clipboard: {}", e))
1538 }
1539 #[cfg(not(feature = "clipboard-support"))]
1540 {
1541 let _ = text; Err("Clipboard support not enabled".to_string())
1543 }
1544 }
1545
1546 pub fn copy_selection(&self) -> Result<(), String> {
1548 let content = self.value();
1551 self.copy_to_clipboard(&content)
1552 }
1553
1554 pub fn cut_selection(&mut self) -> Result<(), String> {
1556 let content = self.value();
1559 self.copy_to_clipboard(&content)?;
1560 self.reset();
1561 Ok(())
1562 }
1563}
1564
1565impl Default for Model {
1566 fn default() -> Self {
1567 Self::new()
1568 }
1569}
1570
1571impl Component for Model {
1573 fn focus(&mut self) -> Option<Cmd> {
1574 self.focus = true;
1575 self.current_style = self.focused_style.clone();
1576 self.cursor.focus()
1577 }
1578
1579 fn blur(&mut self) {
1580 self.focus = false;
1581 self.current_style = self.blurred_style.clone();
1582 self.cursor.blur();
1583 }
1584
1585 fn focused(&self) -> bool {
1586 self.focus
1587 }
1588}
1589
1590pub fn default_styles() -> (TextareaStyle, TextareaStyle) {
1592 let focused = default_focused_style();
1593 let blurred = default_blurred_style();
1594 (focused, blurred)
1595}
1596
1597pub fn new() -> Model {
1599 Model::new()
1600}