Documentation
¶
Overview ¶
This library implements a gap buffer, which is a data structure to be used as the container of the text for a (simple or not so simple) text editor.
Movements to the left or right and deletion to the left and right work on Unicode runes. It supports line movements (up and down a line from the current one), it splits lines based on the newline character '\n'.
Caveats:
- Only line feed '\n' line endings are supported by this library, so Windows-style CR-LF (`\r\n`) line endings will not work with movements up and down a line. Single Unicode rune movements are going to work, but the CR '\r' character is handled as a "normal" character, as part of the string.
- This implementation is not thread save
- A gap buffer is not ideal for using multiple cursors, as that would involve multiple jumps and copying of data in the gap buffer
A gap buffer is an array with a "gap" - unused elements in the array - at the cursor position, where text is to be inserted and deleted.
The string "Hello world!" with the cursor at the end of "Hello" - "Hello| world!" - looks like this in a gap buffer array:
Hello|< gap start, the cursor position gap end >| world! ['H', 'e', 'l', 'l', 'o', 0, 0, 0, 0, 0, ' ', 'w', 'o', 'r', 'l', 'd', '!'] 0 1 2 3 4 | gap | 5 6 7 8 9 10 11
Movement in the gap buffer works by moving the start and end of the gap, same with deletion of unicode runes in both directions.
Moving the cursor two runes to the left:
Hel|< gap start, the cursor position gap end >|lo world! ['H', 'e', 'l', 0, 0, 0, 0, 0, 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!'] 0 1 2 | gap | 3 4 5 6 7 8 9 10 11
Deleting three runes to the left:
|< gap start, the cursor position gap end >|lo world! ['H', 'e', 'l', 0, 0, 0, 0, 0, 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!'] | gap | 1 2 3 4 5 6 7 8 9
Insertion happens at the cursor position by appending at the start of the gap and moving the start of the gap accordingly.
New|< gap start, the cursor position gap end >|lo world!
['N', 'e', 'w', 0, 0, 0, 0, 0, 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!'] 0 1 2 | gap | 3 4 5 6 7 8 9 10 11
Implementation Detail: The line lengths are stored in a another gap buffer, which is synchronized with the gap buffer of the text itself.
Index ¶
- type GapBuffer
- func (g *GapBuffer) Col() int
- func (g *GapBuffer) DownMv()
- func (g *GapBuffer) Insert(str string)
- func (g *GapBuffer) LeftDel()
- func (g *GapBuffer) LeftMv()
- func (g *GapBuffer) Line() int
- func (g *GapBuffer) LineCol() (line int, col int)
- func (g *GapBuffer) LineLength() int
- func (g *GapBuffer) LineRuneCol() (line int, runeCol int)
- func (g *GapBuffer) RightDel()
- func (g *GapBuffer) RightMv()
- func (g *GapBuffer) RuneCol() int
- func (g *GapBuffer) Size() int
- func (g *GapBuffer) String() string
- func (g *GapBuffer) StringLength() int
- func (g *GapBuffer) StringPair() (left string, right string)
- func (g *GapBuffer) UpMv()
Examples ¶
- GapBuffer.Col
- GapBuffer.DownMv
- GapBuffer.Insert
- GapBuffer.LeftDel
- GapBuffer.LeftMv
- GapBuffer.Line
- GapBuffer.LineCol
- GapBuffer.LineLength
- GapBuffer.LineRuneCol
- GapBuffer.RightDel
- GapBuffer.RightMv
- GapBuffer.RuneCol
- GapBuffer.Size
- GapBuffer.String
- GapBuffer.StringLength
- GapBuffer.StringPair
- GapBuffer.UpMv
- New
- NewCap
- NewStr
- NewStrCap
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type GapBuffer ¶
type GapBuffer struct {
// contains filtered or unexported fields
}
GapBuffer represents a gap buffer.
func New ¶
func New() *GapBuffer
Construct a new, empty GapBuffer with the default capacity.
See also NewCap, NewStr, NewStrCap.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new, empty gap buffer. gapBuffer := gap.New() // Print the content of the gap buffer as a single string. fmt.Println(gapBuffer.String()) }
Output:
func NewCap ¶
Construct a new GapBuffer from a capacity. The capacity is the number of bytes the gap buffer can hold without a resize.
The default size is 1024 bytes, if you know that you need less or more space, you can set the initial size to something more appropriate.
See also New, NewStr, NewStrCap.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new, empty gap buffer with a capacity of 10 bytes. gapBuffer := gap.NewCap(10) // Print the content size of the gap buffer in bytes. fmt.Println(gapBuffer.Size()) }
Output: 10
func NewStr ¶
Construct a new GapBuffer from a string. The cursor position is set to the end of the string.
See also New, NewCap, NewStrCap.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new gap buffer containing "Hello, World!". gapBuffer := gap.NewStr("Hello, World!") // Print the content of the gap buffer as a single string. fmt.Println(gapBuffer.String()) }
Output: Hello, World!
func NewStrCap ¶
Construct a new GapBuffer from a string and a capacity. The cursor position is set to the end of the string. The capacity is the number of bytes the gap buffer can hold without a resize.
The default size is 1024 bytes, if you know that you need less or more space, you can set the initial size to something more appropriate.
See also New, NewCap, NewStr, GapBuffer.Size.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new gap buffer containing "Hello, World!" with a capacity of 10 // bytes. // But "Hello, World!" is 13 bytes long, so the gap buffer's size will be // grown. gapBuffer := gap.NewStrCap("Hello, World!", 10) // Print the content size of the gap buffer in bytes. fmt.Println(gapBuffer.Size()) }
Output: 26
func (*GapBuffer) Col ¶
Return the byte column of the cursor, the number of bytes from the start of the line to the cursor.
Numbering starts from 1.
See also GapBuffer.RuneCol, GapBuffer.LineCol, GapBuffer.LineRuneCol.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new gap buffer containing the two lines "Hello, World!" and // "My name is 阿保昭則." gapBuffer := gap.NewStr("Hello, World!\nMy name is 阿保昭則.") // Get the current column position of the cursor in bytes. // Numbering starts at column 1! col := gapBuffer.Col() // Print the current column position of the cursor in bytes, which is 24. // The string "My name is 阿保昭則." is 24 bytes long, but contains 16 unicode // runes. fmt.Println(col) }
Output: 24
func (*GapBuffer) DownMv ¶
func (g *GapBuffer) DownMv()
Move the cursor down one line.
The cursor "tries" to hold the current position in the new line, like we are used to in text editors.
Before the moves:
Some |text No More text
After the first move:
Some text No| More text
After the second move:
Some text No More |text
See also GapBuffer.UpMv, GapBuffer.LeftMv, GapBuffer.RightMv, GapBuffer.LeftDel, GapBuffer.RightDel.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new gap buffer containing the two lines "Hello, World!" and // "My name is John." gapBuffer := gap.NewStr("Hello, World!\nMy name is John.") // Move the cursor up one line. gapBuffer.UpMv() // And move down again, we are were we started. gapBuffer.DownMv() // Get the part of the buffer before - to the left - and after - to the // right - of the current cursor position. l, r := gapBuffer.StringPair() // l should be "Hello, World!\nMy name is John." and r should be empty. fmt.Printf("left: '%s' |cursor| right: '%s'\n", l, r) }
Output: left: 'Hello, World! My name is John.' |cursor| right: ''
func (*GapBuffer) Insert ¶
Insert inserts the given string at the current cursor position. The string can be a single unicode scalar point or text of arbitrary size and anything in between (like a single unicode rune).
The cursor is moved to the end of the inserted text.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new gap buffer containing "Hello, World!". gapBuffer := gap.NewStr("Hello, World!") // Insert " My name is John." at the current position. gapBuffer.Insert(" My name is John.") // Print the content of the gap buffer as a single string. fmt.Println(gapBuffer.String()) }
Output: Hello, World! My name is John.
func (*GapBuffer) LeftDel ¶
func (g *GapBuffer) LeftDel()
Delete the unicode rune to the left of the cursor. Like the "backspace" key.
See also GapBuffer.RightDel, GapBuffer.LeftMv, GapBuffer.RightMv, GapBuffer.UpMv, GapBuffer.DownMv.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new gap buffer containing the two lines "Hello, World!" and // "My name is John." gapBuffer := gap.NewStr("Hello, World!\nMy name is John.") // Delete 16 runes to the left of the cursor (like backspace). gapBuffer.LeftDel() gapBuffer.LeftDel() gapBuffer.LeftDel() gapBuffer.LeftDel() gapBuffer.LeftDel() gapBuffer.LeftDel() gapBuffer.LeftDel() gapBuffer.LeftDel() gapBuffer.LeftDel() gapBuffer.LeftDel() gapBuffer.LeftDel() gapBuffer.LeftDel() gapBuffer.LeftDel() gapBuffer.LeftDel() gapBuffer.LeftDel() gapBuffer.LeftDel() // Print the content of the gap buffer as a single string. fmt.Println(gapBuffer.String()) }
Output: Hello, World!
func (*GapBuffer) LeftMv ¶
func (g *GapBuffer) LeftMv()
Move the cursor one unicode rune to the left.
See also GapBuffer.RightMv, GapBuffer.LeftDel, GapBuffer.RightDel, GapBuffer.UpMv, GapBuffer.DownMv.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new gap buffer containing the two lines "Hello, World!" and // "My name is John." gapBuffer := gap.NewStr("Hello, World!\nMy name is John.") // Move 16 runes to the left of the cursor. gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() // Get the part of the buffer before - to the left - and after - to the // right - of the current cursor position. l, r := gapBuffer.StringPair() // l should be "Hello, World!" and r should be '\nMy name is John.'. fmt.Printf("left: '%s' |cursor| right: '%s'\n", l, r) }
Output: left: 'Hello, World! ' |cursor| right: 'My name is John.'
func (*GapBuffer) Line ¶
Return the line number of the current line the cursor is in.
Numbering starts from 1.
See also GapBuffer.Col, GapBuffer.RuneCol, GapBuffer.LineRuneCol.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new gap buffer containing the two lines "Hello, World!" and // "My name is John." gapBuffer := gap.NewStr("Hello, World!\nMy name is John.") // Get the current line position of the cursor. // Numbering starts at line 1! line := gapBuffer.Line() // Print the current line position of the cursor. fmt.Println(line) }
Output: 2
func (*GapBuffer) LineCol ¶
Return the line and byte column of the cursor. Byte column means the number of bytes from the start of the line to the cursor.
Numbering starts from 1 for both the line number and the column number.
See also GapBuffer.Col, GapBuffer.RuneCol, GapBuffer.LineRuneCol, GapBuffer.Line.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new gap buffer containing the two lines "Hello, World!" and // "My name is 阿保昭則." gapBuffer := gap.NewStr("Hello, World!\nMy name is 阿保昭則.") // Get the current line and column position in bytes of the cursor. // Numbering starts at line 1 and column 1! line, col := gapBuffer.LineCol() // Print the current line position of the cursor. fmt.Printf("Line: %d column: %d", line, col) }
Output: Line: 2 column: 24
func (*GapBuffer) LineLength ¶
Return the length of the current line the cursor is in, in bytes. This returns the "whole" line length, including the part to the right of the cursor but without the newline character at the end of the line.
See also GapBuffer.Col, GapBuffer.RuneCol, which is the length to the left of the cursor.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new gap buffer containing "Hello, Wôrld!" gapBuffer := gap.NewStr("Hello, Wôrld!") // Get the length of the current line in bytes. length := gapBuffer.LineLength() // Print the length of the current line in bytes. // The string "Hello, Wôrld!" is 14 bytes long, but contains 13 unicode // runes. fmt.Println(length) }
Output: 14
func (*GapBuffer) LineRuneCol ¶
Return the line and rune column of the cursor. Rune column means the number of unicode runes from the start of the line to the cursor.
Numbering starts from 1 for both the line number and the column number.
See also GapBuffer.Line, GapBuffer.Col, GapBuffer.RuneCol, GapBuffer.LineCol.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new gap buffer containing the two lines "Hello, World!" and // "My name is 阿保昭則." gapBuffer := gap.NewStr("Hello, World!\nMy name is 阿保昭則.") // Get the current column position of the cursor in runes. // Numbering starts at column 1! line, col := gapBuffer.LineRuneCol() // Print the current line position of the cursor. fmt.Printf("Line: %d column: %d", line, col) }
Output: Line: 2 column: 16
func (*GapBuffer) RightDel ¶
func (g *GapBuffer) RightDel()
Delete the unicode rune to the right of the cursor. Like the "delete" key.
See also GapBuffer.LeftDel, GapBuffer.RightMv, GapBuffer.LeftMv, GapBuffer.UpMv, GapBuffer.DownMv.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new gap buffer containing the two lines "Hello, World!" and // "My name is John." gapBuffer := gap.NewStr("Hello, World!\nMy name is John.") // Move 5 runes to the left of the cursor. gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() // Delete 5 runes to the right, like the "delete" key does. gapBuffer.RightDel() gapBuffer.RightDel() gapBuffer.RightDel() gapBuffer.RightDel() gapBuffer.RightDel() // Get the part of the buffer before - to the left - and after - to the // right - of the current cursor position. l, r := gapBuffer.StringPair() // l should be "Hello, World!\nMy name is" and r should be empty. fmt.Printf("left: '%s' |cursor| right: '%s'\n", l, r) }
Output: left: 'Hello, World! My name is ' |cursor| right: ''
func (*GapBuffer) RightMv ¶
func (g *GapBuffer) RightMv()
Move the cursor one unicode rune to the right.
See also GapBuffer.LeftMv, GapBuffer.LeftDel, GapBuffer.RightDel, GapBuffer.UpMv, GapBuffer.DownMv.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new gap buffer containing the two lines "Hello, World!" and // "My name is John." gapBuffer := gap.NewStr("Hello, World!\nMy name is John.") // Move 16 runes to the left of the cursor. gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() // Move 8 runes to the right. gapBuffer.RightMv() gapBuffer.RightMv() gapBuffer.RightMv() gapBuffer.RightMv() gapBuffer.RightMv() gapBuffer.RightMv() gapBuffer.RightMv() gapBuffer.RightMv() // Get the part of the buffer before - to the left - and after - to the // right - of the current cursor position. l, r := gapBuffer.StringPair() // l should be "Hello, World!\nMy name " and r should be 'is John.'. fmt.Printf("left: '%s' |cursor| right: '%s'\n", l, r) }
Output: left: 'Hello, World! My name ' |cursor| right: 'is John.'
func (*GapBuffer) RuneCol ¶
Return the rune column of the cursor, the number of unicode runes from the start of the line to the cursor.
Numbering starts from 1.
See also GapBuffer.Col, GapBuffer.LineCol, GapBuffer.LineRuneCol.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new gap buffer containing the two lines "Hello, World!" and // "My name is 阿保昭則." gapBuffer := gap.NewStr("Hello, World!\nMy name is 阿保昭則.") // Get the current column position of the cursor in runes. // Numbering starts at column 1! runeCol := gapBuffer.RuneCol() // Print the current column position of the cursor in runes, which is 16. // The string "My name is 阿保昭則." is 24 bytes long, but contains 16 unicode // runes. fmt.Println(runeCol) }
Output: 16
func (*GapBuffer) Size ¶
Return the current number of bytes in the buffer, including the "empty" space in the gap.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new gap buffer containing the two lines "Hello, World!" and // "My name is 阿保昭則." gapBuffer := gap.NewStr("Hello, World!\nMy name is 阿保昭則.") // Get the current size in bytes of the gap buffer. // This includes (unused bytes in) the gap size := gapBuffer.Size() // Print the current line position of the cursor. fmt.Println(size) }
Output: 1024
func (*GapBuffer) String ¶
Return the contents of the gap buffer as a string.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new gap buffer containing "Hello, World!". gapBuffer := gap.NewStr("Hello, World!") // Print the content of the gap buffer as a single string. fmt.Println(gapBuffer.String()) }
Output: Hello, World!
func (*GapBuffer) StringLength ¶
Return the length in bytes of the contents of the gap buffer.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new gap buffer containing "Hello, World!". gapBuffer := gap.NewStr("Hello, World!") // Get the length of the content of the gap buffer in bytes. length := gapBuffer.StringLength() // Print the length of the content of the gap buffer in bytes. fmt.Println(length) }
Output: 13
func (*GapBuffer) StringPair ¶
Return the contents of the gap buffer as two strings. The part to the left of the cursor is returned in `left` and the part to the right of the cursor is returned in `right`.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new gap buffer containing the two lines "Hello, World!" and // "My name is John." gapBuffer := gap.NewStr("Hello, World!\nMy name is John.") // Move 16 runes to the left of the cursor. gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() gapBuffer.LeftMv() // Get the part of the buffer before - to the left - and after - to the // right - of the current cursor position. l, r := gapBuffer.StringPair() // l should be "Hello, World!" and r should be '\nMy name is John.'. fmt.Printf("left: '%s' |cursor| right: '%s'\n", l, r) }
Output: left: 'Hello, World! ' |cursor| right: 'My name is John.'
func (*GapBuffer) UpMv ¶
func (g *GapBuffer) UpMv()
Move the cursor up one line.
The cursor "tries" to hold the current position in the new line, like we are used to in text editors.
Before the moves:
Some text No More |text
After the first move:
Some text No| More text
After the second move:
Some |text No More text
See also GapBuffer.DownMv, GapBuffer.LeftMv, GapBuffer.RightMv, GapBuffer.LeftDel, GapBuffer.RightDel.
Example ¶
package main import ( "fmt" gap "codeberg.org/Release-Candidate/go-gap-buffer" ) func main() { // Create a new gap buffer containing the two lines "Hello, World!" and // "My name is John." gapBuffer := gap.NewStr("Hello, World!\nMy name is John.") // Move the cursor up one line. gapBuffer.UpMv() // Get the part of the buffer before - to the left - and after - to the // right - of the current cursor position. l, r := gapBuffer.StringPair() // l should be "Hello, World!" and r should be "\nMy name is John." fmt.Printf("left: '%s' |cursor| right: '%s'\n", l, r) }
Output: left: 'Hello, World!' |cursor| right: ' My name is John.'