# `Yex.UndoManager`
[🔗](https://github.com/satoren/y_ex/blob/main/lib/undo_manager.ex#L19)

Represents a Y.UndoManager instance.

## Tracked origins

A transaction's origin is the term passed to `Yex.Doc.transaction/3`, or `nil`
when none was given. The manager only captures changes from origins it tracks:

* With no tracked origins (the default), it captures only transactions whose
  origin is `nil`.
* Once at least one origin is tracked, it captures only transactions with a
  tracked origin, and transactions with a `nil` origin are no longer captured.
  Removing every tracked origin with `exclude_origin/2` goes back to capturing
  only `nil`.

This matches Yjs, where `trackedOrigins` defaults to `new Set([null])`, except that
`nil` cannot be tracked alongside other origins. The manager's own undo and redo
transactions are always tracked, so undone changes can be redone.

To track origins from the moment the manager is created, pass them as
`tracked_origins` in `Yex.UndoManager.Options` instead of calling
`include_origin/2` afterwards; otherwise a `nil`-origin change made in between
is captured.

## Undo and redo origins

Undo and redo commit a transaction whose origin is the manager itself. Update
subscribers (`Yex.Doc.monitor_update/2`) and observers (`Yex.SharedType.observe/2`)
receive that `%Yex.UndoManager{}` as the origin, so it can be matched against the
manager in use:

    receive do
      {:update_v1, _update, ^undo_manager, _doc} -> :undo_or_redo
    end

# `t`

```elixir
@type t() :: %Yex.UndoManager{doc: Yex.Doc.t(), reference: reference()}
```

# `can_redo?`

```elixir
@spec can_redo?(t()) :: boolean()
```

Returns whether the redo stack holds at least one item.

# `can_undo?`

```elixir
@spec can_undo?(t()) :: boolean()
```

Returns whether the undo stack holds at least one item.

# `clear`

```elixir
@spec clear(t()) :: :ok | {:error, term()}
```

Clears all StackItems stored within current UndoManager, effectively resetting its state.

## Example:
    text = Doc.get_text(doc, "text")
    undo_manager = UndoManager.new(doc, text)

    Text.insert(text, 0, "Hello")
    Text.insert(text, 5, " World")
    UndoManager.clear(undo_manager)
    # All undo/redo history is now cleared

Raises `Yex.TransactionAcqError` instead of blocking when a write
transaction on the document is open, for example inside
`Yex.Doc.transaction/3`.

# `exclude_origin`

Excludes an origin from being tracked by the UndoManager.

This removes an origin added with `include_origin/2` or `tracked_origins`. Origins
that were never included are already not captured, so excluding one has no effect.
Excluding the last tracked origin makes the manager capture `nil`-origin
transactions again.

# `expand_scope`

Expands the scope of the UndoManager to include additional shared types.
The scope can be a Text, Array, or Map type.

Raises `Yex.TransactionAcqError` when a transaction on the document is open.

# `include_origin`

Includes an origin to be tracked by the UndoManager.

After the first included origin, transactions with a `nil` origin are no longer
captured. See "Tracked origins" in the module documentation.

# `is_valid_scope`
*macro* 

# `new`

```elixir
@spec new(
  Yex.Doc.t(),
  struct()
) :: {:ok, t()} | {:error, term()}
```

Creates a new UndoManager for the given document and scope with default options.
The scope can be a Text, Array, Map, XmlText, XmlElement, or XmlFragment type.

## Errors
- Returns `{:error, "Invalid scope: expected a struct"}` if scope is not a struct
- Returns `{:error, "Failed to get branch reference"}` if there's an error accessing the scope

# `new_with_options`

```elixir
@spec new_with_options(Yex.Doc.t(), struct(), Yex.UndoManager.Options.t()) ::
  {:ok, t()} | {:error, term()}
```

Creates a new UndoManager with the given options.

## Options

See `Yex.UndoManager.Options` for available options.

## Errors
- Returns `{:error, "NIF error: <message>"}` if underlying NIF returns an error
- Raises `Yex.TransactionAcqError` when a transaction on the document is open,
  for example inside `Yex.Doc.transaction/3`

# `redo`

```elixir
@spec redo(t()) :: :ok | {:error, term()}
```

Redoes the last undone change.

Returns `:ok` whether or not anything was redone; use `redo_with_result/1`
to learn which. Raises `Yex.TransactionAcqError` when a transaction
on the document is open.

# `redo_with_result`

```elixir
@spec redo_with_result(t()) :: {:ok, boolean()} | {:error, term()}
```

Redoes the last undone change and reports whether the document changed.

Returns `{:ok, false}` when the redo stack is empty. Raises
`Yex.TransactionAcqError` when a transaction on the document is open,
whether or not the stack is empty.

# `stop_capturing`

Stops capturing changes for the current stack item.
This ensures that the next change will create a new stack item instead of
being merged with the previous one, even if it occurs within the normal timeout window.

## Example:
    text = Doc.get_text(doc, "text")
    undo_manager = UndoManager.new(doc, text)

    Text.insert(text, 0, "a")
    UndoManager.stop_capturing(undo_manager)
    Text.insert(text, 1, "b")
    UndoManager.undo(undo_manager)
    # Text.to_string(text) will be "a" (only "b" was removed)

# `undo`

```elixir
@spec undo(t()) :: :ok | {:error, term()}
```

Undoes the last tracked change.

Returns `:ok` whether or not anything was undone; use `undo_with_result/1`
to learn which. Raises `Yex.TransactionAcqError` when a transaction
on the document is open (for example when called inside
`Yex.Doc.transaction/3`), because undo commits a transaction of its own.

# `undo_with_result`

```elixir
@spec undo_with_result(t()) :: {:ok, boolean()} | {:error, term()}
```

Undoes the last tracked change and reports whether the document changed.

Returns `{:ok, false}` when the undo stack is empty. Raises
`Yex.TransactionAcqError` under the same condition as `undo/1`,
whether or not the stack is empty.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
