Skip to content

Commit 67e4c32

Browse files
author
SqlRush
committed
Parse terminal ESC charset selections
1 parent 06729bd commit 67e4c32

8 files changed

Lines changed: 25 additions & 11 deletions

docs/cc-100-roadmap.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,8 @@ M7 补充:terminal CSI parser 现在把带参数的 `CSI Pl;Pr s` 解析成 le
316316

317317
M7 补充:terminal CSI parser 现在识别带 intermediate space 的 `CSI Ps SP @` / `CSI Ps SP A` scroll-left/right 序列,避免误解析成 insert-characters 或 cursor-up。
318318

319+
M7 补充:terminal ESC parser 现在把 charset selection (`ESC ( B` / `ESC ) 0`) 解析成结构化 charset action,并在 terminal parser 可见文本管线中消费,避免常见终端 charset 选择序列残留为 unknown。
320+
319321
M7 补充:terminal CSI parser 现在把 DEC selective erase `CSI ? Ps J` / `CSI ? Ps K` 标记为 selective display/line erase,和普通 ED/EL 区分开。
320322

321323
M7 补充:terminal CSI parser 现在把 ECMA `CSI Ps N` / `CSI Ps O` 解析成 erase-in-field / erase-in-area action,覆盖 to-end/to-start/all 三种 region。
@@ -948,7 +950,7 @@ M7 补充:prompt history `LogEntry` 读取现在接受 `sessionID`/`session`/`
948950

949951
本轮补充:terminal CSI parser 现在把 DEC `?2026h/l` synchronized output mode 解析成 mode action,和 renderer/snapshot 的 BSU/ESU 包裹路径闭环。
950952

951-
本轮补充:terminal ESC helper 增加官方 ESC final-byte 判定和 `ParseESCSequence`/`ParseESCContent`,覆盖 RIS reset、DECSC/DECRC save/restore、IND/RI/NEL cursor action、HTS/charset ignored 分支和 unknown sequence fallback。
953+
本轮补充:terminal ESC helper 增加官方 ESC final-byte 判定和 `ParseESCSequence`/`ParseESCContent`,覆盖 RIS reset、DECSC/DECRC save/restore、IND/RI/NEL cursor action、HTScharset selection 和 unknown sequence fallback。
952954

953955
本轮补充:terminal SGR helper 增加官方 `TextStyle` 状态解析基础,覆盖 reset、bold/dim/italic/underline/blink/inverse/hidden/strikethrough/overline、普通/亮色命名色、256 色、RGB 色、underline color、分号和冒号参数格式;完整 ANSI parser/render style parity 仍继续推进。
954956

docs/claude-code-go-rewrite-plan.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ test/parity/ # golden tests against TS/official behavior
453453
- 本轮补充:terminal CSI parser 把 DEC `?69h/l` left/right margin mode 解析成结构化 `leftRightMarginMode` mode action,补齐 scroll-region 相邻的 margin 状态序列。
454454
- 本轮补充:terminal CSI parser 现在把带参数的 `CSI Pl;Pr s` 解析成 left/right horizontal margin region action,同时保留无参数 `CSI s` save-cursor 语义,和 DEC `?69h/l` margin mode 闭环。
455455
- 本轮补充:terminal CSI parser 现在识别带 intermediate space 的 `CSI Ps SP @` / `CSI Ps SP A` scroll-left/right 序列,避免误解析成 insert-characters 或 cursor-up。
456+
- 本轮补充:terminal ESC parser 现在把 charset selection (`ESC ( B` / `ESC ) 0`) 解析成结构化 charset action,并在 terminal parser 可见文本管线中消费,避免常见终端 charset 选择序列残留为 unknown。
456457
- 本轮补充:terminal CSI parser 现在把 DEC selective erase `CSI ? Ps J` / `CSI ? Ps K` 标记为 selective display/line erase,和普通 ED/EL 区分开。
457458
- 本轮补充:terminal CSI parser 现在把 ECMA `CSI Ps N` / `CSI Ps O` 解析成 erase-in-field / erase-in-area action,覆盖 to-end/to-start/all 三种 region。
458459
- 本轮补充:terminal CSI parser 把 DEC insert/delete columns (`CSI Ps ' }` / `CSI Ps ' ~`) 归入 edit action,避免列编辑控制序列落入 unknown fallback。
@@ -547,7 +548,7 @@ test/parity/ # golden tests against TS/official behavior
547548
- 本轮补充:terminal CSI parser 现在也把 DEC `?1001h/l` highlight、`?1005h/l` UTF-8 mouse mode、`?1015h/l` urxvt numeric mouse mode 和 xterm `?1016h/l` SGR-pixels mouse mode 解析成 mouseTracking action,和输入侧 numeric/SGR mouse 兼容面闭环。
548549
- 本轮补充:terminal CSI parser 现在把 xterm `?1007h/l` alternate scroll mode 解析成独立 mode action,避免 alternate-screen wheel 兼容序列落入 unknown。
549550
- 本轮补充:terminal CSI parser 现在把 DEC `?2026h/l` synchronized output mode 解析成 mode action,和 renderer/snapshot 的 BSU/ESU 包裹路径闭环。
550-
- 本轮补充:terminal ESC helper 增加官方 ESC final-byte 判定和 `ParseESCSequence`/`ParseESCContent`,覆盖 RIS reset、DECSC/DECRC save/restore、IND/RI/NEL cursor action、HTS/charset ignored 分支和 unknown sequence fallback。
551+
- 本轮补充:terminal ESC helper 增加官方 ESC final-byte 判定和 `ParseESCSequence`/`ParseESCContent`,覆盖 RIS reset、DECSC/DECRC save/restore、IND/RI/NEL cursor action、HTScharset selection 和 unknown sequence fallback。
551552
- 本轮补充:terminal SGR helper 增加官方 `TextStyle` 状态解析基础,覆盖 reset、bold/dim/italic/underline/blink/inverse/hidden/strikethrough/overline、普通/亮色命名色、256 色、RGB 色、underline color、分号和冒号参数格式;完整 ANSI parser/render style 应用仍继续推进。
552553
- 本轮补充:terminal sequence dispatcher 增加官方 `identifySequence` 等价分流,按 CSI/OSC/ESC/SS3/unknown 识别并委派现有 parser,SS3 按官方 output parser 作为 unknown action;streaming tokenizer 和 text grapheme action 仍继续推进。
553554
- 本轮补充:terminal tokenizer 增加官方 streaming escape boundary 状态机,支持跨 chunk buffer/flush/reset、CSI/SS3/OSC/DCS/APC 序列边界、OSC BEL/ST terminator、ESC intermediate charset 序列、invalid CSI text fallback 和 opt-in X10 mouse payload 消费;完整 text grapheme action parser 仍继续推进。

docs/first-second-parity-audit.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,7 @@ M7 progress now includes:
507507
- `internal/tui`: key parsing and configurable keybindings now expose `ctrl+space` from raw NUL and Kitty/CSI-u `0u`, `32;5u`, `64;5u`, and `50;5u` forms, with `ctrl-space`/`ctrl-@`/`ctrl-2` and compact `cSpace`/`c@`/`c2` aliases available for user keymaps and scripts.
508508
- `internal/tui`: key parsing and configurable keybindings now expose Ctrl-backslash, Ctrl-], Ctrl-^/Ctrl-6, and Ctrl-_/Ctrl-/ from raw terminal control bytes and Kitty/CSI-u forms, with symbol, named, and compact aliases available for user keymaps and scripts.
509509
- `internal/tui`: key parsing and configurable keybindings now expose F1-F12 from common SS3, legacy Linux-console, tilde CSI, and modified CSI function-key sequences, with `f1`, `function-key-1`, and `fn1` style aliases available for user keymaps and scripts.
510+
- `internal/tui`: terminal ESC parsing now recognizes charset selection sequences such as `ESC ( B` and `ESC ) 0` as structured charset actions and consumes them in visible-text parsing instead of surfacing them as unknown terminal output.
510511
- `internal/contracts`/`internal/session`: image content blocks now normalize source aliases such as `mediaType`/`mimeType`/`contentType` and `base64`/`payload`, including top-level image block fields, into canonical `ImageSource` values during transcript resume.
511512
- `internal/tui`: interaction script single-step JSON now unwraps `step`/`scriptStep`/`interactionStep`/`record`/`entry`/`item`/`event` objects in arrays, JSONL records, and wrapper-object step lists.
512513
- `internal/tui`: interaction script single-step JSON now also unwraps JSON:API/resource-style `resource`/`node`/`attributes`/`properties`/`attrs` step items, so API fixture arrays and JSONL records can keep step resource envelopes.

internal/tui/terminal_esc.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,16 @@ type ESCActionType string
2222
const (
2323
ESCActionCursor ESCActionType = "cursor"
2424
ESCActionReset ESCActionType = "reset"
25+
ESCActionCharset ESCActionType = "charset"
2526
ESCActionUnknown ESCActionType = "unknown"
2627
)
2728

2829
type ESCAction struct {
29-
Type ESCActionType
30-
Cursor CSICursorAction
31-
Sequence string
30+
Type ESCActionType
31+
Cursor CSICursorAction
32+
CharsetSlot byte
33+
CharsetDesignator byte
34+
Sequence string
3235
}
3336

3437
func IsESCFinal(b byte) bool {
@@ -63,7 +66,7 @@ func ParseESCContent(chars string) (ESCAction, bool) {
6366
return ESCAction{Type: ESCActionCursor, Cursor: CSICursorAction{Type: CSICursorActionTabSet}}, true
6467
case ESCCharsetSelectLeft, ESCCharsetSelectRight:
6568
if len(chars) >= 2 {
66-
return ESCAction{}, false
69+
return ESCAction{Type: ESCActionCharset, CharsetSlot: chars[0], CharsetDesignator: chars[1], Sequence: ESCPrefix + chars}, true
6770
}
6871
}
6972
return ESCAction{Type: ESCActionUnknown, Sequence: ESCPrefix + chars}, true

internal/tui/terminal_parser.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,8 @@ func (p *TerminalParser) processSequence(sequence string) (TerminalAction, bool)
361361
case ESCActionReset:
362362
p.Reset()
363363
return TerminalAction{Type: TerminalActionReset}, true
364+
case ESCActionCharset:
365+
return TerminalAction{}, false
364366
default:
365367
return TerminalAction{Type: TerminalActionUnknown, Sequence: action.ESC.Sequence}, true
366368
}

internal/tui/terminal_parser_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,11 @@ func TestTerminalVisibleTextUsesParserAndPreservesRawBell(t *testing.T) {
4646
"d" +
4747
"\x1b^pm\x1b\\" +
4848
"e" +
49+
"\x1b(B" +
4950
"\x1bXsos\x07" +
50-
"f\x07"
51+
"f" +
52+
"\x1b)0" +
53+
"\x07"
5154
if got := TerminalVisibleText(input); got != "aredbcdef\x07" {
5255
t.Fatalf("visible = %q", got)
5356
}

internal/tui/terminal_sequence_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,9 @@ func TestParseTerminalSequenceDispatchesActions(t *testing.T) {
7575
t.Fatalf("unknown esc dispatch = %#v ok=%v", unknown, ok)
7676
}
7777

78-
if ignored, ok := ParseTerminalSequence("\x1b(B"); ok || ignored.Type != "" {
79-
t.Fatalf("ignored esc dispatch = %#v ok=%v", ignored, ok)
78+
charset, ok := ParseTerminalSequence("\x1b(B")
79+
if !ok || charset.Type != TerminalSequenceESC || charset.ESC.Type != ESCActionCharset || charset.ESC.CharsetSlot != '(' || charset.ESC.CharsetDesignator != 'B' {
80+
t.Fatalf("charset esc dispatch = %#v ok=%v", charset, ok)
8081
}
8182
if text, ok := ParseTerminalSequence("plain"); ok || text.Type != "" {
8283
t.Fatalf("plain dispatch = %#v ok=%v", text, ok)

internal/tui/tui_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5743,8 +5743,9 @@ func TestParseESCSequenceActions(t *testing.T) {
57435743
if action, ok := ParseESCContent(""); ok || action.Type != "" {
57445744
t.Fatalf("empty esc content parsed = %#v", action)
57455745
}
5746-
if action, ok := ParseESCSequence("\x1b(B"); ok || action.Type != "" {
5747-
t.Fatalf("charset selection should be ignored = %#v", action)
5746+
charset, ok := ParseESCSequence("\x1b(B")
5747+
if !ok || charset.Type != ESCActionCharset || charset.CharsetSlot != '(' || charset.CharsetDesignator != 'B' {
5748+
t.Fatalf("charset selection = %#v ok=%v", charset, ok)
57485749
}
57495750

57505751
reset, ok := ParseESCSequence(ESCResetSequence)

0 commit comments

Comments
 (0)