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

Don't reflow the alt buffer on resize #12719

Merged
55 commits merged into from
Apr 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
83466a4
I think this is the conhost half of the changes
zadjii-msft Feb 23, 2022
e94e08c
This quite nearly implements everything for the Terminal half
zadjii-msft Feb 23, 2022
dd702c7
Most of the remaining todos, comments
zadjii-msft Feb 23, 2022
1209fa4
one last comment
zadjii-msft Feb 23, 2022
bf24cdd
more comments are always good
zadjii-msft Feb 23, 2022
dff1b94
spel
zadjii-msft Feb 24, 2022
60d2c2e
fix tests
zadjii-msft Feb 24, 2022
57280d8
I think this does the whole thing
zadjii-msft Feb 24, 2022
d08d216
defer main buffer resizes till we exit, to try and prevent flashing i…
zadjii-msft Feb 25, 2022
6529579
deferred resizing both in the Terminal and the ConPTY
zadjii-msft Feb 25, 2022
5d8c0d0
comments mostly
zadjii-msft Feb 25, 2022
54dc304
Stashing this for now. I think this does the save/restore cursor stuf…
zadjii-msft Feb 28, 2022
57094b7
start writing tests
zadjii-msft Mar 8, 2022
69d0973
make this test way more elaborate
zadjii-msft Mar 8, 2022
0a1ed70
I thought this was a test for https://github.com/microsoft/terminal/p…
zadjii-msft Mar 8, 2022
9453aa5
More test cleanup. Make sure viewport doesnt move
zadjii-msft Mar 8, 2022
e658431
Merge remote-tracking branch 'origin/main' into dev/migrie/b/alt-buff…
zadjii-msft Mar 8, 2022
4c96fc0
Merge branch 'dev/migrie/b/alt-buffer-terminal' into dev/migrie/b/349…
zadjii-msft Mar 8, 2022
0cfb463
Merge remote-tracking branch 'origin/main' into dev/migrie/b/alt-buff…
zadjii-msft Mar 16, 2022
bf3d79e
fine that's not a word
zadjii-msft Mar 16, 2022
7237fce
thats not a word either
zadjii-msft Mar 17, 2022
65b4571
notes from j4james
zadjii-msft Mar 17, 2022
c8f0a5e
Merge branch 'dev/migrie/b/alt-buffer-terminal' into dev/migrie/b/349…
zadjii-msft Mar 17, 2022
0163878
cleanup
zadjii-msft Mar 18, 2022
7c3e808
Merge remote-tracking branch 'origin/main' into dev/migrie/b/alt-buff…
zadjii-msft Apr 1, 2022
324aa2d
Merge branch 'dev/migrie/b/alt-buffer-terminal' into dev/migrie/b/349…
zadjii-msft Apr 1, 2022
9eb0658
Adds a test for https://github.com/microsoft/terminal/pull/12719#disc…
zadjii-msft Apr 1, 2022
3b1ced3
well that explains why I never saw this :P
zadjii-msft Apr 1, 2022
0e711c0
this is barking up the wrong tree
zadjii-msft Apr 1, 2022
31f9904
Revert "this is barking up the wrong tree"
zadjii-msft Apr 1, 2022
0ad1478
Add oneline test case to comments
zadjii-msft Apr 4, 2022
48d47f8
Just entirely revert the main buffer deferred resizing. This shows th…
zadjii-msft Apr 4, 2022
d7bce5a
Revert "Just entirely revert the main buffer deferred resizing. This …
zadjii-msft Apr 5, 2022
2285ea5
Hey look this worked
zadjii-msft Apr 5, 2022
0e8a917
more tests
zadjii-msft Apr 5, 2022
764237c
add a helper to do this all in one place
zadjii-msft Apr 5, 2022
3a52eab
A test that repros the crash
zadjii-msft Apr 5, 2022
4c1404d
Fix underlying bug in AltBufferResizeCrash
lhecker Apr 5, 2022
4a05760
notes
zadjii-msft Apr 6, 2022
002fbbc
Merge remote-tracking branch 'origin/main' into dev/migrie/b/alt-buff…
zadjii-msft Apr 12, 2022
f209d98
fix the urls in the alt buffer
zadjii-msft Apr 12, 2022
66fa2dc
Merge commit 'f209d98e4310d8df57ca5b0d4e36700a140ba0bd' into dev/migr…
DHowett Apr 12, 2022
554fb8f
Merge remote-tracking branch 'origin/main' into HEAD
DHowett Apr 12, 2022
2a9eae3
Merge commit '554fb8f4a' into dev/migrie/b/3493-no-wrap-alt-buffer
DHowett Apr 12, 2022
f2677bd
runformat on the merge
zadjii-msft Apr 13, 2022
098f6e3
all together, these tests can pollute each other now
zadjii-msft Apr 13, 2022
e14bba4
Merge remote-tracking branch 'origin/main' into dev/migrie/b/3493-no-…
zadjii-msft Apr 14, 2022
6b89642
POC: You can test the input sent to the connection
zadjii-msft Apr 14, 2022
bea906d
better, better, alright lets do this
zadjii-msft Apr 14, 2022
fe35515
this is what you call test-driven-development
zadjii-msft Apr 14, 2022
f6da747
this is the real test now
zadjii-msft Apr 14, 2022
3360201
tests work, which will make Dustin very happy
zadjii-msft Apr 14, 2022
23c6bf8
as discussed with dustin
zadjii-msft Apr 14, 2022
d157184
I actually had the wrong issue numbers
zadjii-msft Apr 14, 2022
93dbb9e
it wouldn't be a mike PR without x86 breaks and spel check
zadjii-msft Apr 14, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 74 additions & 78 deletions src/cascadia/TerminalCore/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,38 @@ void Terminal::UpdateAppearance(const ICoreAppearance& appearance)
// appropriate HRESULT for failing to resize.
[[nodiscard]] HRESULT Terminal::UserResize(const COORD viewportSize) noexcept
{
const auto oldDimensions = _mutableViewport.Dimensions();
const auto oldDimensions = _GetMutableViewport().Dimensions();
if (viewportSize == oldDimensions)
{
return S_FALSE;
}

// Shortcut: if we're in the alt buffer, just resize the
// alt buffer and put off resizing the main buffer till we switch back. Fortunately, this is easy. We don't need to
// worry about the viewport and scrollback at all! The alt buffer never has
// any scrollback, so we just need to resize it and presto, we're done.
if (_inAltBuffer())
{
// stash this resize for the future.
_deferredResize = til::size{ viewportSize };

_altBuffer->GetCursor().StartDeferDrawing();
// we're capturing `this` here because when we exit, we want to EndDefer on the (newly created) active buffer.
auto endDefer = wil::scope_exit([this]() noexcept { _altBuffer->GetCursor().EndDeferDrawing(); });

// GH#3494: We don't need to reflow the alt buffer. Apps that use the
Copy link
Member

Choose a reason for hiding this comment

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

Beautiful.

// alt buffer will redraw themselves. This prevents graphical artifacts.
//
// This is consistent with VTE
RETURN_IF_FAILED(_altBuffer->ResizeTraditional(viewportSize));

// Since the _mutableViewport is no longer the size of the actual
// viewport, then update our _altBufferSize tracker we're using to help
// us out here.
_altBufferSize = til::size{ viewportSize };
return S_OK;
}

const auto dx = ::base::ClampSub(viewportSize.X, oldDimensions.X);
const short newBufferHeight = ::base::ClampAdd(viewportSize.Y, _scrollbackLines);

Expand Down Expand Up @@ -415,48 +441,6 @@ void Terminal::UpdateAppearance(const ICoreAppearance& appearance)
// before, and shouldn't be now either.
_scrollOffset = originalOffsetWasZero ? 0 : static_cast<int>(::base::ClampSub(_mutableViewport.Top(), newVisibleTop));

// Now that we've finished the hard work of resizing the main buffer and
// getting the viewport back into the right spot, we need to ALSO resize the
// alt buffer, if one exists. Fortunately, this is easy. We don't need to
// worry about the viewport and scrollback at all! The alt buffer never has
// any scrollback, so we just need to resize it and presto, we're done.
if (_inAltBuffer())
{
_altBuffer->GetCursor().StartDeferDrawing();
// we're capturing `this` here because when we exit, we want to EndDefer on the (newly created) active buffer.
auto endDefer = wil::scope_exit([this]() noexcept { _altBuffer->GetCursor().EndDeferDrawing(); });

// First allocate a new text buffer to take the place of the current one.
std::unique_ptr<TextBuffer> newTextBuffer;
try
{
// GH#3848 - Stash away the current attributes
const auto oldBufferAttributes = _altBuffer->GetCurrentAttributes();
newTextBuffer = std::make_unique<TextBuffer>(bufferSize,
TextAttribute{},
0, // temporarily set size to 0 so it won't render.
true,
_mainBuffer->GetRenderer());

// start defer drawing on the new buffer
newTextBuffer->GetCursor().StartDeferDrawing();

// We don't need any fancy position information. We're just gonna
// resize the buffer, it's gonna be in exactly the place it is now.
// There's no scrollback to worry about!

RETURN_IF_FAILED(TextBuffer::Reflow(*_altBuffer.get(),
*newTextBuffer.get(),
_GetMutableViewport(),
std::nullopt));

// Restore the active text attributes
newTextBuffer->SetCurrentAttributes(oldBufferAttributes);
_altBuffer.swap(newTextBuffer);
}
CATCH_RETURN();
}

// GH#5029 - make sure to InvalidateAll here, so that we'll paint the entire visible viewport.
try
{
Expand Down Expand Up @@ -723,7 +707,7 @@ bool Terminal::SendMouseEvent(const COORD viewportPos, const unsigned int uiButt
// clamp them to be within the range [(0, 0), (W, H)].
#pragma warning(suppress : 26496) // analysis can't tell we're assigning through a reference below
auto clampedPos{ viewportPos };
_mutableViewport.ToOrigin().Clamp(clampedPos);
_GetMutableViewport().ToOrigin().Clamp(clampedPos);
return _terminalInput->HandleMouse(clampedPos, uiButton, GET_KEYSTATE_WPARAM(states.Value()), wheelDelta, state);
}

Expand Down Expand Up @@ -951,7 +935,10 @@ WORD Terminal::_TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept

Viewport Terminal::_GetMutableViewport() const noexcept
{
return _inAltBuffer() ? Viewport::FromDimensions(_mutableViewport.Dimensions()) :
// GH#3493: if we're in the alt buffer, then it's possible that the mutable
// viewport's size hasn't been updated yet. In that case, use the
// temporarily stashed _altBufferSize instead.
return _inAltBuffer() ? Viewport::FromDimensions(_altBufferSize.to_win32_coord()) :
_mutableViewport;
}

Expand All @@ -968,7 +955,7 @@ int Terminal::ViewStartIndex() const noexcept

int Terminal::ViewEndIndex() const noexcept
{
return _inAltBuffer() ? _mutableViewport.Height() - 1 : _mutableViewport.BottomInclusive();
return _inAltBuffer() ? _altBufferSize.height : _mutableViewport.BottomInclusive();
}

// _VisibleStartIndex is the first visible line of the buffer
Expand All @@ -986,9 +973,14 @@ int Terminal::_VisibleEndIndex() const noexcept

Viewport Terminal::_GetVisibleViewport() const noexcept
{
// GH#3493: if we're in the alt buffer, then it's possible that the mutable
// viewport's size hasn't been updated yet. In that case, use the
// temporarily stashed _altBufferSize instead.
const COORD origin{ 0, gsl::narrow<short>(_VisibleStartIndex()) };
const COORD size{ _inAltBuffer() ? _altBufferSize.to_win32_coord() :
Copy link
Member

Choose a reason for hiding this comment

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

This is almost aching for another helper ... or, seems like you could use the Dimensions off the GetMutableViewport helper's return value? I'd prefer concentrating the magic in one place. :)

Copy link
Member

@lhecker lhecker Apr 13, 2022

Choose a reason for hiding this comment

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

Can't we make a til::rect _viewports[2] and index into it with a bool _altBufferInUse?
Like... _viewports[_altBufferInUse].to_win32_coord() or something like that...
(I didn't say anything since we're already stretched thin as is.)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Bear in mind that we'll probably need to support more buffers than just main and alt in the long term. So I'd expect properties like this would ultimately be part of the text buffer itself, and then you could just do something like _activeBuffer().mutableViewport. But we also need to get the architecture synced up with conhost, which currently manages this sort of state in the SCREEN_INFORMATION class. Not that I'm saying this is essential for now, but if you're trying to clean up the code, it's best to also consider how we are likely to extend this in the future.

_mutableViewport.Dimensions() };
return Viewport::FromDimensions(origin,
_mutableViewport.Dimensions());
size);
}

