mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-23 21:08:06 -05:00
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.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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<TextBuffer>(bufferSize, attr, cursorSize, true, renderer);
|
||||
_mainBuffer = std::make_unique<TextBuffer>(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<TextBuffer> 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<TextBuffer>(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<short> 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<int>(::base::ClampSub(_mutableViewport.Top(), newVisibleTop));
|
||||
|
||||
// Now that we've finished the hard work of resizing the main buffer and
|
||||
// getting the viewport back into the right spot, we need to ALSO resize the
|
||||
// alt buffer, if one exists. Fortunately, this is easy. We don't need to
|
||||
// worry about the viewport and scrollback at all! The alt buffer never has
|
||||
// any scrollback, so we just need to resize it and presto, we're done.
|
||||
if (_inAltBuffer())
|
||||
{
|
||||
_altBuffer->GetCursor().StartDeferDrawing();
|
||||
// we're capturing `this` here because when we exit, we want to EndDefer on the (newly created) active buffer.
|
||||
auto endDefer = wil::scope_exit([this]() noexcept { _altBuffer->GetCursor().EndDeferDrawing(); });
|
||||
|
||||
// First allocate a new text buffer to take the place of the current one.
|
||||
std::unique_ptr<TextBuffer> newTextBuffer;
|
||||
try
|
||||
{
|
||||
// GH#3848 - Stash away the current attributes
|
||||
const auto oldBufferAttributes = _altBuffer->GetCurrentAttributes();
|
||||
newTextBuffer = std::make_unique<TextBuffer>(bufferSize,
|
||||
TextAttribute{},
|
||||
0, // temporarily set size to 0 so it won't render.
|
||||
true,
|
||||
_mainBuffer->GetRenderer());
|
||||
|
||||
// start defer drawing on the new buffer
|
||||
newTextBuffer->GetCursor().StartDeferDrawing();
|
||||
|
||||
// We don't need any fancy position information. We're just gonna
|
||||
// resize the buffer, it's gonna be in exactly the place it is now.
|
||||
// There's no scrollback to worry about!
|
||||
|
||||
RETURN_IF_FAILED(TextBuffer::Reflow(*_altBuffer.get(),
|
||||
*newTextBuffer.get(),
|
||||
_GetMutableViewport(),
|
||||
std::nullopt));
|
||||
|
||||
// Restore the active text attributes
|
||||
newTextBuffer->SetCurrentAttributes(oldBufferAttributes);
|
||||
_altBuffer.swap(newTextBuffer);
|
||||
}
|
||||
CATCH_RETURN();
|
||||
}
|
||||
|
||||
// GH#5029 - make sure to InvalidateAll here, so that we'll paint the entire visible viewport.
|
||||
try
|
||||
{
|
||||
_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<SHORT>(_buffer->GetRowByOffset(0).size());
|
||||
const auto rowSize = gsl::narrow<SHORT>(_activeBuffer().GetRowByOffset(0).size());
|
||||
|
||||
// invalidate the first line
|
||||
SMALL_RECT region{ start.X, start.Y, gsl::narrow<short>(rowSize - 1), gsl::narrow<short>(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<short>(rowSize - 1), gsl::narrow<short>(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<short>(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<short>(-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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +138,8 @@ public:
|
||||
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 @@ private:
|
||||
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<TextBuffer> _buffer;
|
||||
std::unique_ptr<TextBuffer> _mainBuffer;
|
||||
std::unique_ptr<TextBuffer> _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 @@ private:
|
||||
|
||||
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<SMALL_RECT> _GetSelectionRects() const noexcept;
|
||||
|
||||
@@ -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()
|
||||
// - <none>
|
||||
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<short>(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)
|
||||
// - <none>
|
||||
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)
|
||||
// - <none>
|
||||
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()
|
||||
// - <none>
|
||||
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)
|
||||
// - <none>
|
||||
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<TextBuffer>(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();
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
// - <none>
|
||||
// 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:
|
||||
// - <none>
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ public:
|
||||
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 @@ public:
|
||||
|
||||
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<bool> _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<CursorState, 2> _savedCursorState;
|
||||
bool _usingAltBuffer = false;
|
||||
|
||||
size_t _SetRgbColorsHelper(const ::Microsoft::Console::VirtualTerminal::VTParameters options,
|
||||
TextAttribute& attr,
|
||||
const bool isForeground);
|
||||
|
||||
@@ -54,7 +54,7 @@ std::vector<SMALL_RECT> 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<SelectionE
|
||||
// - the new start/end for a selection
|
||||
std::pair<COORD, COORD> 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<COORD, COORD> Terminal::_ExpandSelectionAnchors(std::pair<COORD, COORD
|
||||
COORD start = anchors.first;
|
||||
COORD end = anchors.second;
|
||||
|
||||
const auto bufferSize = _buffer->GetSize();
|
||||
const auto bufferSize = _activeBuffer().GetSize();
|
||||
switch (_multiClickSelectionMode)
|
||||
{
|
||||
case SelectionExpansion::Line:
|
||||
@@ -215,8 +215,8 @@ std::pair<COORD, COORD> Terminal::_ExpandSelectionAnchors(std::pair<COORD, COORD
|
||||
end = { bufferSize.RightInclusive(), end.Y };
|
||||
break;
|
||||
case SelectionExpansion::Word:
|
||||
start = _buffer->GetWordStart(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<short, short>(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<short, short>(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<short>(_VisibleStartIndex()) + viewportPos.Y;
|
||||
COORD bufferPos = { viewportPos.X, yPos };
|
||||
_buffer->GetSize().Clamp(bufferPos);
|
||||
_activeBuffer().GetSize().Clamp(bufferPos);
|
||||
return bufferPos;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 | <break>
|
||||
// |BBBBBBBB_ | <break>
|
||||
// (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<short>();
|
||||
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<size_t>(width - 50));
|
||||
|
||||
TestUtils::VerifySpanOfText(L"B", iter1, 0, 50);
|
||||
TestUtils::VerifySpanOfText(L" ", iter1, 0, static_cast<size_t>(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<size_t>(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 | <break>
|
||||
// |BBBBBBBB_ | <break>
|
||||
// (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<short>();
|
||||
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<size_t>(width - 50));
|
||||
|
||||
TestUtils::VerifySpanOfText(L"B", iter1, 0, 50);
|
||||
TestUtils::VerifySpanOfText(L" ", iter1, 0, static_cast<size_t>(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<size_t>(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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -79,7 +79,7 @@ private:
|
||||
|
||||
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<short> 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<short> columns, bool replace)
|
||||
std::list<short> TerminalBufferTests::_GetTabStops()
|
||||
{
|
||||
std::list<short> 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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -39,7 +39,6 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& 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;
|
||||
|
||||
@@ -130,7 +130,7 @@ constexpr HRESULT vec2_narrow(U x, U y, AtlasEngine::vec2<T>& 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;
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& 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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -456,14 +456,14 @@ void Renderer::TriggerScroll(const COORD* const pcoordDelta)
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -79,7 +79,6 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& 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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] virtual HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& 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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -46,7 +46,6 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& 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 Cluster> const clusters, const COORD coord, const bool fTrimLeft, const bool lineWrapped) noexcept override;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT InvalidateSystem(const RECT* prcDirtyClient) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& 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<const Cluster> 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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -31,7 +31,6 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& 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;
|
||||
|
||||
Reference in New Issue
Block a user