359 lines
10 KiB
Go
Executable File
359 lines
10 KiB
Go
Executable File
package buffer
|
|
|
|
import (
|
|
"io"
|
|
"unicode/utf8"
|
|
|
|
ropes "github.com/zyedidia/rope"
|
|
)
|
|
|
|
type RopeBuffer struct {
|
|
rope *ropes.Node
|
|
anchors []*Cursor
|
|
}
|
|
|
|
func NewRopeBuffer(contents []byte) *RopeBuffer {
|
|
return &RopeBuffer{
|
|
ropes.New(contents),
|
|
nil,
|
|
}
|
|
}
|
|
|
|
// LineColToPos returns the index of the byte at line, col. If line is less than
|
|
// zero, or more than the number of available lines, the function will panic. If
|
|
// col is less than zero, the function will panic. If col is greater than the
|
|
// length of the line, the position of the last byte of the line is returned,
|
|
// instead.
|
|
func (b *RopeBuffer) LineColToPos(line, col int) int {
|
|
pos := b.getLineStartPos(line)
|
|
|
|
// Have to do this algorithm for safety. If this function was declared to panic
|
|
// or index out of bounds memory, if col > the given line length, it would be
|
|
// more efficient and simpler. But unfortunately, I believe it is necessary.
|
|
if col > 0 {
|
|
_, r := b.rope.SplitAt(pos)
|
|
l, _ := r.SplitAt(b.rope.Len() - pos)
|
|
|
|
l.EachLeaf(func(n *ropes.Node) bool {
|
|
data := n.Value() // Reference; not a copy.
|
|
for _, r := range string(data) {
|
|
if col == 0 || r == '\n' {
|
|
return true // Found the position of the column
|
|
}
|
|
pos++
|
|
col--
|
|
}
|
|
return false // Have not gotten to the appropriate position, yet
|
|
})
|
|
}
|
|
|
|
return pos
|
|
}
|
|
|
|
// Line returns a slice of the data at the given line, including the ending line-
|
|
// delimiter. line starts from zero. Data returned may or may not be a copy: do not
|
|
// write it.
|
|
func (b *RopeBuffer) Line(line int) []byte {
|
|
pos := b.getLineStartPos(line)
|
|
bytes := 0
|
|
|
|
_, r := b.rope.SplitAt(pos)
|
|
l, _ := r.SplitAt(b.rope.Len() - pos)
|
|
|
|
var isCRLF bool // true if the last byte was '\r'
|
|
l.EachLeaf(func(n *ropes.Node) bool {
|
|
data := n.Value() // Reference; not a copy.
|
|
for i, r := range string(data) {
|
|
if r == '\r' {
|
|
isCRLF = true
|
|
} else if r == '\n' {
|
|
bytes += i // Add bytes before i
|
|
if isCRLF {
|
|
bytes += 2 // Add the CRLF bytes
|
|
} else {
|
|
bytes += 1 // Add LF byte
|
|
}
|
|
return true // Read (past-tense) the whole line
|
|
} else {
|
|
isCRLF = false
|
|
}
|
|
}
|
|
bytes += len(data)
|
|
return false // Have not read the whole line, yet
|
|
})
|
|
|
|
return b.rope.Slice(pos, pos+bytes) // NOTE: may be faster to do it ourselves
|
|
}
|
|
|
|
// Returns a slice of the buffer from startLine, startCol, to endLine, endCol,
|
|
// inclusive bounds. The returned value may or may not be a copy of the data,
|
|
// so do not write to it.
|
|
func (b *RopeBuffer) Slice(startLine, startCol, endLine, endCol int) []byte {
|
|
endPos := b.LineColToPos(endLine, endCol)
|
|
if length := b.rope.Len(); endPos >= length {
|
|
endPos = length - 1
|
|
}
|
|
return b.rope.Slice(b.LineColToPos(startLine, startCol), endPos+1)
|
|
}
|
|
|
|
// Bytes returns all of the bytes in the buffer. This function is very likely
|
|
// to copy all of the data in the buffer. Use sparingly. Try using other methods,
|
|
// where possible.
|
|
func (b *RopeBuffer) Bytes() []byte {
|
|
return b.rope.Value()
|
|
}
|
|
|
|
// Insert copies a byte slice (inserting it) into the position at line, col.
|
|
func (b *RopeBuffer) Insert(line, col int, value []byte) {
|
|
b.rope.Insert(b.LineColToPos(line, col), value)
|
|
b.shiftAnchors(line, col, utf8.RuneCount(value))
|
|
}
|
|
|
|
// Remove deletes any characters between startLine, startCol, and endLine,
|
|
// endCol, inclusive bounds.
|
|
func (b *RopeBuffer) Remove(startLine, startCol, endLine, endCol int) {
|
|
start := b.LineColToPos(startLine, startCol)
|
|
end := b.LineColToPos(endLine, endCol) + 1
|
|
|
|
if len := b.rope.Len(); end >= len {
|
|
end = len
|
|
if start > end {
|
|
return
|
|
}
|
|
}
|
|
|
|
b.rope.Remove(start, end)
|
|
// Shift anchors within the range
|
|
b.shiftAnchorsRemovedRange(start, startLine, startCol, endLine, endCol)
|
|
// Shift anchors after the range
|
|
b.shiftAnchors(endLine, endCol+1, start-end-1)
|
|
}
|
|
|
|
// Returns the number of occurrences of 'sequence' in the buffer, within the range
|
|
// of start line and col, to end line and col. End is exclusive.
|
|
func (b *RopeBuffer) Count(startLine, startCol, endLine, endCol int, sequence []byte) int {
|
|
startPos := b.LineColToPos(startLine, startCol)
|
|
endPos := b.LineColToPos(endLine, endCol)
|
|
return b.rope.Count(startPos, endPos, sequence)
|
|
}
|
|
|
|
// Len returns the number of bytes in the buffer.
|
|
func (b *RopeBuffer) Len() int {
|
|
return b.rope.Len()
|
|
}
|
|
|
|
// Lines returns the number of lines in the buffer. If the buffer is empty,
|
|
// 1 is returned, because there is always at least one line. This function
|
|
// basically counts the number of newline ('\n') characters in a buffer.
|
|
func (b *RopeBuffer) Lines() int {
|
|
rope := b.rope
|
|
return rope.Count(0, rope.Len(), []byte{'\n'}) + 1
|
|
}
|
|
|
|
// getLineStartPos returns the first byte index of the given line (starting from zero).
|
|
// The returned index can be equal to the length of the buffer, not pointing to any byte,
|
|
// which means the byte is on the last, and empty, line of the buffer. If line is greater
|
|
// than or equal to the number of lines in the buffer, a panic is issued.
|
|
func (b *RopeBuffer) getLineStartPos(line int) int {
|
|
var pos int
|
|
|
|
if line > 0 {
|
|
b.rope.IndexAllFunc(0, b.rope.Len(), []byte{'\n'}, func(idx int) bool {
|
|
line--
|
|
pos = idx + 1 // idx+1 = start of line after delimiter
|
|
return line <= 0 // If pos is now the start of the line we're searching for
|
|
})
|
|
}
|
|
|
|
if line > 0 { // If there aren't enough lines to reach line...
|
|
panic("getLineStartPos: not enough lines in buffer to reach position")
|
|
}
|
|
|
|
return pos
|
|
}
|
|
|
|
// RunesInLineWithDelim returns the number of runes in the given line. That is, the
|
|
// number of Utf-8 codepoints in the line, not bytes. Includes the line delimiter
|
|
// in the count. If that line delimiter is CRLF ('\r\n'), then it adds two.
|
|
func (b *RopeBuffer) RunesInLineWithDelim(line int) int {
|
|
linePos := b.getLineStartPos(line)
|
|
|
|
ropeLen := b.rope.Len()
|
|
|
|
if linePos >= ropeLen {
|
|
return 0
|
|
}
|
|
|
|
var count int
|
|
|
|
_, r := b.rope.SplitAt(linePos)
|
|
l, _ := r.SplitAt(ropeLen - linePos)
|
|
|
|
l.EachLeaf(func(n *ropes.Node) bool {
|
|
data := n.Value() // Reference; not a copy.
|
|
for _, r := range string(data) {
|
|
count++ // Before: we count the line delimiter
|
|
if r == '\n' {
|
|
return true // Read (past-tense) the whole line
|
|
}
|
|
}
|
|
return false // Have not read the whole line, yet
|
|
})
|
|
|
|
return count
|
|
}
|
|
|
|
// RunesInLine returns the number of runes in the given line. That is, the
|
|
// number of Utf-8 codepoints in the line, not bytes. Excludes line delimiters.
|
|
func (b *RopeBuffer) RunesInLine(line int) int {
|
|
linePos := b.getLineStartPos(line)
|
|
|
|
ropeLen := b.rope.Len()
|
|
|
|
if linePos >= ropeLen {
|
|
return 0
|
|
}
|
|
|
|
var count int
|
|
|
|
_, r := b.rope.SplitAt(linePos)
|
|
l, _ := r.SplitAt(ropeLen - linePos)
|
|
|
|
var isCRLF bool
|
|
l.EachLeaf(func(n *ropes.Node) bool {
|
|
data := n.Value() // Reference; not a copy.
|
|
for _, r := range string(data) {
|
|
if r == '\r' {
|
|
isCRLF = true
|
|
} else if r == '\n' {
|
|
return true // Read (past-tense) the whole line
|
|
} else {
|
|
if isCRLF {
|
|
isCRLF = false
|
|
count++ // Add the '\r' we previously thought was part of the delim.
|
|
}
|
|
}
|
|
count++
|
|
}
|
|
return false // Have not read the whole line, yet
|
|
})
|
|
|
|
return count
|
|
}
|
|
|
|
// ClampLineCol is a utility function to clamp any provided line and col to
|
|
// only possible values within the buffer, pointing to runes. It first clamps
|
|
// the line, then clamps the column. The column is clamped between zero and
|
|
// the last rune before the line delimiter.
|
|
func (b *RopeBuffer) ClampLineCol(line, col int) (int, int) {
|
|
if line < 0 {
|
|
line = 0
|
|
} else if lines := b.Lines() - 1; line > lines {
|
|
line = lines
|
|
}
|
|
|
|
if col < 0 {
|
|
col = 0
|
|
} else if runes := b.RunesInLine(line); col > runes {
|
|
col = runes
|
|
}
|
|
|
|
return line, col
|
|
}
|
|
|
|
// PosToLineCol converts a byte offset (position) of the buffer's bytes, into
|
|
// a line and column. Unless you are working with the Bytes() function, this
|
|
// is unlikely to be useful to you. Position will be clamped.
|
|
func (b *RopeBuffer) PosToLineCol(pos int) (int, int) {
|
|
var line, col int
|
|
var wasAtNewline bool
|
|
|
|
if pos <= 0 {
|
|
return line, col
|
|
}
|
|
|
|
b.rope.EachLeaf(func(n *ropes.Node) bool {
|
|
data := n.Value()
|
|
var i int
|
|
for i < len(data) {
|
|
if wasAtNewline { // Start of line
|
|
if data[i] != '\n' { // If the start of this line does not happen to be a delim...
|
|
wasAtNewline = false // Say we weren't previously at a delimiter
|
|
}
|
|
line, col = line+1, 0
|
|
} else if data[i] == '\n' { // End of line
|
|
wasAtNewline = true
|
|
col++
|
|
} else {
|
|
col++
|
|
}
|
|
|
|
_, size := utf8.DecodeRune(data[i:])
|
|
i += size
|
|
pos -= size
|
|
|
|
if pos < 0 {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
})
|
|
|
|
return line, col
|
|
}
|
|
|
|
func (b *RopeBuffer) WriteTo(w io.Writer) (int64, error) {
|
|
return b.rope.WriteTo(w)
|
|
}
|
|
|
|
// Currently meant for the Remove function: imagine if the removed region passes through a Cursor position.
|
|
// We want to shift the cursor to the start of the region, based upon where the cursor position is.
|
|
func (b *RopeBuffer) shiftAnchorsRemovedRange(startPos, startLine, startCol, endLine, endCol int) {
|
|
for _, v := range b.anchors {
|
|
cursorLine, cursorCol := v.GetLineCol()
|
|
if cursorLine >= startLine && cursorLine <= endLine {
|
|
// If the anchor is not within the start or end columns
|
|
if (cursorLine == startLine && cursorCol < startCol) || (cursorLine == endLine && cursorCol > endCol) {
|
|
continue
|
|
}
|
|
cursorPos := b.LineColToPos(cursorLine, cursorCol)
|
|
v.line, v.col = b.PosToLineCol(cursorPos + (startPos - cursorPos))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (b *RopeBuffer) shiftAnchors(insertLine, insertCol, runeCount int) {
|
|
for _, v := range b.anchors {
|
|
cursorLine, cursorCol := v.GetLineCol()
|
|
if insertLine < cursorLine || (insertLine == cursorLine && insertCol <= cursorCol) {
|
|
v.line, v.col = b.PosToLineCol(b.LineColToPos(cursorLine, cursorCol) + runeCount)
|
|
}
|
|
}
|
|
}
|
|
|
|
// RegisterCursor adds the Cursor to a slice which the Buffer uses to update
|
|
// each Cursor based on changes that occur in the Buffer. Various functions are
|
|
// called on the Cursor depending upon where the edits occurred and how it should
|
|
// modify the Cursor's position. Unregister a Cursor before deleting it from
|
|
// memory, or forgetting it, with UnregisterPosition.
|
|
func (b *RopeBuffer) RegisterCursor(cursor *Cursor) {
|
|
if cursor == nil {
|
|
return
|
|
}
|
|
b.anchors = append(b.anchors, cursor)
|
|
}
|
|
|
|
// UnregisterCursor will remove the cursor from the list of watched Cursors.
|
|
// It is mandatory that a Cursor be unregistered before being freed from memory,
|
|
// or otherwise being forgotten.
|
|
func (b *RopeBuffer) UnregisterCursor(cursor *Cursor) {
|
|
for i, v := range b.anchors {
|
|
if cursor == v {
|
|
// Delete item at i without preserving order
|
|
b.anchors[i] = b.anchors[len(b.anchors)-1]
|
|
b.anchors[len(b.anchors)-1] = nil
|
|
b.anchors = b.anchors[:len(b.anchors)-1]
|
|
}
|
|
}
|
|
}
|