github.com/utopiagio/gio@v0.0.8/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  }