Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Regression in scrolled-forward invalidation behavior from #4171 0586955c88fac8c6f631b89a8b19d4338bfa26ad #5302

Closed
DHowett-MSFT opened this issue Apr 9, 2020 · 6 comments · Fixed by #5317
Labels
Area-Output Related to output processing (inserting text into buffer, retrieving buffer text, etc.) Impact-Compatibility Sure don't work like it used to. Issue-Bug It either shouldn't be doing this or needs an investigation. Needs-Tag-Fix Doesn't match tag requirements Priority-2 A description (P2) Product-Conhost For issues in the Console codebase Resolution-Fix-Committed Fix is checked in, but it might be 3-4 weeks until a release.
Milestone

Comments

@DHowett-MSFT
Copy link
Contributor

Repro from MSFT:25863049

In an unelevated command prompt window, run "dir" twice to fill up the buffer, scroll down until the prompt is in the middle of the screen, then run "dism"

It should print information below the prompt about how it needs to be run elevated, and then return to a prompt.

Instead, it prints that information at a random location.

image

Bisect brought me to #4171.

(/cc @j4james)

@DHowett-MSFT DHowett-MSFT added Product-Conhost For issues in the Console codebase Issue-Bug It either shouldn't be doing this or needs an investigation. Priority-2 A description (P2) Impact-Compatibility Sure don't work like it used to. labels Apr 9, 2020
@DHowett-MSFT DHowett-MSFT added this to the 21H1 milestone Apr 9, 2020
@ghost ghost added Needs-Tag-Fix Doesn't match tag requirements Needs-Triage It's a new issue that the core contributor team needs to triage at the next triage meeting labels Apr 9, 2020
@DHowett-MSFT DHowett-MSFT added Area-Output Related to output processing (inserting text into buffer, retrieving buffer text, etc.) and removed Needs-Triage It's a new issue that the core contributor team needs to triage at the next triage meeting labels Apr 9, 2020
@ghost ghost removed the Needs-Tag-Fix Doesn't match tag requirements label Apr 9, 2020
@DHowett-MSFT
Copy link
Contributor Author

The ability to scroll the bottom line of the console up is an atrocity that, unfortunately, thousands of our users apparently love.

@DHowett-MSFT DHowett-MSFT added the Help Wanted We encourage anyone to jump in on these. label Apr 9, 2020
@j4james
Copy link
Collaborator

j4james commented Apr 9, 2020

It looks like it's got something to do with the fact that the new carriage return handler makes a MoveToBottom call which the original implementation didn't do. That's something that almost all VT movement operations use, because without it the margin limits don't always work correctly. And I suspect the fact that the original carriage return didn't do that was actually a bug of sorts.

I'm still not sure why the MoveToBottom call results in things being so messed up though. Because that suggests that even if we went back to the original behaviour, there are probably other ways in which this problem could manifest. I need to do some more digging though.

@DHowett-MSFT
Copy link
Contributor Author

So, this is probably a bad interaction with our "virtual bottom". In 19H1, we added a mode that disables the atrocity mentioned above (scroll-forward). The "bottom" of the buffer is tracked in SCREEN_INFORMATION::_virtualBottom, and MoveToBottom exclusively prefers that one even if that mode isn't enabled (!!)

@j4james
Copy link
Collaborator

j4james commented Apr 10, 2020

OK, I think I know what's going on now. When the viewport bottom is below the virtual bottom, the virtual bottom doesn't get updated. So you've got an app writing stuff to an area of the screen that won't be visible once the viewport is forced back to the correct position (which happens with a MoveToBottom call).

In earlier versions of the code, this was less of a problem. You might have a VT operation that triggered MoveToBottom, moving the viewport back up so it hid some of the output. However, the cursor was generally allowed to remain outside the viewport, and eventually would force the viewport down to bring it back into view again.

A bunch of VT things would be broken in these situations, and there were still a couple of cases where the viewport would not correct its position, but that was less common.

Once I refactored the cursor movement in PR #3628, things got a lot worse though. Now every cursor movement operation makes sure the position is clamped within the viewport, so it doesn't have the chance to force the viewport back down like it used to. And then PR #4171 made it so every VT carriage return could trigger the issue, and that's going to happen every time the command prompt is output.

The bottom line is I think this has been broken for a long time. It's just that the effects of the issue have been made a lot worse by those two PRs.

