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) } // RuneAtPos returns the UTF-8 rune at the byte position `pos` of the buffer. The // position must be a correct position, otherwise zero is returned. func (b *RopeBuffer) RuneAtPos(pos int) (val rune) { _, 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. val, _ = utf8.DecodeRune(data[0:]) return true }) return 0 } // EachRuneAtPos executes the function `f` at each rune after byte position `pos`. // This function should be used as opposed to performing a "per character" operation // manually, as it enables caching buffer operations and safety checks. The function // returns when the end of the buffer is met or `f` returns true. func (b *RopeBuffer) EachRuneAtPos(pos int, f func(pos int, r rune) bool) { _, 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 i, r := range string(data) { if f(pos+i, r) { return true } } return false }) } // 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] } } }