MingaEditor.State (Minga v0.1.0)

Copy Markdown View Source

Internal state for the Editor GenServer.

Field categories

EditorState fields fall into three categories:

Workspace fields live in state.workspace (MingaEditor.Workspace.State) and are saved/restored when switching tabs. Each tab carries a snapshot of the workspace so switching tabs restores the full editing context.

Shell fields live in state.shell_state (MingaEditor.Shell.Traditional.State) and hold presentation concerns: chrome, overlays, transient UI state. The active shell module is state.shell. See MingaEditor.Shell for the behaviour definition.

Global fields are shared across all tabs and never snapshotted: port_manager, theme, render_timer, focus_stack, tab_bar, capabilities, layout, modeline_click_regions, tab_bar_click_regions, agent, picker_ui, whichkey.

Composed sub-structs

Summary

Types

Display metadata derived from the active window's content type.

A document highlight range from the LSP server.

Line number display style.

t()

Functions

Returns the active buffer index.

Returns display metadata for the active window's content.

Returns the active window struct, or nil if windows aren't initialized.

Returns the active window's viewport, falling back to state.workspace.viewport when no window is active. Use this for scroll commands that need to read/write the viewport of the focused window (not the terminal-level viewport).

Adds a new buffer and makes it the active buffer for the current window.

Pure variant of add_buffer/2. Returns {state, effects} instead of performing side effects directly.

Applies render pipeline mutations back to the editor state.

Returns the active buffer pid.

Returns the buffer list.

Builds a complete per-tab context for an agent tab.

Pure variant of remove_dead_buffer/2. Returns {state, effects} instead of performing side effects directly.

Finds the agent chat window in the windows map.

Returns the index of the buffer whose file path matches file_path, or nil.

Switches focus to the given window, saving the current cursor to the outgoing window and restoring the target window's stored cursor.

Invalidates render caches for all windows.

Monitors a buffer pid so the Editor receives :DOWN when it dies.

Monitors a list of buffer pids. Convenience wrapper around monitor_buffer/2.

Updates the active window's viewport. Falls back to updating state.workspace.viewport when no window is active.

Rebuilds state.agent from the Session process when switching to an agent tab. The Session is the source of truth for status, pending approval, and error. The editor's agent field is a rendering cache, not a source of truth.

Removes a dead buffer pid from all state locations.

Writes a tab context back into the live editor state.

Returns the appropriate keymap scope for the active window's content type.

Derives the keymap scope from a window's content type.

Returns the screen rect for layout computation, excluding the global minibuffer row and reserving space for the file tree panel when open.

Scrolls the agent chat window's viewport by delta lines and updates pinned state. Delegates to Window.scroll_viewport/3.

Sets the context for the next add_buffer call.

Returns true if the given tool should NOT be prompted for installation.

Captures the current per-tab fields into a context map.

Returns true if the editor has more than one window.

Starts a new buffer under the buffer supervisor for the given file path.

Switches to the buffer at idx, making it active for the current window.

Switches to the tab with target_id.

Pure variant of switch_tab/2. Returns {state, effects} instead of performing side effects directly.

Syncs the active window's buffer reference with state.workspace.buffers.active.

Snapshots the active buffer's cursor into the active window struct.

Transitions the editor to a new vim mode.

Returns the screen rect for the file tree panel, or nil if closed.

Applies a function to the shell state and returns the updated state.

Updates the window struct for the given window id via a mapper function.

Applies a function to the workspace and returns the updated state.

Types

backend()

@type backend() :: :tui | :native_gui | :headless

content_context()

@type content_context() :: %{
  type: :buffer | :agent,
  display_name: String.t(),
  directory: String.t(),
  dirty: boolean(),
  filetype: atom()
}

Display metadata derived from the active window's content type.

Used by title, modeline, and any other subsystem that needs to answer "what is the user looking at?" without assuming a buffer is active.

document_highlight()

@type document_highlight() :: Minga.LSP.DocumentHighlight.t()

A document highlight range from the LSP server.

line_number_style()

@type line_number_style() :: :hybrid | :absolute | :relative | :none

