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

Prevent the virtual viewport bottom being updated incorrectly #12972

Merged
9 commits merged into from
May 2, 2022

Conversation

j4james
Copy link
Collaborator

@j4james j4james commented Apr 24, 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

@ghost ghost added Area-VT Virtual Terminal sequence support Issue-Bug It either shouldn't be doing this or needs an investigation. Product-Conhost For issues in the Console codebase labels Apr 24, 2022
@j4james j4james marked this pull request as ready for review April 26, 2022 21:31
@j4james
Copy link
Collaborator Author

j4james commented Apr 26, 2022

I know these fixes are a bit hacky, and it feels like we're just papering over the cracks, but this is the best I could come up for now. I'm a little concerned that the changes in PR #12703 might expose these virtual buffer problems more frequently (since we're no longer calling MoveToBottom all the time), so I thought it might be a good idea to try and get this resolved sooner rather than later.

// Make sure the new virtual bottom is far enough down to include both
// the cursor row and the last non-space row.
const auto cursorRow = newTextBuffer->GetCursor().GetPosition().Y;
const auto lastNonSpaceRow = newTextBuffer->GetLastNonSpaceCharacter().Y;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetLastNonSpaceCharacter is very very slow if the text buffer is mostly empty.
Will this be a problem? Is there some other way we can extract this information?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I would have thought the TextBuffer::Reflow should have been able to provide that information for us, since it's just created that new buffer. But then that made me ask why the TextBuffer isn't tracking the virtual bottom in general? And why we've rewritten essentially the same resizing code in the Terminal class but in a completely different way? (Although it's worth noting that the Terminal implementation also uses GetLastNonSpaceCharacter.) And my conclusion was that all of this buffer management code really needs a unified rewrite, but that was way more effort than I wanted to get into now, if ever. Hence the quick fix hack.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's mostly on me. SCREEN_INFO is pretty tightly bound to the rest of the console itself. It was a lot harder to separate out from... everything else that makes the console the console. I was trying to create a version with a cleaner interface in Terminal.

In retrospect, yes, a unified implementation would have been smart. I think I would have liked to create a cleaner boundary around SCREEN_INFO so that terminal could have reused it too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I realise it won't be easy decoupling that code from the console, but I think it'll be worth revisiting at some point, possibly when/if we get around to implementing VT paging. Because ideally I'd like for us to have a unified buffer management class that can handle both the console buffers and the VT pages in the same way (along with XTerm's alt/main weirdness). And that class could hopefully then be shared between conhost and Terminal and reduce some of the duplication. But it's not urgent.

src/host/screenInfo.cpp Show resolved Hide resolved
@DHowett
Copy link
Member

DHowett commented May 2, 2022

@msftbot make sure @zadjii-msft signs off

@ghost ghost added the AutoMerge Marked for automatic merge by the bot when requirements are met label May 2, 2022
@ghost
Copy link

ghost commented May 2, 2022

Hello @DHowett!

Because you've given me some instructions on how to help merge this pull request, I'll be modifying my merge approach. Here's how I understand your requirements for merging this pull request:

  • I'll only merge this pull request if it's approved by @zadjii-msft

If this doesn't seem right to you, you can tell me to cancel these instructions and use the auto-merge policy that has been configured for this repository. Try telling me "forget everything I just told you".

@zadjii-msft zadjii-msft self-assigned this May 2, 2022
@DHowett DHowett removed the AutoMerge Marked for automatic merge by the bot when requirements are met label May 2, 2022
@zadjii-msft zadjii-msft removed their assignment May 2, 2022
@DHowett DHowett added the AutoMerge Marked for automatic merge by the bot when requirements are met label May 2, 2022
@ghost
Copy link

ghost commented May 2, 2022

Hello @DHowett!

Because this pull request has the AutoMerge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.

@ghost ghost merged commit e3f4ec8 into microsoft:main May 2, 2022
ghost pushed a commit that referenced this pull request May 9, 2022
## Summary of the Pull Request

When calculating the position of the virtual bottom after a resize with
reflow, it was possible for it to end up less than the height of the
viewport. This meant that the top of the virtual viewport would be
negative, which resulted in other operations failing further down the
line. This PR updates the virtual bottom calculation to fix that
scenario.

## References

This was probably a regression introduced in PR #12972.

## PR Checklist
* [x] Closes #13034
* [x] CLA signed.
* [x] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [x] I've discussed this with core contributors already. Issue number where discussion took place: #13034

## Validation Steps Performed

I wasn't able to replicate the exact case described in issue #13034,
because I don't have Windows 11, so can't configure the default
terminal. However, I was able to reproduce a similar failure using a
`SetConsoleScreenBufferInfoEx` call, and I've confirmed that this PR
has fixed that.

I've also added another screen buffer test to make sure the
`ResizeWithReflow` method doesn't shrink the virtual bottom when
resizing at the top of the buffer.
ghost pushed a commit that referenced this pull request May 12, 2022
When the buffer is resized with a reflow, we were previously calculating
the new virtual bottom based on the position of last non-space
character. If the viewport was largely blank when resized, this could
result in the new virtual bottom being higher than it should be.

This PR attempts to address that problem by restoring the virtual bottom
to a position that is the same distance from the cursor row as it was
prior to the resize.

This was a regression introduced in PR #12972.

We still take the last non-space row into account when determining the
virtual bottom, because if the content of the screen is forced to wrap,
the virtual bottom will need to be lower (relative to the cursor) than
it was before.

We also need to check that we don't overflow the bottom of the buffer,
which can occur when the viewport is at the bottom of the buffer, and
the cursor position is pushed down as a result of content wrapping above
it.

I've manually confirmed that this fixes the problem reported in issue
#13078, and I've also extended the existing `RefreshWithReflow` unit
test to cover that particular scenario.

Closes #13078
carlos-zamora pushed a commit that referenced this pull request May 12, 2022
When the buffer is resized with a reflow, we were previously calculating
the new virtual bottom based on the position of last non-space
character. If the viewport was largely blank when resized, this could
result in the new virtual bottom being higher than it should be.

This PR attempts to address that problem by restoring the virtual bottom
to a position that is the same distance from the cursor row as it was
prior to the resize.

This was a regression introduced in PR #12972.

We still take the last non-space row into account when determining the
virtual bottom, because if the content of the screen is forced to wrap,
the virtual bottom will need to be lower (relative to the cursor) than
it was before.

We also need to check that we don't overflow the bottom of the buffer,
which can occur when the viewport is at the bottom of the buffer, and
the cursor position is pushed down as a result of content wrapping above
it.

I've manually confirmed that this fixes the problem reported in issue
#13078, and I've also extended the existing `RefreshWithReflow` unit
test to cover that particular scenario.

Closes #13078

(cherry picked from commit c2f8308)
Service-Card-Id: 81864034
Service-Version: 1.14
@j4james j4james deleted the fix-update-bottom-2 branch May 14, 2022 12:50
@ghost
Copy link

ghost commented May 24, 2022

🎉Windows Terminal Preview v1.14.143 has been released which incorporates this pull request.:tada:

Handy links:

This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-VT Virtual Terminal sequence support AutoMerge Marked for automatic merge by the bot when requirements are met Issue-Bug It either shouldn't be doing this or needs an investigation. Product-Conhost For issues in the Console codebase
Projects
None yet
Development

Successfully merging this pull request may close these issues.

The virtual viewport bottom is not maintained correctly
5 participants