From 13fb1f58d0b7e4c0865c34d05c3527a1eced6257 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 12 Apr 2022 17:19:46 -0500 Subject: [PATCH] Enable using the alt buffer in the Terminal (#12561) This PR allows the Terminal to actually use the alt buffer appropriately. Currently, we just render the alt buffer state into the main buffer and that is wild. It means things like `vim` will let the user scroll up to see the previous history (which it shouldn't). Very first thing this PR does: updates the `{Trigger|Invalidate}Circling` methods to instead be `{Trigger|Invalidate}Flush(bool circling)`. We need this so that when an app requests the alt buffer in conpty, we can immediately flush the frame before asking the Terminal side to switch to the other buffer. The `Circling` methods was a great place to do this, but we don't actually want to set the circled flag in VtRenderer when that happens just for a flush. The Terminal's implementation is a little different than conhost's. Conhost's implementation grew organically, so I had it straight up create an entire new screen buffer for the alt buffer. The Terminal doesn't need all that! All we need to do is have a separate `TextBuffer` for the alt buffer contents. This makes other parts easier as well - we don't really need to do anything with the `_mutableViewport` in the alt buffer, because it's always in the same place. So, we can just leave it alone and when we come back to the main buffer, there it is. Helper methods have been updated to account for this. * [x] Closes #381 * [x] Closes #3492 * #3686, #3082, #3321, #3493 are all good follow-ups here. --- src/buffer/out/textBuffer.cpp | 2 +- src/cascadia/TerminalCore/ITerminalApi.hpp | 3 + src/cascadia/TerminalCore/Terminal.cpp | 188 +++++--- src/cascadia/TerminalCore/Terminal.hpp | 12 +- src/cascadia/TerminalCore/TerminalApi.cpp | 210 ++++++--- .../TerminalCore/TerminalDispatch.cpp | 84 ++++ .../TerminalCore/TerminalDispatch.hpp | 22 + .../TerminalCore/TerminalSelection.cpp | 58 +-- .../TerminalCore/terminalrenderdata.cpp | 24 +- .../ConptyRoundtripTests.cpp | 400 ++++++++++++++++-- .../UnitTests_TerminalCore/ScrollTest.cpp | 2 +- .../TerminalApiTest.cpp | 40 +- .../TerminalBufferTests.cpp | 20 +- src/host/VtIo.cpp | 10 + src/host/VtIo.hpp | 2 + src/host/screenInfo.cpp | 19 + src/interactivity/onecore/BgfxEngine.cpp | 6 - src/interactivity/onecore/BgfxEngine.hpp | 1 - src/renderer/atlas/AtlasEngine.api.cpp | 2 +- src/renderer/atlas/AtlasEngine.h | 2 +- src/renderer/base/RenderEngineBase.cpp | 16 + src/renderer/base/renderer.cpp | 4 +- src/renderer/base/renderer.hpp | 2 +- src/renderer/dx/DxRenderer.cpp | 14 - src/renderer/dx/DxRenderer.hpp | 1 - src/renderer/gdi/gdirenderer.hpp | 1 - src/renderer/gdi/invalidate.cpp | 15 - src/renderer/inc/IRenderEngine.hpp | 2 +- src/renderer/inc/RenderEngineBase.hpp | 2 + src/renderer/uia/UiaRenderer.cpp | 14 - src/renderer/uia/UiaRenderer.hpp | 1 - src/renderer/vt/VtSequences.cpp | 11 + src/renderer/vt/invalidate.cpp | 4 +- src/renderer/vt/state.cpp | 7 + src/renderer/vt/vtrenderer.hpp | 4 +- src/renderer/wddmcon/WddmConRenderer.cpp | 6 - src/renderer/wddmcon/WddmConRenderer.hpp | 1 - 37 files changed, 915 insertions(+), 297 deletions(-) diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 274bde52ebf..b2bc461e124 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -562,7 +562,7 @@ bool TextBuffer::IncrementCircularBuffer(const bool inVtMode) // to the logical position 0 in the window (cursor coordinates and all other coordinates). if (_isActiveBuffer) { - _renderer.TriggerCircling(); + _renderer.TriggerFlush(true); } // Prune hyperlinks to delete obsolete references diff --git a/src/cascadia/TerminalCore/ITerminalApi.hpp b/src/cascadia/TerminalCore/ITerminalApi.hpp index 3aebe7ea1c8..7f41e0ce71f 100644 --- a/src/cascadia/TerminalCore/ITerminalApi.hpp +++ b/src/cascadia/TerminalCore/ITerminalApi.hpp @@ -69,6 +69,9 @@ namespace Microsoft::Terminal::Core virtual void PushGraphicsRendition(const ::Microsoft::Console::VirtualTerminal::VTParameters options) = 0; virtual void PopGraphicsRendition() = 0; + virtual void UseAlternateScreenBuffer() = 0; + virtual void UseMainScreenBuffer() = 0; + protected: ITerminalApi() = default; }; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 5db2cff6b9e..41951c72acf 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -40,6 +40,7 @@ Terminal::Terminal() : _mutableViewport{ Viewport::Empty() }, _title{}, _pfnWriteInput{ nullptr }, + _altBuffer{ nullptr }, _scrollOffset{ 0 }, _snapOnInput{ true }, _altGrAliasing{ true }, @@ -85,7 +86,7 @@ void Terminal::Create(COORD viewportSize, SHORT scrollbackLines, Renderer& rende Utils::ClampToShortMax(viewportSize.Y + scrollbackLines, 1) }; const TextAttribute attr{}; const UINT cursorSize = 12; - _buffer = std::make_unique(bufferSize, attr, cursorSize, true, renderer); + _mainBuffer = std::make_unique(bufferSize, attr, cursorSize, true, renderer); } // Method Description: @@ -147,21 +148,14 @@ void Terminal::UpdateSettings(ICoreSettings settings) // size is smaller than where the mutable viewport currently is, we'll want // to make sure to rotate the buffer contents upwards, so the mutable viewport // remains at the bottom of the buffer. - if (_buffer) + + // Regenerate the pattern tree for the new buffer size + if (_mainBuffer) { // Clear the patterns first - _buffer->ClearPatternRecognizers(); - if (settings.DetectURLs()) - { - // Add regex pattern recognizers to the buffer - // For now, we only add the URI regex pattern - _hyperlinkPatternId = _buffer->AddPatternRecognizer(linkPattern); - UpdatePatternsUnderLock(); - } - else - { - ClearPatternTree(); - } + _mainBuffer->ClearPatternRecognizers(); + _detectURLs = settings.DetectURLs(); + _updateUrlDetection(); } } @@ -213,9 +207,12 @@ void Terminal::UpdateAppearance(const ICoreAppearance& appearance) break; } - if (_buffer) + // We're checking if the main buffer exists here, but then setting the + // appearance of the active one. If the main buffer exists, then at least + // one buffer exists and _activeBuffer() will work + if (_mainBuffer) { - _buffer->GetCursor().SetStyle(appearance.CursorHeight(), cursorShape); + _activeBuffer().GetCursor().SetStyle(appearance.CursorHeight(), cursorShape); } _defaultCursorShape = cursorShape; @@ -252,9 +249,9 @@ void Terminal::UpdateAppearance(const ICoreAppearance& appearance) const bool originalOffsetWasZero = _scrollOffset == 0; // skip any drawing updates that might occur until we swap _buffer with the new buffer or if we exit early. - _buffer->GetCursor().StartDeferDrawing(); - // we're capturing _buffer by reference here because when we exit, we want to EndDefer on the current active buffer. - auto endDefer = wil::scope_exit([&]() noexcept { _buffer->GetCursor().EndDeferDrawing(); }); + _mainBuffer->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 { _mainBuffer->GetCursor().EndDeferDrawing(); }); // First allocate a new text buffer to take the place of the current one. std::unique_ptr newTextBuffer; @@ -265,13 +262,14 @@ void Terminal::UpdateAppearance(const ICoreAppearance& appearance) // but after the resize, we'll want to make sure that the new buffer's // current attributes (the ones used for printing new text) match the // old buffer's. - const auto oldBufferAttributes = _buffer->GetCurrentAttributes(); + const auto oldBufferAttributes = _mainBuffer->GetCurrentAttributes(); newTextBuffer = std::make_unique(bufferSize, TextAttribute{}, 0, // temporarily set size to 0 so it won't render. - _buffer->IsActiveBuffer(), - _buffer->GetRenderer()); + _mainBuffer->IsActiveBuffer(), + _mainBuffer->GetRenderer()); + // start defer drawing on the new buffer newTextBuffer->GetCursor().StartDeferDrawing(); // Build a PositionInformation to track the position of both the top of @@ -289,7 +287,7 @@ void Terminal::UpdateAppearance(const ICoreAppearance& appearance) oldRows.visibleViewportTop = newVisibleTop; const std::optional oldViewStart{ oldViewportTop }; - RETURN_IF_FAILED(TextBuffer::Reflow(*_buffer.get(), + RETURN_IF_FAILED(TextBuffer::Reflow(*_mainBuffer.get(), *newTextBuffer.get(), _mutableViewport, { oldRows })); @@ -405,7 +403,7 @@ void Terminal::UpdateAppearance(const ICoreAppearance& appearance) _mutableViewport = Viewport::FromDimensions({ 0, proposedTop }, viewportSize); - _buffer.swap(newTextBuffer); + _mainBuffer.swap(newTextBuffer); // GH#3494: Maintain scrollbar position during resize // Make sure that we don't scroll past the mutableViewport at the bottom of the buffer @@ -417,10 +415,52 @@ void Terminal::UpdateAppearance(const ICoreAppearance& appearance) // before, and shouldn't be now either. _scrollOffset = originalOffsetWasZero ? 0 : static_cast(::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 newTextBuffer; + try + { + // GH#3848 - Stash away the current attributes + const auto oldBufferAttributes = _altBuffer->GetCurrentAttributes(); + newTextBuffer = std::make_unique(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 { - _buffer->TriggerRedrawAll(); + _activeBuffer().TriggerRedrawAll(); } CATCH_LOG(); _NotifyScrollEvent(); @@ -432,7 +472,7 @@ void Terminal::Write(std::wstring_view stringView) { auto lock = LockForWriting(); - auto& cursor = _buffer->GetCursor(); + auto& cursor = _activeBuffer().GetCursor(); const til::point cursorPosBefore{ cursor.GetPosition() }; _stateMachine->ProcessString(stringView); @@ -502,10 +542,10 @@ bool Terminal::IsTrackingMouseInput() const noexcept // - The position std::wstring Terminal::GetHyperlinkAtPosition(const COORD position) { - auto attr = _buffer->GetCellDataAt(_ConvertToBufferCell(position))->TextAttr(); + auto attr = _activeBuffer().GetCellDataAt(_ConvertToBufferCell(position))->TextAttr(); if (attr.IsHyperlink()) { - auto uri = _buffer->GetHyperlinkUriFromId(attr.GetHyperlinkId()); + auto uri = _activeBuffer().GetHyperlinkUriFromId(attr.GetHyperlinkId()); return uri; } // also look through our known pattern locations in our pattern interval tree @@ -516,8 +556,8 @@ std::wstring Terminal::GetHyperlinkAtPosition(const COORD position) const auto end = result->stop; std::wstring uri; - const auto startIter = _buffer->GetCellDataAt(_ConvertToBufferCell(start.to_win32_coord())); - const auto endIter = _buffer->GetCellDataAt(_ConvertToBufferCell(end.to_win32_coord())); + const auto startIter = _activeBuffer().GetCellDataAt(_ConvertToBufferCell(start.to_win32_coord())); + const auto endIter = _activeBuffer().GetCellDataAt(_ConvertToBufferCell(end.to_win32_coord())); for (auto iter = startIter; iter != endIter; ++iter) { uri += iter->Chars(); @@ -535,7 +575,7 @@ std::wstring Terminal::GetHyperlinkAtPosition(const COORD position) // - The hyperlink ID uint16_t Terminal::GetHyperlinkIdAtPosition(const COORD position) { - return _buffer->GetCellDataAt(_ConvertToBufferCell(position))->TextAttr().GetHyperlinkId(); + return _activeBuffer().GetCellDataAt(_ConvertToBufferCell(position))->TextAttr().GetHyperlinkId(); } // Method description: @@ -732,26 +772,26 @@ void Terminal::_InvalidateFromCoords(const COORD start, const COORD end) if (start.Y == end.Y) { SMALL_RECT region{ start.X, start.Y, end.X, end.Y }; - _buffer->TriggerRedraw(Viewport::FromInclusive(region)); + _activeBuffer().TriggerRedraw(Viewport::FromInclusive(region)); } else { - const auto rowSize = gsl::narrow(_buffer->GetRowByOffset(0).size()); + const auto rowSize = gsl::narrow(_activeBuffer().GetRowByOffset(0).size()); // invalidate the first line SMALL_RECT region{ start.X, start.Y, gsl::narrow(rowSize - 1), gsl::narrow(start.Y) }; - _buffer->TriggerRedraw(Viewport::FromInclusive(region)); + _activeBuffer().TriggerRedraw(Viewport::FromInclusive(region)); if ((end.Y - start.Y) > 1) { // invalidate the lines in between the first and last line region = SMALL_RECT{ 0, start.Y + 1, gsl::narrow(rowSize - 1), gsl::narrow(end.Y - 1) }; - _buffer->TriggerRedraw(Viewport::FromInclusive(region)); + _activeBuffer().TriggerRedraw(Viewport::FromInclusive(region)); } // invalidate the last line region = SMALL_RECT{ 0, end.Y, end.X, end.Y }; - _buffer->TriggerRedraw(Viewport::FromInclusive(region)); + _activeBuffer().TriggerRedraw(Viewport::FromInclusive(region)); } } @@ -897,34 +937,37 @@ WORD Terminal::_TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept Viewport Terminal::_GetMutableViewport() const noexcept { - return _mutableViewport; + return _inAltBuffer() ? Viewport::FromDimensions(_mutableViewport.Dimensions()) : + _mutableViewport; } short Terminal::GetBufferHeight() const noexcept { - return _mutableViewport.BottomExclusive(); + return _GetMutableViewport().BottomExclusive(); } // ViewStartIndex is also the length of the scrollback int Terminal::ViewStartIndex() const noexcept { - return _mutableViewport.Top(); + return _inAltBuffer() ? 0 : _mutableViewport.Top(); } int Terminal::ViewEndIndex() const noexcept { - return _mutableViewport.BottomInclusive(); + return _inAltBuffer() ? _mutableViewport.Height() - 1 : _mutableViewport.BottomInclusive(); } // _VisibleStartIndex is the first visible line of the buffer int Terminal::_VisibleStartIndex() const noexcept { - return std::max(0, ViewStartIndex() - _scrollOffset); + return _inAltBuffer() ? ViewStartIndex() : + std::max(0, ViewStartIndex() - _scrollOffset); } int Terminal::_VisibleEndIndex() const noexcept { - return std::max(0, ViewEndIndex() - _scrollOffset); + return _inAltBuffer() ? ViewEndIndex() : + std::max(0, ViewEndIndex() - _scrollOffset); } Viewport Terminal::_GetVisibleViewport() const noexcept @@ -944,7 +987,7 @@ Viewport Terminal::_GetVisibleViewport() const noexcept // I had to make a bunch of hacks to get Japanese and emoji to work-ish. void Terminal::_WriteBuffer(const std::wstring_view& stringView) { - auto& cursor = _buffer->GetCursor(); + auto& cursor = _activeBuffer().GetCursor(); // Defer the cursor drawing while we are iterating the string, for a better performance. // We can not waste time displaying a cursor event when we know more text is coming right behind it. @@ -963,8 +1006,8 @@ void Terminal::_WriteBuffer(const std::wstring_view& stringView) // from the stringView to form a single code point. const auto isSurrogate = wch >= 0xD800 && wch <= 0xDFFF; const auto view = stringView.substr(i, isSurrogate ? 2 : 1); - const OutputCellIterator it{ view, _buffer->GetCurrentAttributes() }; - const auto end = _buffer->Write(it); + const OutputCellIterator it{ view, _activeBuffer().GetCurrentAttributes() }; + const auto end = _activeBuffer().Write(it); const auto cellDistance = end.GetCellDistance(it); const auto inputDistance = end.GetInputDistance(it); @@ -1006,7 +1049,7 @@ void Terminal::_WriteBuffer(const std::wstring_view& stringView) // Notify UIA of new text. // It's important to do this here instead of in TextBuffer, because here you have access to the entire line of text, // whereas TextBuffer writes it one character at a time via the OutputCellIterator. - _buffer->TriggerNewTextNotification(stringView); + _activeBuffer().TriggerNewTextNotification(stringView); cursor.EndDeferDrawing(); } @@ -1015,8 +1058,8 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition) { #pragma warning(suppress : 26496) // cpp core checks wants this const but it's modified below. auto proposedCursorPosition = proposedPosition; - auto& cursor = _buffer->GetCursor(); - const Viewport bufferSize = _buffer->GetSize(); + auto& cursor = _activeBuffer().GetCursor(); + const Viewport bufferSize = _activeBuffer().GetSize(); // If we're about to scroll past the bottom of the buffer, instead cycle the // buffer. @@ -1026,7 +1069,7 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition) { for (auto dy = 0; dy < newRows; dy++) { - _buffer->IncrementCircularBuffer(); + _activeBuffer().IncrementCircularBuffer(); proposedCursorPosition.Y--; rowsPushedOffTopOfBuffer++; @@ -1069,7 +1112,8 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition) if (scrollAmount > 0) { const auto newViewTop = std::max(0, proposedCursorPosition.Y - (_mutableViewport.Height() - 1)); - if (newViewTop != _mutableViewport.Top()) + // In the alt buffer, we never need to adjust _mutableViewport, which is the viewport of the main buffer. + if (!_inAltBuffer() && newViewTop != _mutableViewport.Top()) { _mutableViewport = Viewport::FromDimensions({ 0, gsl::narrow(newViewTop) }, _mutableViewport.Dimensions()); @@ -1079,7 +1123,7 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition) // If the viewport moved, or we circled the buffer, we might need to update // our _scrollOffset - if (updatedViewport || newRows != 0) + if (!_inAltBuffer() && (updatedViewport || newRows != 0)) { const auto oldScrollOffset = _scrollOffset; @@ -1093,7 +1137,7 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition) // Clamp the range to make sure that we don't scroll way off the top of the buffer _scrollOffset = std::clamp(_scrollOffset, 0, - _buffer->GetSize().Height() - _mutableViewport.Height()); + _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); @@ -1111,12 +1155,17 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition) // That didn't change the viewport and therefore the TriggerScroll(void) // method can't detect the delta on its own. COORD delta{ 0, gsl::narrow_cast(-rowsPushedOffTopOfBuffer) }; - _buffer->TriggerScroll(delta); + _activeBuffer().TriggerScroll(delta); } } void Terminal::UserScrollViewport(const int viewTop) { + if (_inAltBuffer()) + { + return; + } + // we're going to modify state here that the renderer could be reading. auto lock = LockForWriting(); @@ -1130,7 +1179,7 @@ void Terminal::UserScrollViewport(const int viewTop) // We can use the void variant of TriggerScroll here because // we adjusted the viewport so it can detect the difference // from the previous frame drawn. - _buffer->TriggerScroll(); + _activeBuffer().TriggerScroll(); } int Terminal::GetScrollOffset() noexcept @@ -1229,12 +1278,12 @@ void Microsoft::Terminal::Core::Terminal::TaskbarProgressChangedCallback(std::fu void Terminal::SetCursorOn(const bool isOn) { auto lock = LockForWriting(); - _buffer->GetCursor().SetIsOn(isOn); + _activeBuffer().GetCursor().SetIsOn(isOn); } bool Terminal::IsCursorBlinkingAllowed() const noexcept { - const auto& cursor = _buffer->GetCursor(); + const auto& cursor = _activeBuffer().GetCursor(); return cursor.IsBlinkingAllowed(); } @@ -1246,7 +1295,7 @@ bool Terminal::IsCursorBlinkingAllowed() const noexcept void Terminal::UpdatePatternsUnderLock() noexcept { auto oldTree = _patternIntervalTree; - _patternIntervalTree = _buffer->GetPatterns(_VisibleStartIndex(), _VisibleEndIndex()); + _patternIntervalTree = _activeBuffer().GetPatterns(_VisibleStartIndex(), _VisibleEndIndex()); _InvalidatePatternTree(oldTree); _InvalidatePatternTree(_patternIntervalTree); } @@ -1343,3 +1392,28 @@ void Terminal::ApplyScheme(const Scheme& colorScheme) _renderSettings.MakeAdjustedColorArray(); } + +bool Terminal::_inAltBuffer() const noexcept +{ + return _altBuffer != nullptr; +} + +TextBuffer& Terminal::_activeBuffer() const noexcept +{ + return _inAltBuffer() ? *_altBuffer : *_mainBuffer; +} + +void Terminal::_updateUrlDetection() +{ + if (_detectURLs) + { + // Add regex pattern recognizers to the buffer + // For now, we only add the URI regex pattern + _hyperlinkPatternId = _activeBuffer().AddPatternRecognizer(linkPattern); + UpdatePatternsUnderLock(); + } + else + { + ClearPatternTree(); + } +} diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index d094115d4de..4fd6a20ded6 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -138,6 +138,8 @@ class Microsoft::Terminal::Core::Terminal final : void PushGraphicsRendition(const ::Microsoft::Console::VirtualTerminal::VTParameters options) override; void PopGraphicsRendition() override; + void UseAlternateScreenBuffer() override; + void UseMainScreenBuffer() override; #pragma endregion #pragma region ITerminalInput @@ -319,11 +321,11 @@ class Microsoft::Terminal::Core::Terminal final : SelectionExpansion _multiClickSelectionMode; #pragma endregion - // TODO: These members are not shared by an alt-buffer. They should be - // encapsulated, such that a Terminal can have both a main and alt buffer. - std::unique_ptr _buffer; + std::unique_ptr _mainBuffer; + std::unique_ptr _altBuffer; Microsoft::Console::Types::Viewport _mutableViewport; SHORT _scrollbackLines; + bool _detectURLs{ false }; // _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. @@ -375,6 +377,10 @@ class Microsoft::Terminal::Core::Terminal final : void _NotifyTerminalCursorPositionChanged() noexcept; + bool _inAltBuffer() const noexcept; + TextBuffer& _activeBuffer() const noexcept; + void _updateUrlDetection(); + #pragma region TextSelection // These methods are defined in TerminalSelection.cpp std::vector _GetSelectionRects() const noexcept; diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 5cee876e51d..36fae0efd42 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -18,7 +18,7 @@ void Terminal::PrintString(std::wstring_view stringView) TextAttribute Terminal::GetTextAttributes() const { - return _buffer->GetCurrentAttributes(); + return _activeBuffer().GetCurrentAttributes(); } bool Terminal::ReturnResponse(std::wstring_view responseString) @@ -33,12 +33,12 @@ bool Terminal::ReturnResponse(std::wstring_view responseString) void Terminal::SetTextAttributes(const TextAttribute& attrs) { - _buffer->SetCurrentAttributes(attrs); + _activeBuffer().SetCurrentAttributes(attrs); } Viewport Terminal::GetBufferSize() { - return _buffer->GetSize(); + return _activeBuffer().GetSize(); } void Terminal::SetCursorPosition(short x, short y) @@ -49,12 +49,12 @@ void Terminal::SetCursorPosition(short x, short y) const short absoluteY = viewOrigin.Y + y; COORD newPos{ absoluteX, absoluteY }; viewport.Clamp(newPos); - _buffer->GetCursor().SetPosition(newPos); + _activeBuffer().GetCursor().SetPosition(newPos); } COORD Terminal::GetCursorPosition() { - const auto absoluteCursorPos = _buffer->GetCursor().GetPosition(); + const auto absoluteCursorPos = _activeBuffer().GetCursor().GetPosition(); const auto viewport = _GetMutableViewport(); const auto viewOrigin = viewport.Origin(); const short relativeX = absoluteCursorPos.X - viewOrigin.X; @@ -73,11 +73,11 @@ COORD Terminal::GetCursorPosition() // - void Terminal::CursorLineFeed(const bool withReturn) { - auto cursorPos = _buffer->GetCursor().GetPosition(); + auto cursorPos = _activeBuffer().GetCursor().GetPosition(); // since we explicitly just moved down a row, clear the wrap status on the // row we just came from - _buffer->GetRowByOffset(cursorPos.Y).SetWrapForced(false); + _activeBuffer().GetRowByOffset(cursorPos.Y).SetWrapForced(false); cursorPos.Y++; if (withReturn) @@ -101,10 +101,12 @@ void Terminal::DeleteCharacter(const size_t count) { SHORT dist; THROW_IF_FAILED(SizeTToShort(count, &dist)); - const auto cursorPos = _buffer->GetCursor().GetPosition(); + const auto cursorPos = _activeBuffer().GetCursor().GetPosition(); const auto copyToPos = cursorPos; const COORD copyFromPos{ cursorPos.X + dist, cursorPos.Y }; - const auto sourceWidth = _mutableViewport.RightExclusive() - copyFromPos.X; + const auto viewport = _GetMutableViewport(); + + const auto sourceWidth = viewport.RightExclusive() - copyFromPos.X; SHORT width; THROW_IF_FAILED(UIntToShort(sourceWidth, &width)); @@ -121,8 +123,8 @@ void Terminal::DeleteCharacter(const size_t count) // Iterate over the source cell data and copy it over to the target do { - const auto data = OutputCell(*(_buffer->GetCellDataAt(sourcePos))); - _buffer->Write(OutputCellIterator({ &data, 1 }), targetPos); + const auto data = OutputCell(*(_activeBuffer().GetCellDataAt(sourcePos))); + _activeBuffer().Write(OutputCellIterator({ &data, 1 }), targetPos); } while (source.WalkInBounds(sourcePos, walkDirection) && target.WalkInBounds(targetPos, walkDirection)); } @@ -143,10 +145,11 @@ void Terminal::InsertCharacter(const size_t count) // TODO: GitHub issue #2163 SHORT dist; THROW_IF_FAILED(SizeTToShort(count, &dist)); - const auto cursorPos = _buffer->GetCursor().GetPosition(); + const auto cursorPos = _activeBuffer().GetCursor().GetPosition(); const auto copyFromPos = cursorPos; const COORD copyToPos{ cursorPos.X + dist, cursorPos.Y }; - const auto sourceWidth = _mutableViewport.RightExclusive() - copyFromPos.X; + const auto viewport = _GetMutableViewport(); + const auto sourceWidth = viewport.RightExclusive() - copyFromPos.X; SHORT width; THROW_IF_FAILED(UIntToShort(sourceWidth, &width)); @@ -164,21 +167,21 @@ void Terminal::InsertCharacter(const size_t count) // Iterate over the source cell data and copy it over to the target do { - const auto data = OutputCell(*(_buffer->GetCellDataAt(sourcePos))); - _buffer->Write(OutputCellIterator({ &data, 1 }), targetPos); + const auto data = OutputCell(*(_activeBuffer().GetCellDataAt(sourcePos))); + _activeBuffer().Write(OutputCellIterator({ &data, 1 }), targetPos); } while (source.WalkInBounds(sourcePos, walkDirection) && target.WalkInBounds(targetPos, walkDirection)); - const auto eraseIter = OutputCellIterator(UNICODE_SPACE, _buffer->GetCurrentAttributes(), dist); - _buffer->Write(eraseIter, cursorPos); + const auto eraseIter = OutputCellIterator(UNICODE_SPACE, _activeBuffer().GetCurrentAttributes(), dist); + _activeBuffer().Write(eraseIter, cursorPos); } void Terminal::EraseCharacters(const size_t numChars) { - const auto absoluteCursorPos = _buffer->GetCursor().GetPosition(); + const auto absoluteCursorPos = _activeBuffer().GetCursor().GetPosition(); const auto viewport = _GetMutableViewport(); const short distanceToRight = viewport.RightExclusive() - absoluteCursorPos.X; const short fillLimit = std::min(static_cast(numChars), distanceToRight); - const auto eraseIter = OutputCellIterator(UNICODE_SPACE, _buffer->GetCurrentAttributes(), fillLimit); - _buffer->Write(eraseIter, absoluteCursorPos); + const auto eraseIter = OutputCellIterator(UNICODE_SPACE, _activeBuffer().GetCurrentAttributes(), fillLimit); + _activeBuffer().Write(eraseIter, absoluteCursorPos); } // Method description: @@ -193,7 +196,7 @@ void Terminal::EraseCharacters(const size_t numChars) // - true if succeeded, false otherwise bool Terminal::EraseInLine(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType eraseType) { - const auto cursorPos = _buffer->GetCursor().GetPosition(); + const auto cursorPos = _activeBuffer().GetCursor().GetPosition(); const auto viewport = _GetMutableViewport(); COORD startPos = { 0 }; startPos.Y = cursorPos.Y; @@ -218,10 +221,10 @@ bool Terminal::EraseInLine(const ::Microsoft::Console::VirtualTerminal::Dispatch return false; } - const auto eraseIter = OutputCellIterator(UNICODE_SPACE, _buffer->GetCurrentAttributes(), nlength); + const auto eraseIter = OutputCellIterator(UNICODE_SPACE, _activeBuffer().GetCurrentAttributes(), nlength); // Explicitly turn off end-of-line wrap-flag-setting when erasing cells. - _buffer->Write(eraseIter, startPos, false); + _activeBuffer().Write(eraseIter, startPos, false); return true; } @@ -236,22 +239,33 @@ bool Terminal::EraseInLine(const ::Microsoft::Console::VirtualTerminal::Dispatch bool Terminal::EraseInDisplay(const DispatchTypes::EraseType eraseType) { // Store the relative cursor position so we can restore it later after we move the viewport - const auto cursorPos = _buffer->GetCursor().GetPosition(); + const auto cursorPos = _activeBuffer().GetCursor().GetPosition(); #pragma warning(suppress : 26496) // This is written by ConvertToOrigin, cpp core checks is wrong saying it should be const. auto relativeCursor = cursorPos; - _mutableViewport.ConvertToOrigin(&relativeCursor); + const auto viewport = _GetMutableViewport(); + + viewport.ConvertToOrigin(&relativeCursor); // Initialize the new location of the viewport // the top and bottom parameters are determined by the eraseType SMALL_RECT newWin; - newWin.Left = _mutableViewport.Left(); - newWin.Right = _mutableViewport.RightExclusive(); + newWin.Left = viewport.Left(); + newWin.Right = viewport.RightExclusive(); if (eraseType == DispatchTypes::EraseType::All) { + // If we're in the alt buffer, take a shortcut. Just increment the buffer enough to cycle the whole thing out and start fresh. This + if (_inAltBuffer()) + { + for (auto i = 0; i < viewport.Height(); i++) + { + _activeBuffer().IncrementCircularBuffer(); + } + return true; + } // In this case, we simply move the viewport down, effectively pushing whatever text was on the screen into the scrollback // and thus 'erasing' the text visible to the user - const auto coordLastChar = _buffer->GetLastNonSpaceCharacter(_mutableViewport); + const auto coordLastChar = _activeBuffer().GetLastNonSpaceCharacter(viewport); if (coordLastChar.X == 0 && coordLastChar.Y == 0) { // Nothing to clear, just return @@ -261,38 +275,38 @@ bool Terminal::EraseInDisplay(const DispatchTypes::EraseType eraseType) short sNewTop = coordLastChar.Y + 1; // Increment the circular buffer only if the new location of the viewport would be 'below' the buffer - const short delta = (sNewTop + _mutableViewport.Height()) - (_buffer->GetSize().Height()); + const short delta = (sNewTop + viewport.Height()) - (_activeBuffer().GetSize().Height()); for (auto i = 0; i < delta; i++) { - _buffer->IncrementCircularBuffer(); + _activeBuffer().IncrementCircularBuffer(); sNewTop--; } newWin.Top = sNewTop; - newWin.Bottom = sNewTop + _mutableViewport.Height(); + newWin.Bottom = sNewTop + viewport.Height(); } else if (eraseType == DispatchTypes::EraseType::Scrollback) { // We only want to erase the scrollback, and leave everything else on the screen as it is // so we grab the text in the viewport and rotate it up to the top of the buffer COORD scrollFromPos{ 0, 0 }; - _mutableViewport.ConvertFromOrigin(&scrollFromPos); - _buffer->ScrollRows(scrollFromPos.Y, _mutableViewport.Height(), -scrollFromPos.Y); + viewport.ConvertFromOrigin(&scrollFromPos); + _activeBuffer().ScrollRows(scrollFromPos.Y, viewport.Height(), -scrollFromPos.Y); // Since we only did a rotation, the text that was in the scrollback is now _below_ where we are going to move the viewport // and we have to make sure we erase that text - const auto eraseStart = _mutableViewport.Height(); - const auto eraseEnd = _buffer->GetLastNonSpaceCharacter(_mutableViewport).Y; + const auto eraseStart = viewport.Height(); + const auto eraseEnd = _activeBuffer().GetLastNonSpaceCharacter(viewport).Y; for (SHORT i = eraseStart; i <= eraseEnd; i++) { - _buffer->GetRowByOffset(i).Reset(_buffer->GetCurrentAttributes()); + _activeBuffer().GetRowByOffset(i).Reset(_activeBuffer().GetCurrentAttributes()); } // Reset the scroll offset now because there's nothing for the user to 'scroll' to _scrollOffset = 0; newWin.Top = 0; - newWin.Bottom = _mutableViewport.Height(); + newWin.Bottom = viewport.Height(); } else { @@ -350,7 +364,7 @@ void Terminal::SetColorTableEntry(const size_t tableIndex, const COLORREF color) } // Repaint everything - the colors might have changed - _buffer->TriggerRedrawAll(); + _activeBuffer().TriggerRedrawAll(); } // Method Description: @@ -412,8 +426,8 @@ void Terminal::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) return; } - _buffer->GetCursor().SetType(finalCursorType); - _buffer->GetCursor().SetBlinkingAllowed(shouldBlink); + _activeBuffer().GetCursor().SetType(finalCursorType); + _activeBuffer().GetCursor().SetBlinkingAllowed(shouldBlink); } void Terminal::SetInputMode(const TerminalInput::Mode mode, const bool enabled) @@ -426,7 +440,7 @@ void Terminal::SetRenderMode(const RenderSettings::Mode mode, const bool enabled _renderSettings.SetRenderMode(mode, enabled); // Repaint everything - the colors will have changed - _buffer->TriggerRedrawAll(); + _activeBuffer().TriggerRedrawAll(); } void Terminal::EnableXtermBracketedPasteMode(const bool enabled) @@ -447,12 +461,12 @@ bool Terminal::IsVtInputEnabled() const void Terminal::SetCursorVisibility(const bool visible) { - _buffer->GetCursor().SetIsVisible(visible); + _activeBuffer().GetCursor().SetIsVisible(visible); } void Terminal::EnableCursorBlinking(const bool enable) { - _buffer->GetCursor().SetBlinkingAllowed(enable); + _activeBuffer().GetCursor().SetBlinkingAllowed(enable); // GH#2642 - From what we've gathered from other terminals, when blinking is // disabled, the cursor should remain On always, and have the visibility @@ -460,7 +474,7 @@ void Terminal::EnableCursorBlinking(const bool enable) // to disable blinking, the cursor stays stuck On. At this point, only the // cursor visibility property controls whether the user can see it or not. // (Yes, the cursor can be On and NOT Visible) - _buffer->GetCursor().SetIsOn(true); + _activeBuffer().GetCursor().SetIsOn(true); } void Terminal::CopyToClipboard(std::wstring_view content) @@ -477,11 +491,11 @@ void Terminal::CopyToClipboard(std::wstring_view content) // - void Terminal::AddHyperlink(std::wstring_view uri, std::wstring_view params) { - auto attr = _buffer->GetCurrentAttributes(); - const auto id = _buffer->GetHyperlinkId(uri, params); + auto attr = _activeBuffer().GetCurrentAttributes(); + const auto id = _activeBuffer().GetHyperlinkId(uri, params); attr.SetHyperlinkId(id); - _buffer->SetCurrentAttributes(attr); - _buffer->AddHyperlinkToMap(uri, id); + _activeBuffer().SetCurrentAttributes(attr); + _activeBuffer().AddHyperlinkToMap(uri, id); } // Method Description: @@ -490,9 +504,9 @@ void Terminal::AddHyperlink(std::wstring_view uri, std::wstring_view params) // - void Terminal::EndHyperlink() { - auto attr = _buffer->GetCurrentAttributes(); + auto attr = _activeBuffer().GetCurrentAttributes(); attr.SetHyperlinkId(0); - _buffer->SetCurrentAttributes(attr); + _activeBuffer().SetCurrentAttributes(attr); } // Method Description: @@ -565,7 +579,7 @@ std::wstring_view Terminal::GetWorkingDirectory() // - void Terminal::PushGraphicsRendition(const VTParameters options) { - _sgrStack.Push(_buffer->GetCurrentAttributes(), options); + _sgrStack.Push(_activeBuffer().GetCurrentAttributes(), options); } // Method Description: @@ -577,6 +591,96 @@ void Terminal::PushGraphicsRendition(const VTParameters options) // - void Terminal::PopGraphicsRendition() { - const TextAttribute current = _buffer->GetCurrentAttributes(); - _buffer->SetCurrentAttributes(_sgrStack.Pop(current)); + const TextAttribute current = _activeBuffer().GetCurrentAttributes(); + _activeBuffer().SetCurrentAttributes(_sgrStack.Pop(current)); +} + +void Terminal::UseAlternateScreenBuffer() +{ + // the new alt buffer is exactly the size of the viewport. + const COORD bufferSize{ _mutableViewport.Dimensions() }; + const auto cursorSize = _mainBuffer->GetCursor().GetSize(); + + ClearSelection(); + _mainBuffer->ClearPatternRecognizers(); + + // Create a new alt buffer + _altBuffer = std::make_unique(bufferSize, + TextAttribute{}, + cursorSize, + true, + _mainBuffer->GetRenderer()); + _mainBuffer->SetAsActiveBuffer(false); + + // Copy our cursor state to the new buffer's cursor + { + // Update the alt buffer's cursor style, visibility, and position to match our own. + auto& myCursor = _mainBuffer->GetCursor(); + auto& tgtCursor = _altBuffer->GetCursor(); + tgtCursor.SetStyle(myCursor.GetSize(), myCursor.GetType()); + tgtCursor.SetIsVisible(myCursor.IsVisible()); + tgtCursor.SetBlinkingAllowed(myCursor.IsBlinkingAllowed()); + + // The new position should match the viewport-relative position of the main buffer. + auto tgtCursorPos = myCursor.GetPosition(); + tgtCursorPos.Y -= _mutableViewport.Top(); + tgtCursor.SetPosition(tgtCursorPos); + } + + // update all the hyperlinks on the screen + _updateUrlDetection(); + + // Update scrollbars + _NotifyScrollEvent(); + + // redraw the screen + try + { + _activeBuffer().TriggerRedrawAll(); + } + CATCH_LOG(); +} +void Terminal::UseMainScreenBuffer() +{ + // Short-circuit: do nothing. + if (!_inAltBuffer()) + { + return; + } + + ClearSelection(); + + // Copy our cursor state back to the main buffer's cursor + { + // Update the alt buffer's cursor style, visibility, and position to match our own. + auto& myCursor = _altBuffer->GetCursor(); + auto& tgtCursor = _mainBuffer->GetCursor(); + tgtCursor.SetStyle(myCursor.GetSize(), myCursor.GetType()); + tgtCursor.SetIsVisible(myCursor.IsVisible()); + tgtCursor.SetBlinkingAllowed(myCursor.IsBlinkingAllowed()); + + // The new position should match the viewport-relative position of the main buffer. + // This is the equal and opposite effect of what we did in UseAlternateScreenBuffer + auto tgtCursorPos = myCursor.GetPosition(); + tgtCursorPos.Y += _mutableViewport.Top(); + tgtCursor.SetPosition(tgtCursorPos); + } + + _mainBuffer->SetAsActiveBuffer(true); + // destroy the alt buffer + _altBuffer = nullptr; + + // update all the hyperlinks on the screen + _mainBuffer->ClearPatternRecognizers(); + _updateUrlDetection(); + + // Update scrollbars + _NotifyScrollEvent(); + + // redraw the screen + try + { + _activeBuffer().TriggerRedrawAll(); + } + CATCH_LOG(); } diff --git a/src/cascadia/TerminalCore/TerminalDispatch.cpp b/src/cascadia/TerminalCore/TerminalDispatch.cpp index 1747185b64a..689e29de93a 100644 --- a/src/cascadia/TerminalCore/TerminalDispatch.cpp +++ b/src/cascadia/TerminalCore/TerminalDispatch.cpp @@ -658,6 +658,9 @@ bool TerminalDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, case DispatchTypes::ModeParams::W32IM_Win32InputMode: success = EnableWin32InputMode(enable); break; + case DispatchTypes::ModeParams::ASB_AlternateScreenBuffer: + success = enable ? UseAlternateScreenBuffer() : UseMainScreenBuffer(); + break; default: // If no functions to call, overall dispatch was a failure. success = false; @@ -786,3 +789,84 @@ bool TerminalDispatch::HardReset() return true; } + +// Routine Description: +// - DECSC - Saves the current "cursor state" into a memory buffer. This +// includes the cursor position, origin mode, graphic rendition, and +// active character set. +// Arguments: +// - +// Return Value: +// - True. +bool TerminalDispatch::CursorSaveState() +{ + // TODO GH#3849: When de-duplicating this, the AdaptDispatch version of this + // is more elaborate. + const auto attributes = _terminalApi.GetTextAttributes(); + COORD coordCursor = _terminalApi.GetCursorPosition(); + // The cursor is given to us by the API as relative to current viewport top. + + // VT is also 1 based, not 0 based, so correct by 1. + auto& savedCursorState = _savedCursorState.at(_usingAltBuffer); + savedCursorState.Column = coordCursor.X + 1; + savedCursorState.Row = coordCursor.Y + 1; + savedCursorState.Attributes = attributes; + + return true; +} + +// Routine Description: +// - DECRC - Restores a saved "cursor state" from the DECSC command back into +// the console state. This includes the cursor position, origin mode, graphic +// rendition, and active character set. +// Arguments: +// - +// Return Value: +// - True. +bool TerminalDispatch::CursorRestoreState() +{ + // TODO GH#3849: When de-duplicating this, the AdaptDispatch version of this + // is more elaborate. + auto& savedCursorState = _savedCursorState.at(_usingAltBuffer); + + auto row = savedCursorState.Row; + const auto col = savedCursorState.Column; + + // The saved coordinates are always absolute, so we need reset the origin mode temporarily. + CursorPosition(row, col); + + // Restore text attributes. + _terminalApi.SetTextAttributes(savedCursorState.Attributes); + + return true; +} + +// - ASBSET - Creates and swaps to the alternate screen buffer. In virtual terminals, there exists both a "main" +// screen buffer and an alternate. ASBSET creates a new alternate, and switches to it. If there is an already +// existing alternate, it is discarded. +// Arguments: +// - None +// Return Value: +// - True. +bool TerminalDispatch::UseAlternateScreenBuffer() +{ + CursorSaveState(); + _terminalApi.UseAlternateScreenBuffer(); + _usingAltBuffer = true; + return true; +} + +// Routine Description: +// - ASBRST - From the alternate buffer, returns to the main screen buffer. +// From the main screen buffer, does nothing. The alternate is discarded. +// Arguments: +// - None +// Return Value: +// - True. +bool TerminalDispatch::UseMainScreenBuffer() +{ + _terminalApi.UseMainScreenBuffer(); + _usingAltBuffer = false; + CursorRestoreState(); + return true; +} diff --git a/src/cascadia/TerminalCore/TerminalDispatch.hpp b/src/cascadia/TerminalCore/TerminalDispatch.hpp index cfec1aaadfe..42188bc4ecf 100644 --- a/src/cascadia/TerminalCore/TerminalDispatch.hpp +++ b/src/cascadia/TerminalCore/TerminalDispatch.hpp @@ -39,6 +39,9 @@ class TerminalDispatch : public Microsoft::Console::VirtualTerminal::TermDispatc bool CarriageReturn() override; bool SetWindowTitle(std::wstring_view title) override; + bool UseAlternateScreenBuffer() override; // ASBSET + bool UseMainScreenBuffer() override; // ASBRST + bool HorizontalTabSet() override; // HTS bool ForwardTab(const size_t numTabs) override; // CHT, HT bool BackwardsTab(const size_t numTabs) override; // CBT @@ -82,12 +85,31 @@ class TerminalDispatch : public Microsoft::Console::VirtualTerminal::TermDispatc bool DoConEmuAction(const std::wstring_view string) override; + bool CursorSaveState() override; + bool CursorRestoreState() override; + private: ::Microsoft::Terminal::Core::ITerminalApi& _terminalApi; + // Dramatically simplified version of AdaptDispatch::CursorState + struct CursorState + { + unsigned int Row = 1; + unsigned int Column = 1; + TextAttribute Attributes = {}; + }; + std::vector _tabStopColumns; bool _initDefaultTabStops = true; + // We have two instances of the saved cursor state, because we need + // one for the main buffer (at index 0), and another for the alt buffer + // (at index 1). The _usingAltBuffer property keeps tracks of which + // buffer is active, so can be used as an index into this array to + // obtain the saved state that should be currently active. + std::array _savedCursorState; + bool _usingAltBuffer = false; + size_t _SetRgbColorsHelper(const ::Microsoft::Console::VirtualTerminal::VTParameters options, TextAttribute& attr, const bool isForeground); diff --git a/src/cascadia/TerminalCore/TerminalSelection.cpp b/src/cascadia/TerminalCore/TerminalSelection.cpp index c0b20b64d23..2a29b109eb0 100644 --- a/src/cascadia/TerminalCore/TerminalSelection.cpp +++ b/src/cascadia/TerminalCore/TerminalSelection.cpp @@ -54,7 +54,7 @@ std::vector Terminal::_GetSelectionRects() const noexcept try { - return _buffer->GetTextRects(_selection->start, _selection->end, _blockSelection, false); + return _activeBuffer().GetTextRects(_selection->start, _selection->end, _blockSelection, false); } CATCH_LOG(); return result; @@ -182,7 +182,7 @@ void Terminal::SetSelectionEnd(const COORD viewportPos, std::optional Terminal::_PivotSelection(const COORD targetPos, bool& targetStart) const { - if (targetStart = _buffer->GetSize().CompareInBounds(targetPos, _selection->pivot) <= 0) + if (targetStart = _activeBuffer().GetSize().CompareInBounds(targetPos, _selection->pivot) <= 0) { // target is before pivot // treat target as start @@ -207,7 +207,7 @@ std::pair Terminal::_ExpandSelectionAnchors(std::pairGetSize(); + const auto bufferSize = _activeBuffer().GetSize(); switch (_multiClickSelectionMode) { case SelectionExpansion::Line: @@ -215,8 +215,8 @@ std::pair Terminal::_ExpandSelectionAnchors(std::pairGetWordStart(start, _wordDelimiters); - end = _buffer->GetWordEnd(end, _wordDelimiters); + start = _activeBuffer().GetWordStart(start, _wordDelimiters); + end = _activeBuffer().GetWordEnd(end, _wordDelimiters); break; case SelectionExpansion::Char: default: @@ -325,7 +325,7 @@ void Terminal::UpdateSelection(SelectionDirection direction, SelectionExpansion _scrollOffset -= amtBelowView; } _NotifyScrollEvent(); - _buffer->TriggerScroll(); + _activeBuffer().TriggerScroll(); } } @@ -334,22 +334,22 @@ void Terminal::_MoveByChar(SelectionDirection direction, COORD& pos) switch (direction) { case SelectionDirection::Left: - _buffer->GetSize().DecrementInBounds(pos); - pos = _buffer->GetGlyphStart(til::point{ pos }).to_win32_coord(); + _activeBuffer().GetSize().DecrementInBounds(pos); + pos = _activeBuffer().GetGlyphStart(til::point{ pos }).to_win32_coord(); break; case SelectionDirection::Right: - _buffer->GetSize().IncrementInBounds(pos); - pos = _buffer->GetGlyphEnd(til::point{ pos }).to_win32_coord(); + _activeBuffer().GetSize().IncrementInBounds(pos); + pos = _activeBuffer().GetGlyphEnd(til::point{ pos }).to_win32_coord(); break; case SelectionDirection::Up: { - const auto bufferSize{ _buffer->GetSize() }; + const auto bufferSize{ _activeBuffer().GetSize() }; pos = { pos.X, std::clamp(base::ClampSub(pos.Y, 1).RawValue(), bufferSize.Top(), bufferSize.BottomInclusive()) }; break; } case SelectionDirection::Down: { - const auto bufferSize{ _buffer->GetSize() }; + const auto bufferSize{ _activeBuffer().GetSize() }; pos = { pos.X, std::clamp(base::ClampAdd(pos.Y, 1).RawValue(), bufferSize.Top(), bufferSize.BottomInclusive()) }; break; } @@ -361,19 +361,19 @@ void Terminal::_MoveByWord(SelectionDirection direction, COORD& pos) switch (direction) { case SelectionDirection::Left: - const auto wordStartPos{ _buffer->GetWordStart(pos, _wordDelimiters) }; - if (_buffer->GetSize().CompareInBounds(_selection->pivot, pos) < 0) + const auto wordStartPos{ _activeBuffer().GetWordStart(pos, _wordDelimiters) }; + if (_activeBuffer().GetSize().CompareInBounds(_selection->pivot, pos) < 0) { // If we're moving towards the pivot, move one more cell pos = wordStartPos; - _buffer->GetSize().DecrementInBounds(pos); + _activeBuffer().GetSize().DecrementInBounds(pos); } else if (wordStartPos == pos) { // already at the beginning of the current word, // move to the beginning of the previous word - _buffer->GetSize().DecrementInBounds(pos); - pos = _buffer->GetWordStart(pos, _wordDelimiters); + _activeBuffer().GetSize().DecrementInBounds(pos); + pos = _activeBuffer().GetWordStart(pos, _wordDelimiters); } else { @@ -382,19 +382,19 @@ void Terminal::_MoveByWord(SelectionDirection direction, COORD& pos) } break; case SelectionDirection::Right: - const auto wordEndPos{ _buffer->GetWordEnd(pos, _wordDelimiters) }; - if (_buffer->GetSize().CompareInBounds(pos, _selection->pivot) < 0) + const auto wordEndPos{ _activeBuffer().GetWordEnd(pos, _wordDelimiters) }; + if (_activeBuffer().GetSize().CompareInBounds(pos, _selection->pivot) < 0) { // If we're moving towards the pivot, move one more cell - pos = _buffer->GetWordEnd(pos, _wordDelimiters); - _buffer->GetSize().IncrementInBounds(pos); + pos = _activeBuffer().GetWordEnd(pos, _wordDelimiters); + _activeBuffer().GetSize().IncrementInBounds(pos); } else if (wordEndPos == pos) { // already at the end of the current word, // move to the end of the next word - _buffer->GetSize().IncrementInBounds(pos); - pos = _buffer->GetWordEnd(pos, _wordDelimiters); + _activeBuffer().GetSize().IncrementInBounds(pos); + pos = _activeBuffer().GetWordEnd(pos, _wordDelimiters); } else { @@ -404,18 +404,18 @@ void Terminal::_MoveByWord(SelectionDirection direction, COORD& pos) break; case SelectionDirection::Up: _MoveByChar(direction, pos); - pos = _buffer->GetWordStart(pos, _wordDelimiters); + pos = _activeBuffer().GetWordStart(pos, _wordDelimiters); break; case SelectionDirection::Down: _MoveByChar(direction, pos); - pos = _buffer->GetWordEnd(pos, _wordDelimiters); + pos = _activeBuffer().GetWordEnd(pos, _wordDelimiters); break; } } void Terminal::_MoveByViewport(SelectionDirection direction, COORD& pos) { - const auto bufferSize{ _buffer->GetSize() }; + const auto bufferSize{ _activeBuffer().GetSize() }; switch (direction) { case SelectionDirection::Left: @@ -444,7 +444,7 @@ void Terminal::_MoveByViewport(SelectionDirection direction, COORD& pos) void Terminal::_MoveByBuffer(SelectionDirection direction, COORD& pos) { - const auto bufferSize{ _buffer->GetSize() }; + const auto bufferSize{ _activeBuffer().GetSize() }; switch (direction) { case SelectionDirection::Left: @@ -489,7 +489,7 @@ const TextBuffer::TextAndColor Terminal::RetrieveSelectedTextFromBuffer(bool sin const auto includeCRLF = !singleLine || _blockSelection; const auto trimTrailingWhitespace = !singleLine && (!_blockSelection || _trimBlockSelection); const auto formatWrappedRows = _blockSelection; - return _buffer->GetText(includeCRLF, trimTrailingWhitespace, selectionRects, GetAttributeColors, formatWrappedRows); + return _activeBuffer().GetText(includeCRLF, trimTrailingWhitespace, selectionRects, GetAttributeColors, formatWrappedRows); } // Method Description: @@ -502,7 +502,7 @@ COORD Terminal::_ConvertToBufferCell(const COORD viewportPos) const { const auto yPos = base::ClampedNumeric(_VisibleStartIndex()) + viewportPos.Y; COORD bufferPos = { viewportPos.X, yPos }; - _buffer->GetSize().Clamp(bufferPos); + _activeBuffer().GetSize().Clamp(bufferPos); return bufferPos; } diff --git a/src/cascadia/TerminalCore/terminalrenderdata.cpp b/src/cascadia/TerminalCore/terminalrenderdata.cpp index 212551ae21a..d7d24b5e415 100644 --- a/src/cascadia/TerminalCore/terminalrenderdata.cpp +++ b/src/cascadia/TerminalCore/terminalrenderdata.cpp @@ -25,7 +25,7 @@ COORD Terminal::GetTextBufferEndPosition() const noexcept const TextBuffer& Terminal::GetTextBuffer() noexcept { - return *_buffer; + return _activeBuffer(); } const FontInfo& Terminal::GetFontInfo() noexcept @@ -40,19 +40,19 @@ void Terminal::SetFontInfo(const FontInfo& fontInfo) COORD Terminal::GetCursorPosition() const noexcept { - const auto& cursor = _buffer->GetCursor(); + const auto& cursor = _activeBuffer().GetCursor(); return cursor.GetPosition(); } bool Terminal::IsCursorVisible() const noexcept { - const auto& cursor = _buffer->GetCursor(); + const auto& cursor = _activeBuffer().GetCursor(); return cursor.IsVisible() && !cursor.IsPopupShown(); } bool Terminal::IsCursorOn() const noexcept { - const auto& cursor = _buffer->GetCursor(); + const auto& cursor = _activeBuffer().GetCursor(); return cursor.IsOn(); } @@ -63,18 +63,18 @@ ULONG Terminal::GetCursorPixelWidth() const noexcept ULONG Terminal::GetCursorHeight() const noexcept { - return _buffer->GetCursor().GetSize(); + return _activeBuffer().GetCursor().GetSize(); } CursorType Terminal::GetCursorStyle() const noexcept { - return _buffer->GetCursor().GetType(); + return _activeBuffer().GetCursor().GetType(); } bool Terminal::IsCursorDoubleWidth() const { - const auto position = _buffer->GetCursor().GetPosition(); - TextBufferTextIterator it(TextBufferCellIterator(*_buffer, position)); + const auto position = _activeBuffer().GetCursor().GetPosition(); + TextBufferTextIterator it(TextBufferCellIterator(_activeBuffer(), position)); return IsGlyphFullWidth(*it); } @@ -90,12 +90,12 @@ const bool Terminal::IsGridLineDrawingAllowed() noexcept const std::wstring Microsoft::Terminal::Core::Terminal::GetHyperlinkUri(uint16_t id) const noexcept { - return _buffer->GetHyperlinkUriFromId(id); + return _activeBuffer().GetHyperlinkUriFromId(id); } const std::wstring Microsoft::Terminal::Core::Terminal::GetHyperlinkCustomId(uint16_t id) const noexcept { - return _buffer->GetCustomIdFromId(id); + return _activeBuffer().GetCustomIdFromId(id); } // Method Description: @@ -174,7 +174,7 @@ void Terminal::SelectNewRegion(const COORD coordStart, const COORD coordEnd) if (notifyScrollChange) { - _buffer->TriggerScroll(); + _activeBuffer().TriggerScroll(); _NotifyScrollEvent(); } @@ -227,5 +227,5 @@ const bool Terminal::IsUiaDataInitialized() const noexcept // when a screen reader requests it. However, the terminal might not be fully // initialized yet. So we use this to check if any crucial components of // UiaData are not yet initialized. - return !!_buffer; + return !!_mainBuffer; } diff --git a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp index 17c35ef3e4b..082c2d80707 100644 --- a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp @@ -222,6 +222,9 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final TEST_METHOD(ClearBufferSignal); + TEST_METHOD(SimpleAltBufferTest); + TEST_METHOD(AltBufferToAltBufferTest); + private: bool _writeCallback(const char* const pch, size_t const cch); void _flushFirstFrame(); @@ -322,7 +325,7 @@ void ConptyRoundtripTests::_clearConpty() // After we resize, make sure to get the new textBuffers return { &ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer().GetTextBuffer(), - term->_buffer.get() }; + term->_mainBuffer.get() }; } void ConptyRoundtripTests::ConptyOutputTestCanary() @@ -344,7 +347,7 @@ void ConptyRoundtripTests::SimpleWriteOutputTest() auto& gci = g.getConsoleInformation(); auto& si = gci.GetActiveOutputBuffer(); auto& hostSm = si.GetStateMachine(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; _flushFirstFrame(); @@ -367,7 +370,7 @@ void ConptyRoundtripTests::WriteTwoLinesUsesNewline() auto& si = gci.GetActiveOutputBuffer(); auto& hostSm = si.GetStateMachine(); auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; _flushFirstFrame(); @@ -402,7 +405,7 @@ void ConptyRoundtripTests::WriteAFewSimpleLines() auto& si = gci.GetActiveOutputBuffer(); auto& hostSm = si.GetStateMachine(); auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; _flushFirstFrame(); @@ -443,7 +446,7 @@ void ConptyRoundtripTests::TestWrappingALongString() auto& si = gci.GetActiveOutputBuffer(); auto& hostSm = si.GetStateMachine(); auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; _flushFirstFrame(); _checkConptyOutput = false; @@ -494,7 +497,7 @@ void ConptyRoundtripTests::TestAdvancedWrapping() auto& si = gci.GetActiveOutputBuffer(); auto& hostSm = si.GetStateMachine(); auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; const auto initialTermView = term->GetViewport(); _flushFirstFrame(); @@ -559,7 +562,7 @@ void ConptyRoundtripTests::TestExactWrappingWithoutSpaces() auto& si = gci.GetActiveOutputBuffer(); auto& hostSm = si.GetStateMachine(); auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; const auto initialTermView = term->GetViewport(); @@ -621,7 +624,7 @@ void ConptyRoundtripTests::TestExactWrappingWithSpaces() auto& hostSm = si.GetStateMachine(); auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; const auto initialTermView = term->GetViewport(); _flushFirstFrame(); @@ -683,7 +686,7 @@ void ConptyRoundtripTests::MoveCursorAtEOL() auto& hostSm = si.GetStateMachine(); auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; _flushFirstFrame(); Log::Comment(NoThrowString().Format( @@ -762,7 +765,7 @@ void ConptyRoundtripTests::TestResizeHeight() auto& si = gci.GetActiveOutputBuffer(); auto& hostSm = si.GetStateMachine(); auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_buffer.get(); + auto* termTb = term->_mainBuffer.get(); const auto initialHostView = si.GetViewport(); const auto initialTermView = term->GetViewport(); const auto initialTerminalBufferHeight = term->GetTextBuffer().GetSize().Height(); @@ -962,7 +965,7 @@ void ConptyRoundtripTests::PassthroughCursorShapeImmediately() auto& si = gci.GetActiveOutputBuffer(); auto& hostSm = si.GetStateMachine(); auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; _flushFirstFrame(); @@ -989,7 +992,7 @@ void ConptyRoundtripTests::PassthroughClearScrollback() auto& si = gci.GetActiveOutputBuffer(); auto& hostSm = si.GetStateMachine(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; _flushFirstFrame(); @@ -1066,7 +1069,7 @@ void ConptyRoundtripTests::PassthroughClearAll() auto& gci = g.getConsoleInformation(); auto& si = gci.GetActiveOutputBuffer(); auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_buffer.get(); + auto* termTb = term->_mainBuffer.get(); auto& sm = si.GetStateMachine(); @@ -1153,7 +1156,7 @@ void ConptyRoundtripTests::PassthroughHardReset() auto& si = gci.GetActiveOutputBuffer(); auto& hostSm = si.GetStateMachine(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; _flushFirstFrame(); @@ -1217,7 +1220,7 @@ void ConptyRoundtripTests::OutputWrappedLinesAtTopOfBuffer() auto& si = gci.GetActiveOutputBuffer(); auto& sm = si.GetStateMachine(); auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; _flushFirstFrame(); @@ -1265,7 +1268,7 @@ void ConptyRoundtripTests::OutputWrappedLinesAtBottomOfBuffer() auto& si = gci.GetActiveOutputBuffer(); auto& hostSm = si.GetStateMachine(); auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; _flushFirstFrame(); @@ -1398,7 +1401,7 @@ void ConptyRoundtripTests::ScrollWithChangesInMiddle() auto& si = gci.GetActiveOutputBuffer(); auto& hostSm = si.GetStateMachine(); auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; _flushFirstFrame(); @@ -1499,7 +1502,7 @@ void ConptyRoundtripTests::ScrollWithMargins() auto& hostSm = si.GetStateMachine(); auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; const auto initialTermView = term->GetViewport(); Log::Comment(L"Flush first frame."); @@ -1743,7 +1746,7 @@ void ConptyRoundtripTests::DontWrapMoveCursorInSingleFrame() auto& si = gci.GetActiveOutputBuffer(); auto& hostSm = si.GetStateMachine(); auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; _flushFirstFrame(); @@ -1836,7 +1839,7 @@ void ConptyRoundtripTests::ClearHostTrickeryTest() auto& si = gci.GetActiveOutputBuffer(); auto& hostSm = si.GetStateMachine(); auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; _flushFirstFrame(); @@ -1944,7 +1947,7 @@ void ConptyRoundtripTests::OverstrikeAtBottomOfBuffer() auto& si = gci.GetActiveOutputBuffer(); auto& hostSm = si.GetStateMachine(); auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; _flushFirstFrame(); @@ -2022,7 +2025,7 @@ void ConptyRoundtripTests::MarginsWithStatusLine() auto& si = gci.GetActiveOutputBuffer(); auto& hostSm = si.GetStateMachine(); auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; _flushFirstFrame(); @@ -2113,7 +2116,7 @@ void ConptyRoundtripTests::OutputWrappedLineWithSpace() auto& si = gci.GetActiveOutputBuffer(); auto& sm = si.GetStateMachine(); auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; _flushFirstFrame(); @@ -2179,7 +2182,7 @@ void ConptyRoundtripTests::OutputWrappedLineWithSpaceAtBottomOfBuffer() auto& si = gci.GetActiveOutputBuffer(); auto& sm = si.GetStateMachine(); auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; _flushFirstFrame(); @@ -2334,8 +2337,6 @@ void ConptyRoundtripTests::BreakLinesOnCursorMovement() auto& si = gci.GetActiveOutputBuffer(); auto& hostSm = si.GetStateMachine(); - auto& termTb = *term->_buffer; - _flushFirstFrame(); // Any of the cursor movements that use a LF will actually hard break the @@ -2475,8 +2476,9 @@ void ConptyRoundtripTests::BreakLinesOnCursorMovement() VERIFY_SUCCEEDED(renderer.PaintFrame()); Log::Comment(L"========== Checking the terminal buffer state =========="); - - verifyBuffer(termTb, til::rect{ term->_mutableViewport.ToInclusive() }); + // GH#3492: Now that we support the alt buffer, make sure to validate the + // _alt buffer's_ contents. + verifyBuffer(*term->_altBuffer, til::rect{ term->_mutableViewport.ToInclusive() }); } void ConptyRoundtripTests::TestCursorInDeferredEOLPositionOnNewLineWithSpaces() @@ -2488,7 +2490,7 @@ void ConptyRoundtripTests::TestCursorInDeferredEOLPositionOnNewLineWithSpaces() auto& hostSm = si.GetStateMachine(); auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; const auto termView = term->GetViewport(); _flushFirstFrame(); @@ -2549,7 +2551,7 @@ void ConptyRoundtripTests::ResizeRepaintVimExeBuffer() auto& si = gci.GetActiveOutputBuffer(); auto& sm = si.GetStateMachine(); auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_buffer.get(); + auto* termTb = term->_mainBuffer.get(); _flushFirstFrame(); @@ -2678,7 +2680,7 @@ void ConptyRoundtripTests::ClsAndClearHostClearsScrollbackTest() auto& gci = g.getConsoleInformation(); auto& si = gci.GetActiveOutputBuffer(); auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_buffer.get(); + auto* termTb = term->_mainBuffer.get(); auto& sm = si.GetStateMachine(); @@ -2833,7 +2835,7 @@ void ConptyRoundtripTests::TestResizeWithCookedRead() auto& gci = g.getConsoleInformation(); auto& si = gci.GetActiveOutputBuffer(); auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_buffer.get(); + auto* termTb = term->_mainBuffer.get(); _flushFirstFrame(); @@ -2894,7 +2896,7 @@ void ConptyRoundtripTests::ResizeInitializeBufferWithDefaultAttrs() auto& si = gci.GetActiveOutputBuffer(); auto& sm = si.GetStateMachine(); auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_buffer.get(); + auto* termTb = term->_mainBuffer.get(); _flushFirstFrame(); @@ -3021,7 +3023,7 @@ void ConptyRoundtripTests::NewLinesAtBottomWithBackground() auto& si = gci.GetActiveOutputBuffer(); auto& sm = si.GetStateMachine(); auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_buffer.get(); + auto* termTb = term->_mainBuffer.get(); _flushFirstFrame(); @@ -3191,7 +3193,7 @@ void ConptyRoundtripTests::WrapNewLineAtBottom() auto& si = gci.GetActiveOutputBuffer(); auto& sm = si.GetStateMachine(); auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_buffer.get(); + auto* termTb = term->_mainBuffer.get(); _flushFirstFrame(); @@ -3349,7 +3351,7 @@ void ConptyRoundtripTests::WrapNewLineAtBottomLikeMSYS() auto& si = gci.GetActiveOutputBuffer(); auto& sm = si.GetStateMachine(); auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_buffer.get(); + auto* termTb = term->_mainBuffer.get(); _flushFirstFrame(); @@ -3533,7 +3535,7 @@ void ConptyRoundtripTests::DeleteWrappedWord() auto& si = gci.GetActiveOutputBuffer(); auto& sm = si.GetStateMachine(); auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_buffer.get(); + auto* termTb = term->_mainBuffer.get(); _flushFirstFrame(); @@ -3625,7 +3627,7 @@ void ConptyRoundtripTests::HyperlinkIdConsistency() auto& si = gci.GetActiveOutputBuffer(); auto& hostSm = si.GetStateMachine(); auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; _flushFirstFrame(); @@ -3690,7 +3692,7 @@ void ConptyRoundtripTests::ClearBufferSignal() auto& si = gci.GetActiveOutputBuffer(); auto& sm = si.GetStateMachine(); auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_buffer.get(); + auto* termTb = term->_mainBuffer.get(); _flushFirstFrame(); @@ -3705,12 +3707,11 @@ void ConptyRoundtripTests::ClearBufferSignal() // B's are in red-on-yellow sm.ProcessString(L"\x1b[?25l"); - sm.ProcessString(L"\x1b[?34;42m"); + sm.ProcessString(L"\x1b[34;42m"); sm.ProcessString(std::wstring(50, L'A')); sm.ProcessString(L" "); - sm.ProcessString(L"\x1b[?31;43m"); + sm.ProcessString(L"\x1b[31;43m"); sm.ProcessString(std::wstring(50, L'B')); - sm.ProcessString(L"\x1b[?m"); sm.ProcessString(L"\x1b[?25h"); auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& viewport, const bool before) { @@ -3752,3 +3753,318 @@ void ConptyRoundtripTests::ClearBufferSignal() Log::Comment(L"========== Checking the terminal buffer state (after) =========="); verifyBuffer(*termTb, til::rect{ term->_mutableViewport.ToInclusive() }, false); } + +void ConptyRoundtripTests::SimpleAltBufferTest() +{ + Log::Comment(L"A test for entering and exiting the alt buffer, via conpty. " + L"Ensures cursor is in the right place, and contents are " + L"restored accordingly."); + auto& g = ServiceLocator::LocateGlobals(); + auto& renderer = *g.pRender; + auto& gci = g.getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer(); + auto& sm = si.GetStateMachine(); + auto* hostTb = &si.GetTextBuffer(); + auto* termTb = term->_mainBuffer.get(); + + _flushFirstFrame(); + + _checkConptyOutput = false; + _logConpty = true; + + // Print two lines of text: + // |AAAAAAAA | + // |BBBBBBBB_ | + // (cursor on the '_') + // A's are in blue-on-green, + // B's are in red-on-yellow + + // A one line version: (more or less) + // + // printf "\x1b[2J\x1b[H" ; printf + // "\x1b[?25l\x1b[34;42mAAAAA\n\x1b[31;43mBBBBB\x1b[?25h" ; sleep 2 ; printf + // "\x1b[?1049h" ; sleep 2 ; printf "CCCCC" ; sleep 2 ; printf "\x1b[?1049l" + // ; sleep 2 + + sm.ProcessString(L"\x1b[?25l"); + sm.ProcessString(L"\x1b[34;42m"); + sm.ProcessString(std::wstring(50, L'A')); + sm.ProcessString(L"\n"); + sm.ProcessString(L"\x1b[31;43m"); + sm.ProcessString(std::wstring(50, L'B')); + sm.ProcessString(L"\x1b[?25h"); + + // Four frames here: + // * After text is printed to main buffer + // * after we go to the alt buffer + // * print more to the alt buffer + // * after we go back to the buffer + + enum class Frame : int + { + InMainBufferBefore = 0, + InAltBufferBefore, + InAltBufferAfter, + InMainBufferAfter + }; + + auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& viewport, const Frame frame) { + const short width = viewport.narrow_width(); + auto iter0 = tb.GetCellDataAt({ 0, 0 }); + auto iter1 = tb.GetCellDataAt({ 0, 1 }); + switch (frame) + { + case Frame::InMainBufferBefore: + case Frame::InMainBufferAfter: + { + TestUtils::VerifySpanOfText(L"A", iter0, 0, 50); + TestUtils::VerifySpanOfText(L" ", iter0, 0, static_cast(width - 50)); + + TestUtils::VerifySpanOfText(L"B", iter1, 0, 50); + TestUtils::VerifySpanOfText(L" ", iter1, 0, static_cast(width - 50)); + COORD expectedCursor{ 50, 1 }; + VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); + break; + } + case Frame::InAltBufferBefore: + { + TestUtils::VerifySpanOfText(L" ", iter0, 0, width); + TestUtils::VerifySpanOfText(L" ", iter1, 0, width); + + COORD expectedCursor{ 50, 1 }; + VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); + break; + } + case Frame::InAltBufferAfter: + { + TestUtils::VerifySpanOfText(L" ", iter0, 0, width); + TestUtils::VerifySpanOfText(L" ", iter1, 0, 50u); + TestUtils::VerifySpanOfText(L"C", iter1, 0, 5u); + TestUtils::VerifySpanOfText(L" ", iter1, 0, static_cast(width - 55)); + + COORD expectedCursor{ 55, 1 }; + VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); + break; + } + } + }; + + Log::Comment(L"========== Checking the host buffer state (InMainBufferBefore) =========="); + verifyBuffer(*hostTb, til::rect{ si.GetViewport().ToInclusive() }, Frame::InMainBufferBefore); + + Log::Comment(L"Painting the frame"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); + Log::Comment(L"========== Checking the terminal buffer state (InMainBufferBefore) =========="); + VERIFY_ARE_EQUAL(0, term->_GetMutableViewport().Top()); + verifyBuffer(*termTb, til::rect{ term->_GetMutableViewport().ToInclusive() }, Frame::InMainBufferBefore); + + Log::Comment(L"========== Switch to the alt buffer =========="); + + gci.LockConsole(); // Lock must be taken to manipulate alt/main buffer state. + auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); + sm.ProcessString(L"\x1b[?1049h"); + + Log::Comment(L"Painting the frame"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); + + auto& siAlt = gci.GetActiveOutputBuffer(); + auto* hostAltTb = &siAlt.GetTextBuffer(); + auto* termAltTb = &term->_activeBuffer(); + + VERIFY_IS_TRUE(term->_inAltBuffer()); + VERIFY_ARE_NOT_EQUAL(termAltTb, termTb); + + Log::Comment(L"========== Checking the host buffer state (InAltBufferBefore) =========="); + verifyBuffer(*hostAltTb, til::rect{ siAlt.GetViewport().ToInclusive() }, Frame::InAltBufferBefore); + + Log::Comment(L"Painting the frame"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); + Log::Comment(L"========== Checking the terminal buffer state (InAltBufferBefore) =========="); + VERIFY_ARE_EQUAL(0, term->_GetMutableViewport().Top()); + verifyBuffer(*termAltTb, til::rect{ term->_GetMutableViewport().ToInclusive() }, Frame::InAltBufferBefore); + + Log::Comment(L"========== Add some text to the alt buffer =========="); + + sm.ProcessString(L"CCCCC"); + Log::Comment(L"========== Checking the host buffer state (InAltBufferAfter) =========="); + verifyBuffer(*hostAltTb, til::rect{ siAlt.GetViewport().ToInclusive() }, Frame::InAltBufferAfter); + Log::Comment(L"Painting the frame"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); + + Log::Comment(L"========== Checking the terminal buffer state (InAltBufferAfter) =========="); + VERIFY_ARE_EQUAL(0, term->_GetMutableViewport().Top()); + verifyBuffer(*termAltTb, til::rect{ term->_GetMutableViewport().ToInclusive() }, Frame::InAltBufferAfter); + + Log::Comment(L"========== Back to the main buffer =========="); + + sm.ProcessString(L"\x1b[?1049l"); + Log::Comment(L"========== Checking the host buffer state (InMainBufferAfter) =========="); + verifyBuffer(*hostTb, til::rect{ si.GetViewport().ToInclusive() }, Frame::InMainBufferAfter); + Log::Comment(L"Painting the frame"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); + + Log::Comment(L"========== Checking the terminal buffer state (InMainBufferAfter) =========="); + VERIFY_ARE_EQUAL(0, term->_GetMutableViewport().Top()); + verifyBuffer(*termTb, til::rect{ term->_GetMutableViewport().ToInclusive() }, Frame::InMainBufferAfter); +} + +void ConptyRoundtripTests::AltBufferToAltBufferTest() +{ + Log::Comment(L"When we request the alt buffer when we're already in the alt buffer, we should still clear it out and replace it."); + auto& g = ServiceLocator::LocateGlobals(); + auto& renderer = *g.pRender; + auto& gci = g.getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer(); + auto& sm = si.GetStateMachine(); + auto* hostTb = &si.GetTextBuffer(); + auto* termTb = term->_mainBuffer.get(); + + _flushFirstFrame(); + + _checkConptyOutput = false; + _logConpty = true; + + // Print two lines of text: + // |AAAAAAAA | + // |BBBBBBBB_ | + // (cursor on the '_') + // A's are in blue-on-green, + // B's are in red-on-yellow + + // A one line version: (more or less) + // + // printf "\x1b[2J\x1b[H" ; printf + // "\x1b[?25l\x1b[34;42mAAAAA\n\x1b[31;43mBBBBB\x1b[?25h" ; sleep 2 ; printf + // "\x1b[?1049h" ; sleep 2 ; printf "CCCCC" ; sleep 2 ; printf "\x1b[?1049h" + // ; sleep 2 + + sm.ProcessString(L"\x1b[?25l"); + sm.ProcessString(L"\x1b[34;42m"); + sm.ProcessString(std::wstring(50, L'A')); + sm.ProcessString(L"\n"); + sm.ProcessString(L"\x1b[31;43m"); + sm.ProcessString(std::wstring(50, L'B')); + sm.ProcessString(L"\x1b[?25h"); + + // Four frames here: + // * After text is printed to main buffer + // * after we go to the alt buffer + // * print more to the alt buffer + // * after we go back to the buffer + + enum class Frame : int + { + InMainBufferBefore = 0, + InAltBufferBefore, + InAltBufferAfter, + StillInAltBuffer + }; + + auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& viewport, const Frame frame) { + const short width = viewport.narrow_width(); + auto iter0 = tb.GetCellDataAt({ 0, 0 }); + auto iter1 = tb.GetCellDataAt({ 0, 1 }); + switch (frame) + { + case Frame::InMainBufferBefore: + { + TestUtils::VerifySpanOfText(L"A", iter0, 0, 50); + TestUtils::VerifySpanOfText(L" ", iter0, 0, static_cast(width - 50)); + + TestUtils::VerifySpanOfText(L"B", iter1, 0, 50); + TestUtils::VerifySpanOfText(L" ", iter1, 0, static_cast(width - 50)); + COORD expectedCursor{ 50, 1 }; + VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); + break; + } + case Frame::InAltBufferBefore: + { + TestUtils::VerifySpanOfText(L" ", iter0, 0, width); + TestUtils::VerifySpanOfText(L" ", iter1, 0, width); + + COORD expectedCursor{ 50, 1 }; + VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); + break; + } + case Frame::InAltBufferAfter: + { + TestUtils::VerifySpanOfText(L" ", iter0, 0, width); + TestUtils::VerifySpanOfText(L" ", iter1, 0, 50u); + TestUtils::VerifySpanOfText(L"C", iter1, 0, 5u); + TestUtils::VerifySpanOfText(L" ", iter1, 0, static_cast(width - 55)); + + COORD expectedCursor{ 55, 1 }; + VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); + break; + } + case Frame::StillInAltBuffer: + { + TestUtils::VerifySpanOfText(L" ", iter0, 0, width); + TestUtils::VerifySpanOfText(L" ", iter1, 0, width); + + COORD expectedCursor{ 55, 1 }; + VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); + break; + } + } + }; + + Log::Comment(L"========== Checking the host buffer state (InMainBufferBefore) =========="); + verifyBuffer(*hostTb, til::rect{ si.GetViewport().ToInclusive() }, Frame::InMainBufferBefore); + + Log::Comment(L"Painting the frame"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); + Log::Comment(L"========== Checking the terminal buffer state (InMainBufferBefore) =========="); + verifyBuffer(*termTb, til::rect{ term->_GetMutableViewport().ToInclusive() }, Frame::InMainBufferBefore); + + Log::Comment(L"========== Switch to the alt buffer =========="); + + gci.LockConsole(); // Lock must be taken to manipulate alt/main buffer state. + auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); + sm.ProcessString(L"\x1b[?1049h"); + + Log::Comment(L"Painting the frame"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); + + auto* siAlt = &gci.GetActiveOutputBuffer(); + auto* hostAltTb = &siAlt->GetTextBuffer(); + auto* termAltTb = &term->_activeBuffer(); + + VERIFY_IS_TRUE(term->_inAltBuffer()); + VERIFY_ARE_NOT_EQUAL(termAltTb, termTb); + + Log::Comment(L"========== Checking the host buffer state (InAltBufferBefore) =========="); + verifyBuffer(*hostAltTb, til::rect{ siAlt->GetViewport().ToInclusive() }, Frame::InAltBufferBefore); + + Log::Comment(L"Painting the frame"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); + Log::Comment(L"========== Checking the terminal buffer state (InAltBufferBefore) =========="); + verifyBuffer(*termAltTb, til::rect{ term->_GetMutableViewport().ToInclusive() }, Frame::InAltBufferBefore); + + Log::Comment(L"========== Add some text to the alt buffer =========="); + + sm.ProcessString(L"CCCCC"); + Log::Comment(L"========== Checking the host buffer state (InAltBufferAfter) =========="); + verifyBuffer(*hostAltTb, til::rect{ siAlt->GetViewport().ToInclusive() }, Frame::InAltBufferAfter); + Log::Comment(L"Painting the frame"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); + + Log::Comment(L"========== Checking the terminal buffer state (InAltBufferAfter) =========="); + verifyBuffer(*termAltTb, til::rect{ term->_GetMutableViewport().ToInclusive() }, Frame::InAltBufferAfter); + + Log::Comment(L"========== Stay in the alt buffer, what happens? =========="); + + sm.ProcessString(L"\x1b[?1049h"); + Log::Comment(L"Painting the frame"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); + + siAlt = &gci.GetActiveOutputBuffer(); + hostAltTb = &siAlt->GetTextBuffer(); + termAltTb = &term->_activeBuffer(); + + Log::Comment(L"========== Checking the host buffer state (StillInAltBuffer) =========="); + verifyBuffer(*hostAltTb, til::rect{ siAlt->GetViewport().ToInclusive() }, Frame::StillInAltBuffer); + + Log::Comment(L"========== Checking the terminal buffer state (StillInAltBuffer) =========="); + verifyBuffer(*termAltTb, til::rect{ term->_GetMutableViewport().ToInclusive() }, Frame::StillInAltBuffer); +} diff --git a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp index 38b89d69bdc..81fad12a2d8 100644 --- a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp @@ -157,7 +157,7 @@ void ScrollTest::TestNotifyScrolling() Log::Comment(L"Watch out - this test takes a while to run, and won't " L"output anything unless in encounters an error. This is expected."); - auto& termTb = *_term->_buffer; + auto& termTb = *_term->_mainBuffer; auto& termSm = *_term->_stateMachine; const auto totalBufferSize = termTb.GetSize().Height(); diff --git a/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp b/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp index 362535da374..9acfeb739dd 100644 --- a/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp @@ -138,29 +138,29 @@ void TerminalApiTest::CursorVisibility() DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); - VERIFY_IS_TRUE(term._buffer->GetCursor().IsVisible()); - VERIFY_IS_TRUE(term._buffer->GetCursor().IsOn()); - VERIFY_IS_TRUE(term._buffer->GetCursor().IsBlinkingAllowed()); + VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsVisible()); + VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsOn()); + VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsBlinkingAllowed()); term.SetCursorOn(false); - VERIFY_IS_TRUE(term._buffer->GetCursor().IsVisible()); - VERIFY_IS_FALSE(term._buffer->GetCursor().IsOn()); - VERIFY_IS_TRUE(term._buffer->GetCursor().IsBlinkingAllowed()); + VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsVisible()); + VERIFY_IS_FALSE(term._mainBuffer->GetCursor().IsOn()); + VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsBlinkingAllowed()); term.SetCursorOn(true); - VERIFY_IS_TRUE(term._buffer->GetCursor().IsVisible()); - VERIFY_IS_TRUE(term._buffer->GetCursor().IsOn()); - VERIFY_IS_TRUE(term._buffer->GetCursor().IsBlinkingAllowed()); + VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsVisible()); + VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsOn()); + VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsBlinkingAllowed()); term.SetCursorVisibility(false); - VERIFY_IS_FALSE(term._buffer->GetCursor().IsVisible()); - VERIFY_IS_TRUE(term._buffer->GetCursor().IsOn()); - VERIFY_IS_TRUE(term._buffer->GetCursor().IsBlinkingAllowed()); + VERIFY_IS_FALSE(term._mainBuffer->GetCursor().IsVisible()); + VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsOn()); + VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsBlinkingAllowed()); term.SetCursorOn(false); - VERIFY_IS_FALSE(term._buffer->GetCursor().IsVisible()); - VERIFY_IS_FALSE(term._buffer->GetCursor().IsOn()); - VERIFY_IS_TRUE(term._buffer->GetCursor().IsBlinkingAllowed()); + VERIFY_IS_FALSE(term._mainBuffer->GetCursor().IsVisible()); + VERIFY_IS_FALSE(term._mainBuffer->GetCursor().IsOn()); + VERIFY_IS_TRUE(term._mainBuffer->GetCursor().IsBlinkingAllowed()); } void TerminalApiTest::CursorVisibilityViaStateMachine() @@ -170,7 +170,7 @@ void TerminalApiTest::CursorVisibilityViaStateMachine() DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); - auto& tbi = *(term._buffer); + auto& tbi = *(term._mainBuffer); auto& stateMachine = *(term._stateMachine); auto& cursor = tbi.GetCursor(); @@ -222,7 +222,7 @@ void TerminalApiTest::CheckDoubleWidthCursor() DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); - auto& tbi = *(term._buffer); + auto& tbi = *(term._mainBuffer); auto& stateMachine = *(term._stateMachine); auto& cursor = tbi.GetCursor(); @@ -266,7 +266,7 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlink() DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); - auto& tbi = *(term._buffer); + auto& tbi = *(term._mainBuffer); auto& stateMachine = *(term._stateMachine); // Process the opening osc 8 sequence @@ -292,7 +292,7 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlinkCustomId() DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); - auto& tbi = *(term._buffer); + auto& tbi = *(term._mainBuffer); auto& stateMachine = *(term._stateMachine); // Process the opening osc 8 sequence @@ -320,7 +320,7 @@ void TerminalCoreUnitTests::TerminalApiTest::AddHyperlinkCustomIdDifferentUri() DummyRenderer renderer{ &term }; term.Create({ 100, 100 }, 0, renderer); - auto& tbi = *(term._buffer); + auto& tbi = *(term._mainBuffer); auto& stateMachine = *(term._stateMachine); // Process the opening osc 8 sequence diff --git a/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp b/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp index 1684fa244b0..aac133fd308 100644 --- a/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp @@ -79,7 +79,7 @@ class TerminalCoreUnitTests::TerminalBufferTests final void TerminalBufferTests::TestSimpleBufferWriting() { - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; auto& termSm = *term->_stateMachine; const auto initialView = term->GetViewport(); @@ -98,7 +98,7 @@ void TerminalBufferTests::TestSimpleBufferWriting() void TerminalBufferTests::TestWrappingCharByChar() { - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; auto& termSm = *term->_stateMachine; const auto initialView = term->GetViewport(); auto& cursor = termTb.GetCursor(); @@ -137,7 +137,7 @@ void TerminalBufferTests::TestWrappingCharByChar() void TerminalBufferTests::TestWrappingALongString() { - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; auto& termSm = *term->_stateMachine; const auto initialView = term->GetViewport(); auto& cursor = termTb.GetCursor(); @@ -171,7 +171,7 @@ void TerminalBufferTests::TestWrappingALongString() void TerminalBufferTests::DontSnapToOutputTest() { - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; auto& termSm = *term->_stateMachine; const auto initialView = term->GetViewport(); @@ -253,7 +253,7 @@ void TerminalBufferTests::DontSnapToOutputTest() void TerminalBufferTests::_SetTabStops(std::list columns, bool replace) { - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; auto& termSm = *term->_stateMachine; auto& cursor = termTb.GetCursor(); @@ -275,7 +275,7 @@ void TerminalBufferTests::_SetTabStops(std::list columns, bool replace) std::list TerminalBufferTests::_GetTabStops() { std::list columns; - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; auto& termSm = *term->_stateMachine; const auto initialView = term->GetViewport(); const auto lastColumn = initialView.RightInclusive(); @@ -321,7 +321,7 @@ void TerminalBufferTests::TestResetClearTabStops() void TerminalBufferTests::TestAddTabStop() { - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; auto& termSm = *term->_stateMachine; auto& cursor = termTb.GetCursor(); @@ -366,7 +366,7 @@ void TerminalBufferTests::TestAddTabStop() void TerminalBufferTests::TestClearTabStop() { - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; auto& termSm = *term->_stateMachine; auto& cursor = termTb.GetCursor(); @@ -475,7 +475,7 @@ void TerminalBufferTests::TestClearTabStop() void TerminalBufferTests::TestGetForwardTab() { - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; auto& termSm = *term->_stateMachine; const auto initialView = term->GetViewport(); auto& cursor = termTb.GetCursor(); @@ -545,7 +545,7 @@ void TerminalBufferTests::TestGetForwardTab() void TerminalBufferTests::TestGetReverseTab() { - auto& termTb = *term->_buffer; + auto& termTb = *term->_mainBuffer; auto& termSm = *term->_stateMachine; auto& cursor = termTb.GetCursor(); diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index 6863ef080a6..14271a4fb95 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -380,6 +380,16 @@ bool VtIo::IsUsingVt() const return hr; } +[[nodiscard]] HRESULT VtIo::SwitchScreenBuffer(const bool useAltBuffer) +{ + HRESULT hr = S_OK; + if (_pVtRenderEngine) + { + hr = _pVtRenderEngine->SwitchScreenBuffer(useAltBuffer); + } + return hr; +} + void VtIo::CloseInput() { // This will release the lock when it goes out of scope diff --git a/src/host/VtIo.hpp b/src/host/VtIo.hpp index 6c204d6cf44..3abba39c184 100644 --- a/src/host/VtIo.hpp +++ b/src/host/VtIo.hpp @@ -36,6 +36,8 @@ namespace Microsoft::Console::VirtualTerminal [[nodiscard]] HRESULT SuppressResizeRepaint(); [[nodiscard]] HRESULT SetCursorPosition(const COORD coordCursor); + [[nodiscard]] HRESULT SwitchScreenBuffer(const bool useAltBuffer); + void CloseInput(); void CloseOutput(); diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index 49dea7ba74b..351e877ecd6 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -1942,6 +1942,15 @@ const SCREEN_INFORMATION& SCREEN_INFORMATION::GetMainBuffer() const s_RemoveScreenBuffer(psiOldAltBuffer); // this will also delete the old alt buffer } + // GH#381: When we switch into the alt buffer: + // * flush the current frame, to clear out anything that we prepared for this buffer. + // * Emit a ?1049h/l to the remote side, to let them know that we've switched buffers. + if (gci.IsInVtIoMode() && ServiceLocator::LocateGlobals().pRender) + { + ServiceLocator::LocateGlobals().pRender->TriggerFlush(false); + LOG_IF_FAILED(gci.GetVtIo()->SwitchScreenBuffer(true)); + } + ::SetActiveScreenBuffer(*psiNewAltBuffer); // Kind of a hack until we have proper signal channels: If the client app wants window size events, send one for @@ -1972,6 +1981,16 @@ void SCREEN_INFORMATION::UseMainScreenBuffer() psiMain->ProcessResizeWindow(&(psiMain->_rcAltSavedClientNew), &(psiMain->_rcAltSavedClientOld)); psiMain->_fAltWindowChanged = false; } + + // GH#381: When we switch into the main buffer: + // * flush the current frame, to clear out anything that we prepared for this buffer. + // * Emit a ?1049h/l to the remote side, to let them know that we've switched buffers. + if (gci.IsInVtIoMode() && ServiceLocator::LocateGlobals().pRender) + { + ServiceLocator::LocateGlobals().pRender->TriggerFlush(false); + LOG_IF_FAILED(gci.GetVtIo()->SwitchScreenBuffer(false)); + } + ::SetActiveScreenBuffer(*psiMain); psiMain->UpdateScrollBars(); // The alt had disabled scrollbars, re-enable them diff --git a/src/interactivity/onecore/BgfxEngine.cpp b/src/interactivity/onecore/BgfxEngine.cpp index b3bdd606651..5bc8d3123db 100644 --- a/src/interactivity/onecore/BgfxEngine.cpp +++ b/src/interactivity/onecore/BgfxEngine.cpp @@ -63,12 +63,6 @@ BgfxEngine::BgfxEngine(PVOID SharedViewBase, LONG DisplayHeight, LONG DisplayWid return S_OK; } -[[nodiscard]] HRESULT BgfxEngine::InvalidateCircling(_Out_ bool* const pForcePaint) noexcept -{ - *pForcePaint = false; - return S_FALSE; -} - [[nodiscard]] HRESULT BgfxEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept { *pForcePaint = false; diff --git a/src/interactivity/onecore/BgfxEngine.hpp b/src/interactivity/onecore/BgfxEngine.hpp index 634a46fe412..032066b3e05 100644 --- a/src/interactivity/onecore/BgfxEngine.hpp +++ b/src/interactivity/onecore/BgfxEngine.hpp @@ -39,7 +39,6 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT InvalidateSelection(const std::vector& rectangles) noexcept override; [[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override; [[nodiscard]] HRESULT InvalidateAll() noexcept override; - [[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* const pForcePaint) noexcept override; [[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override; [[nodiscard]] HRESULT StartPaint() noexcept override; diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp index 7c49fb99675..525729dc309 100644 --- a/src/renderer/atlas/AtlasEngine.api.cpp +++ b/src/renderer/atlas/AtlasEngine.api.cpp @@ -130,7 +130,7 @@ constexpr HRESULT vec2_narrow(U x, U y, AtlasEngine::vec2& out) noexcept return S_OK; } -[[nodiscard]] HRESULT AtlasEngine::InvalidateCircling(_Out_ bool* const pForcePaint) noexcept +[[nodiscard]] HRESULT AtlasEngine::InvalidateFlush(_In_ const bool /*circled*/, _Out_ bool* const pForcePaint) noexcept { RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint); *pForcePaint = false; diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h index 1645544daba..ad3cc244184 100644 --- a/src/renderer/atlas/AtlasEngine.h +++ b/src/renderer/atlas/AtlasEngine.h @@ -33,7 +33,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT InvalidateSelection(const std::vector& rectangles) noexcept override; [[nodiscard]] HRESULT InvalidateScroll(const COORD* pcoordDelta) noexcept override; [[nodiscard]] HRESULT InvalidateAll() noexcept override; - [[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* pForcePaint) noexcept override; + [[nodiscard]] HRESULT InvalidateFlush(_In_ const bool circled, _Out_ bool* const pForcePaint) noexcept override; [[nodiscard]] HRESULT InvalidateTitle(std::wstring_view proposedTitle) noexcept override; [[nodiscard]] HRESULT NotifyNewText(const std::wstring_view newText) noexcept override; [[nodiscard]] HRESULT PrepareRenderInfo(const RenderFrameInfo& info) noexcept override; diff --git a/src/renderer/base/RenderEngineBase.cpp b/src/renderer/base/RenderEngineBase.cpp index ca506b9d2ab..cf588ffe634 100644 --- a/src/renderer/base/RenderEngineBase.cpp +++ b/src/renderer/base/RenderEngineBase.cpp @@ -82,3 +82,19 @@ void RenderEngineBase::WaitUntilCanRender() noexcept // Throttle the render loop a bit by default (~60 FPS), improving throughput. Sleep(8); } + +// Routine Description: +// - Notifies us that we're about to circle the buffer, giving us a chance to +// force a repaint before the buffer contents are lost. +// - The default implementation of flush, is to do nothing for most renderers. +// Arguments: +// - circled - ignored +// - pForcePaint - Always filled with false +// Return Value: +// - S_FALSE because we don't use this. +[[nodiscard]] HRESULT RenderEngineBase::InvalidateFlush(_In_ const bool /*circled*/, _Out_ bool* const pForcePaint) noexcept +{ + RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint); + *pForcePaint = false; + return S_FALSE; +} diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index e720708b9e2..a880fbc0bd7 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -456,14 +456,14 @@ void Renderer::TriggerScroll(const COORD* const pcoordDelta) // - // Return Value: // - -void Renderer::TriggerCircling() +void Renderer::TriggerFlush(const bool circling) { const auto rects = _GetSelectionRects(); FOREACH_ENGINE(pEngine) { bool fEngineRequestsRepaint = false; - HRESULT hr = pEngine->InvalidateCircling(&fEngineRequestsRepaint); + HRESULT hr = pEngine->InvalidateFlush(circling, &fEngineRequestsRepaint); LOG_IF_FAILED(hr); LOG_IF_FAILED(pEngine->InvalidateSelection(rects)); diff --git a/src/renderer/base/renderer.hpp b/src/renderer/base/renderer.hpp index 0a84c6b6e0b..e47d5a21450 100644 --- a/src/renderer/base/renderer.hpp +++ b/src/renderer/base/renderer.hpp @@ -51,7 +51,7 @@ namespace Microsoft::Console::Render void TriggerScroll(); void TriggerScroll(const COORD* const pcoordDelta); - void TriggerCircling(); + void TriggerFlush(const bool circling); void TriggerTitleChange(); void TriggerNewTextNotification(const std::wstring_view newText); diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp index 265160cc983..42a3ea7e90f 100644 --- a/src/renderer/dx/DxRenderer.cpp +++ b/src/renderer/dx/DxRenderer.cpp @@ -1200,20 +1200,6 @@ try } CATCH_RETURN(); -// Routine Description: -// - This currently has no effect in this renderer. -// Arguments: -// - pForcePaint - Always filled with false -// Return Value: -// - S_FALSE because we don't use this. -[[nodiscard]] HRESULT DxEngine::InvalidateCircling(_Out_ bool* const pForcePaint) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint); - - *pForcePaint = false; - return S_FALSE; -} - // Routine Description: // - Gets the area in pixels of the surface we are targeting // Arguments: diff --git a/src/renderer/dx/DxRenderer.hpp b/src/renderer/dx/DxRenderer.hpp index 7cfe9834abb..5d86fa648ae 100644 --- a/src/renderer/dx/DxRenderer.hpp +++ b/src/renderer/dx/DxRenderer.hpp @@ -79,7 +79,6 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT InvalidateSelection(const std::vector& rectangles) noexcept override; [[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override; [[nodiscard]] HRESULT InvalidateAll() noexcept override; - [[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* const pForcePaint) noexcept override; [[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override; [[nodiscard]] HRESULT StartPaint() noexcept override; diff --git a/src/renderer/gdi/gdirenderer.hpp b/src/renderer/gdi/gdirenderer.hpp index 8b0cab05b4d..e2d98ff939a 100644 --- a/src/renderer/gdi/gdirenderer.hpp +++ b/src/renderer/gdi/gdirenderer.hpp @@ -33,7 +33,6 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override; [[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept override; [[nodiscard]] HRESULT InvalidateAll() noexcept override; - [[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* const pForcePaint) noexcept override; [[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override; [[nodiscard]] HRESULT StartPaint() noexcept override; diff --git a/src/renderer/gdi/invalidate.cpp b/src/renderer/gdi/invalidate.cpp index ca77b03cd12..c55e21d2144 100644 --- a/src/renderer/gdi/invalidate.cpp +++ b/src/renderer/gdi/invalidate.cpp @@ -109,21 +109,6 @@ HRESULT GdiEngine::InvalidateAll() noexcept RETURN_HR(InvalidateSystem(&rc)); } -// Method Description: -// - Notifies us that we're about to circle the buffer, giving us a chance to -// force a repaint before the buffer contents are lost. The GDI renderer -// doesn't care if we lose text - we're only painting visible text anyways, -// so we return false. -// Arguments: -// - Receives a bool indicating if we should force the repaint. -// Return Value: -// - S_FALSE - we succeeded, but the result was false. -HRESULT GdiEngine::InvalidateCircling(_Out_ bool* const pForcePaint) noexcept -{ - *pForcePaint = false; - return S_FALSE; -} - // Method Description: // - Notifies us that we're about to be torn down. This gives us a last chance // to force a repaint before the buffer contents are lost. The GDI renderer diff --git a/src/renderer/inc/IRenderEngine.hpp b/src/renderer/inc/IRenderEngine.hpp index 3d13595aabc..3bd45e7e734 100644 --- a/src/renderer/inc/IRenderEngine.hpp +++ b/src/renderer/inc/IRenderEngine.hpp @@ -67,7 +67,7 @@ namespace Microsoft::Console::Render [[nodiscard]] virtual HRESULT InvalidateSelection(const std::vector& rectangles) noexcept = 0; [[nodiscard]] virtual HRESULT InvalidateScroll(const COORD* pcoordDelta) noexcept = 0; [[nodiscard]] virtual HRESULT InvalidateAll() noexcept = 0; - [[nodiscard]] virtual HRESULT InvalidateCircling(_Out_ bool* pForcePaint) noexcept = 0; + [[nodiscard]] virtual HRESULT InvalidateFlush(_In_ const bool circled, _Out_ bool* const pForcePaint) noexcept = 0; [[nodiscard]] virtual HRESULT InvalidateTitle(std::wstring_view proposedTitle) noexcept = 0; [[nodiscard]] virtual HRESULT NotifyNewText(const std::wstring_view newText) noexcept = 0; [[nodiscard]] virtual HRESULT PrepareRenderInfo(const RenderFrameInfo& info) noexcept = 0; diff --git a/src/renderer/inc/RenderEngineBase.hpp b/src/renderer/inc/RenderEngineBase.hpp index 452dd0cc39f..25998933381 100644 --- a/src/renderer/inc/RenderEngineBase.hpp +++ b/src/renderer/inc/RenderEngineBase.hpp @@ -53,6 +53,8 @@ namespace Microsoft::Console::Render [[nodiscard]] virtual bool RequiresContinuousRedraw() noexcept override; + [[nodiscard]] HRESULT InvalidateFlush(_In_ const bool circled, _Out_ bool* const pForcePaint) noexcept override; + void WaitUntilCanRender() noexcept override; protected: diff --git a/src/renderer/uia/UiaRenderer.cpp b/src/renderer/uia/UiaRenderer.cpp index 1b31b6f4916..2d83d970dbc 100644 --- a/src/renderer/uia/UiaRenderer.cpp +++ b/src/renderer/uia/UiaRenderer.cpp @@ -168,20 +168,6 @@ CATCH_RETURN(); return S_OK; } -// Routine Description: -// - This currently has no effect in this renderer. -// Arguments: -// - pForcePaint - Always filled with false -// Return Value: -// - S_FALSE because we don't use this. -[[nodiscard]] HRESULT UiaEngine::InvalidateCircling(_Out_ bool* const pForcePaint) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint); - - *pForcePaint = false; - return S_FALSE; -} - [[nodiscard]] HRESULT UiaEngine::NotifyNewText(const std::wstring_view newText) noexcept try { diff --git a/src/renderer/uia/UiaRenderer.hpp b/src/renderer/uia/UiaRenderer.hpp index 8f1603aeac6..c56f1cc1084 100644 --- a/src/renderer/uia/UiaRenderer.hpp +++ b/src/renderer/uia/UiaRenderer.hpp @@ -46,7 +46,6 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT InvalidateSelection(const std::vector& rectangles) noexcept override; [[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override; [[nodiscard]] HRESULT InvalidateAll() noexcept override; - [[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* const pForcePaint) noexcept override; [[nodiscard]] HRESULT NotifyNewText(const std::wstring_view newText) noexcept override; [[nodiscard]] HRESULT PaintBackground() noexcept override; [[nodiscard]] HRESULT PaintBufferLine(gsl::span const clusters, const COORD coord, const bool fTrimLeft, const bool lineWrapped) noexcept override; diff --git a/src/renderer/vt/VtSequences.cpp b/src/renderer/vt/VtSequences.cpp index 58bb3f2f3bb..05fd6f95184 100644 --- a/src/renderer/vt/VtSequences.cpp +++ b/src/renderer/vt/VtSequences.cpp @@ -434,6 +434,17 @@ using namespace Microsoft::Console::Render; return _Write("\x1b[?9001h"); } +// Method Description: +// - Send a sequence to the connected terminal to switch to the alternate or main screen buffer. +// Arguments: +// - useAltBuffer: if true, switch to the alt buffer, otherwise to the main buffer. +// Return Value: +// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. +[[nodiscard]] HRESULT VtEngine::_SwitchScreenBuffer(const bool useAltBuffer) noexcept +{ + return _Write(useAltBuffer ? "\x1b[?1049h" : "\x1b[?1049l"); +} + // Method Description: // - Formats and writes a sequence to add a hyperlink to the terminal buffer // Arguments: diff --git a/src/renderer/vt/invalidate.cpp b/src/renderer/vt/invalidate.cpp index 5132f8bec0b..86e21637845 100644 --- a/src/renderer/vt/invalidate.cpp +++ b/src/renderer/vt/invalidate.cpp @@ -105,7 +105,7 @@ CATCH_RETURN(); // - Receives a bool indicating if we should force the repaint. // Return Value: // - S_OK -[[nodiscard]] HRESULT VtEngine::InvalidateCircling(_Out_ bool* const pForcePaint) noexcept +[[nodiscard]] HRESULT VtEngine::InvalidateFlush(_In_ const bool circled, _Out_ bool* const pForcePaint) noexcept { // If we're in the middle of a resize request, don't try to immediately start a frame. if (_inResizeRequest) @@ -118,7 +118,7 @@ CATCH_RETURN(); // Keep track of the fact that we circled, we'll need to do some work on // end paint to specifically handle this. - _circled = true; + _circled = circled; } _trace.TraceTriggerCircling(*pForcePaint); diff --git a/src/renderer/vt/state.cpp b/src/renderer/vt/state.cpp index 2f9d83decda..1f3cf516461 100644 --- a/src/renderer/vt/state.cpp +++ b/src/renderer/vt/state.cpp @@ -527,3 +527,10 @@ HRESULT VtEngine::RequestWin32Input() noexcept RETURN_IF_FAILED(_Flush()); return S_OK; } + +HRESULT VtEngine::SwitchScreenBuffer(const bool useAltBuffer) noexcept +{ + RETURN_IF_FAILED(_SwitchScreenBuffer(useAltBuffer)); + RETURN_IF_FAILED(_Flush()); + return S_OK; +} diff --git a/src/renderer/vt/vtrenderer.hpp b/src/renderer/vt/vtrenderer.hpp index ee2a1cd720d..0d1e36bb94b 100644 --- a/src/renderer/vt/vtrenderer.hpp +++ b/src/renderer/vt/vtrenderer.hpp @@ -56,7 +56,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT InvalidateSystem(const RECT* prcDirtyClient) noexcept override; [[nodiscard]] HRESULT InvalidateSelection(const std::vector& rectangles) noexcept override; [[nodiscard]] HRESULT InvalidateAll() noexcept override; - [[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* pForcePaint) noexcept override; + [[nodiscard]] HRESULT InvalidateFlush(_In_ const bool circled, _Out_ bool* const pForcePaint) noexcept override; [[nodiscard]] HRESULT PaintBackground() noexcept override; [[nodiscard]] HRESULT PaintBufferLine(gsl::span clusters, COORD coord, bool fTrimLeft, bool lineWrapped) noexcept override; [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, COORD coordTarget) noexcept override; @@ -85,6 +85,7 @@ namespace Microsoft::Console::Render void SetTerminalCursorTextPosition(const COORD coordCursor) noexcept; [[nodiscard]] virtual HRESULT ManuallyClearScrollback() noexcept; [[nodiscard]] HRESULT RequestWin32Input() noexcept; + [[nodiscard]] HRESULT SwitchScreenBuffer(const bool useAltBuffer) noexcept; protected: wil::unique_hfile _hFile; @@ -196,6 +197,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT _ListenForDSR() noexcept; [[nodiscard]] HRESULT _RequestWin32Input() noexcept; + [[nodiscard]] HRESULT _SwitchScreenBuffer(const bool useAltBuffer) noexcept; [[nodiscard]] virtual HRESULT _MoveCursor(const COORD coord) noexcept = 0; [[nodiscard]] HRESULT _RgbUpdateDrawingBrushes(const TextAttribute& textAttributes) noexcept; diff --git a/src/renderer/wddmcon/WddmConRenderer.cpp b/src/renderer/wddmcon/WddmConRenderer.cpp index 16f1cda9b3e..5a1c6f5abf9 100644 --- a/src/renderer/wddmcon/WddmConRenderer.cpp +++ b/src/renderer/wddmcon/WddmConRenderer.cpp @@ -191,12 +191,6 @@ bool WddmConEngine::IsInitialized() return S_OK; } -[[nodiscard]] HRESULT WddmConEngine::InvalidateCircling(_Out_ bool* const pForcePaint) noexcept -{ - *pForcePaint = false; - return S_FALSE; -} - [[nodiscard]] HRESULT WddmConEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept { *pForcePaint = false; diff --git a/src/renderer/wddmcon/WddmConRenderer.hpp b/src/renderer/wddmcon/WddmConRenderer.hpp index 4a8bf83ef47..58f935b97a3 100644 --- a/src/renderer/wddmcon/WddmConRenderer.hpp +++ b/src/renderer/wddmcon/WddmConRenderer.hpp @@ -31,7 +31,6 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT InvalidateSelection(const std::vector& rectangles) noexcept override; [[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override; [[nodiscard]] HRESULT InvalidateAll() noexcept override; - [[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* const pForcePaint) noexcept override; [[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override; [[nodiscard]] HRESULT StartPaint() noexcept override;