Skip to content

Commit c5ffa59

Browse files
author
SqlRush
committed
Parse CSI horizontal margin sequences
1 parent 296c6a3 commit c5ffa59

5 files changed

Lines changed: 32 additions & 3 deletions

File tree

docs/cc-100-roadmap.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ M7 补充:terminal CSI parser 现在把 DEC `?67h/l` backarrow key mode 解析
222222

223223
M7 补充:terminal CSI parser 现在把 DEC `?69h/l` left/right margin mode 解析成结构化 `leftRightMarginMode` mode action,补齐 scroll-region 相邻的 margin 状态序列。
224224

225+
M7 补充:terminal CSI parser 现在把带参数的 `CSI Pl;Pr s` 解析成 left/right horizontal margin region action,同时保留无参数 `CSI s` save-cursor 语义,和 DEC `?69h/l` margin mode 闭环。
226+
225227
M7 补充:terminal CSI parser 现在把 REP repeat-preceding-character (`CSI b`/`CSI 4b`) 解析成 edit action,visible-text/snapshot pipeline 和 ANSI message wrapping/trim 会按重复次数展开前一个可重复 grapheme。
226228

227229
M7 补充:terminal CSI parser 现在按 ANSI 默认参数解析 scroll-region (`CSI r`/`CSI ;10r`),缺失 top 默认为 1,缺失 bottom 保持为 0 表示 reset/full-height,避免把 reset 误判为单行区域。

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ test/parity/ # golden tests against TS/official behavior
290290
- 本轮补充:terminal CSI parser 把 DEC `?66h/l` application keypad mode 解析成结构化 `applicationKeypad` mode action,补齐 application cursor mode 相邻的输入状态序列。
291291
- 本轮补充:terminal CSI parser 把 DEC `?67h/l` backarrow key mode 解析成结构化 `backarrowKey` mode action,补齐键盘输入状态序列。
292292
- 本轮补充:terminal CSI parser 把 DEC `?69h/l` left/right margin mode 解析成结构化 `leftRightMarginMode` mode action,补齐 scroll-region 相邻的 margin 状态序列。
293+
- 本轮补充:terminal CSI parser 现在把带参数的 `CSI Pl;Pr s` 解析成 left/right horizontal margin region action,同时保留无参数 `CSI s` save-cursor 语义,和 DEC `?69h/l` margin mode 闭环。
293294
- 本轮补充:terminal CSI parser 把 REP repeat-preceding-character (`CSI b`) 归入 edit action,visible-text/snapshot pipeline 和 ANSI message wrapping/trim 会按重复次数展开前一个可重复 grapheme。
294295
- 本轮补充:terminal CSI parser 按 ANSI 默认参数解析 scroll-region (`CSI r`/`CSI ;10r`),缺失 top 默认为 1,缺失 bottom 保持 0 表示 reset/full-height,避免 reset scroll region 被误判为单行区域。
295296
- 本轮补充:terminal CSI parser 把 DECSTR soft reset (`CSI !p`) 归入 reset action,并在 terminal parser 中清理 SGR/link 状态。

docs/first-second-parity-audit.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ M7 progress now includes:
244244
- `internal/tui`: terminal CSI parser now recognizes DEC application keypad mode (`?66h/l`) as a structured `applicationKeypad` mode action.
245245
- `internal/tui`: terminal CSI parser now recognizes DEC backarrow key mode (`?67h/l`) as a structured `backarrowKey` mode action.
246246
- `internal/tui`: terminal CSI parser now recognizes DEC left/right margin mode (`?69h/l`) as a structured `leftRightMarginMode` action.
247+
- `internal/tui`: terminal CSI parsing now distinguishes parameterized `CSI Pl;Pr s` left/right horizontal margin regions from bare `CSI s` save-cursor semantics.
247248
- `internal/tui`: keybinding config, keymap resolution, and interaction script named-key input now accept terminal aliases for `ctrl-h`/`ctrl-i`/`ctrl-j`/`ctrl-m`, `ctrl-[`, and `ctrl-?`, including `control-*` and compact/camel variants.
248249
- `internal/tui`: keybinding config now accepts page navigation aliases such as `pgup`, `pg-up`, `prior`, `pgdn`, `pg-down`, and `next`.
249250
- `internal/tui`: keybinding config and named-key script input now accept DOM-style arrow key aliases such as `arrowLeft`, `arrowRight`, `arrowUp`, `arrowDown`, and modifier-arrow variants.

