Minga.Buffer.State (Minga v0.1.0)

Copy Markdown View Source

Internal state for the Buffer GenServer.

Holds the gap buffer, file path, dirty flag, and undo/redo stacks.

Undo coalescing

Rapid edits (e.g., AI-driven or fast typing) that arrive within @undo_coalesce_ms of each other are grouped into a single undo entry. Instead of pushing a new snapshot for every mutation, the top of the undo stack is kept and only the document is replaced. This bounds memory usage under burst editing while preserving correct undo for human-speed edits.

Summary

Types

Buffer type controlling behavior

The source of an edit for undo/redo attribution.

An undo/redo stack entry: the document snapshot, version, and edit source.

t()

Functions

Resets the coalescing timer so the next push_undo/2 will always create a new undo entry. Call this at undo boundaries (e.g., mode transitions like leaving insert mode).

Marks the buffer as having unsaved changes (bumps version).

Records the current version as the save point for dirty tracking.

Pushes the current document onto the undo stack and replaces it with new_buf. Clears the redo stack. The undo stack is capped at 1000 entries.

Pushes an undo entry unconditionally, bypassing time-based coalescing.

Sets the dirty flag based on whether the given version matches the saved version. Used by undo/redo which restore a version from the stack rather than incrementing.

The coalescing window in milliseconds. Edits arriving within this window of the previous undo push are merged into the same undo entry.

Types

buffer_type()

@type buffer_type() :: :file | :nofile | :nowrite | :prompt | :terminal

Buffer type controlling behavior:

  • :file — (default) normal file buffer, supports save/dirty/undo
  • :nofile — no file association, implicitly read-only, no save
  • :nowrite — has a file path for display but cannot save
  • :prompt — like :nofile but the last line is editable (for agent input, command input)
  • :terminal — backed by an external process writing into the buffer

edit_source()

@type edit_source() :: :user | :agent | :lsp | :recovery

The source of an edit for undo/redo attribution.

stack_entry()

@type stack_entry() ::
  {version :: non_neg_integer(), document :: Minga.Buffer.Document.t(),
   source :: edit_source()}

An undo/redo stack entry: the document snapshot, version, and edit source.

t()

@type t() :: %Minga.Buffer.State{
  buffer_type: buffer_type(),
  consumer_cursors: %{required(atom()) => non_neg_integer()},
  decorations: Minga.Core.Decorations.t(),
  dirty: boolean(),
  document: Minga.Buffer.Document.t(),
  edit_log: [{non_neg_integer(), Minga.Buffer.EditDelta.t()}],
  edit_seq: non_neg_integer(),
  explicit_options: MapSet.t(atom()),
  face_overrides: %{required(String.t()) => keyword()},
  file_path: String.t() | nil,
  file_size: non_neg_integer() | nil,
  filetype: atom(),
  last_undo_at: integer(),
  mtime: integer() | nil,
  name: String.t() | nil,
  options: %{required(atom()) => term()},
  pending_edits: [Minga.Buffer.EditDelta.t()],
  persistent: boolean(),
  read_only: boolean(),
  redo_stack: [stack_entry()],
  saved_version: non_neg_integer(),
  swap_dir: String.t() | nil,
  swap_timer: reference() | nil,
  undo_stack: [stack_entry()],
  unlisted: boolean(),
  version: non_neg_integer()
}

Functions

break_undo_coalescing(state)

@spec break_undo_coalescing(t()) :: t()

Resets the coalescing timer so the next push_undo/2 will always create a new undo entry. Call this at undo boundaries (e.g., mode transitions like leaving insert mode).

mark_dirty(state)

@spec mark_dirty(t()) :: t()

Marks the buffer as having unsaved changes (bumps version).

mark_saved(state)

@spec mark_saved(t()) :: t()

Records the current version as the save point for dirty tracking.

push_undo(state, new_buf, source)

@spec push_undo(t(), Minga.Buffer.Document.t(), edit_source()) :: t()

Pushes the current document onto the undo stack and replaces it with new_buf. Clears the redo stack. The undo stack is capped at 1000 entries.

If the previous undo push happened within 300ms, the new document replaces the current one without pushing another snapshot onto the stack. This coalesces rapid edits (AI, fast typing) into a single undo step while preserving distinct undo entries for edits separated by a pause.

push_undo_force(state, new_buf, source)

@spec push_undo_force(t(), Minga.Buffer.Document.t(), edit_source()) :: t()

Pushes an undo entry unconditionally, bypassing time-based coalescing.

Use this for explicit actions (like :replace_content, agent batch edits) where each invocation should always be a separate undo step regardless of timing.

sync_dirty(state)

@spec sync_dirty(t()) :: t()

Sets the dirty flag based on whether the given version matches the saved version. Used by undo/redo which restore a version from the stack rather than incrementing.

undo_coalesce_ms()

@spec undo_coalesce_ms() :: pos_integer()

The coalescing window in milliseconds. Edits arriving within this window of the previous undo push are merged into the same undo entry.