Fix wide char support for WriteConsoleOutputAttribute (#18796)

When we overwrite the attributes during the fill,
we must retain the lead/trail byte attributes.

Closes #18746

## Validation Steps Performed
* Added a unit test 
This commit is contained in:
Leonard Hecker 2025-04-14 23:12:01 +02:00 committed by GitHub
parent 6682bed311
commit 90c312f7da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 52 additions and 13 deletions

View File

@ -110,15 +110,25 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon
switch (mode)
{
case FillConsoleMode::WriteAttribute:
{
for (; columns < columnsAvailable && inputPos < lengthToWrite; ++columns, ++inputPos)
{
infoBuffer[columns].Attributes = input[inputPos];
// Overwrite all attributes except for the lead/trail byte markers.
// Those are used by WriteConsoleOutputWImplHelper to correctly serialize the input.
constexpr auto LT = COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE;
auto& attributes = infoBuffer[columns].Attributes;
attributes = (input[inputPos] & ~LT) | (attributes & LT);
}
break;
}
case FillConsoleMode::FillAttribute:
for (const auto attr = input[0]; columns < columnsAvailable && inputPos < lengthToWrite; ++columns, ++inputPos)
{
infoBuffer[columns].Attributes = attr;
// Overwrite all attributes except for the lead/trail byte markers.
// Those are used by WriteConsoleOutputWImplHelper to correctly serialize the input.
constexpr auto LT = COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE;
auto& attributes = infoBuffer[columns].Attributes;
attributes = (attr & ~LT) | (attributes & LT);
}
break;
case FillConsoleMode::WriteCharacter:

View File

@ -52,6 +52,16 @@ static constexpr std::wstring_view s_initialContentVT{
// clang-format on
};
static constexpr std::wstring_view s_initialContentVTWide{
// clang-format off
L""
sgr_red("") sgr_blu("") sgr_red("") sgr_blu("") "\r\n"
sgr_red("") sgr_blu("") sgr_red("") sgr_blu("") "\r\n"
sgr_blu("") sgr_red("") sgr_blu("") sgr_red("") "\r\n"
sgr_blu("") sgr_red("") sgr_blu("") sgr_red("")
// clang-format on
};
class ::Microsoft::Console::VirtualTerminal::VtIoTests
{
BEGIN_TEST_CLASS(VtIoTests)
@ -71,11 +81,11 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests
return { &rxBuf[0], read };
}
void setupInitialContents() const
void setupInitialContents(bool wide) const
{
auto& sm = screenInfo->GetStateMachine();
sm.ProcessString(L"\033c");
sm.ProcessString(s_initialContentVT);
sm.ProcessString(wide ? s_initialContentVTWide : s_initialContentVT);
sm.ProcessString(L"\x1b[H" sgr_rst());
}
@ -277,7 +287,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests
TEST_METHOD(WriteConsoleOutputAttribute)
{
setupInitialContents();
setupInitialContents(false);
static constexpr std::array payload{ red, blu, red, blu };
static constexpr til::point target{ 6, 1 };
@ -295,7 +305,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests
TEST_METHOD(WriteConsoleOutputCharacterW)
{
setupInitialContents();
setupInitialContents(false);
size_t written = 0;
std::string_view expected;
@ -354,7 +364,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests
actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
setupInitialContents();
setupInitialContents(false);
// Writing at the start of a line.
THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, red, 3, { 0, 0 }, cellsModified, false));
@ -388,6 +398,25 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(FillConsoleOutputAttributeWide)
{
setupInitialContents(true);
size_t cellsModified = 0;
std::string_view expected;
std::string_view actual;
// Writing nothing should produce nothing.
THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, red, 4, { 2, 1 }, cellsModified, false));
expected =
decsc() //
cup(2, 3) sgr_red("五六") //
decrc();
actual = readOutput();
VERIFY_ARE_EQUAL(4u, cellsModified);
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(FillConsoleOutputCharacterW)
{
size_t cellsModified = 0;
@ -407,7 +436,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests
actual = readOutput();
VERIFY_ARE_EQUAL(expected, actual);
setupInitialContents();
setupInitialContents(false);
// Writing at the start of a line.
THROW_IF_FAILED(routines.FillConsoleOutputCharacterWImpl(*screenInfo, L'a', 3, { 0, 0 }, cellsModified, false));
@ -453,7 +482,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests
std::string_view expected;
std::string_view actual;
setupInitialContents();
setupInitialContents(false);
// Scrolling from nowhere to somewhere are no-ops and should not emit anything.
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 0, -1, -1 }, {}, std::nullopt, L' ', 0, false));
@ -487,7 +516,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests
//
// m n M N o p O P
//
setupInitialContents();
setupInitialContents(false);
// Scrolling from somewhere to somewhere.
//
@ -626,7 +655,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests
std::string_view expected;
std::string_view actual;
setupInitialContents();
setupInitialContents(false);
// Scrolling from nowhere to somewhere are no-ops and should not emit anything.
THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 0, -1, -1 }, {}, std::nullopt, L' ', 0, false));
@ -660,7 +689,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests
//
// m n M N o p O P
//
setupInitialContents();
setupInitialContents(false);
// Scrolling from somewhere to somewhere.
//
@ -797,7 +826,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests
&screenInfoAlt));
routines.SetConsoleActiveScreenBufferImpl(*screenInfoAlt);
setupInitialContents();
setupInitialContents(false);
THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfoAlt, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING));
readOutput();