Line number display style.

t()

@type t() :: %MingaEditor.State{
  backend: backend(),
  buffer_add_context: MingaEditor.Shell.buffer_add_context(),
  buffer_monitors: %{required(pid()) => reference()},
  capabilities: MingaEditor.Frontend.Capabilities.t(),
  editing_model: :vim | :cua,
  face_override_registries: %{
    required(pid()) => MingaEditor.UI.Face.Registry.t()
  },
  focus_stack: [module()],
  font_registry: MingaEditor.UI.FontRegistry.t(),
  git_remote_op:
    {msg_ref :: reference(), task_monitor :: reference(),
     {git_root :: String.t(), success_msg :: String.t(),
      error_prefix :: String.t()}}
    | nil,
  last_cursor_line: non_neg_integer() | nil,
  last_test_command: {String.t(), String.t()} | nil,
  layout: MingaEditor.Layout.t() | nil,
  lsp: MingaEditor.State.LSP.t(),
  message_store: MingaEditor.UI.Panel.MessageStore.t(),
  parser_status: MingaEditor.Shell.Traditional.Modeline.parser_status(),
  pending_quit: :quit | :quit_all | nil,
  port_manager: GenServer.server() | nil,
  render_timer: reference() | nil,
  session: MingaEditor.State.Session.t(),
  shell: module(),
  shell_state:
    MingaEditor.Shell.Traditional.State.t() | MingaEditor.Shell.Board.State.t(),
  space_leader_pending: boolean(),
  space_leader_timer: reference() | nil,
  stashed_board_state: MingaEditor.Shell.Board.State.t() | nil,
  theme: MingaEditor.UI.Theme.t(),
  workspace: MingaEditor.Workspace.State.t()
}

Functions

active_buffer(state)

@spec active_buffer(t()) :: non_neg_integer()

Returns the active buffer index.

active_content_context(state)

@spec active_content_context(t()) :: content_context()

Returns display metadata for the active window's content.

Buffer windows return file/buffer metadata. Agent chat windows return agent-specific display info. Falls back to buffer metadata when the active window is nil or unrecognized.

active_tab(state)

@spec active_tab(t()) :: MingaEditor.State.Tab.t() | nil

active_tab_kind(state)

@spec active_tab_kind(t()) :: MingaEditor.State.Tab.kind()

active_window_struct(state)

@spec active_window_struct(t()) :: MingaEditor.Window.t() | nil

Returns the active window struct, or nil if windows aren't initialized.

active_window_viewport(state)

@spec active_window_viewport(t()) :: MingaEditor.Viewport.t()

Returns the active window's viewport, falling back to state.workspace.viewport when no window is active. Use this for scroll commands that need to read/write the viewport of the focused window (not the terminal-level viewport).

add_buffer(state, pid, opts \\ [])

@spec add_buffer(t(), pid(), keyword()) :: t()

Adds a new buffer and makes it the active buffer for the current window.

Thin wrapper around add_buffer_pure/3 that applies effects inline.

add_buffer_pure(state, pid, opts \\ [])

@spec add_buffer_pure(t(), pid(), keyword()) :: {t(), [MingaEditor.effect()]}

Pure variant of add_buffer/2. Returns {state, effects} instead of performing side effects directly.

Generic concerns (buffer pool) are handled here. Shell-specific presentation logic (tab bar, card routing) is dispatched through shell.on_buffer_added/4. The only effect returned is {:monitor, pid}.

The buffer-add context is read from state.buffer_add_context (set by picker preview) or overridden via opts[:context]. After dispatch the field is reset to :open.

agent(map)

@spec agent(t()) :: MingaEditor.State.Agent.t()

apply_render_output(state, render_output)

@spec apply_render_output(t(), MingaEditor.RenderPipeline.Input.t()) :: t()

Applies render pipeline mutations back to the editor state.

The render pipeline updates window caches (invalidation tracking, context fingerprints), click regions, and layout during rendering. This function writes those mutations back after the pipeline completes.

The render_output is a RenderPipeline.Input struct with the mutated fields. Only windows, shell_state, and layout carry meaningful changes; other fields are unchanged.

