Added RopeBuffer Buffer implementation
This commit is contained in:
parent
817b2c66c4
commit
bd6f1cf79e
1
go.mod
1
go.mod
@ -6,4 +6,5 @@ require (
|
|||||||
github.com/gdamore/tcell/v2 v2.2.0
|
github.com/gdamore/tcell/v2 v2.2.0
|
||||||
github.com/mattn/go-runewidth v0.0.10 // indirect
|
github.com/mattn/go-runewidth v0.0.10 // indirect
|
||||||
github.com/zyedidia/clipboard v1.0.3 // indirect
|
github.com/zyedidia/clipboard v1.0.3 // indirect
|
||||||
|
github.com/zyedidia/rope v0.0.0-20210117014038-fa241571635a // indirect
|
||||||
)
|
)
|
||||||
|
2
go.sum
2
go.sum
@ -10,6 +10,8 @@ github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
|
|||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/zyedidia/clipboard v1.0.3 h1:F/nCDVYMdbDWTmY8s8cJl0tnwX32q96IF09JHM14bUI=
|
github.com/zyedidia/clipboard v1.0.3 h1:F/nCDVYMdbDWTmY8s8cJl0tnwX32q96IF09JHM14bUI=
|
||||||
github.com/zyedidia/clipboard v1.0.3/go.mod h1:zykFnZUXX0ErxqvYLUFEq7QDJKId8rmh2FgD0/Y8cjA=
|
github.com/zyedidia/clipboard v1.0.3/go.mod h1:zykFnZUXX0ErxqvYLUFEq7QDJKId8rmh2FgD0/Y8cjA=
|
||||||
|
github.com/zyedidia/rope v0.0.0-20210117014038-fa241571635a h1:GqVrOhVdC792A34K1Fw1HN6RC0wr5yAJVdtt8xaxXBA=
|
||||||
|
github.com/zyedidia/rope v0.0.0-20210117014038-fa241571635a/go.mod h1:IKo1js3O2gdESAUH9F3B33wlrRBnpJMJR/gXyY0a3ho=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
|
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
|
||||||
|
56
ui/buffer/buffer.go
Normal file
56
ui/buffer/buffer.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package buffer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Buffer is wrapper around any buffer data structure like ropes or a gap buffer
|
||||||
|
// that can be used for text editors. One way this interface helps is by making
|
||||||
|
// all API function parameters line and column indexes, so it is simple and easy
|
||||||
|
// to index and use like a text editor. All lines and columns start at zero, and
|
||||||
|
// all "end" ranges are inclusive.
|
||||||
|
//
|
||||||
|
// Any bounds out of range are panics! If you are unsure your position or range
|
||||||
|
// may be out of bounds, use ClampLineCol() or compare with Lines() or ColsInLine().
|
||||||
|
type Buffer interface {
|
||||||
|
// 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.
|
||||||
|
Line(line int) []byte
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
Slice(startLine, startCol, endLine, endCol int) []byte
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
Bytes() []byte
|
||||||
|
|
||||||
|
// Insert copies a byte slice (inserting it) into the position at line, col.
|
||||||
|
Insert(line, col int, value []byte)
|
||||||
|
|
||||||
|
// Remove deletes any characters between startLine, startCol, and endLine,
|
||||||
|
// endCol, inclusive bounds.
|
||||||
|
Remove(startLine, startCol, endLine, endCol int)
|
||||||
|
|
||||||
|
// Len returns the number of bytes in the buffer.
|
||||||
|
Len() int
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
Lines() int
|
||||||
|
|
||||||
|
// ColsInLine returns the number of columns in the given line. That is, the
|
||||||
|
// number of Utf-8 codepoints (or runes) in line, not bytes.
|
||||||
|
RunesInLine(line int) int
|
||||||
|
|
||||||
|
// ClampLineCol is a utility function to clamp any provided line and col to
|
||||||
|
// only possible values within the buffer. It first clamps the line, then clamps
|
||||||
|
// the column within that line.
|
||||||
|
ClampLineCol(line, col int) (int, int)
|
||||||
|
|
||||||
|
WriteTo(w io.Writer) (int64, error)
|
||||||
|
}
|
213
ui/buffer/rope.go
Normal file
213
ui/buffer/rope.go
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
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)
|
||||||
|
}
|
47
ui/buffer/rope_test.go
Normal file
47
ui/buffer/rope_test.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package buffer
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestRopeInserting(t *testing.T) {
|
||||||
|
var buf Buffer = NewRopeBuffer([]byte("some"))
|
||||||
|
buf.Insert(0, 4, []byte(" text")) // Insert " text" after "some"
|
||||||
|
buf.Insert(0, 0, []byte("with\n\t"))
|
||||||
|
// "with\n\tsome text"
|
||||||
|
|
||||||
|
buf.Remove(0, 4, 1, 5) // Delete from line 0, col 4, to line 1, col 6 "\n\tsome "
|
||||||
|
|
||||||
|
if str := string(buf.Bytes()); str != "withtext" {
|
||||||
|
t.Errorf("string does not match \"withtext\", got %#v", str)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRopeBounds(t *testing.T) {
|
||||||
|
var buf Buffer = NewRopeBuffer([]byte("this\nis (は)\n\tsome\ntext\n"))
|
||||||
|
//this
|
||||||
|
//is (は)
|
||||||
|
// some
|
||||||
|
//text
|
||||||
|
//
|
||||||
|
|
||||||
|
if buf.Lines() != 5 {
|
||||||
|
t.Errorf("Expected buf.Lines() == 5")
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len := buf.RunesInLine(1); len != 6 { // "is" in English and in japanese
|
||||||
|
t.Errorf("Expected 6 runes in line 2, found %v", len)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
line, col := buf.ClampLineCol(15, 5) // Should become last line, first column
|
||||||
|
if line != 4 && col != 0 {
|
||||||
|
t.Errorf("Expected to clamp line col to 4,0 got %v,%v", line, col)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
if line := string(buf.Line(2)); line != "\tsome\n" {
|
||||||
|
t.Errorf("Expected line 3 to equal \"\\tsome\", got %#v", line)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,9 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/fivemoreminix/diesel/ui/buffer"
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,6 +26,7 @@ type Region struct {
|
|||||||
// tools, is autocomplete ready, and contains the various information about
|
// tools, is autocomplete ready, and contains the various information about
|
||||||
// content being edited.
|
// content being edited.
|
||||||
type TextEdit struct {
|
type TextEdit struct {
|
||||||
|
Buffer buffer.Buffer
|
||||||
LineNumbers bool // Whether to render line numbers (and therefore the column)
|
LineNumbers bool // Whether to render line numbers (and therefore the column)
|
||||||
Dirty bool // Whether the buffer has been edited
|
Dirty bool // Whether the buffer has been edited
|
||||||
UseHardTabs bool // When true, tabs are '\t'
|
UseHardTabs bool // When true, tabs are '\t'
|
||||||
@ -31,7 +34,6 @@ type TextEdit struct {
|
|||||||
IsCRLF bool // Whether the file's line endings are CRLF (\r\n) or LF (\n)
|
IsCRLF bool // Whether the file's line endings are CRLF (\r\n) or LF (\n)
|
||||||
FilePath string // Will be empty if the file has not been saved yet
|
FilePath string // Will be empty if the file has not been saved yet
|
||||||
|
|
||||||
buffer []string // TODO: replace line-based buffer with gap buffer
|
|
||||||
screen *tcell.Screen // We keep our own reference to the screen for cursor purposes.
|
screen *tcell.Screen // We keep our own reference to the screen for cursor purposes.
|
||||||
x, y int
|
x, y int
|
||||||
width, height int
|
width, height int
|
||||||
@ -50,11 +52,11 @@ type TextEdit struct {
|
|||||||
// it can be assumed that the TextEdit has no file association, or it is unsaved.
|
// it can be assumed that the TextEdit has no file association, or it is unsaved.
|
||||||
func NewTextEdit(screen *tcell.Screen, filePath, contents string, theme *Theme) *TextEdit {
|
func NewTextEdit(screen *tcell.Screen, filePath, contents string, theme *Theme) *TextEdit {
|
||||||
te := &TextEdit{
|
te := &TextEdit{
|
||||||
|
Buffer: nil,
|
||||||
LineNumbers: true,
|
LineNumbers: true,
|
||||||
UseHardTabs: true,
|
UseHardTabs: true,
|
||||||
TabSize: 4,
|
TabSize: 4,
|
||||||
FilePath: filePath,
|
FilePath: filePath,
|
||||||
buffer: nil,
|
|
||||||
screen: screen,
|
screen: screen,
|
||||||
Theme: theme,
|
Theme: theme,
|
||||||
}
|
}
|
||||||
@ -65,8 +67,9 @@ func NewTextEdit(screen *tcell.Screen, filePath, contents string, theme *Theme)
|
|||||||
// SetContents applies the string to the internal buffer of the TextEdit component.
|
// SetContents applies the string to the internal buffer of the TextEdit component.
|
||||||
// The string is determined to be either CRLF or LF based on line-endings.
|
// The string is determined to be either CRLF or LF based on line-endings.
|
||||||
func (t *TextEdit) SetContents(contents string) {
|
func (t *TextEdit) SetContents(contents string) {
|
||||||
|
var i int
|
||||||
loop:
|
loop:
|
||||||
for _, r := range contents {
|
for i < len(contents) {
|
||||||
switch r {
|
switch r {
|
||||||
case '\n':
|
case '\n':
|
||||||
t.IsCRLF = false
|
t.IsCRLF = false
|
||||||
@ -76,14 +79,10 @@ loop:
|
|||||||
t.IsCRLF = true
|
t.IsCRLF = true
|
||||||
break loop
|
break loop
|
||||||
}
|
}
|
||||||
|
i += utf8.DecodeRune(contents[i:])
|
||||||
}
|
}
|
||||||
|
|
||||||
delimiter := "\n"
|
t.Buffer = buffer.NewRopeBuffer(contents)
|
||||||
if t.IsCRLF {
|
|
||||||
delimiter = "\r\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
t.buffer = strings.Split(contents, delimiter) // Split contents into lines
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLineDelimiter returns "\r\n" for a CRLF buffer, or "\n" for an LF buffer.
|
// GetLineDelimiter returns "\r\n" for a CRLF buffer, or "\n" for an LF buffer.
|
||||||
@ -232,6 +231,8 @@ func (t *TextEdit) Insert(contents string) {
|
|||||||
// column of that new line. Text before the cursor on the current line remains on that line,
|
// column of that new line. Text before the cursor on the current line remains on that line,
|
||||||
// text at or after the cursor on the current line is moved to the new line.
|
// text at or after the cursor on the current line is moved to the new line.
|
||||||
func (t *TextEdit) insertNewLine() {
|
func (t *TextEdit) insertNewLine() {
|
||||||
|
t.Dirty = true
|
||||||
|
|
||||||
lineRunes := []rune(t.buffer[t.cury]) // A slice of runes of the old line
|
lineRunes := []rune(t.buffer[t.cury]) // A slice of runes of the old line
|
||||||
movedRunes := lineRunes[t.curx:] // A slice of the old line containing runes to be moved
|
movedRunes := lineRunes[t.curx:] // A slice of the old line containing runes to be moved
|
||||||
newLineRunes := make([]rune, len(movedRunes))
|
newLineRunes := make([]rune, len(movedRunes))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user