Minga Port Protocol Specification

Copy Markdown View Source

The BEAM editor core and rendering frontends communicate over a binary protocol on stdin/stdout of each frontend process. All frontends (Swift/Metal on macOS, GTK4 on Linux, Zig/libvaxis TUI) speak the same base protocol. GUI frontends additionally receive structured chrome opcodes documented in GUI_PROTOCOL.md. This document is the authoritative reference for implementing a Minga frontend. You should be able to build a working frontend by reading only this file (plus GUI_PROTOCOL.md for native GUIs).

Transport

The frontend runs as a child process of the BEAM. Communication uses stdin (BEAM → Frontend) and stdout (Frontend → BEAM).

Framing: Every message is prefixed with a 4-byte big-endian unsigned integer indicating the payload length. The payload follows immediately. Erlang's {:packet, 4} Port option handles framing on the BEAM side; frontends must read/write the 4-byte length header explicitly.


 length (4B)   payload (length bytes)  
 big-endian    opcode (1B) + fields    

Batching: The BEAM may concatenate multiple commands into a single length-prefixed message. The frontend must parse commands sequentially within a payload using commandSize() to determine where each command ends. The Elixir encoder typically batches an entire render frame into one message.

Byte order: All multi-byte integers are big-endian unless noted otherwise.

Text encoding: All text fields (draw_text content, language names, query source) are UTF-8 encoded.


Quick Reference

BEAM → Frontend (Render Commands)

OpcodeNameSizeDescription
0x10draw_text14 + text_lenDraw styled text at a position
0x11set_cursor5Position the cursor
0x12clear1Clear the entire screen
0x13batch_end1End of frame; flush to screen
0x14define_region15Create/update a layout region
0x15set_cursor_shape2Change cursor appearance
0x16set_title3 + title_lenSet the window/terminal title
0x18clear_region3Clear a specific region
0x19destroy_region3Remove a region
0x1Aset_active_region3Route draw commands to a region
0x27measure_text7 + text_lenRequest display width of text

BEAM → Frontend (Config Commands)

OpcodeNameSizeDescription
0x50set_font7 + name_lenSet font family, size, weight, and ligatures

BEAM → Frontend (Highlight Commands)

OpcodeNameSizeDescription
0x20set_language3 + name_lenSet the active tree-sitter language
0x21parse_buffer9 + source_lenParse buffer content for highlighting
0x22set_highlight_query5 + query_lenSet a custom highlight query
0x23load_grammar5 + name_len + path_lenLoad a grammar from a shared library
0x24set_injection_query5 + query_lenSet a custom injection query
0x25query_language_at9Query the language at a byte offset
0x26edit_buffer7 + variableIncremental edit deltas

Frontend → BEAM (Input Events)

OpcodeNameSizeDescription
0x01key_press6A key was pressed
0x02resize5Terminal/window was resized
0x03ready5 or 13Frontend is initialized and ready
0x04mouse_event9Mouse button, wheel, or motion (8-byte legacy also accepted)
0x05capabilities_updated9Updated capabilities after async detection

Frontend → BEAM (Highlight Responses)

OpcodeNameSizeDescription
0x30highlight_spans9 + count × 10Syntax highlight byte ranges
0x31highlight_names3 + variableCapture name list for spans
0x32grammar_loaded4 + name_lenGrammar load success/failure
0x33language_at_response7 + name_lenLanguage at a byte offset
0x34injection_ranges3 + variableInjection language regions
0x35text_width7Display width of measured text
0x36fold_rangesvariableFold range results from tree-sitter
0x37indent_result13Indent level result for a line
0x38textobject_resultvariableText object range result (or nil)
0x39textobject_positions9 + count × 9Proactive text object position cache
0x3Brequest_reparse5Parser requests full reparse after stale edit deltas

Frontend → BEAM (Diagnostics)

OpcodeNameSizeDescription
0x60log_message4 + msg_lenLog message from the frontend

Render Commands (BEAM → Frontend)

0x10 draw_text

Draw styled text at a screen position.

