[GH-ISSUE #758] Unify stdin parsing: single parser for all escape sequence types #209

Open
opened 2026-03-02 23:45:16 +03:00 by kerem · 0 comments
Owner

Originally created by @simonklee on GitHub (Mar 1, 2026).
Original GitHub issue: https://github.com/anomalyco/opentui/issues/758

stdinListener in renderer.ts splits stdin data into two mutually exclusive paths before parsing:

stdin "data" event
  ├─ if handleMouseData(data) → return     [Path A: mouse]
  └─ else → _stdinBuffer.process(data)     [Path B: everything else]

This is an either/or dispatch at the chunk level. If a chunk starts with mouse sequences, the entire chunk is routed to Path A and any trailing non-mouse data (keyboard input, focus events, etc.) is silently dropped. If a chunk starts with non-mouse data, any mouse sequences later in the chunk go to Path B where StdinBuffer will split them out but they won't reach MouseParser.

This is the cause of #748, and other related issues when the OS joins different sequence types into the same read() buffer.

There are at least four independent parsers that don't coordinate:

  1. MouseParser.parseAllMouseEvents(): parses SGR/X10 mouse from raw
    data, only reached via Path A
  2. StdinBuffer.extractCompleteSequences(): generic escape sequence
    splitter that already understands CSI, OSC, DCS, APC, SS3, and even SGR mouse
    boundaries. Only reached via Path B.
  3. inputHandlers chain: capability responses, focus, theme mode handlers
    that pattern-match on sequences after StdinBuffer emits them
  4. TerminalPalette: registers its own stdin.on("data") listener
    directly on stdin, completely outside the normal flow
Originally created by @simonklee on GitHub (Mar 1, 2026). Original GitHub issue: https://github.com/anomalyco/opentui/issues/758 `stdinListener` in `renderer.ts` splits stdin data into two mutually exclusive paths before parsing: ``` stdin "data" event ├─ if handleMouseData(data) → return [Path A: mouse] └─ else → _stdinBuffer.process(data) [Path B: everything else] ``` This is an either/or dispatch at the chunk level. If a chunk starts with mouse sequences, the entire chunk is routed to Path A and any trailing non-mouse data (keyboard input, focus events, etc.) is silently dropped. If a chunk starts with non-mouse data, any mouse sequences later in the chunk go to Path B where `StdinBuffer` will split them out but they won't reach `MouseParser`. This is the cause of #748, and other related issues when the OS joins different sequence types into the same `read()` buffer. There are at least four independent parsers that don't coordinate: 1. `MouseParser.parseAllMouseEvents()`: parses SGR/X10 mouse from raw data, only reached via Path A 2. `StdinBuffer.extractCompleteSequences()`: generic escape sequence splitter that already understands CSI, OSC, DCS, APC, SS3, and even SGR mouse boundaries. Only reached via Path B. 3. `inputHandlers` chain: capability responses, focus, theme mode handlers that pattern-match on sequences after `StdinBuffer` emits them 4. `TerminalPalette`: registers its own `stdin.on("data")` listener directly on stdin, completely outside the normal flow
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/opentui#209
No description provided.