# `MingaEditor.Frontend.Protocol`
[🔗](https://github.com/jsmestad/minga/blob/main/lib/minga_editor/frontend/protocol.ex#L1)

Binary protocol encoder/decoder for BEAM ↔ Zig communication.

Messages are length-prefixed binaries (4-byte big-endian header,
handled by Erlang's `{:packet, 4}` Port option). The payload
starts with a 1-byte opcode followed by opcode-specific fields.

## Input Events (Zig → BEAM)

| Opcode | Name        | Payload                                                     |
|--------|-------------|-------------------------------------------------------------|
| 0x01   | key_press   | `codepoint::32, modifiers::8`                               |
| 0x02   | resize      | `width::16, height::16`                                     |
| 0x03   | ready       | `width::16, height::16`                                     |
| 0x04   | mouse_event | `row::16-signed, col::16-signed, button::8, mods::8, type::8` |

## Render Commands (BEAM → Zig)

| Opcode | Name             | Payload                                                              |
|--------|------------------|----------------------------------------------------------------------|
| 0x10   | draw_text        | `row::16, col::16, fg::24, bg::24, attrs::8, text_len::16, text`     |
| 0x1C   | draw_styled_text | `row::16, col::16, fg::24, bg::24, attrs::16, ul_color::24, blend::8, font_weight::8, font_id::8, text_len::16, text` |
| 0x11   | set_cursor       | `row::16, col::16`                                                   |
| 0x12   | clear            | (empty)                                                              |
| 0x13   | batch_end        | (empty)                                                              |
| 0x15   | set_cursor_shape | `shape::8` (BLOCK=0, BEAM=1, UNDERLINE=2)                           |
| 0x1B   | scroll_region    | `top_row::16, bottom_row::16, delta::16-signed`                      |

## Modifier Flags

| Flag  | Value |
|-------|-------|
| SHIFT | 0x01  |
| CTRL  | 0x02  |
| ALT   | 0x04  |
| SUPER | 0x08  |

# `conceal_span`

```elixir
@type conceal_span() :: Minga.Parser.Protocol.conceal_span()
```

# `cursor_shape`

```elixir
@type cursor_shape() :: :block | :beam | :underline
```

Cursor shape.

# `edit_delta`

```elixir
@type edit_delta() :: Minga.Parser.Protocol.edit_delta()
```

# `highlight_span`

```elixir
@type highlight_span() :: Minga.Language.Highlight.Span.t()
```

A highlight span from tree-sitter.

# `input_event`

```elixir
@type input_event() ::
  {:key_press, codepoint :: non_neg_integer(), modifiers()}
  | {:resize, width :: pos_integer(), height :: pos_integer()}
  | {:ready, width :: pos_integer(), height :: pos_integer()}
  | {:ready, width :: pos_integer(), height :: pos_integer(),
     MingaEditor.Frontend.Capabilities.t()}
  | {:capabilities_updated, MingaEditor.Frontend.Capabilities.t()}
  | {:paste_event, text :: String.t()}
  | {:mouse_event, row :: integer(), col :: integer(), mouse_button(),
     modifiers(), mouse_event_type(), click_count :: pos_integer()}
  | {:highlight_spans, buffer_id :: non_neg_integer(),
     version :: non_neg_integer(), [highlight_span()]}
  | {:highlight_names, buffer_id :: non_neg_integer(), [String.t()]}
  | {:conceal_spans, buffer_id :: non_neg_integer(),
     version :: non_neg_integer(), [conceal_span()]}
  | {:grammar_loaded, success :: boolean(), name :: String.t()}
  | {:injection_ranges, buffer_id :: non_neg_integer(),
     [Minga.Language.Highlight.InjectionRange.t()]}
  | {:language_at_response, request_id :: non_neg_integer(),
     language :: String.t()}
  | {:fold_ranges, buffer_id :: non_neg_integer(), version :: non_neg_integer(),
     [{start_line :: non_neg_integer(), end_line :: non_neg_integer()}]}
  | {:indent_result, request_id :: non_neg_integer(), line :: non_neg_integer(),
     indent_level :: integer()}
  | {:textobject_result, request_id :: non_neg_integer(),
     result ::
       {non_neg_integer(), non_neg_integer(), non_neg_integer(),
        non_neg_integer()}
       | nil}
  | {:textobject_positions, buffer_id :: non_neg_integer(),
     version :: non_neg_integer(),
     %{required(atom()) =&gt; [{non_neg_integer(), non_neg_integer()}]}}
  | {:request_reparse, buffer_id :: non_neg_integer()}
  | {:log_message, level :: String.t(), text :: String.t()}
  | {:gui_action, MingaEditor.Frontend.Protocol.GUI.gui_action()}
```

An input event decoded from Zig.

# `modifiers`

```elixir
@type modifiers() :: non_neg_integer()
```

Modifier flag bitmask.

# `mouse_button`

```elixir
@type mouse_button() ::
  :left
  | :middle
  | :right
  | :none
  | :wheel_up
  | :wheel_down
  | :wheel_right
  | :wheel_left
  | {:unknown, non_neg_integer()}
```

Mouse button identifier.

# `mouse_event_type`

```elixir
@type mouse_event_type() ::
  :press | :release | :motion | :drag | {:unknown, non_neg_integer()}
```

Mouse event type.

# `region_role`

```elixir
@type region_role() ::
  :editor | :modeline | :minibuffer | :gutter | :popup | :panel | :border
```

Region role atom.

# `style`

```elixir
@type style() :: [
  fg: non_neg_integer(),
  bg: non_neg_integer(),
  bold: boolean(),
  underline: boolean(),
  italic: boolean(),
  reverse: boolean(),
  strikethrough: boolean(),
  underline_style: :line | :curl | :dashed | :dotted | :double,
  underline_color: non_neg_integer(),
  blend: 0..100,
  font_weight:
    :thin | :light | :regular | :medium | :semibold | :bold | :heavy | :black,
  font_id: non_neg_integer()
]
```

Text style attributes.

# `decode_command`

```elixir
@spec decode_command(binary()) ::
  {:ok,
   :clear
   | :batch_end
   | {:draw_text, map()}
   | {:draw_styled_text, map()}
   | {:set_cursor, non_neg_integer(), non_neg_integer()}
   | {:set_cursor_shape, cursor_shape()}}
  | {:error, :unknown_opcode | :malformed}
```

Decodes a render command from a binary payload (primarily for testing).

# `decode_event`

```elixir
@spec decode_event(binary()) ::
  {:ok, input_event()} | {:error, :unknown_opcode | :malformed}
```

Decodes an input event from a binary payload.

# `encode_batch_end`

```elixir
@spec encode_batch_end() :: binary()
```

Encodes a batch_end command (triggers render flush).

# `encode_clear`

```elixir
@spec encode_clear() :: binary()
```

Encodes a clear screen command.

# `encode_clear_region`

```elixir
@spec encode_clear_region(non_neg_integer()) :: binary()
```

Encodes a clear_region command.

# `encode_close_buffer`

# `encode_cursor`

```elixir
@spec encode_cursor(non_neg_integer(), non_neg_integer()) :: binary()
```

Encodes a set_cursor command.

# `encode_cursor_shape`

```elixir
@spec encode_cursor_shape(cursor_shape()) :: binary()
```

Encodes a set_cursor_shape command.

# `encode_define_region`

```elixir
@spec encode_define_region(
  non_neg_integer(),
  non_neg_integer(),
  region_role(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer()
) :: binary()
```

Encodes a define_region command.

# `encode_destroy_region`

```elixir
@spec encode_destroy_region(non_neg_integer()) :: binary()
```

Encodes a destroy_region command.

# `encode_draw`

```elixir
@spec encode_draw(non_neg_integer(), non_neg_integer(), String.t(), style()) ::
  binary()
```

Encodes a draw_text command.

# `encode_draw_smart`

```elixir
@spec encode_draw_smart(non_neg_integer(), non_neg_integer(), String.t(), style()) ::
  binary()
```

Smart encoder: uses `draw_styled_text` if the style contains extended
attributes, otherwise falls back to the more compact `draw_text`.

# `encode_draw_styled`

```elixir
@spec encode_draw_styled(non_neg_integer(), non_neg_integer(), String.t(), style()) ::
  binary()
```

Encodes a draw_styled_text command with extended attributes.

Extended over `draw_text` with:
- `attrs` expanded to 16 bits (adds strikethrough 0x10, underline_style 3 bits at 0xE0)
- `underline_color` as a separate 24-bit RGB field (0x000000 = use fg)
- `blend` as an 8-bit opacity value (0-100, 100 = fully opaque)
- `font_weight` as an 8-bit weight index (0-7, maps to thin through black)

Use this for text that needs underline styles, strikethrough, underline
color, blend, or per-span font weight. For simple text
(fg/bg/bold/italic/underline/reverse), `encode_draw/4` is more compact.

# `encode_edit_buffer`

# `encode_load_grammar`

# `encode_parse_buffer`

# `encode_query_language_at`

# `encode_register_font`

```elixir
@spec encode_register_font(non_neg_integer(), String.t()) :: binary()
```

Encodes a register_font command to associate a font_id with a font family.

Font ID 0 is the primary font (set via `set_font`). IDs 1-255 are
secondary fonts that can be referenced by `font_id` in `draw_styled_text`.
The GUI creates a FontFace for each registered font at the same size as
the primary. If the secondary font's cell metrics differ from the primary,
the GUI logs a warning and falls back to the primary for those glyphs.

Format: `opcode:8, font_id:8, name_len:16, name:bytes`

# `encode_request_indent`

# `encode_request_textobject`

# `encode_scroll_region`

```elixir
@spec encode_scroll_region(non_neg_integer(), non_neg_integer(), integer()) ::
  binary()
```

Encodes a scroll_region command for terminal scroll optimization.

Tells the Zig renderer to use ANSI scroll region sequences to shift
content within the given screen row range by `delta` lines, avoiding
a full redraw.

* `top_row` / `bottom_row` — screen row range (inclusive) for the scroll region.
* `delta` — positive = scroll up (content moves up, new lines at bottom),
            negative = scroll down (content moves down, new lines at top).

Wire format: `opcode(1) + top_row(2) + bottom_row(2) + delta(2, signed)` = 7 bytes.

# `encode_set_active_region`

```elixir
@spec encode_set_active_region(non_neg_integer()) :: binary()
```

Encodes a set_active_region command. Pass 0 to reset to root.

# `encode_set_fold_query`

# `encode_set_font`

```elixir
@spec encode_set_font(String.t(), pos_integer(), boolean(), atom()) :: binary()
```

Encodes a set_font command to configure the GUI frontend's font.

The font family is resolved by the frontend using NSFontManager (macOS)
so both display names ("JetBrains Mono") and PostScript names
("JetBrainsMonoNF-Regular") work. The TUI ignores this command.

Format: `opcode:8, size:16, weight:8, ligatures:8, name_len:16, name:bytes`

Fields are ordered by category: font identity (size, weight, name) then
rendering features (ligatures). The variable-length name stays at the end.

# `encode_set_font_fallback`

```elixir
@spec encode_set_font_fallback([String.t()]) :: binary()
```

Encodes a set_font_fallback command to configure the GUI's font fallback chain.

The fallback chain is tried in order when the primary font (set via `set_font`)
doesn't have a glyph. Each entry is a font family name resolved by the frontend
via NSFontManager. After the configured fallbacks, the system fallback
(CTFontCreateForString) is used as a last resort.

Format: `opcode:8, count:8, [name_len:16, name:bytes]*`

# `encode_set_highlight_query`

# `encode_set_indent_query`

# `encode_set_injection_query`

# `encode_set_language`

# `encode_set_textobject_query`

# `encode_set_title`

```elixir
@spec encode_set_title(String.t()) :: binary()
```

Encodes a set_title command to update the terminal window title.

# `encode_set_window_bg`

```elixir
@spec encode_set_window_bg(non_neg_integer()) :: binary()
```

Encodes a set_window_bg command to set the default background color.

The Zig renderer uses this as the fallback background for any cell
that doesn't specify an explicit `bg:` value. This prevents cells
from falling back to the terminal's default background, which may
not match the editor theme.

# `has_modifier?`

```elixir
@spec has_modifier?(modifiers(), modifiers()) :: boolean()
```

Checks if a modifier flag is set.

# `mod_alt`

```elixir
@spec mod_alt() :: modifiers()
```

Returns the ALT modifier flag.

# `mod_ctrl`

```elixir
@spec mod_ctrl() :: modifiers()
```

Returns the CTRL modifier flag.

# `mod_shift`

```elixir
@spec mod_shift() :: modifiers()
```

Returns the SHIFT modifier flag.

# `mod_super`

```elixir
@spec mod_super() :: modifiers()
```

Returns the SUPER modifier flag.

---

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