As for how we fix it, I thought we could possibly just add a check in the AdjustCursorPosition to update the virtual bottom if the cursor has moved below it. So at this point...

const bool cursorMovedPastViewport = coordCursor.Y > screenInfo.GetViewport().BottomInclusive();
const bool cursorMovedPastVirtualViewport = coordCursor.Y > screenInfo.GetVirtualViewport().BottomInclusive();
if (NT_SUCCESS(Status))
{
// if at right or bottom edge of window, scroll right or down one char.
if (cursorMovedPastViewport)
{
COORD WindowOrigin;
WindowOrigin.X = 0;
WindowOrigin.Y = coordCursor.Y - screenInfo.GetViewport().BottomInclusive();
Status = screenInfo.SetViewportOrigin(false, WindowOrigin, true);
}
}

...we'd add a second condition, with something like this:

else if (cursorMovedPastVirtualViewport)
{
    screenInfo.SetVirtualBottom(coordCursor.Y);
}

That seems to fix the issue for me, but I don't know if that's necessarily the right solution, or that there aren't other places in the code that would require similar updates.

@j4james
Copy link
Collaborator

j4james commented Apr 10, 2020

Another option would be to add the check in the SCREEN_INFORMATION::SetCursorPosition itself. That feels cleaner, but there are some potentially weird edge cases. For example if a console app temporarily set the cursor position to somewhere way below the bottom of the viewport, that position now becomes the virtual bottom, even if nothing is written there.

And then the minute you get a VT cursor movement operation, the viewport will suddenly jump down to that position. And with the cmd shell always being in VT mode, that's going to happen as soon as the app exits. But that does seem like a weird edge case, and maybe that is the behaviour you would expect in that situation.

And possibly half the problem is we're doing MoveToBottom too much. One of the items on my TODO list was to propose getting rid of most of those calls if possible. And if that happens, then this becomes even less of an issue.

@ghost ghost added the In-PR This issue has a related PR label Apr 10, 2020
@j4james
Copy link
Collaborator

j4james commented Apr 10, 2020

For example if a console app temporarily set the cursor position to somewhere way below the bottom of the viewport, that position now becomes the virtual bottom, even if nothing is written there.

After further investigation, I've decided this really isn't a problem. The SetConsoleCursorPosition API already updates the virtual bottom when the cursor position is moved offscreen, so this isn't any different from the current behaviour. I'm now leaning more towards option 2 as the better approach, and have submitted a PR for that in case you need a quick fix.

@ghost ghost added the Needs-Tag-Fix Doesn't match tag requirements label Apr 14, 2020
DHowett-MSFT pushed a commit that referenced this issue Apr 14, 2020
If an application writes to the screen while not in VT mode, and the
user has scrolled forward in the screen buffer, the _virtual bottom_
location is not updated to take that new content into account. As a
result, the viewport can later jump back to the previous _virtual
bottom_, making the content disappear off screen. This PR attempts to
fix that issue by updating the _virtual bottom_ location whenever the
cursor moves below that point.

## PR Checklist
* [x] CLA signed. 
* [x] Tests added/passed

## Detailed Description of the Pull Request / Additional comments

This simply adds a condition in the
`SCREEN_INFORMATION::SetCursorPosition` to check if the new _Y_
coordinate is below the current _virtual bottom_, and if so, updates the
_virtual bottom_ to that new value.

I considered trying to make it only update when something is actually
written to the screen, but this seemed like a cleaner solution, and is
less likely to miss out on a needed update.

## Validation Steps Performed

I've manually tested the case described in issue #5302, and confirmed
that it now works as expected. I've also added a unit test that checks
the virtual bottom is updated correctly under similar conditions.

Closes #5302
@ghost ghost added Resolution-Fix-Committed Fix is checked in, but it might be 3-4 weeks until a release. and removed Help Wanted We encourage anyone to jump in on these. In-PR This issue has a related PR labels Apr 14, 2020
ghost pushed a commit that referenced this issue May 2, 2022
The "virtual bottom" marks the last line of the mutable viewport area,
which is the part of the buffer that VT sequences can write to. This
region should typically only move downwards as new lines are added to
the buffer, but there were a number of cases where it was incorrectly
being moved up, or moved down further than necessary. This PR attempts
to fix that.