// Writes a string of text to the buffer, then moves the cursor (and viewport)
Expand Down Expand Up @@ -1121,46 +1113,50 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition)
cursor.SetPosition(proposedCursorPosition);

// Move the viewport down if the cursor moved below the viewport.
bool updatedViewport = false;
const auto scrollAmount = std::max(0, proposedCursorPosition.Y - _mutableViewport.BottomInclusive());
if (scrollAmount > 0)
// Obviously, don't need to do this in the alt buffer.
if (!_inAltBuffer())
{
const auto newViewTop = std::max(0, proposedCursorPosition.Y - (_mutableViewport.Height() - 1));
// In the alt buffer, we never need to adjust _mutableViewport, which is the viewport of the main buffer.
if (!_inAltBuffer() && newViewTop != _mutableViewport.Top())
bool updatedViewport = false;
const auto scrollAmount = std::max(0, proposedCursorPosition.Y - _mutableViewport.BottomInclusive());
if (scrollAmount > 0)
{
_mutableViewport = Viewport::FromDimensions({ 0, gsl::narrow<short>(newViewTop) },
_mutableViewport.Dimensions());
updatedViewport = true;
const auto newViewTop = std::max(0, proposedCursorPosition.Y - (_mutableViewport.Height() - 1));
// In the alt buffer, we never need to adjust _mutableViewport, which is the viewport of the main buffer.
if (newViewTop != _mutableViewport.Top())
{
_mutableViewport = Viewport::FromDimensions({ 0, gsl::narrow<short>(newViewTop) },
_mutableViewport.Dimensions());
updatedViewport = true;
}
}
}

