gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/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 // The gap start and end in bytes. 15 gapstart, gapend int 16 text []byte 17 18 // changed tracks whether the buffer content 19 // has changed since the last call to Changed. 20 changed bool 21 } 22 23 var _ textSource = (*editBuffer)(nil) 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, count int) (bytes int, runes int) { 34 e.moveGap(caret, 0) 35 for ; count < 0 && e.gapstart > 0; count++ { 36 _, s := utf8.DecodeLastRune(e.text[:e.gapstart]) 37 e.gapstart -= s 38 bytes += s 39 runes++ 40 e.changed = e.changed || s > 0 41 } 42 for ; count > 0 && e.gapend < len(e.text); count-- { 43 _, s := utf8.DecodeRune(e.text[e.gapend:]) 44 e.gapend += s 45 e.changed = e.changed || s > 0 46 } 47 return 48 } 49 50 // moveGap moves the gap to the caret position. After returning, 51 // the gap is guaranteed to be at least space bytes long. 52 func (e *editBuffer) moveGap(caret, space int) { 53 if e.gapLen() < space { 54 if space < minSpace { 55 space = minSpace 56 } 57 txt := make([]byte, int(e.Size())+space) 58 // Expand to capacity. 59 txt = txt[:cap(txt)] 60 gaplen := len(txt) - int(e.Size()) 61 if caret > e.gapstart { 62 copy(txt, e.text[:e.gapstart]) 63 copy(txt[caret+gaplen:], e.text[caret:]) 64 copy(txt[e.gapstart:], e.text[e.gapend:caret+e.gapLen()]) 65 } else { 66 copy(txt, e.text[:caret]) 67 copy(txt[e.gapstart+gaplen:], e.text[e.gapend:]) 68 copy(txt[caret+gaplen:], e.text[caret:e.gapstart]) 69 } 70 e.text = txt 71 e.gapstart = caret 72 e.gapend = e.gapstart + gaplen 73 } else { 74 if caret > e.gapstart { 75 copy(e.text[e.gapstart:], e.text[e.gapend:caret+e.gapLen()]) 76 } else { 77 copy(e.text[caret+e.gapLen():], e.text[caret:e.gapstart]) 78 } 79 l := e.gapLen() 80 e.gapstart = caret 81 e.gapend = e.gapstart + l 82 } 83 } 84 85 func (e *editBuffer) Size() int64 { 86 return int64(len(e.text) - e.gapLen()) 87 } 88 89 func (e *editBuffer) gapLen() int { 90 return e.gapend - e.gapstart 91 } 92 93 func (e *editBuffer) ReadAt(p []byte, offset int64) (int, error) { 94 if len(p) == 0 { 95 return 0, nil 96 } 97 if offset == e.Size() { 98 return 0, io.EOF 99 } 100 var total int 101 if offset < int64(e.gapstart) { 102 n := copy(p, e.text[offset:e.gapstart]) 103 p = p[n:] 104 total += n 105 offset += int64(n) 106 } 107 if offset >= int64(e.gapstart) { 108 n := copy(p, e.text[offset+int64(e.gapLen()):]) 109 total += n 110 } 111 return total, nil 112 } 113 114 func (e *editBuffer) ReplaceRunes(byteOffset, runeCount int64, s string) { 115 e.deleteRunes(int(byteOffset), int(runeCount)) 116 e.prepend(int(byteOffset), s) 117 } 118 119 func (e *editBuffer) prepend(caret int, s string) { 120 if !utf8.ValidString(s) { 121 s = runes.ReplaceIllFormed().String(s) 122 } 123 124 e.moveGap(caret, len(s)) 125 copy(e.text[caret:], s) 126 e.gapstart += len(s) 127 e.changed = e.changed || len(s) > 0 128 }