There was an earlier, unsuccessful attempt to fix this in PR #9770 which
was later reverted (issue #9872 was the reason it had to be reverted).
PRs #2666, #2705, and #5317 were fixes for related virtual viewport
problems, some of which have either been extended or superseded by this
PR.

`SetConsoleCursorPositionImpl` is one of the cases that actually does
need to move the virtual viewport upwards sometimes, in particular when
the cmd shell resets the buffer with a `CLS` command. But when this
operation "snaps" the viewport to the location of the cursor, it needs
to use the virtual viewport as the frame of reference. This was
partially addressed by PR #2705, but that only applied in
terminal-scrolling mode, so I've now applied that fix regardless of the
mode.

`SetViewportOrigin` takes a flag which determines whether it will also
move the virtual bottom to match the visible viewport. In some case this
is appropriate (`SetConsoleCursorPositionImpl` being one example), but
in other cases (e.g. when panning the viewport downwards in the
`AdjustCursorPosition` function), it should only be allowed to move
downwards. We can't just not set the update flag in those cases, because
that also determines whether or not the viewport would be clamped, and
we don't want change that. So what I've done is limit
`SetViewportOrigin` to only move the virtual bottom downwards, and added
an explicit `UpdateBottom` call in those places that may also require
upward movement.

`ResizeWindow` in the `ConhostInternalGetSet` class has a similar
problem to `SetConsoleCursorPositionImpl`, in that it's updating the
viewport to account for the new size, but if that visible viewport is
scrolled back or forward, it would end up placing the virtual viewport
in the wrong place. So again the solution here was to use the virtual
viewport as the frame of reference for the position. However, if the
viewport is being shrunk, this can still result in the cursor falling
below the bottom, so we need an additional check to adjust for that.
This can't be applied in pty mode, though, because that would break the
conpty resizing operation.

`_InternalSetViewportSize` comes into play when resizing the window
manually, and again the viewport after the resize can end up truncating
the virtual bottom if not handled correctly. This was partially
addressed in the original code by clamping the new viewport above the
virtual bottom under certain conditions, and only in terminal scrolling
mode. I've replaced that with a new algorithm which links the virtual
bottom to the visible viewport bottom if the two intersect, but
otherwise leaves it unchanged. This applies regardless of the scrolling
mode.

`ResizeWithReflow` is another sizing operation that can affect the
virtual bottom. This occurs when a change of the window width requires
the buffer to be reflowed, and we need to reposition the viewport in the
newly generated buffer. Previously we were just setting the virtual
bottom to align with the new visible viewport, but that could easily
result in the buffer truncation if the visible viewport was scrolled
back at the time. We now set the virtual bottom to the last non-space
row, or the cursor row (whichever is larger). There'll be edge cases
where this is probably not ideal, but it should still work reasonably
well.

`MakeCursorVisible` was another case where the virtual bottom was being
updated (when requested with a flag) via a `SetViewportOrigin` call.
When I checked all the places it was used, though, none of them actually
required that behavior, and doing so could result in the virtual bottom
being incorrectly positioned, even after `SetViewportOrigin` was limited
to moving the virtual bottom downwards. So I've now made it so that
`MakeCursorVisible` never updates the virtual bottom.

`SelectAll` in the `Selection` class was a similar case. It was calling
`SetViewportOrigin` with the `updateBottom` flag set when that really
wasn't necessary and could result in the virtual bottom being
incorrectly set. I've changed the flag to false now.

## Validation Steps Performed

I've manually confirmed that the test cases in issue #9754 are working
now, except for the one involving margins, which is bigger problem with
`AdjustCursorPosition` which will need to be addressed separately.

I've also double checked the test cases from several other virtual
bottom issues (#1206, #1222, #5302, and #9872), and confirmed that
they're still working correctly with these changes.

And I've added a few screen buffer tests in which I've tried to cover as
many of the problematic code paths as possible.

Closes #9754
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Output Related to output processing (inserting text into buffer, retrieving buffer text, etc.) Impact-Compatibility Sure don't work like it used to. Issue-Bug It either shouldn't be doing this or needs an investigation. Needs-Tag-Fix Doesn't match tag requirements Priority-2 A description (P2) Product-Conhost For issues in the Console codebase Resolution-Fix-Committed Fix is checked in, but it might be 3-4 weeks until a release.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants