blob: 87da5f6af941ab078976f970d36af21517b775ac [file] [log] [blame]
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package text
// TODO: do we care about "\n" vs "\r" vs "\r\n"? We only recognize "\n" for
// now.
import (
"bytes"
"errors"
"io"
"strings"
"unicode/utf8"
"golang.org/x/image/math/fixed"
)
// Caret is a location in a Frame's text, and is the mechanism for adding and
// removing bytes of text. Conceptually, a Caret and a Frame's text is like an
// int c and a []byte t such that the text before and after that Caret is t[:c]
// and t[c:]. That byte-count location remains unchanged even when a Frame is
// re-sized and laid out into a new tree of Paragraphs, Lines and Boxes.
//
// A Frame can have multiple open Carets. For example, the beginning and end of
// a text selection can be represented by two Carets. Multiple Carets for the
// one Frame are not safe to use concurrently, but it is valid to interleave
// such operations sequentially. For example, if two Carets c0 and c1 for the
// one Frame are positioned at the 10th and 20th byte, and 4 bytes are written
// to c0, inserting what becomes the equivalent of text[10:14], then c0's
// position is updated to be 14 but c1's position is also updated to be 24.
type Caret struct {
f *Frame
// caretsIndex is the index of this Caret in the f.carets slice.
caretsIndex int
// seqNum is the Frame f's sequence number for which this Caret's cached p,
// l, b and k fields are valid. If f has been modified since then, those
// fields will have to be re-calculated based on the pos field (which is
// always valid).
//
// TODO: when re-calculating p, l, b and k, be more efficient than a linear
// scan from the start or end?
seqNum uint64
// p, l and b cache the index of the Caret's Paragraph, Line and Box. None
// of these values can be zero.
p, l, b int32
// k caches the Caret's position in the text, in Frame.text order. It is
// valid to index the Frame.text slice with k, analogous to the Box.i and
// Box.j fields. For a Caret c, letting bb := c.f.boxes[c.b], an invariant
// is that bb.i <= c.k && c.k <= bb.j if the cache is valid (i.e. the
// Caret's seqNum equals the Frame's seqNum).
k int32
// pos is the Caret's position in the text, in layout order. It is the "c"
// as in "t[:c]" in the doc comment for type Caret above. It is not valid
// to index the Frame.text slice with pos, since the Frame.text slice does
// not necessarily hold the textual content in layout order.
pos int32
tmp [utf8.UTFMax]byte
}
// Close closes the Caret.
func (c *Caret) Close() error {
i, j := c.caretsIndex, len(c.f.carets)-1
// Swap c with the last element of c.f.carets.
if i != j {
other := c.f.carets[j]
other.caretsIndex = i
c.f.carets[i] = other
}
c.f.carets[j] = nil
c.f.carets = c.f.carets[:j]
*c = Caret{}
return nil
}
type leanResult int
const (
// leanOK means that the lean changed the Caret's Box.
leanOK leanResult = iota
// leanFailedEOF means that the lean did not change the Caret's Box,
// because the Caret was already at the end / beginning of the Frame (when
// leaning forwards / backwards).
leanFailedEOF
// leanFailedNotEOB means that the lean did not change the Caret's Box,
// because the Caret was not placed at the end / beginning of the Box (when
// leaning forwards / backwards).
leanFailedNotEOB
)
// leanForwards moves the Caret from the right end of one Box to the left end
// of the next Box, crossing Lines and Paragraphs to find that next Box. It
// returns whether the Caret moved to a different Box.
//
// Diagramatically, suppose we have two adjacent boxes (shown by square
// brackets below), with the Caret (an integer location called Caret.pos in the
// Frame's text) in the middle of the "foo2bar3" word:
// [foo0 foo1 foo2]^[bar3 bar4 bar5]
// leanForwards moves Caret.k from fooBox.j to barBox.i, also updating the
// Caret's p, l and b. Caret.pos remains unchanged.
func (c *Caret) leanForwards() leanResult {
if c.k != c.f.boxes[c.b].j {
return leanFailedNotEOB
}
if nextB := c.f.boxes[c.b].next; nextB != 0 {
c.b = nextB
c.k = c.f.boxes[c.b].i
return leanOK
}
if nextL := c.f.lines[c.l].next; nextL != 0 {
c.l = nextL
c.b = c.f.lines[c.l].firstB
c.k = c.f.boxes[c.b].i
return leanOK
}
if nextP := c.f.paragraphs[c.p].next; nextP != 0 {
c.p = nextP
c.l = c.f.paragraphs[c.p].firstL
c.b = c.f.lines[c.l].firstB
c.k = c.f.boxes[c.b].i
return leanOK
}
return leanFailedEOF
}
// leanBackwards is like leanForwards but in the other direction.
func (c *Caret) leanBackwards() leanResult {
if c.k != c.f.boxes[c.b].i {
return leanFailedNotEOB
}
if prevB := c.f.boxes[c.b].prev; prevB != 0 {
c.b = prevB
c.k = c.f.boxes[c.b].j
return leanOK
}
if prevL := c.f.lines[c.l].prev; prevL != 0 {
c.l = prevL
c.b = c.f.lines[c.l].lastBox(c.f)
c.k = c.f.boxes[c.b].j
return leanOK
}
if prevP := c.f.paragraphs[c.p].prev; prevP != 0 {
c.p = prevP
c.l = c.f.paragraphs[c.p].lastLine(c.f)
c.b = c.f.lines[c.l].lastBox(c.f)
c.k = c.f.boxes[c.b].j
return leanOK
}
return leanFailedEOF
}
func (c *Caret) seekStart() {
c.p = c.f.firstP
c.l = c.f.paragraphs[c.p].firstL
c.b = c.f.lines[c.l].firstB
c.k = c.f.boxes[c.b].i
c.pos = 0
}
func (c *Caret) seekEnd() {
c.p = c.f.lastParagraph()
c.l = c.f.paragraphs[c.p].lastLine(c.f)
c.b = c.f.lines[c.l].lastBox(c.f)
c.k = c.f.boxes[c.b].j
c.pos = int32(c.f.len)
}
// calculatePLBK ensures that the Caret's cached p, l, b and k fields are
// valid.
func (c *Caret) calculatePLBK() {
if c.seqNum != c.f.seqNum {
c.seek(c.pos)
}
}
// Seek satisfies the io.Seeker interface.
func (c *Caret) Seek(offset int64, whence int) (int64, error) {
switch whence {
case SeekSet:
// No-op.
case SeekCur:
offset += int64(c.pos)
case SeekEnd:
offset += int64(c.f.len)
default:
return 0, errors.New("text: invalid seek whence")
}
if offset < 0 {
return 0, errors.New("text: negative seek position")
}
if offset > int64(c.f.len) {
offset = int64(c.f.len)
}
c.seek(int32(offset))
return offset, nil
}
func (c *Caret) seek(off int32) {
delta := off - c.pos
// If the new offset is closer to the start or the end than to the current
// c.pos, or if c's cached {p,l,b,k} values are invalid, move to the start
// or end first. In case of a tie, we prefer to seek forwards (i.e. set
// delta > 0).
if (delta < 0 && -delta >= off) || (c.seqNum != c.f.seqNum) {
c.seekStart()
delta = off - c.pos
}
if delta > 0 && delta > int32(c.f.len)-off {
c.seekEnd()
delta = off - c.pos
}
if delta != 0 {
// Seek forwards.
for delta > 0 {
if n := c.f.boxes[c.b].j - c.k; n > 0 {
if n > delta {
n = delta
}
c.pos += n
c.k += n
delta -= n
} else if c.leanForwards() != leanOK {
panic("text: invalid state")
}
}
// Seek backwards.
for delta < 0 {
if n := c.f.boxes[c.b].i - c.k; n < 0 {
if n < delta {
n = delta
}
c.pos += n
c.k += n
delta -= n
} else if c.leanBackwards() != leanOK {
panic("text: invalid state")
}
}
// A Caret can't be placed at the end of a Paragraph, unless it is the
// final Paragraph. A simple way to enforce this is to lean forwards.
c.leanForwards()
}
c.seqNum = c.f.seqNum
}
// Read satisfies the io.Reader interface by copying those bytes after the
// Caret and incrementing the Caret.
func (c *Caret) Read(buf []byte) (n int, err error) {
c.calculatePLBK()
for len(buf) > 0 {
if j := c.f.boxes[c.b].j; c.k < j {
nn := copy(buf, c.f.text[c.k:j])
buf = buf[nn:]
n += nn
c.pos += int32(nn)
c.k += int32(nn)
}
// A Caret can't be placed at the end of a Paragraph, unless it is the
// final Paragraph. A simple way to enforce this is to lean forwards.
if c.leanForwards() == leanFailedEOF {
break
}
}
if int(c.pos) == c.f.len {
err = io.EOF
}
return n, err
}
// ReadByte returns the next byte after the Caret and increments the Caret.
func (c *Caret) ReadByte() (x byte, err error) {
c.calculatePLBK()
for {
if j := c.f.boxes[c.b].j; c.k < j {
x = c.f.text[c.k]
c.pos++
c.k++
// A Caret can't be placed at the end of a Paragraph, unless it is
// the final Paragraph. A simple way to enforce this is to lean
// forwards.
c.leanForwards()
return x, nil
}
if c.leanForwards() == leanFailedEOF {
return 0, io.EOF
}
}
}
// ReadRune returns the next rune after the Caret and increments the Caret.
func (c *Caret) ReadRune() (r rune, size int, err error) {
c.calculatePLBK()
for {
if c.k < c.f.boxes[c.b].j {
r, size, c.b, c.k = c.f.readRune(c.b, c.k)
c.pos += int32(size)
// A Caret can't be placed at the end of a Paragraph, unless it is
// the final Paragraph. A simple way to enforce this is to lean
// forwards.
c.leanForwards()
return r, size, nil
}
if c.leanForwards() == leanFailedEOF {
return 0, 0, io.EOF
}
}
}
// WriteByte inserts x into the Frame's text at the Caret and increments the
// Caret.
func (c *Caret) WriteByte(x byte) error {
c.tmp[0] = x
return c.write(c.tmp[:1], "")
}
// WriteRune inserts r into the Frame's text at the Caret and increments the
// Caret.
func (c *Caret) WriteRune(r rune) (size int, err error) {
size = utf8.EncodeRune(c.tmp[:], r)
if err = c.write(c.tmp[:size], ""); err != nil {
return 0, err
}
return size, nil
}
// WriteString inserts s into the Frame's text at the Caret and increments the
// Caret.
func (c *Caret) WriteString(s string) (n int, err error) {
for len(s) > 0 {
i := 1 + strings.IndexByte(s, '\n')
if i == 0 {
i = len(s)
}
if err = c.write(nil, s[:i]); err != nil {
break
}
n += i
s = s[i:]
}
return n, err
}
// Write inserts s into the Frame's text at the Caret and increments the Caret.
func (c *Caret) Write(s []byte) (n int, err error) {
for len(s) > 0 {
i := 1 + bytes.IndexByte(s, '\n')
if i == 0 {
i = len(s)
}
if err = c.write(s[:i], ""); err != nil {
break
}
n += i
s = s[i:]
}
return n, err
}
// write inserts a []byte or string into the Frame's text at the Caret.
//
// Exactly one of s0 and s1 must be non-empty. That non-empty argument must
// contain at most one '\n' and if it does contain one, it must be the final
// byte.
func (c *Caret) write(s0 []byte, s1 string) error {
if m := maxLen - len(c.f.text); len(s0) > m || len(s1) > m {
return errors.New("text: insufficient space for writing")
}
// Ensure that the Caret is at the end of its Box, and that Box's text is
// at the end of the Frame's buffer.
c.calculatePLBK()
for {
bb, n := &c.f.boxes[c.b], int32(len(c.f.text))
if c.k == bb.j && c.k == n {
break
}
// If the Box's text is empty, move its empty i:j range to the
// equivalent empty range at the end of c.f.text.
if bb.i == bb.j {
bb.i = n
bb.j = n
for _, cc := range c.f.carets {
if cc.b == c.b {
cc.k = n
}
}
continue
}
// Make the Caret be at the end of its Box.
if c.k != bb.j {
c.splitBox(true)
continue
}
// Make a new empty Box and move the Caret to it.
c.splitBox(true)
c.leanForwards()
}
c.f.invalidateCaches()
c.f.paragraphs[c.p].invalidateCaches()
c.f.lines[c.l].invalidateCaches()
length, nl := len(s0), false
if length > 0 {
nl = s0[length-1] == '\n'
c.f.text = append(c.f.text, s0...)
} else {
length = len(s1)
nl = s1[length-1] == '\n'
c.f.text = append(c.f.text, s1...)
}
c.f.len += length
c.f.boxes[c.b].j += int32(length)
c.k += int32(length)
for _, cc := range c.f.carets {
if cc.pos > c.pos {
cc.pos += int32(length)
}
}
c.pos += int32(length)
oldL := c.l
if nl {
breakParagraph(c.f, c.p, c.l, c.b)
c.p = c.f.paragraphs[c.p].next
c.l = c.f.paragraphs[c.p].firstL
c.b = c.f.lines[c.l].firstB
c.k = c.f.boxes[c.b].i
}
// TODO: re-layout the new c.p paragraph, if we saw '\n'.
layout(c.f, oldL)
c.f.seqNum++
return nil
}
// breakParagraph breaks the Paragraph p into two Paragraphs, just after Box b
// in Line l in Paragraph p. b's text must end with a '\n'. The new Paragraph
// is inserted after p.
func breakParagraph(f *Frame, p, l, b int32) {
// Assert that the Box b's text ends with a '\n'.
if j := f.boxes[b].j; j == 0 || f.text[j-1] != '\n' {
panic("text: invalid state")
}
// Make a new, empty Paragraph after this Paragraph p.
newP, _ := f.newParagraph()
nextP := f.paragraphs[p].next
if nextP != 0 {
f.paragraphs[nextP].prev = newP
}
f.paragraphs[newP].next = nextP
f.paragraphs[newP].prev = p
f.paragraphs[p].next = newP
// Any Lines in this Paragraph after the break point's Line l move to the
// newP Paragraph.
if nextL := f.lines[l].next; nextL != 0 {
f.lines[l].next = 0
f.lines[nextL].prev = 0
f.paragraphs[newP].firstL = nextL
}
// Any Boxes in this Line after the break point's Box b move to a new Line
// at the start of the newP Paragraph.
if nextB := f.boxes[b].next; nextB != 0 {
f.boxes[b].next = 0
f.boxes[nextB].prev = 0
newL, _ := f.newLine()
f.lines[newL].firstB = nextB
if newPFirstL := f.paragraphs[newP].firstL; newPFirstL != 0 {
f.lines[newL].next = newPFirstL
f.lines[newPFirstL].prev = newL
}
f.paragraphs[newP].firstL = newL
}
// Make the newP Paragraph's first Line and first Box explicit, since
// Carets require an explicit p, l and b.
{
pp := &f.paragraphs[newP]
if pp.firstL == 0 {
pp.firstL, _ = f.newLine()
}
ll := &f.lines[pp.firstL]
if ll.firstB == 0 {
ll.firstB, _ = f.newBox()
}
}
// TODO: re-layout the newP paragraph.
}
// breakLine breaks the Line l at text index k in Box b. The b-and-k index must
// not be at the start or end of the Line. Text to the right of b-and-k in the
// Line l will be moved to the start of the next Line in the Paragraph, with
// that next Line being created if it didn't already exist.
func breakLine(f *Frame, l, b, k int32) {
// Split this Box into two if necessary, so that k equals a Box's j end.
bb := &f.boxes[b]
if k != bb.j {
if k == bb.i {
panic("TODO: degenerate split left, possibly adjusting the Line's firstB??")
}
newB, realloc := f.newBox()
if realloc {
bb = &f.boxes[b]
}
nextB := bb.next
if nextB != 0 {
f.boxes[nextB].prev = newB
}
f.boxes[newB].next = nextB
f.boxes[newB].prev = b
f.boxes[newB].i = k
f.boxes[newB].j = bb.j
bb.next = newB
bb.j = k
}
// Assert that the break point isn't already at the start or end of the Line.
if bb.next == 0 || (bb.prev == 0 && k == bb.i) {
panic("text: invalid state")
}
// Insert a line after this one, if one doesn't already exist.
ll := &f.lines[l]
if ll.next == 0 {
newL, realloc := f.newLine()
if realloc {
ll = &f.lines[l]
}
f.lines[newL].prev = l
ll.next = newL
}
// Move the remaining boxes to the next line.
nextB, nextL := bb.next, ll.next
bb.next = 0
f.boxes[nextB].prev = 0
fb := f.lines[nextL].firstB
f.lines[nextL].firstB = nextB
// If the next Line already contained Boxes, append them to the end of the
// nextB chain, and join the two newly linked Boxes if possible.
if fb != 0 {
lb := f.lines[nextL].lastBox(f)
lbb := &f.boxes[lb]
fbb := &f.boxes[fb]
lbb.next = fb
fbb.prev = lb
f.joinBoxes(lb, fb, lbb, fbb)
}
}
// layout inserts a soft return in the Line l if its text measures longer than
// f.maxWidth and a suitable line break point is found. This may spill text
// onto the next line, which will also be laid out, and so on recursively.
func layout(f *Frame, l int32) {
if f.maxWidth <= 0 || f.face == nil {
return
}
f.seqNum++
for ; l != 0; l = f.lines[l].next {
var (
firstB = f.lines[l].firstB
reader = f.lineReader(firstB, f.boxes[firstB].i)
breakPoint bAndK
prevR rune
prevRValid bool
advance fixed.Int26_6
)
for {
r, _, err := reader.ReadRune()
if err != nil || r == '\n' {
return
}
if prevRValid {
advance += f.face.Kern(prevR, r)
}
// TODO: match all whitespace, not just ' '?
if r == ' ' {
breakPoint = reader.bAndK()
}
a, ok := f.face.GlyphAdvance(r)
if !ok {
panic("TODO: is falling back on the U+FFFD glyph the responsibility of the caller or the Face?")
}
advance += a
if r != ' ' && advance > f.maxWidth && breakPoint.b != 0 {
breakLine(f, l, breakPoint.b, breakPoint.k)
break
}
prevR, prevRValid = r, true
}
}
}
// Delete deletes nBytes bytes in the specified direction from the Caret's
// location. It returns the number of bytes deleted, which can be fewer than
// that requested if it hits the beginning or end of the Frame.
func (c *Caret) Delete(dir Direction, nBytes int) (dBytes int) {
if nBytes <= 0 {
return 0
}
// Convert a backwards delete of n bytes from position p to a forwards
// delete of n bytes from position p-n.
//
// In general, it's easier to delete forwards than backwards. For example,
// when crossing paragraph boundaries, it's easier to find the first line
// of the next paragraph than the last line of the previous paragraph.
if dir == Backwards {
newPos := int(c.pos) - nBytes
if newPos < 0 {
newPos = 0
nBytes = int(c.pos)
if nBytes == 0 {
return 0
}
}
c.seek(int32(newPos))
}
if int(c.pos) == c.f.len {
return 0
}
c.calculatePLBK()
c.leanForwards()
if c.f.boxes[c.b].i != c.k && c.splitBox(false) {
c.leanForwards()
}
for nBytes > 0 && int(c.pos) != c.f.len {
bb := &c.f.boxes[c.b]
n := bb.j - bb.i
newLine := n != 0 && c.f.text[bb.j-1] == '\n'
if int(n) > nBytes {
n = int32(nBytes)
}
bb.i += n
c.k += n
dBytes += int(n)
nBytes -= int(n)
c.f.len -= int(n)
if bb.i != bb.j {
break
}
if newLine {
c.joinNextParagraph()
}
c.leanForwards()
}
// The mergeIntoOneLine will shake out any empty Boxes.
l := c.f.mergeIntoOneLine(c.p)
layout(c.f, l)
c.f.invalidateCaches()
// Compact c.f.text if it's large enough and the fraction of deleted text
// is above some threshold. The actual threshold value (25%) is arbitrary.
// A lower value means more frequent compactions, so less memory on average
// but more CPU. A higher value means the opposite.
if len(c.f.text) > 4096 && len(c.f.text)/4 < c.f.deletedLen() {
c.f.compactText()
}
c.f.seqNum++
for _, cc := range c.f.carets {
if cc == c {
continue
}
switch relPos := cc.pos - c.pos; {
case relPos <= 0:
// No-op.
case relPos <= int32(dBytes):
cc.pos = c.pos
default:
cc.pos -= int32(dBytes)
}
}
return dBytes
}
// DeleteRunes deletes nRunes runes in the specified direction from the Caret's
// location. It returns the number of runes and bytes deleted, which can be
// fewer than that requested if it hits the beginning or end of the Frame.
func (c *Caret) DeleteRunes(dir Direction, nRunes int) (dRunes, dBytes int) {
// Save the current Caret position, move the Caret by nRunes runes to
// calculate how many bytes to delete, restore that saved Caret position,
// then delete that many bytes.
c.calculatePLBK()
savedC := *c
if dir == Forwards {
for dRunes < nRunes {
var size int
_, size, c.b, c.k = c.f.readRune(c.b, c.k)
if size != 0 {
dRunes++
dBytes += size
} else if c.leanForwards() != leanOK {
break
}
}
} else {
for dRunes < nRunes {
var size int
_, size, c.b, c.k = c.f.readLastRune(c.b, c.k)
if size != 0 {
dRunes++
dBytes += size
} else if c.leanBackwards() != leanOK {
break
}
}
}
*c = savedC
if dBytes != c.Delete(dir, dBytes) {
panic("text: invalid state")
}
return dRunes, dBytes
}
// joinNextParagraph joins c's current and next Paragraph. That next Paragraph
// must exist, and c must be at the last Line of its current Paragraph.
func (c *Caret) joinNextParagraph() {
pp0 := &c.f.paragraphs[c.p]
ll0 := &c.f.lines[c.l]
if pp0.next == 0 || ll0.next != 0 {
panic("text: invalid state")
}
pp1 := &c.f.paragraphs[pp0.next]
l1 := pp1.firstL
ll0.next = l1
c.f.lines[l1].prev = c.l
toFree := pp0.next
pp0.next = pp1.next
if pp0.next != 0 {
c.f.paragraphs[pp0.next].prev = c.p
}
c.f.freeParagraph(toFree)
}
// splitBox splits the Caret's Box into two, at the Caret's location. Unless
// force is set, it does nothing if the Caret is at either edge of its Box. It
// returns whether the Box was split. If so, the new Box is created after, not
// before, the Caret's current Box.
func (c *Caret) splitBox(force bool) bool {
bb := &c.f.boxes[c.b]
if !force && (c.k == bb.i || c.k == bb.j) {
return false
}
newB, realloc := c.f.newBox()
if realloc {
bb = &c.f.boxes[c.b]
}
nextB := bb.next
if nextB != 0 {
c.f.boxes[nextB].prev = newB
}
c.f.boxes[newB] = Box{
next: nextB,
prev: c.b,
i: c.k,
j: bb.j,
}
bb.next = newB
bb.j = c.k
return true
}