Gim/internal/core/gap_buffer.go
Hayden Hargreaves e362c9f118
All checks were successful
Run Test Suite / test (push) Successful in 56s
feat: gap buffer is implemented, tested
Not sure if this is perfect, but it seems to be working
2026-04-02 12:39:30 -07:00

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)
}