[PR #3197] Prevent the horizontal tab character wrapping at the end of a line #25267

Closed
opened 2026-01-31 09:08:23 +00:00 by claunia · 0 comments
Owner

Original Pull Request: https://github.com/microsoft/terminal/pull/3197

State: closed
Merged: Yes


Summary of the Pull Request

When a horizontal tab ('\t') is output on the last column of the screen, the current implementation moves the cursor position to the start of the next line. However, the DEC STD 070 manual specifies that a horizontal tab shouldn't move past the last column of the active line (or the right margin, if we supported horizontal margins). This PR updates the forward tab implementation, to prevent it wrapping onto a new line when it reaches the end of a line.

PR Checklist

Detailed Description of the Pull Request / Additional comments

Originally the SCREEN_INFORMATION::GetForwardTab method had a condition which handled a tab at the end of the line as a special case, moving the cursor to the start of the next line. I've simply removed that condition, so an end-of-line tab is handled the same way as any other position (in this case it will just leaves the cursor where it is).

While testing, though, I found that there were circumstances where you could have tab stops greater than the width of the screen, and when that happens, a tab can still end up wrapping onto the next line. To fix that I had to add an additional check to make sure the tab position was always clamped to the width of the buffer.

With these fixes in place, a tab control should now never move off the active line, so I realised that the DoPrivateTabHelper function could be optimized to calculate all of the tab movements in advance, and then only make a single call to AdjustCursorPosition with the final coordinates. This change is not strictly necessary, though, so it can easily be reverted if there are any objections.

Regarding backwards compatibility, note that the GetForwardTab method is only used in two places:

  • when handling a tab character in the WriteCharsLegacy function, but this only applies in VT mode (see here).
  • when handling the CHT escape sequence in the DoPrivateTabHelper function, and obviously an escape sequence would also only be applicable in VT mode.

So this change should have no effect on legacy console applications, which wouldn't have VT mode activated.

Validation Steps Performed

I've added another step to the TestGetForwardTab test which makes sure that a horizontal tab won't wrap at the end of a line.

I've also confirmed that this fixes the last remaining issue in the Test of autowrap in Vttest (pages 3 and 4 of the Test of cursor movements). Although I should note that this only works in conhost.

**Original Pull Request:** https://github.com/microsoft/terminal/pull/3197 **State:** closed **Merged:** Yes --- ## Summary of the Pull Request When a horizontal tab ('\t') is output on the last column of the screen, the current implementation moves the cursor position to the start of the next line. However, the _DEC STD 070_ manual specifies that a horizontal tab shouldn't move past the last column of the active line (or the right margin, if we supported horizontal margins). This PR updates the forward tab implementation, to prevent it wrapping onto a new line when it reaches the end of a line. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist * [x] Closes #3168 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [x] Tests added/passed * [ ] Requires documentation to be updated * [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #3168 ## Detailed Description of the Pull Request / Additional comments Originally the `SCREEN_INFORMATION::GetForwardTab` method had a condition which handled a tab at the end of the line as a special case, moving the cursor to the start of the next line. I've simply removed that condition, so an end-of-line tab is handled the same way as any other position (in this case it will just leaves the cursor where it is). While testing, though, I found that there were circumstances where you could have tab stops greater than the width of the screen, and when that happens, a tab can still end up wrapping onto the next line. To fix that I had to add an additional check to make sure the tab position was always clamped to the width of the buffer. With these fixes in place, a tab control should now never move off the active line, so I realised that the `DoPrivateTabHelper` function could be optimized to calculate all of the tab movements in advance, and then only make a single call to `AdjustCursorPosition` with the final coordinates. This change is not strictly necessary, though, so it can easily be reverted if there are any objections. Regarding backwards compatibility, note that the `GetForwardTab` method is only used in two places: - when handling a tab character in the `WriteCharsLegacy` function, but this only applies in VT mode (see [here](https://github.com/microsoft/terminal/blob/b9233c03d12bc7addab16647dba07196bd554f6f/src/host/_stream.cpp#L742-L749)). - when handling the `CHT` escape sequence in the `DoPrivateTabHelper` function, and obviously an escape sequence would also only be applicable in VT mode. So this change should have no effect on legacy console applications, which wouldn't have VT mode activated. ## Validation Steps Performed I've added another step to the `TestGetForwardTab` test which makes sure that a horizontal tab won't wrap at the end of a line. I've also confirmed that this fixes the last remaining issue in the _Test of autowrap_ in [Vttest](https://invisible-island.net/vttest/) (pages 3 and 4 of the _Test of cursor movements_). Although I should note that this only works in conhost.
claunia added the pull-request label 2026-01-31 09:08:24 +00:00
Sign in to join this conversation.
No Label pull-request
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#25267