All checks were successful
Run Test Suite / test (push) Successful in 56s
Not sure if this is perfect, but it seems to be working
181 lines
4.8 KiB
Go
181 lines
4.8 KiB
Go
package core
|
|
|
|
// GapBuffer represents a single line of text using the gap buffer data structure.
|
|
// It maintains a gap (empty space) in the buffer where the cursor is positioned,
|
|
// making insertions and deletions at the cursor position very efficient (O(1)).
|
|
type GapBuffer struct {
|
|
buffer []rune // The underlying buffer containing text and gap
|
|
gapStart int // Index where the gap starts
|
|
gapEnd int // Index where the gap ends (exclusive)
|
|
}
|
|
|
|
// NewGapBuffer: creates a new gap buffer with the given initial content.
|
|
// The gap is positioned at the end of the content.
|
|
func NewGapBuffer(content string) *GapBuffer {
|
|
runes := []rune(content)
|
|
initialCapacity := len(runes) + 16 // Extra space for the gap
|
|
buffer := make([]rune, initialCapacity)
|
|
copy(buffer, runes)
|
|
|
|
return &GapBuffer{
|
|
buffer: buffer,
|
|
gapStart: len(runes),
|
|
gapEnd: initialCapacity,
|
|
}
|
|
}
|
|
|
|
// NewEmptyGapBuffer: Creates a new empty gap buffer with default capacity.
|
|
func NewEmptyGapBuffer() *GapBuffer {
|
|
initialCapacity := 16
|
|
return &GapBuffer{
|
|
buffer: make([]rune, initialCapacity),
|
|
gapStart: 0,
|
|
gapEnd: initialCapacity,
|
|
}
|
|
}
|
|
|
|
// GapBuffer.String: Converts the gap buffer to a string, excluding the gap.
|
|
func (gb *GapBuffer) String() string {
|
|
result := make([]rune, 0, gb.Len())
|
|
result = append(result, gb.buffer[:gb.gapStart]...)
|
|
result = append(result, gb.buffer[gb.gapEnd:]...)
|
|
return string(result)
|
|
}
|
|
|
|
// GapBuffer.Len: Returns the length of the text (excluding the gap).
|
|
func (gb *GapBuffer) Len() int {
|
|
return len(gb.buffer) - (gb.gapEnd - gb.gapStart)
|
|
}
|
|
|
|
// GapBuffer.GapSize: Returns the current size of the gap.
|
|
func (gb *GapBuffer) GapSize() int {
|
|
return gb.gapEnd - gb.gapStart
|
|
}
|
|
|
|
// GapBuffer.Insert: Inserts a string at the specified position.
|
|
// This moves the gap to the position first, then inserts the text.
|
|
func (gb *GapBuffer) Insert(pos int, text string) {
|
|
if pos < 0 || pos > gb.Len() {
|
|
return // Invalid position
|
|
}
|
|
|
|
runes := []rune(text)
|
|
if len(runes) == 0 {
|
|
return
|
|
}
|
|
|
|
// Move gap to insertion position
|
|
gb.moveGap(pos)
|
|
|
|
// Ensure gap is large enough
|
|
if gb.GapSize() < len(runes) {
|
|
gb.grow(len(runes))
|
|
}
|
|
|
|
// Insert runes at gap start
|
|
copy(gb.buffer[gb.gapStart:], runes)
|
|
gb.gapStart += len(runes)
|
|
}
|
|
|
|
// GapBuffer.Delete: Deletes count runes starting at position pos.
|
|
func (gb *GapBuffer) Delete(pos, count int) {
|
|
if pos < 0 || pos >= gb.Len() || count <= 0 {
|
|
return
|
|
}
|
|
|
|
// Clamp count to not exceed buffer length
|
|
if pos+count > gb.Len() {
|
|
count = gb.Len() - pos
|
|
}
|
|
|
|
// Move gap to deletion position
|
|
gb.moveGap(pos)
|
|
|
|
// Expand gap to absorb deleted characters
|
|
gb.gapEnd += count
|
|
}
|
|
|
|
// GapBuffer.RuneAt: Returns the rune at the specified position.
|
|
func (gb *GapBuffer) RuneAt(pos int) rune {
|
|
if pos < 0 || pos >= gb.Len() {
|
|
return 0
|
|
}
|
|
|
|
if pos < gb.gapStart {
|
|
return gb.buffer[pos]
|
|
}
|
|
return gb.buffer[pos+gb.GapSize()]
|
|
}
|
|
|
|
// GapBuffer.Substring: Returns a substring from start to end (exclusive).
|
|
func (gb *GapBuffer) Substring(start, end int) string {
|
|
if start < 0 {
|
|
start = 0
|
|
}
|
|
if end > gb.Len() {
|
|
end = gb.Len()
|
|
}
|
|
if start >= end {
|
|
return ""
|
|
}
|
|
|
|
result := make([]rune, 0, end-start)
|
|
for i := start; i < end; i++ {
|
|
result = append(result, gb.RuneAt(i))
|
|
}
|
|
return string(result)
|
|
}
|
|
|
|
// GapBuffer.moveGap: Moves the gap to the specified position.
|
|
func (gb *GapBuffer) moveGap(pos int) {
|
|
if pos < gb.gapStart {
|
|
// Move gap left: shift text from [pos, gapStart) to [gapEnd-delta, gapEnd)
|
|
delta := gb.gapStart - pos
|
|
copy(gb.buffer[gb.gapEnd-delta:gb.gapEnd], gb.buffer[pos:gb.gapStart])
|
|
gb.gapStart = pos
|
|
gb.gapEnd -= delta
|
|
} else if pos > gb.gapStart {
|
|
// Move gap right: shift text from [gapEnd, gapEnd+delta) to [gapStart, gapStart+delta)
|
|
delta := pos - gb.gapStart
|
|
copy(gb.buffer[gb.gapStart:gb.gapStart+delta], gb.buffer[gb.gapEnd:gb.gapEnd+delta])
|
|
gb.gapStart += delta
|
|
gb.gapEnd += delta
|
|
}
|
|
}
|
|
|
|
// GapBuffer.grow: Expands the buffer to accommodate at least minGapSize additional characters.
|
|
func (gb *GapBuffer) grow(minGapSize int) {
|
|
oldLen := len(gb.buffer)
|
|
newGapSize := minGapSize * 2 // Double the required size for future insertions
|
|
newLen := oldLen + newGapSize - gb.GapSize()
|
|
|
|
newBuffer := make([]rune, newLen)
|
|
|
|
// Copy text before gap
|
|
copy(newBuffer, gb.buffer[:gb.gapStart])
|
|
|
|
// Copy text after gap to new position
|
|
newGapEnd := newLen - (oldLen - gb.gapEnd)
|
|
copy(newBuffer[newGapEnd:], gb.buffer[gb.gapEnd:])
|
|
|
|
gb.buffer = newBuffer
|
|
gb.gapEnd = newGapEnd
|
|
}
|
|
|
|
// GapBuffer.Set: Replaces the entire content of the gap buffer.
|
|
func (gb *GapBuffer) Set(content string) {
|
|
runes := []rune(content)
|
|
capacity := len(runes) + 16
|
|
|
|
gb.buffer = make([]rune, capacity)
|
|
copy(gb.buffer, runes)
|
|
gb.gapStart = len(runes)
|
|
gb.gapEnd = capacity
|
|
}
|
|
|
|
// GapBuffer.Clear: Removes all content from the gap buffer.
|
|
func (gb *GapBuffer) Clear() {
|
|
gb.gapStart = 0
|
|
gb.gapEnd = len(gb.buffer)
|
|
}
|