// If the viewport moved, or we circled the buffer, we might need to update
// our _scrollOffset
if (!_inAltBuffer() && (updatedViewport || newRows != 0))
{
const auto oldScrollOffset = _scrollOffset;
// If the viewport moved, or we circled the buffer, we might need to update
// our _scrollOffset
if (updatedViewport || newRows != 0)
{
const auto oldScrollOffset = _scrollOffset;

// scroll if...
// - no selection is active
// - viewport is already at the bottom
const bool scrollToOutput = !IsSelectionActive() && _scrollOffset == 0;
// scroll if...
// - no selection is active
// - viewport is already at the bottom
const bool scrollToOutput = !IsSelectionActive() && _scrollOffset == 0;

_scrollOffset = scrollToOutput ? 0 : _scrollOffset + scrollAmount + newRows;
_scrollOffset = scrollToOutput ? 0 : _scrollOffset + scrollAmount + newRows;

// Clamp the range to make sure that we don't scroll way off the top of the buffer
_scrollOffset = std::clamp(_scrollOffset,
0,
_activeBuffer().GetSize().Height() - _mutableViewport.Height());
// Clamp the range to make sure that we don't scroll way off the top of the buffer
_scrollOffset = std::clamp(_scrollOffset,
0,
_activeBuffer().GetSize().Height() - _mutableViewport.Height());

// If the new scroll offset is different, then we'll still want to raise a scroll event
updatedViewport = updatedViewport || (oldScrollOffset != _scrollOffset);
}
// If the new scroll offset is different, then we'll still want to raise a scroll event
updatedViewport = updatedViewport || (oldScrollOffset != _scrollOffset);
}