opcode:   u8  = 0x10
row:      u16           screen row (0-indexed from top)
col:      u16           screen column (0-indexed from left)
fg:       u24           foreground color (RGB, 0x000000 = terminal default)
bg:       u24           background color (RGB, 0x000000 = terminal default)
attrs:    u8            style attribute flags (see below)
text_len: u16           byte length of text
text:     [text_len]u8  UTF-8 encoded text

Total size: 14 + text_len bytes.

Attribute flags:

FlagValueMeaning
BOLD0x01Bold weight
UNDERLINE0x02Single underline
ITALIC0x04Italic style
REVERSE0x08Swap foreground and background

Multiple flags can be combined with bitwise OR: 0x05 = bold + italic.

Behavior: The frontend writes each grapheme cluster in the text to consecutive screen cells starting at (row, col). Wide characters (CJK, emoji) occupy 2 cells. The frontend is responsible for grapheme iteration and display width calculation. Text that extends past the screen width is clipped.

Color convention: 0x000000 for fg means "use the terminal's default foreground." 0x000000 for bg means "use the terminal's default background." Actual black can be represented as 0x000001 if needed, though in practice themes avoid this ambiguity.

0x11 set_cursor

Position the visible cursor.

opcode: u8  = 0x11
row:    u16           screen row
col:    u16           screen column

Total size: 5 bytes.

Behavior: Move the cursor to the specified position. The cursor is displayed at this location after the next batch_end render. Only one cursor position is active at a time; the last set_cursor in a frame wins.

0x12 clear

Clear the entire screen.

opcode: u8 = 0x12

Total size: 1 byte.

Behavior: Reset all cells to blank (space character, default colors, no attributes). This is always the first command in a render frame.

0x13 batch_end

End of a render frame.

opcode: u8 = 0x13

Total size: 1 byte.

Behavior: The frontend flushes all pending draw operations to the screen. For a TUI, this means writing the diff of changed cells to the terminal. For a GUI, this means triggering a display refresh. No visible changes should appear until batch_end is received.

0x15 set_cursor_shape

Change the cursor's visual appearance.

opcode: u8 = 0x15
shape:  u8           cursor shape

Total size: 2 bytes.

Shape values:

ValueShapeTypical Use
0x00BlockNormal mode
0x01Beam (line)Insert mode
0x02UnderlineReplace mode

0x16 set_title

Set the window or terminal title.

opcode:    u8  = 0x16
title_len: u16           byte length of title
title:     [title_len]u8 UTF-8 encoded title string

Total size: 3 + title_len bytes.

Behavior: TUI frontends emit OSC 0 (\x1b]0;{title}\x07). GUI frontends set the window title.


Render Frame Lifecycle

Every render frame follows this sequence:

clear
draw_text × N       (one per styled text segment, ordered top-to-bottom)
set_cursor           (exactly once)
set_cursor_shape     (exactly once; may be omitted if unchanged)
batch_end            (triggers the actual render)

The BEAM sends the entire frame as a single batched message. The frontend processes commands in order and only renders to screen on batch_end.

Between frames, the frontend must not modify the screen. The BEAM drives all visual updates.


Input Events (Frontend → BEAM)

0x01 key_press

A key was pressed.

opcode:    u8  = 0x01
codepoint: u32           Unicode codepoint of the key
modifiers: u8            modifier flags (see below)

Total size: 6 bytes.

Codepoint values: Standard Unicode codepoints for printable characters. For special keys, use the codepoint values defined by the frontend's input library (e.g., libvaxis uses values above the Unicode range for function keys, arrows, etc.). The BEAM's key handling maps these to editor actions.

Modifier flags:

FlagValue
SHIFT0x01
CTRL0x02
ALT0x04
SUPER0x08

Combined with bitwise OR: Ctrl+Shift = 0x03.

0x02 resize

The terminal or window was resized.

opcode: u8  = 0x02
width:  u16           new width in columns (or pixels for GUI)
height: u16           new height in rows (or pixels for GUI)

Total size: 5 bytes.

Behavior: Sent when the frontend detects a size change (SIGWINCH for TUI, window resize event for GUI). The BEAM re-renders to the new dimensions on the next frame.