bottom_panel(map)

@spec bottom_panel(t()) :: MingaEditor.BottomPanel.t()

buffer(state)

@spec buffer(t()) :: pid() | nil

Returns the active buffer pid.

buffers(state)

@spec buffers(t()) :: [pid()]

Returns the buffer list.

build_agent_tab_defaults(state, windows, agent_buf)

@spec build_agent_tab_defaults(t(), MingaEditor.State.Windows.t(), pid() | nil) ::
  MingaEditor.State.Tab.context()

Builds a complete per-tab context for an agent tab.

Used by agent tab creation paths to ensure all @per_tab_fields are populated. Accepts a pre-built Windows struct for the agent chat window and the agent buffer pid.

cancel_nav_flash(s)

@spec cancel_nav_flash(t()) :: t()

clear_status(s)

@spec clear_status(t()) :: t()

close_buffer_pure(state, pid)

@spec close_buffer_pure(t(), pid()) :: {t(), [MingaEditor.effect()]}

Pure variant of remove_dead_buffer/2. Returns {state, effects} instead of performing side effects directly.

Removes the pid from the buffer list, clears it from special buffer slots, switches to another buffer if the active one died, and cleans up the monitor ref. This function is already pure (no process calls), so the effects list is always empty.

close_dashboard(s)

@spec close_dashboard(t()) :: t()

close_git_status_panel(s)

@spec close_git_status_panel(t()) :: t()

dashboard(map)

@spec dashboard(t()) :: MingaEditor.Dashboard.state() | nil

dismiss_hover_popup(s)

@spec dismiss_hover_popup(t()) :: t()

find_agent_chat_window(state)

@spec find_agent_chat_window(t()) ::
  {MingaEditor.Window.id(), MingaEditor.Window.t()} | nil

Finds the agent chat window in the windows map.

Returns {win_id, window} or nil if no agent chat window exists.

find_buffer_by_path(map, file_path)

@spec find_buffer_by_path(t() | map(), String.t()) :: non_neg_integer() | nil

Returns the index of the buffer whose file path matches file_path, or nil.

Catches :exit for each buffer in case a process has died but not yet been removed from the buffer list.

find_tab_by_buffer(state, pid)

@spec find_tab_by_buffer(t(), pid()) :: MingaEditor.State.Tab.t() | nil

focus_window(state, target_id)

@spec focus_window(t(), MingaEditor.Window.id()) :: t()

Switches focus to the given window, saving the current cursor to the outgoing window and restoring the target window's stored cursor.

No-op if target_id is already the active window or windows aren't set up.

git_status_panel(map)

@spec git_status_panel(t()) ::
  MingaEditor.Frontend.Protocol.GUI.git_status_data() | nil

hover_popup(map)

@spec hover_popup(t()) :: MingaEditor.HoverPopup.t() | nil

invalidate_all_windows(state)

@spec invalidate_all_windows(t()) :: t()

Invalidates render caches for all windows.

Call when the screen layout changes (file tree toggle, agent panel toggle) because cached draws contain baked-in absolute coordinates that become wrong when column offsets shift.

monitor_buffer(state, pid)

@spec monitor_buffer(t(), pid()) :: t()

Monitors a buffer pid so the Editor receives :DOWN when it dies.

Idempotent: if the pid is already monitored, returns state unchanged.

monitor_buffers(state, pids)

@spec monitor_buffers(t(), [pid()]) :: t()

Monitors a list of buffer pids. Convenience wrapper around monitor_buffer/2.

picker_ui(map)

@spec picker_ui(t()) :: MingaEditor.State.Picker.t()

prompt_ui(map)

@spec prompt_ui(t()) :: MingaEditor.State.Prompt.t()

put_active_window_viewport(state, new_vp)

@spec put_active_window_viewport(t(), MingaEditor.Viewport.t()) :: t()

Updates the active window's viewport. Falls back to updating state.workspace.viewport when no window is active.

rebuild_agent_from_session(state, tab)

@spec rebuild_agent_from_session(t(), MingaEditor.State.Tab.t()) :: t()

