Files
prevara/buffer.go
2025-11-30 16:45:22 +00:00

191 lines
4.0 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
package gel
import (
"fmt"
"io"
"strings"
"unicode/utf8"
)
const bufferDebug = false
// editBuffer implements a gap buffer for text editing.
type editBuffer struct {
// caret is the caret position in bytes.
caret int
// pos is the byte position for Read and ReadRune.
pos int
// The gap start and end in bytes.
gapstart, gapend int
text []byte
// changed tracks whether the buffer content
// has changed since the last call to Changed.
changed bool
}
const minSpace = 5
func (e *editBuffer) Zero() {
for i := range e.text {
e.text[i] = 0
}
}
func (e *editBuffer) Changed() bool {
c := e.changed
e.changed = false
return c
}
func (e *editBuffer) deleteRunes(caret, runes int) int {
e.moveGap(caret, 0)
for ; runes < 0 && e.gapstart > 0; runes++ {
_, s := utf8.DecodeLastRune(e.text[:e.gapstart])
e.gapstart -= s
caret -= s
e.changed = e.changed || s > 0
}
for ; runes > 0 && e.gapend < len(e.text); runes-- {
_, s := utf8.DecodeRune(e.text[e.gapend:])
e.gapend += s
e.changed = e.changed || s > 0
}
return caret
}
// moveGap moves the gap to the caret position. After returning,
// the gap is guaranteed to be at least space bytes long.
func (e *editBuffer) moveGap(caret, space int) {
if e.gapLen() < space {
if space < minSpace {
space = minSpace
}
txt := make([]byte, e.len()+space)
// Expand to capacity.
txt = txt[:cap(txt)]
gaplen := len(txt) - e.len()
if caret > e.gapstart {
copy(txt, e.text[:e.gapstart])
copy(txt[caret+gaplen:], e.text[caret:])
copy(txt[e.gapstart:], e.text[e.gapend:caret+e.gapLen()])
} else {
copy(txt, e.text[:caret])
copy(txt[e.gapstart+gaplen:], e.text[e.gapend:])
copy(txt[caret+gaplen:], e.text[caret:e.gapstart])
}
e.text = txt
e.gapstart = caret
e.gapend = e.gapstart + gaplen
} else {
if caret > e.gapstart {
copy(e.text[e.gapstart:], e.text[e.gapend:caret+e.gapLen()])
} else {
copy(e.text[caret+e.gapLen():], e.text[caret:e.gapstart])
}
l := e.gapLen()
e.gapstart = caret
e.gapend = e.gapstart + l
}
}
func (e *editBuffer) len() int {
return len(e.text) - e.gapLen()
}
func (e *editBuffer) gapLen() int {
return e.gapend - e.gapstart
}
func (e *editBuffer) Reset() {
e.pos = 0
}
func (e *editBuffer) Read(p []byte) (int, error) {
if e.pos == e.len() {
return 0, io.EOF
}
var total int
if e.pos < e.gapstart {
n := copy(p, e.text[e.pos:e.gapstart])
p = p[n:]
total += n
e.pos += n
}
if e.pos >= e.gapstart {
n := copy(p, e.text[e.pos+e.gapLen():])
total += n
e.pos += n
}
if e.pos > e.len() {
// Reset position to prevent further issues
e.pos = e.len()
}
return total, nil
}
func (e *editBuffer) ReadRune() (rune, int, error) {
if e.pos == e.len() {
return 0, 0, io.EOF
}
r, s := e.runeAt(e.pos)
e.pos += s
return r, s, nil
}
func (e *editBuffer) String() string {
var b strings.Builder
b.Grow(e.len())
b.Write(e.text[:e.gapstart])
b.Write(e.text[e.gapend:])
return b.String()
}
func (e *editBuffer) prepend(caret int, s string) {
e.moveGap(caret, len(s))
copy(e.text[caret:], s)
e.gapstart += len(s)
e.changed = e.changed || len(s) > 0
}
func (e *editBuffer) dump() {
if bufferDebug {
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:])
}
}
func (e *editBuffer) runeBefore(idx int) (rune, int) {
if idx > e.gapstart {
idx += e.gapLen()
}
return utf8.DecodeLastRune(e.text[:idx])
}
func (e *editBuffer) runeAt(idx int) (rune, int) {
if idx >= e.gapstart {
idx += e.gapLen()
}
return utf8.DecodeRune(e.text[idx:])
}
// Seek implements io.Seeker
func (e *editBuffer) Seek(offset int64, whence int) (ret int64, err error) {
switch whence {
case io.SeekStart:
e.pos = int(offset)
case io.SeekCurrent:
e.pos += int(offset)
case io.SeekEnd:
e.pos = e.len() - int(offset)
}
if e.pos < 0 {
e.pos = 0
} else if e.pos > e.len() {
e.pos = e.len()
}
return int64(e.pos), nil
}