1 package tview
2
3 import (
4 "strings"
5 "unicode"
6 "unicode/utf8"
7
8 "github.com/gdamore/tcell/v2"
9 "github.com/rivo/uniseg"
10 )
11
12 const (
13
14 pieceChainMinCap = 10
15
16
17 editBufferMinCap = 200
18
19
20
21 maxGraphemeClusterSize = 40
22
23
24 minCursorPrefix = 5
25
26
27 minCursorSuffix = 3
28 )
29
30
31 type taAction int
32
33 const (
34 taActionOther taAction = iota
35 taActionTypeSpace
36 taActionTypeNonSpace
37 taActionBackspace
38 taActionDelete
39 )
40
41
42
43 var NewLine = "\n"
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 type textAreaSpan struct {
68
69
70
71 previous, next int
72
73
74
75
76
77
78
79 offset, length int
80 }
81
82
83
84 type textAreaUndoItem struct {
85 before, after int
86 originalBefore, originalAfter int
87 pos [3]int
88 length int
89 continuation bool
90 }
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192 type TextArea struct {
193 *Box
194
195
196 disabled bool
197
198
199
200 width, height int
201
202
203 placeholder string
204
205
206 label string
207
208
209 labelWidth int
210
211
212
213
214 labelStyle tcell.Style
215
216
217
218 textStyle tcell.Style
219
220
221 selectedStyle tcell.Style
222
223
224 placeholderStyle tcell.Style
225
226
227
228
229
230 initialText string
231
232
233
234 editText strings.Builder
235
236
237 length int
238
239
240
241 maxLength int
242
243
244
245
246 spans []textAreaSpan
247
248
249
250
251
252
253 wrap bool
254
255
256
257 wordWrap bool
258
259
260 rowOffset int
261
262
263 columnOffset int
264
265
266 lastHeight, lastWidth int
267
268
269
270 widestLine int
271
272
273
274
275 lineStarts [][3]int
276
277
278
279
280
281 cursor, selectionStart struct {
282
283
284
285
286
287
288 row, column, actualColumn int
289
290
291 pos [3]int
292 }
293
294
295 dragging bool
296
297
298
299
300 clipboard string
301
302
303
304 copyToClipboard func(string)
305
306
307 pasteFromClipboard func() string
308
309
310
311
312 lastAction taAction
313
314
315
316
317
318
319 undoStack []textAreaUndoItem
320
321
322
323 nextUndo int
324
325
326
327
328 changed func()
329
330
331
332 moved func()
333
334
335
336 finished func(tcell.Key)
337 }
338
339
340
341 func NewTextArea() *TextArea {
342 t := &TextArea{
343 Box: NewBox(),
344 wrap: true,
345 wordWrap: true,
346 placeholderStyle: tcell.StyleDefault.Background(Styles.PrimitiveBackgroundColor).Foreground(Styles.TertiaryTextColor),
347 labelStyle: tcell.StyleDefault.Foreground(Styles.SecondaryTextColor),
348 textStyle: tcell.StyleDefault.Background(Styles.PrimitiveBackgroundColor).Foreground(Styles.PrimaryTextColor),
349 selectedStyle: tcell.StyleDefault.Background(Styles.PrimaryTextColor).Foreground(Styles.PrimitiveBackgroundColor),
350 spans: make([]textAreaSpan, 2, pieceChainMinCap),
351 lastAction: taActionOther,
352 }
353 t.editText.Grow(editBufferMinCap)
354 t.spans[0] = textAreaSpan{previous: -1, next: 1}
355 t.spans[1] = textAreaSpan{previous: 0, next: -1}
356 t.cursor.pos = [3]int{1, 0, -1}
357 t.selectionStart = t.cursor
358 t.SetClipboard(nil, nil)
359
360 return t
361 }
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376 func (t *TextArea) SetText(text string, cursorAtTheEnd bool) *TextArea {
377 t.spans = t.spans[:2]
378 t.initialText = text
379 t.editText.Reset()
380 t.lineStarts = nil
381 t.length = len(text)
382 t.rowOffset = 0
383 t.columnOffset = 0
384 t.reset()
385 t.cursor.row, t.cursor.actualColumn, t.cursor.column = 0, 0, 0
386 t.cursor.pos = [3]int{1, 0, -1}
387 t.undoStack = t.undoStack[:0]
388 t.nextUndo = 0
389
390 if len(text) > 0 {
391 t.spans = append(t.spans, textAreaSpan{
392 previous: 0,
393 next: 1,
394 offset: 0,
395 length: -len(text),
396 })
397 t.spans[0].next = 2
398 t.spans[1].previous = 2
399 if cursorAtTheEnd {
400 t.cursor.row = -1
401 if t.lastWidth > 0 {
402 t.findCursor(true, 0)
403 }
404 } else {
405 t.cursor.pos = [3]int{2, 0, -1}
406 }
407 } else {
408 t.spans[0].next = 1
409 t.spans[1].previous = 0
410 }
411 t.selectionStart = t.cursor
412
413 if t.changed != nil {
414 t.changed()
415 }
416
417 if t.lastWidth > 0 && t.moved != nil {
418 t.moved()
419 }
420
421 return t
422 }
423
424
425
426 func (t *TextArea) GetText() string {
427 if t.length == 0 {
428 return ""
429 }
430
431 var text strings.Builder
432 text.Grow(t.length)
433 spanIndex := t.spans[0].next
434 for spanIndex != 1 {
435 span := &t.spans[spanIndex]
436 if span.length < 0 {
437 text.WriteString(t.initialText[span.offset : span.offset-span.length])
438 } else {
439 text.WriteString(t.editText.String()[span.offset : span.offset+span.length])
440 }
441 spanIndex = t.spans[spanIndex].next
442 }
443
444 return text.String()
445 }
446
447
448 func (t *TextArea) HasSelection() bool {
449 return t.selectionStart != t.cursor
450 }
451
452
453
454
455
456
457
458
459
460
461 func (t *TextArea) GetSelection() (text string, start int, end int) {
462 from, to := t.selectionStart.pos, t.cursor.pos
463 if t.cursor.row < t.selectionStart.row || (t.cursor.row == t.selectionStart.row && t.cursor.actualColumn < t.selectionStart.actualColumn) {
464 from, to = to, from
465 }
466
467 if from[0] == 1 {
468 start = t.length
469 }
470 if to[0] == 1 {
471 end = t.length
472 }
473
474 var (
475 index int
476 selection strings.Builder
477 inside bool
478 )
479 for span := t.spans[0].next; span != 1; span = t.spans[span].next {
480 var spanText string
481 length := t.spans[span].length
482 if length < 0 {
483 length = -length
484 spanText = t.initialText
485 } else {
486 spanText = t.editText.String()
487 }
488 spanText = spanText[t.spans[span].offset : t.spans[span].offset+length]
489
490 if from[0] == span && to[0] == span {
491 if from != to {
492 selection.WriteString(spanText[from[1]:to[1]])
493 }
494 start = index + from[1]
495 end = index + to[1]
496 break
497 } else if from[0] == span {
498 if from != to {
499 selection.WriteString(spanText[from[1]:])
500 }
501 start = index + from[1]
502 inside = true
503 } else if to[0] == span {
504 if from != to {
505 selection.WriteString(spanText[:to[1]])
506 }
507 end = index + to[1]
508 break
509 } else if inside && from != to {
510 selection.WriteString(spanText)
511 }
512
513 index += length
514 }
515
516 if selection.Len() != 0 {
517 text = selection.String()
518 }
519 return
520 }
521
522
523
524
525
526
527 func (t *TextArea) GetCursor() (fromRow, fromColumn, toRow, toColumn int) {
528 fromRow, fromColumn = t.selectionStart.row, t.selectionStart.actualColumn
529 toRow, toColumn = t.cursor.row, t.cursor.actualColumn
530 if toRow < fromRow || (toRow == fromRow && toColumn < fromColumn) {
531 fromRow, fromColumn, toRow, toColumn = toRow, toColumn, fromRow, fromColumn
532 }
533 if t.length > 0 && t.wrap && fromColumn >= t.lastWidth {
534 fromRow++
535 fromColumn = 0
536 }
537 if t.length > 0 && t.wrap && toColumn >= t.lastWidth {
538 toRow++
539 toColumn = 0
540 }
541 return
542 }
543
544
545 func (t *TextArea) GetTextLength() int {
546 return t.length
547 }
548
549
550
551
552
553
554
555
556
557
558
559
560 func (t *TextArea) Replace(start, end int, text string) *TextArea {
561 t.Select(start, end)
562 row := t.selectionStart.row
563 t.cursor.pos = t.replace(t.selectionStart.pos, t.cursor.pos, text, false)
564 t.cursor.row = -1
565 t.truncateLines(row - 1)
566 t.findCursor(false, row)
567 t.selectionStart = t.cursor
568 if t.changed != nil {
569 t.changed()
570 }
571 if t.moved != nil {
572 t.moved()
573 }
574 return t
575 }
576
577
578
579
580
581
582
583 func (t *TextArea) Select(start, end int) *TextArea {
584 oldFrom, oldTo := t.selectionStart, t.cursor
585 defer func() {
586 if (oldFrom != t.selectionStart || oldTo != t.cursor) && t.moved != nil {
587 t.moved()
588 }
589 }()
590
591
592 if start < 0 {
593 start = 0
594 }
595 if start > t.length {
596 start = t.length
597 }
598 if end < 0 {
599 end = 0
600 }
601 if end > t.length {
602 end = t.length
603 }
604 if end < start {
605 start, end = end, start
606 }
607
608
609 var row, index int
610 t.cursor.row, t.cursor.pos = -1, [3]int{1, 0, -1}
611 t.selectionStart = t.cursor
612 RowLoop:
613 for {
614 if row >= len(t.lineStarts) {
615 t.extendLines(t.lastWidth, row)
616 if row >= len(t.lineStarts) {
617 break
618 }
619 }
620
621
622 pos := t.lineStarts[row]
623 var (
624 next [3]int
625 lineIndex int
626 )
627 if row+1 < len(t.lineStarts) {
628 next = t.lineStarts[row+1]
629 } else {
630 next = [3]int{1, 0, -1}
631 }
632 for {
633 if pos[0] == next[0] {
634 if start >= index+lineIndex && start < index+lineIndex+next[1]-pos[1] ||
635 end >= index+lineIndex && end < index+lineIndex+next[1]-pos[1] {
636 break
637 }
638 index += lineIndex + next[1] - pos[1]
639 row++
640 continue RowLoop
641 } else {
642 length := t.spans[pos[0]].length
643 if length < 0 {
644 length = -length
645 }
646 if start >= index+lineIndex && start < index+lineIndex+length-pos[1] ||
647 end >= index+lineIndex && end < index+lineIndex+length-pos[1] {
648 break
649 }
650 lineIndex += length - pos[1]
651 pos[0], pos[1] = t.spans[pos[0]].next, 0
652 }
653 }
654
655
656 pos = t.lineStarts[row]
657 endPos := pos
658 var (
659 cluster, text string
660 column, width int
661 )
662 for pos != next {
663 if t.selectionStart.row < 0 && start <= index {
664 t.selectionStart.row, t.selectionStart.column, t.selectionStart.actualColumn = row, column, column
665 t.selectionStart.pos = pos
666 }
667 if t.cursor.row < 0 && end <= index {
668 t.cursor.row, t.cursor.column, t.cursor.actualColumn = row, column, column
669 t.cursor.pos = pos
670 break RowLoop
671 }
672 cluster, text, _, width, pos, endPos = t.step(text, pos, endPos)
673 index += len(cluster)
674 column += width
675 }
676 }
677
678 if t.cursor.row < 0 {
679 t.findCursor(false, 0)
680 t.selectionStart = t.cursor
681 }
682
683 return t
684 }
685
686
687
688
689 func (t *TextArea) SetWrap(wrap bool) *TextArea {
690 if t.wrap != wrap {
691 t.wrap = wrap
692 t.reset()
693 }
694 return t
695 }
696
697
698
699
700
701
702
703
704 func (t *TextArea) SetWordWrap(wrapOnWords bool) *TextArea {
705 if t.wordWrap != wrapOnWords {
706 t.wordWrap = wrapOnWords
707 t.reset()
708 }
709 return t
710 }
711
712
713 func (t *TextArea) SetPlaceholder(placeholder string) *TextArea {
714 t.placeholder = placeholder
715 return t
716 }
717
718
719 func (t *TextArea) SetLabel(label string) *TextArea {
720 t.label = label
721 return t
722 }
723
724
725 func (t *TextArea) GetLabel() string {
726 return t.label
727 }
728
729
730
731 func (t *TextArea) SetLabelWidth(width int) *TextArea {
732 t.labelWidth = width
733 return t
734 }
735
736
737
738
739
740 func (t *TextArea) SetSize(rows, columns int) *TextArea {
741 t.width = columns
742 t.height = rows
743 return t
744 }
745
746
747 func (t *TextArea) GetFieldWidth() int {
748 return t.width
749 }
750
751
752 func (t *TextArea) GetFieldHeight() int {
753 return t.height
754 }
755
756
757 func (t *TextArea) SetDisabled(disabled bool) FormItem {
758 t.disabled = disabled
759 if t.finished != nil {
760 t.finished(-1)
761 }
762 return t
763 }
764
765
766
767
768 func (t *TextArea) SetMaxLength(maxLength int) *TextArea {
769 t.maxLength = maxLength
770 return t
771 }
772
773
774 func (t *TextArea) SetLabelStyle(style tcell.Style) *TextArea {
775 t.labelStyle = style
776 return t
777 }
778
779
780 func (t *TextArea) GetLabelStyle() tcell.Style {
781 return t.labelStyle
782 }
783
784
785
786 func (t *TextArea) SetTextStyle(style tcell.Style) *TextArea {
787 t.textStyle = style
788 return t
789 }
790
791
792 func (t *TextArea) SetSelectedStyle(style tcell.Style) *TextArea {
793 t.selectedStyle = style
794 return t
795 }
796
797
798 func (t *TextArea) SetPlaceholderStyle(style tcell.Style) *TextArea {
799 t.placeholderStyle = style
800 return t
801 }
802
803
804
805
806 func (t *TextArea) GetOffset() (row, column int) {
807 return t.rowOffset, t.columnOffset
808 }
809
810
811
812
813
814 func (t *TextArea) SetOffset(row, column int) *TextArea {
815 t.rowOffset, t.columnOffset = row, column
816 return t
817 }
818
819
820
821
822
823
824
825
826 func (t *TextArea) SetClipboard(copyToClipboard func(string), pasteFromClipboard func() string) *TextArea {
827 t.copyToClipboard = copyToClipboard
828 if t.copyToClipboard == nil {
829 t.copyToClipboard = func(text string) {
830 t.clipboard = text
831 }
832 }
833
834 t.pasteFromClipboard = pasteFromClipboard
835 if t.pasteFromClipboard == nil {
836 t.pasteFromClipboard = func() string {
837 return t.clipboard
838 }
839 }
840
841 return t
842 }
843
844
845
846 func (t *TextArea) SetChangedFunc(handler func()) *TextArea {
847 t.changed = handler
848 return t
849 }
850
851
852
853 func (t *TextArea) SetMovedFunc(handler func()) *TextArea {
854 t.moved = handler
855 return t
856 }
857
858
859 func (t *TextArea) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
860 t.finished = handler
861 return t
862 }
863
864
865 func (t *TextArea) Focus(delegate func(p Primitive)) {
866
867
868 if t.finished != nil && t.disabled {
869 t.finished(-1)
870 return
871 }
872
873 t.Box.Focus(delegate)
874 }
875
876
877 func (t *TextArea) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
878 t.labelWidth = labelWidth
879 t.backgroundColor = bgColor
880 t.labelStyle = t.labelStyle.Foreground(labelColor)
881 t.textStyle = tcell.StyleDefault.Foreground(fieldTextColor).Background(fieldBgColor)
882 return t
883 }
884
885
886
887
888
889
890
891
892
893
894
895
896
897 func (t *TextArea) replace(deleteStart, deleteEnd [3]int, insert string, continuation bool) [3]int {
898
899 if deleteStart == deleteEnd && insert == "" || t.maxLength > 0 && len(insert) > 0 && t.length+len(insert) >= t.maxLength {
900 return deleteEnd
901 }
902
903
904 if t.changed != nil {
905 defer t.changed()
906 }
907
908
909
910 if continuation {
911
912
913 switch {
914 case insert == "" && deleteStart[1] != 0 && deleteEnd[1] == 0:
915
916 length := t.spans[deleteStart[0]].length
917 if length < 0 {
918 t.length -= -length - deleteStart[1]
919 length = -deleteStart[1]
920 } else {
921 t.length -= length - deleteStart[1]
922 length = deleteStart[1]
923 }
924 t.spans[deleteStart[0]].length = length
925 return deleteEnd
926 case insert == "" && deleteStart[1] == 0 && deleteEnd[1] != 0:
927
928 t.spans[deleteEnd[0]].offset += deleteEnd[1]
929 if t.spans[deleteEnd[0]].length < 0 {
930 t.spans[deleteEnd[0]].length += deleteEnd[1]
931 } else {
932 t.spans[deleteEnd[0]].length -= deleteEnd[1]
933 }
934 t.length -= deleteEnd[1]
935 deleteEnd[1] = 0
936 return deleteEnd
937 case insert != "" && deleteStart == deleteEnd && deleteEnd[1] == 0:
938 previous := t.spans[deleteStart[0]].previous
939 bufferSpan := t.spans[previous]
940 if bufferSpan.length > 0 && bufferSpan.offset+bufferSpan.length == t.editText.Len() {
941
942 length, _ := t.editText.WriteString(insert)
943 t.spans[previous].length += length
944 t.length += length
945 return deleteEnd
946 }
947 }
948 }
949
950
951 before := t.spans[deleteStart[0]].previous
952 after := deleteEnd[0]
953 if deleteEnd[1] > 0 {
954 after = t.spans[deleteEnd[0]].next
955 }
956 t.undoStack = t.undoStack[:t.nextUndo]
957 t.undoStack = append(t.undoStack, textAreaUndoItem{
958 before: len(t.spans),
959 after: len(t.spans) + 1,
960 originalBefore: before,
961 originalAfter: after,
962 length: t.length,
963 pos: t.cursor.pos,
964 continuation: continuation,
965 })
966 t.spans = append(t.spans, t.spans[before])
967 t.spans = append(t.spans, t.spans[after])
968 t.nextUndo++
969
970
971
972 for index := deleteStart[0]; index != after; index = t.spans[index].next {
973 if t.spans[index].length < 0 {
974 t.length += t.spans[index].length
975 } else {
976 t.length -= t.spans[index].length
977 }
978 }
979 t.spans[before].next = after
980 t.spans[after].previous = before
981
982
983
984
985
986 if deleteStart[1] != 0 {
987 span := textAreaSpan{
988 previous: before,
989 next: after,
990 offset: t.spans[deleteStart[0]].offset,
991 length: deleteStart[1],
992 }
993 if t.spans[deleteStart[0]].length < 0 {
994 span.length = -span.length
995 }
996 t.length += deleteStart[1]
997 t.spans[before].next = len(t.spans)
998 t.spans[after].previous = len(t.spans)
999 before = len(t.spans)
1000 for row, lineStart := range t.lineStarts {
1001 if lineStart[0] == deleteStart[0] {
1002 if lineStart[1] >= deleteStart[1] {
1003 t.lineStarts = t.lineStarts[:row]
1004 break
1005 }
1006 t.lineStarts[row][0] = len(t.spans)
1007 }
1008 }
1009 t.spans = append(t.spans, span)
1010 }
1011
1012
1013 if insert != "" {
1014 span := textAreaSpan{
1015 previous: before,
1016 next: after,
1017 offset: t.editText.Len(),
1018 }
1019 span.length, _ = t.editText.WriteString(insert)
1020 t.length += span.length
1021 t.spans[before].next = len(t.spans)
1022 t.spans[after].previous = len(t.spans)
1023 before = len(t.spans)
1024 t.spans = append(t.spans, span)
1025 }
1026
1027
1028 if deleteEnd[1] != 0 {
1029 span := textAreaSpan{
1030 previous: before,
1031 next: after,
1032 offset: t.spans[deleteEnd[0]].offset + deleteEnd[1],
1033 }
1034 length := t.spans[deleteEnd[0]].length
1035 if length < 0 {
1036 span.length = length + deleteEnd[1]
1037 t.length -= span.length
1038 } else {
1039 span.length = length - deleteEnd[1]
1040 t.length += span.length
1041 }
1042 t.spans[before].next = len(t.spans)
1043 t.spans[after].previous = len(t.spans)
1044 deleteEnd[0], deleteEnd[1] = len(t.spans), 0
1045 t.spans = append(t.spans, span)
1046 }
1047
1048 return deleteEnd
1049 }
1050
1051
1052 func (t *TextArea) Draw(screen tcell.Screen) {
1053 t.Box.DrawForSubclass(screen, t)
1054
1055
1056 x, y, width, height := t.GetInnerRect()
1057 if width <= 0 || height <= 0 {
1058 return
1059 }
1060 columnOffset := t.columnOffset
1061 if t.wrap {
1062 columnOffset = 0
1063 }
1064
1065
1066 _, labelBg, _ := t.labelStyle.Decompose()
1067 if t.labelWidth > 0 {
1068 labelWidth := t.labelWidth
1069 if labelWidth > width {
1070 labelWidth = width
1071 }
1072 printWithStyle(screen, t.label, x, y, 0, labelWidth, AlignLeft, t.labelStyle, labelBg == tcell.ColorDefault)
1073 x += labelWidth
1074 width -= labelWidth
1075 } else {
1076 _, drawnWidth, _, _ := printWithStyle(screen, t.label, x, y, 0, width, AlignLeft, t.labelStyle, labelBg == tcell.ColorDefault)
1077 x += drawnWidth
1078 width -= drawnWidth
1079 }
1080
1081
1082 if t.width > 0 && t.width < width {
1083 width = t.width
1084 }
1085 if t.height > 0 && t.height < height {
1086 height = t.height
1087 }
1088 if width <= 0 {
1089 return
1090 }
1091
1092
1093 _, bg, _ := t.textStyle.Decompose()
1094 if t.disabled {
1095 bg = t.backgroundColor
1096 }
1097 if bg != t.backgroundColor {
1098 for row := 0; row < height; row++ {
1099 for column := 0; column < width; column++ {
1100 screen.SetContent(x+column, y+row, ' ', nil, t.textStyle)
1101 }
1102 }
1103 }
1104
1105
1106 defer func() {
1107 if t.HasFocus() {
1108 row, column := t.cursor.row, t.cursor.actualColumn
1109 if t.length > 0 && t.wrap && column >= t.lastWidth {
1110 row++
1111 column = 0
1112 }
1113 if row >= 0 &&
1114 row-t.rowOffset >= 0 && row-t.rowOffset < height &&
1115 column-columnOffset >= 0 && column-columnOffset < width {
1116 screen.ShowCursor(x+column-columnOffset, y+row-t.rowOffset)
1117 } else {
1118 screen.HideCursor()
1119 }
1120 }
1121 }()
1122
1123
1124 if t.length == 0 && len(t.placeholder) > 0 {
1125 t.drawPlaceholder(screen, x, y, width, height)
1126 return
1127 }
1128
1129
1130 firstDrawing := t.lastWidth == 0
1131 if t.lastWidth != width && t.lineStarts != nil {
1132 t.reset()
1133 }
1134 t.lastHeight, t.lastWidth = height, width
1135 t.extendLines(width, t.rowOffset+height)
1136 if len(t.lineStarts) <= t.rowOffset {
1137 return
1138 }
1139
1140
1141
1142 if t.cursor.row < 0 {
1143 t.findCursor(true, 0)
1144 if t.selectionStart.row < 0 {
1145 t.selectionStart = t.cursor
1146 }
1147 if firstDrawing && t.moved != nil {
1148 t.moved()
1149 }
1150 }
1151
1152
1153 var cluster, text string
1154 line := t.rowOffset
1155 pos := t.lineStarts[line]
1156 endPos := pos
1157 posX, posY := 0, 0
1158 for pos[0] != 1 {
1159 var clusterWidth int
1160 cluster, text, _, clusterWidth, pos, endPos = t.step(text, pos, endPos)
1161
1162
1163 runes := []rune(cluster)
1164 style := t.selectedStyle
1165 fromRow, fromColumn := t.cursor.row, t.cursor.actualColumn
1166 toRow, toColumn := t.selectionStart.row, t.selectionStart.actualColumn
1167 if fromRow > toRow || fromRow == toRow && fromColumn > toColumn {
1168 fromRow, fromColumn, toRow, toColumn = toRow, toColumn, fromRow, fromColumn
1169 }
1170 if toRow < line ||
1171 toRow == line && toColumn <= posX ||
1172 fromRow > line ||
1173 fromRow == line && fromColumn > posX {
1174 style = t.textStyle
1175 if t.disabled {
1176 style = style.Background(t.backgroundColor)
1177 }
1178 }
1179
1180
1181 if posX+clusterWidth-columnOffset <= width && posX-columnOffset >= 0 && clusterWidth > 0 {
1182 screen.SetContent(x+posX-columnOffset, y+posY, runes[0], runes[1:], style)
1183 }
1184
1185
1186 posX += clusterWidth
1187 if line+1 < len(t.lineStarts) && t.lineStarts[line+1] == pos {
1188
1189 posY++
1190 if posY >= height {
1191 break
1192 }
1193 posX = 0
1194 line++
1195 }
1196 }
1197 }
1198
1199
1200
1201
1202 func (t *TextArea) drawPlaceholder(screen tcell.Screen, x, y, width, height int) {
1203 posX, posY := x, y
1204 lastLineBreak, lastGraphemeBreak := x, x
1205 iterateString(t.placeholder, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth, boundaries int) bool {
1206 if posX+screenWidth > x+width {
1207
1208
1209
1210 clearX := lastLineBreak
1211 if lastLineBreak == x {
1212 clearX = lastGraphemeBreak
1213 }
1214 posY++
1215 if posY >= y+height {
1216 return true
1217 }
1218 newPosX := x
1219 for clearX < posX {
1220 main, comb, _, _ := screen.GetContent(clearX, posY-1)
1221 screen.SetContent(clearX, posY-1, ' ', nil, tcell.StyleDefault.Background(t.backgroundColor))
1222 screen.SetContent(newPosX, posY, main, comb, t.placeholderStyle)
1223 clearX++
1224 newPosX++
1225 }
1226 lastLineBreak, lastGraphemeBreak, posX = x, x, newPosX
1227 }
1228
1229
1230 screen.SetContent(posX, posY, main, comb, t.placeholderStyle)
1231 posX += screenWidth
1232 switch boundaries & uniseg.MaskLine {
1233 case uniseg.LineMustBreak:
1234 posY++
1235 if posY >= y+height {
1236 return true
1237 }
1238 posX = x
1239 case uniseg.LineCanBreak:
1240 lastLineBreak = posX
1241 }
1242 lastGraphemeBreak = posX
1243
1244 return false
1245 })
1246 }
1247
1248
1249
1250
1251 func (t *TextArea) reset() {
1252 t.truncateLines(0)
1253 if t.wrap {
1254 t.cursor.row = -1
1255 t.selectionStart.row = -1
1256 }
1257 t.widestLine = 0
1258 }
1259
1260
1261
1262
1263
1264
1265
1266
1267 func (t *TextArea) extendLines(width, maxLines int) {
1268 if width <= 0 {
1269 return
1270 }
1271
1272
1273 if len(t.lineStarts) == 0 {
1274 if len(t.spans) > 2 {
1275 t.lineStarts = append(t.lineStarts, [3]int{t.spans[0].next, 0, -1})
1276 } else {
1277 return
1278 }
1279 }
1280
1281
1282 pos := t.lineStarts[len(t.lineStarts)-1]
1283 endPos := pos
1284 var (
1285 cluster, text string
1286 lineWidth, clusterWidth, boundaries int
1287 lastGraphemeBreak, lastLineBreak [3]int
1288 widthSinceLineBreak int
1289 )
1290 for pos[0] != 1 {
1291
1292 cluster, text, boundaries, clusterWidth, pos, endPos = t.step(text, pos, endPos)
1293 lineWidth += clusterWidth
1294 widthSinceLineBreak += clusterWidth
1295
1296
1297 if !t.wrap || lineWidth <= width {
1298 if boundaries&uniseg.MaskLine == uniseg.LineMustBreak && (len(text) > 0 || uniseg.HasTrailingLineBreakInString(cluster)) {
1299
1300 t.lineStarts = append(t.lineStarts, pos)
1301 if lineWidth > t.widestLine {
1302 t.widestLine = lineWidth
1303 }
1304 lineWidth = 0
1305 lastGraphemeBreak = [3]int{}
1306 lastLineBreak = [3]int{}
1307 widthSinceLineBreak = 0
1308 if len(t.lineStarts) > maxLines {
1309 break
1310 }
1311 continue
1312 }
1313 } else {
1314 if !t.wordWrap || lastLineBreak == [3]int{} {
1315 if lastGraphemeBreak != [3]int{} {
1316
1317 t.lineStarts = append(t.lineStarts, lastGraphemeBreak)
1318 if lineWidth > t.widestLine {
1319 t.widestLine = lineWidth
1320 }
1321 lineWidth = clusterWidth
1322 lastLineBreak = [3]int{}
1323 }
1324 } else {
1325
1326 t.lineStarts = append(t.lineStarts, lastLineBreak)
1327 if lineWidth > t.widestLine {
1328 t.widestLine = lineWidth
1329 }
1330 lineWidth = widthSinceLineBreak
1331 lastLineBreak = [3]int{}
1332 }
1333 }
1334
1335
1336 if boundaries&uniseg.MaskLine == uniseg.LineCanBreak {
1337 lastLineBreak = pos
1338 widthSinceLineBreak = 0
1339 }
1340 lastGraphemeBreak = pos
1341
1342
1343 if len(t.lineStarts) > maxLines {
1344 break
1345 }
1346 }
1347 }
1348
1349
1350
1351
1352
1353 func (t *TextArea) truncateLines(fromLine int) {
1354 if fromLine < 0 {
1355 fromLine = 0
1356 }
1357 if fromLine < len(t.lineStarts) {
1358 t.lineStarts = t.lineStarts[:fromLine]
1359 }
1360 }
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374 func (t *TextArea) findCursor(clamp bool, startRow int) {
1375 defer func() {
1376 t.cursor.column = t.cursor.actualColumn
1377 }()
1378
1379 if !clamp && t.cursor.row >= 0 {
1380 return
1381 }
1382
1383
1384 if clamp && t.cursor.row >= 0 {
1385 cursorRow := t.cursor.row
1386 if t.wrap && t.cursor.actualColumn >= t.lastWidth {
1387 cursorRow++
1388 }
1389 if cursorRow < t.rowOffset {
1390
1391 t.rowOffset = cursorRow
1392 } else if cursorRow >= t.rowOffset+t.lastHeight {
1393
1394 t.rowOffset = cursorRow - t.lastHeight + 1
1395 if t.rowOffset >= len(t.lineStarts) {
1396 t.extendLines(t.lastWidth, t.rowOffset)
1397 if t.rowOffset >= len(t.lineStarts) {
1398 t.rowOffset = len(t.lineStarts) - 1
1399 if t.rowOffset < 0 {
1400 t.rowOffset = 0
1401 }
1402 }
1403 }
1404 }
1405 if !t.wrap {
1406 if t.cursor.actualColumn < t.columnOffset+minCursorPrefix {
1407
1408 t.columnOffset = t.cursor.actualColumn - minCursorPrefix
1409 if t.columnOffset < 0 {
1410 t.columnOffset = 0
1411 }
1412 } else if t.cursor.actualColumn >= t.columnOffset+t.lastWidth-minCursorSuffix {
1413
1414 t.columnOffset = t.cursor.actualColumn - t.lastWidth + minCursorSuffix
1415 if t.columnOffset >= t.widestLine {
1416 t.columnOffset = t.widestLine - 1
1417 if t.columnOffset < 0 {
1418 t.columnOffset = 0
1419 }
1420 }
1421 }
1422 }
1423 return
1424 }
1425
1426
1427
1428 row := startRow
1429 if row < 0 {
1430 row = 0
1431 }
1432 RowLoop:
1433 for {
1434
1435 if row+1 >= len(t.lineStarts) {
1436 t.extendLines(t.lastWidth, row+1)
1437 }
1438 if row >= len(t.lineStarts) {
1439 t.cursor.row, t.cursor.actualColumn, t.cursor.pos = row, 0, [3]int{1, 0, -1}
1440 break
1441 }
1442
1443
1444 pos := t.lineStarts[row]
1445 for pos[0] != 1 {
1446 if row+1 >= len(t.lineStarts) {
1447 break
1448 }
1449 if t.cursor.pos[0] == pos[0] {
1450
1451 if t.lineStarts[row+1][0] == pos[0] {
1452
1453 if t.cursor.pos[1] >= t.lineStarts[row+1][1] {
1454
1455 row++
1456 continue RowLoop
1457 } else {
1458
1459 break
1460 }
1461 } else {
1462
1463
1464 break
1465 }
1466 } else {
1467
1468 if t.lineStarts[row+1][0] == pos[0] {
1469
1470
1471 row++
1472 continue RowLoop
1473 } else {
1474
1475 pos = [3]int{t.spans[pos[0]].next, 0, -1}
1476 }
1477 }
1478 }
1479
1480
1481 pos = t.lineStarts[row]
1482 endPos := pos
1483 column := 0
1484 var text string
1485 for {
1486 if pos[0] == 1 || t.cursor.pos[0] == pos[0] && t.cursor.pos[1] == pos[1] {
1487
1488 t.cursor.row, t.cursor.actualColumn, t.cursor.pos = row, column, pos
1489 break RowLoop
1490 }
1491 var clusterWidth int
1492 _, text, _, clusterWidth, pos, endPos = t.step(text, pos, endPos)
1493 if row+1 < len(t.lineStarts) && t.lineStarts[row+1] == pos {
1494
1495 row++
1496 continue RowLoop
1497 }
1498 column += clusterWidth
1499 }
1500 }
1501
1502 if clamp && t.cursor.row >= 0 {
1503
1504 t.findCursor(true, startRow)
1505 }
1506 }
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518 func (t *TextArea) step(text string, pos, endPos [3]int) (cluster, rest string, boundaries, width int, newPos, newEndPos [3]int) {
1519 if pos[0] == 1 {
1520 return
1521 }
1522
1523
1524
1525 span := t.spans[pos[0]]
1526 if len(text) < maxGraphemeClusterSize &&
1527 (span.length < 0 && -span.length-pos[1] >= maxGraphemeClusterSize ||
1528 span.length > 0 && t.spans[pos[0]].length-pos[1] >= maxGraphemeClusterSize) {
1529
1530 if span.length < 0 {
1531 text = t.initialText[span.offset+pos[1] : span.offset-span.length]
1532 } else {
1533 text = t.editText.String()[span.offset+pos[1] : span.offset+span.length]
1534 }
1535 endPos = [3]int{span.next, 0, -1}
1536 } else {
1537
1538 for len(text) < maxGraphemeClusterSize && endPos[0] != 1 {
1539 endSpan := t.spans[endPos[0]]
1540 var moreText string
1541 if endSpan.length < 0 {
1542 moreText = t.initialText[endSpan.offset+endPos[1] : endSpan.offset-endSpan.length]
1543 } else {
1544 moreText = t.editText.String()[endSpan.offset+endPos[1] : endSpan.offset+endSpan.length]
1545 }
1546 if len(moreText) > maxGraphemeClusterSize {
1547 moreText = moreText[:maxGraphemeClusterSize]
1548 }
1549 text += moreText
1550 endPos[1] += len(moreText)
1551 if endPos[1] >= endSpan.length {
1552 endPos[0], endPos[1] = endSpan.next, 0
1553 }
1554 }
1555 }
1556
1557
1558 cluster, text, boundaries, pos[2] = uniseg.StepString(text, pos[2])
1559 pos[1] += len(cluster)
1560 for pos[0] != 1 && (span.length < 0 && pos[1] >= -span.length || span.length >= 0 && pos[1] >= span.length) {
1561 pos[0] = span.next
1562 if span.length < 0 {
1563 pos[1] += span.length
1564 } else {
1565 pos[1] -= span.length
1566 }
1567 span = t.spans[pos[0]]
1568 }
1569
1570 if cluster == "\t" {
1571 width = TabSize
1572 } else {
1573 width = boundaries >> uniseg.ShiftWidth
1574 }
1575
1576 return cluster, text, boundaries, width, pos, endPos
1577 }
1578
1579
1580
1581
1582
1583
1584
1585
1586 func (t *TextArea) moveCursor(row, column int) {
1587
1588 if len(t.lineStarts) <= row {
1589
1590 t.extendLines(t.lastWidth, row)
1591 }
1592 if len(t.lineStarts) == 0 {
1593 return
1594 }
1595 if row < 0 {
1596
1597 row = 0
1598 column = 0
1599 } else if row >= len(t.lineStarts) {
1600
1601 row = len(t.lineStarts) - 1
1602 column = -1
1603 }
1604
1605
1606 t.cursor.row, t.cursor.actualColumn = row, 0
1607 if t.wrap {
1608 t.cursor.actualColumn = 0
1609 }
1610 pos := t.lineStarts[row]
1611 endPos := pos
1612 var text string
1613 for pos[0] != 1 {
1614 var clusterWidth int
1615 oldPos := pos
1616 _, text, _, clusterWidth, pos, endPos = t.step(text, pos, endPos)
1617 if len(t.lineStarts) > row+1 && pos == t.lineStarts[row+1] ||
1618 column >= 0 && t.cursor.actualColumn+clusterWidth > column {
1619 pos = oldPos
1620 break
1621 }
1622 t.cursor.actualColumn += clusterWidth
1623 }
1624
1625 if column < 0 {
1626 t.cursor.column = t.cursor.actualColumn
1627 } else {
1628 t.cursor.column = column
1629 }
1630 t.cursor.pos = pos
1631 t.findCursor(true, row)
1632 }
1633
1634
1635
1636
1637
1638 func (t *TextArea) moveWordRight(after, clamp bool) {
1639
1640
1641 pos := t.cursor.pos
1642 endPos := pos
1643 var (
1644 cluster, text string
1645 inWord bool
1646 )
1647 for pos[0] != 0 {
1648 var boundaries int
1649 oldPos := pos
1650 cluster, text, boundaries, _, pos, endPos = t.step(text, pos, endPos)
1651 if oldPos == t.cursor.pos {
1652 continue
1653 }
1654 firstRune, _ := utf8.DecodeRuneInString(cluster)
1655 if !unicode.IsSpace(firstRune) && !unicode.IsPunct(firstRune) {
1656 inWord = true
1657 }
1658 if inWord && boundaries&uniseg.MaskWord != 0 {
1659 if !after {
1660 pos = oldPos
1661 }
1662 break
1663 }
1664 }
1665 startRow := t.cursor.row
1666 t.cursor.row, t.cursor.column, t.cursor.actualColumn = -1, 0, 0
1667 t.cursor.pos = pos
1668 t.findCursor(clamp, startRow)
1669 }
1670
1671
1672
1673
1674 func (t *TextArea) moveWordLeft(clamp bool) {
1675
1676
1677 row := t.cursor.row
1678 if row+1 < len(t.lineStarts) {
1679 t.extendLines(t.lastWidth, row+1)
1680 }
1681 if row >= len(t.lineStarts) {
1682 row = len(t.lineStarts) - 1
1683 }
1684 for row >= 0 {
1685 pos := t.lineStarts[row]
1686 endPos := pos
1687 var lastWordBoundary [3]int
1688 var (
1689 cluster, text string
1690 inWord bool
1691 boundaries int
1692 )
1693 for pos[0] != 1 && pos != t.cursor.pos {
1694 oldBoundaries := boundaries
1695 oldPos := pos
1696 cluster, text, boundaries, _, pos, endPos = t.step(text, pos, endPos)
1697 firstRune, _ := utf8.DecodeRuneInString(cluster)
1698 wordRune := !unicode.IsSpace(firstRune) && !unicode.IsPunct(firstRune)
1699 if oldBoundaries&uniseg.MaskWord != 0 {
1700 if pos != t.cursor.pos && !inWord && wordRune {
1701
1702
1703 lastWordBoundary = oldPos
1704 }
1705 inWord = false
1706 }
1707 if wordRune {
1708 inWord = true
1709 }
1710 }
1711 if lastWordBoundary[0] != 0 {
1712
1713 t.cursor.pos = lastWordBoundary
1714 break
1715 }
1716 row--
1717 }
1718 if row < 0 {
1719
1720 t.cursor.pos = [3]int{t.spans[0].next, 0, -1}
1721 row = 0
1722 }
1723 t.cursor.row, t.cursor.column, t.cursor.actualColumn = -1, 0, 0
1724 t.findCursor(clamp, row)
1725 }
1726
1727
1728
1729 func (t *TextArea) deleteLine() {
1730
1731
1732 startRow := t.cursor.row
1733 if t.cursor.actualColumn == 0 && t.cursor.pos[0] == 1 {
1734 startRow--
1735 }
1736 if startRow+1 < len(t.lineStarts) {
1737 t.extendLines(t.lastWidth, startRow+1)
1738 }
1739 if len(t.lineStarts) == 0 {
1740 return
1741 }
1742 if startRow >= len(t.lineStarts) {
1743 startRow = len(t.lineStarts) - 1
1744 }
1745 for startRow >= 0 {
1746
1747 pos := t.lineStarts[startRow]
1748 span := t.spans[pos[0]]
1749 var text string
1750 if pos[1] > 0 {
1751
1752 if span.length < 0 {
1753 text = t.initialText
1754 } else {
1755 text = t.editText.String()
1756 }
1757 text = text[:span.offset+pos[1]]
1758 } else {
1759
1760 if span.previous != 0 {
1761 span = t.spans[span.previous]
1762 if span.length < 0 {
1763 text = t.initialText[:span.offset-span.length]
1764 } else {
1765 text = t.editText.String()[:span.offset+span.length]
1766 }
1767 }
1768 }
1769 if uniseg.HasTrailingLineBreakInString(text) {
1770
1771
1772 break
1773 }
1774 startRow--
1775 }
1776 if startRow < 0 {
1777
1778 startRow = 0
1779 }
1780
1781
1782 pos := t.cursor.pos
1783 endPos := pos
1784 var cluster, text string
1785 for pos[0] != 1 {
1786 cluster, text, _, _, pos, endPos = t.step(text, pos, endPos)
1787 if uniseg.HasTrailingLineBreakInString(cluster) {
1788 break
1789 }
1790 }
1791
1792
1793 t.cursor.pos = t.replace(t.lineStarts[startRow], pos, "", false)
1794 t.cursor.row = -1
1795 t.truncateLines(startRow)
1796 t.findCursor(true, startRow)
1797 }
1798
1799
1800
1801
1802
1803 func (t *TextArea) getSelection() ([3]int, [3]int, int) {
1804 from := t.selectionStart.pos
1805 to := t.cursor.pos
1806 row := t.selectionStart.row
1807 if t.cursor.row < t.selectionStart.row ||
1808 (t.cursor.row == t.selectionStart.row && t.cursor.actualColumn < t.selectionStart.actualColumn) {
1809 from, to = to, from
1810 row = t.cursor.row
1811 }
1812 return from, to, row
1813 }
1814
1815
1816 func (t *TextArea) getSelectedText() string {
1817 var text strings.Builder
1818
1819 from, to, _ := t.getSelection()
1820 for from[0] != to[0] {
1821 span := t.spans[from[0]]
1822 if span.length < 0 {
1823 text.WriteString(t.initialText[span.offset+from[1] : span.offset-span.length])
1824 } else {
1825 text.WriteString(t.editText.String()[span.offset+from[1] : span.offset+span.length])
1826 }
1827 from[0], from[1] = span.next, 0
1828 }
1829 if from[0] != 1 && from[1] < to[1] {
1830 span := t.spans[from[0]]
1831 if span.length < 0 {
1832 text.WriteString(t.initialText[span.offset+from[1] : span.offset+to[1]])
1833 } else {
1834 text.WriteString(t.editText.String()[span.offset+from[1] : span.offset+to[1]])
1835 }
1836 }
1837
1838 return text.String()
1839 }
1840
1841
1842 func (t *TextArea) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
1843 return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
1844 if t.disabled {
1845 return
1846 }
1847
1848
1849 newLastAction := taActionOther
1850 defer func() {
1851 t.lastAction = newLastAction
1852 }()
1853
1854
1855 if t.moved != nil {
1856 selectionStart, cursor := t.selectionStart, t.cursor
1857 defer func() {
1858 if selectionStart != t.selectionStart || cursor != t.cursor {
1859 t.moved()
1860 }
1861 }()
1862 }
1863
1864
1865 switch key := event.Key(); key {
1866 case tcell.KeyLeft:
1867 if event.Modifiers()&tcell.ModAlt == 0 {
1868
1869 if event.Modifiers()&tcell.ModShift == 0 && t.selectionStart.pos != t.cursor.pos {
1870
1871 if t.selectionStart.row < t.cursor.row || (t.selectionStart.row == t.cursor.row && t.selectionStart.actualColumn < t.cursor.actualColumn) {
1872 t.cursor = t.selectionStart
1873 }
1874 t.findCursor(true, t.cursor.row)
1875 } else if event.Modifiers()&tcell.ModMeta != 0 || event.Modifiers()&tcell.ModCtrl != 0 {
1876
1877 t.moveWordLeft(event.Modifiers()&tcell.ModShift != 0)
1878 } else if t.cursor.actualColumn == 0 {
1879
1880 if t.cursor.row > 0 {
1881 t.moveCursor(t.cursor.row-1, -1)
1882 }
1883 } else {
1884
1885 t.moveCursor(t.cursor.row, t.cursor.actualColumn-1)
1886 }
1887 if event.Modifiers()&tcell.ModShift == 0 {
1888 t.selectionStart = t.cursor
1889 }
1890 } else if !t.wrap {
1891
1892 t.columnOffset--
1893 if t.columnOffset < 0 {
1894 t.columnOffset = 0
1895 }
1896 }
1897 case tcell.KeyRight:
1898 if event.Modifiers()&tcell.ModAlt == 0 {
1899
1900 if event.Modifiers()&tcell.ModShift == 0 && t.selectionStart.pos != t.cursor.pos {
1901
1902 if t.selectionStart.row > t.cursor.row || (t.selectionStart.row == t.cursor.row && t.selectionStart.actualColumn > t.cursor.actualColumn) {
1903 t.cursor = t.selectionStart
1904 }
1905 t.findCursor(true, t.cursor.row)
1906 } else if t.cursor.pos[0] != 1 {
1907 if event.Modifiers()&tcell.ModMeta != 0 || event.Modifiers()&tcell.ModCtrl != 0 {
1908
1909 t.moveWordRight(event.Modifiers()&tcell.ModShift != 0, true)
1910 } else {
1911
1912 var clusterWidth int
1913 _, _, _, clusterWidth, t.cursor.pos, _ = t.step("", t.cursor.pos, t.cursor.pos)
1914 if len(t.lineStarts) <= t.cursor.row+1 {
1915 t.extendLines(t.lastWidth, t.cursor.row+1)
1916 }
1917 if t.cursor.row+1 < len(t.lineStarts) && t.lineStarts[t.cursor.row+1] == t.cursor.pos {
1918
1919 t.cursor.row++
1920 t.cursor.actualColumn = 0
1921 t.cursor.column = 0
1922 t.findCursor(true, t.cursor.row)
1923 } else {
1924
1925 t.moveCursor(t.cursor.row, t.cursor.actualColumn+clusterWidth)
1926 }
1927 }
1928 }
1929 if event.Modifiers()&tcell.ModShift == 0 {
1930 t.selectionStart = t.cursor
1931 }
1932 } else if !t.wrap {
1933
1934 t.columnOffset++
1935 if t.columnOffset >= t.widestLine {
1936 t.columnOffset = t.widestLine - 1
1937 if t.columnOffset < 0 {
1938 t.columnOffset = 0
1939 }
1940 }
1941 }
1942 case tcell.KeyDown:
1943 if event.Modifiers()&tcell.ModAlt == 0 {
1944
1945 column := t.cursor.column
1946 t.moveCursor(t.cursor.row+1, t.cursor.column)
1947 t.cursor.column = column
1948 if event.Modifiers()&tcell.ModShift == 0 {
1949 t.selectionStart = t.cursor
1950 }
1951 } else {
1952
1953 t.rowOffset++
1954 if t.rowOffset >= len(t.lineStarts) {
1955 t.extendLines(t.lastWidth, t.rowOffset)
1956 if t.rowOffset >= len(t.lineStarts) {
1957 t.rowOffset = len(t.lineStarts) - 1
1958 if t.rowOffset < 0 {
1959 t.rowOffset = 0
1960 }
1961 }
1962 }
1963 }
1964 case tcell.KeyUp:
1965 if event.Modifiers()&tcell.ModAlt == 0 {
1966
1967 column := t.cursor.column
1968 t.moveCursor(t.cursor.row-1, t.cursor.column)
1969 t.cursor.column = column
1970 if event.Modifiers()&tcell.ModShift == 0 {
1971 t.selectionStart = t.cursor
1972 }
1973 } else {
1974
1975 t.rowOffset--
1976 if t.rowOffset < 0 {
1977 t.rowOffset = 0
1978 }
1979 }
1980 case tcell.KeyHome, tcell.KeyCtrlA:
1981 t.moveCursor(t.cursor.row, 0)
1982 if event.Modifiers()&tcell.ModShift == 0 {
1983 t.selectionStart = t.cursor
1984 }
1985 case tcell.KeyEnd, tcell.KeyCtrlE:
1986 t.moveCursor(t.cursor.row, -1)
1987 if event.Modifiers()&tcell.ModShift == 0 {
1988 t.selectionStart = t.cursor
1989 }
1990 case tcell.KeyPgDn, tcell.KeyCtrlF:
1991 column := t.cursor.column
1992 t.moveCursor(t.cursor.row+t.lastHeight, t.cursor.column)
1993 t.cursor.column = column
1994 if event.Modifiers()&tcell.ModShift == 0 {
1995 t.selectionStart = t.cursor
1996 }
1997 case tcell.KeyPgUp, tcell.KeyCtrlB:
1998 column := t.cursor.column
1999 t.moveCursor(t.cursor.row-t.lastHeight, t.cursor.column)
2000 t.cursor.column = column
2001 if event.Modifiers()&tcell.ModShift == 0 {
2002 t.selectionStart = t.cursor
2003 }
2004 case tcell.KeyEnter:
2005 from, to, row := t.getSelection()
2006 t.cursor.pos = t.replace(from, to, NewLine, t.lastAction == taActionTypeSpace)
2007 t.cursor.row = -1
2008 t.truncateLines(row - 1)
2009 t.findCursor(true, row)
2010 t.selectionStart = t.cursor
2011 newLastAction = taActionTypeSpace
2012 case tcell.KeyTab:
2013
2014 if t.finished != nil {
2015 t.finished(key)
2016 return
2017 }
2018
2019 from, to, row := t.getSelection()
2020 t.cursor.pos = t.replace(from, to, "\t", t.lastAction == taActionTypeSpace)
2021 t.cursor.row = -1
2022 t.truncateLines(row - 1)
2023 t.findCursor(true, row)
2024 t.selectionStart = t.cursor
2025 newLastAction = taActionTypeSpace
2026 case tcell.KeyBacktab, tcell.KeyEscape:
2027 if t.finished != nil {
2028 t.finished(key)
2029 return
2030 }
2031 case tcell.KeyRune:
2032 if event.Modifiers()&tcell.ModAlt > 0 {
2033
2034 switch event.Rune() {
2035 case 'f':
2036 if event.Modifiers()&tcell.ModShift == 0 {
2037 t.moveWordRight(false, true)
2038 t.selectionStart = t.cursor
2039 } else {
2040 t.moveWordRight(true, true)
2041 }
2042 case 'b':
2043 t.moveWordLeft(true)
2044 if event.Modifiers()&tcell.ModShift == 0 {
2045 t.selectionStart = t.cursor
2046 }
2047 }
2048 } else {
2049
2050 r := event.Rune()
2051 from, to, row := t.getSelection()
2052 newLastAction = taActionTypeNonSpace
2053 if unicode.IsSpace(r) {
2054 newLastAction = taActionTypeSpace
2055 }
2056 t.cursor.pos = t.replace(from, to, string(r), newLastAction == t.lastAction || t.lastAction == taActionTypeNonSpace && newLastAction == taActionTypeSpace)
2057 t.cursor.row = -1
2058 t.truncateLines(row - 1)
2059 t.findCursor(true, row)
2060 t.selectionStart = t.cursor
2061 }
2062 case tcell.KeyBackspace, tcell.KeyBackspace2:
2063 from, to, row := t.getSelection()
2064 if from != to {
2065
2066 t.cursor.pos = t.replace(from, to, "", false)
2067 t.cursor.row = -1
2068 t.truncateLines(row - 1)
2069 t.findCursor(true, row)
2070 t.selectionStart = t.cursor
2071 break
2072 }
2073
2074 beforeCursor := t.cursor
2075 if event.Modifiers()&tcell.ModAlt == 0 {
2076
2077 if t.cursor.actualColumn == 0 {
2078
2079 if t.cursor.row > 0 {
2080 t.moveCursor(t.cursor.row-1, -1)
2081 }
2082 } else {
2083
2084 t.moveCursor(t.cursor.row, t.cursor.actualColumn-1)
2085 }
2086 newLastAction = taActionBackspace
2087 } else {
2088
2089 t.moveWordLeft(false)
2090 }
2091
2092
2093 if t.cursor.pos != beforeCursor.pos {
2094 t.cursor, beforeCursor = beforeCursor, t.cursor
2095 t.cursor.pos = t.replace(beforeCursor.pos, t.cursor.pos, "", t.lastAction == taActionBackspace)
2096 t.cursor.row = -1
2097 t.truncateLines(beforeCursor.row - 1)
2098 t.findCursor(true, beforeCursor.row-1)
2099 }
2100 t.selectionStart = t.cursor
2101 case tcell.KeyDelete, tcell.KeyCtrlD:
2102 from, to, row := t.getSelection()
2103 if from != to {
2104
2105 t.cursor.pos = t.replace(from, to, "", false)
2106 t.cursor.row = -1
2107 t.truncateLines(row - 1)
2108 t.findCursor(true, row)
2109 t.selectionStart = t.cursor
2110 break
2111 }
2112
2113 if t.cursor.pos[0] != 1 {
2114 _, _, _, _, endPos, _ := t.step("", t.cursor.pos, t.cursor.pos)
2115 t.cursor.pos = t.replace(t.cursor.pos, endPos, "", t.lastAction == taActionDelete)
2116 t.cursor.pos[2] = endPos[2]
2117 t.truncateLines(t.cursor.row - 1)
2118 t.findCursor(true, t.cursor.row)
2119 newLastAction = taActionDelete
2120 }
2121 t.selectionStart = t.cursor
2122 case tcell.KeyCtrlK:
2123 pos := t.cursor.pos
2124 endPos := pos
2125 var cluster, text string
2126 for pos[0] != 1 {
2127 var boundaries int
2128 oldPos := pos
2129 cluster, text, boundaries, _, pos, endPos = t.step(text, pos, endPos)
2130 if boundaries&uniseg.MaskLine == uniseg.LineMustBreak {
2131 if uniseg.HasTrailingLineBreakInString(cluster) {
2132 pos = oldPos
2133 }
2134 break
2135 }
2136 }
2137 t.cursor.pos = t.replace(t.cursor.pos, pos, "", false)
2138 row := t.cursor.row
2139 t.cursor.row = -1
2140 t.truncateLines(row - 1)
2141 t.findCursor(true, row)
2142 t.selectionStart = t.cursor
2143 case tcell.KeyCtrlW:
2144 pos := t.cursor.pos
2145 t.moveWordLeft(true)
2146 t.cursor.pos = t.replace(t.cursor.pos, pos, "", false)
2147 row := t.cursor.row - 1
2148 t.cursor.row = -1
2149 t.truncateLines(row)
2150 t.findCursor(true, row)
2151 t.selectionStart = t.cursor
2152 case tcell.KeyCtrlU:
2153 t.deleteLine()
2154 t.selectionStart = t.cursor
2155 case tcell.KeyCtrlL:
2156 t.selectionStart.row, t.selectionStart.column, t.selectionStart.actualColumn = 0, 0, 0
2157 t.selectionStart.pos = [3]int{t.spans[0].next, 0, -1}
2158 row := t.cursor.row
2159 t.cursor.row = -1
2160 t.cursor.pos = [3]int{1, 0, -1}
2161 t.findCursor(false, row)
2162 case tcell.KeyCtrlQ:
2163 if t.cursor != t.selectionStart {
2164 t.copyToClipboard(t.getSelectedText())
2165 t.selectionStart = t.cursor
2166 }
2167 case tcell.KeyCtrlX:
2168 if t.cursor != t.selectionStart {
2169 t.copyToClipboard(t.getSelectedText())
2170 from, to, row := t.getSelection()
2171 t.cursor.pos = t.replace(from, to, "", false)
2172 t.cursor.row = -1
2173 t.truncateLines(row - 1)
2174 t.findCursor(true, row)
2175 t.selectionStart = t.cursor
2176 }
2177 case tcell.KeyCtrlV:
2178 from, to, row := t.getSelection()
2179 t.cursor.pos = t.replace(from, to, t.pasteFromClipboard(), false)
2180 t.cursor.row = -1
2181 t.truncateLines(row - 1)
2182 t.findCursor(true, row)
2183 t.selectionStart = t.cursor
2184 case tcell.KeyCtrlZ:
2185 if t.nextUndo <= 0 {
2186 break
2187 }
2188 for t.nextUndo > 0 {
2189 t.nextUndo--
2190 undo := t.undoStack[t.nextUndo]
2191 t.spans[undo.originalBefore], t.spans[undo.before] = t.spans[undo.before], t.spans[undo.originalBefore]
2192 t.spans[undo.originalAfter], t.spans[undo.after] = t.spans[undo.after], t.spans[undo.originalAfter]
2193 t.cursor.pos, t.undoStack[t.nextUndo].pos = undo.pos, t.cursor.pos
2194 t.length, t.undoStack[t.nextUndo].length = undo.length, t.length
2195 if !undo.continuation {
2196 break
2197 }
2198 }
2199 t.cursor.row = -1
2200 t.truncateLines(0)
2201 t.findCursor(true, 0)
2202 t.selectionStart = t.cursor
2203 if t.changed != nil {
2204 defer t.changed()
2205 }
2206 case tcell.KeyCtrlY:
2207 if t.nextUndo >= len(t.undoStack) {
2208 break
2209 }
2210 for t.nextUndo < len(t.undoStack) {
2211 undo := t.undoStack[t.nextUndo]
2212 t.spans[undo.originalBefore], t.spans[undo.before] = t.spans[undo.before], t.spans[undo.originalBefore]
2213 t.spans[undo.originalAfter], t.spans[undo.after] = t.spans[undo.after], t.spans[undo.originalAfter]
2214 t.cursor.pos, t.undoStack[t.nextUndo].pos = undo.pos, t.cursor.pos
2215 t.length, t.undoStack[t.nextUndo].length = undo.length, t.length
2216 t.nextUndo++
2217 if t.nextUndo < len(t.undoStack) && !t.undoStack[t.nextUndo].continuation {
2218 break
2219 }
2220 }
2221 t.cursor.row = -1
2222 t.truncateLines(0)
2223 t.findCursor(true, 0)
2224 t.selectionStart = t.cursor
2225 if t.changed != nil {
2226 defer t.changed()
2227 }
2228 }
2229 })
2230 }
2231
2232
2233 func (t *TextArea) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
2234 return t.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
2235 if t.disabled {
2236 return false, nil
2237 }
2238
2239 x, y := event.Position()
2240 rectX, rectY, _, _ := t.GetInnerRect()
2241 if !t.InRect(x, y) {
2242 return false, nil
2243 }
2244
2245
2246 if t.moved != nil {
2247 selectionStart, cursor := t.selectionStart, t.cursor
2248 defer func() {
2249 if selectionStart != t.selectionStart || cursor != t.cursor {
2250 t.moved()
2251 }
2252 }()
2253 }
2254
2255
2256 labelWidth := t.labelWidth
2257 if labelWidth == 0 && t.label != "" {
2258 labelWidth = TaggedStringWidth(t.label)
2259 }
2260 column := x - rectX - labelWidth
2261 row := y - rectY
2262 if !t.wrap {
2263 column += t.columnOffset
2264 }
2265 row += t.rowOffset
2266
2267
2268 switch action {
2269 case MouseLeftDown:
2270 t.moveCursor(row, column)
2271 if event.Modifiers()&tcell.ModShift == 0 {
2272 t.selectionStart = t.cursor
2273 }
2274 setFocus(t)
2275 consumed = true
2276 capture = t
2277 t.dragging = true
2278 case MouseMove:
2279 if !t.dragging {
2280 break
2281 }
2282 t.moveCursor(row, column)
2283 consumed = true
2284 case MouseLeftUp:
2285 t.moveCursor(row, column)
2286 consumed = true
2287 capture = nil
2288 t.dragging = false
2289 case MouseLeftDoubleClick:
2290
2291
2292 t.moveWordLeft(false)
2293 t.selectionStart = t.cursor
2294 t.moveWordRight(true, false)
2295 consumed = true
2296 case MouseScrollUp:
2297 if t.rowOffset > 0 {
2298 t.rowOffset--
2299 }
2300 consumed = true
2301 case MouseScrollDown:
2302 t.rowOffset++
2303 if t.rowOffset >= len(t.lineStarts) {
2304 t.rowOffset = len(t.lineStarts) - 1
2305 if t.rowOffset < 0 {
2306 t.rowOffset = 0
2307 }
2308 }
2309 consumed = true
2310 case MouseScrollLeft:
2311 if t.columnOffset > 0 {
2312 t.columnOffset--
2313 }
2314 consumed = true
2315 case MouseScrollRight:
2316 t.columnOffset++
2317 if t.columnOffset >= t.widestLine {
2318 t.columnOffset = t.widestLine - 1
2319 if t.columnOffset < 0 {
2320 t.columnOffset = 0
2321 }
2322 }
2323 consumed = true
2324 }
2325
2326 return
2327 })
2328 }
2329
View as plain text