github.com/xyproto/orbiton/v2@v2.65.12-0.20240516144430-e10a419274ec/undo.go (about) 1 package main 2 3 import ( 4 "errors" 5 "fmt" 6 "sync" 7 "unsafe" 8 ) 9 10 // Undo is a struct that can store several states of the editor and position 11 type Undo struct { 12 mut *sync.RWMutex 13 editorCopies []Editor 14 editorLineCopies []map[int][]rune 15 editorPositionCopies []Position 16 index int 17 size int 18 maxMemoryUse uint64 // can be <= 0 to not check for memory use 19 ignoreSnapshots bool // used when playing back macros 20 } 21 22 const ( 23 // number of undo actions possible to store in the circular buffer 24 defaultUndoCount = 1024 25 26 // maximum amount of memory the undo buffers can use before re-using buffers, 0 to disable 27 defaultUndoMemory = 0 // 32 * 1024 * 1024 28 ) 29 30 var ( 31 // Circular undo buffer with room for N actions, change false to true to check for too limit memory use 32 undo = NewUndo(defaultUndoCount, defaultUndoMemory) 33 34 // Save the contents of one switch. 35 // Used when switching between a .c or .cpp file to the corresponding .h file. 36 switchBuffer = NewUndo(1, defaultUndoMemory) 37 38 // Save a copy of the undo stack when switching between files 39 switchUndoBackup = NewUndo(defaultUndoCount, defaultUndoMemory) 40 ) 41 42 // NewUndo takes arguments that are only for initializing the undo buffers. 43 // The *Position and *vt100.Canvas is used only as a default values for the elements in the undo buffers. 44 func NewUndo(size int, maxMemoryUse uint64) *Undo { 45 return &Undo{&sync.RWMutex{}, make([]Editor, size), make([]map[int][]rune, size), make([]Position, size), 0, size, maxMemoryUse, false} 46 } 47 48 // IgnoreSnapshots is used when playing back macros, to snapshot the macro playback as a whole instead 49 func (u *Undo) IgnoreSnapshots(b bool) { 50 u.ignoreSnapshots = b 51 } 52 53 func lineMapMemoryFootprint(m map[int][]rune) uint64 { 54 var sum uint64 55 for _, v := range m { 56 sum += uint64(cap(v)) 57 } 58 return sum 59 } 60 61 // MemoryFootprint returns how much memory one Undo struct is using 62 // TODO: Check if the size of the slices that contains structs are correct 63 func (u *Undo) MemoryFootprint() uint64 { 64 var sum uint64 65 for _, m := range u.editorLineCopies { 66 sum += lineMapMemoryFootprint(m) 67 } 68 sum += uint64(unsafe.Sizeof(u.index)) 69 sum += uint64(unsafe.Sizeof(u.size)) 70 sum += uint64(unsafe.Sizeof(u.editorCopies)) 71 sum += uint64(unsafe.Sizeof(u.editorPositionCopies)) 72 sum += uint64(unsafe.Sizeof(u.mut)) 73 sum += uint64(unsafe.Sizeof(u.maxMemoryUse)) 74 return sum 75 } 76 77 // Snapshot will store a snapshot, and move to the next position in the circular buffer 78 func (u *Undo) Snapshot(e *Editor) { 79 if u.ignoreSnapshots { 80 return 81 } 82 83 u.mut.Lock() 84 defer u.mut.Unlock() 85 86 u.editorCopies[u.index] = *e 87 u.editorLineCopies[u.index] = e.CopyLines() 88 u.editorPositionCopies[u.index] = e.pos 89 90 // Go forward 1 step in the circular buffer 91 u.index++ 92 // Circular buffer wrap 93 if u.index >= u.size { 94 u.index = 0 95 } 96 97 // If the undo buffer uses too much memory, reduce the size to 10 98 if u.maxMemoryUse > 0 && u.MemoryFootprint() > u.maxMemoryUse { 99 newSize := 10 100 101 smallest := newSize 102 if u.size < smallest { 103 smallest = u.size 104 } 105 106 newUndo := NewUndo(newSize, u.maxMemoryUse) 107 newUndo.index = u.index 108 if newUndo.index >= newUndo.size { 109 newUndo.index = 0 110 } 111 newUndo.mut = u.mut 112 113 u.mut.Lock() 114 defer u.mut.Unlock() 115 116 // Copy over the contents to the new undo struct 117 offset := u.index 118 for i := 0; i < smallest; i++ { 119 copyFromPos := i + offset 120 if copyFromPos > u.size { 121 copyFromPos -= u.size 122 } 123 copyToPos := i 124 fmt.Println(copyFromPos, copyToPos) 125 126 newUndo.editorCopies[copyToPos] = u.editorCopies[copyFromPos] 127 newUndo.editorLineCopies[copyToPos] = u.editorLineCopies[copyFromPos] 128 newUndo.editorPositionCopies[copyToPos] = u.editorPositionCopies[copyFromPos] 129 } 130 131 // Replace the undo struct 132 *u = *newUndo 133 134 // Adjust the index after the size has been changed 135 if u.index >= u.size { 136 u.index = 0 137 } 138 } 139 } 140 141 // Restore will restore a previous snapshot, and move to the previous position in the circular buffer 142 func (u *Undo) Restore(e *Editor) error { 143 u.mut.Lock() 144 defer u.mut.Unlock() 145 146 // Go back 1 step in the circular buffer 147 u.index-- 148 // Circular buffer wrap 149 if u.index < 0 { 150 u.index = u.size - 1 151 } 152 153 // Restore the state from this index, if there is something there 154 if lines := u.editorLineCopies[u.index]; len(lines) > 0 { 155 156 *e = u.editorCopies[u.index] 157 e.lines = lines 158 e.pos = u.editorPositionCopies[u.index] 159 160 return nil 161 } 162 return errors.New("no undo state at this index") 163 } 164 165 // Len will return the current number of stored undo snapshots. 166 // This is the same as the index int that points to the next free slot. 167 func (u *Undo) Len() int { 168 return u.index 169 }