# `Minga.Buffer`
[🔗](https://github.com/jsmestad/minga/blob/main/lib/minga/buffer.ex#L1)

Domain facade for buffer operations.

This is the only valid entry point for code outside the buffer domain.
All buffer operations go through this module: reading content, moving
cursors, editing text, persisting files, and managing decorations.

Internally delegates to `Minga.Buffer.Server` (GenServer) and
`Minga.Buffer.Document` (pure data structure). External callers
should never reference those modules directly.

# `direction`

```elixir
@type direction() :: :left | :right | :up | :down
```

# `document`

```elixir
@type document() :: Minga.Buffer.Document.t()
```

# `position`

```elixir
@type position() :: {line :: non_neg_integer(), col :: non_neg_integer()}
```

# `server`

```elixir
@type server() :: GenServer.server()
```

# `add_block_decoration`

```elixir
@spec add_block_decoration(server(), non_neg_integer(), keyword()) :: reference()
```

Add a block decoration (multi-line annotation) anchored to a line.

# `add_highlight`

```elixir
@spec add_highlight(
  server(),
  Minga.Core.Decorations.highlight_range_pos(),
  Minga.Core.Decorations.highlight_range_pos(),
  keyword()
) :: reference()
```

Add a highlight range.

# `add_virtual_text`

```elixir
@spec add_virtual_text(
  server(),
  Minga.Core.Decorations.highlight_range_pos(),
  keyword()
) :: reference()
```

Add virtual text anchored to a line.

# `append`

```elixir
@spec append(server(), String.t()) :: :ok
```

Append text to the end of the buffer.

# `apply_edit`

```elixir
@spec apply_edit(
  server(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  String.t(),
  Minga.Buffer.EditSource.t()
) :: :ok
```

Replace text in a range with new text (the general-purpose edit operation).

# `apply_edits`

```elixir
@spec apply_edits(
  server(),
  [Minga.Buffer.Server.text_edit()],
  Minga.Buffer.EditSource.t()
) :: :ok
```

Apply a batch of edits atomically (for LSP workspace edits).

# `apply_snapshot`

```elixir
@spec apply_snapshot(server(), document()) :: :ok
```

Restore a previously captured document snapshot.

# `batch_decorations`

```elixir
@spec batch_decorations(server(), (Minga.Core.Decorations.t() -&gt;
                               Minga.Core.Decorations.t())) :: :ok
```

Apply a batch of decoration changes atomically.

# `break_undo_coalescing`

```elixir
@spec break_undo_coalescing(server()) :: :ok
```

Force the next edit to start a new undo group.

# `buffer_name`

```elixir
@spec buffer_name(server()) :: String.t() | nil
```

Logical name of the buffer (e.g., `*Messages*`), or nil for file buffers.

# `buffer_type`

```elixir
@spec buffer_type(server()) :: :file | :scratch | :special
```

Buffer kind: `:file`, `:scratch`, or `:special`.

# `byte_offset_for_line`

```elixir
@spec byte_offset_for_line(server(), non_neg_integer()) :: non_neg_integer()
```

Byte offset of the start of a line (for tree-sitter integration).

# `child_spec`

```elixir
@spec child_spec([Minga.Buffer.Server.start_opt()]) :: Supervisor.child_spec()
```

Supervisor child spec for starting a buffer process.

# `clear_face_override`

```elixir
@spec clear_face_override(server(), String.t()) :: :ok
```

Remove a face override for this buffer.

# `clear_line`

```elixir
@spec clear_line(server(), non_neg_integer()) :: {:ok, String.t()}
```

Clear the contents of a single line.

# `content`

```elixir
@spec content(server()) :: String.t()
```

Full text content of the buffer.

# `content_and_cursor`

```elixir
@spec content_and_cursor(server()) :: {String.t(), position()}
```

Content and cursor position in a single call (avoids two round-trips).

# `cursor`

```elixir
@spec cursor(server()) :: position()
```

Current cursor position as `{line, col}`.

# `decorations`

```elixir
@spec decorations(server()) :: Minga.Core.Decorations.t()
```

Current decoration state (highlights, virtual text, folds, etc.).

# `decorations_version`

```elixir
@spec decorations_version(server()) :: non_neg_integer()
```

Version counter for decorations (for change detection).

# `delete_at`

```elixir
@spec delete_at(server()) :: :ok
```

Delete the character at the cursor.

# `delete_before`

```elixir
@spec delete_before(server()) :: :ok
```

Delete the character before the cursor.

# `delete_lines`

```elixir
@spec delete_lines(server(), non_neg_integer(), non_neg_integer()) :: :ok
```

Delete lines from `start_line` to `end_line` (inclusive).

# `delete_range`

```elixir
@spec delete_range(server(), position(), position()) :: :ok
```

Delete text between two positions.

# `dirty?`

```elixir
@spec dirty?(server()) :: boolean()
```

Whether the buffer has unsaved changes.

# `display_name`

```elixir
@spec display_name(server()) :: String.t()
```

Human-readable display name (file basename or buffer name).

# `ensure_for_path`

```elixir
@spec ensure_for_path(String.t()) :: {:ok, pid()} | {:error, term()}
```

Returns the pid for a buffer at `path`, starting one if it doesn't exist.

If a buffer is already registered for `path`, returns its pid immediately.
Otherwise, starts a new buffer under `Minga.Buffer.Supervisor` and
broadcasts a `:buffer_opened` event so the Editor and other subscribers
can pick it up.

Used by agent tools to guarantee every edited file has a buffer with
undo integration, without depending on the Editor (Layer 2).

# `face_overrides`

```elixir
@spec face_overrides(server()) :: %{required(String.t()) =&gt; keyword()}
```

Current face override map.

# `file_path`

```elixir
@spec file_path(server()) :: String.t() | nil
```

File path of the buffer, or nil for scratch buffers.

# `filetype`

```elixir
@spec filetype(server()) :: atom()
```

Detected filetype (e.g., `:elixir`, `:json`).

# `find_and_replace`

```elixir
@spec find_and_replace(
  server(),
  String.t(),
  String.t(),
  Minga.Buffer.Server.boundary()
) ::
  {:ok, String.t()} | {:error, String.t()}
```

Find and replace the first occurrence of `old_text` with `new_text`.

# `find_and_replace_batch`

```elixir
@spec find_and_replace_batch(
  server(),
  [Minga.Buffer.Server.replace_edit()],
  Minga.Buffer.Server.boundary()
) :: {:ok, [Minga.Buffer.Server.replace_result()]} | {:error, String.t()}
```

Find and replace multiple patterns atomically.

# `flush_edits`

```elixir
@spec flush_edits(server(), atom()) :: [Minga.Buffer.EditDelta.t()]
```

Flush edit deltas accumulated since the given consumer's last read.

# `force_save`

```elixir
@spec force_save(server()) :: :ok | {:error, term()}
```

Save even if the buffer appears clean.

# `get_option`

```elixir
@spec get_option(server(), atom()) :: term()
```

Read a per-buffer option (falls back to global config).

# `insert_char`

```elixir
@spec insert_char(server(), String.t(), Minga.Buffer.EditSource.t()) :: :ok
```

Insert a single character at the cursor.

# `insert_text`

```elixir
@spec insert_text(server(), String.t(), Minga.Buffer.EditSource.t()) :: :ok
```

Insert a multi-character string at the cursor.

# `line_count`

```elixir
@spec line_count(server()) :: pos_integer()
```

Number of lines in the buffer.

# `lines`

```elixir
@spec lines(server(), non_neg_integer(), non_neg_integer()) :: [String.t()]
```

A range of lines starting at `start` (0-indexed), returning `count` lines.

# `lines_content`

```elixir
@spec lines_content(server(), non_neg_integer(), non_neg_integer()) :: String.t()
```

Content of lines from `start_line` to `end_line` (inclusive, 0-indexed).

# `move`

```elixir
@spec move(server(), direction()) :: :ok
```

Move the cursor one step in a direction.

# `move_if_possible`

```elixir
@spec move_if_possible(server(), :left | :right) ::
  {:ok, position()} | {:at_boundary, position()}
```

Move cursor if possible, returning the result position and whether a boundary was hit.

# `move_to`

```elixir
@spec move_to(server(), position()) :: :ok
```

Move the cursor to an exact position.

# `open`

```elixir
@spec open(server(), String.t()) :: :ok | {:error, term()}
```

Open a file into the buffer, replacing current content.

# `persistent?`

```elixir
@spec persistent?(server()) :: boolean()
```

Whether the buffer survives tab close (e.g., `*Messages*`).

# `pid_for_path`

```elixir
@spec pid_for_path(String.t()) :: {:ok, pid()} | :not_found
```

Look up a buffer process by its file path. Returns `:not_found` if no buffer has that file open.

# `read_only?`

```elixir
@spec read_only?(server()) :: boolean()
```

Whether the buffer is read-only.

# `redo`

```elixir
@spec redo(server()) :: :ok | :empty
```

Redo the last undone edit.

# `reload`

```elixir
@spec reload(server()) :: :ok | {:error, term()}
```

Reload content from disk, discarding unsaved changes.

# `remap_face`

```elixir
@spec remap_face(server(), String.t(), keyword()) :: :ok
```

Override a face's attributes for this buffer.

# `remove_block_decoration`

```elixir
@spec remove_block_decoration(server(), reference()) :: :ok
```

Remove a block decoration by ID.

# `remove_highlight`

```elixir
@spec remove_highlight(server(), reference()) :: :ok
```

Remove a highlight by ID.

# `remove_highlight_group`

```elixir
@spec remove_highlight_group(server(), atom()) :: :ok
```

Remove all highlights in a group.

# `remove_virtual_text`

```elixir
@spec remove_virtual_text(server(), reference()) :: :ok
```

Remove virtual text by ID.

# `render_snapshot`

```elixir
@spec render_snapshot(server(), non_neg_integer(), non_neg_integer()) ::
  Minga.Buffer.RenderSnapshot.t()
```

Render-ready snapshot of visible lines for the rendering pipeline.

# `replace_content`

```elixir
@spec replace_content(server(), String.t(), Minga.Buffer.State.edit_source()) ::
  :ok | {:error, term()}
```

Replace the entire buffer content.

# `replace_content_force`

```elixir
@spec replace_content_force(server(), String.t()) :: :ok
```

Replace content unconditionally (bypasses read-only check).

# `replace_content_with_decorations`

```elixir
@spec replace_content_with_decorations(server(), String.t(), function(), keyword()) ::
  :ok
```

Replace buffer content and apply decorations in one atomic operation.

# `save`

```elixir
@spec save(server()) :: :ok | {:error, term()}
```

Save the buffer to disk.

# `save_all_dirty`

```elixir
@spec save_all_dirty() :: {non_neg_integer(), [String.t()]}
```

Saves all dirty file-backed buffers to disk.

Enumerates the Buffer.Registry, filters for dirty buffers, and saves each one.
Read-only buffers and save failures are logged as warnings but do not block
other saves. Returns the count of successfully saved buffers and a list of
any warnings (path + error reason).

# `save_as`

```elixir
@spec save_as(server(), String.t()) :: :ok | {:error, term()}
```

Save the buffer to a new file path.

# `set_filetype`

```elixir
@spec set_filetype(server(), atom()) :: :ok
```

Override the detected filetype.

# `set_option`

```elixir
@spec set_option(server(), atom(), term()) :: {:ok, term()} | {:error, String.t()}
```

Set a per-buffer option override.

# `snapshot`

```elixir
@spec snapshot(server()) :: document()
```

Capture a snapshot of the document state (for undo boundaries and tab switching).

# `start_link`

```elixir
@spec start_link([Minga.Buffer.Server.start_opt()]) :: GenServer.on_start()
```

Start a new buffer process.

# `text_between`

```elixir
@spec text_between(server(), position(), position()) :: String.t()
```

Text between two positions (end exclusive).

# `text_between_inclusive`

```elixir
@spec text_between_inclusive(server(), position(), position()) :: String.t()
```

Text between two positions (end inclusive, includes the character at end_pos).

# `undo`

```elixir
@spec undo(server()) :: :ok | :empty
```

Undo the last edit.

# `unlisted?`

```elixir
@spec unlisted?(server()) :: boolean()
```

Whether the buffer is hidden from buffer lists.

# `version`

```elixir
@spec version(server()) :: non_neg_integer()
```

Monotonic version counter (incremented on every content change).

---

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