0x03 ready

The frontend has initialized and is ready to receive render commands.

Short format (5 bytes):

opcode: u8  = 0x03
width:  u16           initial width
height: u16           initial height

Extended format (13 bytes):

opcode:       u8  = 0x03
width:        u16           initial width
height:       u16           initial height
caps_version: u8            capability format version (currently 1)
caps_len:     u8            length of capability data
caps_data:    [caps_len]u8  capability fields (see "Capability Negotiation" section)

Behavior: Sent exactly once, during startup, after the frontend has set up its rendering surface. The BEAM waits for this event before sending any render commands.

Frontends should use the extended format when possible. The BEAM detects which format was sent by checking the payload length: 5 bytes = short format with default capabilities, 13+ bytes = extended format with explicit capabilities.

0x04 mouse_event

A mouse button, wheel, or motion event.

opcode:      u8  = 0x04
row:         i16           screen row (signed; -1 = outside window)
col:         i16           screen column (signed; -1 = outside window)
button:      u8            button identifier
modifiers:   u8            modifier flags (same as key_press)
event_type:  u8            type of mouse event
click_count: u8            1 = single, 2 = double, 3 = triple (clamped)

Total size: 9 bytes.

Backward compatibility: The BEAM decoder accepts 8-byte messages (legacy format without click_count) and defaults click_count to 1. GUI frontends should always send the 9-byte format with the native click count from the OS. TUI frontends send click_count=1 and let the BEAM detect multi-clicks via timing.

Button values:

ValueButton
0x00Left
0x01Middle
0x02Right
0x03None (motion without button)
0x40Wheel up
0x41Wheel down
0x42Wheel right
0x43Wheel left

Event type values:

ValueType
0x00Press
0x01Release
0x02Motion (no button held)
0x03Drag (button held during motion)

Config Commands (BEAM → Frontend)

Config commands push editor configuration to the frontend. The TUI silently ignores these (terminal fonts are set by the terminal emulator, not the editor). The macOS GUI applies them immediately.

0x50 set_font

Set the font family, size, weight, and ligature preference. Sent once on ready and again when the user changes font config at runtime.

opcode:    u8  = 0x50
size:      u16           font size in points
weight:    u8            font weight (see table below)
ligatures: u8            1 = enable programming ligatures, 0 = disable
name_len:  u16           byte length of the font family name
name:      [name_len]u8  UTF-8 encoded font family name

Total size: 7 + name_len bytes.

Weight values:

ValueWeight
0thin
1light
2regular (default)
3medium
4semibold
5bold
6heavy
7black

Font name resolution (macOS GUI): The name is a user-friendly display name like "JetBrains Mono" or "Fira Code". The frontend resolves it to an installed font using NSFontManager. PostScript names ("JetBrainsMonoNF-Regular") also work. If the font isn't found, the frontend falls back to the system monospace font and logs a warning.

Ligature behavior: When ligatures are enabled and the font supports programming ligatures, the frontend shapes multi-character sequences (like ->, !=, =>) using CoreText and renders them as single wide glyphs spanning the appropriate number of cells. When disabled, each character renders individually regardless of font support. Fonts without ligature tables (e.g., Menlo) are unaffected by this flag.

Grid resize: Changing the font size changes the cell dimensions, which changes how many cells fit in the window. The frontend sends a 0x02 resize event back to the BEAM with the new grid dimensions after applying a font change.


Highlight Commands (BEAM → Frontend)

These commands control tree-sitter syntax highlighting. In the current architecture, the frontend process that handles these may be the same as or separate from the renderer (see Architecture Notes below).

0x20 set_language

Set the active tree-sitter grammar.

opcode:   u8  = 0x20
name_len: u16           byte length of language name
name:     [name_len]u8  language name (e.g., "elixir", "json", "markdown")

Total size: 3 + name_len bytes.

Behavior: Select the grammar for subsequent parse operations. The frontend should look up the language in its grammar registry. If the language is not found, log a warning and continue (highlighting will be unavailable for this buffer).

