Unexpected behaviour when setting tab stops #200

Closed
opened 2026-01-30 21:45:22 +00:00 by claunia · 2 comments
Owner

Originally created by @j4james on GitHub (Mar 21, 2018).

Originally assigned to: @zadjii-msft on GitHub.

  • Your Windows build number:

Microsoft Windows [Version 10.0.16299.309]

  • What you're doing and what's happening:

I've been experimenting with the various escape sequences for setting tab stops and noticed a number of areas in which the behaviour is different from what I would have expected. This seems largely due to the fact that the default tab stops are treated differently from the manually specified tabs.

If you start with just the default tab stops (every 8 columns), and try to use the single-column tab clear command (TBC) to clear one of those tabs, it appears to have no effect in the Windows console. Example test case:

printf "\033[3g\033c|\t|\t|\n\t\033[0g\r\tx\n"

In a Linux console, the TBC clears the tab stop at column 8 (zero-based), so the x appears in column 16 as expected:

|       |       |
                x

In the Windows 10 console, though, the TBC command has no effect, so the x appears in column 8:

|       |       |
        x

The horizontal tab set (HTS) command is similarly out of sync with the default tab stops. So if you start with the defaults, and try to set an addition tab with HTS, that one tab stop appears to wipe out all the defaults. Example test case:

printf "\033[3g\033c|\t|\t|\n    \033H\r\tx\tx\n"

In a Linux console this adds a tab stop in column 4, leaving the default 8 column tabs unchanged, so you get one x in column 4 and one in column 8:

|       |       |
    x   x

In the Windows 10 console, the defaults are erased, so while the first x appears in column 4, the second x appears in the rightmost column of the display:

|       |       |
    x                                                       x

Then there's the all-column TBC, which is supposed to clear all tab stops, but just resets to the defaults in the Windows console. Example test case:

printf "\033[3g\033c|\t|\t|\n\033[3g\tx\n"

On Linux this works as expected - all tabs are cleared, so the x appears in the rightmost column of the display:

|       |       |
                                                            x

On Windows the tabs are reset to the default positions, so the x appears in column 8:

|       |       |
        x

Now if I really did want to reset the tabs to their defaults, I would have expected the ESC c (reset) command to have that effect, but in the Windows console that leaves the most recently set tab stops unchanged. Example test case:

printf "\033[3g\033c    \033H\033c|       |       |\n\tx\n"

This sequence sets a tab in column 4, resets the terminal with ESC c, then attempts to output an x in the first tab position. On Linux the tabs are reset to the 8 column defaults, so the x appears in column 8:

|       |       |
        x

On Windows the tab stop in column 4 is retained, even after the reset, so the x appears in column 4:

|       |       |
    x

Note that in all of these test cases I'm starting with a combination of CSI 3g and ESC c as a cross platform way to reset the tabs before getting to the actual test. If you wanted to be certain of the behaviour, though, it would be preferable to test each sequence from a fresh console.

I should also note there are a few other areas where the tab functionality differs from what I would expect, but I thought it best to check first whether the current behaviour is intentionally the way it is, in which case my other complaints would likely be non-issues too.

  • What's wrong / what should be happening instead:

I'd expect the default tab stops and manual specified tab stops to work in exactly the same way, exhibiting behaviour more in line with what I see in a Linux console. I'd also expect the reset command to reset the tab stops to their default values.

