Skip to content

Custom tab stops are ignored: HTS, TBC, CHT and CBT all do nothing #262

Description

@vyncint

Today — tab stops are fixed at every eighth column. Setting one (HTS, ESC H), clearing one (TBC, CSI g) and moving between them (CHT, CSI I; CBT, CSI Z) are all parsed and ignored. Measured against 0.9.0 on a 24-column terminal, with · marking where the text lands:

$ inspect --size 24x2 sh -c "printf 'a\011b'"                        # default stops
a·······b                                                            # column 8, correct

$ inspect --size 24x2 sh -c "printf '\033[4G\033H\033[1Ga\011b'"     # set a stop at column 4, then tab to it
a·······b                                                            # xterm: a··b, at column 4

$ inspect --size 24x2 sh -c "printf '\011\011X\033[1Zy'"             # two tabs, then one back-tab
················Xy                                                   # xterm: y at column 8

The default stops are right because they are the backend's hardcoded eight; nothing else about tab stops exists. The escape sequences reach a dispatch table that has no entry for them and vanish.

Why it is worth fixing — all four capabilities are in the terminfo entry termlens hands every child by default:

$ infocmp -1 xterm-256color | grep -E '^\s+(hts|tbc|cbt)='
	cbt=\E[Z,
	hts=\EH,
	tbc=\E[3g,

An application that lays a table out by setting its own stops and tabbing between them — which is what the capabilities are for, and what ncurses uses when a program calls them — renders with every column in the wrong place. The failure is a silent misalignment: the characters are all present, so contains still passes and only a column assertion or a whole-screen snapshot catches it, and a snapshot taken before the tab stops were set will happily bless the wrong layout.

Back-tab (CBT) has a second life worth naming: it is what Shift-Tab sends, and an application echoing or forwarding it emits CSI Z on its output side. Today that moves nothing.

Fix — the backend has no notion of tab stops beyond its fixed eight, so the state belongs where the other state it does not model already lives: emu/seq.rs, beside the character sets, focus reporting and the cursor shape.

A tab stop set is a bitmap one bit per column, cols wide, initialised to every eighth column and reset there by RIS — and DECSTR too, whose list already lives in soft_reset. Then:

  • ESC H (HTS) sets the stop at the current column, so the tracker needs the cursor column, which the emulator has.
  • CSI g / CSI 0 g clears the stop at the cursor; CSI 3 g clears all.
  • \t, CSI I (CHT, forward n stops) and CSI Z (CBT, back n stops) move the cursor to the computed column, which the emulator applies as a CSI n G (CHA) on the rewritten stream — the backend dispatches G, so this is a rewrite rather than a new grid operation, the same way DEC Special Graphics became a glyph substitution.

Resize is the one interesting case: a grid that grows needs stops for the new columns. Take the simple answer — extend with the default every-eighth pattern past the old width, leaving existing stops alone — and write it in the rustdoc as a decision rather than leaving a reader to infer it.

Scope note for the PR: \t currently reaches the backend and is handled by its fixed eight, so the rewrite must take over \t as well or the two will disagree the moment a custom stop exists. That is the part to get right first, and a test that sets one stop and then uses a plain \t is the one that proves it.

Done whenHTS sets a stop that a following \t honours; TBC clears one stop and CSI 3 g clears all; CHT and CBT move by whole stops; RIS and DECSTR restore the default every-eighth set; a resize extends the set in a documented way; and a test in the emulation suite covers each, including a plain \t after a custom stop.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't workinggood first issueGood for newcomershelp wantedExtra attention is needed

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions