Improve the VT cursor movement implementation (#3628)

## Summary of the Pull Request

Originally there were 3 different methods for implementing VT cursor movement, and between them they still couldn't handle some of the operations correctly. This PR unifies those operations into a single method that can handle every type of cursor movement, and which fixes some of the issues with the existing implementations. In particular it fixes the `CNL` and `CPL` operations, so they're now correctly constrained by the `DECSTBM` margins.

## References

If this PR is accepted, the method added here should make it trivial to implement the `VPR` and `HPR` commands in issue #3428.

## PR Checklist
* [x] Closes #2926
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] Tests added/passed
* [ ] Requires documentation to be updated
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

## Detailed Description of the Pull Request / Additional comments

The new [`AdaptDispatch::_CursorMovePosition`](d6c4f35cf6/src/terminal/adapter/adaptDispatch.cpp (L169)) method is based on the proposal I made in issue #3428 for the `VPR` and `HPR` comands. It takes three arguments: a row offset (which can be absolute or relative), a column offset (ditto), and a flag specifying whether the position should be constrained by the `DECSTBM` margins.

To make the code more readable, I've implemented the offsets using [a `struct` with some `constexpr` helper functions for the construction](d6c4f35cf6/src/terminal/adapter/adaptDispatch.hpp (L116-L125)). This lets you specify the parameters with expressions like `Offset::Absolute(col)` or `Offset::Forward(distance)` which I think makes the calling code a little easier to understand.

While implementing this new method, I noticed a couple of issues in the existing movement implementations which I thought would be good to fix at the same time.

1. When cursor movement is constrained horizontally, it should be constrained by the buffer width, and not the horizontal viewport boundaries. This is an issue I've previously corrected in other parts of the codebase, and I think the cursor movement was one of the last areas where it was still a problem.

2. A number of the commands had range and overflow checks for their parameters that were either unnecessary (testing for a condition that could never occur) or incorrect (if an operation overflows, the correct behavior is to clamp it, and not just fail). The new implementation handles legitimate overflows correctly, but doesn't check for impossible ranges.

Because of the change of behavior in point 1, I also had to update the implementations of [the `DECSC` and `CPR` commands](9cf7a9b577) to account for the column offset now being relative to the buffer and not the viewport, otherwise those operations would no longer work correctly.

## Validation Steps Performed

Because of the two changes in behavior mentioned above, there were a number of adapter tests that stopped working and needed to be updated. First off there were those that expected the column offset to be relative to the left viewport position and constrained by the viewport width. These now had to be updated to [use the full buffer width](49887a3589) as the allowed horizontal extent.

Then there were all the overflow and out-of-range tests that were testing conditions that could never occur in practice, or where the expected behavior that was tested was actually incorrect. I did spend some time trying to see if there was value in updating these tests somehow, but in the end I decided it was best to just [drop them](6e80d0de19) altogether.

For the `CNL` and `CPL` operations, there didn't appear to be any existing tests, so I added some [new screen buffer tests](d6c4f35cf6) to check that those operations now work correctly, both with and without margins.
This commit is contained in:
James Holderness 2020-01-16 22:33:35 +00:00 committed by msftbot[bot]
parent 4d1c7cf3eb
commit 2fec1787a0
9 changed files with 189 additions and 489 deletions

View file

@ -1436,67 +1436,6 @@ void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool
return Status;
}
// Routine Description:
// - A private API call for moving the cursor vertically in the buffer. This is
// because the vertical cursor movements in VT are constrained by the
// scroll margins, while the absolute positioning is not.
// Parameters:
// - screenInfo - a reference to the screen buffer we should move the cursor for
// - lines - The number of lines to move the cursor. Up is negative, down positive.
// Return value:
// - S_OK if handled successfully. Otherwise an appropriate HRESULT for failing to clamp.
[[nodiscard]] HRESULT DoSrvMoveCursorVertically(SCREEN_INFORMATION& screenInfo, const short lines)
{
auto& cursor = screenInfo.GetActiveBuffer().GetTextBuffer().GetCursor();
COORD clampedPos = { cursor.GetPosition().X, cursor.GetPosition().Y + lines };
// Make sure the cursor doesn't move outside the viewport.
screenInfo.GetViewport().Clamp(clampedPos);
// Make sure the cursor stays inside the margins
if (screenInfo.AreMarginsSet())
{
const auto margins = screenInfo.GetAbsoluteScrollMargins().ToInclusive();
const auto cursorY = cursor.GetPosition().Y;
const auto lo = margins.Top;
const auto hi = margins.Bottom;
// See microsoft/terminal#2929 - If the cursor is _below_ the top
// margin, it should stay below the top margin. If it's _above_ the
// bottom, it should stay above the bottom. Cursor movements that stay
// outside the margins shouldn't necessarily be affected. For example,
// moving up while below the bottom margin shouldn't just jump straight
// to the bottom margin. See
// ScreenBufferTests::CursorUpDownOutsideMargins for a test of that
// behavior.
const bool cursorBelowTop = cursorY >= lo;
const bool cursorAboveBottom = cursorY <= hi;
if (cursorBelowTop)
{
try
{
clampedPos.Y = std::max(clampedPos.Y, lo);
}
CATCH_RETURN();
}
if (cursorAboveBottom)
{
try
{
clampedPos.Y = std::min(clampedPos.Y, hi);
}
CATCH_RETURN();
}
}
cursor.SetPosition(clampedPos);
return S_OK;
}
// Routine Description:
// - A private API call for swapping 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

View file

