Gim/internal/core/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

244 lines
6.3 KiB
Go

package core
type BufferOptions struct {
// tabstop expandtab
}
type BufferType int
const (
ScatchBuffer BufferType = iota
FileBuffer
DirectoryBuffer
)
type Buffer struct {
// Buffer data
Id int
Type BufferType
// File data
Filename string
Filetype string
Lines []*GapBuffer // Changed from []string to []*GapBuffer
// Flags (not used yet)
Modified bool
Loaded bool
Listed bool
ReadOnly bool
// Options BufferOptions
UndoStack *UndoStack
}
// ==================================================
// Helper methods
// ==================================================
// Buffer.Line: Get the line at an index. Returns an empty string if the index
// is out of bounds.
func (b *Buffer) Line(idx int) string {
if idx < 0 || idx >= len(b.Lines) {
return ""
}
return b.Lines[idx].String()
}
// Buffer.SetLine: Set the content of the line at an index. Does nothing if the
// index is out of bounds. This function sets the modified flag.
func (b *Buffer) SetLine(idx int, content string) {
if idx >= 0 && idx < len(b.Lines) {
// Record set line in undo stack
if b.UndoStack != nil {
b.UndoStack.RecordSetLine(idx, b.Lines[idx].String(), content)
}
b.Lines[idx].Set(content)
}
b.Modified = true
}
// Buffer.InsertLine: Insert a line with content at an index. The index is clamped
// to valid bounds (0 to len(Lines)). The new line is inserted before the line at
// the given index. This function sets the modified flag.
func (b *Buffer) InsertLine(idx int, content string) {
if idx < 0 {
idx = 0
}
if idx > len(b.Lines) {
idx = len(b.Lines)
}
// Record insert line in undo stack
if b.UndoStack != nil {
b.UndoStack.RecordInsertLine(idx, content)
}
newLine := NewGapBuffer(content)
b.Lines = append(b.Lines[:idx], append([]*GapBuffer{newLine}, b.Lines[idx:]...)...)
b.Modified = true
}
// Buffer.DeleteLine: Delete a line at an index. Does nothing if the index is out
// of bounds. This function sets the modified flag.
func (b *Buffer) DeleteLine(idx int) {
if idx >= 0 && idx < len(b.Lines) {
// Record delete line in undo stack
if b.UndoStack != nil {
b.UndoStack.RecordDeleteLine(idx, b.Lines[idx].String())
}
b.Lines = append(b.Lines[:idx], b.Lines[idx+1:]...)
}
b.Modified = true
}
// Buffer.LineCount: Get the number of lines in the buffer.
func (b *Buffer) LineCount() int {
return len(b.Lines)
}
// ==================================================
// Undo Stack
// ==================================================
func (b *Buffer) Undo(w *Window) bool {
if b.UndoStack == nil {
return false
}
block := b.UndoStack.Undo()
if block == nil {
return false
}
// Apply changes in REVERSE order
for i := len(block.Changes) - 1; i >= 0; i-- {
change := block.Changes[i]
// Temporarily disable recording while we undo
wasRecording := b.UndoStack.recording
b.UndoStack.recording = false
switch change.Type {
case SetLineChange:
// Restore old data
if change.Line >= 0 && change.Line < len(b.Lines) {
b.Lines[change.Line].Set(change.OldData)
}
case InsertLineChange:
// Remove the inserted line
if change.Line >= 0 && change.Line < len(b.Lines) {
b.Lines = append(b.Lines[:change.Line], b.Lines[change.Line+1:]...)
}
case DeleteLineChange:
// Re-insert the deleted line
if change.Line <= len(b.Lines) {
newLine := NewGapBuffer(change.OldData)
b.Lines = append(b.Lines[:change.Line], append([]*GapBuffer{newLine}, b.Lines[change.Line:]...)...)
}
}
b.UndoStack.recording = wasRecording
}
// Restore cursor position
w.SetCursorLine(block.OldCursor.Line)
w.SetCursorCol(block.OldCursor.Col)
return true
}
func (b *Buffer) Redo(w *Window) bool {
if b.UndoStack == nil {
return false
}
block := b.UndoStack.Redo()
if block == nil {
return false
}
// Apply changes in FORWARD order
for _, change := range block.Changes {
// Temporarily disable recording while we redo
wasRecording := b.UndoStack.recording
b.UndoStack.recording = false
switch change.Type {
case SetLineChange:
// Apply new data
if change.Line >= 0 && change.Line < len(b.Lines) {
b.Lines[change.Line].Set(change.NewData)
}
case InsertLineChange:
// Re-insert the line
if change.Line <= len(b.Lines) {
newLine := NewGapBuffer(change.NewData)
b.Lines = append(b.Lines[:change.Line], append([]*GapBuffer{newLine}, b.Lines[change.Line:]...)...)
}
case DeleteLineChange:
// Re-delete the line
if change.Line >= 0 && change.Line < len(b.Lines) {
b.Lines = append(b.Lines[:change.Line], b.Lines[change.Line+1:]...)
}
}
b.UndoStack.recording = wasRecording
}
// Restore cursor position
w.SetCursorLine(block.NewCursor.Line)
w.SetCursorCol(block.NewCursor.Col)
return true
}
// ==================================================
// Setters
// ==================================================
// Buffer.SetFilename: Set the filename associated with this buffer. This is
// typically the path to the file on disk that this buffer represents.
func (b *Buffer) SetFilename(filename string) {
b.Filename = filename
}
// Buffer.SetFiletype: Set the filetype of this buffer. The filetype is used
// for syntax highlighting and other language-specific features.
func (b *Buffer) SetFiletype(filetype string) {
b.Filetype = filetype
}
// Buffer.SetLines: Replace all lines in the buffer with the provided lines.
// This is useful when loading a file or resetting buffer content.
func (b *Buffer) SetLines(lines []string) {
b.Lines = make([]*GapBuffer, len(lines))
for i, line := range lines {
b.Lines[i] = NewGapBuffer(line)
}
}
// Buffer.SetModified: Set the modified flag for this buffer. A modified buffer
// has unsaved changes that differ from the file on disk.
func (b *Buffer) SetModified(modified bool) {
b.Modified = modified
}
// Buffer.SetLoaded: Set the loaded flag for this buffer. A loaded buffer has
// its content in memory, while an unloaded buffer exists only as metadata.
func (b *Buffer) SetLoaded(loaded bool) {
b.Loaded = loaded
}
// Buffer.SetListed: Set the listed flag for this buffer. A listed buffer appears
// in buffer lists (like :ls), while an unlisted buffer is hidden from normal
// buffer navigation.
func (b *Buffer) SetListed(listed bool) {
b.Listed = listed
}
// Buffer.SetType: Set the buffers type. This type is used to determine handling
// of I/O functions.
func (b *Buffer) SetType(t BufferType) {
b.Type = t
}