mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
5fcfc40ab8
Signed-off-by: Elias Naur <mail@eliasnaur.com>
129 lines
2.9 KiB
Go
129 lines
2.9 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package widget
|
|
|
|
import (
|
|
"io"
|
|
"unicode/utf8"
|
|
|
|
"golang.org/x/text/runes"
|
|
)
|
|
|
|
// editBuffer implements a gap buffer for text editing.
|
|
type editBuffer struct {
|
|
// 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
|
|
}
|
|
|
|
var _ textSource = (*editBuffer)(nil)
|
|
|
|
const minSpace = 5
|
|
|
|
func (e *editBuffer) Changed() bool {
|
|
c := e.changed
|
|
e.changed = false
|
|
return c
|
|
}
|
|
|
|
func (e *editBuffer) deleteRunes(caret, count int) (bytes int, runes int) {
|
|
e.moveGap(caret, 0)
|
|
for ; count < 0 && e.gapstart > 0; count++ {
|
|
_, s := utf8.DecodeLastRune(e.text[:e.gapstart])
|
|
e.gapstart -= s
|
|
bytes += s
|
|
runes++
|
|
e.changed = e.changed || s > 0
|
|
}
|
|
for ; count > 0 && e.gapend < len(e.text); count-- {
|
|
_, s := utf8.DecodeRune(e.text[e.gapend:])
|
|
e.gapend += s
|
|
e.changed = e.changed || s > 0
|
|
}
|
|
return
|
|
}
|
|
|
|
// 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, int(e.Size())+space)
|
|
// Expand to capacity.
|
|
txt = txt[:cap(txt)]
|
|
gaplen := len(txt) - int(e.Size())
|
|
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) Size() int64 {
|
|
return int64(len(e.text) - e.gapLen())
|
|
}
|
|
|
|
func (e *editBuffer) gapLen() int {
|
|
return e.gapend - e.gapstart
|
|
}
|
|
|
|
func (e *editBuffer) ReadAt(p []byte, offset int64) (int, error) {
|
|
if len(p) == 0 {
|
|
return 0, nil
|
|
}
|
|
if offset == e.Size() {
|
|
return 0, io.EOF
|
|
}
|
|
var total int
|
|
if offset < int64(e.gapstart) {
|
|
n := copy(p, e.text[offset:e.gapstart])
|
|
p = p[n:]
|
|
total += n
|
|
offset += int64(n)
|
|
}
|
|
if offset >= int64(e.gapstart) {
|
|
n := copy(p, e.text[offset+int64(e.gapLen()):])
|
|
total += n
|
|
}
|
|
return total, nil
|
|
}
|
|
|
|
func (e *editBuffer) ReplaceRunes(byteOffset, runeCount int64, s string) {
|
|
e.deleteRunes(int(byteOffset), int(runeCount))
|
|
e.prepend(int(byteOffset), s)
|
|
}
|
|
|
|
func (e *editBuffer) prepend(caret int, s string) {
|
|
if !utf8.ValidString(s) {
|
|
s = runes.ReplaceIllFormed().String(s)
|
|
}
|
|
|
|
e.moveGap(caret, len(s))
|
|
copy(e.text[caret:], s)
|
|
e.gapstart += len(s)
|
|
e.changed = e.changed || len(s) > 0
|
|
}
|