MingaEditor.Frontend.Protocol (Minga v0.1.0)

Copy Markdown View Source

Binary protocol encoder/decoder for BEAM ↔ frontend 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 (frontend → BEAM)

OpcodeNamePayload
0x01key_presscodepoint::32, modifiers::8[, seq::32]
0x02resizewidth::16, height::16
0x03readywidth::16, height::16[, caps..., protocol_version::16]
0x04mouse_eventrow::16-signed, col::16-signed, button::8, mods::8, type::8

The extended ready handshake carries the frontend's compiled-in protocol_version; the BEAM rejects a mismatch with protocol_error (0x18).

Render Commands (BEAM → frontend)

Rendering is semantic-first. GUI render commands are encoded by Minga.Frontend.Adapter.GUI, with this module retaining the common side-channel encoders for titles, fonts, window background, frame-transaction boundaries (begin_frame/commit_frame), and the protocol_error version-mismatch signal. The cell-paradigm encoders (draw_text, set_cursor, clear, region commands) were retired in protocol_version 2; batch_end was replaced by the begin/commit frame transaction in protocol_version 3 (#2219).

Modifier Flags

FlagValue
SHIFT0x01
CTRL0x02
ALT0x04
SUPER0x08

Summary

Types

Cursor shape.

A highlight span from tree-sitter.

An input event decoded from Zig.

A frontend-originated input correlation sequence (u32).

Modifier flag bitmask.

Mouse button identifier.

Mouse event type.

Functions

Decodes an input event from a binary payload.

Encodes a begin_frame command that opens a frame transaction (#2219).

Encodes a commit_frame command that closes a frame transaction (#2219).

Encodes a protocol_error command.

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

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

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

Encodes a set_title command to update the terminal window title.

Encodes a set_window_bg command to set the default background color.

Checks if a modifier flag is set.

Returns the ALT modifier flag.

Returns the CTRL modifier flag.

Returns the SHIFT modifier flag.

Returns the SUPER modifier flag.

Types

conceal_span()

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

cursor_shape()

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

Cursor shape.

edit_delta()

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

highlight_span()

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

A highlight span from tree-sitter.

input_event()

@type input_event() ::
  {:key_press, codepoint :: non_neg_integer(), modifiers(), input_seq()}
  | {: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(),
     protocol_version :: non_neg_integer()}
  | {: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}
  | {:node_info, request_id :: non_neg_integer(),
     Minga.Parser.StructuralNavResult.t() | nil}
  | {:match_item_result, request_id :: non_neg_integer(),
     result :: {non_neg_integer(), non_neg_integer()} | nil}
  | {:textobject_positions, buffer_id :: non_neg_integer(),
     version :: non_neg_integer(),
     %{required(atom()) => [{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()}
  | {:request_keyframe, last_good_frame_seq :: non_neg_integer()}

An input event decoded from Zig.

input_seq()

@type input_seq() :: non_neg_integer()

A frontend-originated input correlation sequence (u32).

Stamped by the frontend at input decode for end-to-end keystroke latency instrumentation (ticket #2215). The BEAM echoes the latest processed sequence back on commit_frame. 0 means "no correlation" (legacy frontends that omit it).

modifiers()

@type modifiers() :: non_neg_integer()

Modifier flag bitmask.

mouse_button()

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

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

Mouse event type.

Functions

decode_event(data)

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

Decodes an input event from a binary payload.

encode_begin_frame(frame_seq, base_frame_seq)

@spec encode_begin_frame(non_neg_integer(), non_neg_integer()) :: binary()

Encodes a begin_frame command that opens a frame transaction (#2219).

frame_seq is the strictly monotonic global frame sequence (reuses Renderer.Server's seq) used for resync/attach ordering. base_frame_seq names the frame this transaction's deltas assume; base_frame_seq == 0 means keyframe (full snapshots, no deltas). Both fields are masked to u32 so a large monotonic frame_seq stays wire-safe. fixed:9 = opcode(1) + frame_seq(u32) + base(u32).

encode_close_buffer(buffer_id)

See Minga.Parser.Protocol.encode_close_buffer/1.

encode_commit_frame(frame_seq, input_seq \\ 0)

@spec encode_commit_frame(non_neg_integer(), input_seq()) :: binary()

Encodes a commit_frame command that closes a frame transaction (#2219).

frame_seq must match the open begin_frame's frame_seq. input_seq is the echoed input correlation sequence (formerly carried by batch_end, ticket #2215): the frontend resolves a keystroke-to-write latency sample when the frame presents. input_seq defaults to 0 ("no correlation") and is a SEPARATE id from frame_seq. fixed:9 = opcode(1) + frame_seq(u32) + input_seq(u32).

encode_edit_buffer(buffer_id, version, edits)

See Minga.Parser.Protocol.encode_edit_buffer/3.

encode_load_grammar(name, path)

See Minga.Parser.Protocol.encode_load_grammar/2.

encode_parse_buffer(buffer_id, version, source)

See Minga.Parser.Protocol.encode_parse_buffer/3.

encode_protocol_error(message)

@spec encode_protocol_error(String.t()) :: binary()

Encodes a protocol_error command.

The BEAM emits this when a frontend's handshake protocol_version does not match the BEAM's compiled-in Opcodes.protocol_version(). The frontend displays the UTF-8 reason as a blocking error instead of trying to decode a command stream it cannot parse. len16-framed: opcode(1) + len(u16) + message.

encode_query_language_at(buffer_id, request_id, byte_offset)

See Minga.Parser.Protocol.encode_query_language_at/3.

encode_register_font(font_id, family)

@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(buffer_id, request_id, line)

See Minga.Parser.Protocol.encode_request_indent/3.

encode_request_match_item(buffer_id, request_id, row, col)

See Minga.Parser.Protocol.encode_request_match_item/4.

encode_request_structural_nav(buffer_id, request_id, row, col, action)

@spec encode_request_structural_nav(
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  0..3
) :: binary()

See Minga.Parser.Protocol.encode_request_structural_nav/5.

encode_request_textobject(buffer_id, request_id, row, col, capture_name)

See Minga.Parser.Protocol.encode_request_textobject/5.

encode_set_fold_query(buffer_id, query)

See Minga.Parser.Protocol.encode_set_fold_query/2.

encode_set_font(family, size, ligatures, weight \\ :regular)

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

@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(buffer_id, query)

See Minga.Parser.Protocol.encode_set_highlight_query/2.

encode_set_indent_query(buffer_id, query)

See Minga.Parser.Protocol.encode_set_indent_query/2.

encode_set_injection_query(buffer_id, query)

See Minga.Parser.Protocol.encode_set_injection_query/2.

encode_set_language(buffer_id, name)

See Minga.Parser.Protocol.encode_set_language/2.

encode_set_tags_query(buffer_id, query)

See Minga.Parser.Protocol.encode_set_tags_query/2.

encode_set_textobject_query(buffer_id, query)

See Minga.Parser.Protocol.encode_set_textobject_query/2.

encode_set_title(title)

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

Encodes a set_title command to update the terminal window title.

encode_set_window_bg(rgb)

@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?(mods, flag)

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

Checks if a modifier flag is set.

mod_alt()

@spec mod_alt() :: modifiers()

Returns the ALT modifier flag.

mod_ctrl()

@spec mod_ctrl() :: modifiers()

Returns the CTRL modifier flag.

mod_shift()

@spec mod_shift() :: modifiers()

Returns the SHIFT modifier flag.

mod_super()

@spec mod_super() :: modifiers()

Returns the SUPER modifier flag.