Skip to main content
ansicode

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[\x1b[2$w
\\033[\033[2$w
\\e[\e[2$w
ESC [ESC [ 2 $ w
hex1b 5b 32 24 77

Description

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

bash
# 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/...
python
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)
go
// 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}
javascript
// 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
c
/* 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
yes
GNU screen
yes

Related sequences