github.com/Seikaijyu/gio@v0.0.1/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  	// pos is the byte position for Read and ReadRune.
    15  	pos int
    16  
    17  	// The gap start and end in bytes.
    18  	gapstart, gapend int
    19  	text             []byte
    20  
    21  	// changed tracks whether the buffer content
    22  	// has changed since the last call to Changed.
    23  	changed bool
    24  }
    25  
    26  var _ textSource = (*editBuffer)(nil)
    27  
    28  const minSpace = 5
    29  
    30  func (e *editBuffer) Changed() bool {
    31  	c := e.changed
    32  	e.changed = false
    33  	return c
    34  }
    35  
    36  func (e *editBuffer) deleteRunes(caret, count int) (bytes int, runes int) {
    37  	e.moveGap(caret, 0)
    38  	for ; count < 0 && e.gapstart > 0; count++ {
    39  		_, s := utf8.DecodeLastRune(e.text[:e.gapstart])
    40  		e.gapstart -= s
    41  		bytes += s
    42  		runes++
    43  		e.changed = e.changed || s > 0
    44  	}
    45  	for ; count > 0 && e.gapend < len(e.text); count-- {
    46  		_, s := utf8.DecodeRune(e.text[e.gapend:])
    47  		e.gapend += s
    48  		e.changed = e.changed || s > 0
    49  	}
    50  	return
    51  }
    52  
    53  // moveGap moves the gap to the caret position. After returning,
    54  // the gap is guaranteed to be at least space bytes long.
    55  func (e *editBuffer) moveGap(caret, space int) {
    56  	if e.gapLen() < space {
    57  		if space < minSpace {
    58  			space = minSpace
    59  		}
    60  		txt := make([]byte, int(e.Size())+space)
    61  		// Expand to capacity.
    62  		txt = txt[:cap(txt)]
    63  		gaplen := len(txt) - int(e.Size())
    64  		if caret > e.gapstart {
    65  			copy(txt, e.text[:e.gapstart])
    66  			copy(txt[caret+gaplen:], e.text[caret:])
    67  			copy(txt[e.gapstart:], e.text[e.gapend:caret+e.gapLen()])
    68  		} else {
    69  			copy(txt, e.text[:caret])
    70  			copy(txt[e.gapstart+gaplen:], e.text[e.gapend:])
    71  			copy(txt[caret+gaplen:], e.text[caret:e.gapstart])
    72  		}
    73  		e.text = txt
    74  		e.gapstart = caret
    75  		e.gapend = e.gapstart + gaplen
    76  	} else {
    77  		if caret > e.gapstart {
    78  			copy(e.text[e.gapstart:], e.text[e.gapend:caret+e.gapLen()])
    79  		} else {
    80  			copy(e.text[caret+e.gapLen():], e.text[caret:e.gapstart])
    81  		}
    82  		l := e.gapLen()
    83  		e.gapstart = caret
    84  		e.gapend = e.gapstart + l
    85  	}
    86  }
    87  
    88  func (e *editBuffer) Size() int64 {
    89  	return int64(len(e.text) - e.gapLen())
    90  }
    91  
    92  func (e *editBuffer) gapLen() int {
    93  	return e.gapend - e.gapstart
    94  }
    95  
    96  func (e *editBuffer) ReadAt(p []byte, offset int64) (int, error) {
    97  	if len(p) == 0 {
    98  		return 0, nil
    99  	}
   100  	if offset == e.Size() {
   101  		return 0, io.EOF
   102  	}
   103  	var total int
   104  	if offset < int64(e.gapstart) {
   105  		n := copy(p, e.text[offset:e.gapstart])
   106  		p = p[n:]
   107  		total += n
   108  		offset += int64(n)
   109  	}
   110  	if offset >= int64(e.gapstart) {
   111  		n := copy(p, e.text[offset+int64(e.gapLen()):])
   112  		total += n
   113  	}
   114  	return total, nil
   115  }
   116  
   117  func (e *editBuffer) ReplaceRunes(byteOffset, runeCount int64, s string) {
   118  	e.deleteRunes(int(byteOffset), int(runeCount))
   119  	e.prepend(int(byteOffset), s)
   120  }
   121  
   122  func (e *editBuffer) prepend(caret int, s string) {
   123  	if !utf8.ValidString(s) {
   124  		s = runes.ReplaceIllFormed().String(s)
   125  	}
   126  
   127  	e.moveGap(caret, len(s))
   128  	copy(e.text[caret:], s)
   129  	e.gapstart += len(s)
   130  	e.changed = e.changed || len(s) > 0
   131  }