Minga.Buffer (Minga v0.1.0)

Copy Markdown View Source

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.

Summary

Functions

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

Add virtual text anchored to a line.

Append text to the end of the buffer.

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

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

Restore a previously captured document snapshot.

Apply a batch of decoration changes atomically.

Force the next edit to start a new undo group.

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

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

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

Supervisor child spec for starting a buffer process.

Remove a face override for this buffer.

Clear the contents of a single line.

Full text content of the buffer.

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

Current cursor position as {line, col}.

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

Version counter for decorations (for change detection).

Delete the character at the cursor.

Delete the character before the cursor.

Delete lines from start_line to end_line (inclusive).

Delete text between two positions.

Whether the buffer has unsaved changes.

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

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

Current face override map.

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

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

Find and replace the first occurrence of old_text with new_text.

Find and replace multiple patterns atomically.

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

Save even if the buffer appears clean.

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

Insert a multi-character string at the cursor.

Number of lines in the buffer.

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

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

Move the cursor one step in a direction.

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

Move the cursor to an exact position.

Open a file into the buffer, replacing current content.

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

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

Whether the buffer is read-only.

Redo the last undone edit.

Reload content from disk, discarding unsaved changes.

Override a face's attributes for this buffer.

Remove a block decoration by ID.

Remove a highlight by ID.

Remove all highlights in a group.

Remove virtual text by ID.

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

Replace the entire buffer content.

Replace content unconditionally (bypasses read-only check).

Replace buffer content and apply decorations in one atomic operation.

Save the buffer to disk.

Saves all dirty file-backed buffers to disk.

Save the buffer to a new file path.

Override the detected filetype.

Set a per-buffer option override.

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

Start a new buffer process.

Text between two positions (end exclusive).

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

Undo the last edit.

Whether the buffer is hidden from buffer lists.

Monotonic version counter (incremented on every content change).

Types

direction()

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

document()

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

position()

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

server()

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

Functions

add_block_decoration(server, anchor_line, opts)

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

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

add_highlight(server, start_pos, end_pos, opts)

Add a highlight range.

add_virtual_text(server, anchor, opts)

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

Add virtual text anchored to a line.

append(server, text)

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

Append text to the end of the buffer.

apply_edit(server, start_line, start_col, end_line, end_col, new_text, source \\ Minga.Buffer.EditSource.user())

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

apply_edits(server, edits, source \\ Minga.Buffer.EditSource.lsp(:unknown))

@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(server, new_buf)

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

Restore a previously captured document snapshot.

batch_decorations(server, fun)

@spec batch_decorations(server(), (Minga.Core.Decorations.t() ->
                               Minga.Core.Decorations.t())) :: :ok

Apply a batch of decoration changes atomically.

break_undo_coalescing(server)

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

Force the next edit to start a new undo group.

buffer_name(server)

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

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

buffer_type(server)

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

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

byte_offset_for_line(server, line)

@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(opts)

Supervisor child spec for starting a buffer process.

clear_face_override(server, face_name)

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

Remove a face override for this buffer.

clear_line(server, line)

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

Clear the contents of a single line.

content(server)

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

Full text content of the buffer.

content_and_cursor(server)

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

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

cursor(server)

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

Current cursor position as {line, col}.

decorations(server)

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

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

decorations_version(server)

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

Version counter for decorations (for change detection).

delete_at(server)

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

Delete the character at the cursor.

delete_before(server)

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

Delete the character before the cursor.

delete_lines(server, start_line, end_line)

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

Delete lines from start_line to end_line (inclusive).

delete_range(server, from_pos, to_pos)

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

Delete text between two positions.

dirty?(server)

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

Whether the buffer has unsaved changes.

display_name(server)

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

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

ensure_for_path(path)

@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(server)

@spec face_overrides(server()) :: %{required(String.t()) => keyword()}

Current face override map.

file_path(server)

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

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

filetype(server)

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

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

find_and_replace(server, old_text, new_text, boundary \\ nil)

@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(server, edits, boundary \\ nil)

@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(server, consumer_id)

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

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

force_save(server)

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

Save even if the buffer appears clean.

get_option(server, name)

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

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

insert_char(server, char, source \\ Minga.Buffer.EditSource.user())

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

Insert a single character at the cursor.

insert_text(server, text, source \\ Minga.Buffer.EditSource.user())

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

Insert a multi-character string at the cursor.

line_count(server)

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

Number of lines in the buffer.

lines(server, start, count)

@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(server, start_line, end_line)

@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(server, direction)

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

Move the cursor one step in a direction.

move_if_possible(server, direction)

@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(server, pos)

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

Move the cursor to an exact position.

open(server, file_path)

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

Open a file into the buffer, replacing current content.

persistent?(server)

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

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

pid_for_path(path)

@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?(server)

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

Whether the buffer is read-only.

redo(server)

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

Redo the last undone edit.

reload(server)

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

Reload content from disk, discarding unsaved changes.

remap_face(server, face_name, attrs)

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

Override a face's attributes for this buffer.

remove_block_decoration(server, id)

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

Remove a block decoration by ID.

remove_highlight(server, id)

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

Remove a highlight by ID.

remove_highlight_group(server, group)

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

Remove all highlights in a group.

remove_virtual_text(server, id)

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

Remove virtual text by ID.

render_snapshot(server, first_line, count)

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

replace_content(server, new_content, source \\ :user)

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

Replace the entire buffer content.

replace_content_force(server, new_content)

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

Replace content unconditionally (bypasses read-only check).

replace_content_with_decorations(server, content, decoration_fn, opts \\ [])

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

Replace buffer content and apply decorations in one atomic operation.

save(server)

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

Save the buffer to disk.

save_all_dirty()

@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(server, file_path)

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

Save the buffer to a new file path.

set_filetype(server, filetype)

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

Override the detected filetype.

set_option(server, name, value)

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

Set a per-buffer option override.

snapshot(server)

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

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

start_link(opts \\ [])

Start a new buffer process.

text_between(server, from_pos, to_pos)

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

Text between two positions (end exclusive).

text_between_inclusive(server, start_pos, end_pos)

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

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

undo(server)

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

Undo the last edit.

unlisted?(server)

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

Whether the buffer is hidden from buffer lists.

version(server)

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

Monotonic version counter (incremented on every content change).