github.com/Seikaijyu/gio@v0.0.1/widget/buffer.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package widget 4 5 import ( 6 "io" 7 "unicode/utf8" 8 9 "golang.org/x/text/runes" 10 ) 11 12 // editBuffer implements a gap buffer for text editing. 13 type editBuffer struct { 14 // pos is the byte position for Read and ReadRune. 15 pos int 16 17 // The gap start and end in bytes. 18 gapstart, gapend int 19 text []byte 20 21 // changed tracks whether the buffer content 22 // has changed since the last call to Changed. 23 changed bool 24 } 25 26 var _ textSource = (*editBuffer)(nil) 27 28 const minSpace = 5 29 30 func (e *editBuffer) Changed() bool { 31 c := e.changed 32 e.changed = false 33 return c 34 } 35 36 func (e *editBuffer) deleteRunes(caret, count int) (bytes int, runes int) { 37 e.moveGap(caret, 0) 38 for ; count < 0 && e.gapstart > 0; count++ { 39 _, s := utf8.DecodeLastRune(e.text[:e.gapstart]) 40 e.gapstart -= s 41 bytes += s 42 runes++ 43 e.changed = e.changed || s > 0 44 } 45 for ; count > 0 && e.gapend < len(e.text); count-- { 46 _, s := utf8.DecodeRune(e.text[e.gapend:]) 47 e.gapend += s 48 e.changed = e.changed || s > 0 49 } 50 return 51 } 52 53 // moveGap moves the gap to the caret position. After returning, 54 // the gap is guaranteed to be at least space bytes long. 55 func (e *editBuffer) moveGap(caret, space int) { 56 if e.gapLen() < space { 57 if space < minSpace { 58 space = minSpace 59 } 60 txt := make([]byte, int(e.Size())+space) 61 // Expand to capacity. 62 txt = txt[:cap(txt)] 63 gaplen := len(txt) - int(e.Size()) 64 if caret > e.gapstart { 65 copy(txt, e.text[:e.gapstart]) 66 copy(txt[caret+gaplen:], e.text[caret:]) 67 copy(txt[e.gapstart:], e.text[e.gapend:caret+e.gapLen()]) 68 } else { 69 copy(txt, e.text[:caret]) 70 copy(txt[e.gapstart+gaplen:], e.text[e.gapend:]) 71 copy(txt[caret+gaplen:], e.text[caret:e.gapstart]) 72 } 73 e.text = txt 74 e.gapstart = caret 75 e.gapend = e.gapstart + gaplen 76 } else { 77 if caret > e.gapstart { 78 copy(e.text[e.gapstart:], e.text[e.gapend:caret+e.gapLen()]) 79 } else { 80 copy(e.text[caret+e.gapLen():], e.text[caret:e.gapstart]) 81 } 82 l := e.gapLen() 83 e.gapstart = caret 84 e.gapend = e.gapstart + l 85 } 86 } 87 88 func (e *editBuffer) Size() int64 { 89 return int64(len(e.text) - e.gapLen()) 90 } 91 92 func (e *editBuffer) gapLen() int { 93 return e.gapend - e.gapstart 94 } 95 96 func (e *editBuffer) ReadAt(p []byte, offset int64) (int, error) { 97 if len(p) == 0 { 98 return 0, nil 99 } 100 if offset == e.Size() { 101 return 0, io.EOF 102 } 103 var total int 104 if offset < int64(e.gapstart) { 105 n := copy(p, e.text[offset:e.gapstart]) 106 p = p[n:] 107 total += n 108 offset += int64(n) 109 } 110 if offset >= int64(e.gapstart) { 111 n := copy(p, e.text[offset+int64(e.gapLen()):]) 112 total += n 113 } 114 return total, nil 115 } 116 117 func (e *editBuffer) ReplaceRunes(byteOffset, runeCount int64, s string) { 118 e.deleteRunes(int(byteOffset), int(runeCount)) 119 e.prepend(int(byteOffset), s) 120 } 121 122 func (e *editBuffer) prepend(caret int, s string) { 123 if !utf8.ValidString(s) { 124 s = runes.ReplaceIllFormed().String(s) 125 } 126 127 e.moveGap(caret, len(s)) 128 copy(e.text[caret:], s) 129 e.gapstart += len(s) 130 e.changed = e.changed || len(s) > 0 131 }