Skip to content

Commit 3ee074c

Browse files
author
SqlRush
committed
Parse cursor position reports
1 parent 8d73509 commit 3ee074c

5 files changed

Lines changed: 37 additions & 0 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ test/parity/ # golden tests against TS/official behavior
409409
- 本轮补充:terminal CSI parser 接受 DEC private mode `?1047h/l` alternate-screen buffer 和 `?1048h/l` save/restore cursor,复用已有 mode/cursor actions。
410410
- 本轮补充:terminal CSI parser 把 DECREQTPARM terminal-parameters (`CSI x`) 归入 report action,保留 code/private marker。
411411
- 本轮补充:terminal CSI parser 把 DECRQM mode request (`CSI Ps $ p` / `CSI ? Ps $ p`) 归入 report action,保留 mode code 和 DEC private marker。
412+
- 本轮补充:terminal CSI parser 把 CPR cursor-position responses (`CSI row;col R` / DEC private `CSI ? row;col R`) 归入 report action,结构化暴露 row/column 并保持 visible-text stripping。
412413
- 本轮补充:terminal CSI parser 把 xterm window manipulation/report (`CSI t`) 归入 report action,覆盖常见 `CSI 14t`/`CSI 18t` 查询,并把 `CSI 4;height;width t``CSI 8;rows;cols t` 的 pixel/text-area 尺寸参数结构化暴露。
413414
- 本轮补充:terminal CSI parser 把 DECRPM mode status report (`CSI Ps;Ps $ y` / `CSI ? Ps;Ps $ y`) 归入 report action,保留 mode code、status 和 DEC private marker。
414415
- 本轮补充:terminal CSI parser 把 TBC tab-clear (`CSI g`/`CSI 3g`) 归入 cursor action,保留 clear-current/all code。

docs/first-second-parity-audit.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ M7 progress now includes:
323323
- `internal/tui`: terminal CSI parsing now accepts multi-parameter mode set/reset sequences such as `CSI ?1000;1006;2004h` and exposes the full mode list while preserving the first mode for existing single-mode callers.
324324
- `internal/tui`: terminal CSI parser now emits structured edit actions for insert/delete chars and insert/delete lines, plus forward/back tab cursor actions; output `CSI M` is parsed as delete-lines while input tokenization still keeps X10 mouse payload handling separate.
325325
- `internal/tui`: terminal CSI parser now emits report actions for DSR `CSI n`, including device-status, cursor-position, and private-mode unknown reports.
326+
- `internal/tui`: terminal CSI parser now emits report actions for CPR cursor-position responses such as `CSI row;col R` and DEC private `CSI ? row;col R`, preserving row/column metadata while keeping visible text clean.
326327
- `internal/tui`: terminal sequence dispatcher and parser now classify DCS/APC/PM/SOS string-control sequences as `stringControl` actions with payload, terminator, and incomplete-flush state while keeping visible text extraction free of those invisible payloads.
327328
- `internal/tui`: terminal CSI parser now recognizes DEC X10, highlight, UTF-8, urxvt numeric, and xterm SGR-pixels mouse modes (`?9h/l`, `?1001h/l`, `?1005h/l`, `?1015h/l`, `?1016h/l`) as mouse-tracking mode actions.
328329
- `internal/tui`: terminal CSI parser now recognizes xterm alternate scroll mode (`?1007h/l`) as a structured mode action instead of an unknown sequence.