Originally created by @j4james on GitHub (Mar 21, 2018). Originally assigned to: @zadjii-msft on GitHub. * Your Windows build number: Microsoft Windows [Version 10.0.16299.309] * What you're doing and what's happening: I've been experimenting with the various escape sequences for setting tab stops and noticed a number of areas in which the behaviour is different from what I would have expected. This seems largely due to the fact that the default tab stops are treated differently from the manually specified tabs. If you start with just the default tab stops (every 8 columns), and try to use the single-column tab clear command (TBC) to clear one of those tabs, it appears to have no effect in the Windows console. Example test case: printf "\033[3g\033c|\t|\t|\n\t\033[0g\r\tx\n" In a Linux console, the TBC clears the tab stop at column 8 (zero-based), so the _x_ appears in column 16 as expected: | | | x In the Windows 10 console, though, the TBC command has no effect, so the _x_ appears in column 8: | | | x The horizontal tab set (HTS) command is similarly out of sync with the default tab stops. So if you start with the defaults, and try to set an addition tab with HTS, that one tab stop appears to wipe out all the defaults. Example test case: printf "\033[3g\033c|\t|\t|\n \033H\r\tx\tx\n" In a Linux console this adds a tab stop in column 4, leaving the default 8 column tabs unchanged, so you get one _x_ in column 4 and one in column 8: | | | x x In the Windows 10 console, the defaults are erased, so while the first _x_ appears in column 4, the second _x_ appears in the rightmost column of the display: | | | x x Then there's the all-column TBC, which is supposed to clear all tab stops, but just resets to the defaults in the Windows console. Example test case: printf "\033[3g\033c|\t|\t|\n\033[3g\tx\n" On Linux this works as expected - all tabs are cleared, so the _x_ appears in the rightmost column of the display: | | | x On Windows the tabs are reset to the default positions, so the _x_ appears in column 8: | | | x Now if I really did want to reset the tabs to their defaults, I would have expected the `ESC c` (reset) command to have that effect, but in the Windows console that leaves the most recently set tab stops unchanged. Example test case: printf "\033[3g\033c \033H\033c| | |\n\tx\n" This sequence sets a tab in column 4, resets the terminal with `ESC c`, then attempts to output an _x_ in the first tab position. On Linux the tabs are reset to the 8 column defaults, so the _x_ appears in column 8: | | | x On Windows the tab stop in column 4 is retained, even after the reset, so the _x_ appears in column 4: | | | x Note that in all of these test cases I'm starting with a combination of `CSI 3g` and `ESC c` as a cross platform way to reset the tabs before getting to the actual test. If you wanted to be certain of the behaviour, though, it would be preferable to test each sequence from a fresh console. I should also note there are a few other areas where the tab functionality differs from what I would expect, but I thought it best to check first whether the current behaviour is intentionally the way it is, in which case my other complaints would likely be non-issues too. * What's wrong / what should be happening instead: I'd expect the default tab stops and manual specified tab stops to work in exactly the same way, exhibiting behaviour more in line with what I see in a Linux console. I'd also expect the reset command to reset the tab stops to their default values.
claunia added the Product-ConhostWork-Item labels 2026-01-30 21:45:22 +00:00
Author
Owner

@zadjii-msft commented on GitHub (Mar 28, 2018):

Boy, there's a lot going on here, isn't there.

Almost all of this comes down to the incompatibilities between the legacy tab stops (at every 8 columns) and the way linux-like tab stops are set. We have to try and maintain back-compat with apps that don't understand VT, so if one app sets a bunch of tab stops, it doesn't mess up a later launched windows executable that doesn't understand that and just tabs around blindly.

That being said, I'll file a bug for me to come back and try and make sure it's all consistent.

@zadjii-msft commented on GitHub (Mar 28, 2018): Boy, there's a lot going on here, isn't there. Almost all of this comes down to the incompatibilities between the legacy tab stops (at every 8 columns) and the way linux-like tab stops are set. We have to try and maintain back-compat with apps that don't understand VT, so if one app sets a bunch of tab stops, it doesn't mess up a later launched windows executable that doesn't understand that and just tabs around blindly. That being said, I'll file a bug for me to come back and try and make sure it's all consistent.
Author
Owner

@DHowett-MSFT commented on GitHub (Nov 21, 2018):

It looks like this went out with 1809. Thanks!

@DHowett-MSFT commented on GitHub (Nov 21, 2018): It looks like this went out with 1809. Thanks!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#200