buffer.go raw

   1  // SPDX-License-Identifier: Unlicense OR MIT
   2  
   3  package widget
   4  
   5  import (
   6  	"io"
   7  	"strings"
   8  	"unicode/utf8"
   9  )
  10  
  11  // editBuffer implements a gap buffer for text editing.
  12  type editBuffer struct {
  13  	// pos is the byte position for Read and ReadRune.
  14  	pos int
  15  
  16  	// The gap start and end in bytes.
  17  	gapstart, gapend int
  18  	text             []byte
  19  
  20  	// changed tracks whether the buffer content
  21  	// has changed since the last call to Changed.
  22  	changed bool
  23  }
  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, runes int) int {
  34  	e.moveGap(caret, 0)
  35  	for ; runes < 0 && e.gapstart > 0; runes++ {
  36  		_, s := utf8.DecodeLastRune(e.text[:e.gapstart])
  37  		e.gapstart -= s
  38  		caret -= s
  39  		e.changed = e.changed || s > 0
  40  	}
  41  	for ; runes > 0 && e.gapend < len(e.text); runes-- {
  42  		_, s := utf8.DecodeRune(e.text[e.gapend:])
  43  		e.gapend += s
  44  		e.changed = e.changed || s > 0
  45  	}
  46  	return caret
  47  }
  48  
  49  // moveGap moves the gap to the caret position. After returning,
  50  // the gap is guaranteed to be at least space bytes long.
  51  func (e *editBuffer) moveGap(caret, space int) {
  52  	if e.gapLen() < space {
  53  		if space < minSpace {
  54  			space = minSpace
  55  		}
  56  		txt := make([]byte, e.len()+space)
  57  		// Expand to capacity.
  58  		txt = txt[:cap(txt)]
  59  		gaplen := len(txt) - e.len()
  60  		if caret > e.gapstart {
  61  			copy(txt, e.text[:e.gapstart])
  62  			copy(txt[caret+gaplen:], e.text[caret:])
  63  			copy(txt[e.gapstart:], e.text[e.gapend:caret+e.gapLen()])
  64  		} else {
  65  			copy(txt, e.text[:caret])
  66  			copy(txt[e.gapstart+gaplen:], e.text[e.gapend:])
  67  			copy(txt[caret+gaplen:], e.text[caret:e.gapstart])
  68  		}
  69  		e.text = txt
  70  		e.gapstart = caret
  71  		e.gapend = e.gapstart + gaplen
  72  	} else {
  73  		if caret > e.gapstart {
  74  			copy(e.text[e.gapstart:], e.text[e.gapend:caret+e.gapLen()])
  75  		} else {
  76  			copy(e.text[caret+e.gapLen():], e.text[caret:e.gapstart])
  77  		}
  78  		l := e.gapLen()
  79  		e.gapstart = caret
  80  		e.gapend = e.gapstart + l
  81  	}
  82  }
  83  
  84  func (e *editBuffer) len() int {
  85  	return len(e.text) - e.gapLen()
  86  }
  87  
  88  func (e *editBuffer) gapLen() int {
  89  	return e.gapend - e.gapstart
  90  }
  91  
  92  func (e *editBuffer) Reset() {
  93  	e.Seek(0, io.SeekStart)
  94  }
  95  
  96  // Seek implements io.Seeker
  97  func (e *editBuffer) Seek(offset int64, whence int) (ret int64, err error) {
  98  	switch whence {
  99  	case io.SeekStart:
 100  		e.pos = int(offset)
 101  	case io.SeekCurrent:
 102  		e.pos += int(offset)
 103  	case io.SeekEnd:
 104  		e.pos = e.len() - int(offset)
 105  	}
 106  	if e.pos < 0 {
 107  		e.pos = 0
 108  	} else if e.pos > e.len() {
 109  		e.pos = e.len()
 110  	}
 111  	return int64(e.pos), nil
 112  }
 113  
 114  func (e *editBuffer) Read(p []byte) (int, error) {
 115  	if e.pos == e.len() {
 116  		return 0, io.EOF
 117  	}
 118  	var total int
 119  	if e.pos < e.gapstart {
 120  		n := copy(p, e.text[e.pos:e.gapstart])
 121  		p = p[n:]
 122  		total += n
 123  		e.pos += n
 124  	}
 125  	if e.pos >= e.gapstart {
 126  		n := copy(p, e.text[e.pos+e.gapLen():])
 127  		total += n
 128  		e.pos += n
 129  	}
 130  	if e.pos > e.len() {
 131  		panic("hey!")
 132  	}
 133  	return total, nil
 134  }
 135  
 136  func (e *editBuffer) ReadRune() (rune, int, error) {
 137  	if e.pos == e.len() {
 138  		return 0, 0, io.EOF
 139  	}
 140  	r, s := e.runeAt(e.pos)
 141  	e.pos += s
 142  	return r, s, nil
 143  }
 144  
 145  func (e *editBuffer) String() string {
 146  	var b strings.Builder
 147  	b.Grow(e.len())
 148  	b.Write(e.text[:e.gapstart])
 149  	b.Write(e.text[e.gapend:])
 150  	return b.String()
 151  }
 152  
 153  func (e *editBuffer) prepend(caret int, s string) {
 154  	e.moveGap(caret, len(s))
 155  	copy(e.text[caret:], s)
 156  	e.gapstart += len(s)
 157  	e.changed = e.changed || len(s) > 0
 158  }
 159  
 160  func (e *editBuffer) runeBefore(idx int) (rune, int) {
 161  	if idx > e.gapstart {
 162  		idx += e.gapLen()
 163  	}
 164  	return utf8.DecodeLastRune(e.text[:idx])
 165  }
 166  
 167  func (e *editBuffer) runeAt(idx int) (rune, int) {
 168  	if idx >= e.gapstart {
 169  		idx += e.gapLen()
 170  	}
 171  	return utf8.DecodeRune(e.text[idx:])
 172  }
 173