internal/tui/terminal_csi.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const (
4141
CSICommandEraseField byte = 'N'
4242
CSICommandEraseArea byte = 'O'
4343
CSICommandDeleteCharacters byte = 'P'
44+
CSICommandCursorPosReport byte = 'R'
4445
CSICommandEraseCharacters byte = 'X'
4546
CSICommandBackwardTab byte = 'Z'
4647
CSICommandScrollUp byte = 'S'
@@ -235,6 +236,8 @@ type CSIReportAction struct {
235236
Width int
236237
Rows int
237238
Columns int
239+
Row int
240+
Column int
238241
}
239242

240243
type CSIScrollActionType string
@@ -456,6 +459,8 @@ func ParseCSISequence(sequence string) (CSIAction, bool) {
456459
return csiEdit(CSIEditActionInsertChars, p0), true
457460
case CSICommandDeleteCharacters:
458461
return csiEdit(CSIEditActionDeleteChars, p0), true
462+
case CSICommandCursorPosReport:
463+
return csiCursorPositionReport(p0, p1, privateMode), true
459464
case CSICommandInsertLines:
460465
return csiEdit(CSIEditActionInsertLines, p0), true
461466
case CSICommandDeleteLines:
@@ -585,6 +590,18 @@ func csiReport(code int, privateMode byte) CSIAction {
585590
}
586591
}
587592

593+
func csiCursorPositionReport(row int, column int, privateMode byte) CSIAction {
594+
return CSIAction{
595+
Type: CSIActionReport,
596+
Report: CSIReportAction{
597+
Type: CSIReportActionCursorPosition,
598+
PrivateMode: privateMode,
599+
Row: row,
600+
Column: column,
601+
},
602+
}
603+
}
604+
588605
func csiDeviceAttributes(code int, privateMode byte) CSIAction {
589606
return CSIAction{
590607
Type: CSIActionReport,

internal/tui/terminal_parser_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,21 @@ func TestTerminalParserDispatchesWindowResizeReports(t *testing.T) {
461461
}
462462
}
463463

464+
func TestTerminalParserDispatchesCursorPositionReports(t *testing.T) {
465+
parser := NewTerminalParser()
466+
input := "a" + CSISequence(12, 34, "R") + "b"
467+
actions := parser.Feed(input)
468+
if len(actions) != 3 {
469+
t.Fatalf("actions = %#v", actions)
470+
}
471+
if actions[1].Type != TerminalActionReport || actions[1].Report.Type != CSIReportActionCursorPosition || actions[1].Report.Row != 12 || actions[1].Report.Column != 34 {
472+
t.Fatalf("cursor position report action = %#v", actions[1])
473+
}
474+
if got := TerminalVisibleText(input); got != "ab" {
475+
t.Fatalf("visible = %q", got)
476+
}
477+
}
478+
464479
func TestTerminalParserUsesOutputTokenizerForCSIM(t *testing.T) {
465480
parser := NewTerminalParser()
466481
actions := parser.Feed("\x1b[M`rK")

internal/tui/tui_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5279,6 +5279,9 @@ func TestParseCSISequenceActions(t *testing.T) {
52795279
{seq: CSISequence(6, "n"), want: CSIReportAction{Type: CSIReportActionCursorPosition, Code: 6}},
52805280
{seq: CSISequence(0, "n"), want: CSIReportAction{Type: CSIReportActionUnknown, Code: 0}},
52815281
{seq: CSISequence("?25n"), want: CSIReportAction{Type: CSIReportActionUnknown, Code: 25, PrivateMode: '?'}},
5282+
{seq: CSISequence(12, 34, "R"), want: CSIReportAction{Type: CSIReportActionCursorPosition, Row: 12, Column: 34}},
5283+
{seq: CSISequence("0;0R"), want: CSIReportAction{Type: CSIReportActionCursorPosition, Row: 1, Column: 1}},
5284+
{seq: CSISequence("?7;8R"), want: CSIReportAction{Type: CSIReportActionCursorPosition, PrivateMode: '?', Row: 7, Column: 8}},
52825285
{seq: CSISequence("4$p"), want: CSIReportAction{Type: CSIReportActionModeRequest, Code: 4}},
52835286
{seq: CSISequence("?25$p"), want: CSIReportAction{Type: CSIReportActionModeRequest, Code: 25, PrivateMode: '?'}},
52845287
{seq: CSISequence("4;1$y"), want: CSIReportAction{Type: CSIReportActionModeStatus, Code: 4, Status: 1}},

0 commit comments

Comments
 (0)