From b925b0b977aaf0c062e0c4c05ae008a09672c355 Mon Sep 17 00:00:00 2001 From: Maximus5 Date: Mon, 18 Apr 2022 22:38:52 +0200 Subject: [PATCH] gh-2404, gh-2420, gh-2425, gh-2429: Fix line-wrapping regression. --- src/CE.sln.DotSettings | 3 + src/ConEmuHk/ExtConsole.cpp | 3 +- src/UnitTests/ansi_test.cpp | 245 ++++++++++++++++++++++++++++++++++++ src/UnitTests/main_test.cpp | 6 + 4 files changed, 256 insertions(+), 1 deletion(-) diff --git a/src/CE.sln.DotSettings b/src/CE.sln.DotSettings index eca1c09881..40bebae0ee 100644 --- a/src/CE.sln.DotSettings +++ b/src/CE.sln.DotSettings @@ -46,6 +46,8 @@ LIVE_MONITOR LIVE_MONITOR True + True + True True True True @@ -66,6 +68,7 @@ True True True + True True True True diff --git a/src/ConEmuHk/ExtConsole.cpp b/src/ConEmuHk/ExtConsole.cpp index 2d885e8e46..73ec20913f 100644 --- a/src/ConEmuHk/ExtConsole.cpp +++ b/src/ConEmuHk/ExtConsole.cpp @@ -1159,7 +1159,8 @@ BOOL ExtWriteText(ExtWriteTextParm* Info) y = y2; }; - if (gState.wasWriteAtEol && (csbi.dwCursorPosition.Y == gState.lastWriteCoord.Y) && (csbi.dwCursorPosition.X == gState.lastWriteCoord.X)) + if (gState.wasWriteAtEol && (Info->Buffer[0] != L'\r' && Info->Buffer[0] != L'\n') + && (csbi.dwCursorPosition.Y == gState.lastWriteCoord.Y) && (csbi.dwCursorPosition.X == gState.lastWriteCoord.X)) { lbRc = IntWriteText(h, x, x, L"\r\n", 2, (pTrueColorStart && (nLinePosition >= 0)) ? (pTrueColorStart + nLinePosition) : nullptr, diff --git a/src/UnitTests/ansi_test.cpp b/src/UnitTests/ansi_test.cpp index 5ae333100b..8dd9f59e17 100644 --- a/src/UnitTests/ansi_test.cpp +++ b/src/UnitTests/ansi_test.cpp @@ -158,6 +158,172 @@ int RunLineFeedTest() return result; } +int RunFishPromptTest() +{ + const MHandle hConOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (!IsConEmuMode(hConOut, __FUNCTION__)) + return 0; + + auto testFishPrompt = [&]() + { + const DWORD outFlags = GetMode(hConOut); + CONSOLE_SCREEN_BUFFER_INFO csbi{}; + GetConsoleScreenBufferInfo(hConOut, &csbi); + wchar_t buffer[255] = L""; + swprintf_s(buffer, 255, L"Current output console mode: 0x%08X (%s %s)\r\nScreen width: %i\r\n", outFlags, + (outFlags & ENABLE_VIRTUAL_TERMINAL_PROCESSING) ? L"ENABLE_VIRTUAL_TERMINAL_PROCESSING" : L"!ENABLE_VIRTUAL_TERMINAL_PROCESSING", + (outFlags & DISABLE_NEWLINE_AUTO_RETURN) ? L"DISABLE_NEWLINE_AUTO_RETURN" : L"!DISABLE_NEWLINE_AUTO_RETURN", + csbi.dwSize.X); + Write(hConOut, buffer); + + SetConsoleTextAttribute(hConOut, 14); + Write(hConOut, L"Print(AAA\\r\\n\x23CE ... \\rCCC)\r\n"); + SetConsoleTextAttribute(hConOut, 7); + + Write(hConOut, L"AAA\r\n\x23CE"); + const std::wstring spaces(csbi.dwSize.X - 1, L' '); + Write(hConOut, spaces); + Write(hConOut, L"\rCCC\r\n"); + + const std::wstring expected(L"AAA CCC "); + + bool result = true; + if (!expected.empty()) + { + GetConsoleScreenBufferInfo(hConOut, &csbi); + CHAR_INFO readBuffer[6 * 2] = {}; + SMALL_RECT rcRead = { 0, static_cast(csbi.dwCursorPosition.Y - 2), 5, static_cast(csbi.dwCursorPosition.Y - 1) }; + ReadConsoleOutputW(hConOut, readBuffer, COORD{ 6, 2 }, COORD{ 0, 0 }, &rcRead); + std::wstring data; + for (const auto& ci : readBuffer) + { + data += ci.Char.UnicodeChar; + } + if (data == expected) + { + SetConsoleTextAttribute(hConOut, 10); + Write(hConOut, L"OK\r\n"); + } + else + { + SetConsoleTextAttribute(hConOut, 12); + Write(hConOut, L"FAILED: '" + data + L"' != '" + expected + L"'\r\n"); + result = false; + } + SetConsoleTextAttribute(hConOut, 7); + } + return result; + }; + + // conemu::tests::WaitDebugger("Wait to start fish prompt test", 30000); + + int result = 0; + const DWORD outFlags = GetMode(hConOut); + const bool isDefaultMode = (outFlags == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT)); + CONSOLE_SCREEN_BUFFER_INFO csbi{}; + GetConsoleScreenBufferInfo(hConOut, &csbi); + + SetConsoleTextAttribute(hConOut, 7); + + // Write(hConOut, L"\x1B]9;10\x07"); + SetConsoleMode(hConOut, (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)); + + if (!testFishPrompt()) + result = 1; + + if (!isDefaultMode) + { + SetConsoleMode(hConOut, (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT)); + if (!testFishPrompt()) + result = 1; + } + + SetConsoleMode(hConOut, outFlags); + SetConsoleTextAttribute(hConOut, csbi.wAttributes); + return result; +} + +int RunLineWrapWin11Test() +{ + const MHandle hConOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (!IsConEmuMode(hConOut, __FUNCTION__)) + return 0; + + auto testLineWrap = [&]() + { + const DWORD outFlags = GetMode(hConOut); + CONSOLE_SCREEN_BUFFER_INFO csbi{}; + GetConsoleScreenBufferInfo(hConOut, &csbi); + wchar_t buffer[255] = L""; + swprintf_s(buffer, 255, L"Current output console mode: 0x%08X (%s %s)\r\nScreen width: %i\r\n", outFlags, + (outFlags & ENABLE_VIRTUAL_TERMINAL_PROCESSING) ? L"ENABLE_VIRTUAL_TERMINAL_PROCESSING" : L"!ENABLE_VIRTUAL_TERMINAL_PROCESSING", + (outFlags & DISABLE_NEWLINE_AUTO_RETURN) ? L"DISABLE_NEWLINE_AUTO_RETURN" : L"!DISABLE_NEWLINE_AUTO_RETURN", + csbi.dwSize.X); + Write(hConOut, buffer); + + SetConsoleTextAttribute(hConOut, 14); + Write(hConOut, L"Print(AAA...ABCD{line wrap is expected here}EFG\r\n"); + SetConsoleTextAttribute(hConOut, 7); + + const std::wstring aaa(csbi.dwSize.X - 3, L'A'); + Write(hConOut, aaa); + const std::wstring rest(L"BCDEFG"); + for (size_t i = 0; i < rest.size(); ++i) + { + Write(hConOut, rest.substr(i, 1)); + } + Write(hConOut, L"\r\n"); + + const std::wstring expected(L"AAAEFG"); + + bool result = true; + if (!expected.empty()) + { + GetConsoleScreenBufferInfo(hConOut, &csbi); + CHAR_INFO readBuffer[3 * 2] = {}; + SMALL_RECT rcRead = { 0, static_cast(csbi.dwCursorPosition.Y - 2), 2, static_cast(csbi.dwCursorPosition.Y - 1) }; + ReadConsoleOutputW(hConOut, readBuffer, COORD{ 3, 2 }, COORD{ 0, 0 }, &rcRead); + std::wstring data; + for (const auto& ci : readBuffer) + { + data += ci.Char.UnicodeChar; + } + if (data == expected) + { + SetConsoleTextAttribute(hConOut, 10); + Write(hConOut, L"OK\r\n"); + } + else + { + SetConsoleTextAttribute(hConOut, 12); + Write(hConOut, L"FAILED: '" + data + L"' != '" + expected + L"'\r\n"); + result = false; + } + SetConsoleTextAttribute(hConOut, 7); + } + return result; + }; + + // conemu::tests::WaitDebugger("Wait to start line wrap test", 30000); + + int result = 0; + const DWORD outFlags = GetMode(hConOut); + const bool isDefaultMode = (outFlags == (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT)); + CONSOLE_SCREEN_BUFFER_INFO csbi{}; + GetConsoleScreenBufferInfo(hConOut, &csbi); + + SetConsoleTextAttribute(hConOut, 7); + + SetConsoleMode(hConOut, (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)); + + if (!testLineWrap()) + result = 1; + + SetConsoleMode(hConOut, outFlags); + SetConsoleTextAttribute(hConOut, csbi.wAttributes); + return result; +} + int RunLineFeedTestXTerm() { const MHandle hConOut = GetStdHandle(STD_OUTPUT_HANDLE); @@ -471,6 +637,85 @@ TEST(Ansi, CheckLineFeed) CloseHandle(pi.hThread); } +TEST(Ansi, CheckFishPrompt) +{ + const MHandle hConOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (!conemu::tests::IsConEmuMode(hConOut, __FUNCTION__)) + return; + + conemu::tests::InitConEmuPathVars(); + + STARTUPINFOW si{}; si.cb = sizeof(si); + PROCESS_INFORMATION pi{}; + CEStr testExe; + GetModulePathName(nullptr, testExe); + const CEStr envCmdLine(L"\"%ConEmuBaseDir%\\" ConEmuC_EXE_3264 L"\" -std -c \"", testExe, L"\" RunFishPromptTest"); + const CEStr cmdLine(ExpandEnvStr(envCmdLine)); + const auto created = CreateProcessW(nullptr, cmdLine.data(), nullptr, nullptr, false, NORMAL_PRIORITY_CLASS, nullptr, nullptr, &si, &pi); + if (!created) + { + const DWORD errCode = GetLastError(); + EXPECT_TRUE(created) << "create process failed, code=" << errCode << ", cmd=" << cmdLine.c_str(); + return; + } + + const auto wait = WaitForSingleObject(pi.hProcess, 1000 * 180); + if (wait != WAIT_OBJECT_0) + { + EXPECT_EQ(wait, WAIT_OBJECT_0) << "process was not finished in time: " << cmdLine.c_str(); + TerminateProcess(pi.hProcess, 255); + } + else + { + DWORD exitCode = 255; + EXPECT_TRUE(GetExitCodeProcess(pi.hProcess, &exitCode)); + EXPECT_EQ(0, exitCode); + } + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); +} + +// This test is created after gh-2404 +TEST(Ansi, CheckLineWrapWin11) +{ + const MHandle hConOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (!conemu::tests::IsConEmuMode(hConOut, __FUNCTION__)) + return; + + conemu::tests::InitConEmuPathVars(); + + STARTUPINFOW si{}; si.cb = sizeof(si); + PROCESS_INFORMATION pi{}; + CEStr testExe; + GetModulePathName(nullptr, testExe); + const CEStr envCmdLine(L"\"%ConEmuBaseDir%\\" ConEmuC_EXE_3264 L"\" -std -c \"", testExe, L"\" RunLineWrapWin11Test"); + const CEStr cmdLine(ExpandEnvStr(envCmdLine)); + const auto created = CreateProcessW(nullptr, cmdLine.data(), nullptr, nullptr, false, NORMAL_PRIORITY_CLASS, nullptr, nullptr, &si, &pi); + if (!created) + { + const DWORD errCode = GetLastError(); + EXPECT_TRUE(created) << "create process failed, code=" << errCode << ", cmd=" << cmdLine.c_str(); + return; + } + + const auto wait = WaitForSingleObject(pi.hProcess, 1000 * 180); + if (wait != WAIT_OBJECT_0) + { + EXPECT_EQ(wait, WAIT_OBJECT_0) << "process was not finished in time: " << cmdLine.c_str(); + TerminateProcess(pi.hProcess, 255); + } + else + { + DWORD exitCode = 255; + EXPECT_TRUE(GetExitCodeProcess(pi.hProcess, &exitCode)); + EXPECT_EQ(0, exitCode); + } + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); +} + TEST(Ansi, CheckLineFeedXTerm) { const MHandle hConOut = GetStdHandle(STD_OUTPUT_HANDLE); diff --git a/src/UnitTests/main_test.cpp b/src/UnitTests/main_test.cpp index d85a676d9c..fcc1c58d90 100644 --- a/src/UnitTests/main_test.cpp +++ b/src/UnitTests/main_test.cpp @@ -34,6 +34,8 @@ namespace conemu { namespace tests { void PrepareGoogleTests(); int RunLineFeedTest(); +int RunFishPromptTest(); +int RunLineWrapWin11Test(); int RunLineFeedTestXTerm(); int RunLineFeedTestParent(); int RunLineFeedTestChild(); @@ -58,6 +60,10 @@ int main(int argc, char** argv) { if (argv[i] && strcmp(argv[i], "RunLineFeedTest") == 0) return conemu::tests::RunLineFeedTest(); + if (argv[i] && strcmp(argv[i], "RunFishPromptTest") == 0) + return conemu::tests::RunFishPromptTest(); + if (argv[i] && strcmp(argv[i], "RunLineWrapWin11Test") == 0) + return conemu::tests::RunLineWrapWin11Test(); if (argv[i] && strcmp(argv[i], "RunLineFeedTestXTerm") == 0) return conemu::tests::RunLineFeedTestXTerm(); if (argv[i] && strcmp(argv[i], "RunLineFeedTestParent") == 0)