[GH-ISSUE #644] stdin-buffer incorrectly splits double-escape sequences (Option+Arrow on macOS) #173

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

Originally created by @pedropombeiro on GitHub (Feb 7, 2026).
Original GitHub issue: https://github.com/anomalyco/opentui/issues/644

Description

When using Option+Arrow keys on macOS terminals (iTerm2, etc.), the double-escape sequence \x1b\x1b[D (Option+Left) is incorrectly split by the stdin buffer, causing [D to be printed as literal text instead of being recognized as a key event.

Environment

  • Terminal: iTerm2 (with "Left Option key" set to "Esc+")
  • OS: macOS
  • Terminal sends: \x1b\x1b[D for Option+Left (verified with cat -v showing ^[^[[D)

Root Cause

In packages/core/src/lib/stdin-buffer.ts, the isCompleteSequence function doesn't handle the case where afterEsc starts with another ESC character (the double-escape pattern for meta/option modified keys).

Current flow for input \x1b\x1b[D:

  1. Function sees sequence starts with ESC (\x1b)
  2. afterEsc = \x1b[D
  3. Checks: afterEsc.startsWith("[") → false (it starts with \x1b)
  4. Checks: afterEsc.startsWith("]") → false
  5. Checks: afterEsc.startsWith("O") → false
  6. Checks: afterEsc.length === 1 → false (length is 3)
  7. Falls through to return "complete" on line 74

This causes extractCompleteSequences to emit:

  • \x1b\x1b as one "complete" sequence
  • [D as literal text (the bug!)

Expected Behavior

The sequence \x1b\x1b[D should be kept together and recognized as Option/Meta + Left Arrow, as the parser in parse.keypress.ts already has logic to handle this (lines 331-337).

Proposed Fix

Add handling in isCompleteSequence for when afterEsc starts with ESC:

// Double-escape sequences: ESC ESC [...] for meta/option modified keys
if (afterEsc.startsWith("\u001b")) {
  // Recursively check if the rest is a complete escape sequence
  const innerResult = isCompleteSequence(afterEsc)
  return innerResult
}
Originally created by @pedropombeiro on GitHub (Feb 7, 2026). Original GitHub issue: https://github.com/anomalyco/opentui/issues/644 ## Description When using Option+Arrow keys on macOS terminals (iTerm2, etc.), the double-escape sequence `\x1b\x1b[D` (Option+Left) is incorrectly split by the stdin buffer, causing `[D` to be printed as literal text instead of being recognized as a key event. ## Environment - Terminal: iTerm2 (with "Left Option key" set to "Esc+") - OS: macOS - Terminal sends: `\x1b\x1b[D` for Option+Left (verified with `cat -v` showing `^[^[[D`) ## Root Cause In `packages/core/src/lib/stdin-buffer.ts`, the `isCompleteSequence` function doesn't handle the case where `afterEsc` starts with another ESC character (the double-escape pattern for meta/option modified keys). Current flow for input `\x1b\x1b[D`: 1. Function sees sequence starts with ESC (`\x1b`) 2. `afterEsc` = `\x1b[D` 3. Checks: `afterEsc.startsWith("[")` → false (it starts with `\x1b`) 4. Checks: `afterEsc.startsWith("]")` → false 5. Checks: `afterEsc.startsWith("O")` → false 6. Checks: `afterEsc.length === 1` → false (length is 3) 7. Falls through to `return "complete"` on line 74 This causes `extractCompleteSequences` to emit: - `\x1b\x1b` as one "complete" sequence - `[D` as literal text (the bug!) ## Expected Behavior The sequence `\x1b\x1b[D` should be kept together and recognized as Option/Meta + Left Arrow, as the parser in `parse.keypress.ts` already has logic to handle this (lines 331-337). ## Proposed Fix Add handling in `isCompleteSequence` for when `afterEsc` starts with ESC: ```typescript // Double-escape sequences: ESC ESC [...] for meta/option modified keys if (afterEsc.startsWith("\u001b")) { // Recursively check if the rest is a complete escape sequence const innerResult = isCompleteSequence(afterEsc) return innerResult } ``` ## Related Issues - anomalyco/opencode#2688 - "Opencode freezes when pressing option + left arrow key on MacBook" - anomalyco/opencode#10756 - "OpenCode desktop on macOS doesn't recognize option keys"
kerem closed this issue 2026-03-02 23:45:04 +03:00
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#173
No description provided.