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.
- input modes off (
modes_off/0) -- while raw mode is still on, so the bytes are never echoed back as if typed. - release the scroll region (
release_region/0,CSI r) -- BEFORE any absolute cursor move, else the move clamps into the old region (INV-1). - autowrap + cursor restore (
autowrap_cursor/0) -- before the final prompt handoff (INV-2). - cursor to bottom + fresh line (
move_bottom/1) -- unclamped now that the region is gone. - 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
@spec autowrap_cursor() :: binary()
Step 3: re-enable autowrap (DECAWM) and show the cursor.
@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.
@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).
@spec modes_off() :: binary()
Step 1: disable bracketed paste, focus reporting, and all mouse modes.
@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.
@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).
@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).
@spec release_region() :: binary()
Step 2: CSI r -- reset the scroll region to the full screen.
@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.
@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.