// If the viewport moved, then send a scrolling notification.
if (updatedViewport)
{
_NotifyScrollEvent();
// If the viewport moved, then send a scrolling notification.
if (updatedViewport)
{
_NotifyScrollEvent();
}
}

if (rowsPushedOffTopOfBuffer != 0)
Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalCore/Terminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,9 @@ class Microsoft::Terminal::Core::Terminal final :
SHORT _scrollbackLines;
bool _detectURLs{ false };

til::size _altBufferSize;
Copy link
Member

Choose a reason for hiding this comment

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

Yeah that makes sense. In hindsight, it's obvious that it was easier to mess this up when both buffers were sharing.

std::optional<til::size> _deferredResize{ std::nullopt };

// _scrollOffset is the number of lines above the viewport that are currently visible
// If _scrollOffset is 0, then the visible region of the buffer is the viewport.
int _scrollOffset;
Expand Down
11 changes: 9 additions & 2 deletions src/cascadia/TerminalCore/TerminalApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -598,14 +598,15 @@ void Terminal::PopGraphicsRendition()
void Terminal::UseAlternateScreenBuffer()
{
// the new alt buffer is exactly the size of the viewport.
const COORD bufferSize{ _mutableViewport.Dimensions() };
_altBufferSize = til::size{ _mutableViewport.Dimensions() };

const auto cursorSize = _mainBuffer->GetCursor().GetSize();

ClearSelection();
_mainBuffer->ClearPatternRecognizers();

// Create a new alt buffer
_altBuffer = std::make_unique<TextBuffer>(bufferSize,
_altBuffer = std::make_unique<TextBuffer>(_altBufferSize.to_win32_coord(),
TextAttribute{},
cursorSize,
true,
Expand Down Expand Up @@ -674,6 +675,12 @@ void Terminal::UseMainScreenBuffer()
// destroy the alt buffer
_altBuffer = nullptr;

if (_deferredResize.has_value())
{
LOG_IF_FAILED(UserResize(_deferredResize.value().to_win32_coord()));
_deferredResize = std::nullopt;
}

// update all the hyperlinks on the screen
_mainBuffer->ClearPatternRecognizers();
_updateUrlDetection();
Expand Down
8 changes: 4 additions & 4 deletions src/cascadia/TerminalCore/TerminalSelection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -426,15 +426,15 @@ void Terminal::_MoveByViewport(SelectionDirection direction, COORD& pos)
break;
case SelectionDirection::Up:
{
const auto viewportHeight{ _mutableViewport.Height() };
const auto viewportHeight{ _GetMutableViewport().Height() };
const auto newY{ base::ClampSub<short, short>(pos.Y, viewportHeight) };
pos = newY < bufferSize.Top() ? bufferSize.Origin() : COORD{ pos.X, newY };
break;
}
case SelectionDirection::Down:
{
const auto viewportHeight{ _mutableViewport.Height() };
const auto mutableBottom{ _mutableViewport.BottomInclusive() };
const auto viewportHeight{ _GetMutableViewport().Height() };
const auto mutableBottom{ _GetMutableViewport().BottomInclusive() };
const auto newY{ base::ClampAdd<short, short>(pos.Y, viewportHeight) };
pos = newY > mutableBottom ? COORD{ bufferSize.RightInclusive(), mutableBottom } : COORD{ pos.X, newY };
break;
Expand All @@ -453,7 +453,7 @@ void Terminal::_MoveByBuffer(SelectionDirection direction, COORD& pos)
break;
case SelectionDirection::Right:
case SelectionDirection::Down:
pos = { bufferSize.RightInclusive(), _mutableViewport.BottomInclusive() };
pos = { bufferSize.RightInclusive(), _GetMutableViewport().BottomInclusive() };
break;
}
}
Expand Down
Loading