buffer.go raw
1 // SPDX-License-Identifier: Unlicense OR MIT
2
3 package widget
4
5 import (
6 "io"
7 "strings"
8 "unicode/utf8"
9 )
10
11 // editBuffer implements a gap buffer for text editing.
12 type editBuffer struct {
13 // pos is the byte position for Read and ReadRune.
14 pos int
15
16 // The gap start and end in bytes.
17 gapstart, gapend int
18 text []byte
19
20 // changed tracks whether the buffer content
21 // has changed since the last call to Changed.
22 changed bool
23 }
24
25 const minSpace = 5
26
27 func (e *editBuffer) Changed() bool {
28 c := e.changed
29 e.changed = false
30 return c
31 }
32
33 func (e *editBuffer) deleteRunes(caret, runes int) int {
34 e.moveGap(caret, 0)
35 for ; runes < 0 && e.gapstart > 0; runes++ {
36 _, s := utf8.DecodeLastRune(e.text[:e.gapstart])
37 e.gapstart -= s
38 caret -= s
39 e.changed = e.changed || s > 0
40 }
41 for ; runes > 0 && e.gapend < len(e.text); runes-- {
42 _, s := utf8.DecodeRune(e.text[e.gapend:])
43 e.gapend += s
44 e.changed = e.changed || s > 0
45 }
46 return caret
47 }
48
49 // moveGap moves the gap to the caret position. After returning,
50 // the gap is guaranteed to be at least space bytes long.
51 func (e *editBuffer) moveGap(caret, space int) {
52 if e.gapLen() < space {
53 if space < minSpace {
54 space = minSpace
55 }
56 txt := make([]byte, e.len()+space)
57 // Expand to capacity.
58 txt = txt[:cap(txt)]
59 gaplen := len(txt) - e.len()
60 if caret > e.gapstart {
61 copy(txt, e.text[:e.gapstart])
62 copy(txt[caret+gaplen:], e.text[caret:])
63 copy(txt[e.gapstart:], e.text[e.gapend:caret+e.gapLen()])
64 } else {
65 copy(txt, e.text[:caret])
66 copy(txt[e.gapstart+gaplen:], e.text[e.gapend:])
67 copy(txt[caret+gaplen:], e.text[caret:e.gapstart])
68 }
69 e.text = txt
70 e.gapstart = caret
71 e.gapend = e.gapstart + gaplen
72 } else {
73 if caret > e.gapstart {
74 copy(e.text[e.gapstart:], e.text[e.gapend:caret+e.gapLen()])
75 } else {
76 copy(e.text[caret+e.gapLen():], e.text[caret:e.gapstart])
77 }
78 l := e.gapLen()
79 e.gapstart = caret
80 e.gapend = e.gapstart + l
81 }
82 }
83
84 func (e *editBuffer) len() int {
85 return len(e.text) - e.gapLen()
86 }
87
88 func (e *editBuffer) gapLen() int {
89 return e.gapend - e.gapstart
90 }
91
92 func (e *editBuffer) Reset() {
93 e.Seek(0, io.SeekStart)
94 }
95
96 // Seek implements io.Seeker
97 func (e *editBuffer) Seek(offset int64, whence int) (ret int64, err error) {
98 switch whence {
99 case io.SeekStart:
100 e.pos = int(offset)
101 case io.SeekCurrent:
102 e.pos += int(offset)
103 case io.SeekEnd:
104 e.pos = e.len() - int(offset)
105 }
106 if e.pos < 0 {
107 e.pos = 0
108 } else if e.pos > e.len() {
109 e.pos = e.len()
110 }
111 return int64(e.pos), nil
112 }
113
114 func (e *editBuffer) Read(p []byte) (int, error) {
115 if e.pos == e.len() {
116 return 0, io.EOF
117 }
118 var total int
119 if e.pos < e.gapstart {
120 n := copy(p, e.text[e.pos:e.gapstart])
121 p = p[n:]
122 total += n
123 e.pos += n
124 }
125 if e.pos >= e.gapstart {
126 n := copy(p, e.text[e.pos+e.gapLen():])
127 total += n
128 e.pos += n
129 }
130 if e.pos > e.len() {
131 panic("hey!")
132 }
133 return total, nil
134 }
135
136 func (e *editBuffer) ReadRune() (rune, int, error) {
137 if e.pos == e.len() {
138 return 0, 0, io.EOF
139 }
140 r, s := e.runeAt(e.pos)
141 e.pos += s
142 return r, s, nil
143 }
144
145 func (e *editBuffer) String() string {
146 var b strings.Builder
147 b.Grow(e.len())
148 b.Write(e.text[:e.gapstart])
149 b.Write(e.text[e.gapend:])
150 return b.String()
151 }
152
153 func (e *editBuffer) prepend(caret int, s string) {
154 e.moveGap(caret, len(s))
155 copy(e.text[caret:], s)
156 e.gapstart += len(s)
157 e.changed = e.changed || len(s) > 0
158 }
159
160 func (e *editBuffer) runeBefore(idx int) (rune, int) {
161 if idx > e.gapstart {
162 idx += e.gapLen()
163 }
164 return utf8.DecodeLastRune(e.text[:idx])
165 }
166
167 func (e *editBuffer) runeAt(idx int) (rune, int) {
168 if idx >= e.gapstart {
169 idx += e.gapLen()
170 }
171 return utf8.DecodeRune(e.text[idx:])
172 }
173