DECTABSR —— 制表位报告(`CSI 2 $ w`)
让终端以 DCS 报告形式输出当前水平制表位。补全往返链:HTS(esc-hts)设置、TBC(csi-tbc)清除、DECTABSR 查询当前集合。
字节形式
涵盖所有常见的字符串字面量写法,方便正反查找。
\x1b[2$w\033[2$w\e[2$wESC [ 2 $ w1b 5b 32 24 77说明
DECTABSR —— *制表位报告* —— DECRQPSR(请求陈现状态报告)的一种,请终端以 DCS 形式列出当前水平制表位。它是制表位往返链的第三只脚:
- 在当前列设置制表位:\x1bH(HTS,见 esc-hts)。
- 清除:\x1b[g(当前列)/ \x1b[3g(全部)(TBC,见 csi-tbc)。
- 查询当前集合:\x1b[2$w(DECTABSR —— 本条目)。
请求形态:\x1b[2$w(2 是 Ps 选择子 —— 1 是 DECCIR 光标信息报告、2 是 DECTABSR)。$ 中间字节 + w 末字节同 DECRQPSR 约定。
回复形态(DECRPTAB):\x1bP2$u<col1>/<col2>/…/<colN>\x1b\\ —— DCS 帧,体为以斜杠分隔的当前制表位列号(1 起算)。示例(xterm 80 列默认 8 列制表):
```
\x1bP2$u9/17/25/33/41/49/57/65/73\x1b\\
```
注意 1 列处无制表位(本即光标 home),斜杠仅分隔不终止。DCS 前缀 \x1bP2$u 回显请求的 2$ 加上 u 表示「unsolicited / response」(类比 DECRQSS 的 $r)。
往返模式(TUI 会话前后保存 / 恢复终端制表状态): ``` 启动: 发 \x1b[2$w -> 收 \x1bP2$u9/17/25/...\x1b\\ 解析:以 '/' 分割体 -> [9, 17, 25, ...] 保存 stops_before 退出: 发 \x1b[3g # TBC 清全部 for col in stops_before: 发 \x1b[<col>G\x1bH # CHA 至 col,HTS 于 col ```
声明 dectabsr 的 terminfo 条目里,tput rs2 做的就是这件事 —— 「重置到已知制表状态」用读 + 还原,而不是傻发 \x1b[?5W(DECST8C —— 设 8 列制表)。
边界:
- 空回复(\x1bP2$u\x1b\\)表示一个制表位都没有 —— 发 \x1b[?5W 或写一串 HTS 还原默认。
- 部分模拟器会把隐含的「行尾」位(列 == 屏宽 + 1)也报出来 —— 从保存列表里剥除。
- 回复默认以 \x1b\\(ST 表示为 ESC + 反斜杠)结束;xterm 在 8 位 C1 模式下(见 decscl-compat-level)使用单字节 \x9c。
覆盖度:xterm = 完整(标准)。WezTerm / mlterm / Konsole = 完整。Ghostty = 部分(回但总是默认 8 列)。Kitty / iTerm2 = 部分(回但某些代码路径漏掉用户设置的位)。Alacritty / Linux console / Windows Terminal / cmd / ConPTY / macOS Terminal / gnome-terminal = 无作用(完全不回 —— 探测务必加 ~100 ms 超时)。
规范出处: DEC VT510 RM (DECTABSR / DECRPTAB) / xterm-ctlseqs (CSI 2 $ w)
示例
# Probe tab stops; read DCS reply with timeout.\nprintf '\\033[2$w'\nIFS= read -rs -d '\\\\' -t 0.1 reply </dev/tty 2>/dev/null\necho \"DECTABSR: ${reply}\" # expect: \\x1bP2$u9/17/25/...import sys, re\n# Parse the slash-separated tab list from a DECRPTAB reply.\nreply = '\\x1bP2$u9/17/25/33/41/49/57/65/73\\x1b\\\\'\nm = re.search(r'\\x1bP2\\$u([\\d/]*)\\x1b\\\\', reply)\nstops = [int(c) for c in m.group(1).split('/') if c] if m else []\nprint(stops)// Save-then-restore tab stops across a TUI's runtime.\nfmt.Print(\"\\x1b[2$w\") // request DECTABSR\n// ... read DCS reply, parse to []int ...\n// On exit: TBC all, then HTS at each saved col\nfmt.Print(\"\\x1b[3g\") // TBC 3 clears all\nfor _, col := range saved {\n fmt.Printf(\"\\x1b[%dG\\x1bH\", col)\n}// Detect 'no tab stops at all' (empty body).\nfunction parseDECTABSR(buf) {\n const m = /\\x1bP2\\$u([^\\x1b]*)\\x1b\\\\/.exec(buf);\n if (!m) return null;\n return m[1].split('/').filter(s => s.length).map(Number);\n}\nconsole.log(parseDECTABSR('\\x1bP2$u\\x1b\\\\')); // [] — no tabs at all/* Skeleton: probe + parse (cbreak stdin, timed read). */\nprintf(\"\\x1b[2$w\"); fflush(stdout);\n/* read until ST (0x1b 0x5c). then strtok on '/' between \"$u\" and ST. */终端支持
- xterm
- 支持
- Linux console (fbcon)
- 不支持
- macOS Terminal.app
- 不支持
- iTerm2
- 部分
- Windows Terminal
- 不支持
- cmd.exe / ConPTY
- 不支持
- kitty
- 部分
- alacritty
- 不支持
- WezTerm
- 支持
- Ghostty
- 部分
- GNOME Terminal
- 不支持
- Konsole
- 支持
- tmux
- 支持
- GNU screen
- 支持
| xterm | Linux console (fbcon) | macOS Terminal.app | iTerm2 | Windows Terminal | cmd.exe / ConPTY | kitty | alacritty | WezTerm | Ghostty | GNOME Terminal | Konsole | tmux | GNU screen |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 支持 | 不支持 | 不支持 | 部分 | 不支持 | 不支持 | 部分 | 不支持 | 支持 | 部分 | 不支持 | 支持 | 支持 | 支持 |