The virtual viewport bottom is not maintained correctly #13369

Closed
opened 2026-01-31 03:40:54 +00:00 by claunia · 7 comments
Owner

Originally created by @j4james on GitHub (Apr 9, 2021).

Windows Terminal version (or Windows build number)

10.0.18363.1379, 7f5a19b627

Other Software

No response

Steps to reproduce

  1. Start a bash shell in conhost.
  2. Open the Properties page, and enable the Disable Scroll-Forward option on the Terminal tab.
  3. Generate some output to get a few pages of content in the backscroll buffer.
  4. Execute printf "\e[H" to move the cursor to the top of the current page.
  5. Scroll back to the top of the buffer so the cursor is now out of view.
  6. Type a printable character

Expected Behavior

All of the buffer contents should be preserved.

Actual Behavior

The line that the cursor was on becomes the bottom of the buffer, and everything below that point is truncated. Technically the data is still there - if you press enter a few times, it'll start scrolling into view. The problem is that the virtual viewport bottom is incorrectly set, so with Scroll-Forward disabled, everything below that point is hidden.

There are a number of ways this bug can be triggered - even just resizing the window can do it - but the above described method is probably the easiest to reproduce consistently. Anything that calls SCREEN_INFORMATION::UpdateBottom can potentially trigger it, and there are a quite a few code paths leading to that.

Originally created by @j4james on GitHub (Apr 9, 2021). ### Windows Terminal version (or Windows build number) 10.0.18363.1379, 7f5a19b627037353fc988755ed25e6ecb5bf4675 ### Other Software _No response_ ### Steps to reproduce 1. Start a bash shell in conhost. 2. Open the Properties page, and enable the _Disable Scroll-Forward_ option on the _Terminal_ tab. 3. Generate some output to get a few pages of content in the backscroll buffer. 4. Execute `printf "\e[H"` to move the cursor to the top of the current page. 5. Scroll back to the top of the buffer so the cursor is now out of view. 6. Type a printable character ### Expected Behavior All of the buffer contents should be preserved. ### Actual Behavior The line that the cursor was on becomes the bottom of the buffer, and everything below that point is truncated. Technically the data is still there - if you press enter a few times, it'll start scrolling into view. The problem is that the virtual viewport bottom is incorrectly set, so with _Scroll-Forward_ disabled, everything below that point is hidden. There are a number of ways this bug can be triggered - even just resizing the window can do it - but the above described method is probably the easiest to reproduce consistently. Anything that calls `SCREEN_INFORMATION::UpdateBottom` can potentially trigger it, and there are a quite a few code paths leading to that.
claunia added the Product-ConhostResolution-Fix-CommittedIssue-BugArea-VT labels 2026-01-31 03:40:55 +00:00
Author
Owner

@j4james commented on GitHub (Apr 9, 2021):

In general I think the bottom of the virtual viewport should always be increasing, except for two cases: 1. If the buffer has been shrunk for some reason, then we need to make sure the virtual bottom hasn't ended up out of range. 2. If the buffer is completely cleared with an _EraseScrollback call, then the virtual bottom needs to be reset too.

So I was thinking we could easily make the UpdateBottom method check if the bottom is increasing, or is potentially out of range, and only update in those two situations. Then all that's left is the _EraseScrollback call, which can be handled as a special case - it just needs to reset the bottom to 0, and it should then update automatically to the correct size.

Does that seem a reasonable solution? Or are there other cases I haven't thought of where it should be allowed to update to a smaller size?

Edit: I meant _EraseScrollback rather than VtEraseAll.

