181 lines
3.2 KiB
Go
181 lines
3.2 KiB
Go
package core
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
)
|
|
|
|
type ChangeType string
|
|
|
|
const (
|
|
SetLineChange ChangeType = "SetLine"
|
|
InsertLineChange ChangeType = "InsertLine"
|
|
DeleteLineChange ChangeType = "DeleteLine"
|
|
)
|
|
|
|
type Change struct {
|
|
Type ChangeType
|
|
Line int
|
|
OldData string
|
|
NewData string
|
|
}
|
|
|
|
type ChangeBlock struct {
|
|
Changes []Change
|
|
OldCursor Position // Before OP
|
|
NewCursor Position // After OP
|
|
}
|
|
|
|
type UndoStack struct {
|
|
undoStack []ChangeBlock
|
|
redoStack []ChangeBlock
|
|
current []Change
|
|
recording bool
|
|
oldCursor Position
|
|
}
|
|
|
|
func NewUndoStack() *UndoStack {
|
|
return &UndoStack{
|
|
undoStack: []ChangeBlock{},
|
|
redoStack: []ChangeBlock{},
|
|
current: []Change{},
|
|
recording: false,
|
|
oldCursor: Position{},
|
|
}
|
|
}
|
|
|
|
func (u *UndoStack) BeginBlock(cursor Position) {
|
|
u.current = []Change{}
|
|
u.recording = true
|
|
u.oldCursor = cursor
|
|
}
|
|
|
|
func (u *UndoStack) EndBlock(cursor Position) {
|
|
// If not recording or nothing changed, we can exit safely
|
|
if !u.recording || len(u.current) == 0 {
|
|
return
|
|
}
|
|
|
|
block := ChangeBlock{
|
|
Changes: u.current,
|
|
OldCursor: u.oldCursor,
|
|
NewCursor: cursor,
|
|
}
|
|
|
|
u.undoStack = append(u.undoStack, block)
|
|
u.redoStack = []ChangeBlock{} // Reset old changes, can no longer redo
|
|
|
|
u.recording = false
|
|
u.current = []Change{}
|
|
}
|
|
|
|
func (u *UndoStack) RecordSetLine(line int, oldData, newData string) {
|
|
if !u.recording {
|
|
return
|
|
}
|
|
change := Change{
|
|
Type: SetLineChange,
|
|
Line: line,
|
|
OldData: oldData,
|
|
NewData: newData,
|
|
}
|
|
u.current = append(u.current, change)
|
|
}
|
|
|
|
func (u *UndoStack) RecordInsertLine(line int, newData string) {
|
|
if !u.recording {
|
|
return
|
|
}
|
|
change := Change{
|
|
Type: InsertLineChange,
|
|
Line: line,
|
|
NewData: newData,
|
|
}
|
|
u.current = append(u.current, change)
|
|
}
|
|
|
|
func (u *UndoStack) RecordDeleteLine(line int, oldData string) {
|
|
if !u.recording {
|
|
return
|
|
}
|
|
change := Change{
|
|
Type: DeleteLineChange,
|
|
Line: line,
|
|
OldData: oldData,
|
|
}
|
|
u.current = append(u.current, change)
|
|
}
|
|
|
|
func (u *UndoStack) Undo() *ChangeBlock {
|
|
if len(u.undoStack) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Pop from undo stack
|
|
size := len(u.undoStack)
|
|
block := u.undoStack[size-1]
|
|
u.undoStack = u.undoStack[:size-1]
|
|
|
|
// Push to redo stack
|
|
u.redoStack = append(u.redoStack, block)
|
|
|
|
return &block
|
|
}
|
|
|
|
func (u *UndoStack) Redo() *ChangeBlock {
|
|
if len(u.redoStack) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Pop from redo stack
|
|
size := len(u.redoStack)
|
|
block := u.redoStack[size-1]
|
|
u.redoStack = u.redoStack[:size-1]
|
|
|
|
// Push to undo stack
|
|
u.undoStack = append(u.undoStack, block)
|
|
|
|
return &block
|
|
}
|
|
|
|
func (u *UndoStack) CanUndo() bool {
|
|
return len(u.undoStack) > 0
|
|
}
|
|
|
|
func (u *UndoStack) CanRedo() bool {
|
|
return len(u.redoStack) > 0
|
|
}
|
|
|
|
func (u *UndoStack) Recording() bool {
|
|
return u.recording
|
|
}
|
|
|
|
func (u *UndoStack) List() []string {
|
|
var lines []string
|
|
|
|
stack := slices.Clone(u.undoStack)
|
|
slices.Reverse(stack)
|
|
|
|
for _, b := range stack {
|
|
lines = append(lines, fmt.Sprintf(
|
|
"block (%d:%d) -> (%d:%d)",
|
|
b.OldCursor.Line,
|
|
b.OldCursor.Col,
|
|
b.NewCursor.Line,
|
|
b.NewCursor.Col,
|
|
))
|
|
|
|
for _, c := range b.Changes {
|
|
lines = append(lines, fmt.Sprintf(
|
|
"\t%q #%d (%s) -> (%s)",
|
|
c.Type,
|
|
c.Line,
|
|
c.OldData,
|
|
c.NewData,
|
|
))
|
|
}
|
|
}
|
|
|
|
return lines
|
|
}
|