Compare commits
21 commits
main
...
dev/migrie
Author | SHA1 | Date | |
---|---|---|---|
c091471fa4 | |||
3c4d1577fe | |||
48de202e83 | |||
13c6171224 | |||
02445fd2da | |||
252f466c2d | |||
f37ce0180b | |||
872f4c433c | |||
c6a7e4ee2c | |||
2e9ebcc3ae | |||
3398b19d25 | |||
d116c6bbf6 | |||
9ccc3bcca3 | |||
668bad39c9 | |||
17d25ee113 | |||
95122e325c | |||
ee08a5b866 | |||
c8c794f0d2 | |||
38ebbb6f11 | |||
399b002af5 | |||
4319589510 |
|
@ -1554,9 +1554,13 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi
|
|||
// Arguments:
|
||||
// - oldBuffer - the text buffer to copy the contents FROM
|
||||
// - newBuffer - the text buffer to copy the contents TO
|
||||
// - padTop - If true, and we're increasing the height of the buffer, we should
|
||||
// start by inserting new lines at the top of the buffer, so the contents stay
|
||||
// in the relatively same position. This is only ever set to `true` in the
|
||||
// console by conpty mode.
|
||||
// Return Value:
|
||||
// - S_OK if we successfully copied the contents to the new buffer, otherwise an appropriate HRESULT.
|
||||
HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer)
|
||||
HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer, const bool padTop)
|
||||
{
|
||||
Cursor& oldCursor = oldBuffer.GetCursor();
|
||||
Cursor& newCursor = newBuffer.GetCursor();
|
||||
|
@ -1576,6 +1580,17 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer)
|
|||
COORD cNewCursorPos = { 0 };
|
||||
bool fFoundCursorPos = false;
|
||||
|
||||
// If we increased the height of the buffer, pad the top of the new buffer
|
||||
// with newlines, so that the contents stay in the same relative location.
|
||||
if (padTop && oldBuffer.GetSize().Height() < newBuffer.GetSize().Height())
|
||||
{
|
||||
const auto diff = newBuffer.GetSize().Height() - oldBuffer.GetSize().Height();
|
||||
for (int i = 0; i < diff; i++)
|
||||
{
|
||||
newBuffer.NewlineCursor();
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
// Loop through all the rows of the old buffer and reprint them into the new buffer
|
||||
for (short iOldRow = 0; iOldRow < cOldRowsTotal; iOldRow++)
|
||||
|
|
|
@ -158,7 +158,7 @@ public:
|
|||
const std::wstring_view fontFaceName,
|
||||
const COLORREF backgroundColor);
|
||||
|
||||
static HRESULT Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer);
|
||||
static HRESULT Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer, const bool padTop);
|
||||
|
||||
private:
|
||||
std::deque<ROW> _storage;
|
||||
|
|
|
@ -177,22 +177,25 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
|
|||
}
|
||||
|
||||
const auto oldTop = _mutableViewport.Top();
|
||||
const auto oldBottom = _mutableViewport.BottomExclusive();
|
||||
|
||||
const short newBufferHeight = viewportSize.Y + _scrollbackLines;
|
||||
COORD bufferSize{ viewportSize.X, newBufferHeight };
|
||||
RETURN_IF_FAILED(_buffer->ResizeTraditional(bufferSize));
|
||||
|
||||
auto proposedTop = oldTop;
|
||||
const auto newView = Viewport::FromDimensions({ 0, proposedTop }, viewportSize);
|
||||
const auto proposedBottom = newView.BottomExclusive();
|
||||
// If the new bottom would be below the bottom of the buffer, then slide the
|
||||
// top up so that we'll still fit within the buffer.
|
||||
// Attempt to "stick" to the bottom of the current viewport. Lines will be
|
||||
// moved into scrollback.
|
||||
const auto newViewSize = Viewport::FromDimensions({ 0, 0 }, viewportSize);
|
||||
|
||||
auto proposedBottom = oldBottom;
|
||||
if (proposedBottom > bufferSize.Y)
|
||||
{
|
||||
proposedTop -= (proposedBottom - bufferSize.Y);
|
||||
proposedBottom = bufferSize.Y;
|
||||
}
|
||||
short proposedTop = proposedBottom - newViewSize.Height();
|
||||
|
||||
_mutableViewport = Viewport::FromDimensions({ 0, proposedTop }, viewportSize);
|
||||
|
||||
_scrollOffset = 0;
|
||||
_NotifyScrollEvent();
|
||||
|
||||
|
|
|
@ -30,7 +30,11 @@ namespace Microsoft::Terminal::Core
|
|||
|
||||
// fwdecl unittest classes
|
||||
#ifdef UNIT_TESTING
|
||||
class ConptyRoundtripTests;
|
||||
namespace TerminalCoreUnitTests
|
||||
{
|
||||
class TerminalBufferTests;
|
||||
class ConptyRoundtripTests;
|
||||
};
|
||||
#endif
|
||||
|
||||
class Microsoft::Terminal::Core::Terminal final :
|
||||
|
@ -252,6 +256,7 @@ private:
|
|||
#pragma endregion
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class ::ConptyRoundtripTests;
|
||||
friend class TerminalCoreUnitTests::TerminalBufferTests;
|
||||
friend class TerminalCoreUnitTests::ConptyRoundtripTests;
|
||||
#endif
|
||||
};
|
||||
|
|
|
@ -40,8 +40,17 @@ using namespace Microsoft::Console::Types;
|
|||
|
||||
using namespace Microsoft::Terminal::Core;
|
||||
|
||||
class ConptyRoundtripTests
|
||||
namespace TerminalCoreUnitTests
|
||||
{
|
||||
class TerminalBufferTests;
|
||||
};
|
||||
using namespace TerminalCoreUnitTests;
|
||||
|
||||
class TerminalCoreUnitTests::ConptyRoundtripTests final
|
||||
{
|
||||
static const SHORT TerminalViewWidth = 80;
|
||||
static const SHORT TerminalViewHeight = 32;
|
||||
|
||||
TEST_CLASS(ConptyRoundtripTests);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
|
@ -50,7 +59,7 @@ class ConptyRoundtripTests
|
|||
|
||||
m_state->InitEvents();
|
||||
m_state->PrepareGlobalFont();
|
||||
m_state->PrepareGlobalScreenBuffer();
|
||||
m_state->PrepareGlobalScreenBuffer(TerminalViewWidth, TerminalViewHeight, TerminalViewWidth, TerminalViewHeight);
|
||||
m_state->PrepareGlobalInputBuffer();
|
||||
|
||||
return true;
|
||||
|
@ -71,18 +80,19 @@ class ConptyRoundtripTests
|
|||
{
|
||||
// STEP 1: Set up the Terminal
|
||||
term = std::make_unique<Terminal>();
|
||||
term->Create({ CommonState::s_csBufferWidth, CommonState::s_csBufferHeight }, 0, emptyRT);
|
||||
term->Create({ TerminalViewWidth, TerminalViewHeight }, 100, emptyRT);
|
||||
|
||||
// STEP 2: Set up the Conpty
|
||||
|
||||
// Set up some sane defaults
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& gci = g.getConsoleInformation();
|
||||
|
||||
gci.SetDefaultForegroundColor(INVALID_COLOR);
|
||||
gci.SetDefaultBackgroundColor(INVALID_COLOR);
|
||||
gci.SetFillAttribute(0x07); // DARK_WHITE on DARK_BLACK
|
||||
|
||||
m_state->PrepareNewTextBufferInfo(true);
|
||||
m_state->PrepareNewTextBufferInfo(true, TerminalViewWidth, TerminalViewHeight);
|
||||
auto& currentBuffer = gci.GetActiveOutputBuffer();
|
||||
// Make sure a test hasn't left us in the alt buffer on accident
|
||||
VERIFY_IS_FALSE(currentBuffer._IsAltBuffer());
|
||||
|
@ -106,6 +116,13 @@ class ConptyRoundtripTests
|
|||
g.pRender->AddRenderEngine(_pVtRenderEngine.get());
|
||||
gci.GetActiveOutputBuffer().SetTerminalConnection(_pVtRenderEngine.get());
|
||||
|
||||
_pConApi = std::make_unique<ConhostInternalGetSet>(gci);
|
||||
|
||||
// Manually set the console into conpty mode. We're not actually going
|
||||
// to set up the pipes for conpty, but we want the console to behave
|
||||
// like it would in conpty mode.
|
||||
g.EnableConptyModeForTests();
|
||||
|
||||
expectedOutput.clear();
|
||||
|
||||
return true;
|
||||
|
@ -129,13 +146,18 @@ class ConptyRoundtripTests
|
|||
TEST_METHOD(SimpleWriteOutputTest);
|
||||
TEST_METHOD(WriteTwoLinesUsesNewline);
|
||||
TEST_METHOD(WriteAFewSimpleLines);
|
||||
TEST_METHOD(TestResizeHeight);
|
||||
|
||||
private:
|
||||
bool _writeCallback(const char* const pch, size_t const cch);
|
||||
void _flushFirstFrame();
|
||||
void _resizeConpty(const unsigned short sx, const unsigned short sy);
|
||||
std::deque<std::string> expectedOutput;
|
||||
std::unique_ptr<Microsoft::Console::Render::VtEngine> _pVtRenderEngine;
|
||||
std::unique_ptr<CommonState> m_state;
|
||||
std::unique_ptr<Microsoft::Console::VirtualTerminal::ConGetSet> _pConApi;
|
||||
|
||||
bool _checkConptyOutput{ true };
|
||||
|
||||
DummyRenderTarget emptyRT;
|
||||
std::unique_ptr<Terminal> term;
|
||||
|
@ -144,18 +166,27 @@ private:
|
|||
bool ConptyRoundtripTests::_writeCallback(const char* const pch, size_t const cch)
|
||||
{
|
||||
std::string actualString = std::string(pch, cch);
|
||||
VERIFY_IS_GREATER_THAN(expectedOutput.size(),
|
||||
static_cast<size_t>(0),
|
||||
NoThrowString().Format(L"writing=\"%hs\", expecting %u strings", actualString.c_str(), expectedOutput.size()));
|
||||
|
||||
std::string first = expectedOutput.front();
|
||||
expectedOutput.pop_front();
|
||||
if (_checkConptyOutput)
|
||||
{
|
||||
VERIFY_IS_GREATER_THAN(expectedOutput.size(),
|
||||
static_cast<size_t>(0),
|
||||
NoThrowString().Format(L"writing=\"%hs\", expecting %u strings", actualString.c_str(), expectedOutput.size()));
|
||||
|
||||
Log::Comment(NoThrowString().Format(L"Expected =\t\"%hs\"", first.c_str()));
|
||||
Log::Comment(NoThrowString().Format(L"Actual =\t\"%hs\"", actualString.c_str()));
|
||||
std::string first = expectedOutput.front();
|
||||
expectedOutput.pop_front();
|
||||
|
||||
VERIFY_ARE_EQUAL(first.length(), cch);
|
||||
VERIFY_ARE_EQUAL(first, actualString);
|
||||
Log::Comment(NoThrowString().Format(L"Expected =\t\"%hs\"", first.c_str()));
|
||||
Log::Comment(NoThrowString().Format(L"Actual =\t\"%hs\"", actualString.c_str()));
|
||||
|
||||
VERIFY_ARE_EQUAL(first.length(), cch);
|
||||
VERIFY_ARE_EQUAL(first, actualString);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Writing \"%hs\" to Terminal", actualString.c_str()));
|
||||
}
|
||||
|
||||
// Write the string back to our Terminal
|
||||
const auto converted = ConvertToW(CP_UTF8, actualString);
|
||||
|
@ -177,6 +208,19 @@ void ConptyRoundtripTests::_flushFirstFrame()
|
|||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::_resizeConpty(const unsigned short sx,
|
||||
const unsigned short sy)
|
||||
{
|
||||
// Largely taken from implementation in PtySignalInputThread::_InputThread
|
||||
if (DispatchCommon::s_ResizeWindow(*_pConApi, sx, sy))
|
||||
{
|
||||
// Instead of going through the VtIo to suppress the resize repaint,
|
||||
// just call the method directly on the renderer. This is implemented in
|
||||
// VtIo::SuppressResizeRepaint
|
||||
VERIFY_SUCCEEDED(_pVtRenderEngine->SuppressResizeRepaint());
|
||||
}
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Helper function to validate that a number of characters in a row are all
|
||||
// the same. Validates that the next end-start characters are all equal to the
|
||||
|
@ -364,3 +408,153 @@ void ConptyRoundtripTests::WriteAFewSimpleLines()
|
|||
|
||||
verifyData(termTb);
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::TestResizeHeight()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
||||
TEST_METHOD_PROPERTY(L"Data:dx", L"{-1, 0, 1}")
|
||||
TEST_METHOD_PROPERTY(L"Data:dy", L"{-10, -1, 0, 1, 10}")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
int dx, dy;
|
||||
VERIFY_SUCCEEDED(TestData::TryGetValue(L"dx", dx), L"change in width of buffer");
|
||||
VERIFY_SUCCEEDED(TestData::TryGetValue(L"dy", dy), L"change in height of buffer");
|
||||
|
||||
_checkConptyOutput = false;
|
||||
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
auto* hostTb = &si.GetTextBuffer();
|
||||
auto* termTb = term->_buffer.get();
|
||||
const auto initialHostView = si.GetViewport();
|
||||
const auto initialTermView = term->GetViewport();
|
||||
|
||||
VERIFY_ARE_EQUAL(0, initialHostView.Top());
|
||||
VERIFY_ARE_EQUAL(TerminalViewHeight, initialHostView.BottomExclusive());
|
||||
VERIFY_ARE_EQUAL(0, initialTermView.Top());
|
||||
VERIFY_ARE_EQUAL(TerminalViewHeight, initialTermView.BottomExclusive());
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Print 50 lines of output, which will scroll the viewport"));
|
||||
|
||||
for (auto i = 0; i < 50; i++)
|
||||
{
|
||||
auto wstr = std::wstring(1, static_cast<wchar_t>(L'0' + i));
|
||||
hostSm.ProcessString(wstr);
|
||||
hostSm.ProcessString(L"\r\n");
|
||||
}
|
||||
|
||||
// Conpty's doesn't have a scrollback, it's view's origin is always 0,0
|
||||
const auto secondHostView = si.GetViewport();
|
||||
VERIFY_ARE_EQUAL(0, secondHostView.Top());
|
||||
VERIFY_ARE_EQUAL(TerminalViewHeight, secondHostView.BottomExclusive());
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
const auto secondTermView = term->GetViewport();
|
||||
VERIFY_ARE_EQUAL(50 - initialTermView.Height() + 1, secondTermView.Top());
|
||||
VERIFY_ARE_EQUAL(50, secondTermView.BottomInclusive());
|
||||
|
||||
auto verifyTermData = [](TextBuffer& termTb) {
|
||||
for (short row = 0; row < 50; row++)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
auto iter = termTb.GetCellDataAt({ 0, row });
|
||||
auto expectedString = std::wstring(1, static_cast<wchar_t>(L'0' + row));
|
||||
|
||||
if (iter->Chars() != expectedString)
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(L"row [%d] was mismatched", row));
|
||||
}
|
||||
VERIFY_ARE_EQUAL(expectedString, (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L" ", (iter)->Chars());
|
||||
}
|
||||
};
|
||||
auto verifyHostData = [&si](TextBuffer& hostTb, const int resizeDy = 0) {
|
||||
const auto hostView = si.GetViewport();
|
||||
// The last row of the viewport should be empty
|
||||
// The second last row will have '0'+50
|
||||
// The third last row will have '0'+49
|
||||
// ...
|
||||
// The <height> last row will have '0'+(50-height+1)
|
||||
const auto firstChar = static_cast<wchar_t>(L'0' + (50 - hostView.Height() + 1));
|
||||
|
||||
// If we increased the height of the buffer, then we're going to insert
|
||||
// pad the top of the buffer with blank lines, to keep the real content
|
||||
// we had at the bottom of the buffer.
|
||||
// In that case, check for the first lines to be filled with spaces.
|
||||
const short firstRowWithChars = gsl::narrow_cast<short>(resizeDy > 0 ? resizeDy : 0);
|
||||
if (resizeDy > 0)
|
||||
{
|
||||
for (short row = 0; row < firstRowWithChars; row++)
|
||||
{
|
||||
auto iter = hostTb.GetCellDataAt({ 0, row });
|
||||
VERIFY_ARE_EQUAL(L" ", (iter)->Chars());
|
||||
}
|
||||
}
|
||||
|
||||
// Don't include the last row of the viewport in this check, since it'll be blank
|
||||
for (short row = firstRowWithChars; row < hostView.Height() - 1; row++)
|
||||
{
|
||||
auto iter = hostTb.GetCellDataAt({ 0, row });
|
||||
|
||||
auto expectedString = std::wstring(1, static_cast<wchar_t>(firstChar + row));
|
||||
|
||||
if (iter->Chars() != expectedString)
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(L"row [%d] was mismatched", row));
|
||||
}
|
||||
VERIFY_ARE_EQUAL(expectedString, (iter++)->Chars(), NoThrowString().Format(L"%s", expectedString.data()));
|
||||
VERIFY_ARE_EQUAL(L" ", (iter)->Chars());
|
||||
}
|
||||
|
||||
// Check the last row of the viewport here
|
||||
auto iter = hostTb.GetCellDataAt({ 0, hostView.Height() - 1 });
|
||||
VERIFY_ARE_EQUAL(L" ", (iter)->Chars());
|
||||
};
|
||||
verifyHostData(*hostTb);
|
||||
verifyTermData(*termTb);
|
||||
|
||||
const COORD newViewportSize{
|
||||
gsl::narrow_cast<short>(TerminalViewWidth + dx),
|
||||
gsl::narrow_cast<short>(TerminalViewHeight + dy)
|
||||
};
|
||||
|
||||
auto resizeResult = term->UserResize(newViewportSize);
|
||||
VERIFY_SUCCEEDED(resizeResult);
|
||||
// DebugBreak();
|
||||
_resizeConpty(newViewportSize.X, newViewportSize.Y);
|
||||
// After we resize, make sure to get the new textBuffers
|
||||
hostTb = &si.GetTextBuffer();
|
||||
termTb = term->_buffer.get();
|
||||
|
||||
// Conpty's doesn't have a scrollback, it's view's origin is always 0,0
|
||||
const auto thirdHostView = si.GetViewport();
|
||||
VERIFY_ARE_EQUAL(0, thirdHostView.Top());
|
||||
VERIFY_ARE_EQUAL(newViewportSize.Y, thirdHostView.BottomExclusive());
|
||||
|
||||
// The Terminal should be stuck on the bottom of the viewport
|
||||
const auto thirdTermView = term->GetViewport();
|
||||
|
||||
VERIFY_ARE_EQUAL(50 - thirdTermView.Height() + 1, thirdTermView.Top());
|
||||
VERIFY_ARE_EQUAL(50, thirdTermView.BottomInclusive());
|
||||
|
||||
verifyHostData(*hostTb, dy);
|
||||
verifyTermData(*termTb);
|
||||
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
// Conpty's doesn't have a scrollback, it's view's origin is always 0,0
|
||||
const auto fourthHostView = si.GetViewport();
|
||||
VERIFY_ARE_EQUAL(0, fourthHostView.Top());
|
||||
VERIFY_ARE_EQUAL(newViewportSize.Y, fourthHostView.BottomExclusive());
|
||||
|
||||
// The Terminal should be stuck on the bottom of the viewport
|
||||
const auto fourthTermView = term->GetViewport();
|
||||
|
||||
VERIFY_ARE_EQUAL(50 - fourthTermView.Height() + 1, fourthTermView.Top());
|
||||
VERIFY_ARE_EQUAL(50, fourthTermView.BottomInclusive());
|
||||
}
|
||||
|
|
107
src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp
Normal file
107
src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp
Normal file
|
@ -0,0 +1,107 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include <WexTestClass.h>
|
||||
|
||||
#include "../renderer/inc/DummyRenderTarget.hpp"
|
||||
#include "../cascadia/TerminalCore/Terminal.hpp"
|
||||
#include "MockTermSettings.h"
|
||||
#include "consoletaeftemplates.hpp"
|
||||
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
using namespace Microsoft::Terminal::Core;
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
|
||||
namespace TerminalCoreUnitTests
|
||||
{
|
||||
class TerminalBufferTests;
|
||||
};
|
||||
using namespace TerminalCoreUnitTests;
|
||||
|
||||
class TerminalCoreUnitTests::TerminalBufferTests final
|
||||
{
|
||||
TEST_CLASS(TerminalBufferTests);
|
||||
|
||||
TEST_METHOD(TestResizeHeight);
|
||||
|
||||
TEST_METHOD_SETUP(MethodSetup)
|
||||
{
|
||||
// STEP 1: Set up the Terminal
|
||||
term = std::make_unique<Terminal>();
|
||||
term->Create({ 80, 32 }, 100, emptyRT);
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD_CLEANUP(MethodCleanup)
|
||||
{
|
||||
term = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
DummyRenderTarget emptyRT;
|
||||
std::unique_ptr<Terminal> term;
|
||||
};
|
||||
|
||||
void TerminalBufferTests::TestResizeHeight()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
||||
TEST_METHOD_PROPERTY(L"Data:dy", L"{-10, -1, 0, 1, 10}")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
int dy;
|
||||
VERIFY_SUCCEEDED(TestData::TryGetValue(L"dy", dy), L"change in height of buffer");
|
||||
|
||||
auto& termTb = *term->_buffer;
|
||||
auto& termSm = *term->_stateMachine;
|
||||
const auto initialView = term->GetViewport();
|
||||
|
||||
VERIFY_ARE_EQUAL(0, initialView.Top());
|
||||
VERIFY_ARE_EQUAL(32, initialView.BottomExclusive());
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Print 50 lines of output, which will scroll the viewport"));
|
||||
|
||||
for (auto i = 0; i < 50; i++)
|
||||
{
|
||||
auto wstr = std::wstring(1, static_cast<wchar_t>(L'0' + i));
|
||||
termSm.ProcessString(wstr);
|
||||
termSm.ProcessString(L"\r\n");
|
||||
}
|
||||
|
||||
const auto secondView = term->GetViewport();
|
||||
|
||||
VERIFY_ARE_EQUAL(50 - initialView.Height() + 1, secondView.Top());
|
||||
VERIFY_ARE_EQUAL(50, secondView.BottomInclusive());
|
||||
|
||||
auto verifyBufferContents = [&termTb]() {
|
||||
for (short row = 0; row < 50; row++)
|
||||
{
|
||||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures);
|
||||
auto iter = termTb.GetCellDataAt({ 0, row });
|
||||
auto expectedString = std::wstring(1, static_cast<wchar_t>(L'0' + row));
|
||||
|
||||
if (iter->Chars() != expectedString)
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(L"row [%d] was mismatched", row));
|
||||
}
|
||||
VERIFY_ARE_EQUAL(expectedString, (iter++)->Chars());
|
||||
VERIFY_ARE_EQUAL(L" ", (iter)->Chars());
|
||||
}
|
||||
};
|
||||
verifyBufferContents();
|
||||
|
||||
auto resizeResult = term->UserResize({ 80, gsl::narrow_cast<short>(32 + dy) });
|
||||
VERIFY_SUCCEEDED(resizeResult);
|
||||
|
||||
const auto thirdView = term->GetViewport();
|
||||
|
||||
VERIFY_ARE_EQUAL(50 - thirdView.Height() + 1, thirdView.Top());
|
||||
VERIFY_ARE_EQUAL(50, thirdView.BottomInclusive());
|
||||
|
||||
verifyBufferContents();
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
<Import Project="$(SolutionDir)\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(SolutionDir)\src\common.build.pre.props" />
|
||||
<ItemGroup>
|
||||
<ClCompile Include="TerminalBufferTests.cpp" />
|
||||
<ClCompile Include="ScreenSizeLimitsTest.cpp" />
|
||||
<ClCompile Include="SelectionTest.cpp" />
|
||||
<ClCompile Include="InputTest.cpp" />
|
||||
|
|
|
@ -638,3 +638,17 @@ void ConsoleArguments::SetExpectedSize(COORD dimensions) noexcept
|
|||
_recievedEarlySizeChange = true;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
// Method Description:
|
||||
// - This is a test helper method. It can be used to trick us into thinking
|
||||
// we're headless (in conpty mode), even without parsing any arguments.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void ConsoleArguments::EnableConptyModeForTests()
|
||||
{
|
||||
_headless = true;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -53,6 +53,10 @@ public:
|
|||
|
||||
void SetExpectedSize(COORD dimensions) noexcept;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
void EnableConptyModeForTests();
|
||||
#endif
|
||||
|
||||
static const std::wstring_view VT_MODE_ARG;
|
||||
static const std::wstring_view HEADLESS_ARG;
|
||||
static const std::wstring_view SERVER_HANDLE_ARG;
|
||||
|
|
|
@ -206,6 +206,21 @@ bool VtIo::IsUsingVt() const
|
|||
return _objectsCreated;
|
||||
}
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
// Method Description:
|
||||
// - This is a test helper method. It can be used to trick VtIo into responding
|
||||
// true to `IsUsingVt`, which will cause the console host to act in conpty
|
||||
// mode.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void VtIo::EnableConptyModeForTests()
|
||||
{
|
||||
_objectsCreated = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Routine Description:
|
||||
// Potentially starts this VtIo's input thread and render engine.
|
||||
// If the VtIo hasn't yet been given pipes, then this function will
|
||||
|
@ -431,3 +446,11 @@ void VtIo::EndResize()
|
|||
_pVtRenderEngine->EndResizeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
void VtIo::SetVirtualTop(const short virtualTop) noexcept
|
||||
{
|
||||
if (_pVtRenderEngine)
|
||||
{
|
||||
_pVtRenderEngine->SetVirtualTop(virtualTop);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,11 @@ namespace Microsoft::Console::VirtualTerminal
|
|||
|
||||
void BeginResize();
|
||||
void EndResize();
|
||||
void SetVirtualTop(const short virtualTop) noexcept;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
void EnableConptyModeForTests();
|
||||
#endif
|
||||
|
||||
private:
|
||||
// After CreateIoHandlers is called, these will be invalid.
|
||||
|
|
|
@ -776,7 +776,15 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
|||
{
|
||||
// TODO: MSFT: 9574827 - shouldn't we be looking at or at least logging the failure codes here? (Or making them non-void?)
|
||||
context.PostUpdateWindowSize();
|
||||
WriteToScreen(context, context.GetViewport());
|
||||
|
||||
// Use WriteToScreen to invalidate the viewport with the renderer.
|
||||
// GH#3490 - If we're in conpty mode, don't invalidate the entire
|
||||
// viewport. In conpty mode, the VtEngine will later decide what
|
||||
// part of the buffer actually needs to be re-sent to the terminal.
|
||||
if (!g.getConsoleInformation().IsInVtIoMode())
|
||||
{
|
||||
WriteToScreen(context, context.GetViewport());
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
|
|
@ -16,3 +16,19 @@ bool Globals::IsHeadless() const
|
|||
{
|
||||
return launchArgs.IsHeadless();
|
||||
}
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
// Method Description:
|
||||
// - This is a test helper method. It can be used to trick us into responding
|
||||
// true to `IsHeadless`, which will cause the console host to act in conpty
|
||||
// mode.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Globals::EnableConptyModeForTests()
|
||||
{
|
||||
launchArgs.EnableConptyModeForTests();
|
||||
getConsoleInformation().GetVtIo()->EnableConptyModeForTests();
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -69,6 +69,10 @@ public:
|
|||
|
||||
ApiRoutines api;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
void EnableConptyModeForTests();
|
||||
#endif
|
||||
|
||||
private:
|
||||
CONSOLE_INFORMATION ciConsoleInformation;
|
||||
};
|
||||
|
|
|
@ -1423,17 +1423,38 @@ bool SCREEN_INFORMATION::IsMaximizedY() const
|
|||
}
|
||||
|
||||
// Save cursor's relative height versus the viewport
|
||||
SHORT const sCursorHeightInViewportBefore = _textBuffer->GetCursor().GetPosition().Y - _viewport.Top();
|
||||
short cursorHeightInViewportBefore = _textBuffer->GetCursor().GetPosition().Y - _viewport.Top();
|
||||
|
||||
HRESULT hr = TextBuffer::Reflow(*_textBuffer.get(), *newTextBuffer.get());
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
const bool isConpty = gci.IsInVtIoMode();
|
||||
HRESULT hr = TextBuffer::Reflow(*_textBuffer.get(), *newTextBuffer.get(), isConpty);
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
// GH#3490 - In conpty mode, we want to pad new lines into to the top of
|
||||
// the buffer when we increase the buffer height. Terminals typically
|
||||
// keep the buffer contents "stuck" to the bottom of the viewport when
|
||||
// they increase in height, moving lines from the scrollback into the
|
||||
// viewport. We don't have a scrollback at all in conpty mode, but we
|
||||
// can still keep the content we have at the bottom by inserting new
|
||||
// lines at the top.
|
||||
//
|
||||
// When that happens, make sure to move our relative cursor position
|
||||
// down, so that it stays in the same position relative to the bottom
|
||||
// that it was before.
|
||||
|
||||
if (isConpty && _textBuffer->GetSize().Height() < newTextBuffer->GetSize().Height())
|
||||
{
|
||||
const short diff = newTextBuffer->GetSize().Height() - _textBuffer->GetSize().Height();
|
||||
cursorHeightInViewportBefore += diff;
|
||||
gci.GetVtIo()->SetVirtualTop(diff);
|
||||
}
|
||||
|
||||
Cursor& newCursor = newTextBuffer->GetCursor();
|
||||
// Adjust the viewport so the cursor doesn't wildly fly off up or down.
|
||||
SHORT const sCursorHeightInViewportAfter = newCursor.GetPosition().Y - _viewport.Top();
|
||||
const short cursorHeightInViewportAfter = newCursor.GetPosition().Y - _viewport.Top();
|
||||
COORD coordCursorHeightDiff = { 0 };
|
||||
coordCursorHeightDiff.Y = sCursorHeightInViewportAfter - sCursorHeightInViewportBefore;
|
||||
coordCursorHeightDiff.Y = cursorHeightInViewportAfter - cursorHeightInViewportBefore;
|
||||
LOG_IF_FAILED(SetViewportOrigin(false, coordCursorHeightDiff, true));
|
||||
|
||||
_textBuffer.swap(newTextBuffer);
|
||||
|
@ -2176,8 +2197,13 @@ void SCREEN_INFORMATION::SetDefaultAttributes(const TextAttribute& attributes,
|
|||
commandLine.UpdatePopups(attributes, popupAttributes, oldPrimaryAttributes, oldPopupAttributes);
|
||||
}
|
||||
|
||||
// force repaint of entire viewport
|
||||
GetRenderTarget().TriggerRedrawAll();
|
||||
// Force repaint of entire viewport, unless we're in conpty mode. In that
|
||||
// case, we don't really need to force a redraw of the entire screen just
|
||||
// because the text attributes changed.
|
||||
if (!gci.IsInVtIoMode())
|
||||
{
|
||||
GetRenderTarget().TriggerRedrawAll();
|
||||
}
|
||||
|
||||
gci.ConsoleIme.RefreshAreaAttributes();
|
||||
|
||||
|
|
|
@ -50,6 +50,14 @@ Revision History:
|
|||
#include "../types/IConsoleWindow.hpp"
|
||||
class ConversionAreaInfo; // forward decl window. circular reference
|
||||
|
||||
// fwdecl unittest classes
|
||||
#ifdef UNIT_TESTING
|
||||
namespace TerminalCoreUnitTests
|
||||
{
|
||||
class ConptyRoundtripTests;
|
||||
};
|
||||
#endif
|
||||
|
||||
class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console::IIoProvider
|
||||
{
|
||||
public:
|
||||
|
@ -308,6 +316,6 @@ private:
|
|||
friend class ScreenBufferTests;
|
||||
friend class CommonState;
|
||||
friend class ConptyOutputTests;
|
||||
friend class ConptyRoundtripTests;
|
||||
friend class TerminalCoreUnitTests::ConptyRoundtripTests;
|
||||
#endif
|
||||
};
|
||||
|
|
|
@ -85,6 +85,11 @@ class ConptyOutputTests
|
|||
g.pRender->AddRenderEngine(_pVtRenderEngine.get());
|
||||
gci.GetActiveOutputBuffer().SetTerminalConnection(_pVtRenderEngine.get());
|
||||
|
||||
// Manually set the console into conpty mode. We're not actually going
|
||||
// to set up the pipes for conpty, but we want the console to behave
|
||||
// like it would in conpty mode.
|
||||
g.EnableConptyModeForTests();
|
||||
|
||||
expectedOutput.clear();
|
||||
|
||||
return true;
|
||||
|
|
|
@ -81,16 +81,19 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void PrepareGlobalScreenBuffer()
|
||||
void PrepareGlobalScreenBuffer(const short viewWidth = s_csWindowWidth,
|
||||
const short viewHeight = s_csWindowHeight,
|
||||
const short bufferWidth = s_csBufferWidth,
|
||||
const short bufferHeight = s_csBufferHeight)
|
||||
{
|
||||
CONSOLE_INFORMATION& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
COORD coordWindowSize;
|
||||
coordWindowSize.X = s_csWindowWidth;
|
||||
coordWindowSize.Y = s_csWindowHeight;
|
||||
coordWindowSize.X = viewWidth;
|
||||
coordWindowSize.Y = viewHeight;
|
||||
|
||||
COORD coordScreenBufferSize;
|
||||
coordScreenBufferSize.X = s_csBufferWidth;
|
||||
coordScreenBufferSize.Y = s_csBufferHeight;
|
||||
coordScreenBufferSize.X = bufferWidth;
|
||||
coordScreenBufferSize.Y = bufferHeight;
|
||||
|
||||
UINT uiCursorSize = 12;
|
||||
|
||||
|
@ -143,12 +146,14 @@ public:
|
|||
gci.SetCookedReadData(nullptr);
|
||||
}
|
||||
|
||||
void PrepareNewTextBufferInfo(const bool useDefaultAttributes = false)
|
||||
void PrepareNewTextBufferInfo(const bool useDefaultAttributes = false,
|
||||
const short bufferWidth = s_csBufferWidth,
|
||||
const short bufferHeight = s_csBufferHeight)
|
||||
{
|
||||
CONSOLE_INFORMATION& gci = Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
COORD coordScreenBufferSize;
|
||||
coordScreenBufferSize.X = s_csBufferWidth;
|
||||
coordScreenBufferSize.Y = s_csBufferHeight;
|
||||
coordScreenBufferSize.X = bufferWidth;
|
||||
coordScreenBufferSize.Y = bufferHeight;
|
||||
|
||||
UINT uiCursorSize = 12;
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
|
|||
_lastViewport(initialViewport),
|
||||
_invalidRect(Viewport::Empty()),
|
||||
_fInvalidRectUsed(false),
|
||||
_lastRealCursor({ 0 }),
|
||||
_lastText({ 0 }),
|
||||
_scrollDelta({ 0 }),
|
||||
_quickReturn(false),
|
||||
|
@ -280,34 +279,29 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
|
|||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
// Viewport is smaller now - just update it all.
|
||||
if (oldView.Height() > newView.Height() || oldView.Width() > newView.Width())
|
||||
if (oldView.Width() != newView.Width())
|
||||
{
|
||||
// Viewport is a different width now - just update it all. We may
|
||||
// have re-wrapped the buffer contents.
|
||||
hr = InvalidateAll();
|
||||
}
|
||||
else
|
||||
else if (oldView.Height() < newView.Height())
|
||||
{
|
||||
// At least one of the directions grew.
|
||||
// First try and add everything to the right of the old viewport,
|
||||
// then everything below where the old viewport ended.
|
||||
if (oldView.Width() < newView.Width())
|
||||
{
|
||||
short left = oldView.RightExclusive();
|
||||
short top = 0;
|
||||
short right = newView.RightInclusive();
|
||||
short bottom = oldView.BottomInclusive();
|
||||
Viewport rightOfOldViewport = Viewport::FromInclusive({ left, top, right, bottom });
|
||||
hr = _InvalidCombine(rightOfOldViewport);
|
||||
}
|
||||
if (SUCCEEDED(hr) && oldView.Height() < newView.Height())
|
||||
{
|
||||
short left = 0;
|
||||
short top = oldView.BottomExclusive();
|
||||
short right = newView.RightInclusive();
|
||||
short bottom = newView.BottomInclusive();
|
||||
Viewport belowOldViewport = Viewport::FromInclusive({ left, top, right, bottom });
|
||||
hr = _InvalidCombine(belowOldViewport);
|
||||
}
|
||||
// We grew in height. We inserted empty lines at the top of the
|
||||
// buffer. The cursor is now _actually_ lower (larger Y/row value)
|
||||
// than it was before.
|
||||
_lastText.Y += gsl::narrow_cast<short>(newView.Height() - oldView.Height());
|
||||
// The text content will try and stay "stuck" at the bottom of the
|
||||
// viewport of the terminal, and invalidating the bottom here can
|
||||
// cause unnecessary lines to get written to the terminal. See
|
||||
// GH#3490.
|
||||
}
|
||||
else if (oldView.Height() > newView.Height())
|
||||
{
|
||||
// We shrunk in height. We don't really need to do anything here.
|
||||
// Shrinking in height will remove lines from the top of the buffer
|
||||
// (pushing them into scrollback in the terminal).
|
||||
// The cursor will stay in the same relative position.
|
||||
}
|
||||
}
|
||||
_resized = true;
|
||||
|
@ -409,6 +403,11 @@ bool VtEngine::_AllIsInvalid() const
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
void VtEngine::SetVirtualTop(const short virtualTop) noexcept
|
||||
{
|
||||
_virtualTop = std::max(_virtualTop, virtualTop);
|
||||
}
|
||||
|
||||
void VtEngine::SetTerminalOwner(Microsoft::Console::ITerminalOwner* const terminalOwner)
|
||||
{
|
||||
_terminalOwner = terminalOwner;
|
||||
|
|
|
@ -26,7 +26,10 @@ Author(s):
|
|||
|
||||
// fwdecl unittest classes
|
||||
#ifdef UNIT_TESTING
|
||||
class ConptyRoundtripTests;
|
||||
namespace TerminalCoreUnitTests
|
||||
{
|
||||
class ConptyRoundtripTests;
|
||||
};
|
||||
#endif
|
||||
|
||||
namespace Microsoft::Console::Render
|
||||
|
@ -101,6 +104,7 @@ namespace Microsoft::Console::Render
|
|||
void SetTerminalOwner(Microsoft::Console::ITerminalOwner* const terminalOwner);
|
||||
void BeginResizeRequest();
|
||||
void EndResizeRequest();
|
||||
void SetVirtualTop(const short virtualTop) noexcept;
|
||||
|
||||
protected:
|
||||
wil::unique_hfile _hFile;
|
||||
|
@ -116,7 +120,6 @@ namespace Microsoft::Console::Render
|
|||
Microsoft::Console::Types::Viewport _invalidRect;
|
||||
|
||||
bool _fInvalidRectUsed;
|
||||
COORD _lastRealCursor;
|
||||
COORD _lastText;
|
||||
COORD _scrollDelta;
|
||||
|
||||
|
@ -226,7 +229,7 @@ namespace Microsoft::Console::Render
|
|||
|
||||
friend class VtRendererTest;
|
||||
friend class ConptyOutputTests;
|
||||
friend class ConptyRoundtripTests;
|
||||
friend class TerminalCoreUnitTests::ConptyRoundtripTests;
|
||||
#endif
|
||||
|
||||
void SetTestCallback(_In_ std::function<bool(const char* const, size_t const)> pfn);
|
||||
|
|
Loading…
Reference in a new issue