@j4james commented on GitHub (Apr 9, 2021): In general I think the bottom of the virtual viewport should always be increasing, except for two cases: 1. If the buffer has been shrunk for some reason, then we need to make sure the virtual bottom hasn't ended up out of range. 2. If the buffer is completely cleared with an `_EraseScrollback` call, then the virtual bottom needs to be reset too. So I was thinking we could easily make the `UpdateBottom` method check if the bottom is increasing, or is potentially out of range, and only update in those two situations. Then all that's left is the `_EraseScrollback` call, which can be handled as a special case - it just needs to reset the bottom to 0, and it should then update automatically to the correct size. Does that seem a reasonable solution? Or are there other cases I haven't thought of where it should be allowed to update to a smaller size? Edit: I meant `_EraseScrollback` rather than `VtEraseAll`.
Author
Owner

@DHowett commented on GitHub (Apr 9, 2021):

This seems reasonable to me. @zadjii-msft?

@DHowett commented on GitHub (Apr 9, 2021): This seems reasonable to me. @zadjii-msft?
Author
Owner

@zadjii-msft commented on GitHub (Apr 13, 2021):

That seems reasonable to me. I'll take a look at the PR later to double check.

@zadjii-msft commented on GitHub (Apr 13, 2021): That _seems_ reasonable to me. I'll take a look at the PR later to double check.
Author
Owner

@DHowett commented on GitHub (Apr 28, 2021):

REVERSION NOTES

Bug #9872 reported that this made behavior slightly worse in many regards.

Windows Terminal version (or Windows build number)

74909c0

Other Software

No response

Steps to reproduce

  1. Make sure you're using a build with PR #9770 merged.
  2. Open a conhost cmd shell.
  3. Output some content to fill a few pages of the buffer.
  4. Execute cls.

Expected Behavior

The buffer should be cleared and the viewport should be moved back to the top of the buffer.

Actual Behavior

The buffer is cleared, but the viewport remains where it was before the cls.

I'm afraid I broke this with PR #9770. Previously cls would have reset the virtual bottom when it reset the viewport, but it doesn't do that anymore. And I suspect there are probably more cases like this.

I'm really not sure what the right solution is though - maybe every viewport change made through the console API should reset the virtual bottom? But whatever we do, there are going to be some cases that behave weirdly - I think that's inevitable when you're mixing the legacy APIs with VT.

For now, though, I think it might be a good idea to revert #9770, because this bug seems a lot worse than #9754.

@DHowett commented on GitHub (Apr 28, 2021): **REVERSION NOTES** Bug #9872 reported that this made behavior slightly worse in many regards. > ### Windows Terminal version (or Windows build number) > [74909c0](https://github.com/microsoft/terminal/commit/74909c0c6511787907a6a369c8046c11ee373ae8) > > ### Other Software > _No response_ > > ### Steps to reproduce > 1. Make sure you're using a build with PR #9770 merged. > 2. Open a conhost cmd shell. > 3. Output some content to fill a few pages of the buffer. > 4. Execute `cls`. > > ### Expected Behavior > The buffer should be cleared and the viewport should be moved back to the top of the buffer. > > ### Actual Behavior > The buffer is cleared, but the viewport remains where it was before the `cls`. > > I'm afraid I broke this with PR #9770. Previously `cls` would have reset the virtual bottom when it reset the viewport, but it doesn't do that anymore. And I suspect there are probably more cases like this. > > I'm really not sure what the right solution is though - maybe every viewport change made through the console API should reset the virtual bottom? But whatever we do, there are going to be some cases that behave weirdly - I think that's inevitable when you're mixing the legacy APIs with VT. > > For now, though, I think it might be a good idea to revert #9770, because this bug seems a lot worse than #9754.
Author
Owner

@j4james commented on GitHub (Apr 22, 2022):