Rebuilds state.agent from the Session process when switching to an agent tab. The Session is the source of truth for status, pending approval, and error. The editor's agent field is a rendering cache, not a source of truth.

remove_dead_buffer(state, pid)

@spec remove_dead_buffer(t(), pid()) :: t()

Removes a dead buffer pid from all state locations.

Called from the Editor's :DOWN handler. Removes the pid from the buffer list, clears it from special buffer slots (messages, warnings, help), and switches to another buffer if the active one died. Also cleans up the monitor ref.

Thin wrapper around close_buffer_pure/2 that applies effects inline.

restore_tab_context(state, context)

@spec restore_tab_context(t(), MingaEditor.State.Tab.context()) :: t()

Writes a tab context back into the live editor state.

The context carries workspace fields as a flat map. Empty context means a brand-new tab; we build defaults with the current active buffer and viewport dimensions.

Backward compatibility: old contexts with nested structure are migrated to the new flat format via maybe_migrate_legacy_context/2.

scope_for_active_window(map)

@spec scope_for_active_window(t()) :: atom()

Returns the appropriate keymap scope for the active window's content type.

Used when leaving the file tree (toggle, close, navigate right) to restore the correct scope. Returns :agent for agent chat windows, :editor otherwise.

scope_for_content(content, current_scope)

Derives the keymap scope from a window's content type.

Agent chat windows always use :agent scope. Buffer windows use :editor when coming from :agent scope, and preserve the current scope otherwise (e.g., :file_tree stays as :file_tree).

screen_rect(state)

@spec screen_rect(t()) :: MingaEditor.WindowTree.rect()

Returns the screen rect for layout computation, excluding the global minibuffer row and reserving space for the file tree panel when open.

scroll_agent_chat_window(state, delta)

@spec scroll_agent_chat_window(t(), integer()) :: t()

Scrolls the agent chat window's viewport by delta lines and updates pinned state. Delegates to Window.scroll_viewport/3.

Returns the state unchanged if no agent chat window exists.

set_agent(s, agent)

@spec set_agent(t(), MingaEditor.State.Agent.t()) :: t()

set_bottom_panel(s, panel)

@spec set_bottom_panel(t(), MingaEditor.BottomPanel.t()) :: t()

set_buffer_add_context(state, context)

@spec set_buffer_add_context(t(), MingaEditor.Shell.buffer_add_context()) :: t()

Sets the context for the next add_buffer call.

Used by picker preview to mark buffer additions as transient previews rather than permanent opens. The context is consumed and reset to :open by add_buffer_pure/3.

set_dashboard(s, dash)

@spec set_dashboard(t(), MingaEditor.Dashboard.state()) :: t()

set_git_status_panel(s, data)

@spec set_git_status_panel(t(), map() | nil) :: t()

set_hover_popup(s, popup)

@spec set_hover_popup(t(), MingaEditor.HoverPopup.t()) :: t()

set_nav_flash(s, flash)

@spec set_nav_flash(t(), MingaEditor.NavFlash.t()) :: t()

set_picker_ui(s, pui)

@spec set_picker_ui(t(), MingaEditor.State.Picker.t()) :: t()

set_prompt_ui(s, prompt)

@spec set_prompt_ui(t(), MingaEditor.State.Prompt.t()) :: t()

set_status(s, msg)

@spec set_status(t(), String.t()) :: t()

set_tab_bar(s, tb)

@spec set_tab_bar(t(), MingaEditor.State.TabBar.t() | nil) :: t()

set_tab_session(state, tab_id, session_pid)

@spec set_tab_session(t(), MingaEditor.State.Tab.id(), pid() | nil) :: t()

set_whichkey(s, wk)

@spec set_whichkey(t(), MingaEditor.State.WhichKey.t()) :: t()

skip_tool_prompt?(state, tool_name)

@spec skip_tool_prompt?(t(), atom()) :: boolean()

Returns true if the given tool should NOT be prompted for installation.

A tool is skipped when it's already declined this session, already installed, currently being installed, or already in the prompt queue.

snapshot_tab_context(state)