@ -35,7 +35,6 @@ void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool
[[nodiscard]] NTSTATUS DoSrvPrivateSetScrollingRegion(SCREEN_INFORMATION& screenInfo, const SMALL_RECT& scrollMargins);
[[nodiscard]] NTSTATUS DoSrvPrivateLineFeed(SCREEN_INFORMATION& screenInfo, const bool withReturn);
[[nodiscard]] NTSTATUS DoSrvPrivateReverseLineFeed(SCREEN_INFORMATION& screenInfo);
[[nodiscard]] HRESULT DoSrvMoveCursorVertically(SCREEN_INFORMATION& screenInfo, const short lines);
[[nodiscard]] NTSTATUS DoSrvPrivateUseAlternateScreenBuffer(SCREEN_INFORMATION& screenInfo);
void DoSrvPrivateUseMainScreenBuffer(SCREEN_INFORMATION& screenInfo);

View file

@ -422,23 +422,6 @@ bool ConhostInternalGetSet::PrivateReverseLineFeed()
return NT_SUCCESS(DoSrvPrivateReverseLineFeed(_io.GetActiveOutputBuffer()));
}
// Routine Description:
// - Connects the MoveCursorVertically call directly into our Driver Message servicing call inside Conhost.exe
// MoveCursorVertically is an internal-only "API" call that the vt commands can execute,
// but it is not represented as a function call on out public API surface.
// Return Value:
// - true if successful (see DoSrvMoveCursorVertically). false otherwise.
bool ConhostInternalGetSet::MoveCursorVertically(const ptrdiff_t lines)
{
SHORT l;
if (FAILED(PtrdiffTToShort(lines, &l)))
{
return false;
}
return SUCCEEDED(DoSrvMoveCursorVertically(_io.GetActiveOutputBuffer(), l));
}
// Routine Description:
// - Connects the SetConsoleTitleW API call directly into our Driver Message servicing call inside Conhost.exe
// Arguments:

View file

@ -102,8 +102,6 @@ public:
bool PrivateLineFeed(const bool withReturn) override;
bool PrivateReverseLineFeed() override;
bool MoveCursorVertically(const ptrdiff_t lines) override;
bool SetConsoleTitleW(const std::wstring_view title) override;
bool PrivateUseAlternateScreenBuffer() override;

View file

@ -201,6 +201,8 @@ class ScreenBufferTests
TEST_METHOD(CursorUpDownOutsideMargins);
TEST_METHOD(CursorUpDownExactlyAtMargins);
TEST_METHOD(CursorNextPreviousLine);
TEST_METHOD(CursorSaveRestore);
TEST_METHOD(ScreenAlignmentPattern);
@ -5384,6 +5386,70 @@ void ScreenBufferTests::CursorUpDownExactlyAtMargins()
stateMachine.ProcessString(L"\x1b[r");
}
void ScreenBufferTests::CursorNextPreviousLine()
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& stateMachine = si.GetStateMachine();
auto& cursor = si.GetTextBuffer().GetCursor();
Log::Comment(L"Make sure the viewport is at 0,0");
VERIFY_SUCCEEDED(si.SetViewportOrigin(true, COORD({ 0, 0 }), true));
Log::Comment(L"CNL without margins");
// Starting from column 20 of line 10.
cursor.SetPosition(COORD{ 20, 10 });
// Move down 5 lines (CNL).
stateMachine.ProcessString(L"\x1b[5E");
// We should end up in column 0 of line 15.
VERIFY_ARE_EQUAL(COORD({ 0, 15 }), cursor.GetPosition());
Log::Comment(L"CPL without margins");
// Starting from column 20 of line 10.
cursor.SetPosition(COORD{ 20, 10 });
// Move up 5 lines (CPL).
stateMachine.ProcessString(L"\x1b[5F");
// We should end up in column 0 of line 5.
VERIFY_ARE_EQUAL(COORD({ 0, 5 }), cursor.GetPosition());
// Set the margins to 8:12 (9:13 in VT coordinates).
stateMachine.ProcessString(L"\x1b[9;13r");
// Make sure we clear the margins on exit so they can't break other tests.
auto clearMargins = wil::scope_exit([&] { stateMachine.ProcessString(L"\x1b[r"); });
Log::Comment(L"CNL inside margins");
// Starting from column 20 of line 10.
cursor.SetPosition(COORD{ 20, 10 });
// Move down 5 lines (CNL).
stateMachine.ProcessString(L"\x1b[5E");
// We should stop on line 12, the bottom margin.
VERIFY_ARE_EQUAL(COORD({ 0, 12 }), cursor.GetPosition());
Log::Comment(L"CPL inside margins");
// Starting from column 20 of line 10.
cursor.SetPosition(COORD{ 20, 10 });
// Move up 5 lines (CPL).
stateMachine.ProcessString(L"\x1b[5F");
// We should stop on line 8, the top margin.
VERIFY_ARE_EQUAL(COORD({ 0, 8 }), cursor.GetPosition());
Log::Comment(L"CNL below bottom");
// Starting from column 20 of line 13 (1 below bottom margin).
cursor.SetPosition(COORD{ 20, 13 });
// Move down 5 lines (CNL).
stateMachine.ProcessString(L"\x1b[5E");
// We should end up in column 0 of line 18.
VERIFY_ARE_EQUAL(COORD({ 0, 18 }), cursor.GetPosition());
Log::Comment(L"CPL above top margin");
// Starting from column 20 of line 7 (1 above top margin).
cursor.SetPosition(COORD{ 20, 7 });
// Move up 5 lines (CPL).
stateMachine.ProcessString(L"\x1b[5F");
// We should end up in column 0 of line 2.
VERIFY_ARE_EQUAL(COORD({ 0, 2 }), cursor.GetPosition());
}
void ScreenBufferTests::CursorSaveRestore()
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();

View file

