DECTABSR — Tab Stop Report (`CSI 2 $ w`)
Ask the terminal to dump its current horizontal tab stops as a DCS report. Closes the round-trip story: set via HTS (`esc-hts`), clear via TBC (`csi-tbc`), query the current set via DECTABSR.
Byte forms
Every common string-literal form so you can paste-and-search either direction.
\x1b[2$w\033[2$w\e[2$wESC [ 2 $ w1b 5b 32 24 77Description
DECTABSR — *Tab Stop Report* — is a DECRQPSR (Request Presentation State Report) variant that asks the terminal to enumerate its current horizontal tab stops as a DCS reply. It's the third leg of the tab-stop round trip: - **Set** a tab stop at the current column: `\x1bH` (HTS — see `esc-hts`). - **Clear** tabs: `\x1b[g` (this column) / `\x1b[3g` (all) (TBC — see `csi-tbc`). - **Query** current tab stops: `\x1b[2$w` (DECTABSR — this entry). **Request shape**: `\x1b[2$w` (the `2` is the Ps selector — `1` is DECCIR cursor-info-report, `2` is DECTABSR). The `$` intermediate + `w` final mirror the DECRQPSR convention. **Reply shape (DECRPTAB)**: `\x1bP2$u<col1>/<col2>/…/<colN>\x1b\\` — a DCS frame whose body is a slash-separated list of 1-indexed column numbers where tab stops currently live. Example reply (xterm default 8-column tabs on an 80-col screen): ``` \x1bP2$u9/17/25/33/41/49/57/65/73\x1b\\ ``` Note there's no tab stop at column 1 (already cursor-home, no point) and the slashes separate but don't terminate. The DCS prefix `\x1bP2$u` echoes the request's `2$` plus `u` for 'unsolicited' / 'response' (analogous to DECRQSS's `$r` reply). **Round-trip pattern** (save / restore terminal tab state across a TUI session): ``` startup: send \x1b[2$w -> receive \x1bP2$u9/17/25/...\x1b\\ parse: split body on '/' -> [9, 17, 25, ...] save stops_before exit: send \x1b[3g # TBC clear all for col in stops_before: send \x1b[<col>G\x1bH # CHA to col, HTS at col ``` This is what `tput rs2` does on terminfo entries that declare `dectabsr` — the 'reset to known tab state' sequence reads + restores rather than blindly setting `\x1b[?5W` (DECST8C — set 8-column tabs). **Edge cases**: - Empty reply body (`\x1bP2$u\x1b\\`) means no tab stops are set at all — emit `\x1b[?5W` or write an 8-column HTS sequence to restore a default. - Some emulators report the implicit 'end of line' stop (column == screen width + 1) — strip that out of your saved list. - The reply uses `\x1b\\` (ST as ESC + backslash) by default; xterm in 8-bit C1 mode (see `decscl-compat-level`) uses single-byte `\x9c`. **Coverage**: **xterm** = full (reference). **WezTerm** / **mlterm** / **Konsole** = full. **Ghostty** = partial (emits reply but always with default 8-col tabs). **Kitty** / **iTerm2** = partial (replies but may miss user-set stops on certain code paths). **Alacritty** / **Linux console** / **Windows Terminal** / **cmd / ConPTY** / **macOS Terminal** / **gnome-terminal** = no-op (no reply at all — be sure to time out probes at ~100 ms).
Spec citation: DEC VT510 RM (DECTABSR / DECRPTAB) / xterm-ctlseqs (CSI 2 $ w)
Examples
# 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. */Terminal support
- xterm
- yes
- Linux console (fbcon)
- no
- macOS Terminal.app
- no
- iTerm2
- partial
- Windows Terminal
- no
- cmd.exe / ConPTY
- no
- kitty
- partial
- alacritty
- no
- WezTerm
- yes
- Ghostty
- partial
- GNOME Terminal
- no
- Konsole
- yes
- tmux
- no
- GNU screen
- no
| xterm | Linux console (fbcon) | macOS Terminal.app | iTerm2 | Windows Terminal | cmd.exe / ConPTY | kitty | alacritty | WezTerm | Ghostty | GNOME Terminal | Konsole | tmux | GNU screen |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| yes | no | no | partial | no | no | partial | no | yes | partial | no | yes | no | no |