buffer.go raw

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