github.com/aretext/aretext@v1.3.0/undo/log.go (about) 1 package undo 2 3 // LogEntry represents an entry in the undo log. 4 type LogEntry struct { 5 Ops []Op 6 CursorBegin uint64 7 CursorEnd uint64 8 } 9 10 // Log tracks changes to a document and generates undo/redo operations. 11 type Log struct { 12 stagedEntry LogEntry 13 committedEntries []LogEntry 14 numUndoEntries int 15 numEntriesAtLastSave int 16 } 17 18 // NewLog constructs a new, empty undo log. 19 func NewLog() *Log { 20 return &Log{ 21 stagedEntry: LogEntry{}, 22 committedEntries: nil, 23 numUndoEntries: 0, 24 numEntriesAtLastSave: 0, 25 } 26 } 27 28 // BeginEntry starts a new undo entry. 29 // This should be called before tracking any operations. 30 func (l *Log) BeginEntry(cursorPos uint64) { 31 l.stagedEntry = LogEntry{CursorBegin: cursorPos} 32 } 33 34 // CommitEntry completes an undo entry. 35 // This should be called after BeginEntry. 36 // If no operations were tracked, this does nothing. 37 func (l *Log) CommitEntry(cursorPos uint64) { 38 if len(l.stagedEntry.Ops) == 0 { 39 return 40 } 41 42 if len(l.committedEntries) > l.numUndoEntries { 43 // Invalidate future changes. 44 l.committedEntries = l.committedEntries[0:l.numUndoEntries] 45 } 46 47 if l.numEntriesAtLastSave > l.numUndoEntries { 48 // Invalidate a save point in the future. 49 l.numEntriesAtLastSave = -1 50 } 51 52 l.stagedEntry.CursorEnd = cursorPos 53 l.committedEntries = append(l.committedEntries, l.stagedEntry) 54 l.stagedEntry = LogEntry{} 55 l.numUndoEntries++ 56 } 57 58 // TrackOp tracks a change to the document. 59 // This appends a new, uncommitted change and invalidates any future changes. 60 func (l *Log) TrackOp(op Op) { 61 // Stage a new undo entry. 62 l.stagedEntry.Ops = append(l.stagedEntry.Ops, op) 63 } 64 65 // TrackSave moves the savepoint to the current entry. 66 func (l *Log) TrackSave() { 67 l.numEntriesAtLastSave = l.numUndoEntries 68 } 69 70 // UndoToLastCommitted returns operations to transform the document back to its state before the last entry. 71 // It also moves the current position backwards in the log. 72 func (l *Log) UndoToLastCommitted() (hasEntry bool, ops []Op, cursor uint64) { 73 if l.numUndoEntries == 0 { 74 return false, nil, 0 75 } 76 77 entry := l.committedEntries[l.numUndoEntries-1] 78 ops = make([]Op, 0, len(entry.Ops)) 79 for i := len(entry.Ops) - 1; i >= 0; i-- { 80 ops = append(ops, entry.Ops[i].Inverse()) 81 } 82 83 l.numUndoEntries-- 84 85 return true, ops, entry.CursorBegin 86 } 87 88 // RedoToNextCommitted returns operations to to transform the document to its state after the next entry. 89 // It also moves the current position forward in the log. 90 func (l *Log) RedoToNextCommitted() (hasEntry bool, ops []Op, cursor uint64) { 91 if l.numUndoEntries == len(l.committedEntries) { 92 return false, nil, 0 93 } 94 95 entry := l.committedEntries[l.numUndoEntries] 96 ops = make([]Op, 0, len(entry.Ops)) 97 ops = append(ops, entry.Ops...) 98 99 l.numUndoEntries++ 100 101 return true, ops, entry.CursorEnd 102 } 103 104 // HasUnsavedChanges returns whether the log has unsaved changes. 105 func (l *Log) HasUnsavedChanges() bool { 106 return l.numUndoEntries != l.numEntriesAtLastSave 107 }