0x21 parse_buffer

Parse buffer content and return highlight spans.

opcode:     u8  = 0x21
version:    u32           monotonically increasing version counter
source_len: u32           byte length of source text
source:     [source_len]u8  UTF-8 encoded buffer content

Total size: 9 + source_len bytes.

Behavior: Parse the source text with the currently active grammar. Run the highlight query (and injection query, if set) against the parse tree. Send back highlight_names (if capture names changed) followed by highlight_spans with the version counter. If injection regions are found, also send injection_ranges.

The version counter prevents stale results: the BEAM discards spans with a version lower than the most recently requested parse. The frontend should include the version from the request in the highlight_spans response.

0x22 set_highlight_query

Override the built-in highlight query with custom .scm source.

opcode:    u8  = 0x22
query_len: u32           byte length of query source
query:     [query_len]u8 tree-sitter query source (.scm format)

Total size: 5 + query_len bytes.

Behavior: Compile the query for the currently active language and use it for subsequent highlighting. If compilation fails, log a warning and continue with the previous query (or no query). This is used for user-overridden queries from ~/.config/minga/queries/{lang}/highlights.scm.

0x23 load_grammar

Dynamically load a grammar from a shared library.

opcode:   u8  = 0x23
name_len: u16           byte length of grammar name
name:     [name_len]u8  grammar name
path_len: u16           byte length of library path
path:     [path_len]u8  filesystem path to .so/.dylib

Total size: 5 + name_len + path_len bytes.

Behavior: Load the shared library at path and look up the symbol tree_sitter_{name}. Register the language in the grammar registry. Respond with grammar_loaded (opcode 0x32) indicating success or failure.

0x24 set_injection_query

Override the built-in injection query for language embedding (e.g., Markdown fenced code blocks).

opcode:    u8  = 0x24
query_len: u32           byte length of query source
query:     [query_len]u8 tree-sitter query source (.scm format)

Total size: 5 + query_len bytes.

Behavior: Same as set_highlight_query but for the injection query. The injection query identifies embedded language regions (e.g., code blocks in Markdown) and their language names.

0x25 query_language_at

Ask which language is active at a byte offset (for injection-aware features like comment toggling).

opcode:      u8  = 0x25
request_id:  u32           caller-provided correlation ID
byte_offset: u32           byte offset into the last parsed source

Total size: 9 bytes.

Behavior: Check the injection ranges from the most recent parse. If the byte offset falls within an injection region, return that region's language name. Otherwise, return the outer (root) language name. Respond with language_at_response (opcode 0x33).


Highlight Responses (Frontend → BEAM)

0x30 highlight_spans

Syntax highlight byte ranges with capture IDs.

opcode:  u8  = 0x30
version: u32           version from the parse_buffer request
count:   u32           number of spans
spans:   [count × 10] array of spans

Each span:

start_byte: u32           start byte offset in source
end_byte:   u32           end byte offset in source (exclusive)
capture_id: u16           index into the capture names list

Total size: 9 + count × 10 bytes.

Behavior: Spans are sorted by (start_byte ASC, layer DESC, pattern_index DESC, end_byte ASC). The BEAM uses a first-wins walk: the first span covering a byte position determines its style. Higher-layer spans (from injection languages) take priority over lower-layer spans (from the outer language) at the same position.

0x31 highlight_names

Capture name list for interpreting span capture IDs.

opcode: u8  = 0x31
count:  u16           number of names
names:  [variable]    array of length-prefixed strings

Each name:

name_len: u16           byte length of name
name:     [name_len]u8  capture name (e.g., "keyword", "string", "comment")

Behavior: Sent before or alongside highlight_spans whenever the set of capture names changes (typically on first parse or language switch). The BEAM maps capture names to theme colors. The capture_id in each span is an index into this list.

0x32 grammar_loaded

Response to load_grammar.

opcode:   u8  = 0x32
success:  u8            1 = success, 0 = failure
name_len: u16           byte length of grammar name
name:     [name_len]u8  grammar name

Total size: 4 + name_len bytes.

