celina/core/escape_sequence_logic

Escape sequence parsing logic (I/O independent)

This module contains shared escape sequence parsing logic used by blocking, non-blocking, and async parsers. It eliminates code duplication by separating parsing logic from I/O operations.

Types

BracketSequenceKind = enum
  BskArrowKey, BskNavigationKey, BskMouseX10, BskMouseSGR, BskNumeric,
  BskFocusIn, BskFocusOut, BskInvalid
Classification of bracket escape sequences after ESC[
EscapeResult = object
  isValid*: bool
  keyEvent*: KeyEvent
Result of escape sequence processing
NumericSequenceKind = enum
  NskSingleDigitWithTilde, NskMultiDigit, NskModifiedKey, NskInvalid
Classification of numeric escape sequences after ESC[digit
PasteEndState = enum
  PesNone,                  ## Not in sequence
  PesEsc,                   ## Saw ESC
  PesBracket,               ## Saw ESC [
  Pes2,                     ## Saw ESC [ 2
  Pes20,                    ## Saw ESC [ 2 0
  Pes201                     ## Saw ESC [ 2 0 1
State machine for detecting paste end sequence ESC[201~

Procs

proc classifyBracketSequence(final: char): BracketSequenceKind {....raises: [],
    tags: [], forbids: [].}
Classify bracket sequence type by character after ESC[
proc classifyNumericSequence(nextChar: char; isValid: bool): NumericSequenceKind {.
    ...raises: [], tags: [], forbids: [].}
Classify numeric sequence type by next character: '~' / digit / ';'
proc escapeKey(): KeyEvent {.inline, ...raises: [], tags: [], forbids: [].}
Create an Escape key event
proc escapeResult(): EscapeResult {.inline, ...raises: [], tags: [], forbids: [].}
Create an EscapeResult with Escape key (fallback for invalid sequences)
proc isPasteEndSequence(d1, d2, d3, final: char): bool {.inline, ...raises: [],
    tags: [], forbids: [].}
Check if sequence is ESC[201~ (paste end)
proc isPasteStartSequence(d1, d2, d3, final: char): bool {.inline, ...raises: [],
    tags: [], forbids: [].}
Check if sequence is ESC[200~ (paste start)
proc processModifiedKeySequence(digit: char; modChar: char; modValid: bool;
                                keyChar: char; keyValid: bool): EscapeResult {.
    ...raises: [], tags: [], forbids: [].}
Process modified key sequences: ESC[1;2A (Shift/Ctrl/Alt + key)
proc processMultiDigitFunctionKey(firstDigit, secondDigit, tilde: char;
                                  isValid: bool): EscapeResult {....raises: [],
    tags: [], forbids: [].}
Process multi-digit function keys: ESC[15~ (F5-F12)
proc processSimpleBracketSequence(final: char; isValid: bool): EscapeResult {.
    ...raises: [], tags: [], forbids: [].}
Process simple bracket sequences: ESC[A/H (arrow/navigation keys)
proc processSingleDigitNumeric(digit: char): EscapeResult {....raises: [],
    tags: [], forbids: [].}
Process single digit numeric sequences: ESC[1~ (Home/Insert/Delete etc)
proc processVT100FunctionKey(ch: char; isValid: bool): EscapeResult {.
    ...raises: [], tags: [], forbids: [].}
Process VT100 function keys: ESC O P/Q/R/S (F1-F4)
proc stepPasteEnd(state: var PasteEndState; pending: var string; ch: char;
                  output: var string): bool {....raises: [], tags: [], forbids: [].}

Advance the paste-end state machine by one byte.

Bytes are buffered in pending while a candidate ESC[201~ sequence is being matched, and flushed into output if the match fails. Returns true when the paste end terminator (ESC[201~) is consumed; the caller should then stop reading. The terminator bytes themselves are not appended to output.

Shared by blocking, non-blocking, and async paste readers - only the byte-source differs between them.