internal/tui/terminal_csi.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,16 +220,19 @@ type CSIReportAction struct {
220220
type CSIScrollActionType string
221221

222222
const (
223-
CSIScrollActionUp CSIScrollActionType = "up"
224-
CSIScrollActionDown CSIScrollActionType = "down"
225-
CSIScrollActionSetRegion CSIScrollActionType = "setRegion"
223+
CSIScrollActionUp CSIScrollActionType = "up"
224+
CSIScrollActionDown CSIScrollActionType = "down"
225+
CSIScrollActionSetRegion CSIScrollActionType = "setRegion"
226+
CSIScrollActionSetHorizontalRegion CSIScrollActionType = "setHorizontalRegion"
226227
)
227228

228229
type CSIScrollAction struct {
229230
Type CSIScrollActionType
230231
Count int
231232
Top int
232233
Bottom int
234+
Left int
235+
Right int
233236
}
234237

235238
type CSIModeActionType string
@@ -423,6 +426,9 @@ func ParseCSISequence(sequence string) (CSIAction, bool) {
423426
case CSICommandScrollRegion:
424427
return csiScrollRegion(params), true
425428
case CSICommandSaveCursor:
429+
if privateMode == 0 && len(params) > 0 {
430+
return csiHorizontalRegion(params), true
431+
}
426432
return CSIAction{Type: CSIActionCursor, Cursor: CSICursorAction{Type: CSICursorActionSave}}, true
427433
case CSICommandWindowReport:
428434
return csiWindowReport(csiParamDefault(params, 0, 0), privateMode), true
@@ -549,6 +555,18 @@ func csiScrollRegion(params []int) CSIAction {
549555
return CSIAction{Type: CSIActionScroll, Scroll: CSIScrollAction{Type: CSIScrollActionSetRegion, Top: top, Bottom: bottom}}
550556
}
551557

558+
func csiHorizontalRegion(params []int) CSIAction {
559+
left := 1
560+
if len(params) > 0 && params[0] > 0 {
561+
left = params[0]
562+
}
563+
right := 0
564+
if len(params) > 1 && params[1] > 0 {
565+
right = params[1]
566+
}
567+
return CSIAction{Type: CSIActionScroll, Scroll: CSIScrollAction{Type: CSIScrollActionSetHorizontalRegion, Left: left, Right: right}}
568+
}
569+
552570
func csiModeListAction(params []int, private bool, enabled bool) (CSIAction, bool) {
553571
if len(params) <= 1 {
554572
return CSIAction{}, false

internal/tui/tui_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3817,6 +3817,8 @@ func TestParseCSISequenceActions(t *testing.T) {
38173817
{seq: CSISequence(4, 10, "r"), want: CSIScrollAction{Type: CSIScrollActionSetRegion, Top: 4, Bottom: 10}},
38183818
{seq: ResetScrollRegion, want: CSIScrollAction{Type: CSIScrollActionSetRegion, Top: 1}},
38193819
{seq: CSISequence(";10r"), want: CSIScrollAction{Type: CSIScrollActionSetRegion, Top: 1, Bottom: 10}},
3820+
{seq: CSISequence(10, 80, "s"), want: CSIScrollAction{Type: CSIScrollActionSetHorizontalRegion, Left: 10, Right: 80}},
3821+
{seq: CSISequence(";120s"), want: CSIScrollAction{Type: CSIScrollActionSetHorizontalRegion, Left: 1, Right: 120}},
38203822
}
38213823
for _, tc := range scrollCases {
38223824
action, ok := ParseCSISequence(tc.seq)
@@ -3918,6 +3920,11 @@ func TestParseCSISequenceActions(t *testing.T) {
39183920
if len(actions) != 1 || actions[0].Type != TerminalActionMode || len(actions[0].Modes) != 3 || actions[0].Mode != actions[0].Modes[0] {
39193921
t.Fatalf("terminal parser multi mode actions = %#v", actions)
39203922
}
3923+
3924+
actions = parser.Feed(CSISequence(5, 40, "s"))
3925+
if len(actions) != 1 || actions[0].Type != TerminalActionScroll || actions[0].Scroll.Type != CSIScrollActionSetHorizontalRegion || actions[0].Scroll.Left != 5 || actions[0].Scroll.Right != 40 {
3926+
t.Fatalf("terminal parser horizontal region actions = %#v", actions)
3927+
}
39213928
}
39223929

39233930
func TestParseESCSequenceActions(t *testing.T) {

0 commit comments

Comments
 (0)