0x33 language_at_response

Response to query_language_at.

opcode:     u8  = 0x33
request_id: u32           correlation ID from the request
name_len:   u16           byte length of language name (0 if no language set)
name:       [name_len]u8  language name

Total size: 7 + name_len bytes.

0x34 injection_ranges

Language injection regions found during parsing.

opcode: u8  = 0x34
count:  u16           number of ranges
ranges: [variable]    array of injection ranges

Each range:

start_byte: u32           start byte offset
end_byte:   u32           end byte offset (exclusive)
name_len:   u16           byte length of language name
name:       [name_len]u8  language name for this region

Behavior: Sent after highlight_spans when the parse found embedded language regions (e.g., JSON inside a Markdown fenced code block). The BEAM can use these ranges for injection-aware features like line comment toggling.

0x39 textobject_positions

Proactive cache of all .around text object positions in the current file, sent after each parse (initial and incremental). The BEAM caches these per-window for ]f/[f-style navigation with zero per-keystroke IPC.

opcode:  u8  = 0x39
version: u32           parse version counter
count:   u32           number of entries
entries: [count × 9]   array of position entries

Each entry:

type_id: u8   text object type (see table below)
row:     u32  0-indexed line number
col:     u32  0-indexed byte column

Total size: 9 + count × 9 bytes.

Type ID values:

ValueType
0x00function
0x01class
0x02parameter
0x03block
0x04comment
0x05test

Behavior: The Zig parser runs the textobjects.scm query against the parse tree and collects the start positions of all .around captures (e.g., @function.around, @class.around). Entries are sorted by (row, col) before sending. The BEAM decodes them into a %{atom => [{row, col}]} map and stores them on the active window struct. Navigation commands (]f, [f, etc.) scan this cached data with no further IPC to Zig.

0x3B request_reparse

Sent by the parser when it receives an edit_buffer command with stale edit deltas (byte offsets that don't match the parser's stored source for that buffer). This typically happens after system sleep/wake, when the BEAM's view of the buffer has drifted from the parser's. The parser discards the buffer's tree and source and asks the BEAM to resend the full content via parse_buffer.

opcode:     u8  = 0x3B
buffer_id:  u32          the buffer that needs a full reparse

Total size: 5 bytes.

Behavior: The BEAM receives this event, looks up the buffer PID for the given buffer_id, and sends a full set_language + parse_buffer sequence for that buffer. This is the same path used when setting up a buffer for the first time, so custom user queries are replayed correctly.


Log Messages (Frontend → BEAM)

0x60 log_message

A log message from the frontend process.

opcode:  u8  = 0x60
level:   u8            log level
msg_len: u16           byte length of message
msg:     [msg_len]u8   UTF-8 encoded log text

Total size: 4 + msg_len bytes.

Log level values:

ValueLevel
0x00Error
0x01Warning
0x02Info
0x03Debug

Behavior: The BEAM routes these to the *Messages* buffer, prefixed with the log level (e.g., [ZIG/WARN] message text). Frontends should use this for diagnostic messages that help the user understand what the rendering layer is doing.


Error Handling

Unknown opcodes: The receiver should log a warning and skip the command. For batched messages, use commandSize() to advance past the unknown command.

Malformed payloads: If a payload is too short for its opcode's expected format, the receiver should log a warning and discard the message.

Version mismatches: The BEAM discards highlight_spans responses where the version is lower than the most recently requested version. This prevents stale async results from overwriting current highlights.

Frontend crash: The BEAM's supervisor detects the Port exit and can restart the frontend. Buffer state, undo history, and cursor positions are preserved in the BEAM. The restarted frontend receives a full re-render on its first frame.


Architecture Notes

Current design

Tree-sitter parsing runs in a dedicated minga-parser Zig process, separate from the rendering frontend. The renderer process handles only render commands (0x10-0x1B plus GUI chrome 0x70-0x78). The parser process handles highlight commands (0x20-0x26) and sends highlight responses (0x30-0x3B). Both use the same {:packet, 4} framing on their respective stdin/stdout pipes. The BEAM manages both Port processes, routing commands to the appropriate one.

This separation means rendering frontends (Swift/Metal, GTK4, Zig/libvaxis) only need to implement render commands. Tree-sitter parsing is handled by the shared parser process regardless of which frontend is active.


Capability Negotiation

The ready event supports an extended format with capability fields. This lets the BEAM adapt rendering strategy based on what the frontend supports.

Extended Ready Format

0x03 ready (extended):
  width:          u16
  height:         u16
  caps_version:   u8    (currently 1)
  caps_len:       u8    (length of remaining fields)
  frontend_type:  u8    (0=tui, 1=native_gui, 2=web)
  color_depth:    u8    (0=mono, 1=256color, 2=rgb)
  unicode_width:  u8    (0=wcwidth, 1=unicode_15)
  image_support:  u8    (0=none, 1=kitty, 2=sixel, 3=native)
  float_support:  u8    (0=emulated, 1=native)
  text_rendering: u8    (0=monospace, 1=proportional)

Total size: 13 bytes.

Frontends that send the short 5-byte ready format are assumed to have default capabilities: {tui, rgb, wcwidth, none, emulated, monospace}.

0x05 capabilities_updated

Sent after the initial ready event when the frontend detects additional capabilities asynchronously (e.g., a TUI terminal responds to capability queries like DA1 after startup).

opcode:         u8  = 0x05
caps_version:   u8    (currently 1)
caps_len:       u8    (length of remaining fields)
frontend_type:  u8
color_depth:    u8
unicode_width:  u8
image_support:  u8
float_support:  u8
text_rendering: u8

Total size: 9 bytes.

Behavior: The BEAM updates its stored capabilities for this frontend. No re-render is triggered; the updated caps take effect on the next frame.

Capability Fields

FieldValuesDescription
frontend_type0=tui, 1=native_gui, 2=webType of rendering surface
color_depth0=mono, 1=256color, 2=rgbColor support level
unicode_width0=wcwidth, 1=unicode_15Character width calculation method
image_support0=none, 1=kitty, 2=sixel, 3=nativeInline image protocol
float_support0=emulated, 1=nativeFloating window support
text_rendering0=monospace, 1=proportionalFont rendering model

Implementation Notes

The TUI backend sends ready with default capabilities immediately at startup, then sends capabilities_updated once libvaxis finishes its async terminal capability detection (triggered by the DA1 response). The GUI backend sends ready with full native capabilities upfront since there is no detection delay.

Layout Regions

Regions express layout structure so frontends can map editor areas to their native abstraction: virtual viewports with clipping (TUI), NSView hierarchy (AppKit), GtkWidget containers (GTK4).

Region ID 0 is the implicit root region (the entire screen). All draw commands before any set_active_region target the root.

0x14 define_region

Create or update a layout region.

opcode:    u8  = 0x14
id:        u16           region identifier (must be > 0)
parent_id: u16           parent region (0 = root)
role:      u8            semantic role (see table below)
row:       u16           top-left row relative to parent
col:       u16           top-left column relative to parent
width:     u16           width in columns
height:    u16           height in rows
z_order:   u8            stacking order (higher = on top)

Total size: 15 bytes.

Region roles:

ValueRoleDescription
0editorMain editor viewport
1modelineStatus line
2minibufferCommand/message input area
3gutterLine numbers and signs
4popupFloating completion, which-key, etc.
5panelSide panel (file tree, etc.)
6borderWindow split borders

0x18 clear_region

Clear all cells within a region to blank.

opcode: u8  = 0x18
id:     u16           region to clear

Total size: 3 bytes.

0x19 destroy_region

Remove a region and clear its area.

opcode: u8  = 0x19
id:     u16           region to destroy

Total size: 3 bytes.

If the destroyed region was the active region, the frontend resets to the root region.

0x1A set_active_region

Route subsequent draw_text commands to a region.

opcode: u8  = 0x1A
id:     u16           region to activate (0 = root)

Total size: 3 bytes.

Behavior: After this command, draw_text coordinates are relative to the active region's origin. The frontend offsets and clips draw commands to stay within the region bounds. The active region resets to root on clear.

TUI Implementation

The TUI renderer maintains a hash map of regions. When set_active_region is called, the renderer adds the region's row/col offset to every subsequent draw_text and clips at the region boundary. clear_region blanks only the cells within the region's bounds.

GUI Implementation

Native GUI frontends should map regions to native view objects: define_region creates a view, destroy_region removes it, set_active_region targets draw commands into the view's coordinate space. The role field provides semantic hints for styling and layout behavior.

Text Measurement

For proportional-font frontends, the BEAM cannot compute display widths on its own. A request/response pair lets the BEAM query the frontend for rendered text width.

0x27 measure_text (BEAM → Frontend)

Request the display width of a text segment.

opcode:     u8  = 0x27
request_id: u32           unique request identifier
text_len:   u16           byte length of text
text:       [text_len]u8  UTF-8 encoded text to measure

Total size: 7 + text_len bytes.

0x35 text_width (Frontend → BEAM)

Response with the measured display width.

opcode:     u8  = 0x35
request_id: u32           matches the measure_text request
width:      u16           display width in columns (monospace) or pixels (GUI)

Total size: 7 bytes.

Strategy

The BEAM uses a two-tier approach based on the frontend's text_rendering capability:

  • Monospace frontends (TUI, monospace GUI): The BEAM uses its own UAX #11 width tables. measure_text is available for spot-checking alignment on startup but is not used per-keystroke.
  • Proportional frontends (native GUI): The BEAM queries the frontend via measure_text for layout-critical computations (cursor placement, column alignment, menu truncation). Results are cached keyed by text content. The cache is flushed when the frontend sends capabilities_updated (e.g., after a font size change).

Incremental Content Sync

0x26 edit_buffer

Send compact edit deltas instead of full file content. Enables tree-sitter incremental parsing (sub-millisecond reparse for single-character edits).

opcode:     u8  = 0x26
version:    u32           buffer version counter
edit_count: u16           number of edits

per edit:
  start_byte:     u32     byte offset where the edit begins
  old_end_byte:   u32     byte offset where the old text ends
  new_end_byte:   u32     byte offset where the new text ends
  start_row:      u32     row at start_byte
  start_col:      u32     column at start_byte
  old_end_row:    u32     row at old_end_byte
  old_end_col:    u32     column at old_end_byte
  new_end_row:    u32     row at new_end_byte
  new_end_col:    u32     column at new_end_byte
  text_len:       u32     byte length of inserted text
  text:           [text_len]u8  the inserted text (empty for deletions)

Total size: 7 + (40 + text_len) per edit.

Behavior: The parser applies each edit to its stored copy of the source, calls ts_tree_edit() on the existing parse tree, then performs an incremental reparse. Unchanged subtrees are reused, making the reparse cost proportional to the edit size rather than the file size.

Fallback: parse_buffer (opcode 0x21) remains available for initial file load, language switches, and error recovery. If incremental parsing fails, the parser falls back to full reparse automatically.

Edit semantics: Each edit replaces the byte range [start_byte, old_end_byte) with the inserted text. For insertions, old_end_byte == start_byte. For deletions, text_len == 0. The row/col positions are needed by tree-sitter's TSInputEdit for invalidating the correct tree nodes.


GUI Chrome Protocol

Native GUI frontends (SwiftUI, GTK4) receive additional structured data opcodes for chrome elements like tab bars, file trees, status bars, and popups. These opcodes live in the 0x70-0x78 range and are sent only when the frontend reports frontend_type = native_gui in its capabilities.

See GUI_PROTOCOL.md for the complete specification of GUI chrome opcodes, gui_action input events, theme color slots, and the behavioral contract for GUI frontends.


Future: Buffer Fork UI

When buffer forking lands, the protocol may need new opcodes for fork-related UI elements: fork status indicators in the modeline, merge conflict region rendering, or a fork branch picker. These will be additive (new opcodes, no changes to existing ones). Frontend implementors can safely ignore unknown opcodes by reading and discarding the payload based on the length prefix.