@ -81,137 +81,6 @@ void AdaptDispatch::PrintString(const std::wstring_view string)
CATCH_LOG();
}
// Routine Description:
// - Generalizes cursor movement for up/down/left/right and next/previous line.
// Arguments:
// - dir - Specific direction to move
// - distance - Magnitude of the move
// Return Value:
// - True if handled successfully. False otherwise.
bool AdaptDispatch::_CursorMovement(const CursorDirection dir, const size_t distance) const
{
// First retrieve some information about the buffer
CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
// Make sure to reset the viewport (with MoveToBottom )to where it was
// before the user scrolled the console output
bool success = (_pConApi->MoveToBottom() && _pConApi->GetConsoleScreenBufferInfoEx(csbiex));
if (success)
{
COORD cursor = csbiex.dwCursorPosition;
// For next/previous line, we unconditionally need to move the X position to the left edge of the viewport.
switch (dir)
{
case CursorDirection::NextLine:
case CursorDirection::PrevLine:
cursor.X = csbiex.srWindow.Left;
break;
}
// Safely convert the UINT magnitude of the move we were given into a short (which is the size the console deals with)
SHORT delta = 0;
success = SUCCEEDED(SizeTToShort(distance, &delta));
if (success)
{
// Prepare our variables for math. All operations are some variation on these two parameters
SHORT* pModify = nullptr; // The coordinate X or Y gets modified
SHORT boundary = 0; // There is a particular edge of the viewport that is our boundary condition as we approach it.
// Up and Down modify the Y coordinate. Left and Right modify the X.
switch (dir)
{
case CursorDirection::Up:
case CursorDirection::Down:
case CursorDirection::NextLine:
case CursorDirection::PrevLine:
pModify = &cursor.Y;
break;
case CursorDirection::Left:
case CursorDirection::Right:
pModify = &cursor.X;
break;
default:
success = false;
break;
}
// Moving upward is bounded by top, etc.
switch (dir)
{
case CursorDirection::Up:
case CursorDirection::PrevLine:
boundary = csbiex.srWindow.Top;
break;
case CursorDirection::Down:
case CursorDirection::NextLine:
boundary = csbiex.srWindow.Bottom;
break;
case CursorDirection::Left:
boundary = csbiex.srWindow.Left;
break;
case CursorDirection::Right:
boundary = csbiex.srWindow.Right;
break;
default:
success = false;
break;
}
if (success && pModify)
{
// For up and left, we need to subtract the magnitude of the vector to get the new spot. Right/down = add.
// Use safe short subtraction to prevent under/overflow.
switch (dir)
{
case CursorDirection::Up:
case CursorDirection::Left:
case CursorDirection::PrevLine:
success = SUCCEEDED(ShortSub(*pModify, delta, pModify));
break;
case CursorDirection::Down:
case CursorDirection::Right:
case CursorDirection::NextLine:
success = SUCCEEDED(ShortAdd(*pModify, delta, pModify));
break;
}
if (success)
{
// Now apply the boundary condition. Up, Left can't be smaller than their boundary. Top, Right can't be larger.
switch (dir)
{
case CursorDirection::Up:
case CursorDirection::Left:
case CursorDirection::PrevLine:
*pModify = std::max(*pModify, boundary);
break;
case CursorDirection::Down:
case CursorDirection::Right:
case CursorDirection::NextLine:
// For the bottom and right edges, the viewport value is stated to be one outside the rectangle.
*pModify = std::min(*pModify, gsl::narrow<SHORT>(boundary - 1));
break;
default:
success = false;
break;
}
if (success)
{
// Finally, attempt to set the adjusted cursor position back into the console.
success = _pConApi->SetConsoleCursorPosition(cursor);
}
}
}
}
}
return success;
}
// Routine Description:
// - CUU - Handles cursor upward movement by given distance.
// CUU and CUD are handled seperately from other CUP sequences, because they are
@ -225,12 +94,7 @@ bool AdaptDispatch::_CursorMovement(const CursorDirection dir, const size_t dist
// - True if handled successfully. False otherwise.
bool AdaptDispatch::CursorUp(const size_t distance)
{
SHORT sDelta = 0;
if (SUCCEEDED(SizeTToShort(distance, &sDelta)))
{
return _pConApi->MoveCursorVertically(-sDelta);
}
return false;
return _CursorMovePosition(Offset::Backward(distance), Offset::Unchanged(), true);
}
// Routine Description:
@ -246,12 +110,7 @@ bool AdaptDispatch::CursorUp(const size_t distance)
// - True if handled successfully. False otherwise.
bool AdaptDispatch::CursorDown(const size_t distance)
{
SHORT sDelta = 0;
if (SUCCEEDED(SizeTToShort(distance, &sDelta)))
{
return _pConApi->MoveCursorVertically(sDelta);
}
return false;
return _CursorMovePosition(Offset::Forward(distance), Offset::Unchanged(), true);
}
// Routine Description:
@ -262,7 +121,7 @@ bool AdaptDispatch::CursorDown(const size_t distance)
// - True if handled successfully. False otherwise.
bool AdaptDispatch::CursorForward(const size_t distance)
{
return _CursorMovement(CursorDirection::Right, distance);
return _CursorMovePosition(Offset::Unchanged(), Offset::Forward(distance), true);
}
// Routine Description:
@ -273,7 +132,7 @@ bool AdaptDispatch::CursorForward(const size_t distance)
// - True if handled successfully. False otherwise.
bool AdaptDispatch::CursorBackward(const size_t distance)
{
return _CursorMovement(CursorDirection::Left, distance);
return _CursorMovePosition(Offset::Unchanged(), Offset::Backward(distance), true);
}
// Routine Description:
@ -285,7 +144,7 @@ bool AdaptDispatch::CursorBackward(const size_t distance)
// - True if handled successfully. False otherwise.
bool AdaptDispatch::CursorNextLine(const size_t distance)
{
return _CursorMovement(CursorDirection::NextLine, distance);
return _CursorMovePosition(Offset::Forward(distance), Offset::Absolute(1), true);
}
// Routine Description:
@ -297,18 +156,18 @@ bool AdaptDispatch::CursorNextLine(const size_t distance)
// - True if handled successfully. False otherwise.
bool AdaptDispatch::CursorPrevLine(const size_t distance)
{
return _CursorMovement(CursorDirection::PrevLine, distance);
return _CursorMovePosition(Offset::Backward(distance), Offset::Absolute(1), true);
}
// Routine Description:
// - Generalizes cursor movement to a specific coordinate position
// - If a parameter is left blank, we will maintain the existing position in that dimension.
// - Generalizes cursor movement to a specific position, which can be absolute or relative.
// Arguments:
// - row - Optional row to move to
// - column - Optional column to move to
// - rowOffset - The row to move to
// - colOffset - The column to move to
// - clampInMargins - Should the position be clamped within the scrolling margins
// Return Value:
// - True if handled successfully. False otherwise.
bool AdaptDispatch::_CursorMovePosition(const std::optional<size_t> row, const std::optional<size_t> column) const
bool AdaptDispatch::_CursorMovePosition(const Offset rowOffset, const Offset colOffset, const bool clampInMargins) const
{
bool success = true;
@ -321,81 +180,67 @@ bool AdaptDispatch::_CursorMovePosition(const std::optional<size_t> row, const s
if (success)
{
// handle optional parameters. If not specified, keep same cursor position from what we just loaded.
size_t rowActual = 0;
size_t columnActual = 0;
// Calculate the viewport boundaries as inclusive values.
// srWindow is exclusive so we need to subtract 1 from the bottom.
const int viewportTop = csbiex.srWindow.Top;
const int viewportBottom = csbiex.srWindow.Bottom - 1;
if (row)
{
if (row.value() != 0)
{
rowActual = row.value() - 1; // In VT, the origin is 1,1. For our array, it's 0,0. So subtract 1.
// Calculate the absolute margins of the scrolling area.
const int topMargin = viewportTop + _scrollMargins.Top;
const int bottomMargin = viewportTop + _scrollMargins.Bottom;
const bool marginsSet = topMargin < bottomMargin;
// If the origin mode is relative, and the scrolling region is set (the bottom is non-zero),
// line numbers start at the top margin of the scrolling region, and cannot move below the bottom.
if (_isOriginModeRelative && _scrollMargins.Bottom != 0)
{
rowActual += _scrollMargins.Top;
rowActual = std::min<decltype(rowActual)>(rowActual, _scrollMargins.Bottom);
}
}
else
{
success = false; // The parser should never return 0 (0 maps to 1), so this is a failure condition.
}
}
else
// For relative movement, the given offsets will be relative to
// the current cursor position.
int row = csbiex.dwCursorPosition.Y;
int col = csbiex.dwCursorPosition.X;
// But if the row is absolute, it will be relative to the top of the
// viewport, or the top margin, depending on the origin mode.
if (rowOffset.IsAbsolute)
{
// remember, in VT speak, this is relative to the viewport. not absolute.
SHORT diff = 0;
success = SUCCEEDED(ShortSub(csbiex.dwCursorPosition.Y, csbiex.srWindow.Top, &diff)) && SUCCEEDED(ShortToSizeT(diff, &rowActual));
row = _isOriginModeRelative ? topMargin : viewportTop;
}
if (success)
// And if the column is absolute, it'll be relative to column 0.
// Horizontal positions are not affected by the viewport.
if (colOffset.IsAbsolute)
{
if (column)
col = 0;
}
// Adjust the base position by the given offsets and clamp the results.
// The row is constrained within the viewport's vertical boundaries,
// while the column is constrained by the buffer width.
row = std::clamp(row + rowOffset.Value, viewportTop, viewportBottom);
col = std::clamp(col + colOffset.Value, 0, csbiex.dwSize.X - 1);
// If the operation needs to be clamped inside the margins, or the origin
// mode is relative (which always requires margin clamping), then the row
// may need to be adjusted further.
if (marginsSet && (clampInMargins || _isOriginModeRelative))
{
// See microsoft/terminal#2929 - If the cursor is _below_ the top
// margin, it should stay below the top margin. If it's _above_ the
// bottom, it should stay above the bottom. Cursor movements that stay
// outside the margins shouldn't necessarily be affected. For example,
// moving up while below the bottom margin shouldn't just jump straight
// to the bottom margin. See
// ScreenBufferTests::CursorUpDownOutsideMargins for a test of that
// behavior.
if (csbiex.dwCursorPosition.Y >= topMargin)
{
if (column.value() != 0)
{
columnActual = column.value() - 1; // In VT, the origin is 1,1. For our array, it's 0,0. So subtract 1.
}
else
{
success = false; // The parser should never return 0 (0 maps to 1), so this is a failure condition.
}
row = std::max(row, topMargin);
}
else
if (csbiex.dwCursorPosition.Y <= bottomMargin)
{
// remember, in VT speak, this is relative to the viewport. not absolute.
SHORT diff = 0;
success = SUCCEEDED(ShortSub(csbiex.dwCursorPosition.X, csbiex.srWindow.Left, &diff)) && SUCCEEDED(ShortToSizeT(diff, &columnActual));
row = std::min(row, bottomMargin);
}
}
if (success)
{
COORD cursor = csbiex.dwCursorPosition;
// Safely convert the size_t positions we were given into shorts (which is the size the console deals with)
success = SUCCEEDED(SizeTToShort(rowActual, &cursor.Y)) && SUCCEEDED(SizeTToShort(columnActual, &cursor.X));
if (success)
{
// Set the line and column values as offsets from the viewport edge. Use safe math to prevent overflow.
success = SUCCEEDED(ShortAdd(cursor.Y, csbiex.srWindow.Top, &cursor.Y)) &&
SUCCEEDED(ShortAdd(cursor.X, csbiex.srWindow.Left, &cursor.X));
if (success)
{
// Apply boundary tests to ensure the cursor isn't outside the viewport rectangle.
cursor.Y = std::clamp(cursor.Y, csbiex.srWindow.Top, gsl::narrow<SHORT>(csbiex.srWindow.Bottom - 1));
cursor.X = std::clamp(cursor.X, csbiex.srWindow.Left, gsl::narrow<SHORT>(csbiex.srWindow.Right - 1));
// Finally, attempt to set the adjusted cursor position back into the console.
success = _pConApi->SetConsoleCursorPosition(cursor);
}
}
}
// Finally, attempt to set the adjusted cursor position back into the console.
const COORD newPos = { gsl::narrow_cast<SHORT>(col), gsl::narrow_cast<SHORT>(row) };
success = _pConApi->SetConsoleCursorPosition(newPos);
}
return success;
@ -409,7 +254,7 @@ bool AdaptDispatch::_CursorMovePosition(const std::optional<size_t> row, const s
// - True if handled successfully. False otherwise.
bool AdaptDispatch::CursorHorizontalPositionAbsolute(const size_t column)
{
return _CursorMovePosition(std::nullopt, column);
return _CursorMovePosition(Offset::Unchanged(), Offset::Absolute(column), false);
}
// Routine Description:
@ -420,7 +265,7 @@ bool AdaptDispatch::CursorHorizontalPositionAbsolute(const size_t column)
// - True if handled successfully. False otherwise.
bool AdaptDispatch::VerticalLinePositionAbsolute(const size_t line)
{
return _CursorMovePosition(line, std::nullopt);
return _CursorMovePosition(Offset::Absolute(line), Offset::Unchanged(), false);
}
// Routine Description:
@ -432,7 +277,7 @@ bool AdaptDispatch::VerticalLinePositionAbsolute(const size_t line)
// - True if handled successfully. False otherwise.
bool AdaptDispatch::CursorPosition(const size_t line, const size_t column)
{
return _CursorMovePosition(line, column);
return _CursorMovePosition(Offset::Absolute(line), Offset::Absolute(column), false);
}
// Routine Description:
@ -458,15 +303,14 @@ bool AdaptDispatch::CursorSaveState()
if (success)
{
// The cursor is given to us by the API as relative to the whole buffer.
// But in VT speak, the cursor should be relative to the current viewport. Adjust.
COORD const coordCursor = csbiex.dwCursorPosition;
SMALL_RECT const srViewport = csbiex.srWindow;
// But in VT speak, the cursor row should be relative to the current viewport top.
COORD coordCursor = csbiex.dwCursorPosition;
coordCursor.Y -= csbiex.srWindow.Top;
// VT is also 1 based, not 0 based, so correct by 1.
auto& savedCursorState = _savedCursorState.at(_usingAltBuffer);
savedCursorState.Column = coordCursor.X - srViewport.Left + 1;
savedCursorState.Row = coordCursor.Y - srViewport.Top + 1;
savedCursorState.Column = coordCursor.X + 1;
savedCursorState.Row = coordCursor.Y + 1;
savedCursorState.IsOriginModeRelative = _isOriginModeRelative;
savedCursorState.Attributes = attributes;
savedCursorState.TermOutput = _termOutput;
@ -488,7 +332,7 @@ bool AdaptDispatch::CursorRestoreState()
auto& savedCursorState = _savedCursorState.at(_usingAltBuffer);
auto row = savedCursorState.Row;
auto col = savedCursorState.Column;
const auto col = savedCursorState.Column;
// If the origin mode is relative, and the scrolling region is set (the bottom is non-zero),
// we need to make sure the restored position is clamped within the margins.
@ -500,7 +344,7 @@ bool AdaptDispatch::CursorRestoreState()
// The saved coordinates are always absolute, so we need reset the origin mode temporarily.
_isOriginModeRelative = false;
bool success = _CursorMovePosition(row, col);
bool success = CursorPosition(row, col);
// Once the cursor position is restored, we can then restore the actual origin mode.
_isOriginModeRelative = savedCursorState.IsOriginModeRelative;
@ -851,8 +695,7 @@ bool AdaptDispatch::_CursorPositionReport() const
// First pull the cursor position relative to the entire buffer out of the console.
COORD coordCursorPos = csbiex.dwCursorPosition;
// Now adjust it for its position in respect to the current viewport.
coordCursorPos.X -= csbiex.srWindow.Left;
// Now adjust it for its position in respect to the current viewport top.
coordCursorPos.Y -= csbiex.srWindow.Top;
// NOTE: 1,1 is the top-left corner of the viewport in VT-speak, so add 1.

View file

@ -101,15 +101,6 @@ namespace Microsoft::Console::VirtualTerminal
const std::basic_string_view<size_t> parameters) override; // DTTERM_WindowManipulation
private:
enum class CursorDirection
{
Up,
Down,
Left,
Right,
NextLine,
PrevLine
};
enum class ScrollDirection
{
Up,
@ -123,9 +114,18 @@ namespace Microsoft::Console::VirtualTerminal
TextAttribute Attributes = {};
TerminalOutput TermOutput = {};
};
struct Offset
{
int Value;
bool IsAbsolute;
// VT origin is at 1,1 so we need to subtract 1 from absolute positions.
static constexpr Offset Absolute(const size_t value) { return { gsl::narrow_cast<int>(value) - 1, true }; };
static constexpr Offset Forward(const size_t value) { return { gsl::narrow_cast<int>(value), false }; };
static constexpr Offset Backward(const size_t value) { return { -gsl::narrow_cast<int>(value), false }; };
static constexpr Offset Unchanged() { return Forward(0); };
};
bool _CursorMovement(const CursorDirection dir, const size_t distance) const;
bool _CursorMovePosition(const std::optional<size_t> row, const std::optional<size_t> column) const;
bool _CursorMovePosition(const Offset rowOffset, const Offset colOffset, const bool clampInMargins) const;
bool _EraseSingleLineHelper(const CONSOLE_SCREEN_BUFFER_INFOEX& csbiex,
const DispatchTypes::EraseType eraseType,
const size_t lineId) const;

View file

@ -93,8 +93,6 @@ namespace Microsoft::Console::VirtualTerminal
virtual bool PrivateSuppressResizeRepaint() = 0;
virtual bool IsConsolePty(bool& isPty) const = 0;
virtual bool MoveCursorVertically(const ptrdiff_t lines) = 0;
virtual bool DeleteLines(const size_t count) = 0;
virtual bool InsertLines(const size_t count) = 0;

View file

@ -394,19 +394,6 @@ public:
return TRUE;
}
bool MoveCursorVertically(const ptrdiff_t lines) override
{
Log::Comment(L"MoveCursorVertically MOCK called...");
short l;
VERIFY_SUCCEEDED(PtrdiffTToShort(lines, &l));
if (_moveCursorVerticallyResult)
{
VERIFY_ARE_EQUAL(_expectedLines, l);
_cursorPos = { _cursorPos.X, _cursorPos.Y + l };
}
return !!_moveCursorVerticallyResult;
}
bool SetConsoleTitleW(const std::wstring_view title)
{
Log::Comment(L"SetConsoleTitleW MOCK called...");
@ -763,8 +750,6 @@ public:
// Attribute default is gray on black.
_attribute = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED;
_expectedAttribute = _attribute;
_expectedLines = 0;
}
void PrepCursor(CursorX xact, CursorY yact)
@ -774,16 +759,16 @@ public:
switch (xact)
{
case CursorX::LEFT:
Log::Comment(L"Cursor set to left edge of viewport.");
_cursorPos.X = _viewport.Left;
Log::Comment(L"Cursor set to left edge of buffer.");
_cursorPos.X = 0;
break;
case CursorX::RIGHT:
Log::Comment(L"Cursor set to right edge of viewport.");
_cursorPos.X = _viewport.Right - 1;
Log::Comment(L"Cursor set to right edge of buffer.");
_cursorPos.X = _bufferSize.X - 1;
break;
case CursorX::XCENTER:
Log::Comment(L"Cursor set to centered X of viewport.");
_cursorPos.X = _viewport.Left + ((_viewport.Right - _viewport.Left) / 2);
Log::Comment(L"Cursor set to centered X of buffer.");
_cursorPos.X = _bufferSize.X / 2;
break;
}
@ -880,7 +865,6 @@ public:
bool _expectedMeta = false;
unsigned int _expectedOutputCP = 0;
bool _isPty = false;
short _expectedLines = 0;
bool _privateBoldTextResult = false;
bool _expectedIsBold = false;
bool _isBold = false;
@ -942,7 +926,6 @@ public:
COLORREF _expectedCursorColor = 0;
bool _getConsoleOutputCPResult = false;
bool _isConsolePtyResult = false;
bool _moveCursorVerticallyResult = false;
bool _privateSetDefaultAttributesResult = false;
bool _moveToBottomResult = false;
@ -1062,24 +1045,6 @@ public:
Log::Comment(L"Test 1: Cursor doesn't move when placed in corner of viewport.");
_testGetSet->PrepData(direction);
switch (direction)
{
case CursorDirection::UP:
Log::Comment(L"Testing up direction.");
_testGetSet->_expectedLines = -1;
_testGetSet->_moveCursorVerticallyResult = true;
break;
case CursorDirection::DOWN:
Log::Comment(L"Testing down direction.");
_testGetSet->_expectedLines = 1;
_testGetSet->_moveCursorVerticallyResult = true;
break;
default:
_testGetSet->_expectedLines = 0;
_testGetSet->_moveCursorVerticallyResult = false;
break;
}
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(1));
Log::Comment(L"Test 1b: Cursor moves to left of line with next/prev line command when cursor can't move higher/lower.");
@ -1100,7 +1065,7 @@ public:
if (fDoTest1b)
{
_testGetSet->_expectedCursorPos.X = _testGetSet->_viewport.Left;
_testGetSet->_expectedCursorPos.X = 0;
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(1));
}
else
@ -1116,13 +1081,9 @@ public:
{
case CursorDirection::UP:
_testGetSet->_expectedCursorPos.Y--;
_testGetSet->_expectedLines = -1;
_testGetSet->_moveCursorVerticallyResult = true;
break;
case CursorDirection::DOWN:
_testGetSet->_expectedCursorPos.Y++;
_testGetSet->_expectedLines = 1;
_testGetSet->_moveCursorVerticallyResult = true;
break;
case CursorDirection::RIGHT:
_testGetSet->_expectedCursorPos.X++;
@ -1132,11 +1093,11 @@ public:
break;
case CursorDirection::NEXTLINE:
_testGetSet->_expectedCursorPos.Y++;
_testGetSet->_expectedCursorPos.X = _testGetSet->_viewport.Left;
_testGetSet->_expectedCursorPos.X = 0;
break;
case CursorDirection::PREVLINE:
_testGetSet->_expectedCursorPos.Y--;
_testGetSet->_expectedCursorPos.X = _testGetSet->_viewport.Left;
_testGetSet->_expectedCursorPos.X = 0;
break;
}
@ -1152,26 +1113,22 @@ public:
{
case CursorDirection::UP:
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Top;
_testGetSet->_expectedLines = -100;
_testGetSet->_moveCursorVerticallyResult = true;
break;
case CursorDirection::DOWN:
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Bottom - 1;
_testGetSet->_expectedLines = 100;
_testGetSet->_moveCursorVerticallyResult = true;
break;
case CursorDirection::RIGHT:
_testGetSet->_expectedCursorPos.X = _testGetSet->_viewport.Right - 1;
_testGetSet->_expectedCursorPos.X = _testGetSet->_bufferSize.X - 1;
break;
case CursorDirection::LEFT:
_testGetSet->_expectedCursorPos.X = _testGetSet->_viewport.Left;
_testGetSet->_expectedCursorPos.X = 0;
break;
case CursorDirection::NEXTLINE:
_testGetSet->_expectedCursorPos.X = _testGetSet->_viewport.Left;
_testGetSet->_expectedCursorPos.X = 0;
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Bottom - 1;
break;
case CursorDirection::PREVLINE:
_testGetSet->_expectedCursorPos.X = _testGetSet->_viewport.Left;
_testGetSet->_expectedCursorPos.X = 0;
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Top;
break;
}
@ -1179,64 +1136,19 @@ public:
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(100));
// error cases
// give too large an up distance, cursor move should fail, cursor should stay the same.
Log::Comment(L"Test 4: When given invalid (massive) move distance that doesn't fit in a short, call fails and cursor doesn't move.");
_testGetSet->PrepData(CursorX::XCENTER, CursorY::YCENTER);
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(UINT_MAX));
VERIFY_ARE_EQUAL(_testGetSet->_expectedCursorPos, _testGetSet->_cursorPos);
// cause short underflow. cursor move should fail. cursor should stay the same.
Log::Comment(L"Test 5: When an over/underflow occurs in cursor math, call fails and cursor doesn't move.");
_testGetSet->PrepData(direction);
switch (direction)
{
case CursorDirection::UP:
case CursorDirection::PREVLINE:
_testGetSet->_cursorPos.Y = -10;
break;
case CursorDirection::DOWN:
case CursorDirection::NEXTLINE:
_testGetSet->_cursorPos.Y = 10;
break;
case CursorDirection::RIGHT:
_testGetSet->_cursorPos.X = 10;
break;
case CursorDirection::LEFT:
_testGetSet->_cursorPos.X = -10;
break;
}
_testGetSet->_expectedCursorPos = _testGetSet->_cursorPos;
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(SHRT_MAX + 1));
VERIFY_ARE_EQUAL(_testGetSet->_expectedCursorPos, _testGetSet->_cursorPos);
// SetConsoleCursorPosition throws failure. Parameters are otherwise normal.
Log::Comment(L"Test 6: When SetConsoleCursorPosition throws a failure, call fails and cursor doesn't move.");
Log::Comment(L"Test 4: When SetConsoleCursorPosition throws a failure, call fails and cursor doesn't move.");
_testGetSet->PrepData(direction);
_testGetSet->_setConsoleCursorPositionResult = FALSE;
_testGetSet->_moveCursorVerticallyResult = false;
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(0));
VERIFY_ARE_EQUAL(_testGetSet->_expectedCursorPos, _testGetSet->_cursorPos);
// GetConsoleScreenBufferInfo throws failure. Parameters are otherwise normal.
Log::Comment(L"Test 7: When GetConsoleScreenBufferInfo throws a failure, call fails and cursor doesn't move.");
Log::Comment(L"Test 5: When GetConsoleScreenBufferInfo throws a failure, call fails and cursor doesn't move.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
_testGetSet->_getConsoleScreenBufferInfoExResult = FALSE;
_testGetSet->_moveCursorVerticallyResult = true;
Log::Comment(NoThrowString().Format(
L"Cursor Up and Down don't need GetConsoleScreenBufferInfoEx, so they will succeed"));
if (direction == CursorDirection::UP || direction == CursorDirection::DOWN)
{
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(0));
}
else
{
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(0));
}
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(0));
VERIFY_ARE_EQUAL(_testGetSet->_expectedCursorPos, _testGetSet->_cursorPos);
}
@ -1250,7 +1162,8 @@ public:
short sCol = (_testGetSet->_viewport.Right - _testGetSet->_viewport.Left) / 2;
short sRow = (_testGetSet->_viewport.Bottom - _testGetSet->_viewport.Top) / 2;
_testGetSet->_expectedCursorPos.X = _testGetSet->_viewport.Left + (sCol - 1);
// The X coordinate is unaffected by the viewport.
_testGetSet->_expectedCursorPos.X = sCol - 1;
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Top + (sRow - 1);
VERIFY_IS_TRUE(_pDispatch.get()->CursorPosition(sRow, sCol));
@ -1258,7 +1171,8 @@ public:
Log::Comment(L"Test 2: Move to 0, 0 (which is 1,1 in VT speak)");
_testGetSet->PrepData(CursorX::RIGHT, CursorY::BOTTOM);
_testGetSet->_expectedCursorPos.X = _testGetSet->_viewport.Left;
// The X coordinate is unaffected by the viewport.
_testGetSet->_expectedCursorPos.X = 0;
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Top;
VERIFY_IS_TRUE(_pDispatch.get()->CursorPosition(1, 1));
@ -1266,45 +1180,27 @@ public:
Log::Comment(L"Test 3: Move beyond rectangle (down/right too far). Should be bounded back in.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
sCol = (_testGetSet->_viewport.Right - _testGetSet->_viewport.Left) * 2;
sCol = (_testGetSet->_bufferSize.X) * 2;
sRow = (_testGetSet->_viewport.Bottom - _testGetSet->_viewport.Top) * 2;
_testGetSet->_expectedCursorPos.X = _testGetSet->_viewport.Right - 1;
_testGetSet->_expectedCursorPos.X = _testGetSet->_bufferSize.X - 1;
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Bottom - 1;
VERIFY_IS_TRUE(_pDispatch.get()->CursorPosition(sRow, sCol));
Log::Comment(L"Test 4: Values too large for short. Cursor shouldn't move. Return false.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
VERIFY_IS_FALSE(_pDispatch.get()->CursorPosition(UINT_MAX, UINT_MAX));
Log::Comment(L"Test 5: Overflow during addition. Cursor shouldn't move. Return false.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
_testGetSet->_viewport.Left = SHRT_MAX;
_testGetSet->_viewport.Top = SHRT_MAX;
VERIFY_IS_FALSE(_pDispatch.get()->CursorPosition(5, 5));
Log::Comment(L"Test 6: GetConsoleInfo API returns false. No move, return false.");
Log::Comment(L"Test 4: GetConsoleInfo API returns false. No move, return false.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
_testGetSet->_getConsoleScreenBufferInfoExResult = FALSE;
VERIFY_IS_FALSE(_pDispatch.get()->CursorPosition(1, 1));
Log::Comment(L"Test 7: SetCursor API returns false. No move, return false.");
Log::Comment(L"Test 5: SetCursor API returns false. No move, return false.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
_testGetSet->_setConsoleCursorPositionResult = FALSE;
VERIFY_IS_FALSE(_pDispatch.get()->CursorPosition(1, 1));
Log::Comment(L"Test 8: Move to 0,0. Cursor shouldn't move. Return false. 1,1 is the top left corner in VT100 speak. 0,0 isn't a position. The parser will give 1 for a 0 input.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
VERIFY_IS_FALSE(_pDispatch.get()->CursorPosition(0, 0));
}
TEST_METHOD(CursorSingleDimensionMoveTest)
@ -1318,8 +1214,8 @@ public:
//// Used to switch between the various function options.
typedef bool (AdaptDispatch::*CursorMoveFunc)(size_t);
CursorMoveFunc moveFunc = nullptr;
SHORT* psViewportEnd = nullptr;
SHORT* psViewportStart = nullptr;
SHORT sRangeEnd = 0;
SHORT sRangeStart = 0;
SHORT* psCursorExpected = nullptr;
// Modify variables based on directionality of this test
@ -1327,26 +1223,27 @@ public:
size_t dir;
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiDirection", dir));
direction = (AbsolutePosition)dir;
_testGetSet->PrepData();
switch (direction)
{
case AbsolutePosition::CursorHorizontal:
Log::Comment(L"Testing cursor horizontal movement.");
psViewportEnd = &_testGetSet->_viewport.Right;
psViewportStart = &_testGetSet->_viewport.Left;
sRangeEnd = _testGetSet->_bufferSize.X;
sRangeStart = 0;
psCursorExpected = &_testGetSet->_expectedCursorPos.X;
moveFunc = &AdaptDispatch::CursorHorizontalPositionAbsolute;
break;
case AbsolutePosition::VerticalLine:
Log::Comment(L"Testing vertical line movement.");
psViewportEnd = &_testGetSet->_viewport.Bottom;
psViewportStart = &_testGetSet->_viewport.Top;
sRangeEnd = _testGetSet->_viewport.Bottom;
sRangeStart = _testGetSet->_viewport.Top;
psCursorExpected = &_testGetSet->_expectedCursorPos.Y;
moveFunc = &AdaptDispatch::VerticalLinePositionAbsolute;
break;
}
if (moveFunc == nullptr || psViewportEnd == nullptr || psViewportStart == nullptr || psCursorExpected == nullptr)
if (moveFunc == nullptr || psCursorExpected == nullptr)
{
VERIFY_FAIL();
return;
@ -1355,16 +1252,16 @@ public:
Log::Comment(L"Test 1: Place cursor within the viewport. Start from top left, move to middle.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
short sVal = (*psViewportEnd - *psViewportStart) / 2;
short sVal = (sRangeEnd - sRangeStart) / 2;
*psCursorExpected = *psViewportStart + (sVal - 1);
*psCursorExpected = sRangeStart + (sVal - 1);
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(sVal));
Log::Comment(L"Test 2: Move to 0 (which is 1 in VT speak)");
_testGetSet->PrepData(CursorX::RIGHT, CursorY::BOTTOM);
*psCursorExpected = *psViewportStart;
*psCursorExpected = sRangeStart;
sVal = 1;
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(sVal));
@ -1372,29 +1269,13 @@ public:
Log::Comment(L"Test 3: Move beyond rectangle (down/right too far). Should be bounded back in.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
sVal = (*psViewportEnd - *psViewportStart) * 2;
sVal = (sRangeEnd - sRangeStart) * 2;
*psCursorExpected = *psViewportEnd - 1;
*psCursorExpected = sRangeEnd - 1;
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(sVal));
Log::Comment(L"Test 4: Values too large for short. Cursor shouldn't move. Return false.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
sVal = SHORT_MAX;
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(sVal));
Log::Comment(L"Test 5: Overflow during addition. Cursor shouldn't move. Return false.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
_testGetSet->_viewport.Left = SHRT_MAX;
sVal = 5;
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(sVal));
Log::Comment(L"Test 6: GetConsoleInfo API returns false. No move, return false.");
Log::Comment(L"Test 4: GetConsoleInfo API returns false. No move, return false.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
_testGetSet->_getConsoleScreenBufferInfoExResult = FALSE;
@ -1403,7 +1284,7 @@ public:
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(sVal));
Log::Comment(L"Test 7: SetCursor API returns false. No move, return false.");
Log::Comment(L"Test 5: SetCursor API returns false. No move, return false.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
_testGetSet->_setConsoleCursorPositionResult = FALSE;
@ -1411,13 +1292,6 @@ public:
sVal = 1;
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(sVal));
Log::Comment(L"Test 8: Move to 0. Cursor shouldn't move. Return false. 1 is the left edge in VT100 speak. 0 isn't a position. The parser will give 1 for a 0 input.");
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
sVal = 0;
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(sVal));
}
TEST_METHOD(CursorSaveRestoreTest)
@ -1942,8 +1816,7 @@ public:
// start with the cursor position in the buffer.
COORD coordCursorExpected = _testGetSet->_cursorPos;
// to get to VT, we have to adjust it to its position relative to the viewport.
coordCursorExpected.X -= _testGetSet->_viewport.Left;
// to get to VT, we have to adjust it to its position relative to the viewport top.
coordCursorExpected.Y -= _testGetSet->_viewport.Top;
// Then note that VT is 1,1 based for the top left, so add 1. (The rest of the console uses 0,0 for array index bases.)
@ -2041,6 +1914,7 @@ public:
Log::Comment(L"Starting test...");
SMALL_RECT srTestMargins = { 0 };
_testGetSet->_bufferSize = { 100, 600 };
_testGetSet->_viewport.Right = 8;
_testGetSet->_viewport.Bottom = 8;
_testGetSet->_getConsoleScreenBufferInfoExResult = TRUE;