undo

package
v1.13.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 13, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package undo implements the Yjs UndoManager semantics in pure Go.

An UndoManager subscribes to a Doc's AfterTransaction lifecycle and records each qualifying write as a StackItem on its undo stack. Undo / Redo replay those StackItems in reverse / forward direction, using a redone chain on Item to resurrect deletions without violating the YATA-style "IDs are immutable" invariant.

Scope-and-origin filtering: a transaction qualifies only if at least one of its changedTypes lies under the configured scope (root branch match in this first cut; nested-type ancestry is planned) AND its Origin is in the configured trackedOrigins set. The default trackedOrigins is {nil}, which matches local edits made without an explicit origin.

Capture-timeout grouping: bursty edits within captureTimeout collapse into a single StackItem so a single Undo restores a recognisable unit of work. StopCapturing forces the next change to open a fresh StackItem regardless of timing.

Wire format: UndoManager state is local. There is no wire encoding for the undo or redo stacks. Two replicas each running their own UndoManager observe independent histories.

See docs/undo-manager-design.md for the full design rationale and the ship plan; this package is grown commit-by-commit against that plan.

Index

Constants

View Source
const DefaultCaptureTimeout = 500 * time.Millisecond

DefaultCaptureTimeout groups subsequent edits into the same StackItem when they land within this window of the previous capture. Matches the upstream yjs default of 500 milliseconds.

Variables

This section is empty.

Functions

This section is empty.

Types

type Options

type Options struct {
	// CaptureTimeout groups bursty edits into a single StackItem.
	// Zero means "use DefaultCaptureTimeout"; a negative value
	// disables grouping (every transaction becomes its own
	// StackItem).
	CaptureTimeout time.Duration

	// TrackedOrigins is the set of TransactionMut.Origin values
	// that qualify as "this UndoManager should record this edit".
	// nil means default-track-local: a single entry of the untyped
	// nil origin (matching the default a local edit produces).
	TrackedOrigins map[any]struct{}

	// IgnoreRemoteMapChanges, when true, suppresses StackItem
	// capture for transactions whose only effect on the scope is
	// a Map operation overwriting a key from a non-tracked origin.
	// Off by default; the implementation hook lands with full
	// nested-type support in a follow-up.
	IgnoreRemoteMapChanges bool
}

Options configures an UndoManager. All fields are optional; a zero value is equivalent to passing no Options at all.

type StackItem

type StackItem struct {
	Insertions *encoding.IdSet
	Deletions  *encoding.IdSet
	Meta       map[string]any
}

StackItem records one captured change for Undo or Redo.

Insertions covers item ID ranges created during the captured transaction window. Undo deletes everything in Insertions; Redo recreates it.

Deletions covers item ID ranges tombstoned during the captured window. Undo restores them (via the redoItem chain in Item.Redone); Redo re-deletes the restorations.

Meta is an arbitrary user payload — typically used by editor integrations to store and restore a selection range across an Undo / Redo round-trip. The core UndoManager logic never reads or writes Meta; only the consumer does.

type UndoManager

type UndoManager struct {
	// contains filtered or unexported fields
}

UndoManager records local mutations under a scope of root branches and replays them on Undo / Redo. See package doc for semantics.

An UndoManager is safe for concurrent reads of CanUndo / CanRedo; Undo, Redo, StopCapturing, and Clear must be serialised by the caller (mirroring the Doc write-lock contract).

func NewUndoManager

func NewUndoManager(d *doc.Doc, scope []*block.Branch, opts ...Options) *UndoManager

NewUndoManager registers an UndoManager on doc, watching the given scope of root branches. Returns the manager; call Close when done to unregister the handler.

Panics if scope is empty (a no-scope UndoManager would silently drop every transaction; almost always a programming error).

func (*UndoManager) CanRedo

func (um *UndoManager) CanRedo() bool

CanRedo reports whether there is anything on the redo stack.

func (*UndoManager) CanUndo

func (um *UndoManager) CanUndo() bool

CanUndo reports whether there is anything on the undo stack.

func (*UndoManager) Clear

func (um *UndoManager) Clear()

Clear empties both stacks. After Clear, CanUndo and CanRedo both return false. Existing keep flags on items previously held against GC are released by the GC pass when the items become reachable again (not yet wired in this skeleton; tracked in tech-debt).

func (*UndoManager) Close

func (um *UndoManager) Close()

Close unregisters this UndoManager from the doc and releases its stacks. After Close, all methods become no-ops (in particular Undo and Redo return false even if they would otherwise have anything to do).

func (*UndoManager) Redo

func (um *UndoManager) Redo() bool

Redo is the mirror of Undo, replaying the top of the redo stack. Returns true if a StackItem was applied.

func (*UndoManager) StopCapturing

func (um *UndoManager) StopCapturing()

StopCapturing prevents the next change from grouping with the current top StackItem. Useful before a logical "boundary" event such as a save, a programmatic batch, or a user-visible step end.

func (*UndoManager) Undo

func (um *UndoManager) Undo() bool

Undo pops the top of the undo stack and replays it against the doc: items inserted during the captured window are deleted, items deleted during the window are resurrected via redoItem. Returns true if a StackItem was applied, false if the stack was empty or the manager is closed.

The replay runs in its own WriteTxn with Origin set to the manager, so the resulting AfterTransaction is routed to the redo stack rather than appended to the undo stack.

Undo must not be called from inside an active transaction on the same doc, and concurrent Undo / Redo calls must be serialised by the caller.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL