Raxol.Terminal.InlineDriver.Sequences (Raxol Terminal v2.6.1)

Copy Markdown View Source

Pure ANSI byte-sequence constants + builders for the inline driver profile (unit T2d).

Kept separate from the GenServer (Raxol.Terminal.InlineDriver) so the Tier A suite can assert on exact bytes without a process, a device, or the OS tty -- this module has no side effects at all.

The canonical teardown order (pinned, load-bearing)

Ordering is not cosmetic: the negative suite proves each swap strands the cursor or echoes escapes into the restored shell.

  1. input modes off (modes_off/0) -- while raw mode is still on, so the bytes are never echoed back as if typed.
  2. release the scroll region (release_region/0, CSI r) -- BEFORE any absolute cursor move, else the move clamps into the old region (INV-1).
  3. autowrap + cursor restore (autowrap_cursor/0) -- before the final prompt handoff (INV-2).
  4. cursor to bottom + fresh line (move_bottom/1) -- unclamped now that the region is gone.
  5. stty restore -- LAST, after every escape write, else cooked mode line-processes the escapes as garbage (INV-3). This step is OS-level and lives in Raxol.Terminal.InlineDriver.emit_teardown/2, not here.

Summary

Functions

Step 3: re-enable autowrap (DECAWM) and show the cursor.

DSR-6 (CSI 6n): ask the terminal where its cursor is. The terminal replies on the INPUT stream with a CPR (CSI row ; col R), which Raxol.Terminal.InlineDriver.probe_cursor/2 consumes via Raxol.Terminal.InlineDriver.CursorReport — the reply is never allowed to reach the app's input path. Emitted only by that opt-in probe, never by init_bytes/0: a probe writes bytes to the device, so callers must ask for it.

Startup bytes. Deliberately contains no \e[?1049h -- the inline profile never owns the alternate screen (LC-P-NOALT, T2d's headline invariant). Resets stray mouse modes, then enables focus reporting and bracketed paste (both reversed by modes_off/0 at teardown).

Step 1: disable bracketed paste, focus reporting, and all mouse modes.

Enable click reporting (SGR encoding, press/release only). Written by the driver AFTER init_bytes/0 when its mouse?: option is set; the teardown's modes_off/0 disables it unconditionally either way.

Step 4: absolute-move to (rows, 1) (park_bottom/1) then CRLF. Safe to be unclamped because the scroll region is already gone by this point (step 2).

The bare absolute park at (rows, 1) -- the shared "magic byte" builder both move_bottom/1 (park + fresh-line CRLF, for the shell handoff) and suspend_bytes/1 (park only, for the editor handoff) compose from. Safe to be unclamped only AFTER the region release (step 2).

Step 2: CSI r -- reset the scroll region to the full screen.

Suspend bytes -- the editor-handoff sibling of teardown_bytes/1 (an external $EDITOR process, not process exit, is about to own the tty). Steps 1-3 of the canonical teardown order, verbatim (modes_off/0, release_region/0, autowrap_cursor/0), plus an absolute cursor park at (rows, 1) -- WITHOUT step 4's trailing CRLF.

Steps 1-4 concatenated in canonical order. Step 5 (stty restore) is OS-level and not modeled here -- see Raxol.Terminal.InlineDriver.emit_teardown/2.

Functions

autowrap_cursor()

@spec autowrap_cursor() :: binary()

Step 3: re-enable autowrap (DECAWM) and show the cursor.

cursor_position_request()

@spec cursor_position_request() :: binary()

DSR-6 (CSI 6n): ask the terminal where its cursor is. The terminal replies on the INPUT stream with a CPR (CSI row ; col R), which Raxol.Terminal.InlineDriver.probe_cursor/2 consumes via Raxol.Terminal.InlineDriver.CursorReport — the reply is never allowed to reach the app's input path. Emitted only by that opt-in probe, never by init_bytes/0: a probe writes bytes to the device, so callers must ask for it.

init_bytes()

@spec init_bytes() :: binary()

Startup bytes. Deliberately contains no \e[?1049h -- the inline profile never owns the alternate screen (LC-P-NOALT, T2d's headline invariant). Resets stray mouse modes, then enables focus reporting and bracketed paste (both reversed by modes_off/0 at teardown).

modes_off()

@spec modes_off() :: binary()

Step 1: disable bracketed paste, focus reporting, and all mouse modes.

mouse_on()

@spec mouse_on() :: binary()

Enable click reporting (SGR encoding, press/release only). Written by the driver AFTER init_bytes/0 when its mouse?: option is set; the teardown's modes_off/0 disables it unconditionally either way.

move_bottom(rows)

@spec move_bottom(pos_integer()) :: binary()

Step 4: absolute-move to (rows, 1) (park_bottom/1) then CRLF. Safe to be unclamped because the scroll region is already gone by this point (step 2).

park_bottom(rows)

@spec park_bottom(pos_integer()) :: binary()

The bare absolute park at (rows, 1) -- the shared "magic byte" builder both move_bottom/1 (park + fresh-line CRLF, for the shell handoff) and suspend_bytes/1 (park only, for the editor handoff) compose from. Safe to be unclamped only AFTER the region release (step 2).

release_region()

@spec release_region() :: binary()

Step 2: CSI r -- reset the scroll region to the full screen.

suspend_bytes(rows)

@spec suspend_bytes(pos_integer()) :: binary()

Suspend bytes -- the editor-handoff sibling of teardown_bytes/1 (an external $EDITOR process, not process exit, is about to own the tty). Steps 1-3 of the canonical teardown order, verbatim (modes_off/0, release_region/0, autowrap_cursor/0), plus an absolute cursor park at (rows, 1) -- WITHOUT step 4's trailing CRLF.

Why steps 1-3 apply unchanged

Same reasoning as the teardown order: modes must go off WHILE STILL RAW (else the escapes echo back as if typed), and CSI r must precede any absolute cursor move (else the move clamps into the still-active old region, INV-1).

Why this DROPS step 4's trailing CRLF

move_bottom/1's CRLF exists to hand the SHELL a fresh line after this process exits for good -- the shell prompt that appears next expects to start at column 1 of a blank row. An editor handoff is not that: the region has just been released, so a CRLF here would scroll the FULL (now region-free) screen up by one line, smearing whatever was on the bottom row (a stale footer) into the row above it -- and that row is inside the just-released history area, which cannot be re-addressed once scrolled (the seal-time-only invariant). The bare park (CSI rows;1 H, no \r\n) leaves the cursor at the right row without scrolling anything, which is exactly what the editor needs: it will clear/redraw its own screen (or, for a non-alt-screen editor, simply start typing from wherever the cursor already is) without this driver having pre-scrolled a footer row into unreachable history first.

Never emits \e[2J/\e[3J -- same substrate law as every other sequence builder in this module.

teardown_bytes(rows)

@spec teardown_bytes(pos_integer()) :: binary()

Steps 1-4 concatenated in canonical order. Step 5 (stty restore) is OS-level and not modeled here -- see Raxol.Terminal.InlineDriver.emit_teardown/2.