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

Copy Markdown View Source

Pure scanner for a DSR-6 cursor position report (CPR — CSI Pr ; Pc R, the reply a terminal sends to CSI 6n) inside a raw tty input stream.

Extracted from Raxol.Terminal.InlineDriver.probe_cursor/2's receive loop so the byte-boundary contract is unit-testable with zero process/device setup:

  • The CPR itself is consumed — split out of the stream so it can never reach Raxol.Terminal.ANSI.InputParser (which would decode a row-1 reply, \e[1;<n>R, as a modified F3 keypress — the classic DSR/F3 wire collision — and deliver a phantom key event to the app).
  • Every other byte is preserved in arrival order — real keystrokes interleaved with (or split around) the reply are handed back for normal dispatch, never dropped (the same leak-free rule the T1 capability probe's {:leak_free, _} action enforces).
  • The held-back tail is bounded — at most 12 bytes (the longest possible CPR is \e[9999;9999R, 12 bytes) are ever kept waiting for a continuation chunk, so a flooding device cannot grow the probe buffer: everything already scanned past is flushed forward immediately.

The scan is byte-oriented (no /u regex, no String walking): raw tty input is untrusted binary and may contain invalid UTF-8, 8-bit C1 controls, or anything else — none of which may crash the scanner.

Summary

Types

1-based {row, col} as reported by the terminal.

Functions

Scans buffer for a complete CPR.

Types

position()

@type position() :: {pos_integer(), pos_integer()}

1-based {row, col} as reported by the terminal.

Functions

scan(buffer)

@spec scan(binary()) ::
  {:reply, position(), binary(), binary()} | {:pending, binary(), binary()}

Scans buffer for a complete CPR.

  • {:reply, {row, col}, leading, trailing} — a CPR was found. leading/trailing are the non-CPR bytes before/after it, in arrival order, for normal input dispatch. The CPR bytes themselves appear in neither.
  • {:pending, forward, keep} — no complete CPR yet. forward is safe to dispatch as ordinary input NOW; keep (at most 12 bytes, possibly empty) is the tail that could still be the start of a CPR split across chunks — prepend it to the next chunk and scan again (or dispatch it as ordinary input once the probe deadline lapses).

A 0 row or column (\e[0;0R — not a screen position any 1-based terminal can report) is rejected as a reply and forwarded as ordinary input; scanning continues past it.