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