I just want to make some notes here of the different code paths via which this bug can surface, so I've got a bunch of test cases to work with. I'm going to take another stab at fixing this now.

  1. AdjustCursorPosition -> SetViewportOrigin (first call)
  • Open a bash shell in conhost.
  • Disable scroll forward.
  • Create a bunch of content overflowing the viewport.
  • Set margins with printf "\e[1;10r"
  • Scroll back exactly 9 lines.
  • Press enter.
  • Note the virtual bottom has moved up and truncated part of the buffer.
  1. AdjustCursorPosition -> SetViewportOrigin (second call)
  • Open a bash shell in conhost.
  • Disable scroll forward.
  • Create a bunch of content overflowing the viewport.
  • Move the cursor to the top of the viewport with printf "\e[H"
  • Scroll back to the top of the buffer.
  • Press enter.
  • Note the virtual bottom has moved up to the cursor position.
  1. Selection::SelectAll -> SetViewportOrigin
  • Open a bash shell in conhost.
  • Disable scroll forward.
  • Create a bunch of content overflowing the viewport.
  • Scroll back to the top of the buffer.
  • From the system menu select the Edit | Select All menu item.
  • Note that the virtual bottom has been moved up to the first page of the buffer.
  1. SetConsoleWindowInfoImpl -> SetViewport
  • Open a bash shell in conhost.
  • Disable scroll forward.
  • Create a bunch of content overflowing the viewport.
  • Execute sleep 3; printf "\e[8;20;80t"; sleep 10 (assuming 80x20 is different to the default size)
  • Immediately scroll to the top of the buffer.
  • When the 3s has elapsed, note the virtual bottom has moved up to your position in the scrollback.
  • After another 10s it should be restored though.
  1. ConsoleImeInfo::_WriteUndeterminedChars -> MakeCurrentCursorVisible
  • Install a language with an IME, like Japanese.
  • Open a bash shell in conhost.
  • Disable scroll forward.
  • Create a bunch of content overflowing the viewport.
  • Move the cursor to the top of the viewport with printf "\e[H"
  • Enable the IME in preperation for typing in that language.
  • Scroll back to the top of the buffer.
  • Start typing something in the IME.
  • Note the virtual bottom has moved up to the cursor position.
  1. Window::SetIsFullscreen -> MakeCurrentCursorVisible
  • Open a bash shell in conhost.
  • Disable scroll forward.
  • Disable wrap text output on resize.
  • Make the window full screen with Alt+Enter.
  • Create a bunch of content overflowing the viewport.
  • Mark the viewport range with printf "BOTTOM\e[HTOP\n"
  • Scroll back to the top of the buffer so the cursor is no longer visible.
  • Restore the window to its original size with Alt+Enter again.
  • Note the virtual bottom has moved up to the cursor position (just below TOP).
  1. Scrolling::s_HandleKeyScrollingEvent -> MakeCurrentCursorVisible
  • Open a cmd shell in conhost.
  • Disable scroll forward
  • Create a bunch of content overflowing the viewport.
  • Move the cursor to the top of the viewport with echo ^[[H
  • Scroll back to the top of the buffer.
  • Press Ctrl+End
  • Note the virtual bottom has moved up to the cursor position.
  1. AdjustCursorPosition -> MakeCursorVisible
  • Open a cmd shell in conhost.
  • Enable scroll forward
  • Press enter a couple of times so you're a halfway down the viewport.
  • Scroll forward in the buffer so the cursor is offscreen.
  • Press enter.
  • Scroll back to the top of the buffer.
  • Execute cls.
  • Note that the cursor doesn't move to the top of the buffer.
  1. SetConsoleCursorPositionImpl -> SetViewportOrigin
  • Open a pwsh shell in conhost
  • Enable scroll forward
  • Create a bunch of content overflowing the viewport.
  • Move the cursor to the top of the viewport with echo `e[H
  • Scroll back to the top of the buffer.
  • While still in the scrollback, execute echo `e[H again.
  • Note that the cursor now moves further back into the scrollback, which should be impossible.
  1. _InternalSetViewportSize
  • Open a pwsh shell in conhost.
  • Disable scroll forward.
  • Create a bunch of content overflowing the viewport.
  • Scroll back to the top of the buffer.
  • Shrink the window vertically (it must be a smaller height).
  • Note the virtual bottom has moved to the top of the buffer.
  1. ResizeWithReflow -> SetViewportOrigin
  • Open a pwsh shell in conhost.
  • Disable scroll forward.
  • Enable wrap text output on resize.
  • Create a bunch of content overflowing the viewport, making sure that it wraps.
  • Setup the width so it's on the verge of having to reformat the content.
  • Move the cursor to the top of the viewport with echo `e[H
  • Scroll back to the top of the buffer.
  • Shrink the window horizontally so the content reformats.
  • Note the virtual bottom has moved up.
@j4james commented on GitHub (Apr 22, 2022): I just want to make some notes here of the different code paths via which this bug can surface, so I've got a bunch of test cases to work with. I'm going to take another stab at fixing this now. 1. `AdjustCursorPosition` -> `SetViewportOrigin` (first call) * Open a bash shell in conhost. * Disable scroll forward. * Create a bunch of content overflowing the viewport. * Set margins with `printf "\e[1;10r"` * Scroll back exactly 9 lines. * Press enter. * Note the virtual bottom has moved up and truncated part of the buffer. 2. `AdjustCursorPosition` -> `SetViewportOrigin` (second call) * Open a bash shell in conhost. * Disable scroll forward. * Create a bunch of content overflowing the viewport. * Move the cursor to the top of the viewport with `printf "\e[H"` * Scroll back to the top of the buffer. * Press enter. * Note the virtual bottom has moved up to the cursor position. 3. `Selection::SelectAll` -> `SetViewportOrigin` * Open a bash shell in conhost. * Disable scroll forward. * Create a bunch of content overflowing the viewport. * Scroll back to the top of the buffer. * From the system menu select the _Edit | Select All_ menu item. * Note that the virtual bottom has been moved up to the first page of the buffer. 4. `SetConsoleWindowInfoImpl` -> `SetViewport` * Open a bash shell in conhost. * Disable scroll forward. * Create a bunch of content overflowing the viewport. * Execute `sleep 3; printf "\e[8;20;80t"; sleep 10` (assuming 80x20 is different to the default size) * Immediately scroll to the top of the buffer. * When the 3s has elapsed, note the virtual bottom has moved up to your position in the scrollback. * After another 10s it should be restored though. 5. `ConsoleImeInfo::_WriteUndeterminedChars` -> `MakeCurrentCursorVisible` * Install a language with an IME, like Japanese. * Open a bash shell in conhost. * Disable scroll forward. * Create a bunch of content overflowing the viewport. * Move the cursor to the top of the viewport with `printf "\e[H"` * Enable the IME in preperation for typing in that language. * Scroll back to the top of the buffer. * Start typing something in the IME. * Note the virtual bottom has moved up to the cursor position. 6. `Window::SetIsFullscreen` -> `MakeCurrentCursorVisible` * Open a bash shell in conhost. * Disable scroll forward. * Disable wrap text output on resize. * Make the window full screen with Alt+Enter. * Create a bunch of content overflowing the viewport. * Mark the viewport range with `printf "BOTTOM\e[HTOP\n"` * Scroll back to the top of the buffer so the cursor is no longer visible. * Restore the window to its original size with Alt+Enter again. * Note the virtual bottom has moved up to the cursor position (just below TOP). 7. `Scrolling::s_HandleKeyScrollingEvent` -> `MakeCurrentCursorVisible` * Open a cmd shell in conhost. * Disable scroll forward * Create a bunch of content overflowing the viewport. * Move the cursor to the top of the viewport with `echo ^[[H` * Scroll back to the top of the buffer. * Press Ctrl+End * Note the virtual bottom has moved up to the cursor position. 8. `AdjustCursorPosition` -> `MakeCursorVisible` * Open a cmd shell in conhost. * Enable scroll forward * Press enter a couple of times so you're a halfway down the viewport. * Scroll forward in the buffer so the cursor is offscreen. * Press enter. * Scroll back to the top of the buffer. * Execute `cls`. * Note that the cursor doesn't move to the top of the buffer. 9. `SetConsoleCursorPositionImpl` -> `SetViewportOrigin` * Open a pwsh shell in conhost * Enable scroll forward * Create a bunch of content overflowing the viewport. * Move the cursor to the top of the viewport with ```echo `e[H``` * Scroll back to the top of the buffer. * While still in the scrollback, execute ```echo `e[H``` again. * Note that the cursor now moves further back into the scrollback, which should be impossible. 10. `_InternalSetViewportSize` * Open a pwsh shell in conhost. * Disable scroll forward. * Create a bunch of content overflowing the viewport. * Scroll back to the top of the buffer. * Shrink the window vertically (it must be a smaller height). * Note the virtual bottom has moved to the top of the buffer. 11. `ResizeWithReflow` -> `SetViewportOrigin` * Open a pwsh shell in conhost. * Disable scroll forward. * Enable wrap text output on resize. * Create a bunch of content overflowing the viewport, making sure that it wraps. * Setup the width so it's on the verge of having to reformat the content. * Move the cursor to the top of the viewport with ```echo `e[H``` * Scroll back to the top of the buffer. * Shrink the window horizontally so the content reformats. * Note the virtual bottom has moved up.
Author
Owner

@j4james commented on GitHub (Apr 23, 2022):

Another two test cases. These are similar to test cases 3 and 7, they weren't fixed by initial attempts to fix 3 and 7.

3b. Selection::SelectAll -> SetViewportOrigin

  • Open a bash shell in conhost.
  • Enable scroll forward
  • Press enter a couple of times so you're a halfway down the viewport.
  • Scroll forward in the buffer so the cursor is offscreen.
  • From the system menu select the Edit | Select All menu item.
  • Scroll back to the top of the buffer.
  • Execute printf "\e[H".
  • Note that the cursor jumps downwards in the buffer.

7b. Scrolling::s_HandleKeyScrollingEvent -> MakeCurrentCursorVisible

  • Open a cmd shell in conhost.
  • Enable scroll forward
  • Press enter a couple of times so you're a halfway down the viewport.
  • Scroll forward in the buffer so the cursor is offscreen.
  • Press Ctrl+End
  • Scroll back to the top of the buffer.
  • Execute echo ^[[HTOP.
  • Note that it now considers the cursor row be the top of the virtual viewport.
@j4james commented on GitHub (Apr 23, 2022): Another two test cases. These are similar to test cases 3 and 7, they weren't fixed by initial attempts to fix 3 and 7. 3b. `Selection::SelectAll` -> `SetViewportOrigin` * Open a bash shell in conhost. * Enable scroll forward * Press enter a couple of times so you're a halfway down the viewport. * Scroll forward in the buffer so the cursor is offscreen. * From the system menu select the _Edit | Select All_ menu item. * Scroll back to the top of the buffer. * Execute `printf "\e[H"`. * Note that the cursor jumps downwards in the buffer. 7b. `Scrolling::s_HandleKeyScrollingEvent` -> `MakeCurrentCursorVisible` * Open a cmd shell in conhost. * Enable scroll forward * Press enter a couple of times so you're a halfway down the viewport. * Scroll forward in the buffer so the cursor is offscreen. * Press Ctrl+End * Scroll back to the top of the buffer. * Execute `echo ^[[HTOP`. * Note that it now considers the cursor row be the top of the virtual viewport.
Author
Owner

@ghost commented on GitHub (May 24, 2022):

:tada:This issue was addressed in #12972, which has now been successfully released as Windows Terminal Preview v1.14.143.🎉

Handy links:

@ghost commented on GitHub (May 24, 2022): :tada:This issue was addressed in #12972, which has now been successfully released as `Windows Terminal Preview v1.14.143`.:tada: Handy links: * [Release Notes](https://github.com/microsoft/terminal/releases/tag/v1.14.143) * [Store Download](https://www.microsoft.com/store/apps/9n8g5rfz9xk3?cid=storebadge&ocid=badge)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#13369