github.com/gop9/olt@v0.0.0-20200202132135-d956aad50b08/gio/widget/buffer.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package widget 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) Changed() bool { 33 c := e.changed 34 e.changed = false 35 return c 36 } 37 38 func (e *editBuffer) deleteRunes(runes int) { 39 e.moveGap(0) 40 for ; runes < 0 && e.gapstart > 0; runes++ { 41 _, s := utf8.DecodeLastRune(e.text[:e.gapstart]) 42 e.gapstart -= s 43 e.caret -= s 44 e.changed = e.changed || s > 0 45 } 46 for ; runes > 0 && e.gapend < len(e.text); runes-- { 47 _, s := utf8.DecodeRune(e.text[e.gapend:]) 48 e.gapend += s 49 e.changed = e.changed || s > 0 50 } 51 e.dump() 52 } 53 54 // moveGap moves the gap to the caret position. After returning, 55 // the gap is guaranteed to be at least space bytes long. 56 func (e *editBuffer) moveGap(space int) { 57 if e.gapLen() < space { 58 if space < minSpace { 59 space = minSpace 60 } 61 txt := make([]byte, e.len()+space) 62 // Expand to capacity. 63 txt = txt[:cap(txt)] 64 gaplen := len(txt) - e.len() 65 if e.caret > e.gapstart { 66 copy(txt, e.text[:e.gapstart]) 67 copy(txt[e.caret+gaplen:], e.text[e.caret:]) 68 copy(txt[e.gapstart:], e.text[e.gapend:e.caret+e.gapLen()]) 69 } else { 70 copy(txt, e.text[:e.caret]) 71 copy(txt[e.gapstart+gaplen:], e.text[e.gapend:]) 72 copy(txt[e.caret+gaplen:], e.text[e.caret:e.gapstart]) 73 } 74 e.text = txt 75 e.gapstart = e.caret 76 e.gapend = e.gapstart + gaplen 77 } else { 78 if e.caret > e.gapstart { 79 copy(e.text[e.gapstart:], e.text[e.gapend:e.caret+e.gapLen()]) 80 } else { 81 copy(e.text[e.caret+e.gapLen():], e.text[e.caret:e.gapstart]) 82 } 83 l := e.gapLen() 84 e.gapstart = e.caret 85 e.gapend = e.gapstart + l 86 } 87 e.dump() 88 } 89 90 func (e *editBuffer) len() int { 91 return len(e.text) - e.gapLen() 92 } 93 94 func (e *editBuffer) gapLen() int { 95 return e.gapend - e.gapstart 96 } 97 98 func (e *editBuffer) Read(p []byte) (int, error) { 99 if e.pos == e.len() { 100 return 0, io.EOF 101 } 102 var n int 103 if e.pos < e.gapstart { 104 n += copy(p, e.text[e.pos:e.gapstart]) 105 p = p[n:] 106 } 107 n += copy(p, e.text[e.gapend:]) 108 e.pos += n 109 return n, nil 110 } 111 112 func (e *editBuffer) ReadRune() (rune, int, error) { 113 if e.pos == e.len() { 114 return 0, 0, io.EOF 115 } 116 r, s := e.runeAt(e.pos) 117 e.pos += s 118 return r, s, nil 119 } 120 121 func (e *editBuffer) String() string { 122 var b strings.Builder 123 b.Grow(e.len()) 124 b.Write(e.text[:e.gapstart]) 125 b.Write(e.text[e.gapend:]) 126 return b.String() 127 } 128 129 func (e *editBuffer) prepend(s string) { 130 e.moveGap(len(s)) 131 copy(e.text[e.caret:], s) 132 e.gapstart += len(s) 133 e.changed = e.changed || len(s) > 0 134 e.dump() 135 } 136 137 func (e *editBuffer) dump() { 138 if bufferDebug { 139 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:]) 140 } 141 } 142 143 func (e *editBuffer) move(runes int) { 144 for ; runes < 0 && e.caret > 0; runes++ { 145 _, s := e.runeBefore(e.caret) 146 e.caret -= s 147 } 148 for ; runes > 0 && e.caret < len(e.text); runes-- { 149 _, s := e.runeAt(e.caret) 150 e.caret += s 151 } 152 e.dump() 153 } 154 155 func (e *editBuffer) runeBefore(idx int) (rune, int) { 156 if idx > e.gapstart { 157 idx += e.gapLen() 158 } 159 return utf8.DecodeLastRune(e.text[:idx]) 160 } 161 162 func (e *editBuffer) runeAt(idx int) (rune, int) { 163 if idx >= e.gapstart { 164 idx += e.gapLen() 165 } 166 return utf8.DecodeRune(e.text[idx:]) 167 }