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