@spec snapshot_tab_context(t()) :: MingaEditor.State.Tab.context()

Captures the current per-tab fields into a context map.

The returned map is stored in the outgoing tab so it can be restored when the user switches back.

split?(state)

@spec split?(t()) :: boolean()

Returns true if the editor has more than one window.

start_buffer(file_path)

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

Starts a new buffer under the buffer supervisor for the given file path.

status_msg(map)

@spec status_msg(t()) :: String.t() | nil

switch_buffer(state, idx)

@spec switch_buffer(t(), non_neg_integer()) :: t()

Switches to the buffer at idx, making it active for the current window.

Centralizes Buffers.switch_to + window sync so callers don't need to remember to call sync_active_window_buffer/1. Shell-specific presentation logic (tab label updates, etc.) is dispatched through shell.on_buffer_switched/2.

switch_tab(state, target_id)

@spec switch_tab(t(), MingaEditor.State.Tab.id()) :: t()

Switches to the tab with target_id.

Snapshots the current tab's context, stores it, updates the tab bar's active pointer, and restores the target tab's saved context into the live editor state. Invalidates layout and window caches since the entire visual context changes.

Thin wrapper around switch_tab_pure/2 that applies effects inline.

switch_tab_pure(state, target_id)

@spec switch_tab_pure(t(), MingaEditor.State.Tab.id()) ::
  {t(), [MingaEditor.effect()]}

Pure variant of switch_tab/2. Returns {state, effects} instead of performing side effects directly.

Snapshots the current tab's context, updates the tab bar pointer, restores the target tab's context, invalidates layout. Side effects (spinner stop/start, agent session rebuild) are returned as effects.

The returned effects list may include:

  • :stop_spinner — cancel the outgoing agent's spinner timer
  • {:rebuild_agent_session, tab} — rebuild agent state from session process
  • :start_spinner — conditionally restart spinner for incoming agent

sync_active_window_buffer(state)

@spec sync_active_window_buffer(t()) :: t()

Syncs the active window's buffer reference with state.workspace.buffers.active.

Call this after any operation that changes state.workspace.buffers.active to keep the window tree consistent. No-op when windows aren't initialized.

sync_active_window_cursor(state)

@spec sync_active_window_cursor(t()) :: t()

Snapshots the active buffer's cursor into the active window struct.

Call this before rendering split views so inactive windows have a fresh cursor position for the active window when it becomes inactive later.

tab_bar(map)

@spec tab_bar(t()) :: MingaEditor.State.TabBar.t() | nil

transition_mode(state, mode, mode_state \\ nil)

@spec transition_mode(t(), Minga.Mode.mode(), Minga.Mode.state() | nil) :: t()

Transitions the editor to a new vim mode.

Convenience wrapper around VimState.transition/3 that operates on the full EditorState. This is the preferred API for call sites that already have an EditorState.

Examples

# Simple transition (uses default mode_state):
EditorState.transition_mode(state, :normal)
EditorState.transition_mode(state, :insert)

# With explicit mode_state (required for visual, search, etc.):
EditorState.transition_mode(state, :visual, %VisualState{...})

tree_rect(state)

@spec tree_rect(t()) :: MingaEditor.WindowTree.rect() | nil

Returns the screen rect for the file tree panel, or nil if closed.

update_picker_ui(s, fun)

@spec update_picker_ui(t(), (MingaEditor.State.Picker.t() ->
                         MingaEditor.State.Picker.t())) :: t()

update_shell_state(state, fun)

Applies a function to the shell state and returns the updated state.

update_window(state, id, fun)

@spec update_window(t(), MingaEditor.Window.id(), (MingaEditor.Window.t() ->
                                               MingaEditor.Window.t())) ::
  t()

Updates the window struct for the given window id via a mapper function.

update_workspace(state, fun)

@spec update_workspace(t(), (MingaEditor.Workspace.State.t() ->
                         MingaEditor.Workspace.State.t())) :: t()

Applies a function to the workspace and returns the updated state.

whichkey(map)

@spec whichkey(t()) :: MingaEditor.State.WhichKey.t()