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  }