214 lines
6.1 KiB
Go
214 lines
6.1 KiB
Go
package buffer
|
|
|
|
import (
|
|
"io"
|
|
"unicode/utf8"
|
|
|
|
"github.com/zyedidia/rope"
|
|
)
|
|
|
|
type RopeBuffer rope.Node
|
|
|
|
func NewRopeBuffer(contents []byte) *RopeBuffer {
|
|
return (*RopeBuffer)(rope.New(contents))
|
|
}
|
|
|
|
// 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) pos(line, col int) int {
|
|
var pos int
|
|
|
|
_rope := (*rope.Node)(b)
|
|
|
|
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 := _rope.SplitAt(pos)
|
|
l, _ := r.SplitAt(_rope.Len() - pos)
|
|
|
|
l.EachLeaf(func(n *rope.Node) bool {
|
|
data := n.Value() // Reference; not a copy.
|
|
var i int
|
|
for i < len(data) {
|
|
if col == 0 || data[i] == '\n' {
|
|
return true // Found the position of the column
|
|
}
|
|
pos++
|
|
col--
|
|
|
|
// Respect Utf-8 codepoint boundaries
|
|
_, size := utf8.DecodeRune(data[i:])
|
|
i += size
|
|
}
|
|
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
|
|
|
|
_rope := (*rope.Node)(b)
|
|
_, r := _rope.SplitAt(pos)
|
|
l, _ := r.SplitAt(_rope.Len() - pos)
|
|
|
|
l.EachLeaf(func(n *rope.Node) bool {
|
|
data := n.Value() // Reference; not a copy.
|
|
var i int
|
|
for i < len(data) {
|
|
if data[i] == '\n' {
|
|
bytes++ // Add the newline byte
|
|
return true // Read (past-tense) the whole line
|
|
}
|
|
|
|
// Respect Utf-8 codepoint boundaries
|
|
_, size := utf8.DecodeRune(data[i:])
|
|
bytes += size
|
|
i += size
|
|
}
|
|
return false // Have not read the whole line, yet
|
|
})
|
|
|
|
return _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 {
|
|
return (*rope.Node)(b).Slice(b.pos(startLine, startCol), b.pos(endLine, endCol)+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 (*rope.Node)(b).Value()
|
|
}
|
|
|
|
// Insert copies a byte slice (inserting it) into the position at line, col.
|
|
func (b *RopeBuffer) Insert(line, col int, value []byte) {
|
|
(*rope.Node)(b).Insert(b.pos(line, col), value)
|
|
}
|
|
|
|
// Remove deletes any characters between startLine, startCol, and endLine,
|
|
// endCol, inclusive bounds.
|
|
func (b *RopeBuffer) Remove(startLine, startCol, endLine, endCol int) {
|
|
(*rope.Node)(b).Remove(b.pos(startLine, startCol), b.pos(endLine, endCol)+1)
|
|
}
|
|
|
|
// Len returns the number of bytes in the buffer.
|
|
func (b *RopeBuffer) Len() int {
|
|
return (*rope.Node)(b).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 := (*rope.Node)(b)
|
|
return rope.Count(0, rope.Len(), []byte{'\n'}) + 1
|
|
}
|
|
|
|
// pos is the first byte index in the line we want to count runes/cols. pos can point to
|
|
// a newline or be greater than or equal to the number of bytes in the buffer, and will
|
|
// return 0 for both cases.
|
|
func (b *RopeBuffer) runesInLine(pos int) int {
|
|
_rope := (*rope.Node)(b)
|
|
ropeLen := _rope.Len()
|
|
|
|
if pos >= ropeLen {
|
|
return 0
|
|
}
|
|
|
|
var count int
|
|
|
|
_, r := _rope.SplitAt(pos)
|
|
l, _ := r.SplitAt(ropeLen - pos)
|
|
|
|
l.EachLeaf(func(n *rope.Node) bool {
|
|
data := n.Value() // Reference; not a copy.
|
|
var i int
|
|
for i < len(data) {
|
|
if data[i] == '\n' {
|
|
return true // Read (past-tense) the whole line
|
|
}
|
|
count++
|
|
|
|
// Respect Utf-8 codepoint boundaries
|
|
_, size := utf8.DecodeRune(data[i:])
|
|
i += size
|
|
}
|
|
return false // Have not read the whole line, yet
|
|
})
|
|
|
|
return count
|
|
}
|
|
|
|
// 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 {
|
|
_rope := (*rope.Node)(b)
|
|
var pos int
|
|
|
|
if line > 0 {
|
|
_rope.IndexAllFunc(0, _rope.Len(), []byte{'\n'}, func(idx int) bool {
|
|
line--
|
|
pos = idx + 1 // idx+1 = start of line after delimiter
|
|
if line <= 0 { // If pos is now the start of the line we're searching for...
|
|
return true // Stop indexing
|
|
}
|
|
return false
|
|
})
|
|
}
|
|
|
|
if line > 0 { // If there aren't enough lines to reach line...
|
|
panic("getLineStartPos: not enough lines in buffer to reach position")
|
|
}
|
|
|
|
return pos
|
|
}
|
|
|
|
// RunesInLine returns the number of runes in the given line. That is, the
|
|
// number of Utf-8 codepoints in the line, not bytes.
|
|
func (b *RopeBuffer) RunesInLine(line int) int {
|
|
linePos := b.getLineStartPos(line)
|
|
return b.runesInLine(linePos)
|
|
}
|
|
|
|
// 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.
|
|
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 cols := b.RunesInLine(line)-1; col > cols {
|
|
col = cols
|
|
}
|
|
|
|
return line, col
|
|
}
|
|
|
|
func (b *RopeBuffer) WriteTo(w io.Writer) (int64, error) {
|
|
return (*rope.Node)(b).WriteTo(w)
|
|
}
|