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