From 450eec48de252a3a8d270bade847755ecbeb5a75 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 1 Aug 2024 22:38:10 +0200 Subject: [PATCH] A minor ConPTY refactoring: Goodbye VtEngine Edition (#17510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The idea is that we can translate Console API calls directly to VT at least as well as the current VtEngine setup can. For instance, a call to `SetConsoleCursorPosition` clearly translates directly to a `CUP` escape sequence. Effectively, instead of translating output asynchronously in the renderer thread, we'll do it synchronously right during the Console API call. Most importantly, the this means that any VT output that an application generates will now be given to the terminal unmodified. Aside from reducing our project's complexity quite a bit and opening the path towards various interesting work like sixels, Device Control Strings, buffer snapshotting, synchronized updates, and more, it also improves performance for mixed text output like enwik8.txt in conhost to 1.3-2x and in Windows Terminal via ConPTY to roughly 20x. This adds support for overlapped IO, because now that output cannot be "skipped" anymore (VtEngine worked like a renderer after all) it's become crucial to block conhost as little as possible. ⚠️ Intentionally unresolved changes/quirks: * To force a delayed EOL wrap to wrap, `WriteCharsLegacy` emits a `\r\n` if necessary. This breaks text reflow on window resize. We cannot emit ` \r` the way readline does it, because this would overwrite the first column in the next row with a whitespace. The alternative is to read back the affected cell from the buffer and emit that character and its attributes followed by a `\r`. I chose to not do that, because buffer read-back is lossy (= UCS2). Unless the window is resized, the difference is unnoticeable and historically, conhost had no support for buffer reflow anyway. * If `ENABLE_VIRTUAL_TERMINAL_PROCESSING` is set while `DISABLE_NEWLINE_AUTO_RETURN` is reset, we'll blindly replace all LF with CRLF. This may hypothetically break DCS sequences, but it's the only way to do this without parsing the given VT string and thus the only way we can achieve passthrough mode in the future. * `ENABLE_WRAP_AT_EOL_OUTPUT` is translated to `DECAWM`. Between Windows XP and Windows 11 21H2, `ENABLE_WRAP_AT_EOL_OUTPUT` being reset would cause the cursor position to reset to wherever a write started, _if_ the write, including expanded control chars, was less than 100 characters long. If it was longer than that, the cursor position would end up in an effectively random position. After lengthy research I believe that this is a bug introduced in Windows XP and that the original intention was for this mode to be equivalent to `DECAWM`. This is compounded by MSDN's description (emphasis mine): > If this mode is disabled, the **last character** in the row is > overwritten with any subsequent characters. ⚠️ Unresolved issues/quirks: * Focus/Unfocus events are injected into the output stream without checking whether the VT output is currently in a ground state. This may break whatever VT sequence is currently ongoing. This is an existing issue. * `VtIo::Writer::WriteInfos` should properly verify the width of each individual character. * Using `SetConsoleActiveScreenBuffer` destroys surrogate pairs and extended (VT) attributes. It could be translated to VT pages in the long term. * Similarly, `ScrollConsoleScreenBuffer` results in the same and could be translated to `DECCRA` and `DECFRA` in the near term. This is important because otherwise `vim` output may loose its extended attributes during scrolling. * Reflowing a long line until it wraps results in the cooked read prompt to be misaligned vertically. * `SCREEN_INFORMATION::s_RemoveScreenBuffer` should trigger a buffer switch similar to `SetConsoleActiveScreenBuffer`. * Translation of `COMMON_LVB_GRID_HORIZONTAL` to `SGR 53` was dropped and may be reintroduced alongside `UNDERSCORE` = `SGR 4`. * Move the `OSC 0 ; P t BEL` sequence to `WriteWindowTitle` and swap the `BEL` with the `ST` (`ESC \`). * PowerShell on Windows 10 ships with PSReadLine 2.0.0-beta2 which emits SGR 37/40 instead of 39/49. This results in black spaces when typing and there's no good way to fix that. * A test is missing that ensures that `FillConsoleOutputCharacterW` results in a `CSI n J` during the PowerShell shim. * A test is missing that ensures that `PtySignal::ClearBuffer` does not result in any VT being generated. Closes #262 Closes #1173 Closes #3016 Closes #4129 Closes #5228 Closes #8698 Closes #12336 Closes #15014 Closes #15888 Closes #16461 Closes #16911 Closes #17151 Closes #17313 --- .github/actions/spelling/allow/apis.txt | 3 +- .github/actions/spelling/expect/alphabet.txt | 7 - .github/actions/spelling/expect/expect.txt | 25 +- NOTICE.md | 65 - OpenConsole.sln | 55 - oss/dynamic_bitset/LICENSE | 21 - oss/dynamic_bitset/MAINTAINER_README.md | 17 - oss/dynamic_bitset/cgmanifest.json | 15 - oss/dynamic_bitset/dynamic_bitset.hpp | 1944 ------- oss/libpopcnt/LICENSE | 26 - oss/libpopcnt/MAINTAINER_README.md | 17 - oss/libpopcnt/cgmanifest.json | 15 - oss/libpopcnt/libpopcnt.h | 798 --- src/ConsolePerf.wprp | 2 - src/Terminal.wprp | 1 - src/buffer/out/textBuffer.cpp | 7 - .../TerminalConnection/ConptyConnection.cpp | 220 +- .../TerminalConnection/ConptyConnection.h | 12 +- src/cascadia/TerminalCore/Terminal.cpp | 8 - src/cascadia/TerminalCore/Terminal.hpp | 3 - src/cascadia/TerminalCore/TerminalApi.cpp | 5 - .../UnitTests_Control/ControlCoreTests.cpp | 6 - .../ConptyRoundtripTests.cpp | 5055 ----------------- .../UnitTests_TerminalCore/ScrollTest.cpp | 1 - .../UnitTests_TerminalCore/UnitTests.vcxproj | 40 - src/common.build.pre.props | 2 +- src/host/ConsoleArguments.cpp | 11 - src/host/ConsoleArguments.hpp | 5 - src/host/CursorBlinker.cpp | 10 +- src/host/PtySignalInputThread.cpp | 6 +- src/host/VtInputThread.cpp | 120 +- src/host/VtIo.cpp | 664 ++- src/host/VtIo.hpp | 94 +- src/host/_output.cpp | 158 +- src/host/_stream.cpp | 215 +- src/host/_stream.h | 1 + src/host/consoleInformation.cpp | 28 +- src/host/directio.cpp | 37 + src/host/exe/Host.EXE.vcxproj | 3 - src/host/ft_fuzzer/Host.FuzzWrapper.vcxproj | 3 - src/host/getset.cpp | 256 +- src/host/globals.cpp | 16 - src/host/globals.h | 4 - src/host/inputBuffer.hpp | 1 - src/host/outputStream.cpp | 35 +- src/host/outputStream.hpp | 1 - src/host/screenInfo.cpp | 71 +- src/host/screenInfo.hpp | 20 +- src/host/server.h | 6 +- src/host/srvinit.cpp | 44 +- src/host/ut_host/ConptyOutputTests.cpp | 465 -- src/host/ut_host/Host.UnitTests.vcxproj | 5 - .../ut_host/Host.UnitTests.vcxproj.filters | 6 - src/host/ut_host/ScreenBufferTests.cpp | 28 - src/host/ut_host/VtIoTests.cpp | 888 +-- src/host/ut_host/VtRendererTests.cpp | 1757 ------ src/host/ut_host/sources | 2 - src/inc/LibraryIncludes.h | 10 - src/inc/conpty-static.h | 3 - src/inc/til.h | 2 +- src/inc/til/bitmap.h | 593 -- .../base/InteractivityFactory.cpp | 7 +- src/interactivity/base/ServiceLocator.cpp | 9 - src/interactivity/onecore/BgfxEngine.cpp | 6 - src/interactivity/onecore/BgfxEngine.hpp | 1 - .../Interactivity.Win32.UnitTests.vcxproj | 3 - src/interactivity/win32/windowio.cpp | 6 +- src/project.inc | 2 - src/renderer/atlas/AtlasEngine.api.cpp | 7 - src/renderer/atlas/AtlasEngine.cpp | 7 - src/renderer/atlas/AtlasEngine.h | 2 - src/renderer/atlas/pch.h | 7 - src/renderer/base/RenderEngineBase.cpp | 16 - src/renderer/base/renderer.cpp | 52 - src/renderer/base/renderer.hpp | 14 - src/renderer/dirs | 1 - src/renderer/gdi/gdirenderer.hpp | 1 - src/renderer/gdi/invalidate.cpp | 15 - src/renderer/inc/IRenderEngine.hpp | 2 - src/renderer/inc/RenderEngineBase.hpp | 2 - src/renderer/uia/UiaRenderer.cpp | 14 - src/renderer/uia/UiaRenderer.hpp | 1 - src/renderer/vt/VtSequences.cpp | 527 -- src/renderer/vt/Xterm256Engine.cpp | 185 - src/renderer/vt/Xterm256Engine.hpp | 49 - src/renderer/vt/XtermEngine.cpp | 576 -- src/renderer/vt/XtermEngine.hpp | 82 - src/renderer/vt/dirs | 3 - src/renderer/vt/invalidate.cpp | 144 - src/renderer/vt/lib/sources | 8 - src/renderer/vt/lib/sources.dep | 3 - src/renderer/vt/lib/vt.vcxproj | 18 - src/renderer/vt/lib/vt.vcxproj.filters | 42 - src/renderer/vt/math.cpp | 54 - src/renderer/vt/paint.cpp | 722 --- src/renderer/vt/precomp.cpp | 4 - src/renderer/vt/precomp.h | 33 - src/renderer/vt/sources.inc | 38 - src/renderer/vt/state.cpp | 554 -- src/renderer/vt/tracing.cpp | 356 -- src/renderer/vt/tracing.hpp | 50 - src/renderer/vt/ut_lib/sources | 20 - src/renderer/vt/ut_lib/sources.dep | 3 - src/renderer/vt/ut_lib/vt.unittest.vcxproj | 19 - src/renderer/vt/vt-renderer-common.vcxitems | 29 - src/renderer/vt/vtrenderer.hpp | 246 - src/renderer/wddmcon/WddmConRenderer.cpp | 6 - src/renderer/wddmcon/WddmConRenderer.hpp | 1 - src/server/IoDispatchers.cpp | 12 +- src/terminal/adapter/ITerminalApi.hpp | 1 - src/terminal/adapter/InteractDispatch.cpp | 6 +- src/terminal/adapter/adaptDispatch.cpp | 304 +- src/terminal/adapter/adaptDispatch.hpp | 6 +- .../adapter/ut_adapter/adapterTest.cpp | 8 +- .../parser/OutputStateMachineEngine.cpp | 65 +- .../parser/OutputStateMachineEngine.hpp | 12 +- src/terminal/parser/ft_fuzzer/sources | 2 - src/terminal/parser/stateMachine.cpp | 13 +- src/terminal/parser/stateMachine.hpp | 19 +- src/til/ut_til/BitmapTests.cpp | 1095 ---- src/til/ut_til/sources | 1 - src/til/ut_til/til.unit.tests.vcxproj | 2 - src/til/ut_til/til.unit.tests.vcxproj.filters | 4 - src/winconpty/winconpty.cpp | 4 +- src/winconpty/winconpty.h | 3 - 125 files changed, 1974 insertions(+), 17468 deletions(-) delete mode 100644 oss/dynamic_bitset/LICENSE delete mode 100644 oss/dynamic_bitset/MAINTAINER_README.md delete mode 100644 oss/dynamic_bitset/cgmanifest.json delete mode 100644 oss/dynamic_bitset/dynamic_bitset.hpp delete mode 100644 oss/libpopcnt/LICENSE delete mode 100644 oss/libpopcnt/MAINTAINER_README.md delete mode 100644 oss/libpopcnt/cgmanifest.json delete mode 100644 oss/libpopcnt/libpopcnt.h delete mode 100644 src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp delete mode 100644 src/host/ut_host/ConptyOutputTests.cpp delete mode 100644 src/host/ut_host/VtRendererTests.cpp delete mode 100644 src/inc/til/bitmap.h delete mode 100644 src/renderer/vt/VtSequences.cpp delete mode 100644 src/renderer/vt/Xterm256Engine.cpp delete mode 100644 src/renderer/vt/Xterm256Engine.hpp delete mode 100644 src/renderer/vt/XtermEngine.cpp delete mode 100644 src/renderer/vt/XtermEngine.hpp delete mode 100644 src/renderer/vt/dirs delete mode 100644 src/renderer/vt/invalidate.cpp delete mode 100644 src/renderer/vt/lib/sources delete mode 100644 src/renderer/vt/lib/sources.dep delete mode 100644 src/renderer/vt/lib/vt.vcxproj delete mode 100644 src/renderer/vt/lib/vt.vcxproj.filters delete mode 100644 src/renderer/vt/math.cpp delete mode 100644 src/renderer/vt/paint.cpp delete mode 100644 src/renderer/vt/precomp.cpp delete mode 100644 src/renderer/vt/precomp.h delete mode 100644 src/renderer/vt/sources.inc delete mode 100644 src/renderer/vt/state.cpp delete mode 100644 src/renderer/vt/tracing.cpp delete mode 100644 src/renderer/vt/tracing.hpp delete mode 100644 src/renderer/vt/ut_lib/sources delete mode 100644 src/renderer/vt/ut_lib/sources.dep delete mode 100644 src/renderer/vt/ut_lib/vt.unittest.vcxproj delete mode 100644 src/renderer/vt/vt-renderer-common.vcxitems delete mode 100644 src/renderer/vt/vtrenderer.hpp delete mode 100644 src/til/ut_til/BitmapTests.cpp diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 839fcbe76b2..3a760315415 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -65,8 +65,8 @@ GETTEXTLENGTH Hashtable HIGHCONTRASTON HIGHCONTRASTW -hinternet HIGHQUALITYSCALE +hinternet HINTERNET hotkeys href @@ -155,6 +155,7 @@ NOTIFYBYPOS NOTIFYICON NOTIFYICONDATA ntprivapi +NTSYSCALLAPI numr oaidl ocidl diff --git a/.github/actions/spelling/expect/alphabet.txt b/.github/actions/spelling/expect/alphabet.txt index 6a2ee3bb519..ccd019fb734 100644 --- a/.github/actions/spelling/expect/alphabet.txt +++ b/.github/actions/spelling/expect/alphabet.txt @@ -1,20 +1,15 @@ -AAAAA -AAAAAAAAAAAAA AAAAAABBBBBBCCC AAAAABBBBBBCCC abcd abcd ABCDEFGHIJ abcdefghijk -ABCDEFGHIJKLMNO -abcdefghijklmnop ABCDEFGHIJKLMNOPQRS ABCDEFGHIJKLMNOPQRST ABCG ABE abf BBBBB -BBBBBBBB BBBBBCCC BBBBCCCCC BBGGRR @@ -29,10 +24,8 @@ QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQ QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ QQQQQQQQQQABCDEFGHIJPQRST QQQQQQQQQQABCDEFGHIJPQRSTQQQQQQQQQQ -qrstuvwxyz qwerty qwertyuiopasdfg -YYYYYYYDDDDDDDDDDD ZAAZZ ZABBZ ZBAZZ diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index fde8b098ae3..fd80a86e42d 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -17,7 +17,6 @@ ADDALIAS ADDREF ADDSTRING ADDTOOL -AFew AFill AFX AHelper @@ -66,7 +65,6 @@ ARRAYSIZE ARROWKEYS asan ASBSET -asdfghjkl ASetting ASingle ASYNCDONTCARE @@ -125,6 +123,7 @@ BKCOLOR BKGND Bksp Blt +blu BLUESCROLL bmi BODGY @@ -145,7 +144,6 @@ buffersize buflen buildtransitive buildsystems -burriter BValue bytebuffer cac @@ -210,7 +208,6 @@ cmw CNL cnn Codeflow -codenav codepages codepath coinit @@ -362,6 +359,7 @@ DBGFONTS DBGOUTPUT dbh dblclk +Dcd DColor DCOLORVALUE dcommon @@ -379,7 +377,7 @@ DECALN DECANM DECARM DECAUPSS -DECAWM +decawm DECBI DECBKM DECCARA @@ -421,6 +419,7 @@ DECPCCM DECPCTERM DECPS DECRARA +decrc DECRC DECREQTPARM DECRLM @@ -436,6 +435,7 @@ DECRSPS decrst DECSACE DECSASD +decsc DECSC DECSCA DECSCNM @@ -475,7 +475,6 @@ DEFPUSHBUTTON defterm DELAYLOAD DELETEONRELEASE -Delt depersist deprioritized deserializers @@ -556,7 +555,6 @@ Efast efghijklmn EHsc EINS -EJO ELEMENTNOTAVAILABLE EMPTYBOX enabledelayedexpansion @@ -626,7 +624,6 @@ FINDDOWN FINDREGEX FINDSTRINGEXACT FINDUP -FIter FITZPATRICK FIXEDFILEINFO Flg @@ -723,6 +720,7 @@ GETWHEELSCROLLLINES Gfun gfx GGI +GHgh GHIJK GHIJKL gitcheckin @@ -948,7 +946,6 @@ LCONTROL LCTRL lcx LEFTALIGN -libpopcnt libsancov libtickit licate @@ -1046,7 +1043,6 @@ MAPBITMAP MAPVIRTUALKEY MAPVK MAXDIMENSTRING -maxing MAXSHORT maxval maxversiontested @@ -1506,7 +1502,6 @@ REGSTR RELBINPATH remoting renamer -renderengine rendersize reparented reparenting @@ -1847,7 +1842,6 @@ Trd TREX triaged triaging -Tribool TRIMZEROHEADINGS trx tsa @@ -1941,7 +1935,6 @@ uxtheme Vanara vararg vclib -vcprintf vcxitems vectorize VERCTRL @@ -1985,7 +1978,6 @@ vtio vtmode vtpipeterm vtpt -vtrenderer VTRGB VTRGBTo vtseq @@ -2007,6 +1999,7 @@ wcswidth wddm wddmcon WDDMCONSOLECONTEXT +WDK wdm webpage websites @@ -2074,7 +2067,6 @@ Winperf WInplace winres winrt -wintelnet winternl winuser winuserp @@ -2175,7 +2167,6 @@ XTWINOPS xunit xutr XVIRTUALSCREEN -XWalk yact YCast YCENTER @@ -2184,7 +2175,6 @@ YLimit YPan YSubstantial YVIRTUALSCREEN -YWalk Zab zabcd Zabcdefghijklmn @@ -2192,6 +2182,5 @@ Zabcdefghijklmnopqrstuvwxyz ZCmd ZCtrl ZWJs -zxcvbnm ZYXWVU ZYXWVUTd diff --git a/NOTICE.md b/NOTICE.md index 091060db2cd..c08d041f8e2 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -84,71 +84,6 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ``` -## kimwalisch/libpopcnt - -**Source**: [https://github.com/kimwalisch/libpopcnt](https://github.com/kimwalisch/libpopcnt) - -### License - -``` -BSD 2-Clause License - -Copyright (c) 2016 - 2019, Kim Walisch -Copyright (c) 2016 - 2019, Wojciech Muła - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -``` - -## dynamic_bitset - -**Source**: [https://github.com/pinam45/dynamic_bitset](https://github.com/pinam45/dynamic_bitset) - -### License - -``` -MIT License - -Copyright (c) 2019 Maxime Pinard - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -``` - ## \{fmt\} **Source**: [https://github.com/fmtlib/fmt](https://github.com/fmtlib/fmt) diff --git a/OpenConsole.sln b/OpenConsole.sln index a6b0afa9453..6d1e8aefba2 100644 --- a/OpenConsole.sln +++ b/OpenConsole.sln @@ -131,7 +131,6 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InteractivityWin32", "src\interactivity\win32\lib\win32.LIB.vcxproj", "{06EC74CB-9A12-429C-B551-8532EC964726}" ProjectSection(ProjectDependencies) = postProject {1C959542-BAC2-4E55-9A6D-13251914CBB9} = {1C959542-BAC2-4E55-9A6D-13251914CBB9} - {990F2657-8580-4828-943F-5DD657D11842} = {990F2657-8580-4828-943F-5DD657D11842} {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F} = {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F} EndProjectSection EndProject @@ -140,14 +139,9 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InteractivityBase", "src\interactivity\base\lib\InteractivityBase.vcxproj", "{06EC74CB-9A12-429C-B551-8562EC964846}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Interactivity.Win32.Tests.Unit", "src\interactivity\win32\ut_interactivity_win32\Interactivity.Win32.UnitTests.vcxproj", "{D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}" - ProjectSection(ProjectDependencies) = postProject - {990F2657-8580-4828-943F-5DD657D11842} = {990F2657-8580-4828-943F-5DD657D11842} - EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CloseTest", "src\tools\closetest\CloseTest.vcxproj", "{C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RendererVt", "src\renderer\vt\lib\vt.vcxproj", "{990F2657-8580-4828-943F-5DD657D11842}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VtPipeTerm", "src\tools\vtpipeterm\VtPipeTerm.vcxproj", "{814DBDDE-894E-4327-A6E1-740504850098}" ProjectSection(ProjectDependencies) = postProject {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B} = {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B} @@ -157,8 +151,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ConEchoKey", "src\tools\ech EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Types", "src\types\lib\types.vcxproj", "{18D09A24-8240-42D6-8CB6-236EEE820263}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RendererVt.unittest", "src\renderer\vt\ut_lib\vt.unittest.vcxproj", "{990F2657-8580-4828-943F-5DD657D11843}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BufferOut", "src\buffer\out\lib\bufferout.vcxproj", "{0CF235BD-2DA0-407E-90EE-C467E8BBC714}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalConnection", "src\cascadia\TerminalConnection\TerminalConnection.vcxproj", "{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}" @@ -1117,29 +1109,6 @@ Global {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Release|x64.Build.0 = Release|x64 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Release|x86.ActiveCfg = Release|Win32 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Release|x86.Build.0 = Release|Win32 - {990F2657-8580-4828-943F-5DD657D11842}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {990F2657-8580-4828-943F-5DD657D11842}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {990F2657-8580-4828-943F-5DD657D11842}.AuditMode|x64.ActiveCfg = Release|x64 - {990F2657-8580-4828-943F-5DD657D11842}.AuditMode|x86.ActiveCfg = Release|Win32 - {990F2657-8580-4828-943F-5DD657D11842}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {990F2657-8580-4828-943F-5DD657D11842}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {990F2657-8580-4828-943F-5DD657D11842}.Debug|ARM64.Build.0 = Debug|ARM64 - {990F2657-8580-4828-943F-5DD657D11842}.Debug|x64.ActiveCfg = Debug|x64 - {990F2657-8580-4828-943F-5DD657D11842}.Debug|x64.Build.0 = Debug|x64 - {990F2657-8580-4828-943F-5DD657D11842}.Debug|x86.ActiveCfg = Debug|Win32 - {990F2657-8580-4828-943F-5DD657D11842}.Debug|x86.Build.0 = Debug|Win32 - {990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 - {990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 - {990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|x64.Build.0 = Fuzzing|x64 - {990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 - {990F2657-8580-4828-943F-5DD657D11842}.Release|Any CPU.ActiveCfg = Release|Win32 - {990F2657-8580-4828-943F-5DD657D11842}.Release|ARM64.ActiveCfg = Release|ARM64 - {990F2657-8580-4828-943F-5DD657D11842}.Release|ARM64.Build.0 = Release|ARM64 - {990F2657-8580-4828-943F-5DD657D11842}.Release|x64.ActiveCfg = Release|x64 - {990F2657-8580-4828-943F-5DD657D11842}.Release|x64.Build.0 = Release|x64 - {990F2657-8580-4828-943F-5DD657D11842}.Release|x86.ActiveCfg = Release|Win32 - {990F2657-8580-4828-943F-5DD657D11842}.Release|x86.Build.0 = Release|Win32 {814DBDDE-894E-4327-A6E1-740504850098}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 {814DBDDE-894E-4327-A6E1-740504850098}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {814DBDDE-894E-4327-A6E1-740504850098}.AuditMode|x64.ActiveCfg = Release|x64 @@ -1210,28 +1179,6 @@ Global {18D09A24-8240-42D6-8CB6-236EEE820263}.Release|x64.Build.0 = Release|x64 {18D09A24-8240-42D6-8CB6-236EEE820263}.Release|x86.ActiveCfg = Release|Win32 {18D09A24-8240-42D6-8CB6-236EEE820263}.Release|x86.Build.0 = Release|Win32 - {990F2657-8580-4828-943F-5DD657D11843}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {990F2657-8580-4828-943F-5DD657D11843}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {990F2657-8580-4828-943F-5DD657D11843}.AuditMode|x64.ActiveCfg = Release|x64 - {990F2657-8580-4828-943F-5DD657D11843}.AuditMode|x86.ActiveCfg = Release|Win32 - {990F2657-8580-4828-943F-5DD657D11843}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {990F2657-8580-4828-943F-5DD657D11843}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {990F2657-8580-4828-943F-5DD657D11843}.Debug|ARM64.Build.0 = Debug|ARM64 - {990F2657-8580-4828-943F-5DD657D11843}.Debug|x64.ActiveCfg = Debug|x64 - {990F2657-8580-4828-943F-5DD657D11843}.Debug|x64.Build.0 = Debug|x64 - {990F2657-8580-4828-943F-5DD657D11843}.Debug|x86.ActiveCfg = Debug|Win32 - {990F2657-8580-4828-943F-5DD657D11843}.Debug|x86.Build.0 = Debug|Win32 - {990F2657-8580-4828-943F-5DD657D11843}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {990F2657-8580-4828-943F-5DD657D11843}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 - {990F2657-8580-4828-943F-5DD657D11843}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 - {990F2657-8580-4828-943F-5DD657D11843}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 - {990F2657-8580-4828-943F-5DD657D11843}.Release|Any CPU.ActiveCfg = Release|Win32 - {990F2657-8580-4828-943F-5DD657D11843}.Release|ARM64.ActiveCfg = Release|ARM64 - {990F2657-8580-4828-943F-5DD657D11843}.Release|ARM64.Build.0 = Release|ARM64 - {990F2657-8580-4828-943F-5DD657D11843}.Release|x64.ActiveCfg = Release|x64 - {990F2657-8580-4828-943F-5DD657D11843}.Release|x64.Build.0 = Release|x64 - {990F2657-8580-4828-943F-5DD657D11843}.Release|x86.ActiveCfg = Release|Win32 - {990F2657-8580-4828-943F-5DD657D11843}.Release|x86.Build.0 = Release|Win32 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 @@ -2444,11 +2391,9 @@ Global {06EC74CB-9A12-429C-B551-8562EC964846} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB} {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB} {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB} = {A10C4720-DCA4-4640-9749-67F4314F527C} - {990F2657-8580-4828-943F-5DD657D11842} = {05500DEF-2294-41E3-AF9A-24E580B82836} {814DBDDE-894E-4327-A6E1-740504850098} = {A10C4720-DCA4-4640-9749-67F4314F527C} {814CBEEE-894E-4327-A6E1-740504850098} = {A10C4720-DCA4-4640-9749-67F4314F527C} {18D09A24-8240-42D6-8CB6-236EEE820263} = {89CDCC5C-9F53-4054-97A4-639D99F169CD} - {990F2657-8580-4828-943F-5DD657D11843} = {05500DEF-2294-41E3-AF9A-24E580B82836} {0CF235BD-2DA0-407E-90EE-C467E8BBC714} = {1E4A062E-293B-4817-B20D-BF16B979E350} {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} = {59840756-302F-44DF-AA47-441A9D673202} {CA5CAD1A-ABCD-429C-B551-8562EC954746} = {9921CA0A-320C-4460-8623-3A3196E7F4CB} diff --git a/oss/dynamic_bitset/LICENSE b/oss/dynamic_bitset/LICENSE deleted file mode 100644 index ee9bc70667d..00000000000 --- a/oss/dynamic_bitset/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Maxime Pinard - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/oss/dynamic_bitset/MAINTAINER_README.md b/oss/dynamic_bitset/MAINTAINER_README.md deleted file mode 100644 index 283b1414f09..00000000000 --- a/oss/dynamic_bitset/MAINTAINER_README.md +++ /dev/null @@ -1,17 +0,0 @@ -### Notes for Future Maintainers - -This was originally imported by @miniksa in March 2020. - -The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme. -Please update the provenance information in that file when ingesting an updated version of the dependent library. -That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards. - -## What should be done to update this in the future? - -1. Go to pinam45/dynamic_bitset repository on GitHub. -2. Take the entire contents of the include directory wholesale and drop it in the root directory here. -3. Don't change anything about it. -4. Validate that the license in the root of the repository didn't change and update it if so. It is sitting in the same directory as this readme. - If it changed dramatically, ensure that it is still compatible with our license scheme. Also update the NOTICE file in the root of our repository to declare the third-party usage. -5. Submit the pull. - diff --git a/oss/dynamic_bitset/cgmanifest.json b/oss/dynamic_bitset/cgmanifest.json deleted file mode 100644 index 32a19e36327..00000000000 --- a/oss/dynamic_bitset/cgmanifest.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/component-detection-manifest.json", - "Registrations": [ - { - "component": { - "type": "git", - "git": { - "repositoryUrl": "https://github.com/pinam45/dynamic_bitset", - "commitHash": "00f2d066ce9deebf28b006636150e5a882beb83f" - } - } - } - ], - "Version": 1 -} diff --git a/oss/dynamic_bitset/dynamic_bitset.hpp b/oss/dynamic_bitset/dynamic_bitset.hpp deleted file mode 100644 index 8a0e385fb47..00000000000 --- a/oss/dynamic_bitset/dynamic_bitset.hpp +++ /dev/null @@ -1,1944 +0,0 @@ -// -// Copyright (c) 2019 Maxime Pinard -// -// Distributed under the MIT license -// See accompanying file LICENSE or copy at -// https://opensource.org/licenses/MIT -// -#ifndef DYNAMIC_BITSET_DYNAMIC_BITSET_HPP -#define DYNAMIC_BITSET_DYNAMIC_BITSET_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef DYNAMIC_BITSET_NO_LIBPOPCNT -# if __has_include() -# include -# define DYNAMIC_BITSET_USE_LIBPOPCNT -# endif -#endif - -#if defined(__clang__) -# define DYNAMIC_BITSET_CLANG -# ifdef __has_builtin -# if __has_builtin(__builtin_popcount) && __has_builtin(__builtin_popcountl) \ - && __has_builtin(__builtin_popcountll) -# define DYNAMIC_BITSET_CLANG_builtin_popcount -# endif -# if __has_builtin(__builtin_ctz) && __has_builtin(__builtin_ctzl) \ - && __has_builtin(__builtin_ctzll) -# define DYNAMIC_BITSET_CLANG_builtin_ctz -# endif -# endif -#elif defined(__GNUC__) -# define DYNAMIC_BITSET_GCC -#elif defined(_MSC_VER) -# define DYNAMIC_BITSET_MSVC -# include -# pragma intrinsic(_BitScanForward) -# if defined(_M_X64) || defined(_M_ARM64) -# define DYNAMIC_BITSET_MSVC_64 -# pragma intrinsic(_BitScanForward64) -# else -# endif -#endif - -template> -class dynamic_bitset -{ - static_assert(std::is_unsigned::value, "Block is not an unsigned integral type"); - -public: - typedef size_t size_type; - typedef Block block_type; - typedef Allocator allocator_type; - - static constexpr size_type bits_per_block = CHAR_BIT * sizeof(block_type); - static constexpr size_type npos = std::numeric_limits::max(); - - class reference - { - public: - constexpr reference(dynamic_bitset& bitset, size_type bit_pos); - constexpr reference(const reference&) noexcept = default; - constexpr reference(reference&&) noexcept = default; - ~reference() noexcept = default; - - constexpr reference& operator=(bool v); - constexpr reference& operator=(const reference& rhs); - constexpr reference& operator=(reference&& rhs) noexcept; - - constexpr reference& operator&=(bool v); - constexpr reference& operator|=(bool v); - constexpr reference& operator^=(bool v); - constexpr reference& operator-=(bool v); - - [[nodiscard]] constexpr bool operator~() const; - [[nodiscard]] constexpr operator bool() const; - constexpr void operator&() = delete; - - constexpr reference& set(); - constexpr reference& reset(); - constexpr reference& flip(); - constexpr reference& assign(bool v); - - private: - block_type& m_block; - block_type m_mask; - }; - typedef bool const_reference; - - // copy/move constructors = default - constexpr dynamic_bitset(const dynamic_bitset& other) = default; - constexpr dynamic_bitset(dynamic_bitset&& other) noexcept = default; - constexpr dynamic_bitset& operator=( - const dynamic_bitset& other) = default; - constexpr dynamic_bitset& operator=( - dynamic_bitset&& other) noexcept = default; - - // other constructors - constexpr explicit dynamic_bitset(const allocator_type& allocator = allocator_type()); - constexpr explicit dynamic_bitset(size_type nbits, - unsigned long long init_val = 0, - const allocator_type& allocator = allocator_type()); - constexpr dynamic_bitset(std::initializer_list init_vals, - const allocator_type& allocator = allocator_type()); - - // string constructors - template - constexpr explicit dynamic_bitset( - std::basic_string_view<_CharT, _Traits> str, - typename std::basic_string_view<_CharT, _Traits>::size_type pos = 0, - typename std::basic_string_view<_CharT, _Traits>::size_type n = - std::basic_string_view<_CharT, _Traits>::npos, - _CharT zero = _CharT('0'), - _CharT one = _CharT('1'), - const allocator_type& allocator = allocator_type()); - - template - constexpr explicit dynamic_bitset( - const std::basic_string<_CharT, _Traits, _Alloc>& str, - typename std::basic_string<_CharT, _Traits, _Alloc>::size_type pos = 0, - typename std::basic_string<_CharT, _Traits, _Alloc>::size_type n = - std::basic_string<_CharT, _Traits, _Alloc>::npos, - _CharT zero = _CharT('0'), - _CharT one = _CharT('1'), - const allocator_type& allocator = allocator_type()); - - template - constexpr explicit dynamic_bitset( - const _CharT* str, - typename std::basic_string<_CharT>::size_type pos = 0, - typename std::basic_string<_CharT>::size_type n = std::basic_string<_CharT>::npos, - _CharT zero = _CharT('0'), - _CharT one = _CharT('1'), - const allocator_type& allocator = allocator_type()); - - // destructor - ~dynamic_bitset() noexcept = default; - - // size changing operations - constexpr void resize(size_type nbits, bool value = false); - constexpr void clear(); - constexpr void push_back(bool value); - constexpr void pop_back(); - constexpr void append(block_type block); - constexpr void append(std::initializer_list blocks); - template - constexpr void append(BlockInputIterator first, BlockInputIterator last); - - // bitset operations - constexpr dynamic_bitset& operator&=( - const dynamic_bitset& rhs); - constexpr dynamic_bitset& operator|=( - const dynamic_bitset& rhs); - constexpr dynamic_bitset& operator^=( - const dynamic_bitset& rhs); - constexpr dynamic_bitset& operator-=( - const dynamic_bitset& rhs); - constexpr dynamic_bitset& operator<<=(size_type shift); - constexpr dynamic_bitset& operator>>=(size_type shift); - [[nodiscard]] constexpr dynamic_bitset operator<<(size_type shift) const; - [[nodiscard]] constexpr dynamic_bitset operator>>(size_type shift) const; - [[nodiscard]] constexpr dynamic_bitset operator~() const; - - // bit operations - constexpr dynamic_bitset& set(size_type pos, size_type len, bool value); - constexpr dynamic_bitset& set(size_type pos, bool value = true); - constexpr dynamic_bitset& set(); - constexpr dynamic_bitset& reset(size_type pos, size_type len); - constexpr dynamic_bitset& reset(size_type pos); - constexpr dynamic_bitset& reset(); - constexpr dynamic_bitset& flip(size_type pos, size_type len); - constexpr dynamic_bitset& flip(size_type pos); - constexpr dynamic_bitset& flip(); - [[nodiscard]] constexpr bool test(size_type pos) const; - [[nodiscard]] constexpr bool test_set(size_type pos, bool value = true); - [[nodiscard]] constexpr bool all() const; - [[nodiscard]] constexpr bool any() const; - [[nodiscard]] constexpr bool none() const; - [[nodiscard]] constexpr size_type count() const noexcept; - - // subscript operators - [[nodiscard]] constexpr reference operator[](size_type pos); - [[nodiscard]] constexpr const_reference operator[](size_type pos) const; - - //container-like functions - [[nodiscard]] constexpr size_type size() const noexcept; - [[nodiscard]] constexpr size_type num_blocks() const noexcept; - [[nodiscard]] constexpr bool empty() const noexcept; - [[nodiscard]] constexpr size_type capacity() const noexcept; - constexpr void reserve(size_type num_bits); - constexpr void shrink_to_fit(); - - // subsets - [[nodiscard]] constexpr bool is_subset_of(const dynamic_bitset& bitset) const; - [[nodiscard]] constexpr bool is_proper_subset_of( - const dynamic_bitset& bitset) const; - [[nodiscard]] constexpr bool intersects(const dynamic_bitset& bitset) const; - - // find functions - [[nodiscard]] constexpr size_type find_first() const; - [[nodiscard]] constexpr size_type find_next(size_type prev) const; - - // utils - constexpr void swap(dynamic_bitset& other); - [[nodiscard]] constexpr allocator_type get_allocator() const; - template, - typename _Alloc = std::allocator<_CharT>> - [[nodiscard]] constexpr std::basic_string<_CharT, _Traits, _Alloc> to_string( - _CharT zero = _CharT('0'), - _CharT one = _CharT('1')) const; - template - constexpr void iterate_bits_on(Function&& function, Parameters&&... parameters) const; - - // friend external binary operators - template - friend constexpr bool operator==(const dynamic_bitset& lhs, - const dynamic_bitset& rhs); - template - friend constexpr bool operator<(const dynamic_bitset& lhs, - const dynamic_bitset& rhs); - -private: - template - struct dependent_false : public std::false_type - { - }; - - std::vector m_blocks; - size_type m_bits_number; - - static constexpr block_type zero_block = block_type(0); - static constexpr block_type one_block = block_type(~zero_block); - static constexpr size_type block_last_bit_index = bits_per_block - 1; - - static constexpr size_type blocks_required(size_type nbits) noexcept; - - static constexpr size_type block_index(size_type pos) noexcept; - static constexpr size_type bit_index(size_type pos) noexcept; - - static constexpr block_type bit_mask(size_type pos) noexcept; - static constexpr block_type bit_mask(size_type first, size_type last) noexcept; - - static constexpr void set_block_bits(block_type& block, - size_type first, - size_type last, - bool val = true) noexcept; - static constexpr void flip_block_bits(block_type& block, - size_type first, - size_type last) noexcept; - - static constexpr size_type block_count(const block_type& block) noexcept; - static constexpr size_type block_count(const block_type& block, size_type nbits) noexcept; - - static constexpr size_type first_on(const block_type& block) noexcept; - - template - constexpr void init_from_string(std::basic_string_view<_CharT, _Traits> str, - typename std::basic_string_view<_CharT, _Traits>::size_type pos, - typename std::basic_string_view<_CharT, _Traits>::size_type n, - _CharT zero, - _CharT one); - - constexpr block_type& get_block(size_type pos); - constexpr const block_type& get_block(size_type pos) const; - constexpr block_type& last_block(); - constexpr block_type last_block() const; - - // used bits in the last block - constexpr size_type extra_bits_number() const noexcept; - // unused bits in the last block - constexpr size_type unused_bits_number() const noexcept; - - template - constexpr void apply(const dynamic_bitset& other, BinaryOperation binary_op); - template - constexpr void apply(UnaryOperation unary_op); - constexpr void apply_left_shift(size_type shift); - constexpr void apply_right_shift(size_type shift); - - // reset unused bits to 0 - constexpr void sanitize(); - - // check functions used in asserts - constexpr bool check_unused_bits() const noexcept; - constexpr bool check_size() const noexcept; - constexpr bool check_consistency() const noexcept; -}; - -// Deduction guideline for expressions like "dynamic_bitset a(32);" with an integral type as parameter -// to use the constructor with the initial size instead of the constructor with the allocator. -template>> -dynamic_bitset(integral_type)->dynamic_bitset<>; - -//================================================================================================= -// dynamic_bitset external functions declarations -//================================================================================================= - -template -constexpr bool operator!=(const dynamic_bitset& lhs, - const dynamic_bitset& rhs); -template -constexpr bool operator<=(const dynamic_bitset& lhs, - const dynamic_bitset& rhs); -template -constexpr bool operator>(const dynamic_bitset& lhs, - const dynamic_bitset& rhs); -template -constexpr bool operator>=(const dynamic_bitset& lhs, - const dynamic_bitset& rhs); - -template -constexpr dynamic_bitset operator&(const dynamic_bitset& lhs, - const dynamic_bitset& rhs); -template -constexpr dynamic_bitset operator|(const dynamic_bitset& lhs, - const dynamic_bitset& rhs); -template -constexpr dynamic_bitset operator^(const dynamic_bitset& lhs, - const dynamic_bitset& rhs); -template -constexpr dynamic_bitset operator-(const dynamic_bitset& lhs, - const dynamic_bitset& rhs); - -template -constexpr std::basic_ostream<_CharT, _Traits>& operator<<( - std::basic_ostream<_CharT, _Traits>& os, - const dynamic_bitset& bitset); - -template -constexpr std::basic_istream<_CharT, _Traits>& operator>>(std::basic_istream<_CharT, _Traits>& is, - dynamic_bitset& bitset); - -template -constexpr void swap(dynamic_bitset& bitset1, - dynamic_bitset& bitset2); - -//================================================================================================= -// dynamic_bitset::reference functions implementations -//================================================================================================= - -template -constexpr dynamic_bitset::reference::reference( - dynamic_bitset& bitset, - size_type bit_pos) - : m_block(bitset.get_block(bit_pos)), m_mask(dynamic_bitset::bit_mask(bit_pos)) -{ -} - -template -constexpr typename dynamic_bitset::reference& dynamic_bitset:: - reference::operator=(bool v) -{ - assign(v); - return *this; -} - -template -constexpr typename dynamic_bitset::reference& dynamic_bitset:: - reference::operator=(const dynamic_bitset::reference& rhs) -{ - assign(rhs); - return *this; -} - -template -constexpr typename dynamic_bitset::reference& dynamic_bitset:: - reference::operator=(dynamic_bitset::reference&& rhs) noexcept -{ - assign(rhs); - return *this; -} - -template -constexpr typename dynamic_bitset::reference& dynamic_bitset:: - reference::operator&=(bool v) -{ - if(!v) - { - reset(); - } - return *this; -} - -template -constexpr typename dynamic_bitset::reference& dynamic_bitset:: - reference::operator|=(bool v) -{ - if(v) - { - set(); - } - return *this; -} - -template -constexpr typename dynamic_bitset::reference& dynamic_bitset:: - reference::operator^=(bool v) -{ - if(v) - { - flip(); - } - return *this; -} - -template -constexpr typename dynamic_bitset::reference& dynamic_bitset:: - reference::operator-=(bool v) -{ - if(v) - { - reset(); - } - return *this; -} - -template -constexpr bool dynamic_bitset::reference::operator~() const -{ - return (m_block & m_mask) == zero_block; -} - -template -constexpr dynamic_bitset::reference::operator bool() const -{ - return (m_block & m_mask) != zero_block; -} - -template -constexpr typename dynamic_bitset::reference& dynamic_bitset:: - reference::set() -{ - m_block |= m_mask; - return *this; -} - -template -constexpr typename dynamic_bitset::reference& dynamic_bitset:: - reference::reset() -{ - m_block &= ~m_mask; - return *this; -} - -template -constexpr typename dynamic_bitset::reference& dynamic_bitset:: - reference::flip() -{ - m_block ^= m_mask; - return *this; -} - -template -constexpr typename dynamic_bitset::reference& dynamic_bitset:: - reference::assign(bool v) -{ - if(v) - { - set(); - } - else - { - reset(); - } - return *this; -} - -//================================================================================================= -// dynamic_bitset public functions implementations -//================================================================================================= - -template -constexpr dynamic_bitset::dynamic_bitset(const allocator_type& allocator) - : m_blocks(allocator), m_bits_number(0) -{ -} - -template -constexpr dynamic_bitset::dynamic_bitset(size_type nbits, - unsigned long long init_val, - const allocator_type& allocator) - : m_blocks(blocks_required(nbits), allocator), m_bits_number(nbits) -{ - if(nbits == 0 || init_val == 0) - { - return; - } - - constexpr size_type init_val_required_blocks = sizeof(unsigned long long) / sizeof(block_type); - if constexpr(init_val_required_blocks == 1) - { - m_blocks[0] = init_val; - } - else - { - const unsigned long long block_mask = static_cast(one_block); - const size_type blocks_to_init = std::min(m_blocks.size(), init_val_required_blocks); - for(size_type i = 0; i < blocks_to_init; ++i) - { - m_blocks[i] = block_type((init_val >> (i * bits_per_block) & block_mask)); - } - } - sanitize(); -} - -template -constexpr dynamic_bitset::dynamic_bitset( - std::initializer_list init_vals, - const allocator_type& allocator) - : m_blocks(allocator), m_bits_number(0) -{ - append(init_vals); -} - -template -template -constexpr dynamic_bitset::dynamic_bitset( - std::basic_string_view<_CharT, _Traits> str, - typename std::basic_string_view<_CharT, _Traits>::size_type pos, - typename std::basic_string_view<_CharT, _Traits>::size_type n, - _CharT zero, - _CharT one, - const allocator_type& allocator) - : m_blocks(allocator), m_bits_number(0) -{ - assert(pos < str.size()); - init_from_string(str, pos, n, zero, one); -} - -template -template -constexpr dynamic_bitset::dynamic_bitset( - const std::basic_string<_CharT, _Traits, _Alloc>& str, - typename std::basic_string<_CharT, _Traits, _Alloc>::size_type pos, - typename std::basic_string<_CharT, _Traits, _Alloc>::size_type n, - _CharT zero, - _CharT one, - const allocator_type& allocator) - : m_blocks(allocator), m_bits_number(0) -{ - assert(pos < str.size()); - init_from_string(std::basic_string_view<_CharT, _Traits>(str), pos, n, zero, one); -} - -template -template -constexpr dynamic_bitset::dynamic_bitset( - const _CharT* str, - typename std::basic_string<_CharT>::size_type pos, - typename std::basic_string<_CharT>::size_type n, - _CharT zero, - _CharT one, - const allocator_type& allocator) - : m_blocks(allocator), m_bits_number(0) -{ - init_from_string(std::basic_string_view<_CharT>(str), pos, n, zero, one); -} - -template -constexpr void dynamic_bitset::resize(size_type nbits, bool value) -{ - if(nbits == m_bits_number) - { - return; - } - - const size_type old_num_blocks = num_blocks(); - const size_type new_num_blocks = blocks_required(nbits); - - const block_type init_value = value ? one_block : zero_block; - if(new_num_blocks != old_num_blocks) - { - m_blocks.resize(new_num_blocks, init_value); - } - - if(value && nbits > m_bits_number && old_num_blocks > 0) - { - // set value of the new bits in the old last block - const size_type extra_bits = extra_bits_number(); - if(extra_bits > 0) - { - m_blocks[old_num_blocks - 1] |= (init_value << extra_bits); - } - } - - m_bits_number = nbits; - sanitize(); - assert(check_consistency()); -} - -template -constexpr void dynamic_bitset::clear() -{ - m_blocks.clear(); - m_bits_number = 0; -} - -template -constexpr void dynamic_bitset::push_back(bool value) -{ - const size_type new_last_bit = m_bits_number++; - if(m_bits_number <= m_blocks.size() * bits_per_block) - { - if(value) - { - set(new_last_bit, value); - } - } - else - { - m_blocks.push_back(block_type(value)); - } - assert(operator[](new_last_bit) == value); - assert(check_consistency()); -} - -template -constexpr void dynamic_bitset::pop_back() -{ - if(empty()) - { - return; - } - - --m_bits_number; - if(m_blocks.size() > blocks_required(m_bits_number)) - { - m_blocks.pop_back(); - // no extra bits: sanitize not required - assert(extra_bits_number() == 0); - } - else - { - sanitize(); - } - assert(check_consistency()); -} - -template -constexpr void dynamic_bitset::append(block_type block) -{ - const size_type extra_bits = extra_bits_number(); - if(extra_bits == 0) - { - m_blocks.push_back(block); - } - else - { - last_block() |= (block << extra_bits); - m_blocks.push_back(block_type(block >> (bits_per_block - extra_bits))); - } - - m_bits_number += bits_per_block; - assert(check_consistency()); -} - -template -constexpr void dynamic_bitset::append(std::initializer_list blocks) -{ - if(blocks.size() == 0) - { - return; - } - - append(std::cbegin(blocks), std::cend(blocks)); -} - -template -template -constexpr void dynamic_bitset::append(BlockInputIterator first, - BlockInputIterator last) -{ - if(first == last) - { - return; - } - - // if random access iterators, std::distance complexity is constant - if constexpr(std::is_same_v< - typename std::iterator_traits::iterator_category, - std::random_access_iterator_tag>) - { - assert(std::distance(first, last) > 0); - m_blocks.reserve(m_blocks.size() + static_cast(std::distance(first, last))); - } - - const size_type extra_bits = extra_bits_number(); - const size_type unused_bits = unused_bits_number(); - if(extra_bits == 0) - { - auto pos = m_blocks.insert(std::end(m_blocks), first, last); - assert(std::distance(pos, std::end(m_blocks)) > 0); - m_bits_number += - static_cast(std::distance(pos, std::end(m_blocks))) * bits_per_block; - } - else - { - last_block() |= (*first << extra_bits); - block_type block = block_type(*first >> unused_bits); - ++first; - while(first != last) - { - block |= (*first << extra_bits); - m_blocks.push_back(block); - m_bits_number += bits_per_block; - block = block_type(*first >> unused_bits); - ++first; - } - m_blocks.push_back(block); - m_bits_number += bits_per_block; - } - - assert(check_consistency()); -} -template -constexpr dynamic_bitset& dynamic_bitset::operator&=( - const dynamic_bitset& rhs) -{ - assert(size() == rhs.size()); - //apply(rhs, std::bit_and()); - for(size_type i = 0; i < m_blocks.size(); ++i) - { - m_blocks[i] &= rhs.m_blocks[i]; - } - return *this; -} - -template -constexpr dynamic_bitset& dynamic_bitset::operator|=( - const dynamic_bitset& rhs) -{ - assert(size() == rhs.size()); - //apply(rhs, std::bit_or()); - for(size_type i = 0; i < m_blocks.size(); ++i) - { - m_blocks[i] |= rhs.m_blocks[i]; - } - return *this; -} - -template -constexpr dynamic_bitset& dynamic_bitset::operator^=( - const dynamic_bitset& rhs) -{ - assert(size() == rhs.size()); - //apply(rhs, std::bit_xor()); - for(size_type i = 0; i < m_blocks.size(); ++i) - { - m_blocks[i] ^= rhs.m_blocks[i]; - } - return *this; -} - -template -constexpr dynamic_bitset& dynamic_bitset::operator-=( - const dynamic_bitset& rhs) -{ - assert(size() == rhs.size()); - //apply(rhs, [](const block_type& x, const block_type& y) { return (x & ~y); }); - for(size_type i = 0; i < m_blocks.size(); ++i) - { - m_blocks[i] &= ~rhs.m_blocks[i]; - } - return *this; -} - -template -constexpr dynamic_bitset& dynamic_bitset::operator<<=( - size_type shift) -{ - if(shift != 0) - { - if(shift >= m_bits_number) - { - reset(); - } - else - { - apply_left_shift(shift); - sanitize(); // unused bits can have changed, reset them to 0 - } - } - return *this; -} - -template -constexpr dynamic_bitset& dynamic_bitset::operator>>=( - size_type shift) -{ - if(shift != 0) - { - if(shift >= m_bits_number) - { - reset(); - } - else - { - apply_right_shift(shift); - } - } - return *this; -} - -template -constexpr dynamic_bitset dynamic_bitset::operator<<( - size_type shift) const -{ - return dynamic_bitset(*this) <<= shift; -} - -template -constexpr dynamic_bitset dynamic_bitset::operator>>( - size_type shift) const -{ - return dynamic_bitset(*this) >>= shift; -} - -template -constexpr dynamic_bitset dynamic_bitset::operator~() const -{ - dynamic_bitset bitset(*this); - bitset.flip(); - return bitset; -} - -template -constexpr dynamic_bitset& dynamic_bitset::set(size_type pos, - size_type len, - bool value) -{ - assert(pos < size()); - assert(pos + len - 1 < size()); - if(len == 0) - { - return *this; - } - - const size_type first_block = block_index(pos); - const size_type last_block = block_index(pos + len - 1); - const size_type first_bit_index = bit_index(pos); - const size_type last_bit_index = bit_index(pos + len - 1); - - if(first_block == last_block) - { - set_block_bits(m_blocks[first_block], first_bit_index, last_bit_index, value); - } - else - { - size_type first_full_block = first_block; - size_type last_full_block = last_block; - - if(first_bit_index != 0) - { - ++first_full_block; // first block is not full - set_block_bits(m_blocks[first_block], first_bit_index, block_last_bit_index, value); - } - - if(last_bit_index != block_last_bit_index) - { - --last_full_block; // last block is not full - set_block_bits(m_blocks[last_block], 0, last_bit_index, value); - } - - const block_type full_block = value ? one_block : zero_block; - for(size_type i = first_full_block; i <= last_full_block; ++i) - { - m_blocks[i] = full_block; - } - } - - return *this; -} - -template -constexpr dynamic_bitset& dynamic_bitset::set(size_type pos, - bool value) -{ - assert(pos < size()); - - if(value) - { - m_blocks[block_index(pos)] |= bit_mask(pos); - } - else - { - m_blocks[block_index(pos)] &= ~bit_mask(pos); - } - - return *this; -} - -template -constexpr dynamic_bitset& dynamic_bitset::set() -{ - std::fill(std::begin(m_blocks), std::end(m_blocks), one_block); - sanitize(); - return *this; -} - -template -constexpr dynamic_bitset& dynamic_bitset::reset(size_type pos, - size_type len) -{ - return set(pos, len, false); -} - -template -constexpr dynamic_bitset& dynamic_bitset::reset(size_type pos) -{ - return set(pos, false); -} - -template -constexpr dynamic_bitset& dynamic_bitset::reset() -{ - std::fill(std::begin(m_blocks), std::end(m_blocks), zero_block); - return *this; -} - -template -constexpr dynamic_bitset& dynamic_bitset::flip(size_type pos, - size_type len) -{ - assert(pos < size()); - assert(pos + len - 1 < size()); - if(len == 0) - { - return *this; - } - - const size_type first_block = block_index(pos); - const size_type last_block = block_index(pos + len - 1); - const size_type first_bit_index = bit_index(pos); - const size_type last_bit_index = bit_index(pos + len - 1); - - if(first_block == last_block) - { - flip_block_bits(m_blocks[first_block], first_bit_index, last_bit_index); - } - else - { - size_type first_full_block = first_block; - size_type last_full_block = last_block; - - if(first_bit_index != 0) - { - ++first_full_block; // first block is not full - flip_block_bits(m_blocks[first_block], first_bit_index, block_last_bit_index); - } - - if(last_bit_index != block_last_bit_index) - { - --last_full_block; // last block is not full - flip_block_bits(m_blocks[last_block], 0, last_bit_index); - } - - for(size_type i = first_full_block; i <= last_full_block; ++i) - { - m_blocks[i] = block_type(~m_blocks[i]); - } - } - - return *this; -} - -template -constexpr dynamic_bitset& dynamic_bitset::flip(size_type pos) -{ - assert(pos < size()); - m_blocks[block_index(pos)] ^= bit_mask(pos); - return *this; -} - -template -constexpr dynamic_bitset& dynamic_bitset::flip() -{ - std::transform( - std::cbegin(m_blocks), std::cend(m_blocks), std::begin(m_blocks), std::bit_not()); - sanitize(); - return *this; -} - -template -constexpr bool dynamic_bitset::test(size_type pos) const -{ - assert(pos < size()); - return (m_blocks[block_index(pos)] & bit_mask(pos)) != zero_block; -} - -template -constexpr bool dynamic_bitset::test_set(size_type pos, bool value) -{ - bool const result = test(pos); - if(result != value) - { - set(pos, value); - } - return result; -} - -template -constexpr bool dynamic_bitset::all() const -{ - if(empty()) - { - return true; - } - - const block_type full_block = one_block; - if(extra_bits_number() == 0) - { - for(const block_type& block: m_blocks) - { - if(block != full_block) - { - return false; - } - } - } - else - { - for(size_type i = 0; i < m_blocks.size() - 1; ++i) - { - if(m_blocks[i] != full_block) - { - return false; - } - } - if(last_block() != (full_block >> unused_bits_number())) - { - return false; - } - } - return true; -} - -template -constexpr bool dynamic_bitset::any() const -{ - for(const block_type& block: m_blocks) - { - if(block != zero_block) - { - return true; - } - } - return false; -} - -template -constexpr bool dynamic_bitset::none() const -{ - return !any(); -} - -template -constexpr typename dynamic_bitset::size_type dynamic_bitset::count() - const noexcept -{ - if(empty()) - { - return 0; - } - -#ifdef DYNAMIC_BITSET_USE_LIBPOPCNT - const size_type count = - static_cast(popcnt(m_blocks.data(), m_blocks.size() * sizeof(block_type))); -#else - size_type count = 0; - - // full blocks - for(size_type i = 0; i < m_blocks.size() - 1; ++i) - { - count += block_count(m_blocks[i]); - } - - // last block - const block_type& block = last_block(); - if(block != zero_block) - { - const size_t extra_bits = extra_bits_number(); - if(extra_bits == 0) - { - count += block_count(block); - } - else - { - count += block_count(block, extra_bits); - } - } -#endif - return count; -} - -template -constexpr - typename dynamic_bitset::reference dynamic_bitset::operator[]( - size_type pos) -{ - assert(pos < size()); - return dynamic_bitset::reference(*this, pos); -} - -template -constexpr typename dynamic_bitset:: - const_reference dynamic_bitset::operator[](size_type pos) const -{ - return test(pos); -} - -template -constexpr typename dynamic_bitset::size_type dynamic_bitset::size() - const noexcept -{ - return m_bits_number; -} - -template -constexpr typename dynamic_bitset::size_type dynamic_bitset:: - num_blocks() const noexcept -{ - return m_blocks.size(); -} - -template -constexpr bool dynamic_bitset::empty() const noexcept -{ - return size() == 0; -} - -template -constexpr typename dynamic_bitset::size_type dynamic_bitset::capacity() - const noexcept -{ - return m_blocks.capacity() * bits_per_block; -} - -template -constexpr void dynamic_bitset::reserve(size_type num_bits) -{ - m_blocks.reserve(blocks_required(num_bits)); -} - -template -constexpr void dynamic_bitset::shrink_to_fit() -{ - m_blocks.shrink_to_fit(); -} - -template -constexpr bool dynamic_bitset::is_subset_of( - const dynamic_bitset& bitset) const -{ - assert(size() == bitset.size()); - for(size_type i = 0; i < m_blocks.size(); ++i) - { - if((m_blocks[i] & ~bitset.m_blocks[i]) != zero_block) - { - return false; - } - } - return true; -} - -template -constexpr bool dynamic_bitset::is_proper_subset_of( - const dynamic_bitset& bitset) const -{ - assert(size() == bitset.size()); - bool is_proper = false; - for(size_type i = 0; i < m_blocks.size(); ++i) - { - const block_type& self_block = m_blocks[i]; - const block_type& other_block = bitset.m_blocks[i]; - - if((self_block & ~other_block) != zero_block) - { - return false; - } - if((~self_block & other_block) != zero_block) - { - is_proper = true; - } - } - return is_proper; -} - -template -constexpr bool dynamic_bitset::intersects( - const dynamic_bitset& bitset) const -{ - const size_type min_blocks_number = std::min(m_blocks.size(), bitset.m_blocks.size()); - for(size_type i = 0; i < min_blocks_number; ++i) - { - if((m_blocks[i] & bitset.m_blocks[i]) != zero_block) - { - return true; - } - } - return false; -} - -template -constexpr typename dynamic_bitset::size_type dynamic_bitset:: - find_first() const -{ - for(size_type i = 0; i < m_blocks.size(); ++i) - { - if(m_blocks[i] != zero_block) - { - return i * bits_per_block + first_on(m_blocks[i]); - } - } - return npos; -} - -template -constexpr typename dynamic_bitset::size_type dynamic_bitset:: - find_next(size_type prev) const -{ - if(empty() || prev >= (size() - 1)) - { - return npos; - } - - const size_type first_bit = prev + 1; - const size_type first_block = block_index(first_bit); - const size_type first_bit_index = bit_index(first_bit); - const block_type first_block_shifted = block_type(m_blocks[first_block] >> first_bit_index); - - if(first_block_shifted != zero_block) - { - return first_bit + first_on(first_block_shifted); - } - else - { - for(size_type i = first_block + 1; i < m_blocks.size(); ++i) - { - if(m_blocks[i] != zero_block) - { - return i * bits_per_block + first_on(m_blocks[i]); - } - } - } - return npos; -} - -template -constexpr void dynamic_bitset::swap(dynamic_bitset& other) -{ - std::swap(m_blocks, other.m_blocks); - std::swap(m_bits_number, other.m_bits_number); -} - -template -constexpr typename dynamic_bitset::allocator_type dynamic_bitset< - Block, - Allocator>::get_allocator() const -{ - return m_blocks.get_allocator(); -} - -template -template -constexpr std::basic_string<_CharT, _Traits, _Alloc> dynamic_bitset::to_string( - _CharT zero, - _CharT one) const -{ - const size_type len = size(); - std::basic_string<_CharT, _Traits, _Alloc> str(len, zero); - for(size_type i_block = 0; i_block < m_blocks.size(); ++i_block) - { - if(m_blocks[i_block] == zero_block) - { - continue; - } - block_type mask = block_type(1); - const size_type limit = - i_block * bits_per_block < len ? len - i_block * bits_per_block : bits_per_block; - for(size_type i_bit = 0; i_bit < limit; ++i_bit) - { - if((m_blocks[i_block] & mask) != zero_block) - { - _Traits::assign(str[len - (i_block * bits_per_block + i_bit + 1)], one); - } - mask <<= 1; - } - } - return str; -} - -template -template -constexpr void dynamic_bitset::iterate_bits_on(Function&& function, - Parameters&&... parameters) const -{ - if constexpr(!std::is_invocable_v) - { - static_assert(dependent_false::value, "Function take invalid arguments"); - // function should take (size_t, parameters...) as arguments - } - - if constexpr(std::is_same_v, void>) - { - size_t i_bit = find_first(); - while(i_bit != npos) - { - std::invoke( - std::forward(function), i_bit, std::forward(parameters)...); - i_bit = find_next(i_bit); - } - } - else if constexpr(std::is_convertible_v, - bool>) - { - size_t i_bit = find_first(); - while(i_bit != npos) - { - if(!std::invoke( - std::forward(function), i_bit, std::forward(parameters)...)) - { - break; - } - i_bit = find_next(i_bit); - } - } - else - { - static_assert(dependent_false::value, "Function have invalid return type"); - // return type should be void, or convertible to bool - } -} - -template -[[nodiscard]] constexpr bool operator==(const dynamic_bitset& lhs, - const dynamic_bitset& rhs) -{ - return (lhs.m_bits_number == rhs.m_bits_number) && (lhs.m_blocks == rhs.m_blocks); -} - -template -[[nodiscard]] constexpr bool operator<(const dynamic_bitset& lhs, - const dynamic_bitset& rhs) -{ - using size_type = typename dynamic_bitset::size_type; - using block_type = typename dynamic_bitset::block_type; - const size_type lhs_size = lhs.size(); - const size_type rhs_size = rhs.size(); - const size_type lhs_blocks_size = lhs.m_blocks.size(); - const size_type rhs_blocks_size = rhs.m_blocks.size(); - - if(lhs_size == rhs_size) - { - // if comparison of two empty bitsets - if(lhs_size == 0) - { - return false; - } - - for(size_type i = lhs_blocks_size - 1; i > 0; --i) - { - if(lhs.m_blocks[i] != rhs.m_blocks[i]) - { - return lhs.m_blocks[i] < rhs.m_blocks[i]; - } - } - return lhs.m_blocks[0] < rhs.m_blocks[0]; - } - - // empty bitset inferior to 0-only bitset - if(lhs_size == 0) - { - return true; - } - if(rhs_size == 0) - { - return false; - } - - const bool rhs_longer = rhs_size > lhs_size; - const dynamic_bitset& longest_bitset = rhs_longer ? rhs : lhs; - const size_type longest_blocks_size = std::max(lhs_blocks_size, rhs_blocks_size); - const size_type shortest_blocks_size = std::min(lhs_blocks_size, rhs_blocks_size); - for(size_type i = longest_blocks_size - 1; i >= shortest_blocks_size; --i) - { - if(longest_bitset.m_blocks[i] != block_type(0)) - { - return rhs_longer; - } - } - - for(size_type i = shortest_blocks_size - 1; i > 0; --i) - { - if(lhs.m_blocks[i] != rhs.m_blocks[i]) - { - return lhs.m_blocks[i] < rhs.m_blocks[i]; - } - } - if(lhs.m_blocks[0] != rhs.m_blocks[0]) - { - return lhs.m_blocks[0] < rhs.m_blocks[0]; - } - return lhs_size < rhs_size; -} - -//================================================================================================= -// dynamic_bitset private functions implementations -//================================================================================================= - -template -constexpr typename dynamic_bitset::size_type dynamic_bitset:: - blocks_required(size_type nbits) noexcept -{ - return nbits / bits_per_block + static_cast(nbits % bits_per_block > 0); -} - -template -constexpr typename dynamic_bitset::size_type dynamic_bitset:: - block_index(size_type pos) noexcept -{ - return pos / bits_per_block; -} - -template -constexpr typename dynamic_bitset::size_type dynamic_bitset:: - bit_index(size_type pos) noexcept -{ - return pos % bits_per_block; -} - -template -constexpr typename dynamic_bitset::block_type dynamic_bitset:: - bit_mask(size_type pos) noexcept -{ - return block_type(block_type(1) << bit_index(pos)); -} - -template -constexpr typename dynamic_bitset::block_type dynamic_bitset:: - bit_mask(size_type first, size_type last) noexcept -{ - first = bit_index(first); - last = bit_index(last); - if(last == (block_last_bit_index)) - { - return block_type(one_block << first); - } - else - { - return block_type(((block_type(1) << (last + 1)) - 1) ^ ((block_type(1) << first) - 1)); - } -} - -template -constexpr void dynamic_bitset::set_block_bits(block_type& block, - size_type first, - size_type last, - bool val) noexcept -{ - if(val) - { - block |= bit_mask(first, last); - } - else - { - block &= ~bit_mask(first, last); - } -} - -template -constexpr void dynamic_bitset::flip_block_bits(block_type& block, - size_type first, - size_type last) noexcept -{ - block ^= bit_mask(first, last); -} - -template -constexpr typename dynamic_bitset::size_type dynamic_bitset:: - block_count(const block_type& block) noexcept -{ - if(block == zero_block) - { - return 0; - } - -#if defined(DYNAMIC_BITSET_GCC) \ - || (defined(DYNAMIC_BITSET_CLANG) && defined(DYNAMIC_BITSET_CLANG_builtin_popcount)) - if constexpr(std::is_same_v) - { - return static_cast(__builtin_popcountll(block)); - } - if constexpr(std::is_same_v) - { - return static_cast(__builtin_popcountl(block)); - } - if constexpr(sizeof(block_type) <= sizeof(unsigned int)) - { - return static_cast(__builtin_popcount(static_cast(block))); - } -#endif - - size_type count = 0; - block_type mask = 1; - for(size_type bit_index = 0; bit_index < bits_per_block; ++bit_index) - { - count += ((block & mask) != zero_block); - mask <<= 1; - } - return count; -} - -template -constexpr typename dynamic_bitset::size_type dynamic_bitset:: - block_count(const block_type& block, size_type nbits) noexcept -{ - assert(nbits <= bits_per_block); - if(block == zero_block) - { - return 0; - } - -#if defined(DYNAMIC_BITSET_GCC) \ - || (defined(DYNAMIC_BITSET_CLANG) && defined(DYNAMIC_BITSET_CLANG_builtin_popcount)) - const block_type shifted_block = block_type(block << (bits_per_block - nbits)); - if constexpr(std::is_same_v) - { - return static_cast(__builtin_popcountll(shifted_block)); - } - if constexpr(std::is_same_v) - { - return static_cast(__builtin_popcountl(shifted_block)); - } - if constexpr(sizeof(block_type) <= sizeof(unsigned int)) - { - return static_cast(__builtin_popcount(static_cast(shifted_block))); - } -#endif - - size_type count = 0; - block_type mask = 1; - for(size_type bit_index = 0; bit_index < nbits; ++bit_index) - { - count += ((block & mask) != zero_block); - mask <<= 1; - } - - return count; -} - -template -constexpr typename dynamic_bitset::size_type dynamic_bitset:: - first_on(const block_type& block) noexcept -{ - assert(block != zero_block); - -#if defined(DYNAMIC_BITSET_GCC) \ - || (defined(DYNAMIC_BITSET_CLANG) && defined(DYNAMIC_BITSET_CLANG_builtin_ctz)) - if constexpr(std::is_same_v) - { - return static_cast(__builtin_ctzll(block)); - } - if constexpr(std::is_same_v) - { - return static_cast(__builtin_ctzl(block)); - } - if constexpr(sizeof(block_type) <= sizeof(unsigned int)) - { - return static_cast(__builtin_ctz(static_cast(block))); - } -#elif defined(DYNAMIC_BITSET_MSVC) -# if defined(DYNAMIC_BITSET_MSVC_64) - if constexpr(std::is_same_v) - { - unsigned long index = std::numeric_limits::max(); - _BitScanForward64(&index, block); - return static_cast(index); - } -# endif - if constexpr(std::is_same_v) - { - unsigned long index = std::numeric_limits::max(); - _BitScanForward(&index, block); - return static_cast(index); - } - if constexpr(sizeof(block_type) <= sizeof(unsigned long)) - { - unsigned long index = std::numeric_limits::max(); - _BitScanForward(&index, static_cast(block)); - return static_cast(index); - } -#endif - - block_type mask = block_type(1); - for(size_type i = 0; i < bits_per_block; ++i) - { - if((block & mask) != zero_block) - { - return i; - } - mask <<= 1; - } - return npos; -} - -template -template -constexpr void dynamic_bitset::init_from_string( - std::basic_string_view<_CharT, _Traits> str, - typename std::basic_string_view<_CharT, _Traits>::size_type pos, - typename std::basic_string_view<_CharT, _Traits>::size_type n, - [[maybe_unused]] _CharT zero, - _CharT one) -{ - assert(pos < str.size()); - - const size_type size = std::min(n, str.size() - pos); - m_bits_number = size; - - m_blocks.clear(); - m_blocks.resize(blocks_required(size)); - for(size_t i = 0; i < size; ++i) - { - const _CharT c = str[(pos + size - 1) - i]; - assert(c == zero || c == one); - if(c == one) - { - set(i); - } - } -} - -template -constexpr typename dynamic_bitset::block_type& dynamic_bitset:: - get_block(size_type pos) -{ - return m_blocks[block_index(pos)]; -} - -template -constexpr const typename dynamic_bitset::block_type& dynamic_bitset< - Block, - Allocator>::get_block(size_type pos) const -{ - return m_blocks[block_index(pos)]; -} - -template -constexpr typename dynamic_bitset::block_type& dynamic_bitset:: - last_block() -{ - return m_blocks[m_blocks.size() - 1]; -} - -template -constexpr typename dynamic_bitset::block_type dynamic_bitset:: - last_block() const -{ - return m_blocks[m_blocks.size() - 1]; -} - -template -constexpr typename dynamic_bitset::size_type dynamic_bitset:: - extra_bits_number() const noexcept -{ - return bit_index(m_bits_number); -} - -template -constexpr typename dynamic_bitset::size_type dynamic_bitset:: - unused_bits_number() const noexcept -{ - return bits_per_block - extra_bits_number(); -} - -template -template -constexpr void dynamic_bitset::apply( - const dynamic_bitset& other, - BinaryOperation binary_op) -{ - assert(num_blocks() == other.num_blocks()); - std::transform(std::cbegin(m_blocks), - std::cend(m_blocks), - std::cbegin(other.m_blocks), - std::begin(m_blocks), - binary_op); -} - -template -template -constexpr void dynamic_bitset::apply(UnaryOperation unary_op) -{ - std::transform(std::cbegin(m_blocks), std::cend(m_blocks), std::begin(m_blocks), unary_op); -} - -template -constexpr void dynamic_bitset::apply_left_shift(size_type shift) -{ - assert(shift > 0); - assert(shift < capacity()); - - const size_type blocks_shift = shift / bits_per_block; - const size_type bits_offset = shift % bits_per_block; - - if(bits_offset == 0) - { - for(size_type i = m_blocks.size() - 1; i >= blocks_shift; --i) - { - m_blocks[i] = m_blocks[i - blocks_shift]; - } - } - else - { - const size_type reverse_bits_offset = bits_per_block - bits_offset; - for(size_type i = m_blocks.size() - 1; i > blocks_shift; --i) - { - m_blocks[i] = - block_type((m_blocks[i - blocks_shift] << bits_offset) - | block_type(m_blocks[i - blocks_shift - 1] >> reverse_bits_offset)); - } - m_blocks[blocks_shift] = block_type(m_blocks[0] << bits_offset); - } - - // set bit that came at the right to 0 in unmodified blocks - std::fill(std::begin(m_blocks), - std::begin(m_blocks) - + static_cast(blocks_shift), - zero_block); -} - -template -constexpr void dynamic_bitset::apply_right_shift(size_type shift) -{ - assert(shift > 0); - assert(shift < capacity()); - - const size_type blocks_shift = shift / bits_per_block; - const size_type bits_offset = shift % bits_per_block; - const size_type last_block_to_shift = m_blocks.size() - blocks_shift - 1; - - if(bits_offset == 0) - { - for(size_type i = 0; i <= last_block_to_shift; ++i) - { - m_blocks[i] = m_blocks[i + blocks_shift]; - } - } - else - { - const size_type reverse_bits_offset = bits_per_block - bits_offset; - for(size_type i = 0; i < last_block_to_shift; ++i) - { - m_blocks[i] = - block_type((m_blocks[i + blocks_shift] >> bits_offset) - | block_type(m_blocks[i + blocks_shift + 1] << reverse_bits_offset)); - } - m_blocks[last_block_to_shift] = block_type(m_blocks[m_blocks.size() - 1] >> bits_offset); - } - - // set bit that came at the left to 0 in unmodified blocks - std::fill( - std::begin(m_blocks) - + static_cast(last_block_to_shift + 1), - std::end(m_blocks), - zero_block); -} - -template -constexpr void dynamic_bitset::sanitize() -{ - size_type shift = m_bits_number % bits_per_block; - if(shift > 0) - { - last_block() &= ~(one_block << shift); - } -} - -template -constexpr bool dynamic_bitset::check_unused_bits() const noexcept -{ - const size_type extra_bits = extra_bits_number(); - if(extra_bits > 0) - { - return (last_block() & (one_block << extra_bits)) == zero_block; - } - return true; -} - -template -constexpr bool dynamic_bitset::check_size() const noexcept -{ - return blocks_required(size()) == m_blocks.size(); -} - -template -constexpr bool dynamic_bitset::check_consistency() const noexcept -{ - return check_unused_bits() && check_size(); -} - -//================================================================================================= -// dynamic_bitset external functions implementations -//================================================================================================= - -template -constexpr bool operator!=(const dynamic_bitset& lhs, - const dynamic_bitset& rhs) -{ - return !(lhs == rhs); -} - -template -constexpr bool operator<=(const dynamic_bitset& lhs, - const dynamic_bitset& rhs) -{ - return !(rhs < lhs); -} - -template -constexpr bool operator>(const dynamic_bitset& lhs, - const dynamic_bitset& rhs) -{ - return rhs < lhs; -} - -template -constexpr bool operator>=(const dynamic_bitset& lhs, - const dynamic_bitset& rhs) -{ - return !(lhs < rhs); -} - -template -constexpr dynamic_bitset operator&(const dynamic_bitset& lhs, - const dynamic_bitset& rhs) -{ - dynamic_bitset result(lhs); - return result &= rhs; -} - -template -constexpr dynamic_bitset operator|(const dynamic_bitset& lhs, - const dynamic_bitset& rhs) -{ - dynamic_bitset result(lhs); - return result |= rhs; -} - -template -constexpr dynamic_bitset operator^(const dynamic_bitset& lhs, - const dynamic_bitset& rhs) -{ - dynamic_bitset result(lhs); - return result ^= rhs; -} - -template -constexpr dynamic_bitset operator-(const dynamic_bitset& lhs, - const dynamic_bitset& rhs) -{ - dynamic_bitset result(lhs); - return result -= rhs; -} - -template -constexpr std::basic_ostream<_CharT, _Traits>& operator<<( - std::basic_ostream<_CharT, _Traits>& os, - const dynamic_bitset& bitset) -{ - // A better implementation is possible - return os << bitset.template to_string<_CharT, _Traits>(); -} - -template -constexpr std::basic_istream<_CharT, _Traits>& operator>>(std::basic_istream<_CharT, _Traits>& is, - dynamic_bitset& bitset) -{ - // A better implementation is possible - constexpr _CharT zero = _CharT('0'); - constexpr _CharT one = _CharT('1'); - typename std::basic_istream<_CharT, _Traits>::sentry s(is); - if(!s) - { - return is; - } - - dynamic_bitset reverse_bitset; - _CharT val; - is.get(val); - while(is.good()) - { - if(val == one) - { - reverse_bitset.push_back(true); - } - else if(val == zero) - { - reverse_bitset.push_back(false); - } - else - { - is.unget(); - break; - } - is.get(val); - } - - bitset.clear(); - if(!reverse_bitset.empty()) - { - for(typename dynamic_bitset::size_type i = reverse_bitset.size() - 1; - i > 0; - --i) - { - bitset.push_back(reverse_bitset.test(i)); - } - bitset.push_back(reverse_bitset.test(0)); - } - - return is; -} - -template -constexpr void swap(dynamic_bitset& bitset1, - dynamic_bitset& bitset2) -{ - bitset1.swap(bitset2); -} - -#endif //DYNAMIC_BITSET_DYNAMIC_BITSET_HPP diff --git a/oss/libpopcnt/LICENSE b/oss/libpopcnt/LICENSE deleted file mode 100644 index 15a721096fb..00000000000 --- a/oss/libpopcnt/LICENSE +++ /dev/null @@ -1,26 +0,0 @@ -BSD 2-Clause License - -Copyright (c) 2016 - 2019, Kim Walisch -Copyright (c) 2016 - 2019, Wojciech Muła - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/oss/libpopcnt/MAINTAINER_README.md b/oss/libpopcnt/MAINTAINER_README.md deleted file mode 100644 index 843a481edbf..00000000000 --- a/oss/libpopcnt/MAINTAINER_README.md +++ /dev/null @@ -1,17 +0,0 @@ -### Notes for Future Maintainers - -This was originally imported by @miniksa in March 2020. - -The provenance information (where it came from and which commit) is stored in the file `cgmanifest.json` in the same directory as this readme. -Please update the provenance information in that file when ingesting an updated version of the dependent library. -That provenance file is automatically read and inventoried by Microsoft systems to ensure compliance with appropriate governance standards. - -## What should be done to update this in the future? - -1. Go to kimwalisch/libpopcnt repository on GitHub. -2. Take the `libpopcnt.h` file. -3. Don't change anything about it. -4. Validate that the `LICENSE` in the root of the repository didn't change and update it if so. It is sitting in the same directory as this readme. - If it changed dramatically, ensure that it is still compatible with our license scheme. Also update the NOTICE file in the root of our repository to declare the third-party usage. -5. Submit the pull. - diff --git a/oss/libpopcnt/cgmanifest.json b/oss/libpopcnt/cgmanifest.json deleted file mode 100644 index b65c9c29b07..00000000000 --- a/oss/libpopcnt/cgmanifest.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/component-detection-manifest.json", - "Registrations": [ - { - "component": { - "type": "git", - "git": { - "repositoryUrl": "https://github.com/kimwalisch/libpopcnt", - "commitHash": "c49987e90e56191c399cab881ab87b5daecc9b8e" - } - } - } - ], - "Version": 1 -} diff --git a/oss/libpopcnt/libpopcnt.h b/oss/libpopcnt/libpopcnt.h deleted file mode 100644 index de24253d9a6..00000000000 --- a/oss/libpopcnt/libpopcnt.h +++ /dev/null @@ -1,798 +0,0 @@ -/* - * libpopcnt.h - C/C++ library for counting the number of 1 bits (bit - * population count) in an array as quickly as possible using - * specialized CPU instructions i.e. POPCNT, AVX2, AVX512, NEON. - * - * Copyright (c) 2016 - 2020, Kim Walisch - * Copyright (c) 2016 - 2018, Wojciech Muła - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef LIBPOPCNT_H -#define LIBPOPCNT_H - -#include -#include - -#ifndef __has_builtin - #define __has_builtin(x) 0 -#endif - -#ifndef __has_attribute - #define __has_attribute(x) 0 -#endif - -#ifdef __GNUC__ - #define GNUC_PREREQ(x, y) \ - (__GNUC__ > x || (__GNUC__ == x && __GNUC_MINOR__ >= y)) -#else - #define GNUC_PREREQ(x, y) 0 -#endif - -#ifdef __clang__ - #define CLANG_PREREQ(x, y) \ - (__clang_major__ > x || (__clang_major__ == x && __clang_minor__ >= y)) -#else - #define CLANG_PREREQ(x, y) 0 -#endif - -#if (_MSC_VER < 1900) && \ - !defined(__cplusplus) - #define inline __inline -#endif - -#if (defined(__i386__) || \ - defined(__x86_64__) || \ - defined(_M_IX86) || \ - defined(_M_X64)) - #define X86_OR_X64 -#endif - -#if GNUC_PREREQ(4, 2) || \ - __has_builtin(__builtin_popcount) - #define HAVE_BUILTIN_POPCOUNT -#endif - -#if GNUC_PREREQ(4, 2) || \ - CLANG_PREREQ(3, 0) - #define HAVE_ASM_POPCNT -#endif - -#if defined(X86_OR_X64) && \ - (defined(HAVE_ASM_POPCNT) || \ - defined(_MSC_VER)) - #define HAVE_POPCNT -#endif - -#if defined(X86_OR_X64) && \ - GNUC_PREREQ(4, 9) - #define HAVE_AVX2 -#endif - -#if defined(X86_OR_X64) && \ - GNUC_PREREQ(5, 0) - #define HAVE_AVX512 -#endif - -#if defined(X86_OR_X64) && !defined(_M_ARM64EC) - /* MSVC compatible compilers (Windows) */ - #if defined(_MSC_VER) - /* clang-cl (LLVM 10 from 2020) requires /arch:AVX2 or - * /arch:AVX512 to enable vector instructions */ - #if defined(__clang__) - #if defined(__AVX2__) - #define HAVE_AVX2 - #endif - #if defined(__AVX512__) - #define HAVE_AVX2 - #define HAVE_AVX512 - #endif - /* MSVC 2017 or later does not require - * /arch:AVX2 or /arch:AVX512 */ - #elif _MSC_VER >= 1910 - #define HAVE_AVX2 - #define HAVE_AVX512 - #endif - /* Clang (Unix-like OSes) */ - #elif CLANG_PREREQ(3, 8) && \ - __has_attribute(target) && \ - (!defined(__apple_build_version__) || __apple_build_version__ >= 8000000) - #define HAVE_AVX2 - #define HAVE_AVX512 - #endif -#endif - -/* - * Only enable CPUID runtime checks if this is really - * needed. E.g. do not enable if user has compiled - * using -march=native on a CPU that supports AVX512. - */ -#if defined(X86_OR_X64) && \ - (defined(__cplusplus) || \ - defined(_MSC_VER) || \ - (GNUC_PREREQ(4, 2) || \ - __has_builtin(__sync_val_compare_and_swap))) && \ - ((defined(HAVE_AVX512) && !(defined(__AVX512__) || defined(__AVX512BW__))) || \ - (defined(HAVE_AVX2) && !defined(__AVX2__)) || \ - (defined(HAVE_POPCNT) && !defined(__POPCNT__))) - #define HAVE_CPUID -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * This uses fewer arithmetic operations than any other known - * implementation on machines with fast multiplication. - * It uses 12 arithmetic operations, one of which is a multiply. - * http://en.wikipedia.org/wiki/Hamming_weight#Efficient_implementation - */ -static inline uint64_t popcount64(uint64_t x) -{ - uint64_t m1 = 0x5555555555555555ll; - uint64_t m2 = 0x3333333333333333ll; - uint64_t m4 = 0x0F0F0F0F0F0F0F0Fll; - uint64_t h01 = 0x0101010101010101ll; - - x -= (x >> 1) & m1; - x = (x & m2) + ((x >> 2) & m2); - x = (x + (x >> 4)) & m4; - - return (x * h01) >> 56; -} - -#if defined(HAVE_ASM_POPCNT) && \ - defined(__x86_64__) - -static inline uint64_t popcnt64(uint64_t x) -{ - __asm__ ("popcnt %1, %0" : "=r" (x) : "0" (x)); - return x; -} - -#elif defined(HAVE_ASM_POPCNT) && \ - defined(__i386__) - -static inline uint32_t popcnt32(uint32_t x) -{ - __asm__ ("popcnt %1, %0" : "=r" (x) : "0" (x)); - return x; -} - -static inline uint64_t popcnt64(uint64_t x) -{ - return popcnt32((uint32_t) x) + - popcnt32((uint32_t)(x >> 32)); -} - -#elif defined(_MSC_VER) && \ - defined(_M_X64) - -#include - -static inline uint64_t popcnt64(uint64_t x) -{ - return _mm_popcnt_u64(x); -} - -#elif defined(_MSC_VER) && \ - defined(_M_IX86) - -#include - -static inline uint64_t popcnt64(uint64_t x) -{ - return _mm_popcnt_u32((uint32_t) x) + - _mm_popcnt_u32((uint32_t)(x >> 32)); -} - -/* non x86 CPUs */ -#elif defined(HAVE_BUILTIN_POPCOUNT) - -static inline uint64_t popcnt64(uint64_t x) -{ - return __builtin_popcountll(x); -} - -/* no hardware POPCNT, - * use pure integer algorithm */ -#else - -static inline uint64_t popcnt64(uint64_t x) -{ - return popcount64(x); -} - -#endif - -#if defined(HAVE_CPUID) - -#if defined(_MSC_VER) - #include - #include -#endif - -/* %ecx bit flags */ -#define bit_POPCNT (1 << 23) - -/* %ebx bit flags */ -#define bit_AVX2 (1 << 5) -#define bit_AVX512 (1 << 30) - -/* xgetbv bit flags */ -#define XSTATE_SSE (1 << 1) -#define XSTATE_YMM (1 << 2) -#define XSTATE_ZMM (7 << 5) - -static inline void run_cpuid(int eax, int ecx, int* abcd) -{ -#if defined(_MSC_VER) - __cpuidex(abcd, eax, ecx); -#else - int ebx = 0; - int edx = 0; - - #if defined(__i386__) && \ - defined(__PIC__) - /* in case of PIC under 32-bit EBX cannot be clobbered */ - __asm__ ("movl %%ebx, %%edi;" - "cpuid;" - "xchgl %%ebx, %%edi;" - : "=D" (ebx), - "+a" (eax), - "+c" (ecx), - "=d" (edx)); - #else - __asm__ ("cpuid;" - : "+b" (ebx), - "+a" (eax), - "+c" (ecx), - "=d" (edx)); - #endif - - abcd[0] = eax; - abcd[1] = ebx; - abcd[2] = ecx; - abcd[3] = edx; -#endif -} - -#if defined(HAVE_AVX2) || \ - defined(HAVE_AVX512) - -static inline int get_xcr0() -{ - int xcr0; - -#if defined(_MSC_VER) - xcr0 = (int) _xgetbv(0); -#else - __asm__ ("xgetbv" : "=a" (xcr0) : "c" (0) : "%edx" ); -#endif - - return xcr0; -} - -#endif - -static inline int get_cpuid() -{ - int flags = 0; - int abcd[4]; - - run_cpuid(1, 0, abcd); - - if ((abcd[2] & bit_POPCNT) == bit_POPCNT) - flags |= bit_POPCNT; - -#if defined(HAVE_AVX2) || \ - defined(HAVE_AVX512) - - int osxsave_mask = (1 << 27); - - /* ensure OS supports extended processor state management */ - if ((abcd[2] & osxsave_mask) != osxsave_mask) - return 0; - - int ymm_mask = XSTATE_SSE | XSTATE_YMM; - int zmm_mask = XSTATE_SSE | XSTATE_YMM | XSTATE_ZMM; - - int xcr0 = get_xcr0(); - - if ((xcr0 & ymm_mask) == ymm_mask) - { - run_cpuid(7, 0, abcd); - - if ((abcd[1] & bit_AVX2) == bit_AVX2) - flags |= bit_AVX2; - - if ((xcr0 & zmm_mask) == zmm_mask) - { - if ((abcd[1] & bit_AVX512) == bit_AVX512) - flags |= bit_AVX512; - } - } - -#endif - - return flags; -} - -#endif /* cpuid */ - -#if defined(HAVE_AVX2) - -#include - -#if !defined(_MSC_VER) - __attribute__ ((target ("avx2"))) -#endif -static inline void CSA256(__m256i* h, __m256i* l, __m256i a, __m256i b, __m256i c) -{ - __m256i u = _mm256_xor_si256(a, b); - *h = _mm256_or_si256(_mm256_and_si256(a, b), _mm256_and_si256(u, c)); - *l = _mm256_xor_si256(u, c); -} - -#if !defined(_MSC_VER) - __attribute__ ((target ("avx2"))) -#endif -static inline __m256i popcnt256(__m256i v) -{ - __m256i lookup1 = _mm256_setr_epi8( - 4, 5, 5, 6, 5, 6, 6, 7, - 5, 6, 6, 7, 6, 7, 7, 8, - 4, 5, 5, 6, 5, 6, 6, 7, - 5, 6, 6, 7, 6, 7, 7, 8 - ); - - __m256i lookup2 = _mm256_setr_epi8( - 4, 3, 3, 2, 3, 2, 2, 1, - 3, 2, 2, 1, 2, 1, 1, 0, - 4, 3, 3, 2, 3, 2, 2, 1, - 3, 2, 2, 1, 2, 1, 1, 0 - ); - - __m256i low_mask = _mm256_set1_epi8(0x0f); - __m256i lo = _mm256_and_si256(v, low_mask); - __m256i hi = _mm256_and_si256(_mm256_srli_epi16(v, 4), low_mask); - __m256i popcnt1 = _mm256_shuffle_epi8(lookup1, lo); - __m256i popcnt2 = _mm256_shuffle_epi8(lookup2, hi); - - return _mm256_sad_epu8(popcnt1, popcnt2); -} - -/* - * AVX2 Harley-Seal popcount (4th iteration). - * The algorithm is based on the paper "Faster Population Counts - * using AVX2 Instructions" by Daniel Lemire, Nathan Kurz and - * Wojciech Mula (23 Nov 2016). - * @see https://arxiv.org/abs/1611.07612 - */ -#if !defined(_MSC_VER) - __attribute__ ((target ("avx2"))) -#endif -static inline uint64_t popcnt_avx2(const __m256i* ptr, uint64_t size) -{ - __m256i cnt = _mm256_setzero_si256(); - __m256i ones = _mm256_setzero_si256(); - __m256i twos = _mm256_setzero_si256(); - __m256i fours = _mm256_setzero_si256(); - __m256i eights = _mm256_setzero_si256(); - __m256i sixteens = _mm256_setzero_si256(); - __m256i twosA, twosB, foursA, foursB, eightsA, eightsB; - - uint64_t i = 0; - uint64_t limit = size - size % 16; - uint64_t* cnt64; - - for(; i < limit; i += 16) - { - CSA256(&twosA, &ones, ones, _mm256_loadu_si256(ptr + i + 0), _mm256_loadu_si256(ptr + i + 1)); - CSA256(&twosB, &ones, ones, _mm256_loadu_si256(ptr + i + 2), _mm256_loadu_si256(ptr + i + 3)); - CSA256(&foursA, &twos, twos, twosA, twosB); - CSA256(&twosA, &ones, ones, _mm256_loadu_si256(ptr + i + 4), _mm256_loadu_si256(ptr + i + 5)); - CSA256(&twosB, &ones, ones, _mm256_loadu_si256(ptr + i + 6), _mm256_loadu_si256(ptr + i + 7)); - CSA256(&foursB, &twos, twos, twosA, twosB); - CSA256(&eightsA, &fours, fours, foursA, foursB); - CSA256(&twosA, &ones, ones, _mm256_loadu_si256(ptr + i + 8), _mm256_loadu_si256(ptr + i + 9)); - CSA256(&twosB, &ones, ones, _mm256_loadu_si256(ptr + i + 10), _mm256_loadu_si256(ptr + i + 11)); - CSA256(&foursA, &twos, twos, twosA, twosB); - CSA256(&twosA, &ones, ones, _mm256_loadu_si256(ptr + i + 12), _mm256_loadu_si256(ptr + i + 13)); - CSA256(&twosB, &ones, ones, _mm256_loadu_si256(ptr + i + 14), _mm256_loadu_si256(ptr + i + 15)); - CSA256(&foursB, &twos, twos, twosA, twosB); - CSA256(&eightsB, &fours, fours, foursA, foursB); - CSA256(&sixteens, &eights, eights, eightsA, eightsB); - - cnt = _mm256_add_epi64(cnt, popcnt256(sixteens)); - } - - cnt = _mm256_slli_epi64(cnt, 4); - cnt = _mm256_add_epi64(cnt, _mm256_slli_epi64(popcnt256(eights), 3)); - cnt = _mm256_add_epi64(cnt, _mm256_slli_epi64(popcnt256(fours), 2)); - cnt = _mm256_add_epi64(cnt, _mm256_slli_epi64(popcnt256(twos), 1)); - cnt = _mm256_add_epi64(cnt, popcnt256(ones)); - - for(; i < size; i++) - cnt = _mm256_add_epi64(cnt, popcnt256(_mm256_loadu_si256(ptr + i))); - - cnt64 = (uint64_t*) &cnt; - - return cnt64[0] + - cnt64[1] + - cnt64[2] + - cnt64[3]; -} - -#endif - -#if defined(HAVE_AVX512) - -#include - -#if !defined(_MSC_VER) - __attribute__ ((target ("avx512bw"))) -#endif -static inline __m512i popcnt512(__m512i v) -{ - __m512i m1 = _mm512_set1_epi8(0x55); - __m512i m2 = _mm512_set1_epi8(0x33); - __m512i m4 = _mm512_set1_epi8(0x0F); - __m512i vm = _mm512_and_si512(_mm512_srli_epi16(v, 1), m1); - __m512i t1 = _mm512_sub_epi8(v, vm); - __m512i tm = _mm512_and_si512(t1, m2); - __m512i tm2 = _mm512_and_si512(_mm512_srli_epi16(t1, 2), m2); - __m512i t2 = _mm512_add_epi8(tm, tm2); - __m512i tt = _mm512_add_epi8(t2, _mm512_srli_epi16(t2, 4)); - __m512i t3 = _mm512_and_si512(tt, m4); - - return _mm512_sad_epu8(t3, _mm512_setzero_si512()); -} - -#if !defined(_MSC_VER) - __attribute__ ((target ("avx512bw"))) -#endif -static inline void CSA512(__m512i* h, __m512i* l, __m512i a, __m512i b, __m512i c) -{ - *l = _mm512_ternarylogic_epi32(c, b, a, 0x96); - *h = _mm512_ternarylogic_epi32(c, b, a, 0xe8); -} - -/* - * AVX512 Harley-Seal popcount (4th iteration). - * The algorithm is based on the paper "Faster Population Counts - * using AVX2 Instructions" by Daniel Lemire, Nathan Kurz and - * Wojciech Mula (23 Nov 2016). - * @see https://arxiv.org/abs/1611.07612 - */ -#if !defined(_MSC_VER) - __attribute__ ((target ("avx512bw"))) -#endif -static inline uint64_t popcnt_avx512(const __m512i* ptr, const uint64_t size) -{ - __m512i cnt = _mm512_setzero_si512(); - __m512i ones = _mm512_setzero_si512(); - __m512i twos = _mm512_setzero_si512(); - __m512i fours = _mm512_setzero_si512(); - __m512i eights = _mm512_setzero_si512(); - __m512i sixteens = _mm512_setzero_si512(); - __m512i twosA, twosB, foursA, foursB, eightsA, eightsB; - - uint64_t i = 0; - uint64_t limit = size - size % 16; - uint64_t* cnt64; - - for(; i < limit; i += 16) - { - CSA512(&twosA, &ones, ones, _mm512_loadu_si512(ptr + i + 0), _mm512_loadu_si512(ptr + i + 1)); - CSA512(&twosB, &ones, ones, _mm512_loadu_si512(ptr + i + 2), _mm512_loadu_si512(ptr + i + 3)); - CSA512(&foursA, &twos, twos, twosA, twosB); - CSA512(&twosA, &ones, ones, _mm512_loadu_si512(ptr + i + 4), _mm512_loadu_si512(ptr + i + 5)); - CSA512(&twosB, &ones, ones, _mm512_loadu_si512(ptr + i + 6), _mm512_loadu_si512(ptr + i + 7)); - CSA512(&foursB, &twos, twos, twosA, twosB); - CSA512(&eightsA, &fours, fours, foursA, foursB); - CSA512(&twosA, &ones, ones, _mm512_loadu_si512(ptr + i + 8), _mm512_loadu_si512(ptr + i + 9)); - CSA512(&twosB, &ones, ones, _mm512_loadu_si512(ptr + i + 10), _mm512_loadu_si512(ptr + i + 11)); - CSA512(&foursA, &twos, twos, twosA, twosB); - CSA512(&twosA, &ones, ones, _mm512_loadu_si512(ptr + i + 12), _mm512_loadu_si512(ptr + i + 13)); - CSA512(&twosB, &ones, ones, _mm512_loadu_si512(ptr + i + 14), _mm512_loadu_si512(ptr + i + 15)); - CSA512(&foursB, &twos, twos, twosA, twosB); - CSA512(&eightsB, &fours, fours, foursA, foursB); - CSA512(&sixteens, &eights, eights, eightsA, eightsB); - - cnt = _mm512_add_epi64(cnt, popcnt512(sixteens)); - } - - cnt = _mm512_slli_epi64(cnt, 4); - cnt = _mm512_add_epi64(cnt, _mm512_slli_epi64(popcnt512(eights), 3)); - cnt = _mm512_add_epi64(cnt, _mm512_slli_epi64(popcnt512(fours), 2)); - cnt = _mm512_add_epi64(cnt, _mm512_slli_epi64(popcnt512(twos), 1)); - cnt = _mm512_add_epi64(cnt, popcnt512(ones)); - - for(; i < size; i++) - cnt = _mm512_add_epi64(cnt, popcnt512(_mm512_loadu_si512(ptr + i))); - - cnt64 = (uint64_t*) &cnt; - - return cnt64[0] + - cnt64[1] + - cnt64[2] + - cnt64[3] + - cnt64[4] + - cnt64[5] + - cnt64[6] + - cnt64[7]; -} - -#endif - -/* x86 CPUs */ -#if defined(X86_OR_X64) - -/* - * Count the number of 1 bits in the data array - * @data: An array - * @size: Size of data in bytes - */ -static inline uint64_t popcnt(const void* data, uint64_t size) -{ - uint64_t i = 0; - uint64_t cnt = 0; - const uint8_t* ptr = (const uint8_t*) data; - -/* - * CPUID runtime checks are only enabled if this is needed. - * E.g. CPUID is disabled when a user compiles his - * code using -march=native on a CPU with AVX512. - */ -#if defined(HAVE_CPUID) - #if defined(__cplusplus) - /* C++11 thread-safe singleton */ - static const int cpuid = get_cpuid(); - #else - static int cpuid_ = -1; - int cpuid = cpuid_; - if (cpuid == -1) - { - cpuid = get_cpuid(); - - #if defined(_MSC_VER) - _InterlockedCompareExchange(&cpuid_, cpuid, -1); - #else - __sync_val_compare_and_swap(&cpuid_, -1, cpuid); - #endif - } - #endif -#endif - -#if defined(HAVE_AVX512) - #if defined(__AVX512__) || defined(__AVX512BW__) - /* AVX512 requires arrays >= 1024 bytes */ - if (i + 1024 <= size) - #else - if ((cpuid & bit_AVX512) && - i + 1024 <= size) - #endif - { - const __m512i* ptr512 = (const __m512i*)(ptr + i); - cnt += popcnt_avx512(ptr512, (size - i) / 64); - i = size - size % 64; - } -#endif - -#if defined(HAVE_AVX2) - #if defined(__AVX2__) - /* AVX2 requires arrays >= 512 bytes */ - if (i + 512 <= size) - #else - if ((cpuid & bit_AVX2) && - i + 512 <= size) - #endif - { - const __m256i* ptr256 = (const __m256i*)(ptr + i); - cnt += popcnt_avx2(ptr256, (size - i) / 32); - i = size - size % 32; - } -#endif - -#if defined(HAVE_POPCNT) - /* - * The user has compiled without -mpopcnt. - * Unfortunately the MSVC compiler does not have - * a POPCNT macro so we cannot get rid of the - * runtime check for MSVC. - */ - #if !defined(__POPCNT__) - if (cpuid & bit_POPCNT) - #endif - { - /* We use unaligned memory accesses here to improve performance */ - for (; i < size - size % 8; i += 8) - cnt += popcnt64(*(const uint64_t*)(ptr + i)); - for (; i < size; i++) - cnt += popcnt64(ptr[i]); - - return cnt; - } -#endif - -#if !defined(HAVE_POPCNT) || \ - !defined(__POPCNT__) - /* - * Pure integer popcount algorithm. - * We use unaligned memory accesses here to improve performance. - */ - for (; i < size - size % 8; i += 8) - cnt += popcount64(*(const uint64_t*)(ptr + i)); - - if (i < size) - { - uint64_t val = 0; - size_t bytes = (size_t)(size - i); - memcpy(&val, &ptr[i], bytes); - cnt += popcount64(val); - } - - return cnt; -#endif -} - -#elif defined(__ARM_NEON) || \ - defined(__aarch64__) - -#include - -static inline uint64x2_t vpadalq(uint64x2_t sum, uint8x16_t t) -{ - return vpadalq_u32(sum, vpaddlq_u16(vpaddlq_u8(t))); -} - -/* - * Count the number of 1 bits in the data array - * @data: An array - * @size: Size of data in bytes - */ -static inline uint64_t popcnt(const void* data, uint64_t size) -{ - uint64_t i = 0; - uint64_t cnt = 0; - uint64_t chunk_size = 64; - const uint8_t* ptr = (const uint8_t*) data; - - if (size >= chunk_size) - { - uint64_t iters = size / chunk_size; - uint64x2_t sum = vcombine_u64(vcreate_u64(0), vcreate_u64(0)); - uint8x16_t zero = vcombine_u8(vcreate_u8(0), vcreate_u8(0)); - - do - { - uint8x16_t t0 = zero; - uint8x16_t t1 = zero; - uint8x16_t t2 = zero; - uint8x16_t t3 = zero; - - /* - * After every 31 iterations we need to add the - * temporary sums (t0, t1, t2, t3) to the total sum. - * We must ensure that the temporary sums <= 255 - * and 31 * 8 bits = 248 which is OK. - */ - uint64_t limit = (i + 31 < iters) ? i + 31 : iters; - - /* Each iteration processes 64 bytes */ - for (; i < limit; i++) - { - uint8x16x4_t input = vld4q_u8(ptr); - ptr += chunk_size; - - t0 = vaddq_u8(t0, vcntq_u8(input.val[0])); - t1 = vaddq_u8(t1, vcntq_u8(input.val[1])); - t2 = vaddq_u8(t2, vcntq_u8(input.val[2])); - t3 = vaddq_u8(t3, vcntq_u8(input.val[3])); - } - - sum = vpadalq(sum, t0); - sum = vpadalq(sum, t1); - sum = vpadalq(sum, t2); - sum = vpadalq(sum, t3); - } - while (i < iters); - - i = 0; - size %= chunk_size; - - uint64_t tmp[2]; - vst1q_u64(tmp, sum); - cnt += tmp[0]; - cnt += tmp[1]; - } - -#if defined(__ARM_FEATURE_UNALIGNED) - /* We use unaligned memory accesses here to improve performance */ - for (; i < size - size % 8; i += 8) - cnt += popcnt64(*(const uint64_t*)(ptr + i)); -#else - if (i + 8 <= size) - { - /* Align memory to an 8 byte boundary */ - for (; (uintptr_t)(ptr + i) % 8; i++) - cnt += popcnt64(ptr[i]); - for (; i < size - size % 8; i += 8) - cnt += popcnt64(*(const uint64_t*)(ptr + i)); - } -#endif - - if (i < size) - { - uint64_t val = 0; - size_t bytes = (size_t)(size - i); - memcpy(&val, &ptr[i], bytes); - cnt += popcount64(val); - } - - return cnt; -} - -/* all other CPUs */ -#else - -/* - * Count the number of 1 bits in the data array - * @data: An array - * @size: Size of data in bytes - */ -static inline uint64_t popcnt(const void* data, uint64_t size) -{ - uint64_t i = 0; - uint64_t cnt = 0; - const uint8_t* ptr = (const uint8_t*) data; - - if (size >= 8) - { - /* - * Since we don't know whether this CPU architecture - * supports unaligned memory accesses we align - * memory to an 8 byte boundary. - */ - for (; (uintptr_t)(ptr + i) % 8; i++) - cnt += popcnt64(ptr[i]); - for (; i < size - size % 8; i += 8) - cnt += popcnt64(*(const uint64_t*)(ptr + i)); - } - - for (; i < size; i++) - cnt += popcnt64(ptr[i]); - - return cnt; -} - -#endif - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* LIBPOPCNT_H */ diff --git a/src/ConsolePerf.wprp b/src/ConsolePerf.wprp index dffa239cccd..544eec39c87 100644 --- a/src/ConsolePerf.wprp +++ b/src/ConsolePerf.wprp @@ -42,7 +42,6 @@ - @@ -66,7 +65,6 @@ - diff --git a/src/Terminal.wprp b/src/Terminal.wprp index 129d83ca0a0..1abe7df41a3 100644 --- a/src/Terminal.wprp +++ b/src/Terminal.wprp @@ -18,7 +18,6 @@ - diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index e85fd718c4c..42d057515e9 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -716,13 +716,6 @@ OutputCellIterator TextBuffer::WriteLine(const OutputCellIterator givenIt, // - true if we successfully incremented the buffer. void TextBuffer::IncrementCircularBuffer(const TextAttribute& fillAttributes) { - // FirstRow is at any given point in time the array index in the circular buffer that corresponds - // to the logical position 0 in the window (cursor coordinates and all other coordinates). - if (_isActiveBuffer && _renderer) - { - _renderer->TriggerFlush(true); - } - // Prune hyperlinks to delete obsolete references _PruneHyperlinks(); diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index e33ddc7b0af..5b8cb22028a 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -5,6 +5,7 @@ #include "ConptyConnection.h" #include +#include #include "CTerminalHandoff.h" #include "LibraryResources.h" @@ -31,29 +32,6 @@ static constexpr auto _errorFormat = L"{0} ({0:#010x})"sv; namespace winrt::Microsoft::Terminal::TerminalConnection::implementation { - // Function Description: - // - creates some basic anonymous pipes and passes them to CreatePseudoConsole - // Arguments: - // - size: The size of the conpty to create, in characters. - // - phInput: Receives the handle to the newly-created anonymous pipe for writing input to the conpty. - // - phOutput: Receives the handle to the newly-created anonymous pipe for reading the output of the conpty. - // - phPc: Receives a token value to identify this conpty -#pragma warning(suppress : 26430) // This statement sufficiently checks the out parameters. Analyzer cannot find this. - static HRESULT _CreatePseudoConsoleAndPipes(const COORD size, const DWORD dwFlags, HANDLE* phInput, HANDLE* phOutput, HPCON* phPC) noexcept - { - RETURN_HR_IF(E_INVALIDARG, phPC == nullptr || phInput == nullptr || phOutput == nullptr); - - wil::unique_hfile outPipeOurSide, outPipePseudoConsoleSide; - wil::unique_hfile inPipeOurSide, inPipePseudoConsoleSide; - - RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(&inPipePseudoConsoleSide, &inPipeOurSide, nullptr, 0)); - RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(&outPipeOurSide, &outPipePseudoConsoleSide, nullptr, 0)); - RETURN_IF_FAILED(ConptyCreatePseudoConsole(size, inPipePseudoConsoleSide.get(), outPipePseudoConsoleSide.get(), dwFlags, phPC)); - *phInput = inPipeOurSide.release(); - *phOutput = outPipeOurSide.release(); - return S_OK; - } - // Function Description: // - launches the client application attached to the new pseudoconsole HRESULT ConptyConnection::_LaunchAttachedClient() noexcept @@ -174,8 +152,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation // Who decided that? #pragma warning(suppress : 26455) // Default constructor should not throw. Declare it 'noexcept' (f.6). - ConptyConnection::ConptyConnection() + ConptyConnection::ConptyConnection() : + _writeOverlappedEvent{ CreateEventExW(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS) } { + THROW_LAST_ERROR_IF(!_writeOverlappedEvent); + _writeOverlapped.hEvent = _writeOverlappedEvent.get(); } // Function Description: @@ -236,7 +217,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation _environment = settings.TryLookup(L"environment").try_as(); _profileGuid = unbox_prop_or(settings, L"profileGuid", _profileGuid); - _flags = PSEUDOCONSOLE_RESIZE_QUIRK; + _flags = 0; // If we're using an existing buffer, we want the new connection // to reuse the existing cursor. When not setting this flag, the @@ -310,10 +291,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation _sessionId = Utils::CreateGuid(); - wil::unique_hfile inPipePseudoConsoleSide; - wil::unique_hfile outPipePseudoConsoleSide; - THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&inPipePseudoConsoleSide, &_inPipe, nullptr, 0)); - THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&_outPipe, &outPipePseudoConsoleSide, nullptr, 0)); + auto pipe = Utils::CreateOverlappedPipe(PIPE_ACCESS_DUPLEX, 128 * 1024); + auto pipeClientClone = duplicateHandle(pipe.client.get()); auto ownedSignal = duplicateHandle(signal); auto ownedReference = duplicateHandle(reference); @@ -338,8 +317,9 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } CATCH_LOG() - *in = inPipePseudoConsoleSide.release(); - *out = outPipePseudoConsoleSide.release(); + _pipe = std::move(pipe.server); + *in = pipe.client.release(); + *out = pipeClientClone.release(); } winrt::hstring ConptyConnection::Commandline() const @@ -366,9 +346,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation // If we do not have pipes already, then this is a fresh connection... not an inbound one that is a received // handoff from an already-started PTY process. - if (!_inPipe) + if (!_pipe) { - THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(til::unwrap_coord_size(dimensions), _flags, &_inPipe, &_outPipe, &_hPC)); + auto pipe = Utils::CreateOverlappedPipe(PIPE_ACCESS_DUPLEX, 128 * 1024); + THROW_IF_FAILED(ConptyCreatePseudoConsole(til::unwrap_coord_size(dimensions), pipe.client.get(), pipe.client.get(), _flags, &_hPC)); + _pipe = std::move(pipe.server); if (_initialParentHwnd != 0) { @@ -500,10 +482,44 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation return; } - // convert from UTF-16LE to UTF-8 as ConPty expects UTF-8 - // TODO GH#3378 reconcile and unify UTF-8 converters - auto str = winrt::to_string(data); - LOG_IF_WIN32_BOOL_FALSE(WriteFile(_inPipe.get(), str.c_str(), (DWORD)str.length(), nullptr, nullptr)); + // Ensure a linear and predictable write order, even across multiple threads. + // A ticket lock is the perfect fit for this as it acts as first-come-first-serve. + std::lock_guard guard{ _writeLock }; + + if (_writePending) + { + _writePending = false; + + DWORD read; + if (!GetOverlappedResult(_pipe.get(), &_writeOverlapped, &read, TRUE)) + { + // Not much we can do when the wait fails. This will kill the connection. + LOG_LAST_ERROR(); + _hPC.reset(); + return; + } + } + + if (FAILED_LOG(til::u16u8(data, _writeBuffer))) + { + return; + } + + if (!WriteFile(_pipe.get(), _writeBuffer.data(), gsl::narrow_cast(_writeBuffer.length()), nullptr, &_writeOverlapped)) + { + switch (const auto gle = GetLastError()) + { + case ERROR_BROKEN_PIPE: + _hPC.reset(); + break; + case ERROR_IO_PENDING: + _writePending = true; + break; + default: + LOG_WIN32(gle); + break; + } + } } void ConptyConnection::Resize(uint32_t rows, uint32_t columns) @@ -562,23 +578,17 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation { _transitionToState(ConnectionState::Closing); - // .reset()ing either of these two will signal ConPTY to send out a CTRL_CLOSE_EVENT to all attached clients. - // FYI: The other members of this class are concurrently read by the _hOutputThread - // thread running in the background and so they're not safe to be .reset(). + // This will signal ConPTY to send out a CTRL_CLOSE_EVENT to all attached clients. + // Once they're all disconnected it'll close its half of the pipes. _hPC.reset(); - _inPipe.reset(); if (_hOutputThread) { - // Loop around `CancelSynchronousIo()` just in case the signal to shut down was missed. - // This may happen if we called `CancelSynchronousIo()` while not being stuck - // in `ReadFile()` and if OpenConsole refuses to exit in a timely manner. + // Loop around `CancelIoEx()` just in case the signal to shut down was missed. for (;;) { - // ConptyConnection::Close() blocks the UI thread, because `_TerminalOutputHandlers` might indirectly - // reference UI objects like `ControlCore`. CancelSynchronousIo() allows us to have the background - // thread exit as fast as possible by aborting any ongoing writes coming from OpenConsole. - CancelSynchronousIo(_hOutputThread.get()); + // The output thread may be stuck waiting for the OVERLAPPED to be signaled. + CancelIoEx(_pipe.get(), nullptr); // Waiting for the output thread to exit ensures that all pending TerminalOutput.raise() // calls have returned and won't notify our caller (ControlCore) anymore. This ensures that @@ -588,17 +598,15 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation { break; } - - LOG_LAST_ERROR(); } } - // Now that the background thread is done, we can safely clean up the other system objects, without - // race conditions, or fear of deadlocking ourselves (e.g. by calling CloseHandle() on _outPipe). - _outPipe.reset(); _hOutputThread.reset(); _piClient.reset(); + _pipe.reset(); + // The output thread should have already transitioned us to Closed. + // This exists just in case there was no output thread. _transitionToState(ConnectionState::Closed); } CATCH_LOG() @@ -645,72 +653,98 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation _LastConPtyClientDisconnected(); }); + const wil::unique_event overlappedEvent{ CreateEventExW(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS) }; + OVERLAPPED overlapped{ .hEvent = overlappedEvent.get() }; + bool overlappedPending = false; char buffer[128 * 1024]; DWORD read = 0; til::u8state u8State; std::wstring wstr; - // process the data of the output pipe in a loop - while (true) + // If we use overlapped IO We want to queue ReadFile() calls before processing the + // string, because TerminalOutput.raise() may take a while (relatively speaking). + // That's why the loop looks a little weird as it starts a read, processes the + // previous string, and finally converts the previous read to the next string. + for (;;) { - const auto readFail{ !ReadFile(_outPipe.get(), &buffer[0], sizeof(buffer), &read, nullptr) }; - - // When we call CancelSynchronousIo() in Close() this is the branch that's taken and gets us out of here. - if (_isStateAtOrBeyond(ConnectionState::Closing)) + // When we have a `wstr` that's ready for processing we must do so without blocking. + // Otherwise, whatever the user typed will be delayed until the next IO operation. + // With overlapped IO that's not a problem because the ReadFile() calls won't block. + if (!ReadFile(_pipe.get(), &buffer[0], sizeof(buffer), &read, &overlapped)) { - return 0; + if (GetLastError() != ERROR_IO_PENDING) + { + break; + } + overlappedPending = true; } - if (readFail) // reading failed (we must check this first, because read will also be 0.) + // wstr can be empty in two situations: + // * The previous call to til::u8u16 failed. + // * We're using overlapped IO, and it's the first iteration. + if (!wstr.empty()) { - // EXIT POINT - const auto lastError = GetLastError(); - if (lastError == ERROR_BROKEN_PIPE) + if (!_receivedFirstByte) { - _LastConPtyClientDisconnected(); - return S_OK; + const auto now = std::chrono::high_resolution_clock::now(); + const std::chrono::duration delta = now - _startTime; + +#pragma warning(suppress : 26477 26485 26494 26482 26446) // We don't control TraceLoggingWrite + TraceLoggingWrite(g_hTerminalConnectionProvider, + "ReceivedFirstByte", + TraceLoggingDescription("An event emitted when the connection receives the first byte"), + TraceLoggingGuid(_sessionId, "SessionGuid", "The WT_SESSION's GUID"), + TraceLoggingFloat64(delta.count(), "Duration"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); + _receivedFirstByte = true; } - else + + try { - _indicateExitWithStatus(HRESULT_FROM_WIN32(lastError)); // print a message - _transitionToState(ConnectionState::Failed); - return gsl::narrow_cast(HRESULT_FROM_WIN32(lastError)); + TerminalOutput.raise(wstr); } + CATCH_LOG(); } - const auto result{ til::u8u16(std::string_view{ &buffer[0], read }, wstr, u8State) }; - if (FAILED(result)) + // Here's the counterpart to the start of the loop. We processed whatever was in `wstr`, + // so blocking synchronously on the pipe is now possible. + // If we used overlapped IO, we need to wait for the ReadFile() to complete. + // If we didn't, we can now safely block on our ReadFile() call. + if (overlappedPending) { - // EXIT POINT - _indicateExitWithStatus(result); // print a message - _transitionToState(ConnectionState::Failed); - return gsl::narrow_cast(result); + overlappedPending = false; + if (FAILED(Utils::GetOverlappedResultSameThread(&overlapped, &read))) + { + break; + } } - if (wstr.empty()) + // winsock2 (WSA) handles of the \Device\Afd type are transparently compatible with + // ReadFile() and the WSARecv() documentations contains this important information: + // > For byte streams, zero bytes having been read [..] indicates graceful closure and that no more bytes will ever be read. + // --> Exit if we've read 0 bytes. + if (read == 0) { - return 0; + break; } - if (!_receivedFirstByte) + if (_isStateAtOrBeyond(ConnectionState::Closing)) { - const auto now = std::chrono::high_resolution_clock::now(); - const std::chrono::duration delta = now - _startTime; - -#pragma warning(suppress : 26477 26485 26494 26482 26446) // We don't control TraceLoggingWrite - TraceLoggingWrite(g_hTerminalConnectionProvider, - "ReceivedFirstByte", - TraceLoggingDescription("An event emitted when the connection receives the first byte"), - TraceLoggingGuid(_sessionId, "SessionGuid", "The WT_SESSION's GUID"), - TraceLoggingFloat64(delta.count(), "Duration"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); - _receivedFirstByte = true; + break; } - // Pass the output to our registered event handlers - TerminalOutput.raise(wstr); + TraceLoggingWrite( + g_hTerminalConnectionProvider, + "ReadFile", + TraceLoggingCountedUtf8String(&buffer[0], read, "buffer"), + TraceLoggingGuid(_sessionId, "session"), + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), + TraceLoggingKeyword(TIL_KEYWORD_TRACE)); + + // If we hit a parsing error, eat it. It's bad utf-8, we can't do anything with it. + FAILED_LOG(til::u8u16({ &buffer[0], gsl::narrow_cast(read) }, wstr, u8State)); } return 0; diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h index 5c604ec29a3..6fd476b0cb0 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.h +++ b/src/cascadia/TerminalConnection/ConptyConnection.h @@ -5,9 +5,10 @@ #include "ConptyConnection.g.h" #include "BaseTerminalConnection.h" - #include "ITerminalHandoff.h" + #include +#include namespace winrt::Microsoft::Terminal::TerminalConnection::implementation { @@ -74,12 +75,17 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation bool _receivedFirstByte{ false }; std::chrono::high_resolution_clock::time_point _startTime{}; - wil::unique_hfile _inPipe; // The pipe for writing input to - wil::unique_hfile _outPipe; // The pipe for reading output from + wil::unique_hfile _pipe; wil::unique_handle _hOutputThread; wil::unique_process_information _piClient; wil::unique_any _hPC; + til::ticket_lock _writeLock; + wil::unique_event _writeOverlappedEvent; + OVERLAPPED _writeOverlapped{}; + std::string _writeBuffer; + bool _writePending = false; + DWORD _flags{ 0 }; til::env _initialEnv{}; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 01987a3bd5e..2aec6b1445a 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -52,14 +52,6 @@ void Terminal::Create(til::size viewportSize, til::CoordType scrollbackLines, Re auto dispatch = std::make_unique(*this, &renderer, _renderSettings, _terminalInput); auto engine = std::make_unique(std::move(dispatch)); _stateMachine = std::make_unique(std::move(engine)); - - // Until we have a true pass-through mode (GH#1173), the decision as to - // whether C1 controls are interpreted or not is made at the conhost level. - // If they are being filtered out, then we will simply never receive them. - // But if they are being accepted by conhost, there's a chance they may get - // passed through in some situations, so it's important that our state - // machine is always prepared to accept them. - _stateMachine->SetParserMode(StateMachine::Mode::AlwaysAcceptC1, true); } // Method Description: diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 663a649dc28..2c4b72fe9d3 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -47,7 +47,6 @@ namespace TerminalCoreUnitTests { class TerminalBufferTests; class TerminalApiTest; - class ConptyRoundtripTests; class ScrollTest; }; #endif @@ -150,7 +149,6 @@ class Microsoft::Terminal::Core::Terminal final : void UseAlternateScreenBuffer(const TextAttribute& attrs) override; void UseMainScreenBuffer() override; - bool IsConsolePty() const noexcept override; bool IsVtInputEnabled() const noexcept override; void NotifyAccessibilityChange(const til::rect& changedRect) noexcept override; void NotifyBufferRotation(const int delta) override; @@ -480,7 +478,6 @@ class Microsoft::Terminal::Core::Terminal final : #ifdef UNIT_TESTING friend class TerminalCoreUnitTests::TerminalBufferTests; friend class TerminalCoreUnitTests::TerminalApiTest; - friend class TerminalCoreUnitTests::ConptyRoundtripTests; friend class TerminalCoreUnitTests::ScrollTest; #endif }; diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index f916521d91a..5982d184c52 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -310,11 +310,6 @@ void Terminal::ShowWindow(bool showOrHide) } } -bool Terminal::IsConsolePty() const noexcept -{ - return false; -} - bool Terminal::IsVtInputEnabled() const noexcept { return false; diff --git a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp index c9f2dbb2ae6..2aaaed243f3 100644 --- a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp +++ b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp @@ -273,8 +273,6 @@ namespace ControlUnitTests // contents. ConPTY will handle the actual clearing of the buffer // contents. We can only ensure that the viewport moved when we did a // clear scrollback. - // - // The ConptyRoundtripTests test the actual clearing of the contents. } void ControlCoreTests::TestClearScreen() { @@ -312,8 +310,6 @@ namespace ControlUnitTests // contents. ConPTY will handle the actual clearing of the buffer // contents. We can only ensure that the viewport moved when we did a // clear scrollback. - // - // The ConptyRoundtripTests test the actual clearing of the contents. } void ControlCoreTests::TestClearAll() { @@ -351,8 +347,6 @@ namespace ControlUnitTests // contents. ConPTY will handle the actual clearing of the buffer // contents. We can only ensure that the viewport moved when we did a // clear scrollback. - // - // The ConptyRoundtripTests test the actual clearing of the contents. } void ControlCoreTests::TestReadEntireBuffer() diff --git a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp deleted file mode 100644 index 2f965351b59..00000000000 --- a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp +++ /dev/null @@ -1,5055 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// -// This test class creates an in-proc conpty host as well as a Terminal, to -// validate that strings written to the conpty create the same response on the -// terminal end. Tests can be written that validate both the contents of the -// host buffer as well as the terminal buffer. Every time that -// `renderer.PaintFrame()` is called, the tests will validate the expected -// output, and then flush the output of the VtEngine straight to the Terminal. - -#include "pch.h" -#include "../../types/inc/Viewport.hpp" -#include "../../types/inc/convert.hpp" - -#include "../renderer/inc/DummyRenderer.hpp" -#include "../../renderer/base/Renderer.hpp" -#include "../../renderer/vt/Xterm256Engine.hpp" -#include "../../renderer/vt/XtermEngine.hpp" - -#include "../host/inputBuffer.hpp" -#include "../host/readDataCooked.hpp" -#include "../host/output.h" -#include "../host/_stream.h" // For WriteCharsLegacy -#include "test/CommonState.hpp" - -#include "../cascadia/TerminalCore/Terminal.hpp" - -#include "../../inc/TestUtils.h" - -using namespace WEX::Common; -using namespace WEX::Logging; -using namespace WEX::TestExecution; -using namespace Microsoft::Console::Types; -using namespace Microsoft::Console::Interactivity; -using namespace Microsoft::Console::VirtualTerminal; - -using namespace Microsoft::Console; -using namespace Microsoft::Console::Render; -using namespace Microsoft::Console::Types; - -using namespace Microsoft::Terminal::Core; - -namespace TerminalCoreUnitTests -{ - class ConptyRoundtripTests; -}; -using namespace TerminalCoreUnitTests; - -class TerminalCoreUnitTests::ConptyRoundtripTests final -{ - // !!! DANGER: Many tests in this class expect the Terminal and Host buffers - // to be 80x32. If you change these, you'll probably inadvertently break a - // bunch of tests !!! - static const til::CoordType TerminalViewWidth = 80; - static const til::CoordType TerminalViewHeight = 32; - - // This test class is for tests that are supposed to emit something in the PTY layer - // and then check that they've been staged for presentation correctly inside - // the Terminal application. Which sequences were used to get here don't matter. - TEST_CLASS(ConptyRoundtripTests); - - TEST_CLASS_SETUP(ClassSetup) - { - m_state = std::make_unique(); - - m_state->InitEvents(); - m_state->PrepareGlobalInputBuffer(); - m_state->PrepareGlobalRenderer(); - m_state->PrepareGlobalScreenBuffer(TerminalViewWidth, TerminalViewHeight, TerminalViewWidth, TerminalViewHeight); - - return true; - } - - TEST_CLASS_CLEANUP(ClassCleanup) - { - m_state->CleanupGlobalScreenBuffer(); - m_state->CleanupGlobalRenderer(); - m_state->CleanupGlobalInputBuffer(); - - m_state.release(); - - return true; - } - - TEST_METHOD_SETUP(MethodSetup) - { - // STEP 1: Set up the Terminal - term = std::make_unique(Terminal::TestDummyMarker{}); - emptyRenderer = std::make_unique(term.get()); - term->Create({ TerminalViewWidth, TerminalViewHeight }, 100, *emptyRenderer); - - // STEP 2: Set up the Conpty - - // Set up some sane defaults - auto& g = ServiceLocator::LocateGlobals(); - auto& gci = g.getConsoleInformation(); - - gci.SetColorTableEntry(TextColor::DEFAULT_FOREGROUND, INVALID_COLOR); - gci.SetColorTableEntry(TextColor::DEFAULT_BACKGROUND, INVALID_COLOR); - gci.SetFillAttribute(0x07); // DARK_WHITE on DARK_BLACK - gci.CalculateDefaultColorIndices(); - - 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()); - VERIFY_SUCCEEDED(currentBuffer.SetViewportOrigin(true, { 0, 0 }, true)); - VERIFY_ARE_EQUAL(til::point{}, currentBuffer.GetTextBuffer().GetCursor().GetPosition()); - - // Set up an xterm-256 renderer for conpty - auto hFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - auto initialViewport = currentBuffer.GetViewport(); - - auto vtRenderEngine = std::make_unique(std::move(hFile), - initialViewport); - auto pfn = std::bind(&ConptyRoundtripTests::_writeCallback, this, std::placeholders::_1, std::placeholders::_2); - vtRenderEngine->SetTestCallback(pfn); - - // Enable the resize quirk, as the Terminal is going to be reacting as if it's enabled. - vtRenderEngine->SetResizeQuirk(true); - - // Configure the OutputStateMachine's _pfnFlushToTerminal - // Use OutputStateMachineEngine::SetTerminalConnection - g.pRender->AddRenderEngine(vtRenderEngine.get()); - gci.GetActiveOutputBuffer().SetTerminalConnection(vtRenderEngine.get()); - - _pConApi = std::make_unique(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. - // - // Also, make sure to backdoor enable the resize quirk here too. - g.EnableConptyModeForTests(std::move(vtRenderEngine), true); - - expectedOutput.clear(); - _checkConptyOutput = true; - _logConpty = false; - - VERIFY_ARE_EQUAL(gci.GetActiveOutputBuffer().GetViewport().Dimensions(), - gci.GetActiveOutputBuffer().GetBufferSize().Dimensions(), - L"If this test fails, then there's a good chance " - L"another test resized the buffer but didn't use IsolationLevel:Method"); - VERIFY_ARE_EQUAL(gci.GetActiveOutputBuffer().GetViewport(), - gci.GetActiveOutputBuffer().GetBufferSize(), - L"If this test fails, then there's a good chance " - L"another test resized the buffer but didn't use IsolationLevel:Method"); - - return true; - } - - TEST_METHOD_CLEANUP(MethodCleanup) - { - m_state->CleanupNewTextBufferInfo(); - - auto& engines = ServiceLocator::LocateGlobals().pRender->_engines; - std::fill(engines.begin(), engines.end(), nullptr); - - VERIFY_ARE_EQUAL(0u, expectedOutput.size(), L"Tests should drain all the output they push into the expected output buffer."); - - emptyRenderer = nullptr; - term = nullptr; - - return true; - } - - TEST_METHOD(ConptyOutputTestCanary); - TEST_METHOD(SimpleWriteOutputTest); - TEST_METHOD(WriteTwoLinesUsesNewline); - TEST_METHOD(WriteAFewSimpleLines); - - TEST_METHOD(PassthroughClearScrollback); - - TEST_METHOD(PassthroughClearAll); - - TEST_METHOD(PassthroughHardReset); - - TEST_METHOD(PassthroughCursorShapeImmediately); - - TEST_METHOD(PassthroughDECCTR); - - TEST_METHOD(TestWrappingALongString); - TEST_METHOD(TestAdvancedWrapping); - TEST_METHOD(TestExactWrappingWithoutSpaces); - TEST_METHOD(TestExactWrappingWithSpaces); - - TEST_METHOD(MoveCursorAtEOL); - - TEST_METHOD(TestResizeHeight); - - TEST_METHOD(OutputWrappedLinesAtTopOfBuffer); - TEST_METHOD(OutputWrappedLinesAtBottomOfBuffer); - TEST_METHOD(ScrollWithChangesInMiddle); - TEST_METHOD(DontWrapMoveCursorInSingleFrame); - TEST_METHOD(ClearHostTrickeryTest); - TEST_METHOD(OverstrikeAtBottomOfBuffer); - TEST_METHOD(MarginsWithStatusLine); - TEST_METHOD(OutputWrappedLineWithSpace); - TEST_METHOD(OutputWrappedLineWithSpaceAtBottomOfBuffer); - - TEST_METHOD(BreakLinesOnCursorMovement); - - TEST_METHOD(ScrollWithMargins); - - TEST_METHOD(TestCursorInDeferredEOLPositionOnNewLineWithSpaces); - - TEST_METHOD(ResizeRepaintVimExeBuffer); - - TEST_METHOD(ClsAndClearHostClearsScrollbackTest); - - TEST_METHOD(TestResizeWithCookedRead); - - TEST_METHOD(NewLinesAtBottomWithBackground); - - TEST_METHOD(WrapNewLineAtBottom); - TEST_METHOD(WrapNewLineAtBottomLikeMSYS); - - TEST_METHOD(DeleteWrappedWord); - - TEST_METHOD(HyperlinkIdConsistency); - - TEST_METHOD(ResizeInitializeBufferWithDefaultAttrs); - - TEST_METHOD(ClearBufferSignal); - - TEST_METHOD(SimpleAltBufferTest); - TEST_METHOD(AltBufferToAltBufferTest); - - TEST_METHOD(TestPowerLineFirstFrame); - - TEST_METHOD(AltBufferResizeCrash); - - TEST_METHOD(TestNoExtendedAttrsOptimization); - TEST_METHOD(TestNoBackgroundAttrsOptimization); - - TEST_METHOD(SimplePromptRegions); - TEST_METHOD(MultilinePromptRegions); - TEST_METHOD(ManyMultilinePromptsWithTrailingSpaces); - TEST_METHOD(ReflowPromptRegions); - TEST_METHOD(ClearMarksTest); - -private: - bool _writeCallback(const char* const pch, const size_t cch); - void _flushFirstFrame(); - void _resizeConpty(const til::CoordType sx, const til::CoordType sy); - void _clearConpty(); - void _clear(int clearBufferMethod, SCREEN_INFORMATION& si); - - [[nodiscard]] std::tuple _performResize(const til::size newSize); - - std::deque expectedOutput; - - std::unique_ptr m_state; - std::unique_ptr _pConApi; - - // Tests can set these variables how they link to configure the behavior of the test harness. - bool _checkConptyOutput{ true }; // If true, the test class will check that the output from conpty was expected - bool _logConpty{ false }; // If true, the test class will log all the output from conpty. Helpful for debugging. - - std::unique_ptr emptyRenderer; - std::unique_ptr term; - - ApiRoutines _apiRoutines; -}; - -bool ConptyRoundtripTests::_writeCallback(const char* const pch, const size_t cch) -{ - auto actualString = std::string(pch, cch); - - if (_checkConptyOutput) - { - VERIFY_IS_GREATER_THAN(expectedOutput.size(), - static_cast(0), - NoThrowString().Format(L"writing=\"%hs\", expecting %u strings", TestUtils::ReplaceEscapes(actualString).c_str(), expectedOutput.size())); - - auto first = expectedOutput.front(); - expectedOutput.pop_front(); - - Log::Comment(NoThrowString().Format(L"Expected =\t\"%hs\"", TestUtils::ReplaceEscapes(first).c_str())); - Log::Comment(NoThrowString().Format(L"Actual =\t\"%hs\"", TestUtils::ReplaceEscapes(actualString).c_str())); - - VERIFY_ARE_EQUAL(first.length(), cch); - VERIFY_ARE_EQUAL(first, actualString); - } - else if (_logConpty) - { - Log::Comment(NoThrowString().Format( - L"Writing \"%hs\" to Terminal", TestUtils::ReplaceEscapes(actualString).c_str())); - } - - // Write the string back to our Terminal - const auto converted = ConvertToW(CP_UTF8, actualString); - term->Write(converted); - - return true; -} - -void ConptyRoundtripTests::_flushFirstFrame() -{ - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - - expectedOutput.push_back("\x1b[2J"); - expectedOutput.push_back("\x1b[m"); - expectedOutput.push_back("\x1b[H"); // Go Home - expectedOutput.push_back("\x1b[?25h"); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); -} - -void ConptyRoundtripTests::_resizeConpty(const til::CoordType sx, - const til::CoordType sy) -{ - // Largely taken from implementation in PtySignalInputThread::_InputThread - if (_pConApi->ResizeWindow(sx, sy)) - { - auto& g = ServiceLocator::LocateGlobals(); - auto& gci = g.getConsoleInformation(); - VERIFY_SUCCEEDED(gci.GetVtIo()->SuppressResizeRepaint()); - } -} - -void ConptyRoundtripTests::_clearConpty() -{ - // Taken verbatim from implementation in PtySignalInputThread::_DoClearBuffer - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - THROW_IF_FAILED(gci.GetActiveOutputBuffer().ClearBuffer()); -} - -[[nodiscard]] std::tuple ConptyRoundtripTests::_performResize(const til::size newSize) -{ - // IMPORTANT! Anyone calling this should make sure that the test is running - // in IsolationLevel: Method. If you don't add that, then it might secretly - // pollute other tests! - - Log::Comment(L"========== Resize the Terminal and conpty =========="); - - auto resizeResult = term->UserResize(newSize); - VERIFY_SUCCEEDED(resizeResult); - _resizeConpty(newSize.width, newSize.height); - - // After we resize, make sure to get the new textBuffers - TextBuffer* hostTb = &ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer().GetTextBuffer(); - TextBuffer* termTb = term->_mainBuffer.get(); - return { hostTb, termTb }; -} - -void ConptyRoundtripTests::ConptyOutputTestCanary() -{ - Log::Comment(NoThrowString().Format( - L"This is a simple test to make sure that everything is working as expected.")); - - _flushFirstFrame(); -} - -void ConptyRoundtripTests::SimpleWriteOutputTest() -{ - Log::Comment(NoThrowString().Format( - L"Write some simple output, and make sure it gets rendered largely " - L"unmodified to the terminal")); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& hostSm = si.GetStateMachine(); - auto& termTb = *term->_mainBuffer; - - _flushFirstFrame(); - - expectedOutput.push_back("Hello World"); - hostSm.ProcessString(L"Hello World"); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - TestUtils::VerifyExpectedString(termTb, L"Hello World ", { 0, 0 }); -} - -void ConptyRoundtripTests::WriteTwoLinesUsesNewline() -{ - Log::Comment(NoThrowString().Format( - L"Write two lines of output. We should use \r\n to move the cursor")); - - 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->_mainBuffer; - - _flushFirstFrame(); - - hostSm.ProcessString(L"AAA"); - hostSm.ProcessString(L"\x1b[2;1H"); - hostSm.ProcessString(L"BBB"); - - auto verifyData = [](TextBuffer& tb) { - TestUtils::VerifyExpectedString(tb, L"AAA", { 0, 0 }); - TestUtils::VerifyExpectedString(tb, L"BBB", { 0, 1 }); - }; - - verifyData(hostTb); - - expectedOutput.push_back("AAA"); - expectedOutput.push_back("\r\n"); - expectedOutput.push_back("BBB"); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - verifyData(termTb); -} - -void ConptyRoundtripTests::WriteAFewSimpleLines() -{ - Log::Comment(NoThrowString().Format( - L"Write more lines of output. We should use \r\n to move the cursor")); - - 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->_mainBuffer; - - _flushFirstFrame(); - - hostSm.ProcessString(L"AAA\n"); - hostSm.ProcessString(L"BBB\n"); - hostSm.ProcessString(L"\n"); - hostSm.ProcessString(L"CCC"); - auto verifyData = [](TextBuffer& tb) { - TestUtils::VerifyExpectedString(tb, L"AAA", { 0, 0 }); - TestUtils::VerifyExpectedString(tb, L"BBB", { 0, 1 }); - TestUtils::VerifyExpectedString(tb, L" ", { 0, 2 }); - TestUtils::VerifyExpectedString(tb, L"CCC", { 0, 3 }); - }; - - verifyData(hostTb); - - expectedOutput.push_back("AAA"); - expectedOutput.push_back("\r\n"); - expectedOutput.push_back("BBB"); - // Jump down to the fourth line because emitting spaces didn't do anything - // and we will skip to emitting the CCC segment. - expectedOutput.push_back("\x1b[4;1H"); - expectedOutput.push_back("CCC"); - - // Cursor goes back on. - expectedOutput.push_back("\x1b[?25h"); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - verifyData(termTb); -} - -void ConptyRoundtripTests::TestWrappingALongString() -{ - 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->_mainBuffer; - - _flushFirstFrame(); - _checkConptyOutput = false; - - const auto initialTermView = term->GetViewport(); - - const auto charsToWrite = gsl::narrow_cast(TestUtils::Test100CharsString.size()); - VERIFY_ARE_EQUAL(100, charsToWrite); - - VERIFY_ARE_EQUAL(0, initialTermView.Top()); - VERIFY_ARE_EQUAL(32, initialTermView.BottomExclusive()); - - hostSm.ProcessString(TestUtils::Test100CharsString); - - const auto secondView = term->GetViewport(); - - VERIFY_ARE_EQUAL(0, secondView.Top()); - VERIFY_ARE_EQUAL(32, secondView.BottomExclusive()); - - auto verifyBuffer = [&](const TextBuffer& tb) { - auto& cursor = tb.GetCursor(); - // Verify the cursor wrapped to the second line - VERIFY_ARE_EQUAL(charsToWrite % initialTermView.Width(), cursor.GetPosition().x); - VERIFY_ARE_EQUAL(1, cursor.GetPosition().y); - - // Verify that we marked the 0th row as _wrapped_ - const auto& row0 = tb.GetRowByOffset(0); - VERIFY_IS_TRUE(row0.WasWrapForced()); - - const auto& row1 = tb.GetRowByOffset(1); - VERIFY_IS_FALSE(row1.WasWrapForced()); - - TestUtils::VerifyExpectedString(tb, TestUtils::Test100CharsString, { 0, 0 }); - }; - - verifyBuffer(hostTb); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - verifyBuffer(termTb); -} - -void ConptyRoundtripTests::TestAdvancedWrapping() -{ - 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->_mainBuffer; - const auto initialTermView = term->GetViewport(); - - _flushFirstFrame(); - - const auto charsToWrite = gsl::narrow_cast(TestUtils::Test100CharsString.size()); - VERIFY_ARE_EQUAL(100, charsToWrite); - - hostSm.ProcessString(TestUtils::Test100CharsString); - hostSm.ProcessString(L"\n"); - hostSm.ProcessString(L" "); - hostSm.ProcessString(L"1234567890"); - - auto verifyBuffer = [&](const TextBuffer& tb) { - auto& cursor = tb.GetCursor(); - // Verify the cursor wrapped to the second line - VERIFY_ARE_EQUAL(2, cursor.GetPosition().y); - VERIFY_ARE_EQUAL(20, cursor.GetPosition().x); - - // Verify that we marked the 0th row as _wrapped_ - const auto& row0 = tb.GetRowByOffset(0); - VERIFY_IS_TRUE(row0.WasWrapForced()); - - const auto& row1 = tb.GetRowByOffset(1); - VERIFY_IS_FALSE(row1.WasWrapForced()); - - TestUtils::VerifyExpectedString(tb, TestUtils::Test100CharsString, { 0, 0 }); - TestUtils::VerifyExpectedString(tb, L" 1234567890", { 0, 2 }); - }; - - verifyBuffer(hostTb); - - // First write the first 80 characters from the string - expectedOutput.push_back(R"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)"); - // Without line breaking, write the remaining 20 chars - expectedOutput.push_back(R"(qrstuvwxyz{|}~!"#$%&)"); - // This is the hard line break - expectedOutput.push_back("\r\n"); - // Now write row 2 of the buffer - expectedOutput.push_back(" 1234567890"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - verifyBuffer(termTb); -} - -void ConptyRoundtripTests::TestExactWrappingWithoutSpaces() -{ - // This test (and TestExactWrappingWitSpaces) reveals a bug in the old - // implementation. - // - // If a line _exactly_ wraps to the next line, we can't tell if the line - // should really wrap, or manually break. The client app is writing a line - // that's exactly the width of the buffer that manually breaks at the - // end of the line, followed by another line. - // - // With the old PaintBufferLine interface, there's no way to know if this - // case is because the line wrapped or not. Hence, the addition of the - // `lineWrapped` parameter - - 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->_mainBuffer; - - const auto initialTermView = term->GetViewport(); - - _flushFirstFrame(); - - const auto charsToWrite = initialTermView.Width(); - VERIFY_ARE_EQUAL(80, charsToWrite); - - for (auto i = 0; i < charsToWrite; i++) - { - // This is a handy way of just printing the printable characters that - // _aren't_ the space character. - const auto wch = static_cast(33 + (i % 94)); - hostSm.ProcessCharacter(wch); - } - - hostSm.ProcessString(L"\n"); - hostSm.ProcessString(L"1234567890"); - - auto verifyBuffer = [&](const TextBuffer& tb) { - auto& cursor = tb.GetCursor(); - // Verify the cursor wrapped to the second line - VERIFY_ARE_EQUAL(1, cursor.GetPosition().y); - VERIFY_ARE_EQUAL(10, cursor.GetPosition().x); - - // Verify that we marked the 0th row as _not wrapped_ - const auto& row0 = tb.GetRowByOffset(0); - VERIFY_IS_FALSE(row0.WasWrapForced()); - - const auto& row1 = tb.GetRowByOffset(1); - VERIFY_IS_FALSE(row1.WasWrapForced()); - - TestUtils::VerifyExpectedString(tb, LR"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)", { 0, 0 }); - TestUtils::VerifyExpectedString(tb, L"1234567890", { 0, 1 }); - }; - - verifyBuffer(hostTb); - - // First write the first 80 characters from the string - expectedOutput.push_back(R"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)"); - - // This is the hard line break - expectedOutput.push_back("\r\n"); - // Now write row 2 of the buffer - expectedOutput.push_back("1234567890"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - verifyBuffer(termTb); -} - -void ConptyRoundtripTests::TestExactWrappingWithSpaces() -{ - // This test is also explained by the comment at the top of TestExactWrappingWithoutSpaces - - 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->_mainBuffer; - const auto initialTermView = term->GetViewport(); - - _flushFirstFrame(); - - const auto charsToWrite = initialTermView.Width(); - VERIFY_ARE_EQUAL(80, charsToWrite); - - for (auto i = 0; i < charsToWrite; i++) - { - // This is a handy way of just printing the printable characters that - // _aren't_ the space character. - const auto wch = static_cast(33 + (i % 94)); - hostSm.ProcessCharacter(wch); - } - - hostSm.ProcessString(L"\n"); - hostSm.ProcessString(L" "); - hostSm.ProcessString(L"1234567890"); - - auto verifyBuffer = [&](const TextBuffer& tb) { - auto& cursor = tb.GetCursor(); - // Verify the cursor wrapped to the second line - VERIFY_ARE_EQUAL(1, cursor.GetPosition().y); - VERIFY_ARE_EQUAL(20, cursor.GetPosition().x); - - // Verify that we marked the 0th row as _not wrapped_ - const auto& row0 = tb.GetRowByOffset(0); - VERIFY_IS_FALSE(row0.WasWrapForced()); - - const auto& row1 = tb.GetRowByOffset(1); - VERIFY_IS_FALSE(row1.WasWrapForced()); - - TestUtils::VerifyExpectedString(tb, LR"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)", { 0, 0 }); - TestUtils::VerifyExpectedString(tb, L" 1234567890", { 0, 1 }); - }; - - verifyBuffer(hostTb); - - // First write the first 80 characters from the string - expectedOutput.push_back(R"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)"); - - // This is the hard line break - expectedOutput.push_back("\r\n"); - // Now write row 2 of the buffer - expectedOutput.push_back(" 1234567890"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - verifyBuffer(termTb); -} - -void ConptyRoundtripTests::MoveCursorAtEOL() -{ - // This is a test for GH#1245 - - 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->_mainBuffer; - _flushFirstFrame(); - - Log::Comment(NoThrowString().Format( - L"Write exactly a full line of text")); - hostSm.ProcessString(std::wstring(TerminalViewWidth, L'A')); - - auto verifyData0 = [](TextBuffer& tb) { - auto iter = tb.GetCellDataAt({ 0, 0 }); - TestUtils::VerifySpanOfText(L"A", iter, 0, TerminalViewWidth); - TestUtils::VerifySpanOfText(L" ", iter, 0, TerminalViewWidth); - }; - - verifyData0(hostTb); - - // TODO: GH#405/#4415 - Before #405 merges, the VT sequences conpty emits - // might change, but the buffer contents shouldn't. - // If they do change and these tests break, that's to be expected. - expectedOutput.push_back(std::string(TerminalViewWidth, 'A')); - expectedOutput.push_back("\x1b[1;80H"); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - verifyData0(termTb); - - Log::Comment(NoThrowString().Format( - L"Emulate backspacing at a bash prompt when the previous line wrapped.\n" - L"We'll move the cursor up to the last char of the prev line, and erase it.")); - hostSm.ProcessString(L"\x1b[1;80H"); - hostSm.ProcessString(L"\x1b[K"); - - auto verifyData1 = [](TextBuffer& tb) { - auto iter = tb.GetCellDataAt({ 0, 0 }); - // There should be 79 'A's, followed by a space, and the following line should be blank. - TestUtils::VerifySpanOfText(L"A", iter, 0, TerminalViewWidth - 1); - TestUtils::VerifySpanOfText(L" ", iter, 0, 1); - TestUtils::VerifySpanOfText(L" ", iter, 0, TerminalViewWidth); - - auto& cursor = tb.GetCursor(); - VERIFY_ARE_EQUAL(TerminalViewWidth - 1, cursor.GetPosition().x); - VERIFY_ARE_EQUAL(0, cursor.GetPosition().y); - }; - - verifyData1(hostTb); - - expectedOutput.push_back(" "); - expectedOutput.push_back("\x1b[1;80H"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - verifyData1(termTb); - - // This test is sometimes flaky in cleanup. - expectedOutput.clear(); -} - -void ConptyRoundtripTests::TestResizeHeight() -{ - // This test class is _60_ tests to ensure that resizing the terminal works - // with conpty correctly. There's a lot of min/maxing in expressions here, - // to account for the sheer number of cases here, and that we have to handle - // both resizing larger and smaller all in one test. - - 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}") - TEST_METHOD_PROPERTY(L"Data:printedRows", L"{1, 10, 50, 200}") - END_TEST_METHOD_PROPERTIES() - int dx, dy; - int printedRows; - 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"); - VERIFY_SUCCEEDED(TestData::TryGetValue(L"printedRows", printedRows), L"Number of rows of text to print"); - - _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->_mainBuffer.get(); - const auto initialHostView = si.GetViewport(); - const auto initialTermView = term->GetViewport(); - const auto initialTerminalBufferHeight = term->GetTextBuffer().GetSize().Height(); - - 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 %d lines of output, which will scroll the viewport", printedRows)); - - for (auto i = 0; i < printedRows; i++) - { - // This looks insane, but this expression is carefully crafted to give - // us only printable characters, starting with `!` (0n33). - // Similar statements are used elsewhere throughout this test. - auto wstr = std::wstring(1, static_cast((i) % 93) + 33); - hostSm.ProcessString(wstr); - hostSm.ProcessString(L"\r\n"); - } - - // Conpty doesn't have a scrollback, its 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(); - // If we've printed more lines than the height of the buffer, then we're - // expecting the viewport to have moved down. Otherwise, the terminal's - // viewport will stay at 0,0. - const auto expectedTerminalViewBottom = std::max(std::min(printedRows + 1, term->GetBufferHeight()), term->GetViewport().Height()); - - VERIFY_ARE_EQUAL(expectedTerminalViewBottom, secondTermView.BottomExclusive()); - VERIFY_ARE_EQUAL(expectedTerminalViewBottom - initialTermView.Height(), secondTermView.Top()); - - auto verifyTermData = [&expectedTerminalViewBottom, &printedRows, this, &initialTerminalBufferHeight](TextBuffer& termTb, const int resizeDy = 0) { - // Some number of lines of text were lost from the scrollback. The - // number of lines lost will be determined by whichever of the initial - // or current buffer is smaller. - const auto numLostRows = std::max(0, - printedRows - std::min(term->GetTextBuffer().GetSize().Height(), initialTerminalBufferHeight) + 1); - - const auto rowsWithText = std::min(printedRows, expectedTerminalViewBottom) - 1 + std::min(resizeDy, 0); - - for (til::CoordType row = 0; row < rowsWithText; row++) - { - SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures); - auto iter = termTb.GetCellDataAt({ 0, row }); - const wchar_t expectedChar = static_cast((row + numLostRows) % 93) + 33; - - auto expectedString = std::wstring(1, expectedChar); - - 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, &initialHostView, &printedRows](TextBuffer& hostTb, const int resizeDy = 0) { - const auto hostView = si.GetViewport(); - - // In the host, there are two regions we're interested in: - - // 1. the first section of the buffer with the output in it. Before - // we're resized, this will be filled with one character on each row. - // 2. The second area below the first that's empty (filled with spaces). - // Initially, this is only one row. - // After we resize, different things will happen. - // * If we decrease the height of the buffer, the characters in the - // buffer will all move _up_ the same number of rows. We'll want to - // only check the first initialView+dy rows for characters. - // * If we increase the height, rows will be added at the bottom. We'll - // want to check the initial viewport height for the original - // characters, but then we'll want to look for more blank rows at the - // bottom. The characters in the initial viewport won't have moved. - - const auto originalViewHeight = resizeDy < 0 ? initialHostView.Height() + resizeDy : initialHostView.Height(); - const auto rowsWithText = std::min(originalViewHeight - 1, printedRows); - const auto scrolled = printedRows > initialHostView.Height(); - // 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 last row will have '0'+(50-height+1) - const auto firstChar = static_cast(scrolled ? - (printedRows - originalViewHeight + 1) : - 0); - - til::CoordType row = 0; - // Don't include the last row of the viewport in this check, since it'll - // be blank. We'll check it in the below loop. - for (; row < rowsWithText; row++) - { - SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures); - auto iter = hostTb.GetCellDataAt({ 0, row }); - - const auto expectedChar = static_cast(((firstChar + row) % 93) + 33); - auto expectedString = std::wstring(1, static_cast(expectedChar)); - - 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 that the remaining rows in the viewport are empty. - for (; row < hostView.Height(); row++) - { - SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures); - auto iter = hostTb.GetCellDataAt({ 0, row }); - VERIFY_ARE_EQUAL(L" ", (iter)->Chars()); - } - }; - - verifyHostData(*hostTb); - verifyTermData(*termTb); - - const til::size newViewportSize{ TerminalViewWidth + dx, - TerminalViewHeight + dy }; - // After we resize, make sure to get the new textBuffers - std::tie(hostTb, termTb) = _performResize(newViewportSize); - - // Conpty's doesn't have a scrollback, its view's origin is always 0,0 - const auto thirdHostView = si.GetViewport(); - VERIFY_ARE_EQUAL(0, thirdHostView.Top()); - VERIFY_ARE_EQUAL(newViewportSize.height, thirdHostView.BottomExclusive()); - - // The Terminal should be stuck to the top of the viewport, unless dy<0, - // rows=50. In that set of cases, we _didn't_ pin the top of the Terminal to - // the old top, we actually shifted it down (because the output was at the - // bottom of the window, not empty lines). - const auto thirdTermView = term->GetViewport(); - if (dy < 0 && (printedRows > initialTermView.Height() && printedRows < initialTerminalBufferHeight)) - { - VERIFY_ARE_EQUAL(secondTermView.Top() - dy, thirdTermView.Top()); - VERIFY_ARE_EQUAL(expectedTerminalViewBottom, thirdTermView.BottomExclusive()); - } - else - { - VERIFY_ARE_EQUAL(secondTermView.Top(), thirdTermView.Top()); - VERIFY_ARE_EQUAL(expectedTerminalViewBottom + dy, thirdTermView.BottomExclusive()); - } - - verifyHostData(*hostTb, dy); - // Note that at this point, nothing should have changed with the Terminal. - verifyTermData(*termTb, dy); - - Log::Comment(NoThrowString().Format(L"Paint a frame to update the Terminal")); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - // Conpty's doesn't have a scrollback, its view's origin is always 0,0 - const auto fourthHostView = si.GetViewport(); - VERIFY_ARE_EQUAL(0, fourthHostView.Top()); - VERIFY_ARE_EQUAL(newViewportSize.height, fourthHostView.BottomExclusive()); - - // The Terminal should be stuck to the top of the viewport, unless dy<0, - // rows=50. In that set of cases, we _didn't_ pin the top of the Terminal to - // the old top, we actually shifted it down (because the output was at the - // bottom of the window, not empty lines). - const auto fourthTermView = term->GetViewport(); - if (dy < 0 && (printedRows > initialTermView.Height() && printedRows < initialTerminalBufferHeight)) - { - VERIFY_ARE_EQUAL(secondTermView.Top() - dy, thirdTermView.Top()); - VERIFY_ARE_EQUAL(expectedTerminalViewBottom, thirdTermView.BottomExclusive()); - } - else - { - VERIFY_ARE_EQUAL(secondTermView.Top(), thirdTermView.Top()); - VERIFY_ARE_EQUAL(expectedTerminalViewBottom + dy, thirdTermView.BottomExclusive()); - } - verifyHostData(*hostTb, dy); - verifyTermData(*termTb, dy); -} - -void ConptyRoundtripTests::PassthroughCursorShapeImmediately() -{ - // This is a test for GH#4106, and more indirectly, GH #2011. - - Log::Comment(NoThrowString().Format( - L"Change the cursor shape with VT. This should immediately be flushed to the Terminal.")); - - auto& g = ServiceLocator::LocateGlobals(); - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& hostSm = si.GetStateMachine(); - auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_mainBuffer; - - _flushFirstFrame(); - - _logConpty = true; - - VERIFY_ARE_NOT_EQUAL(CursorType::VerticalBar, hostTb.GetCursor().GetType()); - VERIFY_ARE_NOT_EQUAL(CursorType::VerticalBar, termTb.GetCursor().GetType()); - - expectedOutput.push_back("\x1b[5 q"); - hostSm.ProcessString(L"\x1b[5 q"); - - VERIFY_ARE_EQUAL(CursorType::VerticalBar, hostTb.GetCursor().GetType()); - VERIFY_ARE_EQUAL(CursorType::VerticalBar, termTb.GetCursor().GetType()); -} - -void ConptyRoundtripTests::PassthroughClearScrollback() -{ - Log::Comment(NoThrowString().Format( - L"Write more lines of output than there are lines in the viewport. Clear the scrollback with ^[[3J")); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& hostSm = si.GetStateMachine(); - - auto& termTb = *term->_mainBuffer; - - _flushFirstFrame(); - - _logConpty = true; - - const auto hostView = si.GetViewport(); - const auto end = 2 * hostView.Height(); - for (auto i = 0; i < end; i++) - { - Log::Comment(NoThrowString().Format(L"Writing line %d/%d", i, end)); - expectedOutput.push_back("X"); - if (i < hostView.BottomInclusive()) - { - expectedOutput.push_back("\r\n"); - } - else - { - // After we hit the bottom of the viewport, the newlines come in - // separated by empty writes for whatever reason. - expectedOutput.push_back("\r"); - expectedOutput.push_back("\n"); - expectedOutput.push_back(""); - } - - hostSm.ProcessString(L"X\n"); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); - } - - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - // Verify that we've printed height*2 lines of X's to the Terminal - const auto termFirstView = term->GetViewport(); - for (til::CoordType y = 0; y < 2 * termFirstView.Height(); y++) - { - TestUtils::VerifyExpectedString(termTb, L"X ", { 0, y }); - } - - // Write a Erase Scrollback VT sequence to the host, it should come through to the Terminal - expectedOutput.push_back("\x1b[3J"); - hostSm.ProcessString(L"\x1b[3J"); - - _checkConptyOutput = false; - - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - const auto termSecondView = term->GetViewport(); - VERIFY_ARE_EQUAL(0, termSecondView.Top()); - - // Verify the top of the Terminal viewport contains the contents of the old viewport - for (til::CoordType y = 0; y < termSecondView.BottomInclusive(); y++) - { - TestUtils::VerifyExpectedString(termTb, L"X ", { 0, y }); - } - - // Verify below the new viewport (the old viewport) has been cleared out - for (auto y = termSecondView.BottomInclusive(); y < termFirstView.BottomInclusive(); y++) - { - TestUtils::VerifyExpectedString(termTb, std::wstring(TerminalViewWidth, L' '), { 0, y }); - } -} - -void ConptyRoundtripTests::PassthroughClearAll() -{ - // see https://github.com/microsoft/terminal/issues/2832 - Log::Comment(L"This is a test to make sure that when the client emits a " - L"^[[2J, we actually forward the 2J to the terminal, to move " - L"the viewport. 2J importantly moves the viewport, so that " - L"all the _current_ buffer contents are moved to scrollback. " - L"We shouldn't just paint over the current viewport with spaces."); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_mainBuffer.get(); - - auto& sm = si.GetStateMachine(); - - _flushFirstFrame(); - - _checkConptyOutput = false; - _logConpty = true; - - const auto hostView = si.GetViewport(); - const auto end = 2 * hostView.Height(); - for (auto i = 0; i < end; i++) - { - if (i > 0) - { - sm.ProcessString(L"\r\n"); - } - - sm.ProcessString(L"~"); - } - - auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& viewport, const bool afterClear = false) { - const auto width = viewport.width(); - - // "~" rows - for (til::CoordType row = 0; row < viewport.bottom; row++) - { - Log::Comment(NoThrowString().Format(L"Checking row %d", row)); - VERIFY_IS_FALSE(tb.GetRowByOffset(row).WasWrapForced()); - auto iter = tb.GetCellDataAt({ 0, row }); - if (afterClear && row >= viewport.top) - { - TestUtils::VerifySpanOfText(L" ", iter, 0, width); - } - else - { - TestUtils::VerifySpanOfText(L"~", iter, 0, 1); - TestUtils::VerifySpanOfText(L" ", iter, 0, width - 1); - } - } - }; - - Log::Comment(L"========== Checking the host buffer state (before) =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive()); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the terminal buffer state (before) =========="); - verifyBuffer(*termTb, term->_mutableViewport.ToExclusive()); - - const til::rect originalTerminalView{ term->_mutableViewport.ToInclusive() }; - - // Here, we'll emit the 2J to EraseAll, and move the viewport contents into - // the scrollback. - sm.ProcessString(L"\x1b[2J"); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - // Make sure that the terminal's new viewport is actually just lower than it - // used to be. - const til::rect newTerminalView{ term->_mutableViewport.ToInclusive() }; - VERIFY_ARE_EQUAL(end, newTerminalView.top); - VERIFY_IS_GREATER_THAN(newTerminalView.top, originalTerminalView.top); - - Log::Comment(L"========== Checking the host buffer state (after) =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive(), true); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - Log::Comment(L"========== Checking the terminal buffer state (after) =========="); - verifyBuffer(*termTb, newTerminalView, true); -} - -void ConptyRoundtripTests::PassthroughHardReset() -{ - // This test is highly similar to PassthroughClearScrollback. - Log::Comment(NoThrowString().Format( - L"Write more lines of output than there are lines in the viewport. Clear everything with ^[c")); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& hostSm = si.GetStateMachine(); - - auto& termTb = *term->_mainBuffer; - - _flushFirstFrame(); - - _logConpty = true; - - const auto hostView = si.GetViewport(); - const auto end = 2 * hostView.Height(); - for (auto i = 0; i < end; i++) - { - Log::Comment(NoThrowString().Format(L"Writing line %d/%d", i, end)); - expectedOutput.push_back("X"); - if (i < hostView.BottomInclusive()) - { - expectedOutput.push_back("\r\n"); - } - else - { - // After we hit the bottom of the viewport, the newlines come in - // separated by empty writes for whatever reason. - expectedOutput.push_back("\r"); - expectedOutput.push_back("\n"); - expectedOutput.push_back(""); - } - - hostSm.ProcessString(L"X\n"); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); - } - - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - // Verify that we've printed height*2 lines of X's to the Terminal - const auto termFirstView = term->GetViewport(); - for (til::CoordType y = 0; y < 2 * termFirstView.Height(); y++) - { - TestUtils::VerifyExpectedString(termTb, L"X ", { 0, y }); - } - - // Write a Hard Reset VT sequence to the host, it should come through to the Terminal - // along with a DECSET sequence to re-enable win32 input and focus events. - expectedOutput.push_back("\033c"); - expectedOutput.push_back("\033[?9001h\033[?1004h"); - hostSm.ProcessString(L"\033c"); - - const auto termSecondView = term->GetViewport(); - VERIFY_ARE_EQUAL(0, termSecondView.Top()); - - // Verify everything has been cleared out - for (til::CoordType y = 0; y < termFirstView.BottomInclusive(); y++) - { - TestUtils::VerifyExpectedString(termTb, std::wstring(TerminalViewWidth, L' '), { 0, y }); - } -} - -void ConptyRoundtripTests::PassthroughDECCTR() -{ - Log::Comment(L"Update the color table with DECCTR. This should immediately be flushed to the Terminal."); - - auto& g = ServiceLocator::LocateGlobals(); - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& hostSm = si.GetStateMachine(); - auto& termRenderSettings = term->GetRenderSettings(); - - _flushFirstFrame(); - - _logConpty = true; - - // We're going to update color table entries 101 through 103, so we start by - // initializing those entries to black in the Terminal render settings. - termRenderSettings.SetColorTableEntry(101, RGB(0, 0, 0)); - termRenderSettings.SetColorTableEntry(102, RGB(0, 0, 0)); - termRenderSettings.SetColorTableEntry(103, RGB(0, 0, 0)); - - // DECCTR is expected to arrive in two parts. The control sequence comes - // first, and the string content follows in the second packet. - expectedOutput.push_back("\x1bP2$p"); - expectedOutput.push_back("101;2;100;0;0/102;2;0;100;0/103;2;0;0;100\x1b\\"); - - // Send the control sequence to the host. - hostSm.ProcessString(L"\x1bP2$p101;2;100;0;0/102;2;0;100;0/103;2;0;0;100\x1b\\"); - - // Verify that the color table entries have been updated in the Terminal. - VERIFY_ARE_EQUAL(RGB(255, 0, 0), termRenderSettings.GetColorTableEntry(101)); - VERIFY_ARE_EQUAL(RGB(0, 255, 0), termRenderSettings.GetColorTableEntry(102)); - VERIFY_ARE_EQUAL(RGB(0, 0, 255), termRenderSettings.GetColorTableEntry(103)); - - termRenderSettings.ResetColorTable(); -} - -void ConptyRoundtripTests::OutputWrappedLinesAtTopOfBuffer() -{ - Log::Comment( - L"Case 1: Write a wrapped line right at the start of the buffer, before any circling"); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_mainBuffer; - - _flushFirstFrame(); - - const auto wrappedLineLength = TerminalViewWidth + 20; - - sm.ProcessString(std::wstring(wrappedLineLength, L'A')); - - auto verifyBuffer = [](const TextBuffer& tb) { - // Buffer contents should look like the following: (80 wide) - // (w) means we hard wrapped the line - // (b) means the line is _not_ wrapped (it's broken, the default state.) - // cursor is on the '_' - // - // |AAAAAAAA...AAAA| (w) - // |AAAAA_ ... | (b) (There are 20 'A's on this line.) - // | ... | (b) - - VERIFY_IS_TRUE(tb.GetRowByOffset(0).WasWrapForced()); - VERIFY_IS_FALSE(tb.GetRowByOffset(1).WasWrapForced()); - auto iter0 = tb.GetCellDataAt({ 0, 0 }); - TestUtils::VerifySpanOfText(L"A", iter0, 0, TerminalViewWidth); - auto iter1 = tb.GetCellDataAt({ 0, 1 }); - TestUtils::VerifySpanOfText(L"A", iter1, 0, 20); - auto iter2 = tb.GetCellDataAt({ 20, 1 }); - TestUtils::VerifySpanOfText(L" ", iter2, 0, TerminalViewWidth - 20); - }; - - verifyBuffer(hostTb); - - expectedOutput.push_back(std::string(TerminalViewWidth, 'A')); - expectedOutput.push_back(std::string(20, 'A')); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - verifyBuffer(termTb); -} - -void ConptyRoundtripTests::OutputWrappedLinesAtBottomOfBuffer() -{ - Log::Comment( - L"Case 2: Write a wrapped line at the end of the buffer, once the conpty started circling"); - - 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->_mainBuffer; - - _flushFirstFrame(); - - // First, fill the buffer with contents, so conpty starts circling - - const auto hostView = si.GetViewport(); - const auto end = 2 * hostView.Height(); - for (auto i = 0; i < end; i++) - { - Log::Comment(NoThrowString().Format(L"Writing line %d/%d", i, end)); - expectedOutput.push_back("X"); - if (i < hostView.BottomInclusive()) - { - expectedOutput.push_back("\r\n"); - } - else - { - // After we hit the bottom of the viewport, the newlines come in - // separated by empty writes for whatever reason. - expectedOutput.push_back("\r"); - expectedOutput.push_back("\n"); - expectedOutput.push_back(""); - } - - hostSm.ProcessString(L"X\n"); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); - } - - const auto wrappedLineLength = TerminalViewWidth + 20; - - // The following diagrams show the buffer contents after each string emitted - // from conpty. For each of these diagrams: - // (w) means we hard wrapped the line - // (b) means the line is _not_ wrapped (it's broken, the default state.) - // cursor is on the '_' - - // Initial state: - // |X | (b) - // |X | (b) - // ... - // |X | (b) - // |_ | (b) - - expectedOutput.push_back(std::string(TerminalViewWidth, 'A')); - // |X | (b) - // |X | (b) - // ... - // |X | (b) - // |AAAAAAAA...AAAA|_ (w) The cursor is actually on the last A here - - // TODO GH#5228 might break the "newline & repaint the wrapped char" checks here, that's okay. - expectedOutput.push_back("\r"); // This \r\n is emitted by ScrollFrame to - expectedOutput.push_back("\n"); // add a newline to the bottom of the buffer - // |X | (b) - // |X | (b) - // ... - // |X | (b) - // |AAAAAAAA...AAAA| (b) - // |_ | (b) - - expectedOutput.push_back("\x1b[31;80H"); // Move the cursor BACK to the wrapped row - // |X | (b) - // |X | (b) - // ... - // |X | (b) - // |AAAAAAAA...AAAA| (b) The cursor is actually on the last A here - // | | (b) - - expectedOutput.push_back(std::string(1, 'A')); // Reprint the last character of the wrapped row - // |X | (b) - // |X | (b) - // ... - // |X | (b) - // |AAAAAAAA...AAAA|_ (w) The cursor is actually on the last A here - // | | (b) - - expectedOutput.push_back(std::string(20, 'A')); // Print the second line. - // |X | (b) - // |X | (b) - // ... - // |X | (b) - // |AAAAAAAA...AAAA| (w) - // |AAAAA_ | (b) There are 20 'A's on this line. - - hostSm.ProcessString(std::wstring(wrappedLineLength, L'A')); - - auto verifyBuffer = [](const TextBuffer& tb, const til::CoordType wrappedRow) { - // Buffer contents should look like the following: (80 wide) - // (w) means we hard wrapped the line - // (b) means the line is _not_ wrapped (it's broken, the default state.) - // cursor is on the '_' - // - // |X | (b) - // |X | (b) - // ... - // |X | (b) - // |AAAAAAAA...AAAA| (w) - // |AAAAA_ ... | (b) (There are 20 'A's on this line.) - - VERIFY_IS_TRUE(tb.GetRowByOffset(wrappedRow).WasWrapForced()); - VERIFY_IS_FALSE(tb.GetRowByOffset(wrappedRow + 1).WasWrapForced()); - - auto iter0 = tb.GetCellDataAt({ 0, wrappedRow }); - TestUtils::VerifySpanOfText(L"A", iter0, 0, TerminalViewWidth); - auto iter1 = tb.GetCellDataAt({ 0, wrappedRow + 1 }); - TestUtils::VerifySpanOfText(L"A", iter1, 0, 20); - auto iter2 = tb.GetCellDataAt({ 20, wrappedRow + 1 }); - TestUtils::VerifySpanOfText(L" ", iter2, 0, TerminalViewWidth - 20); - }; - - verifyBuffer(hostTb, hostView.BottomInclusive() - 1); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - verifyBuffer(termTb, term->_mutableViewport.BottomInclusive() - 1); -} - -void ConptyRoundtripTests::ScrollWithChangesInMiddle() -{ - Log::Comment(L"This test checks emitting a wrapped line at the bottom of the" - L" viewport while _also_ emitting other text elsewhere in the same frame. This" - L" output will cause us to scroll the viewport in one frame, but we need to" - L" make sure the wrapped line _stays_ wrapped, and the scrolled text appears in" - L" the right place."); - - 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->_mainBuffer; - - _flushFirstFrame(); - - // First, fill the buffer with contents, so conpty starts circling - - const auto hostView = si.GetViewport(); - const auto end = 2 * hostView.Height(); - for (auto i = 0; i < end; i++) - { - Log::Comment(NoThrowString().Format(L"Writing line %d/%d", i, end)); - expectedOutput.push_back("X"); - if (i < hostView.BottomInclusive()) - { - expectedOutput.push_back("\r\n"); - } - else - { - // After we hit the bottom of the viewport, the newlines come in - // separated by empty writes for whatever reason. - expectedOutput.push_back("\r"); - expectedOutput.push_back("\n"); - expectedOutput.push_back(""); - } - - hostSm.ProcessString(L"X\n"); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); - } - - const auto wrappedLineLength = TerminalViewWidth + 20; - - // In the Terminal, we're going to expect: - expectedOutput.push_back("\x1b[15;1H"); // Move the cursor to row 14, col 0 - expectedOutput.push_back("Y"); // Print a 'Y' - expectedOutput.push_back("\x1b[32;1H"); // Move the cursor to the last row - expectedOutput.push_back(std::string(TerminalViewWidth, 'A')); // Print the first 80 'A's - // This is going to be the end of the first frame - b/c we moved the cursor - // in the middle of the frame, we're going to hide/show the cursor during - // this frame - expectedOutput.push_back("\x1b[?25h"); // hide the cursor - // On the subsequent frame: - // TODO GH#5228 might break the "newline & repaint the wrapped char" checks here, that's okay. - expectedOutput.push_back("\r"); // This \r\n is emitted by ScrollFrame to - expectedOutput.push_back("\n"); // add a newline to the bottom of the buffer - expectedOutput.push_back("\x1b[31;80H"); // Move the cursor BACK to the wrapped row - expectedOutput.push_back(std::string(1, 'A')); // Reprint the last character of the wrapped row - expectedOutput.push_back(std::string(20, 'A')); // Print the second line. - - _logConpty = true; - - // To the host, we'll do something very similar: - hostSm.ProcessString(L"\x1b" - L"7"); // Save cursor - hostSm.ProcessString(L"\x1b[15;1H"); // Move the cursor to row 14, col 0 - hostSm.ProcessString(L"Y"); // Print a 'Y' - hostSm.ProcessString(L"\x1b" - L"8"); // Restore - hostSm.ProcessString(std::wstring(wrappedLineLength, L'A')); // Print 100 'A's - - auto verifyBuffer = [](const TextBuffer& tb, const til::rect& viewport) { - const auto wrappedRow = viewport.bottom - 2; - const auto start = viewport.top; - for (auto i = start; i < wrappedRow; i++) - { - Log::Comment(NoThrowString().Format(L"Checking row %d", i)); - TestUtils::VerifyExpectedString(tb, i == start + 13 ? L"Y" : L"X", { 0, i }); - } - - VERIFY_IS_TRUE(tb.GetRowByOffset(wrappedRow).WasWrapForced()); - VERIFY_IS_FALSE(tb.GetRowByOffset(wrappedRow + 1).WasWrapForced()); - - auto iter0 = tb.GetCellDataAt({ 0, wrappedRow }); - TestUtils::VerifySpanOfText(L"A", iter0, 0, TerminalViewWidth); - - auto iter1 = tb.GetCellDataAt({ 0, wrappedRow + 1 }); - TestUtils::VerifySpanOfText(L"A", iter1, 0, 20); - auto iter2 = tb.GetCellDataAt({ 20, wrappedRow + 1 }); - TestUtils::VerifySpanOfText(L" ", iter2, 0, TerminalViewWidth - 20); - }; - - Log::Comment(NoThrowString().Format(L"Checking the host buffer...")); - verifyBuffer(hostTb, hostView.ToExclusive()); - Log::Comment(NoThrowString().Format(L"... Done")); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(NoThrowString().Format(L"Checking the terminal buffer...")); - verifyBuffer(termTb, term->_mutableViewport.ToExclusive()); - Log::Comment(NoThrowString().Format(L"... Done")); -} - -void ConptyRoundtripTests::ScrollWithMargins() -{ - 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->_mainBuffer; - const auto initialTermView = term->GetViewport(); - - Log::Comment(L"Flush first frame."); - _flushFirstFrame(); - - // Fill up the buffer with some text. - // We're going to write something like this: - // AAAA - // BBBB - // CCCC - // ........ - // QQQQ - // **************** - // The letters represent the data in the TMUX pane. - // The final *** line represents the mode line which we will - // attempt to hold in place and not scroll. - // Note that the last line will contain one '*' less than the width of the window. - - Log::Comment(L"Fill host with text pattern by feeding it into VT parser."); - const auto rowsToWrite = initialTermView.Height() - 1; - - // For all lines but the last one, write out a few of a letter. - for (auto i = 0; i < rowsToWrite; ++i) - { - const auto wch = static_cast(L'A' + i); - hostSm.ProcessCharacter(wch); - hostSm.ProcessCharacter(wch); - hostSm.ProcessCharacter(wch); - hostSm.ProcessCharacter(wch); - hostSm.ProcessCharacter('\n'); - } - - // For the last one, write out the asterisks for the mode line. - for (auto i = 0; i < initialTermView.Width() - 1; ++i) - { - hostSm.ProcessCharacter('*'); - } - - // no newline in the bottom right corner or it will move unexpectedly. - - // Now set up the verification that the buffers are full of the pattern we expect. - // This function will verify the text backing buffers. - auto verifyBuffer = [&](const TextBuffer& tb) { - auto& cursor = tb.GetCursor(); - // Verify the cursor is waiting in the bottom right corner - VERIFY_ARE_EQUAL(initialTermView.Height() - 1, cursor.GetPosition().y); - VERIFY_ARE_EQUAL(initialTermView.Width() - 1, cursor.GetPosition().x); - - // For all rows except the last one, verify that we have a run of four letters. - for (auto i = 0; i < rowsToWrite; ++i) - { - const std::wstring expectedString(4, static_cast(L'A' + i)); - const til::point expectedPos{ 0, i }; - TestUtils::VerifyExpectedString(tb, expectedString, expectedPos); - } - - // For the last row, verify we have an entire row of asterisks for the mode line. - const std::wstring expectedModeLine(initialTermView.Width() - 1, L'*'); - const til::point expectedPos{ 0, rowsToWrite }; - TestUtils::VerifyExpectedString(tb, expectedModeLine, expectedPos); - }; - - // This will verify the text emitted from the PTY. - for (auto i = 0; i < rowsToWrite; ++i) - { - const std::string expectedString(4, static_cast('A' + i)); - expectedOutput.push_back(expectedString); - expectedOutput.push_back("\r\n"); - } - { - const std::string expectedString(initialTermView.Width() - 1, '*'); - expectedOutput.push_back(expectedString); - } - - Log::Comment(L"Verify host buffer contains pattern."); - // Verify the host side. - verifyBuffer(hostTb); - - Log::Comment(L"Emit PTY frame and validate it transmits the right data."); - // Paint the frame - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"Verify terminal buffer contains pattern."); - // Verify the terminal side. - verifyBuffer(termTb); - - Log::Comment(L"!!! OK. Set up the scroll region and let's get scrolling!"); - // This is a simulation of what TMUX does to scroll everything except the mode line. - // First build up our VT strings... - std::wstring reducedScrollRegion; - { - std::wstringstream wss; - // For 20 tall buffer... - // ESC[1;19r - // Set scroll region to lines 1-19. - wss << L"\x1b[1;" << initialTermView.Height() - 1 << L"r"; - reducedScrollRegion = wss.str(); - } - std::wstring completeScrollRegion; - { - std::wstringstream wss; - // For 20 tall buffer... - // ESC[1;20r - // Set scroll region to lines 1-20. (or the whole buffer) - wss << L"\x1b[1;" << initialTermView.Height() << L"r"; - completeScrollRegion = wss.str(); - } - std::wstring reducedCursorBottomRight; - { - std::wstringstream wss; - // For 20 tall and 100 wide buffer - // ESC[19;100H - // Put cursor on line 19 (1 before last) and the right most column 100. - // (Remember that in VT, we start counting from 1 not 0.) - wss << L"\x1b[" << initialTermView.Height() - 1 << L";" << initialTermView.Width() << "H"; - reducedCursorBottomRight = wss.str(); - } - std::wstring completeCursorAtPromptLine; - { - std::wstringstream wss; - // For 20 tall and 100 wide buffer - // ESC[19;1H - // Put cursor on line 19 (1 before last) and the left most column 1. - // (Remember that in VT, we start counting from 1 not 0.) - wss << L"\x1b[" << initialTermView.Height() - 1 << L";1H"; - completeCursorAtPromptLine = wss.str(); - } - - Log::Comment(L"Perform all the operations on the buffer."); - - // OK this is what TMUX does. - // 1. Mark off the scroll area as everything but the mode line. - hostSm.ProcessString(reducedScrollRegion); - // 2. Put the cursor in the bottom-right corner of the scroll region. - hostSm.ProcessString(reducedCursorBottomRight); - // 3. Send a single newline which should do the heavy lifting - // of pushing everything in the scroll region up by 1 line and - // leave everything outside the region alone. - - // This entire block is subject to change in the future with optimizations. - { - // Cursor gets redrawn in the bottom right of the scroll region with the repaint that is forced - // early while the screen is rotated. - std::stringstream ss; - ss << "\x1b[" << initialTermView.Height() - 1 << ";" << initialTermView.Width() << "H"; - expectedOutput.push_back(ss.str()); - - expectedOutput.push_back("\x1b[?25h"); // turn the cursor back on too. - } - - hostSm.ProcessString(L"\n"); - // 4. Remove the scroll area by setting it to the entire size of the viewport. - hostSm.ProcessString(completeScrollRegion); - // 5. Put the cursor back at the beginning of the new line that was just revealed. - hostSm.ProcessString(completeCursorAtPromptLine); - - // Set up the verifications like above. - auto verifyBufferAfter = [&](const TextBuffer& tb, const auto panOffset) { - auto& cursor = tb.GetCursor(); - // Verify the cursor is waiting on the freshly revealed line (1 above mode line) - // and in the left most column. - const auto bottomLine = initialTermView.BottomInclusive() + panOffset; - VERIFY_ARE_EQUAL(bottomLine - 1, cursor.GetPosition().y); - VERIFY_ARE_EQUAL(0, cursor.GetPosition().x); - - // For all rows except the last two, verify that we have a run of four letters. - for (auto i = 0; i < rowsToWrite - 1; ++i) - { - // Start with B this time because the A line got scrolled off the top. - const std::wstring expectedString(4, static_cast(L'B' + i)); - const til::point expectedPos{ 0, panOffset + i }; - TestUtils::VerifyExpectedString(tb, expectedString, expectedPos); - } - - // For the second to last row, verify that it is blank. - { - const std::wstring expectedBlankLine(initialTermView.Width(), L' '); - const til::point blankLinePos{ 0, panOffset + rowsToWrite - 1 }; - TestUtils::VerifyExpectedString(tb, expectedBlankLine, blankLinePos); - } - - // For the last row, verify we have an entire row of asterisks for the mode line. - { - const std::wstring expectedModeLine(initialTermView.Width() - 1, L'*'); - const til::point modeLinePos{ 0, panOffset + rowsToWrite }; - TestUtils::VerifyExpectedString(tb, expectedModeLine, modeLinePos); - } - }; - - // This will verify the text emitted from the PTY. - - expectedOutput.push_back("\r\n"); // cursor moved to bottom left corner - expectedOutput.push_back("\n"); // linefeed pans the viewport down - { - // Cursor gets reset into second line from bottom, left most column - std::stringstream ss; - ss << "\x1b[" << initialTermView.Height() - 1 << ";1H"; - expectedOutput.push_back(ss.str()); - } - { - // Bottom of the scroll region is replaced with a blank line - const std::string expectedString(initialTermView.Width(), ' '); - expectedOutput.push_back(expectedString); - } - expectedOutput.push_back("\r\n"); // cursor moved to bottom left corner - { - // Mode line is redrawn at the bottom of the viewport - const std::string expectedString(initialTermView.Width() - 1, '*'); - expectedOutput.push_back(expectedString); - expectedOutput.push_back(" "); - } - { - // Cursor gets reset into second line from bottom, left most column - std::stringstream ss; - ss << "\x1b[" << initialTermView.Height() - 1 << ";1H"; - expectedOutput.push_back(ss.str()); - } - expectedOutput.push_back("\x1b[?25h"); // turn the cursor back on too. - - Log::Comment(L"Verify host buffer contains pattern moved up one and mode line still in place."); - // Verify the host side. - verifyBufferAfter(hostTb, 0); - - Log::Comment(L"Emit PTY frame and validate it transmits the right data."); - // Paint the frame - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"Verify terminal buffer contains pattern moved up one and mode line still in place."); - // Verify the terminal side. Note the viewport has panned down a line. - verifyBufferAfter(termTb, 1); -} - -void ConptyRoundtripTests::DontWrapMoveCursorInSingleFrame() -{ - // See https://github.com/microsoft/terminal/pull/5181#issuecomment-607427840 - Log::Comment(L"This is a test for when a line of text exactly wrapped, but " - L"the cursor didn't end the frame at the end of line (waiting " - L"for more wrapped text). We should still move the cursor in " - L"this case."); - - 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->_mainBuffer; - - _flushFirstFrame(); - - auto verifyBuffer = [](const TextBuffer& tb) { - // Simple verification: Make sure the cursor is in the correct place, - // and that it's visible. We don't care so much about the buffer - // contents in this test. - const til::point expectedCursor{ 8, 3 }; - VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); - VERIFY_IS_TRUE(tb.GetCursor().IsVisible()); - }; - - hostSm.ProcessString(L"\x1b[?25l"); - hostSm.ProcessString(L"\x1b[H"); - hostSm.ProcessString(L"\x1b[75C"); - hostSm.ProcessString(L"XXXXX"); - hostSm.ProcessString(L"\x1b[4;9H"); - hostSm.ProcessString(L"\x1b[?25h"); - - Log::Comment(L"Checking the host buffer state"); - verifyBuffer(hostTb); - - expectedOutput.push_back("\x1b[75C"); - expectedOutput.push_back("XXXXX"); - expectedOutput.push_back("\x1b[4;9H"); - // We're _not_ expecting a cursor on here, because we didn't actually hide - // the cursor during the course of this frame - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"Checking the terminal buffer state"); - verifyBuffer(termTb); -} - -void ConptyRoundtripTests::ClearHostTrickeryTest() -{ - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:paintEachNewline", L"{0, 1, 2}") - TEST_METHOD_PROPERTY(L"Data:cursorOnNextLine", L"{false, true}") - TEST_METHOD_PROPERTY(L"Data:paintAfterDECALN", L"{false, true}") - TEST_METHOD_PROPERTY(L"Data:changeAttributes", L"{false, true}") - TEST_METHOD_PROPERTY(L"Data:useLongSpaces", L"{false, true}") - TEST_METHOD_PROPERTY(L"Data:printTextAfterSpaces", L"{false, true}") - END_TEST_METHOD_PROPERTIES(); - constexpr auto PaintEveryNewline = 0; - constexpr auto PaintAfterAllNewlines = 1; - - INIT_TEST_PROPERTY(int, paintEachNewline, L"Any of: manually PaintFrame after each newline is emitted, once at the end of all newlines, or not at all"); - INIT_TEST_PROPERTY(bool, cursorOnNextLine, L"Either leave the cursor on the first line, or place it on the second line of the buffer"); - INIT_TEST_PROPERTY(bool, paintAfterDECALN, L"Controls whether we manually paint a frame after the DECALN sequence is emitted."); - INIT_TEST_PROPERTY(bool, changeAttributes, L"If true, change the text attributes after the 'A's and spaces"); - INIT_TEST_PROPERTY(bool, useLongSpaces, L"If true, print 10 spaces instead of 5, longer than a CUF sequence."); - INIT_TEST_PROPERTY(bool, printTextAfterSpaces, L"If true, print \"ZZZZZ\" after the spaces on the first line."); - - // See https://github.com/microsoft/terminal/issues/5039#issuecomment-606833841 - Log::Comment(L"This is a more than comprehensive test for GH#5039. We're " - L"going to print some text to the buffer, then fill the alt-" - L"buffer with text, then switch back to the main buffer. The " - L"text from the alt buffer should not pollute the main buffer."); - - // The text we're printing will look like one of the following, with the - // cursor on the _ - // * cursorOnNextLine=false, useLongSpaces=false: - // AAAAA ZZZZZ_ - // * cursorOnNextLine=false, useLongSpaces=true: - // AAAAA ZZZZZ_ - // * cursorOnNextLine=true, useLongSpaces=false: - // AAAAA ZZZZZ - // BBBBB_ - // * cursorOnNextLine=true, useLongSpaces=true: - // AAAAA ZZZZZ - // BBBBB_ - // - // If printTextAfterSpaces=false, then we won't print the "ZZZZZ" - // - // The interesting case that repros the bug in GH#5039 is - // - paintEachNewline=DontPaintAfterNewlines (2) - // - cursorOnNextLine=false - // - paintAfterDECALN= - // - changeAttributes=true - // - useLongSpaces= - // - printTextAfterSpaces= - // - // All the possible cases are left here though, to catch potential future regressions. - - 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->_mainBuffer; - - _flushFirstFrame(); - - auto verifyBuffer = [&cursorOnNextLine, &useLongSpaces, &printTextAfterSpaces](const TextBuffer& tb, - const til::rect viewport) { - // We _would_ expect the Terminal's cursor to be on { 8, 0 }, but this - // is currently broken due to #381/#4676. So we'll use the viewport - // provided to find the actual Y position of the cursor. - const auto viewTop = viewport.origin().y; - const auto cursorRow = viewTop + (cursorOnNextLine ? 1 : 0); - const auto cursorCol = (cursorOnNextLine ? 5 : - (10 + (useLongSpaces ? 5 : 0) + (printTextAfterSpaces ? 5 : 0))); - const til::point expectedCursor{ cursorCol, cursorRow }; - - VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); - VERIFY_IS_TRUE(tb.GetCursor().IsVisible()); - auto iter = TestUtils::VerifyExpectedString(tb, L"AAAAA", { 0, viewTop }); - TestUtils::VerifyExpectedString(useLongSpaces ? L" " : L" ", iter); - if (printTextAfterSpaces) - { - TestUtils::VerifyExpectedString(L"ZZZZZ", iter); - } - else - { - TestUtils::VerifyExpectedString(L" ", iter); - } - TestUtils::VerifyExpectedString(L" ", iter); - - if (cursorOnNextLine) - { - TestUtils::VerifyExpectedString(tb, L"BBBBB", { 0, cursorRow }); - } - }; - - // We're _not_ checking the conpty output during this test, only the side effects. - _checkConptyOutput = false; - - gci.LockConsole(); // Lock must be taken to manipulate alt/main buffer state. - auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); - - Log::Comment(L"Setting up the host buffer..."); - hostSm.ProcessString(L"AAAAA"); - hostSm.ProcessString(useLongSpaces ? L" " : L" "); - if (changeAttributes) - { - hostSm.ProcessString(L"\x1b[44m"); - } - if (printTextAfterSpaces) - { - hostSm.ProcessString(L"ZZZZZ"); - } - hostSm.ProcessString(L"\x1b[0m"); - - if (cursorOnNextLine) - { - hostSm.ProcessString(L"\n"); - hostSm.ProcessString(L"BBBBB"); - } - Log::Comment(L"Painting after the initial setup."); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"Switching to the alt buffer and using DECALN to fill it with 'E's"); - hostSm.ProcessString(L"\x1b[?1049h"); - hostSm.ProcessString(L"\x1b#8"); - if (paintAfterDECALN) - { - VERIFY_SUCCEEDED(renderer.PaintFrame()); - } - - for (auto i = 0; i < si.GetViewport().Height(); i++) - { - hostSm.ProcessString(L"\n"); - if (paintEachNewline == PaintEveryNewline) - { - VERIFY_SUCCEEDED(renderer.PaintFrame()); - } - } - if (paintEachNewline == PaintAfterAllNewlines) - { - VERIFY_SUCCEEDED(renderer.PaintFrame()); - } - Log::Comment(L"Returning to the main buffer."); - hostSm.ProcessString(L"\x1b[?1049l"); - - Log::Comment(L"Checking the host buffer state"); - verifyBuffer(hostTb, si.GetViewport().ToExclusive()); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"Checking the terminal buffer state"); - verifyBuffer(termTb, term->_mutableViewport.ToExclusive()); -} - -void ConptyRoundtripTests::OverstrikeAtBottomOfBuffer() -{ - // See https://github.com/microsoft/terminal/pull/5181#issuecomment-607545241 - Log::Comment(L"This test replicates the zsh menu-complete functionality. In" - L" the course of a single frame, we're going to both scroll " - L"the frame and print multiple lines of text above the bottom line."); - - 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->_mainBuffer; - - _flushFirstFrame(); - - auto verifyBuffer = [](const TextBuffer& tb, - const til::rect viewport) { - const auto lastRow = viewport.bottom - 1; - const til::point expectedCursor{ 0, lastRow - 1 }; - VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); - VERIFY_IS_TRUE(tb.GetCursor().IsVisible()); - - TestUtils::VerifyExpectedString(tb, L"AAAAAAAAAA DDDDDDDDDD", { 0, lastRow - 2 }); - TestUtils::VerifyExpectedString(tb, L"BBBBBBBBBB", { 0, lastRow - 1 }); - TestUtils::VerifyExpectedString(tb, L"FFFFFFFFFE", { 0, lastRow }); - }; - - _logConpty = true; - // We're _not_ checking the conpty output during this test, only the side effects. - _checkConptyOutput = false; - - hostSm.ProcessString(L"\x1b#8"); - - hostSm.ProcessString(L"\x1b[32;1H"); - - hostSm.ProcessString(L"\x1b[J"); - hostSm.ProcessString(L"AAAAAAAAAA"); - hostSm.ProcessString(L"\x1b[K"); - hostSm.ProcessString(L"\r"); - hostSm.ProcessString(L"\n"); - hostSm.ProcessString(L"BBBBBBBBBB"); - hostSm.ProcessString(L"\x1b[K"); - hostSm.ProcessString(L"\n"); - hostSm.ProcessString(L"CCCCCCCCCC"); - hostSm.ProcessString(L"\x1b[2A"); - hostSm.ProcessString(L"\r"); - hostSm.ProcessString(L"\x1b[20C"); - hostSm.ProcessString(L"DDDDDDDDDD"); - hostSm.ProcessString(L"\x1b[K"); - hostSm.ProcessString(L"\r"); - hostSm.ProcessString(L"\n"); - hostSm.ProcessString(L"\x1b[1B"); - hostSm.ProcessString(L"EEEEEEEEEE"); - hostSm.ProcessString(L"\r"); - hostSm.ProcessString(L"FFFFFFFFF"); - hostSm.ProcessString(L"\r"); - hostSm.ProcessString(L"\x1b[A"); - hostSm.ProcessString(L"\x1b[A"); - hostSm.ProcessString(L"\n"); - - Log::Comment(L"========== Checking the host buffer state =========="); - verifyBuffer(hostTb, si.GetViewport().ToExclusive()); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the terminal buffer state =========="); - - verifyBuffer(termTb, term->_mutableViewport.ToExclusive()); -} - -void ConptyRoundtripTests::MarginsWithStatusLine() -{ - // See https://github.com/microsoft/terminal/issues/5161 - // - // This test reproduces a case from the MSYS/cygwin (runtime < 3.1) vim. - // From what I can tell, they implement scrolling by emitting a newline at - // the bottom of the buffer (to create a new blank line), then they use - // ScrollConsoleScreenBuffer to shift the status line(s) down a line, and - // then they re-printing the status line. - Log::Comment(L"Newline, and scroll the bottom lines of the buffer down with" - L" ScrollConsoleScreenBuffer to emulate how cygwin VIM works"); - - 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->_mainBuffer; - - _flushFirstFrame(); - - auto verifyBuffer = [](const TextBuffer& tb, - const til::rect viewport) { - const auto lastRow = viewport.bottom - 1; - const til::point expectedCursor{ 1, lastRow }; - VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); - VERIFY_IS_TRUE(tb.GetCursor().IsVisible()); - - TestUtils::VerifyExpectedString(tb, L"EEEEEEEEEE", { 0, lastRow - 4 }); - TestUtils::VerifyExpectedString(tb, L"AAAAAAAAAA", { 0, lastRow - 3 }); - TestUtils::VerifyExpectedString(tb, L" ", { 0, lastRow - 2 }); - TestUtils::VerifyExpectedString(tb, L"XBBBBBBBBB", { 0, lastRow - 1 }); - TestUtils::VerifyExpectedString(tb, L"YCCCCCCCCC", { 0, lastRow }); - }; - - // We're _not_ checking the conpty output during this test, only the side effects. - _checkConptyOutput = false; - - // Use DECALN to fill the buffer with 'E's. - hostSm.ProcessString(L"\x1b#8"); - - const auto originalBottom = si.GetViewport().BottomInclusive(); - // Print 3 lines into the bottom of the buffer: - // AAAAAAAAAA - // BBBBBBBBBB - // CCCCCCCCCC - // In this test, the 'B' and 'C' lines represent the status lines at the - // bottom of vim, and the 'A' line is a buffer line. - hostSm.ProcessString(L"\x1b[30;1H"); - - hostSm.ProcessString(L"AAAAAAAAAA"); - hostSm.ProcessString(L"\n"); - hostSm.ProcessString(L"BBBBBBBBBB"); - hostSm.ProcessString(L"\n"); - hostSm.ProcessString(L"CCCCCCCCCC"); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - // After printing the 'C' line, the cursor is on the bottom line of the viewport. - // Emit a newline here to get a new line at the bottom of the viewport. - hostSm.ProcessString(L"\n"); - const auto newBottom = si.GetViewport().BottomInclusive(); - - { - // Emulate calling ScrollConsoleScreenBuffer to scroll the B and C lines - // down one line. - til::inclusive_rect src; - src.top = newBottom - 2; - src.left = 0; - src.right = si.GetViewport().Width(); - src.bottom = originalBottom; - til::point tgt{ 0, newBottom - 1 }; - TextAttribute useThisAttr(0x07); // We don't terribly care about the attributes so this is arbitrary - ScrollRegion(si, src, std::nullopt, tgt, L' ', useThisAttr); - } - - // Move the cursor to the location of the B line - hostSm.ProcessString(L"\x1b[31;1H"); - - // Print an 'X' on the 'B' line, and a 'Y' on the 'C' line. - hostSm.ProcessString(L"X"); - hostSm.ProcessString(L"\n"); - hostSm.ProcessString(L"Y"); - - Log::Comment(L"========== Checking the host buffer state =========="); - verifyBuffer(hostTb, si.GetViewport().ToExclusive()); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the terminal buffer state =========="); - - verifyBuffer(termTb, term->_mutableViewport.ToExclusive()); -} - -void ConptyRoundtripTests::OutputWrappedLineWithSpace() -{ - // See https://github.com/microsoft/terminal/pull/5181#issuecomment-610110348 - Log::Comment(L"Ensures that a buffer line in conhost that wrapped _on a " - L"space_ will still be emitted as wrapped."); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_mainBuffer; - - _flushFirstFrame(); - - const auto firstTextLength = TerminalViewWidth - 2; - const auto spacesLength = 3; - const auto secondTextLength = 1; - - sm.ProcessString(std::wstring(firstTextLength, L'A')); - sm.ProcessString(std::wstring(spacesLength, L' ')); - sm.ProcessString(std::wstring(secondTextLength, L'B')); - - auto verifyBuffer = [&](const TextBuffer& tb) { - // Buffer contents should look like the following: (80 wide) - // (w) means we hard wrapped the line - // (b) means the line is _not_ wrapped (it's broken, the default state.) - // - // |AAAA...AA | (w) - // | B_ ... | (b) (cursor is on the '_') - // | ... | (b) - - VERIFY_IS_TRUE(tb.GetRowByOffset(0).WasWrapForced()); - VERIFY_IS_FALSE(tb.GetRowByOffset(1).WasWrapForced()); - - // First row - auto iter0 = tb.GetCellDataAt({ 0, 0 }); - TestUtils::VerifySpanOfText(L"A", iter0, 0, firstTextLength); - TestUtils::VerifySpanOfText(L" ", iter0, 0, 2); - - // Second row - auto iter1 = tb.GetCellDataAt({ 0, 1 }); - TestUtils::VerifySpanOfText(L" ", iter1, 0, 1); - auto iter2 = tb.GetCellDataAt({ 1, 1 }); - TestUtils::VerifySpanOfText(L"B", iter2, 0, secondTextLength); - }; - - Log::Comment(L"========== Checking the host buffer state =========="); - verifyBuffer(hostTb); - - auto firstLine = std::string(firstTextLength, 'A'); - firstLine += " "; - std::string secondLine{ " B" }; - - expectedOutput.push_back(firstLine); - expectedOutput.push_back(secondLine); - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the terminal buffer state =========="); - verifyBuffer(termTb); -} - -void ConptyRoundtripTests::OutputWrappedLineWithSpaceAtBottomOfBuffer() -{ - // See https://github.com/microsoft/terminal/pull/5181#issuecomment-610110348 - // This is the same test as OutputWrappedLineWithSpace, but at the bottom of - // the buffer, so we get scrolling behavior as well. - Log::Comment(L"Ensures that a buffer line in conhost that wrapped _on a " - L"space_ will still be emitted as wrapped."); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - auto& hostTb = si.GetTextBuffer(); - auto& termTb = *term->_mainBuffer; - - _flushFirstFrame(); - - // First, fill the buffer with contents, so conpty starts circling - const auto hostView = si.GetViewport(); - const auto end = 2 * hostView.Height(); - for (auto i = 0; i < end; i++) - { - Log::Comment(NoThrowString().Format(L"Writing line %d/%d", i, end)); - expectedOutput.push_back("X"); - if (i < hostView.BottomInclusive()) - { - expectedOutput.push_back("\r\n"); - } - else - { - // After we hit the bottom of the viewport, the newlines come in - // separated by empty writes for whatever reason. - expectedOutput.push_back("\r"); - expectedOutput.push_back("\n"); - expectedOutput.push_back(""); - } - - sm.ProcessString(L"X\n"); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); - } - - const auto firstTextLength = TerminalViewWidth - 2; - const auto spacesLength = 3; - const auto secondTextLength = 1; - - auto firstLine = std::string(firstTextLength, 'A'); - firstLine += " "; - std::string secondLine{ " B" }; - - // The following diagrams show the buffer contents after each string emitted - // from conpty. For each of these diagrams: - // (w) means we hard wrapped the line - // (b) means the line is _not_ wrapped (it's broken, the default state.) - // cursor is on the '_' - - // Initial state: - // |X | (b) - // |X | (b) - // ... - // |X | (b) - // |_ | (b) - - expectedOutput.push_back(firstLine); - // |X | (b) - // |X | (b) - // ... - // |X | (b) - // |AAAAAAAA...AA _| (w) The cursor is actually on the last ' ' here - - // TODO GH#5228 might break the "newline & repaint the wrapped char" checks here, that's okay. - expectedOutput.push_back("\r"); // This \r\n is emitted by ScrollFrame to - expectedOutput.push_back("\n"); // add a newline to the bottom of the buffer - // |X | (b) - // |X | (b) - // ... - // |X | (b) - // |AAAAAAAA...AA | (b) - // |_ | (b) - - expectedOutput.push_back("\x1b[31;80H"); // Move the cursor BACK to the wrapped row - // |X | (b) - // |X | (b) - // ... - // |X | (b) - // |AAAAAAAA...AA _| (b) The cursor is actually on the last ' ' here - // | | (b) - - expectedOutput.push_back(std::string(1, ' ')); // Reprint the last character of the wrapped row - // |X | (b) - // |X | (b) - // ... - // |X | (b) - // |AAAAAAAA...AA |_ (w) The cursor is actually on the last ' ' here - // | | (b) - - expectedOutput.push_back(secondLine); - // |X | (b) - // |X | (b) - // ... - // |X | (b) - // |AAAAAAAA...AA | (w) - // | B_ | (b) - - sm.ProcessString(std::wstring(firstTextLength, L'A')); - sm.ProcessString(std::wstring(spacesLength, L' ')); - sm.ProcessString(std::wstring(secondTextLength, L'B')); - - auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& viewport) { - // Buffer contents should look like the following: (80 wide) - // (w) means we hard wrapped the line - // (b) means the line is _not_ wrapped (it's broken, the default state.) - // - // |AAAA...AA | (w) - // | B_ ... | (b) (cursor is on the '_') - // | ... | (b) - - const auto wrappedRow = viewport.bottom - 2; - VERIFY_IS_TRUE(tb.GetRowByOffset(wrappedRow).WasWrapForced()); - VERIFY_IS_FALSE(tb.GetRowByOffset(wrappedRow + 1).WasWrapForced()); - - // First row - auto iter0 = tb.GetCellDataAt({ 0, wrappedRow }); - TestUtils::VerifySpanOfText(L"A", iter0, 0, firstTextLength); - TestUtils::VerifySpanOfText(L" ", iter0, 0, 2); - - // Second row - auto iter1 = tb.GetCellDataAt({ 0, wrappedRow + 1 }); - TestUtils::VerifySpanOfText(L" ", iter1, 0, 1); - auto iter2 = tb.GetCellDataAt({ 1, wrappedRow + 1 }); - TestUtils::VerifySpanOfText(L"B", iter2, 0, secondTextLength); - }; - - Log::Comment(L"========== Checking the host buffer state =========="); - verifyBuffer(hostTb, hostView.ToExclusive()); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the terminal buffer state =========="); - verifyBuffer(termTb, term->_mutableViewport.ToExclusive()); -} - -void ConptyRoundtripTests::BreakLinesOnCursorMovement() -{ - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:cursorMovementMode", L"{0, 1, 2, 3, 4, 5, 6}") - END_TEST_METHOD_PROPERTIES(); - constexpr auto MoveCursorWithCUP = 0; - constexpr auto MoveCursorWithCR_LF = 1; - constexpr auto MoveCursorWithLF_CR = 2; - constexpr auto MoveCursorWithVPR_CR = 3; - constexpr auto MoveCursorWithCUB_LF = 4; - constexpr auto MoveCursorWithCUD_CR = 5; - constexpr auto MoveCursorWithNothing = 6; - INIT_TEST_PROPERTY(int, cursorMovementMode, L"Controls how we move the cursor, either with CUP, newline/carriage-return, or some other VT sequence"); - - Log::Comment(L"This is a test for GH#5291. WSL vim uses spaces to clear the" - L" ends of blank lines, not EL. This test ensures we emit text" - L" from conpty such that the terminal re-creates the state of" - L" the host, which includes wrapped lines of lots of spaces."); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& hostSm = si.GetStateMachine(); - - _flushFirstFrame(); - - // Any of the cursor movements that use a LF will actually hard break the - // line - everything else will leave it marked as wrapped. - const auto expectHardBreak = (cursorMovementMode == MoveCursorWithLF_CR) || - (cursorMovementMode == MoveCursorWithCR_LF) || - (cursorMovementMode == MoveCursorWithCUB_LF); - - auto verifyBuffer = [&](const TextBuffer& tb, - const til::rect viewport) { - const auto lastRow = viewport.bottom - 1; - const til::point expectedCursor{ 5, lastRow }; - VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); - VERIFY_IS_TRUE(tb.GetCursor().IsVisible()); - - for (auto y = viewport.top; y < lastRow; y++) - { - // We're using CUP to move onto the status line _always_, so the - // second-last row will always be marked as wrapped. - const auto rowWrapped = (!expectHardBreak) || (y == lastRow - 1); - VERIFY_ARE_EQUAL(rowWrapped, tb.GetRowByOffset(y).WasWrapForced()); - TestUtils::VerifyExpectedString(tb, L"~ ", { 0, y }); - } - - TestUtils::VerifyExpectedString(tb, L"AAAAA", { 0, lastRow }); - }; - - // We're _not_ checking the conpty output during this test, only the side effects. - _logConpty = true; - _checkConptyOutput = false; - - // Lock must be taken to manipulate alt/main buffer state. - gci.LockConsole(); - auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); - - // Use DECALN to fill the buffer with 'E's. - hostSm.ProcessString(L"\x1b#8"); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"Switching to the alt buffer"); - hostSm.ProcessString(L"\x1b[?1049h"); - auto restoreBuffer = wil::scope_exit([&] { hostSm.ProcessString(L"\x1b[?1049l"); }); - auto& altBuffer = gci.GetActiveOutputBuffer(); - auto& altTextBuffer = altBuffer.GetTextBuffer(); - - // Go home and clear the screen. - hostSm.ProcessString(L"\x1b[H"); - hostSm.ProcessString(L"\x1b[2J"); - - // Write out lines of '~' followed by enough spaces to fill the line. - hostSm.ProcessString(L"\x1b[94m"); - for (auto y = 0; y < altBuffer.GetViewport().BottomInclusive(); y++) - { - // Vim uses CUP to position the cursor on the first cell of each row, every row. - if (cursorMovementMode == MoveCursorWithCUP) - { - std::wstringstream ss; - ss << L"\x1b["; - ss << y + 1; - ss << L";1H"; - hostSm.ProcessString(ss.str()); - } - // As an additional test, try breaking lines manually with \r\n - else if (cursorMovementMode == MoveCursorWithCR_LF) - { - // Don't need to newline on the 0'th row - if (y > 0) - { - hostSm.ProcessString(L"\r\n"); - } - } - // As an additional test, try breaking lines manually with \n\r - else if (cursorMovementMode == MoveCursorWithLF_CR) - { - // Don't need to newline on the 0'th row - if (y > 0) - { - hostSm.ProcessString(L"\n\r"); - } - } - // As an additional test, move the cursor down with VPR, then to the start of the line with CR - else if (cursorMovementMode == MoveCursorWithVPR_CR) - { - // Don't need to newline on the 0'th row - if (y > 0) - { - hostSm.ProcessString(L"\x1b[1e"); - hostSm.ProcessString(L"\r"); - } - } - // As an additional test, move the cursor back with CUB, then down with LF - else if (cursorMovementMode == MoveCursorWithCUB_LF) - { - // Don't need to newline on the 0'th row - if (y > 0) - { - hostSm.ProcessString(L"\x1b[80D"); - hostSm.ProcessString(L"\n"); - } - } - // As an additional test, move the cursor down with CUD, then to the start of the line with CR - else if (cursorMovementMode == MoveCursorWithCUD_CR) - { - // Don't need to newline on the 0'th row - if (y > 0) - { - hostSm.ProcessString(L"\x1b[B"); - hostSm.ProcessString(L"\r"); - } - } - // Win32 vim.exe will simply do _nothing_ in this scenario. It'll just - // print the lines one after the other, without moving the cursor, - // relying on us auto moving to the following line. - else if (cursorMovementMode == MoveCursorWithNothing) - { - } - - // IMPORTANT! The way vim writes these blank lines is as '~' followed by - // enough spaces to fill the line. - // This bug (GH#5291 won't repro if you don't have the spaces). - std::wstring line{ L"~" }; - line += std::wstring(79, L' '); - hostSm.ProcessString(line); - } - - // Print the "Status Line" - hostSm.ProcessString(L"\x1b[32;1H"); - hostSm.ProcessString(L"\x1b[m"); - hostSm.ProcessString(L"AAAAA"); - - Log::Comment(L"========== Checking the host buffer state =========="); - verifyBuffer(altTextBuffer, altBuffer.GetViewport().ToExclusive()); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the terminal buffer state =========="); - // GH#3492: Now that we support the alt buffer, make sure to validate the - // _alt buffer's_ contents. - verifyBuffer(*term->_altBuffer, term->_mutableViewport.ToExclusive()); -} - -void ConptyRoundtripTests::TestCursorInDeferredEOLPositionOnNewLineWithSpaces() -{ - 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->_mainBuffer; - const auto termView = term->GetViewport(); - - _flushFirstFrame(); - _checkConptyOutput = false; - - // newline down to the bottom - hostSm.ProcessString(std::wstring(gsl::narrow_cast(TerminalViewHeight), L'\n')); - // fill width-1 with "A", then add one space and another character.. - hostSm.ProcessString(std::wstring(gsl::narrow_cast(TerminalViewWidth) - 1, L'A') + L" B"); - - auto verifyBuffer = [&](const TextBuffer& tb, til::CoordType bottomRow) { - // Buffer contents should look like the following: (80 wide) - // (w) means we hard wrapped the line - // (b) means the line is _not_ wrapped (it's broken, the default state.) - // cursor is on the '_' - // - // | ^ ^ ^ ^ ^ ^ ^ | ( ) (entire buffer above us; contents do not matter) - // | | ( ) (entire buffer above us; contents do not matter) - // |AAAAAAAA...AAA | (w) (79 'A's, one space) - // |B_ ... | (b) (There's only one 'B' on this line) - - til::point cursorPos{ tb.GetCursor().GetPosition() }; - // The cursor should be on the second char of the last line - VERIFY_ARE_EQUAL(til::point(1, bottomRow), cursorPos); - - const auto& secondToLastRow = tb.GetRowByOffset(bottomRow - 1); - const auto& lastRow = tb.GetRowByOffset(bottomRow); - VERIFY_IS_TRUE(secondToLastRow.WasWrapForced()); - VERIFY_IS_FALSE(lastRow.WasWrapForced()); - - auto expectedStringSecondToLastRow{ std::wstring(gsl::narrow_cast(tb.GetSize().Width()) - 1, L'A') + L" " }; - TestUtils::VerifyExpectedString(tb, expectedStringSecondToLastRow, { 0, bottomRow - 1 }); - TestUtils::VerifyExpectedString(tb, L"B", { 0, bottomRow }); - }; - - const auto hostView = si.GetViewport(); - verifyBuffer(hostTb, hostView.BottomInclusive()); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - const auto newTermView = term->GetViewport(); - verifyBuffer(termTb, newTermView.BottomInclusive()); -} - -void ConptyRoundtripTests::ResizeRepaintVimExeBuffer() -{ - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method") - END_TEST_METHOD_PROPERTIES() - - // See https://github.com/microsoft/terminal/issues/5428 - Log::Comment(L"This test emulates what happens when you decrease the width " - L"of the window while running vim.exe."); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_mainBuffer.get(); - - _flushFirstFrame(); - - _checkConptyOutput = false; - _logConpty = true; - - // This is a helper that will recreate the way that vim redraws the buffer. - auto drawVim = [&sm, &si]() { - const auto hostView = si.GetViewport(); - const auto width = hostView.Width(); - - // Write: - // * AAA to the first line - // * BBB to the second line - // * a bunch of lines with a "~" followed by spaces (just the way vim.exe likes to render) - // * A status line with a bunch of "X"s and _a single space in the last cell_. - sm.ProcessString(L"AAA"); - sm.ProcessString(L"\r\n"); - sm.ProcessString(L"BBB"); - sm.ProcessString(L"\r\n"); - - for (auto i = 2; i < hostView.BottomInclusive(); i++) - { - // IMPORTANT! The way vim writes these blank lines is as '~' followed by - // enough spaces to fill the line. - std::wstring line{ L"~" }; - line += std::wstring(width - 1, L' '); - sm.ProcessString(line); - } - sm.ProcessString(std::wstring(width - 1, L'X')); - sm.ProcessString(L" "); - - // Move the cursor back home, as if it's on the first "A". The bug won't - // repro without this! - sm.ProcessString(L"\x1b[H"); - }; - - drawVim(); - - auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& viewport) { - const auto firstRow = viewport.top; - const auto width = viewport.width(); - const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope; - - // First row - VERIFY_IS_FALSE(tb.GetRowByOffset(firstRow).WasWrapForced()); - auto iter0 = tb.GetCellDataAt({ 0, firstRow }); - TestUtils::VerifySpanOfText(L"A", iter0, 0, 3); - TestUtils::VerifySpanOfText(L" ", iter0, 0, width - 3); - - // Second row - VERIFY_IS_FALSE(tb.GetRowByOffset(firstRow + 1).WasWrapForced()); - auto iter1 = tb.GetCellDataAt({ 0, firstRow + 1 }); - TestUtils::VerifySpanOfText(L"B", iter1, 0, 3); - TestUtils::VerifySpanOfText(L" ", iter1, 0, width - 3); - - // "~" rows - for (auto row = firstRow + 2; row < viewport.bottom - 1; row++) - { - Log::Comment(NoThrowString().Format(L"Checking row %d", row)); - VERIFY_IS_TRUE(tb.GetRowByOffset(row).WasWrapForced()); - auto iter = tb.GetCellDataAt({ 0, row }); - TestUtils::VerifySpanOfText(L"~", iter, 0, 1); - TestUtils::VerifySpanOfText(L" ", iter, 0, width - 1); - } - - // Last row - { - const auto row = viewport.bottom - 1; - Log::Comment(NoThrowString().Format(L"Checking row %d", row)); - VERIFY_IS_TRUE(tb.GetRowByOffset(row).WasWrapForced()); - auto iter = tb.GetCellDataAt({ 0, row }); - TestUtils::VerifySpanOfText(L"X", iter, 0, width - 1); - TestUtils::VerifySpanOfText(L" ", iter, 0, 1); - } - }; - - Log::Comment(L"========== Checking the host buffer state (before) =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive()); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the terminal buffer state (before) =========="); - verifyBuffer(*termTb, term->_mutableViewport.ToExclusive()); - - // After we resize, make sure to get the new textBuffers - std::tie(hostTb, termTb) = _performResize({ TerminalViewWidth - 1, - TerminalViewHeight }); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"Re-painting vim"); - // vim will redraw itself when it notices the buffer size change. - drawVim(); - - Log::Comment(L"========== Checking the host buffer state (after) =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive()); - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the terminal buffer state (after) =========="); - verifyBuffer(*termTb, term->_mutableViewport.ToExclusive()); -} - -void ConptyRoundtripTests::ClsAndClearHostClearsScrollbackTest() -{ - // See https://github.com/microsoft/terminal/issues/3126#issuecomment-620677742 - - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:clearBufferMethod", L"{0, 1, 2}") - END_TEST_METHOD_PROPERTIES(); - INIT_TEST_PROPERTY(int, clearBufferMethod, L"Controls whether we clear the buffer like cmd or like powershell"); - - Log::Comment(L"This test checks the shims for cmd.exe and powershell.exe. " - L"Their build in commands for clearing the console buffer " - L"should work to clear the terminal buffer, not just the " - L"terminal viewport."); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_mainBuffer.get(); - - auto& sm = si.GetStateMachine(); - - _flushFirstFrame(); - - _checkConptyOutput = false; - _logConpty = true; - - const auto hostView = si.GetViewport(); - const auto end = 2 * hostView.Height(); - for (auto i = 0; i < end; i++) - { - if (i > 0) - { - sm.ProcessString(L"\r\n"); - } - - sm.ProcessString(L"~"); - } - - auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& viewport, const bool afterClear = false) { - const auto width = viewport.width(); - - // "~" rows - for (til::CoordType row = 0; row < viewport.bottom; row++) - { - Log::Comment(NoThrowString().Format(L"Checking row %d", row)); - VERIFY_IS_FALSE(tb.GetRowByOffset(row).WasWrapForced()); - auto iter = tb.GetCellDataAt({ 0, row }); - if (afterClear) - { - TestUtils::VerifySpanOfText(L" ", iter, 0, width); - } - else - { - TestUtils::VerifySpanOfText(L"~", iter, 0, 1); - TestUtils::VerifySpanOfText(L" ", iter, 0, width - 1); - } - } - }; - - Log::Comment(L"========== Checking the host buffer state (before) =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive()); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the terminal buffer state (before) =========="); - verifyBuffer(*termTb, term->_mutableViewport.ToExclusive()); - - VERIFY_ARE_EQUAL(si.GetViewport().Dimensions(), si.GetBufferSize().Dimensions()); - VERIFY_ARE_EQUAL(si.GetViewport(), si.GetBufferSize()); - - _clear(clearBufferMethod, si); - - VERIFY_ARE_EQUAL(si.GetViewport().Dimensions(), si.GetBufferSize().Dimensions()); - VERIFY_ARE_EQUAL(si.GetViewport(), si.GetBufferSize()); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the host buffer state (after) =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive(), true); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - Log::Comment(L"========== Checking the terminal buffer state (after) =========="); - verifyBuffer(*termTb, term->_mutableViewport.ToExclusive(), true); -} - -void ConptyRoundtripTests::_clear(int clearBufferMethod, SCREEN_INFORMATION& si) -{ - constexpr auto ClearLikeCls = 0; - constexpr auto ClearLikeClearHost = 1; - constexpr auto ClearWithVT = 2; - - auto& sm = si.GetStateMachine(); - - if (clearBufferMethod == ClearLikeCls) - { - // Execute the cls, EXACTLY LIKE CMD. - - CONSOLE_SCREEN_BUFFER_INFOEX csbiex{ 0 }; - csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); - _apiRoutines.GetConsoleScreenBufferInfoExImpl(si, csbiex); - - til::inclusive_rect src{ 0 }; - src.top = 0; - src.left = 0; - src.right = csbiex.dwSize.X; - src.bottom = csbiex.dwSize.Y; - - til::point tgt{ 0, -csbiex.dwSize.Y }; - VERIFY_SUCCEEDED(_apiRoutines.ScrollConsoleScreenBufferWImpl(si, - src, - tgt, - std::nullopt, // no clip provided, - L' ', - csbiex.wAttributes, - true)); - } - else if (clearBufferMethod == ClearLikeClearHost) - { - CONSOLE_SCREEN_BUFFER_INFOEX csbiex{ 0 }; - csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); - _apiRoutines.GetConsoleScreenBufferInfoExImpl(si, csbiex); - - const auto totalCellsInBuffer = csbiex.dwSize.X * csbiex.dwSize.Y; - size_t cellsWritten = 0; - VERIFY_SUCCEEDED(_apiRoutines.FillConsoleOutputCharacterWImpl(si, - L' ', - totalCellsInBuffer, - { 0, 0 }, - cellsWritten, - true)); - VERIFY_SUCCEEDED(_apiRoutines.FillConsoleOutputAttributeImpl(si, - csbiex.wAttributes, - totalCellsInBuffer, - { 0, 0 }, - cellsWritten)); - } - else if (clearBufferMethod == ClearWithVT) - { - sm.ProcessString(L"\x1b[2J"); - VERIFY_ARE_EQUAL(si.GetViewport().Dimensions(), si.GetBufferSize().Dimensions()); - VERIFY_ARE_EQUAL(si.GetViewport(), si.GetBufferSize()); - - sm.ProcessString(L"\x1b[3J"); - } -} - -void ConptyRoundtripTests::TestResizeWithCookedRead() -{ - // see https://github.com/microsoft/terminal/issues/1856 - Log::Comment(L"This test checks a crash in conpty where resizing the " - L"window with any data in a cooked read (like the input line " - L"in cmd.exe) would cause the conpty to crash."); - - // Resizing with a COOKED_READ used to cause a crash in - // `Selection::s_GetInputLineBoundaries` north of - // `Selection::GetValidAreaBoundaries`. - // - // If this test completes successfully, then we know that we didn't crash. - - // The specific cases that repro the original crash are: - // * (0, -10) - // * (0, -1) - // * (0, 0) - // the rest of the cases are added here for completeness. - - // Don't let the cooked read pollute other tests - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method") - TEST_METHOD_PROPERTY(L"Data:dx", L"{-10, -1, 0, 1, 10}") - TEST_METHOD_PROPERTY(L"Data:dy", L"{-10, -1, 0, 1, 10}") - END_TEST_METHOD_PROPERTIES() - - INIT_TEST_PROPERTY(int, dx, L"The change in width of the buffer"); - INIT_TEST_PROPERTY(int, dy, L"The change in height of the buffer"); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_mainBuffer.get(); - - _flushFirstFrame(); - - _checkConptyOutput = false; - _logConpty = true; - - // Setup the cooked read data - m_state->PrepareReadHandle(); - // TODO GH#5618: This string will get mangled, but we don't really care - // about the buffer contents in this test, so it doesn't really matter. - m_state->PrepareCookedReadData(L"This is some cooked read data"); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - // After we resize, make sure to get the new textBuffers - std::tie(hostTb, termTb) = _performResize({ TerminalViewWidth + dx, - TerminalViewHeight + dy }); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - // By simply reaching the end of this test, we know that we didn't crash. Hooray! -} - -void ConptyRoundtripTests::ResizeInitializeBufferWithDefaultAttrs() -{ - // See https://github.com/microsoft/terminal/issues/3848 - Log::Comment(L"This test checks that the attributes in the text buffer are " - L"initialized to a sensible value during a resize. The entire " - L"buffer shouldn't be filled with _whatever the current " - L"attributes are_, it should be filled with the default " - L"attributes (however the application defines that). Then, " - L"after the resize, we should still be able to print to the " - L"buffer with the old \"current attributes\""); - - 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"{-1, 0, 1}") - TEST_METHOD_PROPERTY(L"Data:leaveTrailingChar", L"{false, true}") - END_TEST_METHOD_PROPERTIES() - - INIT_TEST_PROPERTY(int, dx, L"The change in width of the buffer"); - INIT_TEST_PROPERTY(int, dy, L"The change in height of the buffer"); - INIT_TEST_PROPERTY(bool, leaveTrailingChar, L"If true, we'll print one additional '#' on row 3"); - - // Do nothing if the resize would just be a no-op. - if (dx == 0 && dy == 0) - { - return; - } - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_mainBuffer.get(); - - _flushFirstFrame(); - - _checkConptyOutput = false; - _logConpty = true; - - auto defaultAttrs = si.GetAttributes(); - auto conhostGreenAttrs = TextAttribute(); - conhostGreenAttrs.SetIndexedBackground(TextColor::DARK_GREEN); - auto terminalGreenAttrs = TextAttribute(); - terminalGreenAttrs.SetIndexedBackground(TextColor::DARK_GREEN); - - // Use an initial ^[[m to start printing with default-on-default - sm.ProcessString(L"\x1b[m"); - - // Print three lines with "# #", where the first "# " are in - // default-on-green. - for (auto i = 0; i < 3; i++) - { - sm.ProcessString(L"\x1b[42m"); - sm.ProcessString(L"# "); - sm.ProcessString(L"\x1b[m"); - sm.ProcessString(L"#"); - sm.ProcessString(L"\r\n"); - } - - // Now, leave the active attributes as default-on-green. When we resize the - // buffers, we don't want them initialized with default-on-green, we want - // them to use whatever the set default attributes are. - sm.ProcessString(L"\x1b[42m"); - - // If leaveTrailingChar is true, we'll leave one default-on-green '#' on row - // 3. This will force conpty to change the Terminal's colors to - // default-on-green, so we can check that not only conhost initialize the - // buffer colors correctly, but so does the Terminal. - if (leaveTrailingChar) - { - sm.ProcessString(L"#"); - } - - auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& viewport, const bool isTerminal, const bool afterResize) { - const auto width = viewport.width(); - - // Conhost and Terminal attributes are potentially different. - const auto greenAttrs = isTerminal ? terminalGreenAttrs : conhostGreenAttrs; - - for (til::CoordType row = 0; row < tb.GetSize().Height(); row++) - { - Log::Comment(NoThrowString().Format(L"Checking row %d...", row)); - - VERIFY_IS_FALSE(tb.GetRowByOffset(row).WasWrapForced()); - - const auto hasChar = row < 3; - const auto actualDefaultAttrs = isTerminal ? TextAttribute() : defaultAttrs; - - if (hasChar) - { - auto iter = TestUtils::VerifyLineContains(tb, { 0, row }, L'#', greenAttrs, 1u); - TestUtils::VerifyLineContains(iter, L' ', greenAttrs, 1u); - TestUtils::VerifyLineContains(iter, L'#', TextAttribute(), 1u); - // After the resize, the default attrs of the last char will - // extend to fill the rest of the row. This is GH#32. If that - // bug ever gets fixed, this test will break, but that's - // ABSOLUTELY OKAY. - TestUtils::VerifyLineContains(iter, L' ', (afterResize ? TextAttribute() : actualDefaultAttrs), static_cast(width - 3)); - } - else if (leaveTrailingChar && row == 3) - { - auto iter = TestUtils::VerifyLineContains(tb, { 0, row }, L'#', greenAttrs, 1u); - TestUtils::VerifyLineContains(iter, L' ', (actualDefaultAttrs), static_cast(width - 1)); - } - else - { - TestUtils::VerifyLineContains(tb, { 0, row }, L' ', actualDefaultAttrs, viewport.narrow_width()); - } - } - }; - - Log::Comment(L"========== Checking the host buffer state (before) =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive(), false, false); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the terminal buffer state (before) =========="); - verifyBuffer(*termTb, term->_mutableViewport.ToExclusive(), true, false); - - // After we resize, make sure to get the new textBuffers - std::tie(hostTb, termTb) = _performResize({ TerminalViewWidth + dx, - TerminalViewHeight + dy }); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the host buffer state (after) =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive(), false, true); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the terminal buffer state (after) =========="); - verifyBuffer(*termTb, term->_mutableViewport.ToExclusive(), true, true); -} - -void ConptyRoundtripTests::NewLinesAtBottomWithBackground() -{ - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:paintEachNewline", L"{false, true}") - TEST_METHOD_PROPERTY(L"Data:spacesToPrint", L"{1, 7, 8, 9, 32}") - END_TEST_METHOD_PROPERTIES(); - - INIT_TEST_PROPERTY(bool, paintEachNewline, L"If true, call PaintFrame after each pair of lines."); - INIT_TEST_PROPERTY(int, spacesToPrint, L"Controls the number of spaces printed after the first '#'"); - - // See https://github.com/microsoft/terminal/issues/5502 - Log::Comment(L"Attempts to emit text to a new bottom line with spaces with " - L"a colored background. When that happens, we should make " - L"sure to still print the spaces, because the information " - L"about their background color is important."); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_mainBuffer.get(); - - _flushFirstFrame(); - - _checkConptyOutput = false; - _logConpty = true; - - auto defaultAttrs = si.GetAttributes(); - auto conhostBlueAttrs = defaultAttrs; - conhostBlueAttrs.SetIndexedForeground(TextColor::DARK_GREEN); - conhostBlueAttrs.SetIndexedBackground(TextColor::DARK_BLUE); - auto terminalBlueAttrs = TextAttribute(); - terminalBlueAttrs.SetIndexedForeground(TextColor::DARK_GREEN); - terminalBlueAttrs.SetIndexedBackground(TextColor::DARK_BLUE); - - // We're going to print 4 more rows than the entire height of the viewport, - // causing the buffer to circle 4 times. This is 2 extra iterations of the - // two lines we're printing per iteration. - const auto circledRows = 4; - for (auto i = 0; i < (TerminalViewHeight + circledRows) / 2; i++) - { - // We're printing pairs of lines: ('_' is a space character) - // - // Line 1 chars: __________________ (break) - // Line 1 attrs: DDDDDDDDDDDDDDDDDD (all default attrs) - // Line 2 chars: ____#_________#___ (break) - // Line 2 attrs: BBBBBBBBBBBBBBDDDD (First spacesToPrint+5 are blue BG, then default attrs) - // [<----->] - // This number of spaces controlled by spacesToPrint - if (i > 0) - { - sm.ProcessString(L"\r\n"); - } - - // In WSL: - // echo -e "\e[m\r\n\e[44;32m # \e[m#" - - sm.ProcessString(L"\x1b[m"); - sm.ProcessString(L"\r"); - sm.ProcessString(L"\n"); - sm.ProcessString(L"\x1b[44;32m"); - sm.ProcessString(L" #"); - sm.ProcessString(std::wstring(spacesToPrint, L' ')); - sm.ProcessString(L"\x1b[m"); - sm.ProcessString(L"#"); - - if (paintEachNewline) - { - VERIFY_SUCCEEDED(renderer.PaintFrame()); - } - } - - auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& viewport) { - const auto width = viewport.width(); - const auto isTerminal = viewport.top != 0; - - // Conhost and Terminal attributes are potentially different. - const auto blueAttrs = isTerminal ? terminalBlueAttrs : conhostBlueAttrs; - - for (til::CoordType row = 0; row < viewport.bottom - 2; row++) - { - Log::Comment(NoThrowString().Format(L"Checking row %d", row)); - VERIFY_IS_FALSE(tb.GetRowByOffset(row).WasWrapForced()); - - const auto isBlank = (row % 2) == 0; - const auto rowCircled = row > (viewport.bottom - 1 - circledRows); - // When the buffer circles, new lines will be initialized using the - // current text attributes. Those will be the default-on-default - // attributes. All of the Terminal's buffer will use - // default-on-default. - const auto actualDefaultAttrs = rowCircled || isTerminal ? TextAttribute() : defaultAttrs; - Log::Comment(NoThrowString().Format(L"isBlank=%d, rowCircled=%d", isBlank, rowCircled)); - - if (isBlank) - { - TestUtils::VerifyLineContains(tb, { 0, row }, L' ', actualDefaultAttrs, viewport.narrow_width()); - } - else - { - auto iter = TestUtils::VerifyLineContains(tb, { 0, row }, L' ', blueAttrs, 4u); - TestUtils::VerifyLineContains(iter, L'#', blueAttrs, 1u); - TestUtils::VerifyLineContains(iter, L' ', blueAttrs, static_cast(spacesToPrint)); - TestUtils::VerifyLineContains(iter, L'#', TextAttribute(), 1u); - TestUtils::VerifyLineContains(iter, L' ', actualDefaultAttrs, static_cast(width - 15)); - } - } - }; - - Log::Comment(L"========== Checking the host buffer state =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive()); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the terminal buffer state =========="); - verifyBuffer(*termTb, term->_mutableViewport.ToExclusive()); -} - -void ConptyRoundtripTests::WrapNewLineAtBottom() -{ - // The actual bug case is - // * paintEachNewline=2 (PaintEveryLine) - // * writingMethod=1 (PrintWithWriteCharsLegacy) - // * circledRows=4 - // - // Though, mysteriously, the bug that this test caught _WASN'T_ the fix for - // #5691 - - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:paintEachNewline", L"{0, 1, 2}") - TEST_METHOD_PROPERTY(L"Data:writingMethod", L"{0, 1}") - TEST_METHOD_PROPERTY(L"Data:circledRows", L"{2, 4, 10}") - END_TEST_METHOD_PROPERTIES(); - - // By modifying how often we call PaintFrame, we can test if different - // timings for the frame affect the results. In this test we'll be printing - // a bunch of paired lines. These values control when the PaintFrame calls - // will occur: - constexpr auto PaintAfterBothLines = 1; // Paint after each pair of lines is output - constexpr auto PaintEveryLine = 2; // Paint after each and every line is output. - - constexpr auto PrintWithPrintString = 0; - constexpr auto PrintWithWriteCharsLegacy = 1; - - INIT_TEST_PROPERTY(int, writingMethod, L"Controls using either ProcessString or WriteCharsLegacy to write to the buffer"); - INIT_TEST_PROPERTY(int, circledRows, L"Controls the number of lines we output."); - INIT_TEST_PROPERTY(int, paintEachNewline, L"Controls whether we should call PaintFrame every line of text or not."); - - // GH#5839 - - // This test does expose a real bug when using WriteCharsLegacy to emit - // wrapped lines in conpty without delayed EOL wrap. However, this fix has - // not yet been made, so for now, we need to just skip the cases that cause - // this. - if (writingMethod == PrintWithWriteCharsLegacy && paintEachNewline == PaintEveryLine) - { - Log::Result(WEX::Logging::TestResults::Skipped); - return; - } - - // This test was originally written for - // https://github.com/microsoft/terminal/issues/5691 - // - // It does not _actually_ test #5691 however, it merely checks another issue - // found during debugging. - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_mainBuffer.get(); - - _flushFirstFrame(); - - _checkConptyOutput = false; - _logConpty = true; - - const auto width = static_cast(TerminalViewWidth); - - const auto charsInFirstLine = width; - const auto numCharsFirstColor = 30; - const auto numCharsSecondColor = charsInFirstLine - numCharsFirstColor; - const auto charsInSecondLine = width / 2; - - const auto defaultAttrs = TextAttribute(); - const auto conhostDefaultAttrs = si.GetAttributes(); - - // Helper to abstract calls into either StateMachine::ProcessString or - // WriteCharsLegacy - auto print = [&](const std::wstring_view str) { - if (writingMethod == PrintWithPrintString) - { - sm.ProcessString(str); - } - else if (writingMethod == PrintWithWriteCharsLegacy) - { - WriteCharsLegacy(si, str, nullptr); - } - }; - - // Each of the lines of text in the buffer will look like the following: - // - // Line 1 chars: ~~~~~~~~~~~~~~~~~~ - // Line 1 attrs: YYYYYYYDDDDDDDDDDD (First 30 are yellow FG, then default attrs) - // Line 2 chars: ~~~~~~~~~~________ (there are width/2 '~'s here) - // Line 2 attrs: DDDDDDDDDDDDDDDDDD (all are default attrs) - // - - for (auto i = 0; i < (TerminalViewHeight + circledRows) / 2; i++) - { - Log::Comment(NoThrowString().Format(L"writing pair of lines %d", i)); - - if (i > 0) - { - sm.ProcessString(L"\r\n"); - } - - sm.ProcessString(L"\x1b[33m"); - - print(std::wstring(numCharsFirstColor, L'~')); - - sm.ProcessString(L"\x1b[m"); - - print(std::wstring(numCharsSecondColor, L'~')); - - // If we're painting every line, then paint now, while conpty is in a - // deferred EOL state. Otherwise, we'll wait to paint till after more output. - if (paintEachNewline == PaintEveryLine) - { - VERIFY_SUCCEEDED(renderer.PaintFrame()); - } - - // Print the rest of the string of text, which should continue from the - // wrapped line before it. - print(std::wstring(charsInSecondLine, L'~')); - - if (paintEachNewline == PaintEveryLine || - paintEachNewline == PaintAfterBothLines) - { - VERIFY_SUCCEEDED(renderer.PaintFrame()); - } - } - - // At this point, the entire buffer looks like: - // - // row[0]: |~~~~~~~~~~~~~~~~~~~| - // row[1]: |~~~~~~ | - // row[2]: |~~~~~~~~~~~~~~~~~~~| - // row[3]: |~~~~~~ | - // row[4]: |~~~~~~~~~~~~~~~~~~~| - // row[5]: |~~~~~~ | - - auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& viewport) { - const auto width = viewport.width(); - const auto isTerminal = viewport.top != 0; - - for (til::CoordType row = 0; row < viewport.bottom; row++) - { - Log::Comment(NoThrowString().Format(L"Checking row %d", row)); - - // The first line wrapped, the second didn't, so on and so forth - const auto isWrapped = (row % 2) == 0; - const auto rowCircled = row >= (viewport.bottom - circledRows); - - const auto actualNonSpacesAttrs = defaultAttrs; - const auto actualSpacesAttrs = rowCircled || isTerminal ? defaultAttrs : conhostDefaultAttrs; - - VERIFY_ARE_EQUAL(isWrapped, tb.GetRowByOffset(row).WasWrapForced()); - if (isWrapped) - { - TestUtils::VerifyExpectedString(tb, std::wstring(charsInFirstLine, L'~'), { 0, row }); - } - else - { - auto iter = TestUtils::VerifyExpectedString(tb, std::wstring(charsInSecondLine, L'~'), { 0, row }); - TestUtils::VerifyExpectedString(std::wstring(width - charsInSecondLine, L' '), iter); - } - } - }; - - Log::Comment(L"========== Checking the host buffer state =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive()); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the terminal buffer state =========="); - VERIFY_ARE_EQUAL(circledRows, term->_mutableViewport.Top()); - verifyBuffer(*termTb, term->_mutableViewport.ToExclusive()); -} - -void ConptyRoundtripTests::WrapNewLineAtBottomLikeMSYS() -{ - // See https://github.com/microsoft/terminal/issues/5691 - Log::Comment(L"This test attempts to print text like the MSYS `less` pager " - L"does. When it prints a wrapped line, we should make sure to " - L"not break the line wrapping."); - - // The importantly valuable variable here ended up being - // writingMethod=PrintWithWriteCharsLegacy. That was the one thing that - // actually repro'd this bug. The other variables were introduced as part of - // debugging, and are left for completeness. - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:circledRows", L"{2, 4, 10}") - TEST_METHOD_PROPERTY(L"Data:paintEachNewline", L"{0, 1, 2}") - TEST_METHOD_PROPERTY(L"Data:writingMethod", L"{0, 1}") - END_TEST_METHOD_PROPERTIES(); - - // By modifying how often we call PaintFrame, we can test if different - // timings for the frame affect the results. In this test we'll be printing - // a bunch of paired lines. These values control when the PaintFrame calls - // will occur: - constexpr auto PaintAfterBothLines = 1; // Paint after each pair of lines is output - constexpr auto PaintEveryLine = 2; // Paint after each and every line is output. - - constexpr auto PrintWithPrintString = 0; - constexpr auto PrintWithWriteCharsLegacy = 1; - - INIT_TEST_PROPERTY(int, writingMethod, L"Controls using either ProcessString or WriteCharsLegacy to write to the buffer"); - INIT_TEST_PROPERTY(int, circledRows, L"Controls the number of lines we output."); - INIT_TEST_PROPERTY(int, paintEachNewline, L"Controls whether we should call PaintFrame every line of text or not."); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_mainBuffer.get(); - - _flushFirstFrame(); - - _checkConptyOutput = false; - _logConpty = true; - - const auto width = static_cast(TerminalViewWidth); - - const auto charsInFirstLine = width; - const auto numCharsFirstColor = 30; - const auto numCharsSecondColor = charsInFirstLine - numCharsFirstColor; - const auto charsInSecondLine = width / 2; - - const auto defaultAttrs = TextAttribute(); - const auto conhostDefaultAttrs = si.GetAttributes(); - - // Helper to abstract calls into either StateMachine::ProcessString or - // WriteCharsLegacy - auto print = [&](const std::wstring_view str) { - if (writingMethod == PrintWithPrintString) - { - sm.ProcessString(str); - } - else if (writingMethod == PrintWithWriteCharsLegacy) - { - WriteCharsLegacy(si, str, nullptr); - } - }; - - // Each of the lines of text in the buffer will look like the following: - // - // Line 1 chars: ~~~~~~~~~~~~~~~~~~ - // Line 1 attrs: YYYYYYYDDDDDDDDDDD (First 30 are yellow FG, then default attrs) - // Line 2 chars: ~~~~~~~~~~________ (there are width/2 '~'s here) - // Line 2 attrs: DDDDDDDDDDDDDDDDDD (all are default attrs) - // - // The last line of the buffer will be used as a "prompt" line, with a - // single ':' in it. This is similar to the way `less` typically displays - // its prompt at the bottom of the buffer. - - // First, print a whole viewport full of text. - for (auto i = 0; i < (TerminalViewHeight) / 2; i++) - { - Log::Comment(NoThrowString().Format(L"writing pair of lines %d", i)); - - sm.ProcessString(L"\x1b[33m"); - print(std::wstring(numCharsFirstColor, L'~')); - sm.ProcessString(L"\x1b[m"); - - if (paintEachNewline == PaintEveryLine) - { - print(std::wstring(numCharsSecondColor, L'~')); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - print(std::wstring(charsInSecondLine, L'~')); - } - else - { - // If we're not painting each and every line, then just print all of - // the wrapped text in one go. These '~'s will wrap from the first - // line onto the second line. - print(std::wstring(numCharsSecondColor + charsInSecondLine, L'~')); - } - - sm.ProcessString(L"\r\n"); - - if (paintEachNewline == PaintEveryLine || - paintEachNewline == PaintAfterBothLines) - { - VERIFY_SUCCEEDED(renderer.PaintFrame()); - } - } - - // Then print the trailing ':' on the last line. - print(L":"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - // At this point, the entire buffer looks like: - // - // row[25]: |~~~~~~~~~~~~~~~~~~~| - // row[26]: |~~~~~~ | - // row[27]: |~~~~~~~~~~~~~~~~~~~| - // row[28]: |~~~~~~ | - // row[29]: |~~~~~~~~~~~~~~~~~~~| - // row[30]: |~~~~~~ | - // row[31]: |: | - - // Now, we'll print the lines that wrapped, after circling the buffer. - for (auto i = 0; i < (circledRows) / 2; i++) - { - Log::Comment(NoThrowString().Format(L"writing pair of lines %d", i + (TerminalViewHeight / 2))); - - print(L"\r"); - sm.ProcessString(L"\x1b[33m"); - print(std::wstring(numCharsFirstColor, L'~')); - sm.ProcessString(L"\x1b[m"); - - if (paintEachNewline == PaintEveryLine) - { - // If we're painting every line, then we'll print the first line and - // redraw the "prompt" line (with the ':'), paint the buffer, then - // print the second line and reprint the prompt, as if the user was - // slowly hitting the down arrow one line per frame. - print(std::wstring(numCharsSecondColor, L'~')); - print(L":"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - print(L"\r"); - print(std::wstring(charsInSecondLine, L'~')); - } - else - { - // Otherwise, we'll print the wrapped line all in one frame. - print(std::wstring(numCharsSecondColor + charsInSecondLine, L'~')); - } - - // Print the prompt at the bottom of the buffer - print(L"\r\n"); - print(L":"); - - if (paintEachNewline == PaintEveryLine || - paintEachNewline == PaintAfterBothLines) - { - VERIFY_SUCCEEDED(renderer.PaintFrame()); - } - } - - auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& viewport) { - const auto width = viewport.width(); - const auto isTerminal = viewport.top != 0; - auto lastRow = viewport.bottom - 1; - for (til::CoordType row = 0; row < lastRow; row++) - { - Log::Comment(NoThrowString().Format(L"Checking row %d", row)); - - // The first line wrapped, the second didn't, so on and so forth. - // However, because conpty's buffer is only as tall as the viewport, - // we're going to lose lines off the top of the buffer. Most - // importantly, because we'll have the "prompt" line in the conpty - // buffer, then the top line of the conpty will _not_ be wrapped, - // when the 0th line of the terminal buffer _is_. - const auto isWrapped = (row % 2) == (isTerminal ? 0 : 1); - const auto rowCircled = row >= (viewport.bottom - circledRows); - - const auto actualNonSpacesAttrs = defaultAttrs; - const auto actualSpacesAttrs = rowCircled || isTerminal ? defaultAttrs : conhostDefaultAttrs; - - // When using WriteCharsLegacy to emit a wrapped line, with the - // frame painted before the second half of the wrapped line, the - // cursor needs to be manually moved to the second line, because - // that's what is expected of WriteCharsLegacy, and the terminal - // would otherwise delay that movement. But this means the line - // won't be marked as wrapped, and there's no easy way to fix that. - // For now we're just skipping this test. - if (!(writingMethod == PrintWithWriteCharsLegacy && paintEachNewline == PaintEveryLine && isWrapped)) - { - VERIFY_ARE_EQUAL(isWrapped, tb.GetRowByOffset(row).WasWrapForced()); - } - - if (isWrapped) - { - TestUtils::VerifyExpectedString(tb, std::wstring(charsInFirstLine, L'~'), { 0, row }); - } - else - { - auto iter = TestUtils::VerifyExpectedString(tb, std::wstring(charsInSecondLine, L'~'), { 0, row }); - TestUtils::VerifyExpectedString(std::wstring(width - charsInSecondLine, L' '), iter); - } - } - VERIFY_IS_FALSE(tb.GetRowByOffset(lastRow).WasWrapForced()); - auto iter = TestUtils::VerifyExpectedString(tb, std::wstring(1, L':'), { 0, lastRow }); - TestUtils::VerifyExpectedString(std::wstring(width - 1, L' '), iter); - }; - - Log::Comment(L"========== Checking the host buffer state =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive()); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the terminal buffer state =========="); - VERIFY_ARE_EQUAL(circledRows + 1, term->_mutableViewport.Top()); - verifyBuffer(*termTb, term->_mutableViewport.ToExclusive()); -} - -void ConptyRoundtripTests::DeleteWrappedWord() -{ - // See https://github.com/microsoft/terminal/issues/5839 - Log::Comment(L"This test ensures that when we print a empty row beneath a " - L"wrapped row, that we _actually_ clear it."); - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_mainBuffer.get(); - - _flushFirstFrame(); - - _checkConptyOutput = false; - _logConpty = true; - - // Print two lines of text: - // |AAAAAAAAAAAAA BBBBBB| - // |BBBBBBBB_ | - // (cursor on the '_') - - sm.ProcessString(L"\x1b[?25l"); - sm.ProcessString(std::wstring(50, L'A')); - sm.ProcessString(L" "); - sm.ProcessString(std::wstring(50, L'B')); - sm.ProcessString(L"\x1b[?25h"); - - auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& viewport, const bool after) { - const auto width = viewport.width(); - - auto iter1 = tb.GetCellDataAt({ 0, 0 }); - TestUtils::VerifySpanOfText(L"A", iter1, 0, 50); - TestUtils::VerifySpanOfText(L" ", iter1, 0, 1); - if (after) - { - TestUtils::VerifySpanOfText(L" ", iter1, 0, 50); - - auto iter2 = tb.GetCellDataAt({ 0, 1 }); - TestUtils::VerifySpanOfText(L" ", iter2, 0, width); - } - else - { - TestUtils::VerifySpanOfText(L"B", iter1, 0, 50); - - auto iter2 = tb.GetCellDataAt({ 0, 1 }); - TestUtils::VerifySpanOfText(L"B", iter2, 0, 50 - (width - 51)); - TestUtils::VerifySpanOfText(L" ", iter2, 0, width); - } - }; - - Log::Comment(L"========== Checking the host buffer state (before) =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive(), false); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - Log::Comment(L"========== Checking the terminal buffer state (before) =========="); - verifyBuffer(*termTb, term->_mutableViewport.ToExclusive(), false); - - // Now, go back and erase all the 'B's, as if the user executed a - // backward-kill-word in PowerShell. Afterwards, the buffer will look like: - // - // |AAAAAAAAAAAAA_ | - // | | - // - // We're doing this the same way PsReadline redraws the prompt - by just - // reprinting all of it. - - sm.ProcessString(L"\x1b[?25l"); - sm.ProcessString(L"\x1b[H"); - sm.ProcessString(std::wstring(50, L'A')); - sm.ProcessString(L" "); - - sm.ProcessString(std::wstring(TerminalViewWidth - 51, L' ')); - - sm.ProcessString(L"\x1b[2;1H"); - sm.ProcessString(std::wstring(50 - (TerminalViewWidth - 51), L' ')); - sm.ProcessString(L"\x1b[1;50H"); - sm.ProcessString(L"\x1b[?25h"); - - Log::Comment(L"========== Checking the host buffer state (after) =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive(), true); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - Log::Comment(L"========== Checking the terminal buffer state (after) =========="); - verifyBuffer(*termTb, term->_mutableViewport.ToExclusive(), true); -} - -// This test checks that upon conpty rendering again, terminal still maintains -// the same hyperlink IDs -void ConptyRoundtripTests::HyperlinkIdConsistency() -{ - Log::Comment(NoThrowString().Format( - L"Write a link - the text will simply be 'Link' and the uri will be 'http://example.com'")); - - 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->_mainBuffer; - - _flushFirstFrame(); - - hostSm.ProcessString(L"\x1b]8;;http://example.com\x1b\\Link\x1b]8;;\x1b\\"); - - // For self-generated IDs, conpty will send a custom ID of the form - // {sessionID}-{self-generated ID} - // self-generated IDs begin at 1 and increment from there - const std::string fmt{ "\x1b]8;id={}-1;http://example.com\x1b\\" }; - auto s = fmt::format(fmt, GetCurrentProcessId()); - expectedOutput.push_back(s); - expectedOutput.push_back("Link"); - expectedOutput.push_back("\x1b]8;;\x1b\\"); - - // Force a frame - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - // Move the cursor down - hostSm.ProcessString(L"\x1b[2;1H"); - expectedOutput.push_back("\r\n"); - - // Force a frame - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - // Move the cursor to somewhere in the link text - hostSm.ProcessString(L"\x1b[1;2H"); - expectedOutput.push_back("\x1b[1;2H"); - expectedOutput.push_back("\x1b[?25h"); - - // Force a frame - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - // Move the cursor off the link - hostSm.ProcessString(L"\x1b[2;1H"); - expectedOutput.push_back("\r\n"); - - // Force a frame - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - auto verifyData = [](TextBuffer& tb) { - // Check that all the linked cells still have the same ID - auto& row = tb.GetRowByOffset(0); - auto id = row.GetAttrByColumn(0).GetHyperlinkId(); - for (uint16_t i = 1; i < 4; ++i) - { - VERIFY_ARE_EQUAL(id, row.GetAttrByColumn(i).GetHyperlinkId()); - } - }; - - verifyData(hostTb); - verifyData(termTb); -} - -void ConptyRoundtripTests::ClearBufferSignal() -{ - Log::Comment(L"Write some text to the conpty buffer. Send a ClearBuffer " - L"signal, and check that all but the cursor line is removed " - L"from the host and the terminal."); - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_mainBuffer.get(); - - _flushFirstFrame(); - - _checkConptyOutput = false; - _logConpty = true; - - // Print two lines of text: - // |AAAAAAAAAAAAA BBBBBB| - // |BBBBBBBB_ | - // (cursor on the '_') - // A's are in blue-on-green, - // B's are in red-on-yellow - - sm.ProcessString(L"\x1b[?25l"); - sm.ProcessString(L"\x1b[34;42m"); - sm.ProcessString(std::wstring(50, L'A')); - sm.ProcessString(L" "); - sm.ProcessString(L"\x1b[31;43m"); - sm.ProcessString(std::wstring(50, L'B')); - sm.ProcessString(L"\x1b[?25h"); - - auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& viewport, const bool before) { - const auto width = viewport.width(); - const auto numCharsOnSecondLine = 50 - (width - 51); - auto iter1 = tb.GetCellDataAt({ 0, 0 }); - if (before) - { - TestUtils::VerifySpanOfText(L"A", iter1, 0, 50); - TestUtils::VerifySpanOfText(L" ", iter1, 0, 1); - TestUtils::VerifySpanOfText(L"B", iter1, 0, 50); - til::point expectedCursor{ numCharsOnSecondLine, 1 }; - VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); - } - else - { - TestUtils::VerifySpanOfText(L"B", iter1, 0, numCharsOnSecondLine); - til::point expectedCursor{ numCharsOnSecondLine, 0 }; - VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); - } - }; - - Log::Comment(L"========== Checking the host buffer state (before) =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive(), true); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - Log::Comment(L"========== Checking the terminal buffer state (before) =========="); - verifyBuffer(*termTb, term->_mutableViewport.ToExclusive(), true); - - Log::Comment(L"========== Clear the ConPTY buffer with the signal =========="); - _clearConpty(); - - Log::Comment(L"========== Checking the host buffer state (after) =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive(), false); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - Log::Comment(L"========== Checking the terminal buffer state (after) =========="); - verifyBuffer(*termTb, term->_mutableViewport.ToExclusive(), false); -} - -void ConptyRoundtripTests::SimpleAltBufferTest() -{ - Log::Comment(L"A test for entering and exiting the alt buffer, via conpty. " - L"Ensures cursor is in the right place, and contents are " - L"restored accordingly."); - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_mainBuffer.get(); - - _flushFirstFrame(); - - _checkConptyOutput = false; - _logConpty = true; - - // Print two lines of text: - // |AAAAAAAA | - // |BBBBBBBB_ | - // (cursor on the '_') - // A's are in blue-on-green, - // B's are in red-on-yellow - - // A one line version: (more or less) - // - // printf "\x1b[2J\x1b[H" ; printf - // "\x1b[?25l\x1b[34;42mAAAAA\n\x1b[31;43mBBBBB\x1b[?25h" ; sleep 2 ; printf - // "\x1b[?1049h" ; sleep 2 ; printf "CCCCC" ; sleep 2 ; printf "\x1b[?1049l" - // ; sleep 2 - - sm.ProcessString(L"\x1b[?25l"); - sm.ProcessString(L"\x1b[34;42m"); - sm.ProcessString(std::wstring(50, L'A')); - sm.ProcessString(L"\n"); - sm.ProcessString(L"\x1b[31;43m"); - sm.ProcessString(std::wstring(50, L'B')); - sm.ProcessString(L"\x1b[?25h"); - - // Four frames here: - // * After text is printed to main buffer - // * after we go to the alt buffer - // * print more to the alt buffer - // * after we go back to the buffer - - enum class Frame : int - { - InMainBufferBefore = 0, - InAltBufferBefore, - InAltBufferAfter, - InMainBufferAfter - }; - - auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& viewport, const Frame frame) { - const auto width = viewport.width(); - auto iter0 = tb.GetCellDataAt({ 0, 0 }); - auto iter1 = tb.GetCellDataAt({ 0, 1 }); - switch (frame) - { - case Frame::InMainBufferBefore: - case Frame::InMainBufferAfter: - { - TestUtils::VerifySpanOfText(L"A", iter0, 0, 50); - TestUtils::VerifySpanOfText(L" ", iter0, 0, static_cast(width - 50)); - - TestUtils::VerifySpanOfText(L"B", iter1, 0, 50); - TestUtils::VerifySpanOfText(L" ", iter1, 0, static_cast(width - 50)); - til::point expectedCursor{ 50, 1 }; - VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); - break; - } - case Frame::InAltBufferBefore: - { - TestUtils::VerifySpanOfText(L" ", iter0, 0, width); - TestUtils::VerifySpanOfText(L" ", iter1, 0, width); - - til::point expectedCursor{ 50, 1 }; - VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); - break; - } - case Frame::InAltBufferAfter: - { - TestUtils::VerifySpanOfText(L" ", iter0, 0, width); - TestUtils::VerifySpanOfText(L" ", iter1, 0, 50u); - TestUtils::VerifySpanOfText(L"C", iter1, 0, 5u); - TestUtils::VerifySpanOfText(L" ", iter1, 0, static_cast(width - 55)); - - til::point expectedCursor{ 55, 1 }; - VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); - break; - } - } - }; - - Log::Comment(L"========== Checking the host buffer state (InMainBufferBefore) =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive(), Frame::InMainBufferBefore); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - Log::Comment(L"========== Checking the terminal buffer state (InMainBufferBefore) =========="); - VERIFY_ARE_EQUAL(0, term->_GetMutableViewport().Top()); - verifyBuffer(*termTb, term->_GetMutableViewport().ToExclusive(), Frame::InMainBufferBefore); - - Log::Comment(L"========== Switch to the alt buffer =========="); - - gci.LockConsole(); // Lock must be taken to manipulate alt/main buffer state. - auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); - sm.ProcessString(L"\x1b[?1049h"); - // Don't leave ourselves in the alt buffer - that'll pollute other tests. - auto leaveAltBuffer = wil::scope_exit([&] { sm.ProcessString(L"\x1b[?1049l"); }); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - auto& siAlt = gci.GetActiveOutputBuffer(); - auto* hostAltTb = &siAlt.GetTextBuffer(); - auto* termAltTb = &term->_activeBuffer(); - - VERIFY_IS_TRUE(term->_inAltBuffer()); - VERIFY_ARE_NOT_EQUAL(termAltTb, termTb); - - Log::Comment(L"========== Checking the host buffer state (InAltBufferBefore) =========="); - verifyBuffer(*hostAltTb, siAlt.GetViewport().ToExclusive(), Frame::InAltBufferBefore); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - Log::Comment(L"========== Checking the terminal buffer state (InAltBufferBefore) =========="); - VERIFY_ARE_EQUAL(0, term->_GetMutableViewport().Top()); - verifyBuffer(*termAltTb, term->_GetMutableViewport().ToExclusive(), Frame::InAltBufferBefore); - - Log::Comment(L"========== Add some text to the alt buffer =========="); - - sm.ProcessString(L"CCCCC"); - Log::Comment(L"========== Checking the host buffer state (InAltBufferAfter) =========="); - verifyBuffer(*hostAltTb, siAlt.GetViewport().ToExclusive(), Frame::InAltBufferAfter); - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the terminal buffer state (InAltBufferAfter) =========="); - VERIFY_ARE_EQUAL(0, term->_GetMutableViewport().Top()); - verifyBuffer(*termAltTb, term->_GetMutableViewport().ToExclusive(), Frame::InAltBufferAfter); - - Log::Comment(L"========== Back to the main buffer =========="); - - sm.ProcessString(L"\x1b[?1049l"); - Log::Comment(L"========== Checking the host buffer state (InMainBufferAfter) =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive(), Frame::InMainBufferAfter); - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the terminal buffer state (InMainBufferAfter) =========="); - VERIFY_ARE_EQUAL(0, term->_GetMutableViewport().Top()); - verifyBuffer(*termTb, term->_GetMutableViewport().ToExclusive(), Frame::InMainBufferAfter); -} - -void ConptyRoundtripTests::AltBufferToAltBufferTest() -{ - Log::Comment(L"When we request the alt buffer when we're already in the alt buffer, we should still clear it out and replace it."); - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_mainBuffer.get(); - - _flushFirstFrame(); - - _checkConptyOutput = false; - _logConpty = true; - - // Print two lines of text: - // |AAAAAAAA | - // |BBBBBBBB_ | - // (cursor on the '_') - // A's are in blue-on-green, - // B's are in red-on-yellow - - // A one line version: (more or less) - // - // printf "\x1b[2J\x1b[H" ; printf - // "\x1b[?25l\x1b[34;42mAAAAA\n\x1b[31;43mBBBBB\x1b[?25h" ; sleep 2 ; printf - // "\x1b[?1049h" ; sleep 2 ; printf "CCCCC" ; sleep 2 ; printf "\x1b[?1049h" - // ; sleep 2 - - sm.ProcessString(L"\x1b[?25l"); - sm.ProcessString(L"\x1b[34;42m"); - sm.ProcessString(std::wstring(50, L'A')); - sm.ProcessString(L"\n"); - sm.ProcessString(L"\x1b[31;43m"); - sm.ProcessString(std::wstring(50, L'B')); - sm.ProcessString(L"\x1b[?25h"); - - // Four frames here: - // * After text is printed to main buffer - // * after we go to the alt buffer - // * print more to the alt buffer - // * after we go back to the buffer - - enum class Frame : int - { - InMainBufferBefore = 0, - InAltBufferBefore, - InAltBufferAfter, - StillInAltBuffer - }; - - auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& viewport, const Frame frame) { - const auto width = viewport.width(); - auto iter0 = tb.GetCellDataAt({ 0, 0 }); - auto iter1 = tb.GetCellDataAt({ 0, 1 }); - switch (frame) - { - case Frame::InMainBufferBefore: - { - TestUtils::VerifySpanOfText(L"A", iter0, 0, 50); - TestUtils::VerifySpanOfText(L" ", iter0, 0, static_cast(width - 50)); - - TestUtils::VerifySpanOfText(L"B", iter1, 0, 50); - TestUtils::VerifySpanOfText(L" ", iter1, 0, static_cast(width - 50)); - til::point expectedCursor{ 50, 1 }; - VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); - break; - } - case Frame::InAltBufferBefore: - { - TestUtils::VerifySpanOfText(L" ", iter0, 0, width); - TestUtils::VerifySpanOfText(L" ", iter1, 0, width); - - til::point expectedCursor{ 50, 1 }; - VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); - break; - } - case Frame::InAltBufferAfter: - { - TestUtils::VerifySpanOfText(L" ", iter0, 0, width); - TestUtils::VerifySpanOfText(L" ", iter1, 0, 50u); - TestUtils::VerifySpanOfText(L"C", iter1, 0, 5u); - TestUtils::VerifySpanOfText(L" ", iter1, 0, static_cast(width - 55)); - - til::point expectedCursor{ 55, 1 }; - VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); - break; - } - case Frame::StillInAltBuffer: - { - TestUtils::VerifySpanOfText(L" ", iter0, 0, width); - TestUtils::VerifySpanOfText(L" ", iter1, 0, width); - - til::point expectedCursor{ 55, 1 }; - VERIFY_ARE_EQUAL(expectedCursor, tb.GetCursor().GetPosition()); - break; - } - } - }; - - Log::Comment(L"========== Checking the host buffer state (InMainBufferBefore) =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive(), Frame::InMainBufferBefore); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - Log::Comment(L"========== Checking the terminal buffer state (InMainBufferBefore) =========="); - verifyBuffer(*termTb, term->_GetMutableViewport().ToExclusive(), Frame::InMainBufferBefore); - - Log::Comment(L"========== Switch to the alt buffer =========="); - - gci.LockConsole(); // Lock must be taken to manipulate alt/main buffer state. - auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); - sm.ProcessString(L"\x1b[?1049h"); - // Don't leave ourselves in the alt buffer - that'll pollute other tests. - auto leaveAltBuffer = wil::scope_exit([&] { sm.ProcessString(L"\x1b[?1049l"); }); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - auto* siAlt = &gci.GetActiveOutputBuffer(); - auto* hostAltTb = &siAlt->GetTextBuffer(); - auto* termAltTb = &term->_activeBuffer(); - - VERIFY_IS_TRUE(term->_inAltBuffer()); - VERIFY_ARE_NOT_EQUAL(termAltTb, termTb); - - Log::Comment(L"========== Checking the host buffer state (InAltBufferBefore) =========="); - verifyBuffer(*hostAltTb, siAlt->GetViewport().ToExclusive(), Frame::InAltBufferBefore); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - Log::Comment(L"========== Checking the terminal buffer state (InAltBufferBefore) =========="); - verifyBuffer(*termAltTb, term->_GetMutableViewport().ToExclusive(), Frame::InAltBufferBefore); - - Log::Comment(L"========== Add some text to the alt buffer =========="); - - sm.ProcessString(L"CCCCC"); - Log::Comment(L"========== Checking the host buffer state (InAltBufferAfter) =========="); - verifyBuffer(*hostAltTb, siAlt->GetViewport().ToExclusive(), Frame::InAltBufferAfter); - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the terminal buffer state (InAltBufferAfter) =========="); - verifyBuffer(*termAltTb, term->_GetMutableViewport().ToExclusive(), Frame::InAltBufferAfter); - - Log::Comment(L"========== Stay in the alt buffer, what happens? =========="); - - sm.ProcessString(L"\x1b[?1049h"); - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - siAlt = &gci.GetActiveOutputBuffer(); - hostAltTb = &siAlt->GetTextBuffer(); - termAltTb = &term->_activeBuffer(); - - Log::Comment(L"========== Checking the host buffer state (StillInAltBuffer) =========="); - verifyBuffer(*hostAltTb, siAlt->GetViewport().ToExclusive(), Frame::StillInAltBuffer); - - Log::Comment(L"========== Checking the terminal buffer state (StillInAltBuffer) =========="); - verifyBuffer(*termAltTb, term->_GetMutableViewport().ToExclusive(), Frame::StillInAltBuffer); -} - -void ConptyRoundtripTests::TestPowerLineFirstFrame() -{ - Log::Comment(L"This is a test for GH#8341. If we received colored spaces " - L"BEFORE the first frame, we should still emit them!"); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - - auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_mainBuffer.get(); - - _checkConptyOutput = false; - - TextAttribute whiteOnGreen{}; - whiteOnGreen.SetIndexedForeground(TextColor::DARK_WHITE); - whiteOnGreen.SetIndexedBackground(TextColor::BRIGHT_GREEN); - - TextAttribute greenOnBlack{}; - greenOnBlack.SetIndexedForeground(TextColor::BRIGHT_GREEN); - greenOnBlack.SetIndexedBackground(TextColor::BRIGHT_BLACK); - - TextAttribute whiteOnBlack{}; - whiteOnBlack.SetIndexedForeground(TextColor::DARK_WHITE); - whiteOnBlack.SetIndexedBackground(TextColor::BRIGHT_BLACK); - - TextAttribute blackOnDefault{}; - blackOnDefault.SetIndexedForeground(TextColor::BRIGHT_BLACK); - - TextAttribute defaultOnDefault{}; - - Log::Comment(L"========== Fill test content =========="); - - // As a pwsh one-liner: - // - // "`e[37m`e[102m foo\bar `e[92m`e[100m▶ `e[37mBar `e[90m`e[49m▶ `e[m" - // - // Generally taken from - // https://github.com/microsoft/terminal/issues/8341#issuecomment-731310022, - // but minimized for easier testing. - - sm.ProcessString(L"\x1b[37m\x1b[102m" // dark white on bright green - L" foo\\bar "); - sm.ProcessString(L"\x1b[92m\x1b[100m" // bright green on bright black - L"▶ "); - sm.ProcessString(L"\x1b[37m" // dark white on bright black - L"Bar "); - sm.ProcessString(L"\x1b[90m\x1b[49m" // bright black on default - L"▶ "); - sm.ProcessString(L"\x1b[m\n"); // default on default - - auto verifyBuffer = [&](const TextBuffer& tb) { - // If this test fails on character 8, then it's because we didn't emit the space, we just moved ahead. - auto iter0 = TestUtils::VerifyLineContains(tb, { 0, 0 }, whiteOnGreen, 9u); - TestUtils::VerifyLineContains(iter0, OutputCellIterator{ greenOnBlack, 2u }); - TestUtils::VerifyLineContains(iter0, OutputCellIterator{ whiteOnBlack, 4u }); - TestUtils::VerifyLineContains(iter0, OutputCellIterator{ blackOnDefault, 2u }); - }; - - Log::Comment(L"========== Check host buffer =========="); - verifyBuffer(*hostTb); - - Log::Comment(L"========== Paint first frame =========="); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Check terminal buffer =========="); - verifyBuffer(*termTb); -} - -void ConptyRoundtripTests::AltBufferResizeCrash() -{ - Log::Comment(L"During the review for GH#12719, it was noticed that this " - L"particular combination of resizing could crash the terminal." - L" This test makes sure we don't."); - - // Anything that resizes the buffer needs IsolationLevel:Method - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method") - END_TEST_METHOD_PROPERTIES() - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - - gci.LockConsole(); // Lock must be taken to manipulate alt/main buffer state. - auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); - - _flushFirstFrame(); - - _checkConptyOutput = false; - - // Don't leave ourselves in the alt buffer - that'll pollute other tests. - auto leaveAltBuffer = wil::scope_exit([&] { sm.ProcessString(L"\x1b[?1049l"); }); - - // Note: we really don't care about the output in this test. We could, but - // mostly we care to ensure we don't crash. If we make it through this test, - // then it's a success. - - Log::Comment(L"========== Resize to 132x24 =========="); - sm.ProcessString(L"\x1b[8;24;132t"); - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Move cursor to (99,11) =========="); - sm.ProcessString(L"\x1b[12;100H"); - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Switch to the alt buffer =========="); - sm.ProcessString(L"\x1b[?1049h"); - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Resize to 80x10 =========="); - sm.ProcessString(L"\x1b[8;10;80t"); - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); -} - -void ConptyRoundtripTests::TestNoExtendedAttrsOptimization() -{ - Log::Comment(L"We don't want conpty to optimize out runs of spaces that DO " - L"have extended attrs, because EL / ECH don't fill space with " - L"those attributes"); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - - auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_mainBuffer.get(); - - gci.LockConsole(); // Lock must be taken to manipulate alt/main buffer state. - auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); - - _flushFirstFrame(); - - _checkConptyOutput = false; - - TextAttribute reverseAttrs{}; - reverseAttrs.SetReverseVideo(true); - - auto verifyBuffer = [&](const TextBuffer& tb) { - auto iter0 = TestUtils::VerifyLineContains(tb, { 0, 0 }, L' ', reverseAttrs, 9u); - TestUtils::VerifyExpectedString(L"test", iter0); - TestUtils::VerifyLineContains(iter0, L' ', reverseAttrs, 9u); - - TestUtils::VerifyLineContains(tb, { 0, 1 }, L' ', reverseAttrs, static_cast(TerminalViewWidth)); - }; - - Log::Comment(L"========== Fill test content =========="); - sm.ProcessString(L"\x1b[7m test \x1b[m\n"); - sm.ProcessString(L"\x1b[7m"); - sm.ProcessString(std::wstring(TerminalViewWidth, L' ')); - sm.ProcessString(L"\x1b[m\n"); - - Log::Comment(L"========== Check host buffer =========="); - verifyBuffer(*hostTb); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Check terminal buffer =========="); - verifyBuffer(*termTb); -} - -void ConptyRoundtripTests::TestNoBackgroundAttrsOptimization() -{ - Log::Comment(L"Same as above, with BG attrs"); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - - auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_mainBuffer.get(); - - gci.LockConsole(); // Lock must be taken to manipulate alt/main buffer state. - auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); - - _flushFirstFrame(); - - _checkConptyOutput = false; - - TextAttribute bgAttrs{}; - bgAttrs.SetIndexedBackground(TextColor::DARK_WHITE); - - auto verifyBuffer = [&](const TextBuffer& tb) { - auto iter0 = TestUtils::VerifyLineContains(tb, { 0, 0 }, L' ', bgAttrs, 9u); - TestUtils::VerifyExpectedString(L"test", iter0); - TestUtils::VerifyLineContains(iter0, L' ', bgAttrs, 9u); - - TestUtils::VerifyLineContains(tb, { 0, 1 }, L' ', bgAttrs, static_cast(TerminalViewWidth)); - }; - - Log::Comment(L"========== Fill test content =========="); - sm.ProcessString(L"\x1b[47m test \x1b[m\n"); - sm.ProcessString(L"\x1b[47m"); - sm.ProcessString(std::wstring(TerminalViewWidth, L' ')); - sm.ProcessString(L"\x1b[m\n"); - - Log::Comment(L"========== Check host buffer =========="); - verifyBuffer(*hostTb); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Check terminal buffer =========="); - verifyBuffer(*termTb); -} - -#define FTCS_A L"\x1b]133;A\x1b\\" -#define FTCS_B L"\x1b]133;B\x1b\\" -#define FTCS_C L"\x1b]133;C\x1b\\" -#define FTCS_D L"\x1b]133;D\x1b\\" - -void ConptyRoundtripTests::SimplePromptRegions() -{ - Log::Comment(L"Same as the ScreenBufferTests::ComplicatedPromptRegions, but in conpty"); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - - auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_mainBuffer.get(); - - gci.LockConsole(); // Lock must be taken to manipulate alt/main buffer state. - auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); - - _flushFirstFrame(); - - _checkConptyOutput = false; - - auto verifyBuffer = [&](const TextBuffer& tb) { - const auto& cursor = tb.GetCursor(); - { - const til::point expectedCursor{ 17, 4 }; - VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition()); - } - const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope; - - const auto& row0 = tb.GetRowByOffset(0); - const auto& row4 = tb.GetRowByOffset(4); - VERIFY_IS_TRUE(row0.GetScrollbarData().has_value()); - VERIFY_IS_TRUE(row4.GetScrollbarData().has_value()); - - const auto marks = tb.GetMarkExtents(); - VERIFY_ARE_EQUAL(2u, marks.size()); - - { - auto& mark = marks[0]; - const til::point expectedStart{ 0, 0 }; - const til::point expectedEnd{ 17, 0 }; - const til::point expectedOutputStart{ 24, 0 }; // `Foo-Bar` is 7 characters - const til::point expectedOutputEnd{ 13, 3 }; - VERIFY_ARE_EQUAL(expectedStart, mark.start); - VERIFY_ARE_EQUAL(expectedEnd, mark.end); - - VERIFY_ARE_EQUAL(expectedOutputStart, *mark.commandEnd); - VERIFY_ARE_EQUAL(expectedOutputEnd, *mark.outputEnd); - } - { - auto& mark = marks[1]; - const til::point expectedStart{ 0, 4 }; - const til::point expectedEnd{ 17, 4 }; - VERIFY_ARE_EQUAL(expectedStart, mark.start); - VERIFY_ARE_EQUAL(expectedEnd, mark.end); - VERIFY_IS_FALSE(mark.commandEnd.has_value()); - VERIFY_IS_FALSE(mark.outputEnd.has_value()); - } - }; - - Log::Comment(L"========== Fill test content =========="); - - auto _writePrompt = [](StateMachine& stateMachine, const auto& path) { - // A prompt looks like: - // `PWSH C:\Windows> ` - // - // which is 17 characters for C:\Windows - stateMachine.ProcessString(FTCS_D); - stateMachine.ProcessString(FTCS_A); - stateMachine.ProcessString(L"\x1b]9;9;"); - stateMachine.ProcessString(path); - stateMachine.ProcessString(L"\x7"); - stateMachine.ProcessString(L"PWSH "); - stateMachine.ProcessString(path); - stateMachine.ProcessString(L"> "); - stateMachine.ProcessString(FTCS_B); - }; - - _writePrompt(sm, L"C:\\Windows"); - sm.ProcessString(L"Foo-bar"); - sm.ProcessString(FTCS_C); - sm.ProcessString(L"\r\n"); - sm.ProcessString(L"This is some text \r\n"); // y=1 - sm.ProcessString(L"with varying amounts \r\n"); // y=2 - sm.ProcessString(L"of whitespace\r\n"); // y=3 - - _writePrompt(sm, L"C:\\Windows"); // y=4 - - Log::Comment(L"========== Check host buffer =========="); - verifyBuffer(*hostTb); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Check terminal buffer =========="); - verifyBuffer(*termTb); -} - -void ConptyRoundtripTests::MultilinePromptRegions() -{ - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - - auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_mainBuffer.get(); - - gci.LockConsole(); // Lock must be taken to manipulate alt/main buffer state. - auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); - - _flushFirstFrame(); - - _checkConptyOutput = false; - - auto bufferWidth = term->GetViewport().Width(); - - auto verifyBuffer = [&](const TextBuffer& tb) { - const auto& cursor = tb.GetCursor(); - { - const til::point expectedCursor{ 2, 6 }; - VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition()); - } - const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope; - - const auto& row0 = tb.GetRowByOffset(0); - const auto& row5 = tb.GetRowByOffset(5); - VERIFY_IS_TRUE(row0.GetScrollbarData().has_value()); - VERIFY_IS_TRUE(row5.GetScrollbarData().has_value()); - - const auto marks = tb.GetMarkExtents(); - VERIFY_ARE_EQUAL(2u, marks.size()); - - { - Log::Comment(L"Row 0"); - const auto& row = tb.GetRowByOffset(0); - const auto& attrs = row.Attributes(); - const auto& runs = attrs.runs(); - VERIFY_ARE_EQUAL(2u, runs.size()); - auto run0 = runs[0]; - auto run1 = runs[1]; - VERIFY_ARE_EQUAL(17, run0.length); - VERIFY_ARE_EQUAL(MarkKind::Prompt, run0.value.GetMarkAttributes()); - - VERIFY_ARE_EQUAL(bufferWidth - 17, run1.length); - VERIFY_ARE_EQUAL(MarkKind::None, run1.value.GetMarkAttributes()); - } - { - Log::Comment(L"Row 1"); - const auto& row = tb.GetRowByOffset(1); - const auto& attrs = row.Attributes(); - const auto& runs = attrs.runs(); - VERIFY_ARE_EQUAL(3u, runs.size()); - auto run0 = runs[0]; - auto run1 = runs[1]; - auto run2 = runs[2]; - VERIFY_ARE_EQUAL(2, run0.length); - VERIFY_ARE_EQUAL(MarkKind::Prompt, run0.value.GetMarkAttributes()); - - VERIFY_ARE_EQUAL(7, run1.length); - VERIFY_ARE_EQUAL(MarkKind::Command, run1.value.GetMarkAttributes()); - - VERIFY_ARE_EQUAL(bufferWidth - 9, run2.length); - VERIFY_ARE_EQUAL(MarkKind::None, run2.value.GetMarkAttributes()); - } - { - Log::Comment(L"Row 2"); - const auto& row = tb.GetRowByOffset(2); - const auto& attrs = row.Attributes(); - const auto& runs = attrs.runs(); - VERIFY_ARE_EQUAL(2u, runs.size()); - auto run0 = runs[0]; - auto run1 = runs[1]; - VERIFY_ARE_EQUAL(22, run0.length); - VERIFY_ARE_EQUAL(MarkKind::Output, run0.value.GetMarkAttributes()); - - VERIFY_ARE_EQUAL(bufferWidth - 22, run1.length); - VERIFY_ARE_EQUAL(MarkKind::None, run1.value.GetMarkAttributes()); - } - { - Log::Comment(L"Row 3"); - const auto& row = tb.GetRowByOffset(3); - const auto& attrs = row.Attributes(); - const auto& runs = attrs.runs(); - VERIFY_ARE_EQUAL(2u, runs.size()); - auto run0 = runs[0]; - auto run1 = runs[1]; - VERIFY_ARE_EQUAL(22, run0.length); - VERIFY_ARE_EQUAL(MarkKind::Output, run0.value.GetMarkAttributes()); - - VERIFY_ARE_EQUAL(bufferWidth - 22, run1.length); - VERIFY_ARE_EQUAL(MarkKind::None, run1.value.GetMarkAttributes()); - } - - { - auto& mark = marks[0]; - const til::point expectedStart{ 0, 0 }; - const til::point expectedEnd{ 2, 1 }; - const til::point expectedOutputStart{ 9, 1 }; // `Foo-Bar` is 7 characters - const til::point expectedOutputEnd{ 13, 4 }; - VERIFY_ARE_EQUAL(expectedStart, mark.start); - VERIFY_ARE_EQUAL(expectedEnd, mark.end); - - VERIFY_ARE_EQUAL(expectedOutputStart, *mark.commandEnd); - VERIFY_ARE_EQUAL(expectedOutputEnd, *mark.outputEnd); - } - { - auto& mark = marks[1]; - const til::point expectedStart{ 0, 5 }; - const til::point expectedEnd{ 2, 6 }; - VERIFY_ARE_EQUAL(expectedStart, mark.start); - VERIFY_ARE_EQUAL(expectedEnd, mark.end); - VERIFY_IS_FALSE(mark.commandEnd.has_value()); - VERIFY_IS_FALSE(mark.outputEnd.has_value()); - } - }; - - Log::Comment(L"========== Fill test content =========="); - - auto _writePrompt = [](StateMachine& stateMachine, const auto& path) { - // A prompt looks like: - // `PWSH C:\Windows >` - // `> ` - // - // which two rows. The first is 17 characters for C:\Windows - stateMachine.ProcessString(FTCS_D); - stateMachine.ProcessString(FTCS_A); - stateMachine.ProcessString(L"\x1b]9;9;"); - stateMachine.ProcessString(path); - stateMachine.ProcessString(L"\x7"); - stateMachine.ProcessString(L"PWSH "); - stateMachine.ProcessString(path); - stateMachine.ProcessString(L" >\r\n"); - stateMachine.ProcessString(L"> "); - stateMachine.ProcessString(FTCS_B); - }; - - _writePrompt(sm, L"C:\\Windows"); // y=0,1 - sm.ProcessString(L"Foo-bar"); - sm.ProcessString(FTCS_C); - sm.ProcessString(L"\r\n"); - sm.ProcessString(L"This is some text \r\n"); // y=2 - sm.ProcessString(L"with varying amounts \r\n"); // y=3 - sm.ProcessString(L"of whitespace\r\n"); // y=4 - - _writePrompt(sm, L"C:\\Windows"); // y=5, 6 - - Log::Comment(L"========== Check host buffer =========="); - verifyBuffer(*hostTb); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Check terminal buffer =========="); - verifyBuffer(*termTb); -} - -void ConptyRoundtripTests::ManyMultilinePromptsWithTrailingSpaces() -{ - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - - auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_mainBuffer.get(); - - gci.LockConsole(); // Lock must be taken to manipulate alt/main buffer state. - auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); - - _flushFirstFrame(); - - _checkConptyOutput = false; - - auto bufferWidth = term->GetViewport().Width(); - - auto verifyFirstRowOfPrompt = [&](const ROW& row) { - const auto& attrs = row.Attributes(); - const auto& runs = attrs.runs(); - VERIFY_ARE_EQUAL(2u, runs.size()); - auto run0 = runs[0]; - auto run1 = runs[1]; - VERIFY_ARE_EQUAL(17, run0.length); - VERIFY_ARE_EQUAL(MarkKind::Prompt, run0.value.GetMarkAttributes()); - - VERIFY_ARE_EQUAL(bufferWidth - 17, run1.length); - VERIFY_ARE_EQUAL(MarkKind::None, run1.value.GetMarkAttributes()); - }; - auto verifySecondRowOfPrompt = [&](const ROW& row, const auto expectedCommandLength) { - const auto& attrs = row.Attributes(); - const auto& runs = attrs.runs(); - VERIFY_ARE_EQUAL(3u, runs.size()); - auto run0 = runs[0]; - auto run1 = runs[1]; - auto run2 = runs[2]; - VERIFY_ARE_EQUAL(2, run0.length); - VERIFY_ARE_EQUAL(MarkKind::Prompt, run0.value.GetMarkAttributes()); - - VERIFY_ARE_EQUAL(expectedCommandLength, run1.length); - VERIFY_ARE_EQUAL(MarkKind::Command, run1.value.GetMarkAttributes()); - - VERIFY_ARE_EQUAL(bufferWidth - (2 + expectedCommandLength), run2.length); - VERIFY_ARE_EQUAL(MarkKind::None, run2.value.GetMarkAttributes()); - }; - - auto verifyBuffer = [&](const TextBuffer& tb) { - const auto& cursor = tb.GetCursor(); - { - const til::point expectedCursor{ 0, 11 }; - VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition()); - } - const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope; - - const auto marks = tb.GetMarkExtents(); - VERIFY_ARE_EQUAL(3u, marks.size()); - - Log::Comment(L"Row 0"); - verifyFirstRowOfPrompt(tb.GetRowByOffset(0)); - - Log::Comment(L"Row 1"); - verifySecondRowOfPrompt(tb.GetRowByOffset(1), 7); - - { - Log::Comment(L"Row 2"); - const auto& row = tb.GetRowByOffset(2); - const auto& attrs = row.Attributes(); - const auto& runs = attrs.runs(); - VERIFY_ARE_EQUAL(2u, runs.size()); - auto run0 = runs[0]; - auto run1 = runs[1]; - VERIFY_ARE_EQUAL(22, run0.length); - VERIFY_ARE_EQUAL(MarkKind::Output, run0.value.GetMarkAttributes()); - - VERIFY_ARE_EQUAL(bufferWidth - 22, run1.length); - VERIFY_ARE_EQUAL(MarkKind::None, run1.value.GetMarkAttributes()); - } - { - Log::Comment(L"Row 3"); - const auto& row = tb.GetRowByOffset(3); - const auto& attrs = row.Attributes(); - const auto& runs = attrs.runs(); - VERIFY_ARE_EQUAL(2u, runs.size()); - auto run0 = runs[0]; - auto run1 = runs[1]; - VERIFY_ARE_EQUAL(22, run0.length); - VERIFY_ARE_EQUAL(MarkKind::Output, run0.value.GetMarkAttributes()); - - VERIFY_ARE_EQUAL(bufferWidth - 22, run1.length); - VERIFY_ARE_EQUAL(MarkKind::None, run1.value.GetMarkAttributes()); - } - - Log::Comment(L"Row 5"); - verifyFirstRowOfPrompt(tb.GetRowByOffset(5)); - - Log::Comment(L"Row 6"); - verifySecondRowOfPrompt(tb.GetRowByOffset(6), 7); - - Log::Comment(L"Row 8"); - verifyFirstRowOfPrompt(tb.GetRowByOffset(8)); - - Log::Comment(L"Row 9"); - verifySecondRowOfPrompt(tb.GetRowByOffset(9), 6); - - { - Log::Comment(L"Foo-bar mark on rows 0 & 1"); - - auto& mark = marks[0]; - const til::point expectedStart{ 0, 0 }; - const til::point expectedEnd{ 2, 1 }; - - // The command ends at {9,1} (the end of the Foo-Bar string). - // However, the first character in the output is at {0,2}. - const til::point expectedOutputStart{ 9, 1 }; // `Foo-Bar` is 7 characters - const til::point expectedOutputEnd{ 13, 4 }; - VERIFY_ARE_EQUAL(expectedStart, mark.start); - VERIFY_ARE_EQUAL(expectedEnd, mark.end); - - VERIFY_ARE_EQUAL(expectedOutputStart, *mark.commandEnd); - VERIFY_ARE_EQUAL(expectedOutputEnd, *mark.outputEnd); - } - { - Log::Comment(L"Boo-far mark on rows 5 & 6"); - auto& mark = marks[1]; - const til::point expectedStart{ 0, 5 }; - const til::point expectedEnd{ 2, 6 }; - const til::point expectedOutputStart{ 9, 6 }; // `Boo-far` is 7 characters - const til::point expectedOutputEnd{ 22, 7 }; - - VERIFY_ARE_EQUAL(expectedStart, mark.start); - VERIFY_ARE_EQUAL(expectedEnd, mark.end); - - VERIFY_ARE_EQUAL(expectedOutputStart, *mark.commandEnd); - VERIFY_ARE_EQUAL(expectedOutputEnd, *mark.outputEnd); - } - { - Log::Comment(L"yikes? mark on rows 8 & 9"); - auto& mark = marks[2]; - const til::point expectedStart{ 0, 8 }; - const til::point expectedEnd{ 2, 9 }; - const til::point expectedOutputStart{ 8, 9 }; // `yikes?` is 6 characters - const til::point expectedOutputEnd{ 22, 10 }; - - VERIFY_ARE_EQUAL(expectedStart, mark.start); - VERIFY_ARE_EQUAL(expectedEnd, mark.end); - - VERIFY_ARE_EQUAL(expectedOutputStart, *mark.commandEnd); - VERIFY_ARE_EQUAL(expectedOutputEnd, *mark.outputEnd); - } - }; - - Log::Comment(L"========== Fill test content =========="); - - auto writePrompt = [](StateMachine& stateMachine, const auto& path) { - // A prompt looks like: - // `PWSH C:\Windows >` - // `> ` - // - // which two rows. The first is 17 characters for C:\Windows - stateMachine.ProcessString(FTCS_D); - stateMachine.ProcessString(FTCS_A); - stateMachine.ProcessString(L"\x1b]9;9;"); - stateMachine.ProcessString(path); - stateMachine.ProcessString(L"\x7"); - stateMachine.ProcessString(L"PWSH "); - stateMachine.ProcessString(path); - stateMachine.ProcessString(L" >\r\n"); - stateMachine.ProcessString(L"> "); - stateMachine.ProcessString(FTCS_B); - }; - auto writeCommand = [](StateMachine& stateMachine, const auto& cmd) { - stateMachine.ProcessString(cmd); - stateMachine.ProcessString(FTCS_C); - stateMachine.ProcessString(L"\r\n"); - }; - - writePrompt(sm, L"C:\\Windows"); // y=0,1 - writeCommand(sm, L"Foo-bar"); - sm.ProcessString(L"This is some text \r\n"); // y=2 - sm.ProcessString(L"with varying amounts \r\n"); // y=3 - sm.ProcessString(L"of whitespace\r\n"); // y=4 - - writePrompt(sm, L"C:\\Windows"); // y=5, 6 - writeCommand(sm, L"Boo-far"); // y=6 - sm.ProcessString(L"This is more text \r\n"); // y=7 - - writePrompt(sm, L"C:\\Windows"); // y=8,9 - writeCommand(sm, L"yikes?"); // y=9 - sm.ProcessString(L"This is even more \r\n"); // y=10 - - Log::Comment(L"========== Check host buffer =========="); - verifyBuffer(*hostTb); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Check terminal buffer =========="); - verifyBuffer(*termTb); -} - -void ConptyRoundtripTests::ReflowPromptRegions() -{ - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method") // always isolate things that resize the buffer - TEST_METHOD_PROPERTY(L"Data:dx", L"{-15, -1, 0, 1, 15}") - END_TEST_METHOD_PROPERTIES() - - INIT_TEST_PROPERTY(int, dx, L"The change in width of the buffer"); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - - auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_mainBuffer.get(); - - gci.LockConsole(); // Lock must be taken to manipulate alt/main buffer state. - auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); - - _flushFirstFrame(); - - _checkConptyOutput = false; - - auto originalWidth = term->GetViewport().Width(); - - auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& /*viewport*/, const bool /*isTerminal*/, const bool afterResize) { - const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope; - - // Just the dx=+1 case doesn't unwrap the line onto one line, but the dx=+15 case does. - const bool unwrapped = afterResize && dx > 1; - const int unwrapAdjust = unwrapped ? -1 : 0; - const auto marks = tb.GetMarkExtents(); - VERIFY_ARE_EQUAL(3u, marks.size()); - { - Log::Comment(L"Mark 0"); - - auto& mark = marks[0]; - const til::point expectedStart{ 0, 0 }; - const til::point expectedEnd{ 10, 0 }; - const til::point expectedOutputStart{ 17, 0 }; // `Foo-Bar` is 7 characters - const til::point expectedOutputEnd{ 13, 3 }; - VERIFY_ARE_EQUAL(expectedStart, mark.start); - VERIFY_ARE_EQUAL(expectedEnd, mark.end); - - VERIFY_ARE_EQUAL(expectedOutputStart, *mark.commandEnd); - VERIFY_ARE_EQUAL(expectedOutputEnd, *mark.outputEnd); - } - { - Log::Comment(L"Mark 1"); - - auto& mark = marks[1]; - const til::point expectedStart{ 0, 4 }; - const til::point expectedEnd{ 10, 4 }; - // {originalWidth} characters of 'F', maybe wrapped. - const til::point originalPos = til::point{ 10, 5 }; - til::point afterPos = originalPos; - // walk that original pos dx times into the actual real place in the buffer. - auto bufferViewport = tb.GetSize(); - bufferViewport.WalkInBounds(afterPos, -dx); - const auto expectedOutputStart = !afterResize ? - originalPos : // printed exactly a row, so we're exactly below the prompt - afterPos; - const til::point expectedOutputEnd{ 22, 6 + unwrapAdjust }; - VERIFY_ARE_EQUAL(expectedStart, mark.start); - VERIFY_ARE_EQUAL(expectedEnd, mark.end); - - VERIFY_ARE_EQUAL(expectedOutputStart, *mark.commandEnd); - VERIFY_ARE_EQUAL(expectedOutputEnd, *mark.outputEnd); - } - { - Log::Comment(L"Mark 2"); - - auto& mark = marks[2]; - const til::point expectedStart{ 0, 7 + unwrapAdjust }; - const til::point expectedEnd{ 10, 7 + unwrapAdjust }; - VERIFY_ARE_EQUAL(expectedStart, mark.start); - VERIFY_ARE_EQUAL(expectedEnd, mark.end); - VERIFY_IS_TRUE(mark.commandEnd.has_value()); - VERIFY_IS_FALSE(mark.outputEnd.has_value()); - } - }; - - Log::Comment(L"========== Fill test content =========="); - - auto writePrompt = [](StateMachine& stateMachine, const auto& path) { - // A prompt looks like: - // `PWSH C:\> ` - // - // which is 10 characters for "C:\" - stateMachine.ProcessString(FTCS_D); - stateMachine.ProcessString(FTCS_A); - stateMachine.ProcessString(L"\x1b]9;9;"); - stateMachine.ProcessString(path); - stateMachine.ProcessString(L"\x7"); - stateMachine.ProcessString(L"PWSH "); - stateMachine.ProcessString(path); - stateMachine.ProcessString(L"> "); - stateMachine.ProcessString(FTCS_B); - }; - auto writeCommand = [](StateMachine& stateMachine, const auto& cmd) { - stateMachine.ProcessString(cmd); - stateMachine.ProcessString(FTCS_C); - stateMachine.ProcessString(L"\r\n"); - }; - - // This first prompt didn't reflow at all - writePrompt(sm, L"C:\\"); // y=0 - writeCommand(sm, L"Foo-bar"); // y=0 - sm.ProcessString(L"This is some text \r\n"); // y=1 - sm.ProcessString(L"with varying amounts \r\n"); // y=2 - sm.ProcessString(L"of whitespace\r\n"); // y=3 - - // This second one, the command does. It stretches across lines - writePrompt(sm, L"C:\\"); // y=4 - writeCommand(sm, std::wstring(originalWidth, L'F')); // y=4,5 - sm.ProcessString(L"This is more text \r\n"); // y=6 - - writePrompt(sm, L"C:\\"); // y=7 - writeCommand(sm, L"yikes?"); // y=7 - - Log::Comment(L"========== Checking the host buffer state (before) =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive(), false, false); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the terminal buffer state (before) =========="); - verifyBuffer(*termTb, term->_mutableViewport.ToExclusive(), true, false); - - // After we resize, make sure to get the new textBuffers - std::tie(hostTb, termTb) = _performResize({ TerminalViewWidth + dx, - TerminalViewHeight }); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the host buffer state (after) =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive(), false, true); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the terminal buffer state (after) =========="); - verifyBuffer(*termTb, term->_mutableViewport.ToExclusive(), true, true); -} - -void ConptyRoundtripTests::ClearMarksTest() -{ - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:clearBufferMethod", L"{0, 1, 2}") - END_TEST_METHOD_PROPERTIES(); - - INIT_TEST_PROPERTY(int, clearBufferMethod, L"Controls whether we clear the buffer like cmd or like powershell"); - - Log::Comment(L"This test checks the shims for cmd.exe and powershell.exe. " - L"Their build in commands for clearing the console buffer " - L"should work to clear the terminal buffer, not just the " - L"terminal viewport."); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto* hostTb = &si.GetTextBuffer(); - auto* termTb = term->_mainBuffer.get(); - - auto& sm = si.GetStateMachine(); - - _flushFirstFrame(); - - _checkConptyOutput = false; - _logConpty = false; - - const auto hostView = si.GetViewport(); - const auto end = 2 * hostView.Height(); - - auto writePrompt = [](StateMachine& stateMachine, const auto& path) { - // A prompt looks like: - // `PWSH C:\> ` - // - // which is 10 characters for "C:\" - stateMachine.ProcessString(FTCS_D); - stateMachine.ProcessString(FTCS_A); - stateMachine.ProcessString(L"\x1b]9;9;"); - stateMachine.ProcessString(path); - stateMachine.ProcessString(L"\x7"); - stateMachine.ProcessString(L"PWSH "); - stateMachine.ProcessString(path); - stateMachine.ProcessString(L"> "); - stateMachine.ProcessString(FTCS_B); - }; - auto writeCommand = [](StateMachine& stateMachine, const auto& cmd) { - stateMachine.ProcessString(cmd); - stateMachine.ProcessString(FTCS_C); - stateMachine.ProcessString(L"\r\n"); - }; - - for (auto i = 0; i < end; i++) - { - writePrompt(sm, L"C:\\"); - writeCommand(sm, L"Foo-bar"); - sm.ProcessString(L"This is some text \r\n"); - } - writePrompt(sm, L"C:\\"); - - auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& /*viewport*/, const bool afterClear = false) { - const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope; - const auto marks = tb.GetMarkExtents(); - if (afterClear) - { - VERIFY_ARE_EQUAL(0u, marks.size()); - } - else - { - VERIFY_IS_GREATER_THAN(marks.size(), 1u, L"There should be at least one mark"); - } - }; - - Log::Comment(L"========== Checking the host buffer state (before) =========="); - verifyBuffer(*hostTb, si.GetViewport().ToExclusive()); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - Log::Comment(L"========== Checking the terminal buffer state (before) =========="); - verifyBuffer(*termTb, term->_mutableViewport.ToExclusive()); - - VERIFY_ARE_EQUAL(si.GetViewport().Dimensions(), si.GetBufferSize().Dimensions()); - VERIFY_ARE_EQUAL(si.GetViewport(), si.GetBufferSize()); - - _clear(clearBufferMethod, si); - - VERIFY_ARE_EQUAL(si.GetViewport().Dimensions(), si.GetBufferSize().Dimensions()); - VERIFY_ARE_EQUAL(si.GetViewport(), si.GetBufferSize()); - - Log::Comment(L"Painting the frame"); - VERIFY_SUCCEEDED(renderer.PaintFrame()); - Log::Comment(L"========== Checking the terminal buffer state (after) =========="); - verifyBuffer(*termTb, term->_mutableViewport.ToExclusive(), true); -} diff --git a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp index 33cf3cee8cf..a23831b173c 100644 --- a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp @@ -34,7 +34,6 @@ namespace HRESULT StartPaint() noexcept { return S_OK; } HRESULT EndPaint() noexcept { return S_OK; } HRESULT Present() noexcept { return S_OK; } - HRESULT PrepareForTeardown(_Out_ bool* /*pForcePaint*/) noexcept { return S_OK; } HRESULT ScrollFrame() noexcept { return S_OK; } HRESULT Invalidate(const til::rect* /*psrRegion*/) noexcept { return S_OK; } HRESULT InvalidateCursor(const til::rect* /*psrRegion*/) noexcept { return S_OK; } diff --git a/src/cascadia/UnitTests_TerminalCore/UnitTests.vcxproj b/src/cascadia/UnitTests_TerminalCore/UnitTests.vcxproj index 94967d5b0ed..b6f38d81ab4 100644 --- a/src/cascadia/UnitTests_TerminalCore/UnitTests.vcxproj +++ b/src/cascadia/UnitTests_TerminalCore/UnitTests.vcxproj @@ -23,7 +23,6 @@ Create - @@ -47,45 +46,6 @@ {ca5cad1a-abcd-429c-b551-8562ec954746} - - - - - {990F2657-8580-4828-943F-5DD657D11843} - - - {af0a096a-8b3a-4949-81ef-7df8f0fee91f} - - - {06ec74cb-9a12-429c-b551-8562ec954746} - - - {345fd5a4-b32b-4f29-bd1c-b033bd2c35cc} - - - {06ec74cb-9a12-429c-b551-8562ec964846} - - - {06ec74cb-9a12-429c-b551-8532ec964726} - - - {2fd12fbb-1ddb-46d8-b818-1023c624caca} - - - {18d09a24-8240-42d6-8cb6-236eee820262} - - - {dcf55140-ef6a-4736-a403-957e4f7430bb} - - - {ef3e32a7-5ff6-42b4-b6e2-96cd7d033f00} - - - {1c959542-bac2-4e55-9a6d-13251914cbb9} - diff --git a/src/common.build.pre.props b/src/common.build.pre.props index 72249d16c02..315902305ae 100644 --- a/src/common.build.pre.props +++ b/src/common.build.pre.props @@ -141,7 +141,7 @@ true precomp.h ProgramDatabase - $(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\Win32K;$(SolutionDir)\oss\chromium;$(SolutionDir)\oss\dynamic_bitset;$(SolutionDir)\oss\interval_tree;$(SolutionDir)\oss\libpopcnt;$(SolutionDir)\oss\pcg\include;%(AdditionalIncludeDirectories); + $(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\Win32K;$(SolutionDir)\oss\chromium;$(SolutionDir)\oss\interval_tree;$(SolutionDir)\oss\pcg\include;%(AdditionalIncludeDirectories); true false false diff --git a/src/host/ConsoleArguments.cpp b/src/host/ConsoleArguments.cpp index 0829c6cb70a..d1ecdcb2b16 100644 --- a/src/host/ConsoleArguments.cpp +++ b/src/host/ConsoleArguments.cpp @@ -18,7 +18,6 @@ const std::wstring_view ConsoleArguments::FILEPATH_LEADER_PREFIX = L"\\??\\"; const std::wstring_view ConsoleArguments::WIDTH_ARG = L"--width"; const std::wstring_view ConsoleArguments::HEIGHT_ARG = L"--height"; const std::wstring_view ConsoleArguments::INHERIT_CURSOR_ARG = L"--inheritcursor"; -const std::wstring_view ConsoleArguments::RESIZE_QUIRK = L"--resizeQuirk"; const std::wstring_view ConsoleArguments::FEATURE_ARG = L"--feature"; const std::wstring_view ConsoleArguments::FEATURE_PTY_ARG = L"pty"; const std::wstring_view ConsoleArguments::COM_SERVER_ARG = L"-Embedding"; @@ -495,12 +494,6 @@ void ConsoleArguments::s_ConsumeArg(_Inout_ std::vector& args, _In s_ConsumeArg(args, i); hr = S_OK; } - else if (arg == RESIZE_QUIRK) - { - _resizeQuirk = true; - s_ConsumeArg(args, i); - hr = S_OK; - } else if (arg == GLYPH_WIDTH) { hr = s_GetArgumentValue(args, i, &_textMeasurement); @@ -652,10 +645,6 @@ bool ConsoleArguments::GetInheritCursor() const { return _inheritCursor; } -bool ConsoleArguments::IsResizeQuirkEnabled() const -{ - return _resizeQuirk; -} #ifdef UNIT_TESTING // Method Description: diff --git a/src/host/ConsoleArguments.hpp b/src/host/ConsoleArguments.hpp index 5067c6d3853..902e7f1c667 100644 --- a/src/host/ConsoleArguments.hpp +++ b/src/host/ConsoleArguments.hpp @@ -46,7 +46,6 @@ class ConsoleArguments std::wstring GetOriginalCommandLine() const; std::wstring GetClientCommandline() const; - std::wstring GetVtMode() const; const std::wstring& GetTextMeasurement() const; bool GetForceV1() const; bool GetForceNoHandoff() const; @@ -54,7 +53,6 @@ class ConsoleArguments short GetWidth() const; short GetHeight() const; bool GetInheritCursor() const; - bool IsResizeQuirkEnabled() const; #ifdef UNIT_TESTING void EnableConptyModeForTests(); @@ -72,7 +70,6 @@ class ConsoleArguments static const std::wstring_view WIDTH_ARG; static const std::wstring_view HEIGHT_ARG; static const std::wstring_view INHERIT_CURSOR_ARG; - static const std::wstring_view RESIZE_QUIRK; static const std::wstring_view FEATURE_ARG; static const std::wstring_view FEATURE_PTY_ARG; static const std::wstring_view COM_SERVER_ARG; @@ -107,7 +104,6 @@ class ConsoleArguments _serverHandle(serverHandle), _signalHandle(signalHandle), _inheritCursor(inheritCursor), - _resizeQuirk(false), _runAsComServer{ runAsComServer } { } @@ -135,7 +131,6 @@ class ConsoleArguments DWORD _serverHandle; DWORD _signalHandle; bool _inheritCursor; - bool _resizeQuirk{ false }; [[nodiscard]] HRESULT _GetClientCommandline(_Inout_ std::vector& args, const size_t index, diff --git a/src/host/CursorBlinker.cpp b/src/host/CursorBlinker.cpp index 78f072ccc94..68250b5f423 100644 --- a/src/host/CursorBlinker.cpp +++ b/src/host/CursorBlinker.cpp @@ -82,10 +82,8 @@ void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo) const noexcept auto& cursor = buffer.GetCursor(); auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); auto* const pAccessibilityNotifier = ServiceLocator::LocateAccessibilityNotifier(); - const auto inConpty{ gci.IsInVtIoMode() }; - // GH#2988: ConPTY can now be focused, but it doesn't need to do any of this work either. - if (inConpty || !WI_IsFlagSet(gci.Flags, CONSOLE_HAS_FOCUS)) + if (!WI_IsFlagSet(gci.Flags, CONSOLE_HAS_FOCUS)) { goto DoScroll; } @@ -165,6 +163,12 @@ void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo) const noexcept // guCaretBlinkTime is -1. void CursorBlinker::SetCaretTimer() const noexcept { + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + if (gci.IsInVtIoMode()) + { + return; + } + using filetime_duration = std::chrono::duration>; static constexpr DWORD dwDefTimeout = 0x212; diff --git a/src/host/PtySignalInputThread.cpp b/src/host/PtySignalInputThread.cpp index 534a6d3ae87..45860f9c1a0 100644 --- a/src/host/PtySignalInputThread.cpp +++ b/src/host/PtySignalInputThread.cpp @@ -179,11 +179,7 @@ void PtySignalInputThread::_DoResizeWindow(const ResizeWindowData& data) return; } - if (_api.ResizeWindow(data.sx, data.sy)) - { - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - THROW_IF_FAILED(gci.GetVtIo()->SuppressResizeRepaint()); - } + _api.ResizeWindow(data.sx, data.sy); } void PtySignalInputThread::_DoClearBuffer() const diff --git a/src/host/VtInputThread.cpp b/src/host/VtInputThread.cpp index 2df7ee14989..65c62abf25e 100644 --- a/src/host/VtInputThread.cpp +++ b/src/host/VtInputThread.cpp @@ -55,54 +55,112 @@ DWORD WINAPI VtInputThread::StaticVtInputThreadProc(_In_ LPVOID lpParameter) void VtInputThread::_InputThread() { const auto cleanup = wil::scope_exit([this]() { - ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()->CloseInput(); + _hFile.reset(); + ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()->SendCloseEvent(); }); + OVERLAPPED* overlapped = nullptr; + OVERLAPPED overlappedBuf{}; + wil::unique_event overlappedEvent; + bool overlappedPending = false; char buffer[4096]; - DWORD dwRead = 0; + DWORD read = 0; til::u8state u8State; std::wstring wstr; + if (Utils::HandleWantsOverlappedIo(_hFile.get())) + { + overlappedEvent.reset(CreateEventExW(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS)); + if (overlappedEvent) + { + overlappedBuf.hEvent = overlappedEvent.get(); + overlapped = &overlappedBuf; + } + } + + // If we use overlapped IO We want to queue ReadFile() calls before processing the + // string, because LockConsole/ProcessString may take a while (relatively speaking). + // That's why the loop looks a little weird as it starts a read, processes the + // previous string, and finally converts the previous read to the next string. for (;;) { - const auto ok = ReadFile(_hFile.get(), buffer, ARRAYSIZE(buffer), &dwRead, nullptr); - - // The ReadFile() documentations calls out that: - // > If the lpNumberOfBytesRead parameter is zero when ReadFile returns TRUE on a pipe, the other - // > end of the pipe called the WriteFile function with nNumberOfBytesToWrite set to zero. - // But I was unable to replicate any such behavior. I'm not sure it's true anymore. - // - // However, what the documentations fails to mention is that winsock2 (WSA) handles of the \Device\Afd type are - // transparently compatible with ReadFile() and the WSARecv() documentations contains this important information: - // > For byte streams, zero bytes having been read [..] indicates graceful closure and that no more bytes will ever be read. - // In other words, for pipe HANDLE of unknown type you should consider `lpNumberOfBytesRead == 0` as an exit indicator. - // - // Here, `dwRead == 0` fixes a deadlock when exiting conhost while being in use by WSL whose hypervisor pipes are WSA. - if (!ok || dwRead == 0) + // When we have a `wstr` that's ready for processing we must do so without blocking. + // Otherwise, whatever the user typed will be delayed until the next IO operation. + // With overlapped IO that's not a problem because the ReadFile() calls won't block. + if (overlapped) { - break; + if (!ReadFile(_hFile.get(), &buffer[0], sizeof(buffer), &read, overlapped)) + { + if (GetLastError() != ERROR_IO_PENDING) + { + break; + } + overlappedPending = true; + } } - // If we hit a parsing error, eat it. It's bad utf-8, we can't do anything with it. - if (FAILED_LOG(til::u8u16({ buffer, gsl::narrow_cast(dwRead) }, wstr, u8State))) + // wstr can be empty in two situations: + // * The previous call to til::u8u16 failed. + // * We're using overlapped IO, and it's the first iteration. + if (!wstr.empty()) + { + try + { + // Make sure to call the GLOBAL Lock/Unlock, not the gci's lock/unlock. + // Only the global unlock attempts to dispatch ctrl events. If you use the + // gci's unlock, when you press C-c, it won't be dispatched until the + // next console API call. For something like `powershell sleep 60`, + // that won't happen for 60s + LockConsole(); + const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); + + _pInputStateMachine->ProcessString(wstr); + } + CATCH_LOG(); + } + + // Here's the counterpart to the start of the loop. We processed whatever was in `wstr`, + // so blocking synchronously on the pipe is now possible. + // If we used overlapped IO, we need to wait for the ReadFile() to complete. + // If we didn't, we can now safely block on our ReadFile() call. + if (overlapped) { - continue; + if (overlappedPending) + { + overlappedPending = false; + if (FAILED(Utils::GetOverlappedResultSameThread(overlapped, &read))) + { + break; + } + } + } + else + { + if (!ReadFile(_hFile.get(), &buffer[0], sizeof(buffer), &read, overlapped)) + { + break; + } } - try + // winsock2 (WSA) handles of the \Device\Afd type are transparently compatible with + // ReadFile() and the WSARecv() documentations contains this important information: + // > For byte streams, zero bytes having been read [..] indicates graceful closure and that no more bytes will ever be read. + // --> Exit if we've read 0 bytes. + if (read == 0) { - // Make sure to call the GLOBAL Lock/Unlock, not the gci's lock/unlock. - // Only the global unlock attempts to dispatch ctrl events. If you use the - // gci's unlock, when you press C-c, it won't be dispatched until the - // next console API call. For something like `powershell sleep 60`, - // that won't happen for 60s - LockConsole(); - const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); - - _pInputStateMachine->ProcessString(wstr); + break; } - CATCH_LOG(); + + TraceLoggingWrite( + g_hConhostV2EventTraceProvider, + "ConPTY ReadFile", + TraceLoggingCountedUtf8String(&buffer[0], read, "buffer"), + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), + TraceLoggingKeyword(TIL_KEYWORD_TRACE)); + + // If we hit a parsing error, eat it. It's bad utf-8, we can't do anything with it. + FAILED_LOG(til::u8u16({ &buffer[0], gsl::narrow_cast(read) }, wstr, u8State)); } } diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index 71f170aed3a..06fb4309371 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -4,11 +4,12 @@ #include "precomp.h" #include "VtIo.hpp" +#include + #include "handle.h" // LockConsole #include "output.h" // CloseConsoleProcessState #include "../interactivity/inc/ServiceLocator.hpp" #include "../renderer/base/renderer.hpp" -#include "../renderer/vt/Xterm256Engine.hpp" #include "../types/inc/CodepointWidthDetector.hpp" #include "../types/inc/utils.hpp" @@ -19,16 +20,9 @@ using namespace Microsoft::Console::Types; using namespace Microsoft::Console::Utils; using namespace Microsoft::Console::Interactivity; -VtIo::VtIo() : - _initialized(false), - _lookingForCursorPosition(false) -{ -} - [[nodiscard]] HRESULT VtIo::Initialize(const ConsoleArguments* const pArgs) { _lookingForCursorPosition = pArgs->GetInheritCursor(); - _resizeQuirk = pArgs->IsResizeQuirkEnabled(); // If we were already given VT handles, set up the VT IO engine to use those. if (pArgs->InConptyMode()) @@ -90,6 +84,16 @@ VtIo::VtIo() : _hOutput.reset(OutHandle); _hSignal.reset(SignalHandle); + if (Utils::HandleWantsOverlappedIo(_hOutput.get())) + { + _overlappedEvent.reset(CreateEventExW(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS)); + if (_overlappedEvent) + { + _overlappedBuf.hEvent = _overlappedEvent.get(); + _overlapped = &_overlappedBuf; + } + } + // The only way we're initialized is if the args said we're in conpty mode. // If the args say so, then at least one of in, out, or signal was specified _initialized = true; @@ -97,7 +101,7 @@ VtIo::VtIo() : } // Method Description: -// - Create the VtRenderer and the VtInputThread for this console. +// - Create the VtEngine and the VtInputThread for this console. // MUST BE DONE AFTER CONSOLE IS INITIALIZED, to make sure we've gotten the // buffer size from the attached client application. // Arguments: @@ -112,11 +116,9 @@ VtIo::VtIo() : { return S_FALSE; } - auto& globals = ServiceLocator::LocateGlobals(); - const auto& gci = globals.getConsoleInformation(); // SetWindowVisibility uses the console lock to protect access to _pVtRenderEngine. - assert(gci.IsConsoleLocked()); + assert(ServiceLocator::LocateGlobals().getConsoleInformation().IsConsoleLocked()); try { @@ -124,21 +126,6 @@ VtIo::VtIo() : { _pVtInputThread = std::make_unique(std::move(_hInput), _lookingForCursorPosition); } - - if (IsValidHandle(_hOutput.get())) - { - auto initialViewport = Viewport::FromDimensions({ 0, 0 }, gci.GetWindowSize()); - - auto xterm256Engine = std::make_unique(std::move(_hOutput), - initialViewport); - _pVtRenderEngine = std::move(xterm256Engine); - - if (_pVtRenderEngine) - { - _pVtRenderEngine->SetTerminalOwner(this); - _pVtRenderEngine->SetResizeQuirk(_resizeQuirk); - } - } } CATCH_RETURN(); @@ -167,49 +154,49 @@ bool VtIo::IsUsingVt() const { return S_FALSE; } - auto& g = ServiceLocator::LocateGlobals(); - if (_pVtRenderEngine) + if (_pVtInputThread) { - try - { - g.pRender->AddRenderEngine(_pVtRenderEngine.get()); - g.getConsoleInformation().GetActiveOutputBuffer().SetTerminalConnection(_pVtRenderEngine.get()); - - // Force the whole window to be put together first. - // We don't really need the handle, we just want to leverage the setup steps. - ServiceLocator::LocatePseudoWindow(); - } - CATCH_RETURN(); + LOG_IF_FAILED(_pVtInputThread->Start()); } - if (_pVtInputThread) { - LOG_IF_FAILED(_pVtInputThread->Start()); + auto writer = GetWriter(); + + // GH#4999 - Send a sequence to the connected terminal to request + // win32-input-mode from them. This will enable the connected terminal to + // send us full INPUT_RECORDs as input. If the terminal doesn't understand + // this sequence, it'll just ignore it. + + writer.WriteUTF8( + "\033[?1004h" // Focus Event Mode + "\033[?9001h" // Win32 Input Mode + ); + + // MSFT: 15813316 + // If the terminal application wants us to inherit the cursor position, + // we're going to emit a VT sequence to ask for the cursor position, then + // wait 1s until we get a response. + // If we get a response, the InteractDispatch will call SetCursorPosition, + // which will call to our VtIo::SetCursorPosition method. + if (_lookingForCursorPosition) + { + writer.WriteUTF8("\x1b[6n"); // Cursor Position Report (DSR CPR) + } + + writer.Submit(); } - // MSFT: 15813316 - // If the terminal application wants us to inherit the cursor position, - // we're going to emit a VT sequence to ask for the cursor position, then - // wait 3s until we get a response. - // If we get a response, the InteractDispatch will call SetCursorPosition, - // which will call to our VtIo::SetCursorPosition method. - if (_lookingForCursorPosition && _pVtRenderEngine && _pVtInputThread) + if (_lookingForCursorPosition) { _lookingForCursorPosition = false; - LOG_IF_FAILED(_pVtRenderEngine->RequestCursor()); // Allow the input thread to momentarily gain the console lock. - const auto suspension = g.getConsoleInformation().SuspendLock(); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + const auto suspension = gci.SuspendLock(); _pVtInputThread->WaitUntilDSR(3000); } - // GH#4999 - Send a sequence to the connected terminal to request - // win32-input-mode from them. This will enable the connected terminal to - // send us full INPUT_RECORDs as input. If the terminal doesn't understand - // this sequence, it'll just ignore it. - LOG_IF_FAILED(_pVtRenderEngine->RequestWin32Input()); - if (_pPtySignalInputThread) { // Let the signal thread know that the console is connected. @@ -247,25 +234,6 @@ void VtIo::CreatePseudoWindow() } } -void VtIo::SetWindowVisibility(bool showOrHide) noexcept -{ - auto& gci = ::Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation(); - - gci.LockConsole(); - auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); - - // ConsoleInputThreadProcWin32 calls VtIo::CreatePseudoWindow, - // which calls CreateWindowExW, which causes a WM_SIZE message. - // In short, this function might be called before _pVtRenderEngine exists. - // See PtySignalInputThread::CreatePseudoWindow(). - if (!_pVtRenderEngine) - { - return; - } - - LOG_IF_FAILED(_pVtRenderEngine->SetWindowVisibility(showOrHide)); -} - // Method Description: // - Create and start the signal thread. The signal thread can be created // independent of the i/o threads, and doesn't require a client first @@ -302,69 +270,6 @@ void VtIo::SetWindowVisibility(bool showOrHide) noexcept return S_OK; } -// Method Description: -// - Prevent the renderer from emitting output on the next resize. This prevents -// the host from echoing a resize to the terminal that requested it. -// Arguments: -// - -// Return Value: -// - S_OK if the renderer successfully suppressed the next repaint, otherwise an -// appropriate HRESULT indicating failure. -[[nodiscard]] HRESULT VtIo::SuppressResizeRepaint() -{ - auto hr = S_OK; - if (_pVtRenderEngine) - { - hr = _pVtRenderEngine->SuppressResizeRepaint(); - } - return hr; -} - -// Method Description: -// - Attempts to set the initial cursor position, if we're looking for it. -// If we're not trying to inherit the cursor, does nothing. -// Arguments: -// - coordCursor: The initial position of the cursor. -// Return Value: -// - S_OK if we successfully inherited the cursor or did nothing, else an -// appropriate HRESULT -[[nodiscard]] HRESULT VtIo::SetCursorPosition(const til::point coordCursor) -{ - auto hr = S_OK; - if (_lookingForCursorPosition) - { - if (_pVtRenderEngine) - { - hr = _pVtRenderEngine->InheritCursor(coordCursor); - } - - _lookingForCursorPosition = false; - } - return hr; -} - -[[nodiscard]] HRESULT VtIo::SwitchScreenBuffer(const bool useAltBuffer) -{ - auto hr = S_OK; - if (_pVtRenderEngine) - { - hr = _pVtRenderEngine->SwitchScreenBuffer(useAltBuffer); - } - return hr; -} - -void VtIo::CloseInput() -{ - _pVtInputThread = nullptr; - SendCloseEvent(); -} - -void VtIo::CloseOutput() -{ - auto& g = ServiceLocator::LocateGlobals(); - g.getConsoleInformation().GetActiveOutputBuffer().SetTerminalConnection(nullptr); -} - void VtIo::SendCloseEvent() { LockConsole(); @@ -379,73 +284,16 @@ void VtIo::SendCloseEvent() } } -// The name of this method is an analogy to TCP_CORK. It instructs -// the VT renderer to stop flushing its buffer to the output pipe. -// Don't forget to uncork it! -void VtIo::CorkRenderer(bool corked) const noexcept -{ - _pVtRenderEngine->Cork(corked); -} - -#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: -// - vtRenderEngine: a VT renderer that our VtIo should use as the vt engine during these tests -// Return Value: -// - -void VtIo::EnableConptyModeForTests(std::unique_ptr vtRenderEngine, const bool resizeQuirk) -{ - _initialized = true; - _resizeQuirk = resizeQuirk; - _pVtRenderEngine = std::move(vtRenderEngine); -} -#endif - -// Method Description: -// - Returns true if the Resize Quirk is enabled. This changes the behavior of -// conpty to _not_ InvalidateAll the entire viewport on a resize operation. -// This is used by the Windows Terminal, because it is prepared to be -// connected to a conpty, and handles its own buffer specifically for a -// conpty scenario. -// - See also: GH#3490, #4354, #4741 -// Arguments: -// - -// Return Value: -// - true iff we were started with the `--resizeQuirk` flag enabled. -bool VtIo::IsResizeQuirkEnabled() const +// Returns true for C0 characters and C1 [single-character] CSI. +// A copy of isActionableFromGround() from stateMachine.cpp. +static constexpr bool IsControlCharacter(wchar_t wch) noexcept { - return _resizeQuirk; -} - -// Method Description: -// - Manually tell the renderer that it should emit a "Erase Scrollback" -// sequence to the connected terminal. We need to do this in certain cases -// that we've identified where we believe the client wanted the entire -// terminal buffer cleared, not just the viewport. For more information, see -// GH#3126. -// Arguments: -// - -// Return Value: -// - S_OK if we wrote the sequences successfully, otherwise an appropriate HRESULT -[[nodiscard]] HRESULT VtIo::ManuallyClearScrollback() const noexcept -{ - if (_pVtRenderEngine) - { - return _pVtRenderEngine->ManuallyClearScrollback(); - } - return S_OK; -} - -[[nodiscard]] HRESULT VtIo::RequestMouseMode(bool enable) const noexcept -{ - if (_pVtRenderEngine) - { - return _pVtRenderEngine->RequestMouseMode(enable); - } - return S_OK; + // This is equivalent to: + // return (wch <= 0x1f) || (wch >= 0x7f && wch <= 0x9f); + // It's written like this to get MSVC to emit optimal assembly for findActionableFromGround. + // It lacks the ability to turn boolean operators into binary operations and also happens + // to fail to optimize the printable-ASCII range check into a subtraction & comparison. + return (wch <= 0x1f) | (static_cast(wch - 0x7f) <= 0x20); } // Formats the given console attributes to their closest VT equivalent. @@ -510,3 +358,411 @@ void VtIo::FormatAttributes(std::wstring& target, const TextAttribute& attribute target.append(bufW, len); } + +VtIo::Writer VtIo::GetWriter() noexcept +{ + _corked += 1; + return Writer{ this }; +} + +VtIo::Writer::Writer(VtIo* io) noexcept : + _io{ io } +{ +} + +VtIo::Writer::~Writer() noexcept +{ + // If _io is non-null, then we didn't call Submit, e.g. because of an exception. + // We need to avoid flushing the buffer in that case. + if (_io) + { + _io->_writerTainted = true; + _io->_uncork(); + } +} + +VtIo::Writer::Writer(Writer&& other) noexcept : + _io{ std::exchange(other._io, nullptr) } +{ +} + +VtIo::Writer& VtIo::Writer::operator=(Writer&& other) noexcept +{ + if (this != &other) + { + this->~Writer(); + _io = std::exchange(other._io, nullptr); + } + return *this; +} + +VtIo::Writer::operator bool() const noexcept +{ + return _io != nullptr; +} + +void VtIo::Writer::Submit() +{ + const auto io = std::exchange(_io, nullptr); + io->_uncork(); +} + +void VtIo::_uncork() +{ + _corked -= 1; + if (_corked <= 0) + { + _flushNow(); + } +} + +void VtIo::_flushNow() +{ + size_t minSize = 0; + + if (_writerRestoreCursor) + { + minSize = 4; + _writerRestoreCursor = false; + _back.append("\x1b\x38"); // DECRC: DEC Restore Cursor (+ attributes) + } + + if (_overlappedPending) + { + _overlappedPending = false; + + DWORD written; + if (FAILED(Utils::GetOverlappedResultSameThread(_overlapped, &written))) + { + // Not much we can do here. Let's treat this like a ERROR_BROKEN_PIPE. + _hOutput.reset(); + SendCloseEvent(); + } + } + + _front.clear(); + _front.swap(_back); + + // If it's >128KiB large and twice as large as the previous buffer, free the memory. + // This ensures that there's a pathway for shrinking the buffer from large sizes. + if (const auto cap = _back.capacity(); cap > 128 * 1024 && cap / 2 > _front.size()) + { + _back = std::string{}; + } + + // We encountered an exception and shouldn't flush the broken pieces. + if (_writerTainted) + { + _writerTainted = false; + return; + } + + // If _back (now _front) was empty, we can return early. If all _front contains is + // DECSC/DECRC that was added by BackupCursor & us, we can also return early. + if (_front.size() <= minSize) + { + return; + } + + // No point in calling WriteFile if we already encountered ERROR_BROKEN_PIPE. + // We do this after the above, so that _back doesn't grow indefinitely. + if (!_hOutput) + { + return; + } + + const auto write = gsl::narrow_cast(_front.size()); + + TraceLoggingWrite( + g_hConhostV2EventTraceProvider, + "ConPTY WriteFile", + TraceLoggingCountedUtf8String(_front.data(), write, "buffer"), + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), + TraceLoggingKeyword(TIL_KEYWORD_TRACE)); + + for (;;) + { + if (WriteFile(_hOutput.get(), _front.data(), write, nullptr, _overlapped)) + { + return; + } + + switch (const auto gle = GetLastError()) + { + case ERROR_BROKEN_PIPE: + _hOutput.reset(); + SendCloseEvent(); + return; + case ERROR_IO_PENDING: + _overlappedPending = true; + return; + default: + LOG_WIN32(gle); + return; + } + } +} + +void VtIo::Writer::BackupCursor() const +{ + if (!_io->_writerRestoreCursor) + { + _io->_writerRestoreCursor = true; + _io->_back.append("\x1b\x37"); // DECSC: DEC Save Cursor (+ attributes) + } +} + +void VtIo::Writer::WriteUTF8(std::string_view str) const +{ + _io->_back.append(str); +} + +void VtIo::Writer::WriteUTF16(std::wstring_view str) const +{ + if (str.empty()) + { + return; + } + + const auto existingUTF8Len = _io->_back.size(); + const auto incomingUTF16Len = str.size(); + + // When converting from UTF-16 to UTF-8 the worst case is 3 bytes per UTF-16 code unit. + const auto incomingUTF8Cap = incomingUTF16Len * 3; + const auto totalUTF8Cap = existingUTF8Len + incomingUTF8Cap; + + // Since WideCharToMultiByte() only supports `int` lengths, we check for an overflow past INT_MAX/3. + // We also check for an overflow of totalUTF8Cap just to be sure. + if (incomingUTF16Len > gsl::narrow_cast(INT_MAX / 3) || totalUTF8Cap <= existingUTF8Len) + { + THROW_HR_MSG(E_INVALIDARG, "string too large"); + } + + // NOTE: Throwing inside resize_and_overwrite invokes undefined behavior. + _io->_back._Resize_and_overwrite(totalUTF8Cap, [&](char* buf, const size_t) noexcept { + const auto len = WideCharToMultiByte(CP_UTF8, 0, str.data(), gsl::narrow_cast(incomingUTF16Len), buf + existingUTF8Len, gsl::narrow_cast(incomingUTF8Cap), nullptr, nullptr); + return existingUTF8Len + std::max(0, len); + }); +} + +// When DISABLE_NEWLINE_AUTO_RETURN is not set (Bad! Don't do it!) we'll do newline translation for you. +// That's the only difference of this function from WriteUTF16: It does LF -> CRLF translation. +void VtIo::Writer::WriteUTF16TranslateCRLF(std::wstring_view str) const +{ + const auto beg = str.begin(); + const auto end = str.end(); + auto begCopy = beg; + auto endCopy = beg; + + // Our goal is to prepend a \r in front of \n that don't already have one. + // There's no point in replacing \n\n\n with \r\n\r\n\r\n, however. It's just fine to do \r\n\n\n. + // After all we aren't a text file, we're a terminal, and \r\n and \n are identical if we're at the first column. + for (;;) + { + // To do so, we'll first find the next LF and emit the unrelated text before it. + endCopy = std::find(endCopy, end, L'\n'); + WriteUTF16({ begCopy, endCopy }); + begCopy = endCopy; + + // Done? Great. + if (begCopy == end) + { + break; + } + + // We only need to prepend a CR if the LF isn't already preceded by one. + if (begCopy == beg || begCopy[-1] != L'\r') + { + _io->_back.push_back('\r'); + } + + // Now extend the end of the next WriteUTF16 *past* this series of CRs and LFs. + // We've just ensured that the LF is preceded by a CR, so we can skip all this safely. + while (++endCopy != end && (*endCopy == L'\n' || *endCopy == L'\r')) + { + } + } +} + +// Same as WriteUTF16, but replaces control characters with spaces. +// We don't outright remove them because that would mess up the cursor position. +// conhost traditionally assigned control chars a width of 1 when in the raw write mode. +void VtIo::Writer::WriteUTF16StripControlChars(std::wstring_view str) const +{ + auto it = str.data(); + const auto end = it + str.size(); + + // We can picture `str` as a repeated sequence of regular characters followed by control characters. + while (it != end) + { + const auto begControlChars = FindActionableControlCharacter(it, end - it); + + WriteUTF16({ it, begControlChars }); + + for (it = begControlChars; it != end && IsControlCharacter(*it); ++it) + { + WriteUCS2StripControlChars(*it); + } + } +} + +void VtIo::Writer::WriteUCS2(wchar_t ch) const +{ + char buf[4]; + size_t len = 0; + + if (til::is_surrogate(ch)) + { + ch = UNICODE_REPLACEMENT; + } + + if (ch <= 0x7f) + { + buf[len++] = static_cast(ch); + } + else if (ch <= 0x7ff) + { + buf[len++] = static_cast(0xc0 | (ch >> 6)); + buf[len++] = static_cast(0x80 | (ch & 0x3f)); + } + else + { + buf[len++] = static_cast(0xe0 | (ch >> 12)); + buf[len++] = static_cast(0x80 | ((ch >> 6) & 0x3f)); + buf[len++] = static_cast(0x80 | (ch & 0x3f)); + } + + _io->_back.append(buf, len); +} + +void VtIo::Writer::WriteUCS2StripControlChars(wchar_t ch) const +{ + if (ch < 0x20) + { + static constexpr wchar_t lut[] = { + // clang-format off + L' ', L'☺', L'☻', L'♥', L'♦', L'♣', L'♠', L'•', L'◘', L'○', L'◙', L'♂', L'♀', L'♪', L'♫', L'☼', + L'►', L'◄', L'↕', L'‼', L'¶', L'§', L'▬', L'↨', L'↑', L'↓', L'→', L'←', L'∟', L'↔', L'▲', L'▼', + // clang-format on + }; + ch = lut[ch]; + } + else if (ch == 0x7F) + { + ch = L'⌂'; + } + else if (ch > 0x7F && ch < 0xA0) + { + ch = L'?'; + } + + WriteUCS2(ch); +} + +// CUP: Cursor Position +void VtIo::Writer::WriteCUP(til::point position) const +{ + fmt::format_to(std::back_inserter(_io->_back), FMT_COMPILE("\x1b[{};{}H"), position.y + 1, position.x + 1); +} + +// DECTCEM: Text Cursor Enable +void VtIo::Writer::WriteDECTCEM(bool enabled) const +{ + char buf[] = "\x1b[?25h"; + buf[std::size(buf) - 2] = enabled ? 'h' : 'l'; + _io->_back.append(&buf[0], std::size(buf) - 1); +} + +// SGR 1006: SGR Extended Mouse Mode +void VtIo::Writer::WriteSGR1006(bool enabled) const +{ + char buf[] = "\x1b[?1003;1006h"; + buf[std::size(buf) - 2] = enabled ? 'h' : 'l'; + _io->_back.append(&buf[0], std::size(buf) - 1); +} + +// DECAWM: Autowrap Mode +void VtIo::Writer::WriteDECAWM(bool enabled) const +{ + char buf[] = "\x1b[?7h"; + buf[std::size(buf) - 2] = enabled ? 'h' : 'l'; + _io->_back.append(&buf[0], std::size(buf) - 1); +} + +// ASB: Alternate Screen Buffer +void VtIo::Writer::WriteASB(bool enabled) const +{ + char buf[] = "\x1b[?1049h"; + buf[std::size(buf) - 2] = enabled ? 'h' : 'l'; + _io->_back.append(&buf[0], std::size(buf) - 1); +} + +void VtIo::Writer::WriteAttributes(const TextAttribute& attributes) const +{ + FormatAttributes(_io->_back, attributes); +} + +void VtIo::Writer::WriteInfos(til::point target, std::span infos) const +{ + const auto beg = infos.begin(); + const auto end = infos.end(); + const auto last = end - 1; + WORD attributes = 0xffff; + + WriteCUP(target); + + for (auto it = beg; it != end; ++it) + { + const auto& ci = *it; + auto ch = ci.Char.UnicodeChar; + auto wide = WI_IsAnyFlagSet(ci.Attributes, COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE); + + if (wide) + { + if (WI_IsAnyFlagSet(ci.Attributes, COMMON_LVB_LEADING_BYTE)) + { + if (it == last) + { + // The leading half of a wide glyph won't fit into the last remaining column. + // --> Replace it with a space. + ch = L' '; + wide = false; + } + } + else + { + if (it == beg) + { + // The trailing half of a wide glyph won't fit into the first column. It's incomplete. + // --> Replace it with a space. + ch = L' '; + wide = false; + } + else + { + // Trailing halves of glyphs are ignored within the run. We only emit the leading half. + continue; + } + } + } + + if (attributes != ci.Attributes) + { + attributes = ci.Attributes; + WriteAttributes(TextAttribute{ attributes }); + } + + int repeat = 1; + if (wide && (til::is_surrogate(ch) || IsControlCharacter(ch))) + { + // Control characters, U+FFFD, etc. are narrow characters, so if the caller + // asked for a wide glyph we need to repeat the replacement character twice. + repeat++; + } + + do + { + WriteUCS2StripControlChars(ch); + } while (--repeat); + } +} diff --git a/src/host/VtIo.hpp b/src/host/VtIo.hpp index e56566d2e3b..a2b23de33aa 100644 --- a/src/host/VtIo.hpp +++ b/src/host/VtIo.hpp @@ -3,77 +3,101 @@ #pragma once -#include "../renderer/vt/vtrenderer.hpp" #include "VtInputThread.hpp" #include "PtySignalInputThread.hpp" class ConsoleArguments; -namespace Microsoft::Console::Render -{ - class VtEngine; -} - namespace Microsoft::Console::VirtualTerminal { class VtIo { public: + struct Writer + { + Writer() = default; + Writer(VtIo* io) noexcept; + + ~Writer() noexcept; + + Writer(const Writer&) = delete; + Writer& operator=(const Writer&) = delete; + Writer(Writer&& other) noexcept; + Writer& operator=(Writer&& other) noexcept; + + explicit operator bool() const noexcept; + + void Submit(); + + void BackupCursor() const; + void WriteUTF8(std::string_view str) const; + void WriteUTF16(std::wstring_view str) const; + void WriteUTF16TranslateCRLF(std::wstring_view str) const; + void WriteUTF16StripControlChars(std::wstring_view str) const; + void WriteUCS2(wchar_t ch) const; + void WriteUCS2StripControlChars(wchar_t ch) const; + void WriteCUP(til::point position) const; + void WriteDECTCEM(bool enabled) const; + void WriteSGR1006(bool enabled) const; + void WriteDECAWM(bool enabled) const; + void WriteASB(bool enabled) const; + void WriteAttributes(const TextAttribute& attributes) const; + void WriteInfos(til::point target, std::span infos) const; + + private: + VtIo* _io = nullptr; + }; + + friend struct Writer; + static void FormatAttributes(std::string& target, const TextAttribute& attributes); static void FormatAttributes(std::wstring& target, const TextAttribute& attributes); - VtIo(); - [[nodiscard]] HRESULT Initialize(const ConsoleArguments* const pArgs); - [[nodiscard]] HRESULT CreateAndStartSignalThread() noexcept; [[nodiscard]] HRESULT CreateIoHandlers() noexcept; bool IsUsingVt() const; - [[nodiscard]] HRESULT StartIfNeeded(); [[nodiscard]] HRESULT SuppressResizeRepaint(); [[nodiscard]] HRESULT SetCursorPosition(const til::point coordCursor); [[nodiscard]] HRESULT SwitchScreenBuffer(const bool useAltBuffer); void SendCloseEvent(); - - void CloseInput(); - void CloseOutput(); - - void CorkRenderer(bool corked) const noexcept; - -#ifdef UNIT_TESTING - void EnableConptyModeForTests(std::unique_ptr vtRenderEngine, const bool resizeQuirk = false); -#endif - - bool IsResizeQuirkEnabled() const; - - [[nodiscard]] HRESULT ManuallyClearScrollback() const noexcept; - [[nodiscard]] HRESULT RequestMouseMode(bool enable) const noexcept; - void CreatePseudoWindow(); - void SetWindowVisibility(bool showOrHide) noexcept; + Writer GetWriter() noexcept; private: + [[nodiscard]] HRESULT _Initialize(const HANDLE InHandle, const HANDLE OutHandle, _In_opt_ const HANDLE SignalHandle); + + void _uncork(); + void _flushNow(); + // After CreateIoHandlers is called, these will be invalid. wil::unique_hfile _hInput; wil::unique_hfile _hOutput; // After CreateAndStartSignalThread is called, this will be invalid. wil::unique_hfile _hSignal; - bool _initialized; - - bool _lookingForCursorPosition; - - bool _resizeQuirk{ false }; - bool _closeEventSent{ false }; - - std::unique_ptr _pVtRenderEngine; std::unique_ptr _pVtInputThread; std::unique_ptr _pPtySignalInputThread; - [[nodiscard]] HRESULT _Initialize(const HANDLE InHandle, const HANDLE OutHandle, _In_opt_ const HANDLE SignalHandle); + // We use two buffers: A front and a back buffer. The front buffer is the one we're currently + // sending to the terminal (it's being "presented" = it's on the "front" & "visible"). + // The back buffer is the one we're concurrently writing to. + std::string _front; + std::string _back; + OVERLAPPED* _overlapped = nullptr; + OVERLAPPED _overlappedBuf{}; + wil::unique_event _overlappedEvent; + bool _overlappedPending = false; + bool _writerRestoreCursor = false; + bool _writerTainted = false; + + bool _initialized = false; + bool _lookingForCursorPosition = false; + bool _closeEventSent = false; + int _corked = 0; #ifdef UNIT_TESTING friend class VtIoTests; diff --git a/src/host/_output.cpp b/src/host/_output.cpp index 063091c1708..94272c9090a 100644 --- a/src/host/_output.cpp +++ b/src/host/_output.cpp @@ -74,6 +74,7 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon LockConsole(); const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); auto& screenBuffer = screenInfo.GetActiveBuffer(); const auto bufferSize = screenBuffer.GetBufferSize(); FillConsoleResult result; @@ -83,6 +84,122 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon return {}; } + if (auto writer = gci.GetVtWriterForBuffer(&screenInfo)) + { + writer.BackupCursor(); + + const auto h = bufferSize.Height(); + const auto w = bufferSize.Width(); + auto y = startingCoordinate.y; + auto input = static_cast(data); + size_t inputPos = 0; + til::small_vector infoBuffer; + Viewport unused; + + infoBuffer.resize(gsl::narrow_cast(w)); + + while (y < h && inputPos < lengthToWrite) + { + const auto beg = y == startingCoordinate.y ? startingCoordinate.x : 0; + const auto columnsAvailable = w - beg; + til::CoordType columns = 0; + + const auto readViewport = Viewport::FromInclusive({ beg, y, w - 1, y }); + THROW_IF_FAILED(ReadConsoleOutputWImplHelper(screenInfo, infoBuffer, readViewport, unused)); + + switch (mode) + { + case FillConsoleMode::WriteAttribute: + for (; columns < columnsAvailable && inputPos < lengthToWrite; ++columns, ++inputPos) + { + infoBuffer[columns].Attributes = input[inputPos]; + } + break; + case FillConsoleMode::FillAttribute: + for (const auto attr = input[0]; columns < columnsAvailable && inputPos < lengthToWrite; ++columns, ++inputPos) + { + infoBuffer[columns].Attributes = attr; + } + break; + case FillConsoleMode::WriteCharacter: + for (; columns < columnsAvailable && inputPos < lengthToWrite; ++inputPos) + { + const auto ch = input[inputPos]; + if (ch >= 0x80 && IsGlyphFullWidth(ch)) + { + // If the wide glyph doesn't fit into the last column, pad it with whitespace. + if ((columns + 1) >= columnsAvailable) + { + auto& lead = infoBuffer[columns++]; + lead.Char.UnicodeChar = L' '; + lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE); + break; + } + + auto& lead = infoBuffer[columns++]; + lead.Char.UnicodeChar = ch; + lead.Attributes = lead.Attributes & ~COMMON_LVB_TRAILING_BYTE | COMMON_LVB_LEADING_BYTE; + + auto& trail = infoBuffer[columns++]; + trail.Char.UnicodeChar = ch; + trail.Attributes = trail.Attributes & ~COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE; + } + else + { + auto& lead = infoBuffer[columns++]; + lead.Char.UnicodeChar = ch; + lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE); + } + } + break; + case FillConsoleMode::FillCharacter: + // Identical to WriteCharacter above, but with the if() and for() swapped. + if (const auto ch = input[0]; ch >= 0x80 && IsGlyphFullWidth(ch)) + { + for (; columns < columnsAvailable && inputPos < lengthToWrite; ++inputPos) + { + // If the wide glyph doesn't fit into the last column, pad it with whitespace. + if ((columns + 1) >= columnsAvailable) + { + auto& lead = infoBuffer[columns++]; + lead.Char.UnicodeChar = L' '; + lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE); + break; + } + + auto& lead = infoBuffer[columns++]; + lead.Char.UnicodeChar = ch; + lead.Attributes = lead.Attributes & ~COMMON_LVB_TRAILING_BYTE | COMMON_LVB_LEADING_BYTE; + + auto& trail = infoBuffer[columns++]; + trail.Char.UnicodeChar = ch; + trail.Attributes = trail.Attributes & ~COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE; + } + } + else + { + for (; columns < columnsAvailable && inputPos < lengthToWrite; ++inputPos) + { + auto& lead = infoBuffer[columns++]; + lead.Char.UnicodeChar = ch; + lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE); + } + } + break; + } + + const auto writeViewport = Viewport::FromInclusive({ beg, y, beg + columns - 1, y }); + THROW_IF_FAILED(WriteConsoleOutputWImplHelper(screenInfo, infoBuffer, w, writeViewport, unused)); + + y += 1; + result.cellsModified += columns; + } + + result.lengthRead = inputPos; + + writer.Submit(); + } + else { // Technically we could always pass `data` as `uint16_t*`, because `wchar_t` is guaranteed to be 16 bits large. // However, OutputCellIterator is terrifyingly unsafe code and so we don't do that. @@ -261,13 +378,37 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon size_t& cellsModified, const bool enablePowershellShim) noexcept { - UNREFERENCED_PARAMETER(enablePowershellShim); - try { LockConsole(); const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); + // GH#3126 - This is a shim for powershell's `Clear-Host` function. In + // the vintage console, `Clear-Host` is supposed to clear the entire + // buffer. In conpty however, there's no difference between the viewport + // and the entirety of the buffer. We're going to see if this API call + // exactly matched the way we expect powershell to call it. If it does, + // then let's manually emit a ^[[3J to the connected terminal, so that + // their entire buffer will be cleared as well. + if (enablePowershellShim) + { + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + if (const auto writer = gci.GetVtWriterForBuffer(&OutContext)) + { + const auto currentBufferDimensions{ OutContext.GetBufferSize().Dimensions() }; + const auto wroteWholeBuffer = lengthToWrite == (currentBufferDimensions.area()); + const auto startedAtOrigin = startingCoordinate == til::point{ 0, 0 }; + const auto wroteSpaces = attribute == (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED); + + if (wroteWholeBuffer && startedAtOrigin && wroteSpaces) + { + // PowerShell has previously called FillConsoleOutputCharacterW() which triggered a call to WriteClearScreen(). + cellsModified = lengthToWrite; + return S_OK; + } + } + } + cellsModified = FillConsoleImpl(OutContext, FillConsoleMode::FillAttribute, &attribute, lengthToWrite, startingCoordinate).cellsModified; return S_OK; } @@ -299,8 +440,6 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon LockConsole(); const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); - cellsModified = FillConsoleImpl(OutContext, FillConsoleMode::FillCharacter, &character, lengthToWrite, startingCoordinate).lengthRead; - // GH#3126 - This is a shim for powershell's `Clear-Host` function. In // the vintage console, `Clear-Host` is supposed to clear the entire // buffer. In conpty however, there's no difference between the viewport @@ -311,7 +450,7 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon if (enablePowershellShim) { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (gci.IsInVtIoMode()) + if (auto writer = gci.GetVtWriterForBuffer(&OutContext)) { const auto currentBufferDimensions{ OutContext.GetBufferSize().Dimensions() }; const auto wroteWholeBuffer = lengthToWrite == (currentBufferDimensions.area()); @@ -320,14 +459,15 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon if (wroteWholeBuffer && startedAtOrigin && wroteSpaces) { - // It's important that we flush the renderer at this point so we don't - // have any pending output rendered after the scrollback is cleared. - ServiceLocator::LocateGlobals().pRender->TriggerFlush(false); - return gci.GetVtIo()->ManuallyClearScrollback(); + WriteClearScreen(OutContext); + writer.Submit(); + cellsModified = lengthToWrite; + return S_OK; } } } + cellsModified = FillConsoleImpl(OutContext, FillConsoleMode::FillCharacter, &character, lengthToWrite, startingCoordinate).lengthRead; return S_OK; } CATCH_RETURN(); diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index 5679557ea84..9c3d3378a80 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -14,6 +14,7 @@ #include "dbcs.h" #include "handle.h" #include "misc.h" +#include "VtIo.hpp" #include "../types/inc/convert.hpp" #include "../types/inc/GlyphWidth.hpp" @@ -21,12 +22,14 @@ #include "../interactivity/inc/ServiceLocator.hpp" -#pragma hdrstop using namespace Microsoft::Console::Types; +using namespace Microsoft::Console::VirtualTerminal; using Microsoft::Console::Interactivity::ServiceLocator; -using Microsoft::Console::VirtualTerminal::StateMachine; -// Used by WriteCharsLegacy. -#define IS_GLYPH_CHAR(wch) (((wch) >= L' ') && ((wch) != 0x007F)) + +constexpr bool controlCharPredicate(wchar_t wch) +{ + return wch < L' ' || wch == 0x007F; +} // Routine Description: // - This routine updates the cursor position. Its input is the non-special @@ -109,11 +112,12 @@ static void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point } // As the name implies, this writes text without processing its control characters. -void _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const std::wstring_view& text, til::CoordType* psScrollY) +static bool _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const std::wstring_view& text, til::CoordType* psScrollY) { const auto wrapAtEOL = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT); const auto hasAccessibilityEventing = screenInfo.HasAccessibilityEventing(); auto& textBuffer = screenInfo.GetTextBuffer(); + bool wrapped = false; RowWriteState state{ .text = text, @@ -127,8 +131,9 @@ void _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const std::wst state.columnBegin = cursorPosition.x; textBuffer.Replace(cursorPosition.y, textBuffer.GetCurrentAttributes(), state); cursorPosition.x = state.columnEnd; + wrapped = wrapAtEOL && state.columnEnd >= state.columnLimit; - if (wrapAtEOL && state.columnEnd >= state.columnLimit) + if (wrapped) { textBuffer.SetWrapForced(cursorPosition.y, true); } @@ -140,6 +145,8 @@ void _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const std::wst AdjustCursorPosition(screenInfo, cursorPosition, psScrollY); } + + return wrapped; } // This routine writes a string to the screen while handling control characters. @@ -153,15 +160,16 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t const auto width = textBuffer.GetSize().Width(); auto& cursor = textBuffer.GetCursor(); const auto wrapAtEOL = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT); - auto it = text.begin(); + const auto beg = text.begin(); const auto end = text.end(); + auto it = beg; + + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto writer = gci.GetVtWriterForBuffer(&screenInfo); - // In VT mode, when you have a 120-column terminal you can write 120 columns without the cursor wrapping. - // Whenever the cursor is in that 120th column IsDelayedEOLWrap() will return true. I'm not sure why the VT parts - // of the code base store this as a boolean. It's also unclear why we handle this here. The intention is likely - // so that when we exit VT mode and receive a write a potentially stored delayed wrap would still be handled. - // The way this code does it however isn't correct since it handles it like the old console APIs would and - // so writing a newline while being delay wrapped will print 2 newlines. + // If we enter this if condition, then someone wrote text in VT mode and now switched to non-VT mode. + // Since the Console APIs don't support delayed EOL wrapping, we need to first put the cursor back + // to a position that the Console APIs expect (= not delayed). if (cursor.IsDelayedEOLWrap() && wrapAtEOL) { auto pos = cursor.GetPosition(); @@ -172,6 +180,11 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t pos.x = 0; pos.y++; AdjustCursorPosition(screenInfo, pos, psScrollY); + + if (writer) + { + writer.WriteUTF8("\r\n"); + } } } @@ -179,79 +192,141 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t // If it's not set, we can just straight up give everything to WriteCharsLegacyUnprocessed. if (WI_IsFlagClear(screenInfo.OutputMode, ENABLE_PROCESSED_OUTPUT)) { - _writeCharsLegacyUnprocessed(screenInfo, { it, end }, psScrollY); - it = end; + const auto lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, text, psScrollY); + + if (writer) + { + // We're asked to produce VT output, but also to behave as if these control characters aren't control characters. + // So, to make it work, we simply replace all the control characters with whitespace. + writer.WriteUTF16StripControlChars(text); + if (lastCharWrapped) + { + writer.WriteUTF8("\r\n"); + } + writer.Submit(); + } + + return; } while (it != end) { - const auto nextControlChar = std::find_if(it, end, [](const auto& wch) { return !IS_GLYPH_CHAR(wch); }); + const auto nextControlChar = std::find_if(it, end, controlCharPredicate); if (nextControlChar != it) { - _writeCharsLegacyUnprocessed(screenInfo, { it, nextControlChar }, psScrollY); + const std::wstring_view chunk{ it, nextControlChar }; + const auto lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, chunk, psScrollY); it = nextControlChar; + + if (writer) + { + writer.WriteUTF16(chunk); + if (lastCharWrapped) + { + writer.WriteUTF8("\r\n"); + } + } } - for (; it != end && !IS_GLYPH_CHAR(*it); ++it) + if (it == end) { - switch (*it) + break; + } + + do + { + auto wch = *it; + auto lastCharWrapped = false; + + switch (wch) { case UNICODE_NULL: - _writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], 1 }, psScrollY); - continue; + { + lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], 1 }, psScrollY); + wch = L' '; + break; + } case UNICODE_BELL: + { std::ignore = screenInfo.SendNotifyBeep(); - continue; + break; + } case UNICODE_BACKSPACE: { auto pos = cursor.GetPosition(); pos.x = textBuffer.GetRowByOffset(pos.y).NavigateToPrevious(pos.x); AdjustCursorPosition(screenInfo, pos, psScrollY); - continue; + break; } case UNICODE_TAB: { const auto pos = cursor.GetPosition(); const auto remaining = width - pos.x; const auto tabCount = gsl::narrow_cast(std::min(remaining, 8 - (pos.x & 7))); - _writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], tabCount }, psScrollY); - continue; + lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, { &tabSpaces[0], tabCount }, psScrollY); + break; } case UNICODE_LINEFEED: { auto pos = cursor.GetPosition(); - if (WI_IsFlagClear(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN)) + if (WI_IsFlagClear(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN) && pos.x != 0) { pos.x = 0; + // This causes the current \n to be replaced with a \r\n in the ConPTY VT output. + wch = 0; + lastCharWrapped = true; } textBuffer.GetMutableRowByOffset(pos.y).SetWrapForced(false); pos.y = pos.y + 1; AdjustCursorPosition(screenInfo, pos, psScrollY); - continue; + break; } case UNICODE_CARRIAGERETURN: { auto pos = cursor.GetPosition(); pos.x = 0; AdjustCursorPosition(screenInfo, pos, psScrollY); - continue; + break; } default: + { + // As a special favor to incompetent apps that attempt to display control chars, + // convert to corresponding OEM Glyph Chars + const auto cp = ServiceLocator::LocateGlobals().getConsoleInformation().OutputCP; + const auto ch = gsl::narrow_cast(wch); + const auto result = MultiByteToWideChar(cp, MB_USEGLYPHCHARS, &ch, 1, &wch, 1); + if (result != 1) + { + wch = 0; + } + if (wch) + { + lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, { &wch, 1 }, psScrollY); + } break; } + } - // As a special favor to incompetent apps that attempt to display control chars, - // convert to corresponding OEM Glyph Chars - const auto cp = ServiceLocator::LocateGlobals().getConsoleInformation().OutputCP; - const auto ch = gsl::narrow_cast(*it); - wchar_t wch = 0; - const auto result = MultiByteToWideChar(cp, MB_USEGLYPHCHARS, &ch, 1, &wch, 1); - if (result == 1) + if (writer) { - _writeCharsLegacyUnprocessed(screenInfo, { &wch, 1 }, psScrollY); + if (wch) + { + writer.WriteUCS2(wch); + } + if (lastCharWrapped) + { + writer.WriteUTF8("\r\n"); + } } - } + + ++it; + } while (it != end && controlCharPredicate(*it)); + } + + if (writer) + { + writer.Submit(); } } @@ -259,7 +334,58 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t // This wrapper around StateMachine exists so that we can add the necessary ConPTY transformations. void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str) { - screenInfo.GetStateMachine().ProcessString(str); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto& stateMachine = screenInfo.GetStateMachine(); + // When switch between the main and alt-buffer SCREEN_INFORMATION::GetActiveBuffer() + // may change, so get the VtIo reference now, just in case. + auto writer = gci.GetVtWriterForBuffer(&screenInfo); + + stateMachine.ProcessString(str); + + if (writer) + { + const auto& injections = stateMachine.GetInjections(); + size_t offset = 0; + + const auto write = [&](size_t beg, size_t end) { + const auto chunk = til::safe_slice_abs(str, beg, end); + if (WI_IsFlagSet(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN)) + { + writer.WriteUTF16(chunk); + } + else + { + writer.WriteUTF16TranslateCRLF(chunk); + } + }; + + for (const auto& injection : injections) + { + write(offset, injection.offset); + offset = injection.offset; + + static constexpr std::array mapping{ { + { "\x1b[?1004h\x1b[?9001h" }, // RIS: Focus Event Mode + Win32 Input Mode + { "\033[?1004h" } // DECSET_FOCUS: Focus Event Mode + } }; + + writer.WriteUTF8(mapping[static_cast(injection.type)]); + } + + write(offset, std::wstring_view::npos); + writer.Submit(); + } +} + +// Erases all contents of the given screenInfo, including the current screen and scrollback. +void WriteClearScreen(SCREEN_INFORMATION& screenInfo) +{ + WriteCharsVT( + screenInfo, + L"\x1b[H" // CUP to home + L"\x1b[2J" // Erase in Display: clear the screen + L"\x1b[3J" // Erase in Display: clear the scrollback buffer + ); } // Routine Description: @@ -284,7 +410,7 @@ void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str) std::unique_ptr& waiter) try { - const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); if (WI_IsAnyFlagSet(gci.Flags, (CONSOLE_SUSPENDED | CONSOLE_SELECTING | CONSOLE_SCROLLBAR_TRACKING))) { waiter = std::make_unique(screenInfo, @@ -295,25 +421,16 @@ try return CONSOLE_STATUS_WAIT; } - const auto vtIo = ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo(); const auto restoreVtQuirk = wil::scope_exit([&]() { if (requiresVtQuirk) { screenInfo.ResetIgnoreLegacyEquivalentVTAttributes(); } - if (vtIo->IsUsingVt()) - { - vtIo->CorkRenderer(false); - } }); if (requiresVtQuirk) { screenInfo.SetIgnoreLegacyEquivalentVTAttributes(); } - if (vtIo->IsUsingVt()) - { - vtIo->CorkRenderer(true); - } const std::wstring_view str{ pwchBuffer, *pcbBuffer / sizeof(WCHAR) }; @@ -323,7 +440,7 @@ try } else { - screenInfo.GetStateMachine().ProcessString(str); + WriteCharsVT(screenInfo, str); } return STATUS_SUCCESS; diff --git a/src/host/_stream.h b/src/host/_stream.h index 3897abf22fb..351b9173018 100644 --- a/src/host/_stream.h +++ b/src/host/_stream.h @@ -21,6 +21,7 @@ Revision History: void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str, til::CoordType* psScrollY); void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str); +void WriteClearScreen(SCREEN_INFORMATION& screenInfo); // NOTE: console lock must be held when calling this routine // String has been translated to unicode at this point. diff --git a/src/host/consoleInformation.cpp b/src/host/consoleInformation.cpp index 12e8c5e5b55..3051e00a153 100644 --- a/src/host/consoleInformation.cpp +++ b/src/host/consoleInformation.cpp @@ -118,12 +118,22 @@ ULONG CONSOLE_INFORMATION::GetCSRecursionCount() const noexcept return Status; } -VtIo* CONSOLE_INFORMATION::GetVtIo() +VtIo* CONSOLE_INFORMATION::GetVtIo() noexcept { return &_vtIo; } -bool CONSOLE_INFORMATION::IsInVtIoMode() const +VtIo::Writer CONSOLE_INFORMATION::GetVtWriter() noexcept +{ + return _vtIo.IsUsingVt() ? _vtIo.GetWriter() : VtIo::Writer{}; +} + +VtIo::Writer CONSOLE_INFORMATION::GetVtWriterForBuffer(const SCREEN_INFORMATION* context) noexcept +{ + return _vtIo.IsUsingVt() && (pCurrentScreenBuffer == context || pCurrentScreenBuffer == context->GetAltBuffer()) ? _vtIo.GetWriter() : VtIo::Writer{}; +} + +bool CONSOLE_INFORMATION::IsInVtIoMode() const noexcept { return _vtIo.IsUsingVt(); } @@ -215,20 +225,6 @@ InputBuffer* const CONSOLE_INFORMATION::GetActiveInputBuffer() const void CONSOLE_INFORMATION::SetTitle(const std::wstring_view newTitle) { _Title = std::wstring{ newTitle.begin(), newTitle.end() }; - - // Sanitize the input if we're in pty mode. No control chars - this string - // will get emitted back to the TTY in a VT sequence, and we don't want - // to embed control characters in that string. Note that we can't use - // IsInVtIoMode for this test, because the VT I/O thread won't have - // been created when the title is first set during startup. - if (ServiceLocator::LocateGlobals().launchArgs.InConptyMode()) - { - _Title.erase(std::remove_if(_Title.begin(), _Title.end(), [](auto ch) { - return ch < UNICODE_SPACE || (ch > UNICODE_DEL && ch < UNICODE_NBSP); - }), - _Title.end()); - } - _TitleAndPrefix = _Prefix + _Title; auto* const pRender = ServiceLocator::LocateGlobals().pRender; diff --git a/src/host/directio.cpp b/src/host/directio.cpp index e9338efd096..442236cf424 100644 --- a/src/host/directio.cpp +++ b/src/host/directio.cpp @@ -598,6 +598,9 @@ CATCH_RETURN(); return E_INVALIDARG; } + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto writer = gci.GetVtWriterForBuffer(&context); + for (til::CoordType y = clippedRectangle.Top(); y <= clippedRectangle.BottomInclusive(); y++) { const auto charInfos = buffer.subspan(totalOffset, width); @@ -606,6 +609,11 @@ CATCH_RETURN(); // Make the iterator and write to the target position. storageBuffer.Write(OutputCellIterator(charInfos), target); + if (writer) + { + writer.WriteInfos(target, charInfos); + } + totalOffset += bufferStride; } @@ -615,6 +623,11 @@ CATCH_RETURN(); // Since we've managed to write part of the request, return the clamped part that we actually used. writtenRectangle = clippedRectangle; + if (writer) + { + writer.Submit(); + } + return S_OK; } CATCH_RETURN(); @@ -631,12 +644,23 @@ CATCH_RETURN(); try { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto writer = gci.GetVtWriterForBuffer(&context); + + if (writer) + { + writer.BackupCursor(); + } const auto codepage = gci.OutputCP; LOG_IF_FAILED(_ConvertCellsToWInplace(codepage, buffer, requestRectangle)); RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, buffer, requestRectangle.Width(), requestRectangle, writtenRectangle)); + if (writer) + { + writer.Submit(); + } + return S_OK; } CATCH_RETURN(); @@ -652,6 +676,14 @@ CATCH_RETURN(); try { + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto writer = gci.GetVtWriterForBuffer(&context); + + if (writer) + { + writer.BackupCursor(); + } + if (!context.GetActiveBuffer().GetCurrentFont().IsTrueTypeFont()) { // For compatibility reasons, we must maintain the behavior that munges the data if we are writing while a raster font is enabled. @@ -664,6 +696,11 @@ CATCH_RETURN(); RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, buffer, requestRectangle.Width(), requestRectangle, writtenRectangle)); } + if (writer) + { + writer.Submit(); + } + return S_OK; } CATCH_RETURN(); diff --git a/src/host/exe/Host.EXE.vcxproj b/src/host/exe/Host.EXE.vcxproj index 6de848979aa..0bc466b5488 100644 --- a/src/host/exe/Host.EXE.vcxproj +++ b/src/host/exe/Host.EXE.vcxproj @@ -50,9 +50,6 @@ {1c959542-bac2-4e55-9a6d-13251914cbb9} - - {990f2657-8580-4828-943f-5dd657d11842} - {18d09a24-8240-42d6-8cb6-236eee820262} diff --git a/src/host/ft_fuzzer/Host.FuzzWrapper.vcxproj b/src/host/ft_fuzzer/Host.FuzzWrapper.vcxproj index 6e5fb985efe..9b9cc60bb5c 100644 --- a/src/host/ft_fuzzer/Host.FuzzWrapper.vcxproj +++ b/src/host/ft_fuzzer/Host.FuzzWrapper.vcxproj @@ -45,9 +45,6 @@ {1c959542-bac2-4e55-9a6d-13251914cbb9} - - {990f2657-8580-4828-943f-5dd657d11842} - {18d09a24-8240-42d6-8cb6-236eee820262} diff --git a/src/host/getset.cpp b/src/host/getset.cpp index 3595da335cf..42ea0885517 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -2,24 +2,20 @@ // Licensed under the MIT license. #include "precomp.h" - #include "getset.h" -#include "_output.h" -#include "_stream.h" -#include "output.h" -#include "dbcs.h" +#include "ApiRoutines.h" +#include "directio.h" #include "handle.h" #include "misc.h" -#include "cmdline.h" +#include "output.h" +#include "_output.h" +#include "_stream.h" +#include "../interactivity/inc/ServiceLocator.hpp" #include "../types/inc/convert.hpp" #include "../types/inc/viewport.hpp" -#include "ApiRoutines.h" - -#include "../interactivity/inc/ServiceLocator.hpp" - #pragma hdrstop // The following mask is used to test for valid text attributes. @@ -367,19 +363,27 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept WI_ClearFlag(gci.Flags, CONSOLE_USE_PRIVATE_FLAGS); } - if (gci.IsInVtIoMode()) + if (auto writer = gci.GetVtWriter()) { + auto oldMode = context.InputMode; + auto newMode = mode; + // Mouse input should be received when mouse mode is on and quick edit mode is off // (for more information regarding the quirks of mouse mode and why/how it relates // to quick edit mode, see GH#9970) const auto newQuickEditMode{ WI_IsFlagSet(gci.Flags, CONSOLE_QUICK_EDIT_MODE) }; - const auto oldMouseMode{ !oldQuickEditMode && WI_IsFlagSet(context.InputMode, ENABLE_MOUSE_INPUT) }; - const auto newMouseMode{ !newQuickEditMode && WI_IsFlagSet(mode, ENABLE_MOUSE_INPUT) }; + WI_ClearFlagIf(oldMode, ENABLE_MOUSE_INPUT, oldQuickEditMode); + WI_ClearFlagIf(newMode, ENABLE_MOUSE_INPUT, newQuickEditMode); - if (oldMouseMode != newMouseMode) + if (const auto diff = oldMode ^ newMode) { - LOG_IF_FAILED(gci.GetVtIo()->RequestMouseMode(newMouseMode)); + if (WI_IsFlagSet(diff, ENABLE_MOUSE_INPUT)) + { + writer.WriteSGR1006(WI_IsFlagSet(newMode, ENABLE_MOUSE_INPUT)); + } } + + writer.Submit(); } context.InputMode = mode; @@ -416,7 +420,6 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept { try { - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); @@ -426,6 +429,7 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept auto& screenInfo = context.GetActiveBuffer(); const auto dwOldMode = screenInfo.OutputMode; const auto dwNewMode = mode; + const auto diff = dwOldMode ^ dwNewMode; screenInfo.OutputMode = dwNewMode; @@ -437,19 +441,26 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept screenInfo.GetStateMachine().ResetState(); } - // if we changed rendering modes then redraw the output buffer, - // but only do this if we're not in conpty mode. - if (!gci.IsInVtIoMode() && - (WI_IsFlagSet(dwNewMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING) != WI_IsFlagSet(dwOldMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING) || - WI_IsFlagSet(dwNewMode, ENABLE_LVB_GRID_WORLDWIDE) != WI_IsFlagSet(dwOldMode, ENABLE_LVB_GRID_WORLDWIDE))) + // if we changed rendering modes then redraw the output buffer. + if (WI_IsAnyFlagSet(diff, ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_LVB_GRID_WORLDWIDE)) { - auto* pRender = ServiceLocator::LocateGlobals().pRender; - if (pRender) + if (const auto pRender = ServiceLocator::LocateGlobals().pRender) { pRender->TriggerRedrawAll(); } } + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + if (auto writer = gci.GetVtWriterForBuffer(&context)) + { + if (WI_IsFlagSet(diff, ENABLE_WRAP_AT_EOL_OUTPUT)) + { + writer.WriteDECAWM(WI_IsFlagSet(dwNewMode, ENABLE_WRAP_AT_EOL_OUTPUT)); + } + + writer.Submit(); + } + return S_OK; } CATCH_RETURN(); @@ -466,6 +477,61 @@ void ApiRoutines::SetConsoleActiveScreenBufferImpl(SCREEN_INFORMATION& newContex LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + + if (&newContext.GetActiveBuffer() == &gci.GetActiveOutputBuffer()) + { + return; + } + + if (auto writer = gci.GetVtWriter()) + { + const auto viewport = gci.GetActiveOutputBuffer().GetBufferSize(); + const auto size = viewport.Dimensions(); + const auto area = static_cast(viewport.Width() * viewport.Height()); + + auto& main = newContext.GetMainBuffer(); + auto& alt = newContext.GetActiveBuffer(); + const auto hasAltBuffer = &alt != &main; + + // TODO GH#5094: This could use xterm's XTWINOPS "\e[8;;t" escape sequence here. + THROW_IF_NTSTATUS_FAILED(main.ResizeTraditional(size)); + main.SetViewportSize(&size); + if (hasAltBuffer) + { + THROW_IF_NTSTATUS_FAILED(alt.ResizeTraditional(size)); + alt.SetViewportSize(&size); + } + + Viewport read; + til::small_vector infos; + infos.resize(area, CHAR_INFO{ L' ', FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED }); + + const auto dumpScreenInfo = [&](SCREEN_INFORMATION& screenInfo) { + THROW_IF_FAILED(ReadConsoleOutputWImpl(screenInfo, infos, viewport, read)); + for (til::CoordType i = 0; i < size.height; i++) + { + writer.WriteInfos({ 0, i }, { infos.begin() + i * size.width, static_cast(size.width) }); + } + + writer.WriteCUP(screenInfo.GetTextBuffer().GetCursor().GetPosition()); + writer.WriteAttributes(screenInfo.GetAttributes()); + writer.WriteDECTCEM(screenInfo.GetTextBuffer().GetCursor().IsVisible()); + writer.WriteDECAWM(WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT)); + }; + + writer.WriteASB(false); + dumpScreenInfo(main); + + if (hasAltBuffer) + { + writer.WriteASB(true); + dumpScreenInfo(alt); + } + + writer.Submit(); + } + SetActiveScreenBuffer(newContext.GetActiveBuffer()); } CATCH_LOG(); @@ -565,6 +631,8 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont cursor.SetPosition(clampedCursorPosition); } + // TODO GH#5094: This could use xterm's XTWINOPS "\e[8;;t" escape sequence here. + return S_OK; } CATCH_RETURN(); @@ -621,7 +689,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont // Only do this if we actually changed the value of the palette though - // this API gets called all the time to change all sorts of things, but // not necessarily the palette. - if (changedOneTableEntry && !gci.IsInVtIoMode()) + if (changedOneTableEntry) { if (auto* pRender{ ServiceLocator::LocateGlobals().pRender }) { @@ -681,6 +749,8 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont cursor.SetPosition(clampedCursorPosition); } + // TODO GH#5094: This could use xterm's XTWINOPS "\e[8;;t" escape sequence here. + return S_OK; } CATCH_RETURN(); @@ -713,7 +783,11 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont // MSFT: 15813316 - Try to use this SetCursorPosition call to inherit the cursor position. auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - RETURN_IF_FAILED(gci.GetVtIo()->SetCursorPosition(position)); + if (auto writer = gci.GetVtWriterForBuffer(&context)) + { + writer.WriteCUP(position); + writer.Submit(); + } RETURN_IF_NTSTATUS_FAILED(buffer.SetCursorPosition(position, true)); @@ -789,6 +863,13 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont context.SetCursorInformation(size, isVisible); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + if (auto writer = gci.GetVtWriterForBuffer(&context)) + { + writer.WriteDECTCEM(isVisible); + writer.Submit(); + } + return S_OK; } CATCH_RETURN(); @@ -854,14 +935,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont context.PostUpdateWindowSize(); // 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() && - g.getConsoleInformation().GetVtIo()->IsResizeQuirkEnabled())) - { - WriteToScreen(context, context.GetViewport()); - } + WriteToScreen(context, context.GetViewport()); } return S_OK; } @@ -913,8 +987,8 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont const til::inclusive_rect& source, const til::point target, std::optional clip, - const wchar_t fillCharacter, - const WORD fillAttribute, + wchar_t fillCharacter, + WORD fillAttribute, const bool enableCmdShim) noexcept { try @@ -922,44 +996,73 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); - auto& buffer = context.GetActiveBuffer(); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + if (auto writer = gci.GetVtWriterForBuffer(&context)) + { + auto& buffer = context.GetActiveBuffer(); - TextAttribute useThisAttr(fillAttribute); - ScrollRegion(buffer, source, clip, target, fillCharacter, useThisAttr); + // However, if the character is null and we were given a null attribute (represented as legacy 0), + // then we'll just fill with spaces and whatever the buffer's default colors are. + if (fillCharacter == UNICODE_NULL && fillAttribute == 0) + { + fillCharacter = UNICODE_SPACE; + fillAttribute = buffer.GetAttributes().GetLegacyAttributes(); + } - auto hr = S_OK; + // GH#3126 - This is a shim for cmd's `cls` function. In the + // legacy console, `cls` is supposed to clear the entire buffer. In + // conpty however, there's no difference between the viewport and the + // entirety of the buffer. We're going to see if this API call exactly + // matched the way we expect cmd to call it. If it does, then + // let's manually emit a Full Reset (RIS). + const auto bufferSize = buffer.GetBufferSize(); + if (enableCmdShim && + source.left <= 0 && source.top <= 0 && + source.right >= bufferSize.RightInclusive() && source.bottom >= bufferSize.BottomInclusive() && + target.x == 0 && target.y <= -bufferSize.BottomExclusive() && + !clip && + fillCharacter == UNICODE_SPACE && fillAttribute == buffer.GetAttributes().GetLegacyAttributes()) + { + WriteClearScreen(context); + writer.Submit(); + return S_OK; + } - // GH#3126 - This is a shim for cmd's `cls` function. In the - // legacy console, `cls` is supposed to clear the entire buffer. In - // conpty however, there's no difference between the viewport and the - // entirety of the buffer. We're going to see if this API call exactly - // matched the way we expect cmd to call it. If it does, then - // let's manually emit a ^[[3J to the connected terminal, so that their - // entire buffer will be cleared as well. - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (enableCmdShim && gci.IsInVtIoMode()) - { - const auto currentBufferDimensions = buffer.GetBufferSize().Dimensions(); - const auto sourceIsWholeBuffer = (source.top == 0) && - (source.left == 0) && - (source.right == currentBufferDimensions.width) && - (source.bottom == currentBufferDimensions.height); - const auto targetIsNegativeBufferHeight = (target.x == 0) && - (target.y == -currentBufferDimensions.height); - const auto noClipProvided = clip == std::nullopt; - const auto fillIsBlank = (fillCharacter == UNICODE_SPACE) && - (fillAttribute == buffer.GetAttributes().GetLegacyAttributes()); - - if (sourceIsWholeBuffer && targetIsNegativeBufferHeight && noClipProvided && fillIsBlank) + const auto clipViewport = clip ? Viewport::FromInclusive(*clip) : bufferSize; + const auto sourceViewport = Viewport::FromInclusive(source); + Viewport readViewport; + Viewport writtenViewport; + + const auto w = std::max(0, sourceViewport.Width()); + const auto h = std::max(0, sourceViewport.Height()); + const auto a = static_cast(w * h); + if (a == 0) { - // It's important that we flush the renderer at this point so we don't - // have any pending output rendered after the scrollback is cleared. - ServiceLocator::LocateGlobals().pRender->TriggerFlush(false); - hr = gci.GetVtIo()->ManuallyClearScrollback(); + return S_OK; } + + til::small_vector backup; + til::small_vector fill; + + backup.resize(a, CHAR_INFO{ fillCharacter, fillAttribute }); + fill.resize(a, CHAR_INFO{ fillCharacter, fillAttribute }); + + writer.BackupCursor(); + + RETURN_IF_FAILED(ReadConsoleOutputWImplHelper(context, backup, sourceViewport, readViewport)); + RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, fill, w, sourceViewport.Clamp(clipViewport), writtenViewport)); + RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, backup, w, Viewport::FromDimensions(target, readViewport.Dimensions()).Clamp(clipViewport), writtenViewport)); + + writer.Submit(); + } + else + { + auto& buffer = context.GetActiveBuffer(); + TextAttribute useThisAttr(fillAttribute); + ScrollRegion(buffer, source, clip, target, fillCharacter, useThisAttr); } - return hr; + return S_OK; } CATCH_RETURN(); } @@ -984,6 +1087,13 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont const TextAttribute attr{ attribute }; context.SetAttributes(attr); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + if (auto writer = gci.GetVtWriterForBuffer(&context)) + { + writer.WriteAttributes(attr); + writer.Submit(); + } + return S_OK; } CATCH_RETURN(); @@ -1103,7 +1213,6 @@ void ApiRoutines::GetConsoleWindowImpl(HWND& hwnd) noexcept LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); const IConsoleWindow* pWindow = ServiceLocator::LocateConsoleWindow(); - const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); if (pWindow != nullptr) { hwnd = pWindow->GetWindowHandle(); @@ -1115,6 +1224,7 @@ void ApiRoutines::GetConsoleWindowImpl(HWND& hwnd) noexcept // doesn't actually do anything, but is a unique HWND to this // console, so that they know that this console is in fact a real // console window. + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); if (gci.IsInVtIoMode()) { hwnd = ServiceLocator::LocatePseudoWindow(); @@ -1516,6 +1626,16 @@ void ApiRoutines::GetConsoleDisplayModeImpl(ULONG& flags) noexcept LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); - ServiceLocator::LocateGlobals().getConsoleInformation().SetTitle(title); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + gci.SetTitle(title); + + if (auto writer = gci.GetVtWriter()) + { + writer.WriteUTF8("\x1b]0;"); + writer.WriteUTF16StripControlChars(title); + writer.WriteUTF8("\x7"); + writer.Submit(); + } + return S_OK; } diff --git a/src/host/globals.cpp b/src/host/globals.cpp index d1bbb4f297b..9505d490bc1 100644 --- a/src/host/globals.cpp +++ b/src/host/globals.cpp @@ -25,19 +25,3 @@ 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: -// - vtRenderEngine: a VT renderer that our VtIo should use as the vt engine during these tests -// Return Value: -// - -void Globals::EnableConptyModeForTests(std::unique_ptr vtRenderEngine, const bool resizeQuirk) -{ - launchArgs.EnableConptyModeForTests(); - getConsoleInformation().GetVtIo()->EnableConptyModeForTests(std::move(vtRenderEngine), resizeQuirk); -} -#endif diff --git a/src/host/globals.h b/src/host/globals.h index 812bf059c41..1fbb9da808d 100644 --- a/src/host/globals.h +++ b/src/host/globals.h @@ -76,10 +76,6 @@ class Globals wil::unique_threadpool_wait handoffInboxConsoleExitWait; bool defaultTerminalMarkerCheckRequired = false; -#ifdef UNIT_TESTING - void EnableConptyModeForTests(std::unique_ptr vtRenderEngine, const bool resizeQuirk = false); -#endif - private: CONSOLE_INFORMATION ciConsoleInformation; ApiRoutines defaultApiRoutines; diff --git a/src/host/inputBuffer.hpp b/src/host/inputBuffer.hpp index 653f04e36f1..05d604b7e47 100644 --- a/src/host/inputBuffer.hpp +++ b/src/host/inputBuffer.hpp @@ -15,7 +15,6 @@ namespace Microsoft::Console::Render { class Renderer; - class VtEngine; } class InputBuffer final : public ConsoleObjectHeader diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index 59b81b40e1d..a719f7a63c6 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -33,6 +33,14 @@ ConhostInternalGetSet::ConhostInternalGetSet(_In_ IIoProvider& io) : // - void ConhostInternalGetSet::ReturnResponse(const std::wstring_view response) { + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + + // ConPTY should not respond to requests. That's the job of the terminal. + if (gci.IsInVtIoMode()) + { + return; + } + // TODO GH#4954 During the input refactor we may want to add a "priority" input list // to make sure that "response" input is spooled directly into the application. // We switched this to an append (vs. a prepend) to fix GH#1637, a bug where two CPR @@ -202,7 +210,7 @@ CursorType ConhostInternalGetSet::GetUserDefaultCursorStyle() const // - void ConhostInternalGetSet::ShowWindow(bool showOrHide) { - const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto hwnd = gci.IsInVtIoMode() ? ServiceLocator::LocatePseudoWindow() : ServiceLocator::LocateConsoleWindow()->GetWindowHandle(); // GH#13301 - When we send this ShowWindow message, if we send it to the @@ -279,11 +287,17 @@ void ConhostInternalGetSet::SetWorkingDirectory(const std::wstring_view /*uri*/) // - true if successful. false otherwise. void ConhostInternalGetSet::PlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) { + const auto window = ServiceLocator::LocateConsoleWindow(); + if (!window) + { + return; + } + // Unlock the console, so the UI doesn't hang while we're busy. UnlockConsole(); // This call will block for the duration, unless shutdown early. - const auto windowHandle = ServiceLocator::LocateConsoleWindow()->GetWindowHandle(); + const auto windowHandle = window->GetWindowHandle(); auto& midiAudio = ServiceLocator::LocateGlobals().getConsoleInformation().GetMidiAudio(); midiAudio.PlayNote(windowHandle, noteNumber, velocity, std::chrono::duration_cast(duration)); @@ -332,11 +346,9 @@ bool ConhostInternalGetSet::ResizeWindow(const til::CoordType sColumns, const ti } // If the cursor row is now past the bottom of the viewport, we'll have to - // move the viewport down to bring it back into view. However, we don't want - // to do this in pty mode, because the conpty resize operation is dependent - // on the viewport *not* being adjusted. + // move the viewport down to bring it back into view. const auto cursorOverflow = csbiex.dwCursorPosition.Y - newViewport.BottomInclusive(); - if (cursorOverflow > 0 && !IsConsolePty()) + if (cursorOverflow > 0) { newViewport = Viewport::Offset(newViewport, { 0, cursorOverflow }); } @@ -353,17 +365,6 @@ bool ConhostInternalGetSet::ResizeWindow(const til::CoordType sColumns, const ti return true; } -// Routine Description: -// - Checks if the console host is acting as a pty. -// Arguments: -// - -// Return Value: -// - true if we're in pty mode. -bool ConhostInternalGetSet::IsConsolePty() const -{ - return ServiceLocator::LocateGlobals().getConsoleInformation().IsInVtIoMode(); -} - // Routine Description: // - Checks if the InputBuffer is willing to accept VT Input directly // IsVtInputEnabled is an internal-only "API" call that the vt commands can execute, diff --git a/src/host/outputStream.hpp b/src/host/outputStream.hpp index 9f083f60625..866a6f643dc 100644 --- a/src/host/outputStream.hpp +++ b/src/host/outputStream.hpp @@ -62,7 +62,6 @@ class ConhostInternalGetSet final : public Microsoft::Console::VirtualTerminal:: void SetWorkingDirectory(const std::wstring_view uri) override; void PlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) override; - bool IsConsolePty() const override; bool IsVtInputEnabled() const override; void NotifyAccessibilityChange(const til::rect& changedRect) override; diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index f383bff4755..0084dc5bced 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -1200,20 +1200,7 @@ void SCREEN_INFORMATION::_InternalSetViewportSize(const til::size* const pcoordS _viewport = newViewport; Tracing::s_TraceWindowViewport(_viewport); - // In Conpty mode, call TriggerScroll here without params. By not providing - // params, the renderer will make sure to update the VtEngine with the - // updated viewport size. If we don't do this, the engine can get into a - // torn state on this frame. - // - // Without this statement, the engine won't be told about the new view size - // till the start of the next frame. If any other text gets output before - // that frame starts, there's a very real chance that it'll cause errors as - // the engine tries to invalidate those regions. auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (gci.IsInVtIoMode() && ServiceLocator::LocateGlobals().pRender) - { - ServiceLocator::LocateGlobals().pRender->TriggerScroll(); - } if (gci.HasPendingCookedRead()) { gci.CookedReadData().RedrawAfterResize(); @@ -1761,6 +1748,11 @@ const SCREEN_INFORMATION& SCREEN_INFORMATION::GetMainBuffer() const return *this; } +const SCREEN_INFORMATION* SCREEN_INFORMATION::GetAltBuffer() const noexcept +{ + return _psiAlternateBuffer; +} + // Routine Description: // - Instantiates a new buffer to be used as an alternate buffer. This buffer // does not have a driver handle associated with it and shares a state @@ -1901,15 +1893,6 @@ void SCREEN_INFORMATION::_handleDeferredResize(SCREEN_INFORMATION& siMain) s_RemoveScreenBuffer(psiOldAltBuffer); // this will also delete the old alt buffer } - // GH#381: When we switch into the alt buffer: - // * flush the current frame, to clear out anything that we prepared for this buffer. - // * Emit a ?1049h/l to the remote side, to let them know that we've switched buffers. - if (gci.IsInVtIoMode() && ServiceLocator::LocateGlobals().pRender) - { - ServiceLocator::LocateGlobals().pRender->TriggerFlush(false); - LOG_IF_FAILED(gci.GetVtIo()->SwitchScreenBuffer(true)); - } - ::SetActiveScreenBuffer(*psiNewAltBuffer); // Kind of a hack until we have proper signal channels: If the client app wants window size events, send one for @@ -1937,15 +1920,6 @@ void SCREEN_INFORMATION::UseMainScreenBuffer() { _handleDeferredResize(*psiMain); - // GH#381: When we switch into the main buffer: - // * flush the current frame, to clear out anything that we prepared for this buffer. - // * Emit a ?1049h/l to the remote side, to let them know that we've switched buffers. - if (gci.IsInVtIoMode() && ServiceLocator::LocateGlobals().pRender) - { - ServiceLocator::LocateGlobals().pRender->TriggerFlush(false); - LOG_IF_FAILED(gci.GetVtIo()->SwitchScreenBuffer(false)); - } - ::SetActiveScreenBuffer(*psiMain); psiMain->UpdateScrollBars(); // The alt had disabled scrollbars, re-enable them @@ -1994,7 +1968,7 @@ bool SCREEN_INFORMATION::_IsAltBuffer() const // - true iff this buffer has a main buffer. bool SCREEN_INFORMATION::_IsInPtyMode() const { - const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); return _IsAltBuffer() || gci.IsInVtIoMode(); } @@ -2084,8 +2058,6 @@ void SCREEN_INFORMATION::SetPopupAttributes(const TextAttribute& popupAttributes void SCREEN_INFORMATION::SetDefaultAttributes(const TextAttribute& attributes, const TextAttribute& popupAttributes) { - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto oldPrimaryAttributes = GetAttributes(); const auto oldPopupAttributes = GetPopupAttributes(); @@ -2101,10 +2073,7 @@ void SCREEN_INFORMATION::SetDefaultAttributes(const TextAttribute& attributes, // 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())) - { - _textBuffer->TriggerRedrawAll(); - } + _textBuffer->TriggerRedrawAll(); // If we're an alt buffer, also update our main buffer. if (_psiMainBuffer) @@ -2200,32 +2169,6 @@ void SCREEN_INFORMATION::SetViewport(const Viewport& newViewport, return S_OK; } -// Method Description: -// - Sets up the Output state machine to be in pty mode. Sequences it doesn't -// understand will be written to the pTtyConnection passed in here. -// Arguments: -// - pTtyConnection: This is a TerminalOutputConnection that we can write the -// sequence we didn't understand to. -// Return Value: -// - -void SCREEN_INFORMATION::SetTerminalConnection(_In_ VtEngine* const pTtyConnection) -{ - auto& engine = reinterpret_cast(_stateMachine->Engine()); - if (pTtyConnection) - { - engine.SetTerminalConnection(pTtyConnection, - [&stateMachine = *_stateMachine]() -> bool { - ServiceLocator::LocateGlobals().pRender->NotifyPaintFrame(); - return stateMachine.FlushToTerminal(); - }); - } - else - { - engine.SetTerminalConnection(nullptr, - nullptr); - } -} - // Routine Description: // - Writes cells to the output buffer at the cursor position. // Arguments: diff --git a/src/host/screenInfo.hpp b/src/host/screenInfo.hpp index bf2d5e29fd6..7f0b9357a14 100644 --- a/src/host/screenInfo.hpp +++ b/src/host/screenInfo.hpp @@ -47,14 +47,6 @@ Revision History: #include "../types/inc/Viewport.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: @@ -199,7 +191,7 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console SCREEN_INFORMATION& GetMainBuffer(); const SCREEN_INFORMATION& GetMainBuffer() const; - + const SCREEN_INFORMATION* GetAltBuffer() const noexcept; SCREEN_INFORMATION& GetActiveBuffer(); const SCREEN_INFORMATION& GetActiveBuffer() const; @@ -213,8 +205,6 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console [[nodiscard]] HRESULT ClearBuffer(); - void SetTerminalConnection(_In_ Microsoft::Console::Render::VtEngine* const pTtyConnection); - void UpdateBottom(); FontInfo& GetCurrentFont() noexcept; @@ -226,6 +216,9 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console void SetIgnoreLegacyEquivalentVTAttributes() noexcept; void ResetIgnoreLegacyEquivalentVTAttributes() noexcept; + [[nodiscard]] NTSTATUS ResizeWithReflow(const til::size coordnewScreenSize); + [[nodiscard]] NTSTATUS ResizeTraditional(const til::size coordNewScreenSize); + private: SCREEN_INFORMATION(_In_ Microsoft::Console::Interactivity::IWindowMetrics* pMetrics, _In_ Microsoft::Console::Interactivity::IAccessibilityNotifier* pNotifier, @@ -249,9 +242,6 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console _Out_ bool* const pfIsHorizontalVisible, _Out_ bool* const pfIsVerticalVisible); - [[nodiscard]] NTSTATUS ResizeWithReflow(const til::size coordnewScreenSize); - [[nodiscard]] NTSTATUS ResizeTraditional(const til::size coordNewScreenSize); - [[nodiscard]] NTSTATUS _InitializeOutputStateMachine(); void _FreeOutputStateMachine(); @@ -297,7 +287,5 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console friend class TextBufferIteratorTests; friend class ScreenBufferTests; friend class CommonState; - friend class ConptyOutputTests; - friend class TerminalCoreUnitTests::ConptyRoundtripTests; #endif }; diff --git a/src/host/server.h b/src/host/server.h index 0cf24ef42ff..1e14a94b731 100644 --- a/src/host/server.h +++ b/src/host/server.h @@ -103,7 +103,10 @@ class CONSOLE_INFORMATION : bool IsConsoleLocked() const noexcept; ULONG GetCSRecursionCount() const noexcept; - Microsoft::Console::VirtualTerminal::VtIo* GetVtIo(); + Microsoft::Console::VirtualTerminal::VtIo* GetVtIo() noexcept; + Microsoft::Console::VirtualTerminal::VtIo::Writer GetVtWriter() noexcept; + Microsoft::Console::VirtualTerminal::VtIo::Writer GetVtWriterForBuffer(const SCREEN_INFORMATION* context) noexcept; + bool IsInVtIoMode() const noexcept; SCREEN_INFORMATION& GetActiveOutputBuffer() override; const SCREEN_INFORMATION& GetActiveOutputBuffer() const override; @@ -112,7 +115,6 @@ class CONSOLE_INFORMATION : InputBuffer* const GetActiveInputBuffer() const override; - bool IsInVtIoMode() const; bool HasPendingCookedRead() const noexcept; bool HasPendingPopup() const noexcept; const COOKED_READ_DATA& CookedReadData() const noexcept; diff --git a/src/host/srvinit.cpp b/src/host/srvinit.cpp index 8be3b28f486..5b10705a631 100644 --- a/src/host/srvinit.cpp +++ b/src/host/srvinit.cpp @@ -574,7 +574,7 @@ try // GH#13211 - Make sure the terminal obeys the resizing quirk. Otherwise, // defterm connections to the Terminal are going to have weird resizing. - const auto commandLine = fmt::format(FMT_COMPILE(L" --headless --resizeQuirk --signal {:#x}"), + const auto commandLine = fmt::format(FMT_COMPILE(L" --headless --signal {:#x}"), (int64_t)signalPipeOurSide.release()); ConsoleArguments consoleArgs(commandLine, inPipeOurSide.release(), outPipeOurSide.release()); @@ -841,24 +841,25 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand, // No matter what, create a renderer. try { - g.pRender = nullptr; - - auto renderThread = std::make_unique(); - // stash a local pointer to the thread here - - // We're going to give ownership of the thread to the Renderer, - // but the thread also need to be told who its renderer is, - // and we can't do that until the renderer is constructed. - auto* const localPointerToThread = renderThread.get(); - - g.pRender = new Renderer(gci.GetRenderSettings(), &gci.renderData, nullptr, 0, std::move(renderThread)); - - THROW_IF_FAILED(localPointerToThread->Initialize(g.pRender)); - - // Set up the renderer to be used to calculate the width of a glyph, - // should we be unable to figure out its width another way. - CodepointWidthDetector::Singleton().SetFallbackMethod([](const std::wstring_view& glyph) { - return ServiceLocator::LocateGlobals().pRender->IsGlyphWideByFont(glyph); - }); + if (!gci.IsInVtIoMode()) + { + auto renderThread = std::make_unique(); + // stash a local pointer to the thread here - + // We're going to give ownership of the thread to the Renderer, + // but the thread also need to be told who its renderer is, + // and we can't do that until the renderer is constructed. + auto* const localPointerToThread = renderThread.get(); + + g.pRender = new Renderer(gci.GetRenderSettings(), &gci.renderData, nullptr, 0, std::move(renderThread)); + + THROW_IF_FAILED(localPointerToThread->Initialize(g.pRender)); + + // Set up the renderer to be used to calculate the width of a glyph, + // should we be unable to figure out its width another way. + CodepointWidthDetector::Singleton().SetFallbackMethod([](const std::wstring_view& glyph) { + return ServiceLocator::LocateGlobals().pRender->IsGlyphWideByFont(glyph); + }); + } } catch (...) { @@ -876,7 +877,10 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand, } // Allow the renderer to paint once the rest of the console is hooked up. - g.pRender->EnablePainting(); + if (g.pRender) + { + g.pRender->EnablePainting(); + } if (SUCCEEDED_NTSTATUS(Status) && ConsoleConnectionDeservesVisibleWindow(p)) { diff --git a/src/host/ut_host/ConptyOutputTests.cpp b/src/host/ut_host/ConptyOutputTests.cpp deleted file mode 100644 index f4e5d4b2c7e..00000000000 --- a/src/host/ut_host/ConptyOutputTests.cpp +++ /dev/null @@ -1,465 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include -#include "../../inc/consoletaeftemplates.hpp" -#include "../../types/inc/Viewport.hpp" - -#include "../../renderer/base/Renderer.hpp" -#include "../../renderer/vt/Xterm256Engine.hpp" -#include "../../renderer/vt/XtermEngine.hpp" -#include "../Settings.hpp" - -#include "CommonState.hpp" - -using namespace WEX::Common; -using namespace WEX::Logging; -using namespace WEX::TestExecution; -using namespace Microsoft::Console::Types; -using namespace Microsoft::Console::Interactivity; -using namespace Microsoft::Console::VirtualTerminal; - -using namespace Microsoft::Console; -using namespace Microsoft::Console::Render; -using namespace Microsoft::Console::Types; - -class ConptyOutputTests -{ - // !!! DANGER: Many tests in this class expect the Terminal and Host buffers - // to be 80x32. If you change these, you'll probably inadvertently break a - // bunch of tests !!! - static const til::CoordType TerminalViewWidth = 80; - static const til::CoordType TerminalViewHeight = 32; - - // This test class is to write some things into the PTY and then check that - // the rendering that is coming out of the VT-sequence generator is exactly - // as we expect it to be. - BEGIN_TEST_CLASS(ConptyOutputTests) - TEST_CLASS_PROPERTY(L"IsolationLevel", L"Class") - END_TEST_CLASS() - - TEST_CLASS_SETUP(ClassSetup) - { - m_state = std::make_unique(); - - m_state->InitEvents(); - m_state->PrepareGlobalInputBuffer(); - m_state->PrepareGlobalScreenBuffer(TerminalViewWidth, TerminalViewHeight, TerminalViewWidth, TerminalViewHeight); - - return true; - } - - TEST_CLASS_CLEANUP(ClassCleanup) - { - m_state->CleanupGlobalScreenBuffer(); - m_state->CleanupGlobalInputBuffer(); - - m_state.release(); - - return true; - } - - TEST_METHOD_SETUP(MethodSetup) - { - // Set up some sane defaults - auto& g = ServiceLocator::LocateGlobals(); - auto& gci = g.getConsoleInformation(); - gci.SetColorTableEntry(TextColor::DEFAULT_FOREGROUND, INVALID_COLOR); - gci.SetColorTableEntry(TextColor::DEFAULT_BACKGROUND, INVALID_COLOR); - gci.SetFillAttribute(0x07); // DARK_WHITE on DARK_BLACK - gci.CalculateDefaultColorIndices(); - - g.pRender = new Renderer(gci.GetRenderSettings(), &gci.renderData, nullptr, 0, nullptr); - - 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()); - VERIFY_SUCCEEDED(currentBuffer.SetViewportOrigin(true, { 0, 0 }, true)); - VERIFY_ARE_EQUAL(til::point{}, currentBuffer.GetTextBuffer().GetCursor().GetPosition()); - - // Set up an xterm-256 renderer for conpty - auto hFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - auto initialViewport = currentBuffer.GetViewport(); - - auto vtRenderEngine = std::make_unique(std::move(hFile), - initialViewport); - auto pfn = std::bind(&ConptyOutputTests::_writeCallback, this, std::placeholders::_1, std::placeholders::_2); - vtRenderEngine->SetTestCallback(pfn); - - g.pRender->AddRenderEngine(vtRenderEngine.get()); - gci.GetActiveOutputBuffer().SetTerminalConnection(vtRenderEngine.get()); - - expectedOutput.clear(); - - // 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(std::move(vtRenderEngine)); - - return true; - } - - TEST_METHOD_CLEANUP(MethodCleanup) - { - m_state->CleanupNewTextBufferInfo(); - - auto& g = ServiceLocator::LocateGlobals(); - delete g.pRender; - - VERIFY_ARE_EQUAL(0u, expectedOutput.size(), L"Tests should drain all the output they push into the expected output buffer."); - - return true; - } - - TEST_METHOD(ConptyOutputTestCanary); - TEST_METHOD(SimpleWriteOutputTest); - TEST_METHOD(WriteTwoLinesUsesNewline); - TEST_METHOD(WriteAFewSimpleLines); - TEST_METHOD(InvalidateUntilOneBeforeEnd); - TEST_METHOD(SetConsoleTitleWithControlChars); - TEST_METHOD(IncludeBackgroundColorChangesInFirstFrame); - TEST_METHOD(MoveCursorAfterWrapForced); - -private: - bool _writeCallback(const char* const pch, const size_t cch); - void _flushFirstFrame(); - std::deque expectedOutput; - std::unique_ptr m_state; -}; - -bool ConptyOutputTests::_writeCallback(const char* const pch, const size_t cch) -{ - // Since rendering happens on a background thread that doesn't have the exception handler on it - // we need to rely on VERIFY's return codes instead of exceptions. - const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope; - - auto actualString = std::string(pch, cch); - RETURN_BOOL_IF_FALSE(VERIFY_IS_GREATER_THAN(expectedOutput.size(), - static_cast(0), - NoThrowString().Format(L"writing=\"%hs\", expecting %u strings", actualString.c_str(), expectedOutput.size()))); - - auto first = expectedOutput.front(); - expectedOutput.pop_front(); - - Log::Comment(NoThrowString().Format(L"Expected =\t\"%hs\"", first.c_str())); - Log::Comment(NoThrowString().Format(L"Actual =\t\"%hs\"", actualString.c_str())); - - RETURN_BOOL_IF_FALSE(VERIFY_ARE_EQUAL(first.length(), cch)); - RETURN_BOOL_IF_FALSE(VERIFY_ARE_EQUAL(first, actualString)); - - return true; -} - -void ConptyOutputTests::_flushFirstFrame() -{ - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - - expectedOutput.push_back("\x1b[2J"); - expectedOutput.push_back("\x1b[m"); - expectedOutput.push_back("\x1b[H"); // Go Home - expectedOutput.push_back("\x1b[?25h"); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); -} - -// 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 -// provided string. Will move the provided iterator as it validates. The -// caller should ensure that `iter` starts where they would like to validate. -// Arguments: -// - expectedChar: The character (or characters) we're expecting -// - iter: a iterator pointing to the cell we'd like to start validating at. -// - start: the first index in the range we'd like to validate -// - end: the last index in the range we'd like to validate -// Return Value: -// - -void _verifySpanOfText(const wchar_t* const expectedChar, - TextBufferCellIterator& iter, - const int start, - const int end) -{ - for (auto x = start; x < end; x++) - { - SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures); - if (iter->Chars() != expectedChar) - { - Log::Comment(NoThrowString().Format(L"character [%d] was mismatched", x)); - } - VERIFY_ARE_EQUAL(expectedChar, (iter++)->Chars()); - } - Log::Comment(NoThrowString().Format( - L"Successfully validated %d characters were '%s'", end - start, expectedChar)); -} - -void ConptyOutputTests::ConptyOutputTestCanary() -{ - Log::Comment(NoThrowString().Format( - L"This is a simple test to make sure that everything is working as expected.")); - - _flushFirstFrame(); -} - -void ConptyOutputTests::SimpleWriteOutputTest() -{ - Log::Comment(NoThrowString().Format( - L"Write some simple output, and make sure it gets rendered largely " - L"unmodified to the terminal")); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - - _flushFirstFrame(); - - expectedOutput.push_back("Hello World"); - sm.ProcessString(L"Hello World"); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); -} - -void ConptyOutputTests::WriteTwoLinesUsesNewline() -{ - Log::Comment(NoThrowString().Format( - L"Write two lines of output. We should use \r\n to move the cursor")); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - auto& tb = si.GetTextBuffer(); - - _flushFirstFrame(); - - sm.ProcessString(L"AAA"); - sm.ProcessString(L"\x1b[2;1H"); - sm.ProcessString(L"BBB"); - - { - auto iter = tb.GetCellDataAt({ 0, 0 }); - VERIFY_ARE_EQUAL(L"A", (iter++)->Chars()); - VERIFY_ARE_EQUAL(L"A", (iter++)->Chars()); - VERIFY_ARE_EQUAL(L"A", (iter++)->Chars()); - } - { - auto iter = tb.GetCellDataAt({ 0, 1 }); - VERIFY_ARE_EQUAL(L"B", (iter++)->Chars()); - VERIFY_ARE_EQUAL(L"B", (iter++)->Chars()); - VERIFY_ARE_EQUAL(L"B", (iter++)->Chars()); - } - - expectedOutput.push_back("AAA"); - expectedOutput.push_back("\r\n"); - expectedOutput.push_back("BBB"); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); -} - -void ConptyOutputTests::WriteAFewSimpleLines() -{ - Log::Comment(NoThrowString().Format( - L"Write more lines of output. We should use \r\n to move the cursor")); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - auto& tb = si.GetTextBuffer(); - - _flushFirstFrame(); - - sm.ProcessString(L"AAA\n"); - sm.ProcessString(L"BBB\n"); - sm.ProcessString(L"\n"); - sm.ProcessString(L"CCC"); - - { - auto iter = tb.GetCellDataAt({ 0, 0 }); - VERIFY_ARE_EQUAL(L"A", (iter++)->Chars()); - VERIFY_ARE_EQUAL(L"A", (iter++)->Chars()); - VERIFY_ARE_EQUAL(L"A", (iter++)->Chars()); - } - { - auto iter = tb.GetCellDataAt({ 0, 1 }); - VERIFY_ARE_EQUAL(L"B", (iter++)->Chars()); - VERIFY_ARE_EQUAL(L"B", (iter++)->Chars()); - VERIFY_ARE_EQUAL(L"B", (iter++)->Chars()); - } - { - auto iter = tb.GetCellDataAt({ 0, 2 }); - VERIFY_ARE_EQUAL(L" ", (iter++)->Chars()); - VERIFY_ARE_EQUAL(L" ", (iter++)->Chars()); - VERIFY_ARE_EQUAL(L" ", (iter++)->Chars()); - } - { - auto iter = tb.GetCellDataAt({ 0, 3 }); - VERIFY_ARE_EQUAL(L"C", (iter++)->Chars()); - VERIFY_ARE_EQUAL(L"C", (iter++)->Chars()); - VERIFY_ARE_EQUAL(L"C", (iter++)->Chars()); - } - - expectedOutput.push_back("AAA"); - expectedOutput.push_back("\r\n"); - expectedOutput.push_back("BBB"); - // Jump down to the fourth line because emitting spaces didn't do anything - // and we will skip to emitting the CCC segment. - expectedOutput.push_back("\x1b[4;1H"); - expectedOutput.push_back("CCC"); - - // Cursor goes back on. - expectedOutput.push_back("\x1b[?25h"); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); -} - -void ConptyOutputTests::InvalidateUntilOneBeforeEnd() -{ - Log::Comment(NoThrowString().Format( - L"Make sure we don't use EL and wipe out the last column of text")); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - auto& tb = si.GetTextBuffer(); - - _flushFirstFrame(); - - // Move the cursor to width-15, draw 15 characters - sm.ProcessString(L"\x1b[1;66H"); - sm.ProcessString(L"ABCDEFGHIJKLMNO"); - - { - auto iter = tb.GetCellDataAt({ 78, 0 }); - VERIFY_ARE_EQUAL(L"N", (iter++)->Chars()); - VERIFY_ARE_EQUAL(L"O", (iter++)->Chars()); - } - - expectedOutput.push_back("\x1b[65C"); - expectedOutput.push_back("ABCDEFGHIJKLMNO"); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - // overstrike the first with X and the middle 8 with spaces - sm.ProcessString(L"\x1b[1;66H"); - // ABCDEFGHIJKLMNO - sm.ProcessString(L"X "); - - { - auto iter = tb.GetCellDataAt({ 78, 0 }); - VERIFY_ARE_EQUAL(L" ", (iter++)->Chars()); - VERIFY_ARE_EQUAL(L"O", (iter++)->Chars()); - } - - expectedOutput.push_back("\x1b[1;66H"); - expectedOutput.push_back("X"); // sequence optimizer should choose ECH here - expectedOutput.push_back("\x1b[13X"); - expectedOutput.push_back("\x1b[13C"); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); -} - -void ConptyOutputTests::SetConsoleTitleWithControlChars() -{ - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:control", L"{0x00, 0x0A, 0x1B, 0x80, 0x9B, 0x9C}") - TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method") - END_TEST_METHOD_PROPERTIES() - - int control; - VERIFY_SUCCEEDED(TestData::TryGetValue(L"control", control)); - - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - - Log::Comment(NoThrowString().Format( - L"SetConsoleTitle with a control character (0x%02X) embedded in the text", control)); - - std::wstringstream titleText; - titleText << L"Hello " << wchar_t(control) << L"World!"; - g.getConsoleInformation().SetTitle(titleText.str()); - - // This is the standard init sequences for the first frame. - expectedOutput.push_back("\x1b[2J"); - expectedOutput.push_back("\x1b[m"); - expectedOutput.push_back("\x1b[H"); - - // The title change is propagated as an OSC 0 sequence. - // Control characters are stripped, so it's always "Hello World". - expectedOutput.push_back("\x1b]0;Hello World!\a"); - - // This is also part of the standard init sequence. - expectedOutput.push_back("\x1b[?25h"); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); -} - -void ConptyOutputTests::IncludeBackgroundColorChangesInFirstFrame() -{ - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - - sm.ProcessString(L"\x1b[41mRun 1 \x1b[42mRun 2 \x1b[43mRun 3 \x1b[m"); - - expectedOutput.push_back("\x1b[2J"); // standard init sequence for the first frame - expectedOutput.push_back("\x1b[m"); // standard init sequence for the first frame - expectedOutput.push_back("\x1b[41m"); - expectedOutput.push_back("\x1b[H"); // standard init sequence for the first frame - expectedOutput.push_back("Run 1 "); - expectedOutput.push_back("\x1b[42m"); - expectedOutput.push_back("Run 2 "); - expectedOutput.push_back("\x1b[43m"); - expectedOutput.push_back("Run 3 "); - - // This is also part of the standard init sequence. - expectedOutput.push_back("\x1b[?25h"); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); -} - -void ConptyOutputTests::MoveCursorAfterWrapForced() -{ - auto& g = ServiceLocator::LocateGlobals(); - auto& renderer = *g.pRender; - auto& gci = g.getConsoleInformation(); - auto& si = gci.GetActiveOutputBuffer(); - auto& sm = si.GetStateMachine(); - - // We write a character in the rightmost column to trigger the _wrapForced - // flag. Technically this is a bug, but it's how things currently work. - sm.ProcessString(L"\x1b[1;999H*"); - - expectedOutput.push_back("\x1b[2J"); // standard init sequence for the first frame - expectedOutput.push_back("\x1b[m"); // standard init sequence for the first frame - expectedOutput.push_back("\x1b[1;80H"); - expectedOutput.push_back("*"); - expectedOutput.push_back("\x1b[?25h"); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); - - // Position the cursor on line 2, and fill line 1 with A's. - sm.ProcessString(L"\x1b[2H"); - sm.ProcessString(L"\033[65;1;1;1;999$x"); - - expectedOutput.push_back("\x1b[H"); - expectedOutput.push_back("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); - - // The cursor must be explicitly moved to line 2 at the end of the frame. - // Although that may technically already be the next output location, we - // still need the cursor to be shown in that position when the frame ends. - expectedOutput.push_back("\r\n"); - expectedOutput.push_back("\x1b[?25h"); - - VERIFY_SUCCEEDED(renderer.PaintFrame()); -} diff --git a/src/host/ut_host/Host.UnitTests.vcxproj b/src/host/ut_host/Host.UnitTests.vcxproj index 89d7e2a65e0..18ea1768ff0 100644 --- a/src/host/ut_host/Host.UnitTests.vcxproj +++ b/src/host/ut_host/Host.UnitTests.vcxproj @@ -30,8 +30,6 @@ - - Create @@ -46,9 +44,6 @@ {8222900C-8B6C-452A-91AC-BE95DB04B95F} - - {990F2657-8580-4828-943F-5DD657D11843} - {06ec74cb-9a12-429c-b551-8562ec964846} diff --git a/src/host/ut_host/Host.UnitTests.vcxproj.filters b/src/host/ut_host/Host.UnitTests.vcxproj.filters index 9c946aa560f..45e9e7de3ae 100644 --- a/src/host/ut_host/Host.UnitTests.vcxproj.filters +++ b/src/host/ut_host/Host.UnitTests.vcxproj.filters @@ -57,9 +57,6 @@ Source Files - - Source Files - Source Files @@ -81,9 +78,6 @@ Source Files - - Source Files - diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index 2e802dc2472..a4a57e53f5e 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -17,7 +17,6 @@ #include "../interactivity/inc/ServiceLocator.hpp" #include "../../inc/conattrs.hpp" #include "../../types/inc/Viewport.hpp" -#include "../../renderer/vt/Xterm256Engine.hpp" #include "../../inc/TestUtils.h" @@ -7931,11 +7930,9 @@ void ScreenBufferTests::TestReflowBiggerLongLineWithColor() void ScreenBufferTests::TestDeferredMainBufferResize() { BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:inConpty", L"{false, true}") TEST_METHOD_PROPERTY(L"Data:reEnterAltBuffer", L"{false, true}") END_TEST_METHOD_PROPERTIES(); - INIT_TEST_PROPERTY(bool, inConpty, L"Should we pretend to be in conpty mode?"); INIT_TEST_PROPERTY(bool, reEnterAltBuffer, L"Should we re-enter the alt buffer when we're already in it?"); // A test for https://github.com/microsoft/terminal/pull/12719#discussion_r834860330 @@ -7946,31 +7943,6 @@ void ScreenBufferTests::TestDeferredMainBufferResize() gci.LockConsole(); // Lock must be taken to manipulate buffer. auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); - // HUGELY cribbed from ConptyRoundtripTests::MethodSetup. This fakes the - // console into thinking that it's in ConPTY mode. Yes, we need all this - // just to get gci.IsInVtIoMode() to return true. The screen buffer gates - // all sorts of internal checks on that. - // - // This could theoretically be a helper if other tests need it. - if (inConpty) - { - Log::Comment(L"Set up ConPTY"); - - auto& currentBuffer = gci.GetActiveOutputBuffer(); - // Set up an xterm-256 renderer for conpty - wil::unique_hfile hFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - auto initialViewport = currentBuffer.GetViewport(); - auto vtRenderEngine = std::make_unique(std::move(hFile), - initialViewport); - // We don't care about the output, so let it just drain to the void. - vtRenderEngine->SetTestCallback([](auto&&, auto&&) -> bool { return true; }); - gci.GetActiveOutputBuffer().SetTerminalConnection(vtRenderEngine.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. - ServiceLocator::LocateGlobals().EnableConptyModeForTests(std::move(vtRenderEngine)); - } - auto* siMain = &gci.GetActiveOutputBuffer(); auto& stateMachine = siMain->GetStateMachine(); diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index 89d7bac3815..1a6085b498c 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -2,432 +2,588 @@ // Licensed under the MIT license. #include "precomp.h" -#include -#include "../../inc/consoletaeftemplates.hpp" -#include "../../types/inc/Viewport.hpp" -#include "../VtIo.hpp" -#include "../../interactivity/inc/ServiceLocator.hpp" -#include "../../renderer/base/Renderer.hpp" -#include "../../renderer/vt/Xterm256Engine.hpp" -#include "../../renderer/vt/XtermEngine.hpp" +#include "CommonState.hpp" using namespace WEX::Common; using namespace WEX::Logging; using namespace WEX::TestExecution; -using namespace Microsoft::Console::Interactivity; - -class Microsoft::Console::VirtualTerminal::VtIoTests -{ - BEGIN_TEST_CLASS(VtIoTests) - TEST_CLASS_PROPERTY(L"IsolationLevel", L"Class") - END_TEST_CLASS() - - // General Tests: - TEST_METHOD(NoOpStartTest); - - TEST_METHOD(DtorTestJustEngine); - TEST_METHOD(DtorTestDeleteVtio); - TEST_METHOD(DtorTestStackAlloc); - TEST_METHOD(DtorTestStackAllocMany); - - TEST_METHOD(RendererDtorAndThread); - - TEST_METHOD(BasicAnonymousPipeOpeningWithSignalChannelTest); -}; +using namespace Microsoft::Console::Interactivity; using namespace Microsoft::Console; using namespace Microsoft::Console::VirtualTerminal; using namespace Microsoft::Console::Render; using namespace Microsoft::Console::Types; -void VtIoTests::NoOpStartTest() -{ - VtIo vtio; - VERIFY_IS_FALSE(vtio.IsUsingVt()); - - Log::Comment(L"Verify we succeed at StartIfNeeded even if we weren't initialized"); - VERIFY_SUCCEEDED(vtio.StartIfNeeded()); -} +static constexpr WORD red = FOREGROUND_RED | BACKGROUND_GREEN; +static constexpr WORD blu = FOREGROUND_BLUE | BACKGROUND_GREEN; -Viewport SetUpViewport() +constexpr CHAR_INFO ci_red(wchar_t ch) noexcept { - til::inclusive_rect view; - view.top = view.left = 0; - view.bottom = 31; - view.right = 79; - - return Viewport::FromInclusive(view); -} - -void VtIoTests::DtorTestJustEngine() -{ - Log::Comment(NoThrowString().Format( - L"This test is going to instantiate a bunch of VtIos in different \n" - L"scenarios to see if something causes a weird cleanup.\n" - L"It's here because of the strange nature of VtEngine having members\n" - L"that are only defined in UNIT_TESTING")); - - Log::Comment(NoThrowString().Format( - L"New some engines and delete them")); - for (auto i = 0; i < 25; ++i) - { - Log::Comment(NoThrowString().Format( - L"New/Delete loop #%d", i)); - - wil::unique_hfile hOutputFile; - hOutputFile.reset(INVALID_HANDLE_VALUE); - auto pRenderer256 = new Xterm256Engine(std::move(hOutputFile), SetUpViewport()); - Log::Comment(NoThrowString().Format(L"Made Xterm256Engine")); - delete pRenderer256; - Log::Comment(NoThrowString().Format(L"Deleted.")); - - hOutputFile.reset(INVALID_HANDLE_VALUE); - - auto pRenderEngineXterm = new XtermEngine(std::move(hOutputFile), SetUpViewport(), false); - Log::Comment(NoThrowString().Format(L"Made XtermEngine")); - delete pRenderEngineXterm; - Log::Comment(NoThrowString().Format(L"Deleted.")); - - hOutputFile.reset(INVALID_HANDLE_VALUE); - - auto pRenderEngineXtermAscii = new XtermEngine(std::move(hOutputFile), SetUpViewport(), true); - Log::Comment(NoThrowString().Format(L"Made XtermEngine")); - delete pRenderEngineXtermAscii; - Log::Comment(NoThrowString().Format(L"Deleted.")); - } + return { ch, red }; } -void VtIoTests::DtorTestDeleteVtio() +constexpr CHAR_INFO ci_blu(wchar_t ch) noexcept { - Log::Comment(NoThrowString().Format( - L"This test is going to instantiate a bunch of VtIos in different \n" - L"scenarios to see if something causes a weird cleanup.\n" - L"It's here because of the strange nature of VtEngine having members\n" - L"that are only defined in UNIT_TESTING")); - - Log::Comment(NoThrowString().Format( - L"New some engines and delete them")); - for (auto i = 0; i < 25; ++i) - { - Log::Comment(NoThrowString().Format( - L"New/Delete loop #%d", i)); - - auto hOutputFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - - hOutputFile.reset(INVALID_HANDLE_VALUE); - - auto vtio = new VtIo(); - Log::Comment(NoThrowString().Format(L"Made VtIo")); - vtio->_pVtRenderEngine = std::make_unique(std::move(hOutputFile), - SetUpViewport()); - Log::Comment(NoThrowString().Format(L"Made Xterm256Engine")); - delete vtio; - Log::Comment(NoThrowString().Format(L"Deleted.")); - - hOutputFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - vtio = new VtIo(); - Log::Comment(NoThrowString().Format(L"Made VtIo")); - vtio->_pVtRenderEngine = std::make_unique(std::move(hOutputFile), - SetUpViewport(), - false); - Log::Comment(NoThrowString().Format(L"Made XtermEngine")); - delete vtio; - Log::Comment(NoThrowString().Format(L"Deleted.")); - - hOutputFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - vtio = new VtIo(); - Log::Comment(NoThrowString().Format(L"Made VtIo")); - vtio->_pVtRenderEngine = std::make_unique(std::move(hOutputFile), - SetUpViewport(), - true); - Log::Comment(NoThrowString().Format(L"Made XtermEngine")); - delete vtio; - Log::Comment(NoThrowString().Format(L"Deleted.")); - } + return { ch, blu }; } -void VtIoTests::DtorTestStackAlloc() -{ - Log::Comment(NoThrowString().Format( - L"This test is going to instantiate a bunch of VtIos in different \n" - L"scenarios to see if something causes a weird cleanup.\n" - L"It's here because of the strange nature of VtEngine having members\n" - L"that are only defined in UNIT_TESTING")); - - Log::Comment(NoThrowString().Format( - L"make some engines and let them fall out of scope")); - for (auto i = 0; i < 25; ++i) - { - Log::Comment(NoThrowString().Format( - L"Scope Exit Auto cleanup #%d", i)); - - wil::unique_hfile hOutputFile; - - hOutputFile.reset(INVALID_HANDLE_VALUE); - { - VtIo vtio; - vtio._pVtRenderEngine = std::make_unique(std::move(hOutputFile), - SetUpViewport()); - } - - hOutputFile.reset(INVALID_HANDLE_VALUE); - { - VtIo vtio; - vtio._pVtRenderEngine = std::make_unique(std::move(hOutputFile), - SetUpViewport(), - false); - } - - hOutputFile.reset(INVALID_HANDLE_VALUE); - { - VtIo vtio; - vtio._pVtRenderEngine = std::make_unique(std::move(hOutputFile), - SetUpViewport(), - true); - } - } -} - -void VtIoTests::DtorTestStackAllocMany() -{ - Log::Comment(NoThrowString().Format( - L"This test is going to instantiate a bunch of VtIos in different \n" - L"scenarios to see if something causes a weird cleanup.\n" - L"It's here because of the strange nature of VtEngine having members\n" - L"that are only defined in UNIT_TESTING")); - - Log::Comment(NoThrowString().Format( - L"Try an make a whole bunch all at once, and have them all fall out of scope at once.")); - for (auto i = 0; i < 25; ++i) - { - Log::Comment(NoThrowString().Format( - L"Multiple engines, one scope loop #%d", i)); - - wil::unique_hfile hOutputFile; - { - hOutputFile.reset(INVALID_HANDLE_VALUE); - VtIo vtio1; - vtio1._pVtRenderEngine = std::make_unique(std::move(hOutputFile), - SetUpViewport()); - - hOutputFile.reset(INVALID_HANDLE_VALUE); - VtIo vtio2; - vtio2._pVtRenderEngine = std::make_unique(std::move(hOutputFile), - SetUpViewport(), - false); - - hOutputFile.reset(INVALID_HANDLE_VALUE); - VtIo vtio3; - vtio3._pVtRenderEngine = std::make_unique(std::move(hOutputFile), - SetUpViewport(), - true); - } - } -} +#define cup(y, x) "\x1b[" #y ";" #x "H" // CUP: Cursor Position +#define decawm(h) "\x1b[?7" #h // DECAWM: Autowrap Mode +#define decsc() "\x1b\x37" // DECSC: DEC Save Cursor (+ attributes) +#define decrc() "\x1b\x38" // DECRC: DEC Restore Cursor (+ attributes) + +// The escape sequences that ci_red() / ci_blu() result in. +#define sgr_red(s) "\x1b[0;31;42m" s +#define sgr_blu(s) "\x1b[0;34;42m" s +// What the default attributes `FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED` result in. +#define sgr_rst() "\x1b[0m" + +static constexpr std::wstring_view s_initialContentVT{ + // clang-format off + L"" + sgr_red("AB") sgr_blu("ab") sgr_red("CD") sgr_blu("cd") "\r\n" + sgr_red("EF") sgr_blu("ef") sgr_red("GH") sgr_blu("gh") "\r\n" + sgr_blu("ij") sgr_red("IJ") sgr_blu("kl") sgr_red("KL") "\r\n" + sgr_blu("mn") sgr_red("MN") sgr_blu("op") sgr_red("OP") + // clang-format on +}; -class MockRenderData : public IRenderData +class ::Microsoft::Console::VirtualTerminal::VtIoTests { -public: - Microsoft::Console::Types::Viewport GetViewport() noexcept override - { - return Microsoft::Console::Types::Viewport{}; - } - - til::point GetTextBufferEndPosition() const noexcept override - { - return {}; - } - - TextBuffer& GetTextBuffer() const noexcept override - { - FAIL_FAST_HR(E_NOTIMPL); - } - - const FontInfo& GetFontInfo() const noexcept override - { - FAIL_FAST_HR(E_NOTIMPL); - } - - std::vector GetSelectionRects() noexcept override - { - return std::vector{}; - } + BEGIN_TEST_CLASS(VtIoTests) + TEST_CLASS_PROPERTY(L"IsolationLevel", L"Class") + END_TEST_CLASS() - void LockConsole() noexcept override - { - } + CommonState commonState; + ApiRoutines routines; + SCREEN_INFORMATION* screenInfo = nullptr; + wil::unique_hfile rx; + char rxBuf[4096]; - void UnlockConsole() noexcept override + std::string_view readOutput() noexcept { + DWORD read = 0; + ReadFile(rx.get(), &rxBuf[0], sizeof(rxBuf), &read, nullptr); + return { &rxBuf[0], read }; } - std::pair GetAttributeColors(const TextAttribute& /*attr*/) const noexcept override + void setupInitialContents() const { - return std::make_pair(COLORREF{}, COLORREF{}); + auto& sm = screenInfo->GetStateMachine(); + sm.ProcessString(L"\033c"); + sm.ProcessString(s_initialContentVT); + sm.ProcessString(L"\x1b[H" sgr_rst()); } - til::point GetCursorPosition() const noexcept override + void resetContents() const { - return {}; + auto& sm = screenInfo->GetStateMachine(); + sm.ProcessString(L"\033c"); } - bool IsCursorVisible() const noexcept override + TEST_CLASS_SETUP(ClassSetup) { - return false; - } + wil::unique_hfile tx; + //std::tie(tx, rx) = createOverlappedPipe(16 * 1024); + THROW_IF_WIN32_BOOL_FALSE(CreatePipe(rx.addressof(), tx.addressof(), nullptr, 16 * 1024)); - bool IsCursorOn() const noexcept override - { - return false; - } + DWORD mode = PIPE_READMODE_BYTE | PIPE_NOWAIT; + THROW_IF_WIN32_BOOL_FALSE(SetNamedPipeHandleState(rx.get(), &mode, nullptr, nullptr)); - ULONG GetCursorHeight() const noexcept override - { - return 42ul; - } + commonState.PrepareGlobalInputBuffer(); + commonState.PrepareGlobalScreenBuffer(8, 4, 8, 4); - CursorType GetCursorStyle() const noexcept override - { - return CursorType::FullBox; - } + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + THROW_IF_FAILED(gci.GetVtIo()->_Initialize(nullptr, tx.release(), nullptr)); - ULONG GetCursorPixelWidth() const noexcept override - { - return 12ul; + screenInfo = &gci.GetActiveOutputBuffer(); + return true; } - bool IsCursorDoubleWidth() const override + TEST_METHOD(SetConsoleCursorPosition) { - return false; - } + THROW_IF_FAILED(routines.SetConsoleCursorPositionImpl(*screenInfo, { 2, 3 })); + THROW_IF_FAILED(routines.SetConsoleCursorPositionImpl(*screenInfo, { 0, 0 })); + THROW_IF_FAILED(routines.SetConsoleCursorPositionImpl(*screenInfo, { 7, 3 })); + THROW_IF_FAILED(routines.SetConsoleCursorPositionImpl(*screenInfo, { 3, 2 })); - const bool IsGridLineDrawingAllowed() noexcept override - { - return false; + const auto expected = cup(4, 3) cup(1, 1) cup(4, 8) cup(3, 4); + const auto actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); } - const std::wstring_view GetConsoleTitle() const noexcept override + TEST_METHOD(SetConsoleOutputMode) { - return std::wstring_view{}; - } + const auto initialMode = screenInfo->OutputMode; + const auto cleanup = wil::scope_exit([=]() { + screenInfo->OutputMode = initialMode; + }); - const bool IsSelectionActive() const override - { - return false; - } + screenInfo->OutputMode = 0; - const bool IsBlockSelection() const noexcept override - { - return false; - } + THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfo, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN | ENABLE_LVB_GRID_WORLDWIDE)); // DECAWM ✔️ + THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfo, ENABLE_PROCESSED_OUTPUT | DISABLE_NEWLINE_AUTO_RETURN | ENABLE_LVB_GRID_WORLDWIDE)); // DECAWM ✖️ + THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfo, 0)); // DECAWM ✖️ + THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfo, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | DISABLE_NEWLINE_AUTO_RETURN | ENABLE_LVB_GRID_WORLDWIDE)); // DECAWM ✔️ - void ClearSelection() override - { + const auto expected = + decawm(h) // DECAWM ✔️ + decawm(l) // DECAWM ✖️ + decawm(h); // DECAWM ✔️ + const auto actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); } - void SelectNewRegion(const til::point /*coordStart*/, const til::point /*coordEnd*/) override + TEST_METHOD(SetConsoleTitleW) { - } + std::string_view expected; + std::string_view actual; - std::span GetSearchHighlights() const noexcept override - { - return {}; - } + THROW_IF_FAILED(routines.SetConsoleTitleWImpl( + L"foobar")); + expected = "\x1b]0;foobar\a"; + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); - const til::point_span* GetSearchHighlightFocused() const noexcept override - { - return nullptr; - } + THROW_IF_FAILED(routines.SetConsoleTitleWImpl( + L"foo" + "\u0001\u001f" + "bar")); + expected = "\x1b]0;foo☺▼bar\a"; + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); - const til::point GetSelectionAnchor() const noexcept - { - return {}; + THROW_IF_FAILED(routines.SetConsoleTitleWImpl( + L"foo" + "\u0001\u001f" + "bar" + "\u007f\u009f")); + expected = "\x1b]0;foo☺▼bar⌂?\a"; + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); } - const til::point GetSelectionEnd() const noexcept + TEST_METHOD(SetConsoleCursorInfo) { - return {}; - } + THROW_IF_FAILED(routines.SetConsoleCursorInfoImpl(*screenInfo, 25, false)); + THROW_IF_FAILED(routines.SetConsoleCursorInfoImpl(*screenInfo, 25, true)); - const bool IsUiaDataInitialized() const noexcept - { - return true; + const auto expected = "\x1b[?25l" + "\x1b[?25h"; + const auto actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); } - const std::wstring GetHyperlinkUri(uint16_t /*id*/) const + TEST_METHOD(SetConsoleTextAttribute) { - return {}; - } + for (WORD i = 0; i < 16; i++) + { + THROW_IF_FAILED(routines.SetConsoleTextAttributeImpl(*screenInfo, i | BACKGROUND_RED)); + } - const std::wstring GetHyperlinkCustomId(uint16_t /*id*/) const - { - return {}; - } + for (WORD i = 0; i < 16; i++) + { + THROW_IF_FAILED(routines.SetConsoleTextAttributeImpl(*screenInfo, i << 4 | FOREGROUND_RED)); + } - const std::vector GetPatternId(const til::point /*location*/) const - { - return {}; + THROW_IF_FAILED(routines.SetConsoleTextAttributeImpl(*screenInfo, FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_GREEN | COMMON_LVB_REVERSE_VIDEO)); + THROW_IF_FAILED(routines.SetConsoleTextAttributeImpl(*screenInfo, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | COMMON_LVB_REVERSE_VIDEO)); + + const auto expected = + // 16 foreground colors + "\x1b[0;30;41m" + "\x1b[0;34;41m" + "\x1b[0;32;41m" + "\x1b[0;36;41m" + "\x1b[0;31;41m" + "\x1b[0;35;41m" + "\x1b[0;33;41m" + "\x1b[0;41m" // <-- default foreground (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED) + "\x1b[0;90;41m" + "\x1b[0;94;41m" + "\x1b[0;92;41m" + "\x1b[0;96;41m" + "\x1b[0;91;41m" + "\x1b[0;95;41m" + "\x1b[0;93;41m" + "\x1b[0;97;41m" + // 16 background colors + "\x1b[0;31m" // <-- default background (0) + "\x1b[0;31;44m" + "\x1b[0;31;42m" + "\x1b[0;31;46m" + "\x1b[0;31;41m" + "\x1b[0;31;45m" + "\x1b[0;31;43m" + "\x1b[0;31;47m" + "\x1b[0;31;100m" + "\x1b[0;31;104m" + "\x1b[0;31;102m" + "\x1b[0;31;106m" + "\x1b[0;31;101m" + "\x1b[0;31;105m" + "\x1b[0;31;103m" + "\x1b[0;31;107m" + // The remaining two calls + "\x1b[0;7;95;42m" + "\x1b[0;7m"; + const auto actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(WriteConsoleW) + { + resetContents(); + + size_t written; + std::unique_ptr waiter; + std::string_view expected; + std::string_view actual; + + THROW_IF_FAILED(routines.WriteConsoleWImpl(*screenInfo, L"", written, false, waiter)); + expected = ""; + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // Force-wrap because we write up to the last column. + THROW_IF_FAILED(routines.WriteConsoleWImpl(*screenInfo, L"aaaaaaaa", written, false, waiter)); + expected = "aaaaaaaa\r\n"; + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // Force-wrap because we write up to the last column, but this time with a tab. + THROW_IF_FAILED(routines.WriteConsoleWImpl(*screenInfo, L"a\t\r\nb", written, false, waiter)); + expected = "a\t\r\n\r\nb"; + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(WriteConsoleOutputW) + { + resetContents(); + + std::array payload{ ci_red('a'), ci_red('b'), ci_blu('A'), ci_blu('B') }; + const auto target = Viewport::FromDimensions({ 1, 1 }, { 4, 1 }); + Viewport written; + THROW_IF_FAILED(routines.WriteConsoleOutputWImpl(*screenInfo, payload, target, written)); + + const auto expected = decsc() cup(2, 2) sgr_red("ab") sgr_blu("AB") decrc(); + const auto actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(WriteConsoleOutputAttribute) + { + setupInitialContents(); + + static constexpr std::array payload{ red, blu, red, blu }; + static constexpr til::point target{ 6, 1 }; + size_t written; + THROW_IF_FAILED(routines.WriteConsoleOutputAttributeImpl(*screenInfo, payload, target, written)); + + const auto expected = + decsc() // + cup(2, 7) sgr_red("g") sgr_blu("h") // + cup(3, 1) sgr_red("i") sgr_blu("j") // + decrc(); + const auto actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(WriteConsoleOutputCharacterW) + { + setupInitialContents(); + + size_t written = 0; + std::string_view expected; + std::string_view actual; + + THROW_IF_FAILED(routines.WriteConsoleOutputCharacterWImpl(*screenInfo, L"foobar", { 5, 1 }, written)); + expected = + decsc() // + cup(2, 6) sgr_red("f") sgr_blu("oo") // + cup(3, 1) sgr_blu("ba") sgr_red("r") // + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(6u, written); + VERIFY_ARE_EQUAL(expected, actual); + + // Writing past the end of the buffer. + THROW_IF_FAILED(routines.WriteConsoleOutputCharacterWImpl(*screenInfo, L"foobar", { 5, 3 }, written)); + expected = + decsc() // + cup(4, 6) sgr_blu("f") sgr_red("oo") // + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(3u, written); + VERIFY_ARE_EQUAL(expected, actual); + + // Writing 3 wide chars while intersecting the last column. + THROW_IF_FAILED(routines.WriteConsoleOutputCharacterWImpl(*screenInfo, L"✨✅❌", { 5, 1 }, written)); + expected = + decsc() // + cup(2, 6) sgr_red("✨") sgr_blu(" ") // + cup(3, 1) sgr_blu("✅") sgr_red("❌") // + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(3u, written); + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(FillConsoleOutputAttribute) + { + setupInitialContents(); + + size_t cellsModified = 0; + std::string_view expected; + std::string_view actual; + + // Writing nothing should produce nothing. + THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, red, 0, {}, cellsModified)); + expected = ""; + actual = readOutput(); + VERIFY_ARE_EQUAL(0u, cellsModified); + VERIFY_ARE_EQUAL(expected, actual); + + // Writing at the start of a line. + THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, red, 3, { 0, 0 }, cellsModified)); + expected = + decsc() // + cup(1, 1) sgr_red("ABa") // + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(3u, cellsModified); + VERIFY_ARE_EQUAL(expected, actual); + + // Writing at the end of a line. + THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, red, 3, { 5, 0 }, cellsModified)); + expected = + decsc() // + cup(1, 6) sgr_red("Dcd") // + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(3u, cellsModified); + VERIFY_ARE_EQUAL(expected, actual); + + // Writing across 2 lines. + THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, blu, 8, { 4, 1 }, cellsModified)); + expected = + decsc() // + cup(2, 5) sgr_blu("GHgh") // + cup(3, 1) sgr_blu("ijIJ") // + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(8u, cellsModified); + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(FillConsoleOutputCharacterW) + { + setupInitialContents(); + + size_t cellsModified = 0; + std::string_view expected; + std::string_view actual; + + // Writing nothing should produce nothing. + THROW_IF_FAILED(routines.FillConsoleOutputCharacterWImpl(*screenInfo, L'a', 0, {}, cellsModified)); + expected = ""; + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // Writing at the start of a line. + THROW_IF_FAILED(routines.FillConsoleOutputCharacterWImpl(*screenInfo, L'a', 3, { 0, 0 }, cellsModified)); + expected = + decsc() // + cup(1, 1) sgr_red("aa") sgr_blu("a") // + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // Writing at the end of a line. + THROW_IF_FAILED(routines.FillConsoleOutputCharacterWImpl(*screenInfo, L'b', 3, { 5, 0 }, cellsModified)); + expected = + decsc() // + cup(1, 6) sgr_red("b") sgr_blu("bb") // + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // Writing across 2 lines. + THROW_IF_FAILED(routines.FillConsoleOutputCharacterWImpl(*screenInfo, L'c', 8, { 4, 1 }, cellsModified)); + expected = + decsc() // + cup(2, 5) sgr_red("cc") sgr_blu("cc") // + cup(3, 1) sgr_blu("cc") sgr_red("cc") // + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // Writing 3 wide chars while intersecting the last column. + THROW_IF_FAILED(routines.FillConsoleOutputCharacterWImpl(*screenInfo, L'✨', 3, { 5, 1 }, cellsModified)); + expected = + decsc() // + cup(2, 6) sgr_red("✨") sgr_blu(" ") // + cup(3, 1) sgr_blu("✨") sgr_red("✨") // + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(ScrollConsoleScreenBufferW) + { + std::string_view expected; + std::string_view actual; + + setupInitialContents(); + + // 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)); + THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { -10, -10, -9, -9 }, {}, std::nullopt, L' ', 0, false)); + expected = ""; + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // Scrolling from somewhere to nowhere should clear the area. + THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 0, 1, 1 }, { 10, 10 }, std::nullopt, L' ', red, false)); + expected = + decsc() // + cup(1, 1) sgr_red(" ") // + cup(2, 1) sgr_red(" ") // + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // cmd uses ScrollConsoleScreenBuffer to clear the buffer contents and that gets translated to a clear screen sequence. + THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 0, 7, 3 }, { 0, -4 }, std::nullopt, 0, 0, true)); + expected = "\x1b[H\x1b[2J\x1b[3J"; + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // + // A B a b C D c d + // + // E F e f G H g h + // + // i j I J k l K L + // + // m n M N o p O P + // + setupInitialContents(); + + // Scrolling from somewhere to somewhere. + // + // +-------+ + // A | Z Z | b C D c d + // | src | + // E | Z Z | f G H g h + // +-------+ +-------+ + // i j I J k | B a | L + // | dst | + // m n M N o | F e | P + // +-------+ + THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 1, 0, 2, 1 }, { 5, 2 }, std::nullopt, L'Z', red, false)); + expected = + decsc() // + cup(1, 2) sgr_red("ZZ") // + cup(2, 2) sgr_red("ZZ") // + cup(3, 6) sgr_red("B") sgr_blu("a") // + cup(4, 6) sgr_red("F") sgr_blu("e") // + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // Same, but with a partially out-of-bounds target and clip rect. Clip rects affect both + // the source area that gets filled and the target area that gets a copy of the source contents. + // + // A Z Z b C D c d + // +---+~~~~~~~~~~~~~~~~~~~~~~~+ + // | E $ z z | f G H g $ h + // | $ src | +---$-------+ + // | i $ z z | J k B | E $ L | + // +---$-------+ | $ dst | + // m $ n M N o F | i $ P | + // +~~~~~~~~~~~~~~~~~~~~~~~+-------+ + // clip rect + THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 0, 1, 2, 2 }, { 6, 2 }, til::inclusive_rect{ 1, 1, 6, 3 }, L'z', blu, false)); + expected = + decsc() // + cup(2, 2) sgr_blu("zz") // + cup(3, 2) sgr_blu("zz") // + cup(3, 7) sgr_red("E") // + cup(4, 7) sgr_blu("i") // + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // Same, but with a partially out-of-bounds source. + // The boundaries of the buffer act as a clip rect for reading and so only 2 cells get copied. + // + // +-------+ + // A Z Z b C D c | Y | + // | src | + // E z z f G H g | Y | + // +---+ +-------+ + // i z z J | d | B E L + // |dst| + // m n M N | h | F i P + // +---+ + THROW_IF_FAILED(routines.ScrollConsoleScreenBufferWImpl(*screenInfo, { 7, 0, 8, 1 }, { 4, 2 }, std::nullopt, L'Y', red, false)); + expected = + decsc() // + cup(1, 8) sgr_red("Y") // + cup(2, 8) sgr_red("Y") // + cup(3, 5) sgr_blu("d") // + cup(4, 5) sgr_blu("h") // + decrc(); + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + static constexpr std::array expectedContents{ { + // clang-format off + ci_red('A'), ci_red('Z'), ci_red('Z'), ci_blu('b'), ci_red('C'), ci_red('D'), ci_blu('c'), ci_red('Y'), + ci_red('E'), ci_blu('z'), ci_blu('z'), ci_blu('f'), ci_red('G'), ci_red('H'), ci_blu('g'), ci_red('Y'), + ci_blu('i'), ci_blu('z'), ci_blu('z'), ci_red('J'), ci_blu('d'), ci_red('B'), ci_red('E'), ci_red('L'), + ci_blu('m'), ci_blu('n'), ci_red('M'), ci_red('N'), ci_blu('h'), ci_red('F'), ci_blu('i'), ci_red('P'), + // clang-format on + } }; + std::array actualContents{}; + Viewport actualContentsRead; + THROW_IF_FAILED(routines.ReadConsoleOutputWImpl(*screenInfo, actualContents, Viewport::FromDimensions({}, { 8, 4 }), actualContentsRead)); + VERIFY_IS_TRUE(memcmp(expectedContents.data(), actualContents.data(), sizeof(actualContents)) == 0); + } + + TEST_METHOD(SetConsoleActiveScreenBuffer) + { + SCREEN_INFORMATION* screenInfoAlt; + + VERIFY_NT_SUCCESS(SCREEN_INFORMATION::CreateInstance( + screenInfo->GetViewport().Dimensions(), + screenInfo->GetCurrentFont(), + screenInfo->GetBufferSize().Dimensions(), + screenInfo->GetAttributes(), + screenInfo->GetPopupAttributes(), + screenInfo->GetTextBuffer().GetCursor().GetSize(), + &screenInfoAlt)); + + routines.SetConsoleActiveScreenBufferImpl(*screenInfoAlt); + setupInitialContents(); + THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfoAlt, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)); + readOutput(); + + routines.SetConsoleActiveScreenBufferImpl(*screenInfo); + + const auto expected = + "\x1b[?1049l" // ASB (Alternate Screen Buffer) + cup(1, 1) sgr_red("AB") sgr_blu("ab") sgr_red("CD") sgr_blu("cd") // + cup(2, 1) sgr_red("EF") sgr_blu("ef") sgr_red("GH") sgr_blu("gh") // + cup(3, 1) sgr_blu("ij") sgr_red("IJ") sgr_blu("kl") sgr_red("KL") // + cup(4, 1) sgr_blu("mn") sgr_red("MN") sgr_blu("op") sgr_red("OP") // + cup(1, 1) sgr_rst() // + "\x1b[?25h" // DECTCEM (Text Cursor Enable) + "\x1b[?7h"; // DECAWM (Autowrap Mode) + const auto actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); } }; - -void VtIoTests::RendererDtorAndThread() -{ - Log::Comment(NoThrowString().Format( - L"Test deleting a Renderer a bunch of times")); - - for (auto i = 0; i < 16; ++i) - { - auto data = std::make_unique(); - auto thread = std::make_unique(); - auto* pThread = thread.get(); - auto pRenderer = std::make_unique(RenderSettings{}, data.get(), nullptr, 0, std::move(thread)); - VERIFY_SUCCEEDED(pThread->Initialize(pRenderer.get())); - // Sleep for a hot sec to make sure the thread starts before we enable painting - // If you don't, the thread might wait on the paint enabled event AFTER - // EnablePainting gets called, and if that happens, then the thread will - // never get destructed. This will only ever happen in the vstest test runner, - // which is what CI uses. - /*Sleep(500);*/ - - pThread->EnablePainting(); - pRenderer->TriggerTeardown(); - pRenderer.reset(); - } -} - -void VtIoTests::BasicAnonymousPipeOpeningWithSignalChannelTest() -{ - Log::Comment(L"Test using anonymous pipes for the input and adding a signal channel."); - - Log::Comment(L"\tcreating pipes"); - - wil::unique_handle inPipeReadSide; - wil::unique_handle inPipeWriteSide; - wil::unique_handle outPipeReadSide; - wil::unique_handle outPipeWriteSide; - wil::unique_handle signalPipeReadSide; - wil::unique_handle signalPipeWriteSide; - - VERIFY_WIN32_BOOL_SUCCEEDED(CreatePipe(&inPipeReadSide, &inPipeWriteSide, nullptr, 0), L"Create anonymous in pipe."); - VERIFY_WIN32_BOOL_SUCCEEDED(CreatePipe(&outPipeReadSide, &outPipeWriteSide, nullptr, 0), L"Create anonymous out pipe."); - VERIFY_WIN32_BOOL_SUCCEEDED(CreatePipe(&signalPipeReadSide, &signalPipeWriteSide, nullptr, 0), L"Create anonymous signal pipe."); - - Log::Comment(L"\tinitializing vtio"); - - // CreateIoHandlers() assert()s on IsConsoleLocked() to guard against a race condition. - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - gci.LockConsole(); - const auto cleanup = wil::scope_exit([&]() { - gci.UnlockConsole(); - }); - - VtIo vtio; - VERIFY_IS_FALSE(vtio.IsUsingVt()); - VERIFY_ARE_EQUAL(nullptr, vtio._pPtySignalInputThread); - VERIFY_SUCCEEDED(vtio._Initialize(inPipeReadSide.release(), outPipeWriteSide.release(), signalPipeReadSide.release())); - VERIFY_SUCCEEDED(vtio.CreateAndStartSignalThread()); - VERIFY_SUCCEEDED(vtio.CreateIoHandlers()); - VERIFY_IS_TRUE(vtio.IsUsingVt()); - VERIFY_ARE_NOT_EQUAL(nullptr, vtio._pPtySignalInputThread); -} diff --git a/src/host/ut_host/VtRendererTests.cpp b/src/host/ut_host/VtRendererTests.cpp deleted file mode 100644 index 279012e71fe..00000000000 --- a/src/host/ut_host/VtRendererTests.cpp +++ /dev/null @@ -1,1757 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include -#include "../../inc/consoletaeftemplates.hpp" -#include "../../types/inc/Viewport.hpp" - -#include "../../terminal/adapter/DispatchTypes.hpp" -#include "../host/RenderData.hpp" -#include "../../renderer/vt/Xterm256Engine.hpp" -#include "../../renderer/vt/XtermEngine.hpp" -#include "../Settings.hpp" - -using namespace WEX::Common; -using namespace WEX::Logging; -using namespace WEX::TestExecution; - -namespace Microsoft -{ - namespace Console - { - namespace Render - { - class VtRendererTest; - }; - }; -}; -using namespace Microsoft::Console; -using namespace Microsoft::Console::Render; -using namespace Microsoft::Console::Types; -using namespace Microsoft::Console::VirtualTerminal::DispatchTypes; - -static const std::string CLEAR_SCREEN = "\x1b[2J"; -static const std::string CURSOR_HOME = "\x1b[H"; -// Sometimes when we're expecting the renderengine to not write anything, -// we'll add this to the expected input, and manually write this to the callback -// to make sure nothing else gets written. -// We don't use null because that will confuse the VERIFY macros re: string length. -const char* const EMPTY_CALLBACK_SENTINEL = "\xff"; - -class Microsoft::Console::Render::VtRendererTest -{ - TEST_CLASS(VtRendererTest); - - TEST_CLASS_SETUP(ClassSetup) - { - return true; - } - - TEST_CLASS_CLEANUP(ClassCleanup) - { - return true; - } - - TEST_METHOD_SETUP(MethodSetup) - { - qExpectedInput.clear(); - return true; - } - - // Defining a TEST_METHOD_CLEANUP seemed to break x86 test pass. Not sure why, - // something about the clipboard tests and - // YOU_CAN_ONLY_DESIGNATE_ONE_CLASS_METHOD_TO_BE_A_TEST_METHOD_SETUP_METHOD - // It's probably more correct to leave it out anyways. - - TEST_METHOD(VtSequenceHelperTests); - - TEST_METHOD(Xterm256TestInvalidate); - TEST_METHOD(Xterm256TestColors); - TEST_METHOD(Xterm256TestITUColors); - TEST_METHOD(Xterm256TestCursor); - TEST_METHOD(Xterm256TestExtendedAttributes); - TEST_METHOD(Xterm256TestAttributesAcrossReset); - TEST_METHOD(Xterm256TestDoublyUnderlinedResetBeforeSettingStyle); - - TEST_METHOD(XtermTestInvalidate); - TEST_METHOD(XtermTestColors); - TEST_METHOD(XtermTestCursor); - TEST_METHOD(XtermTestAttributesAcrossReset); - - TEST_METHOD(FormattedString); - - TEST_METHOD(TestWrapping); - - TEST_METHOD(TestResize); - - TEST_METHOD(TestCursorVisibility); - - void Test16Colors(VtEngine* engine); - - std::deque qExpectedInput; - bool WriteCallback(const char* const pch, const size_t cch); - void TestPaint(VtEngine& engine, std::function pfn); - Viewport SetUpViewport(); - - void VerifyFirstPaint(VtEngine& engine) - { - // Verify the first BeginPaint emits a clear and go home - qExpectedInput.push_back("\x1b[2J"); - // Verify the first EndPaint sets the cursor state - qExpectedInput.push_back("\x1b[?25l"); - VERIFY_IS_TRUE(engine._firstPaint); - TestPaint(engine, [&]() { - VERIFY_IS_FALSE(engine._firstPaint); - }); - } - - void VerifyExpectedInputsDrained() - { - if (!qExpectedInput.empty()) - { - for (const auto& exp : qExpectedInput) - { - Log::Error(NoThrowString().Format(L"EXPECTED INPUT NEVER RECEIVED: %hs", exp.c_str())); - } - VERIFY_FAIL(L"there should be no remaining un-drained expected input"); - } - } -}; - -Viewport VtRendererTest::SetUpViewport() -{ - til::inclusive_rect view; - view.top = view.left = 0; - view.bottom = 31; - view.right = 79; - - return Viewport::FromInclusive(view); -} - -bool VtRendererTest::WriteCallback(const char* const pch, const size_t cch) -{ - auto actualString = std::string(pch, cch); - VERIFY_IS_GREATER_THAN(qExpectedInput.size(), - static_cast(0), - NoThrowString().Format(L"writing=\"%hs\", expecting %u strings", actualString.c_str(), qExpectedInput.size())); - - auto first = qExpectedInput.front(); - qExpectedInput.pop_front(); - - 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); - - return true; -} - -// Function Description: -// - Small helper to do a series of testing wrapped by StartPaint/EndPaint calls -// Arguments: -// - engine: the engine to operate on -// - pfn: A function pointer to some test code to run. -// Return Value: -// - -void VtRendererTest::TestPaint(VtEngine& engine, std::function pfn) -{ - VERIFY_SUCCEEDED(engine.StartPaint()); - pfn(); - VERIFY_SUCCEEDED(engine.EndPaint()); -} - -void VtRendererTest::VtSequenceHelperTests() -{ - auto hFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - auto engine = std::make_unique(std::move(hFile), SetUpViewport()); - auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2); - - engine->SetTestCallback(pfn); - - qExpectedInput.push_back("\x1b[?12l"); - VERIFY_SUCCEEDED(engine->_StopCursorBlinking()); - - qExpectedInput.push_back("\x1b[?12h"); - VERIFY_SUCCEEDED(engine->_StartCursorBlinking()); - - qExpectedInput.push_back("\x1b[?25l"); - VERIFY_SUCCEEDED(engine->_HideCursor()); - - qExpectedInput.push_back("\x1b[?25h"); - VERIFY_SUCCEEDED(engine->_ShowCursor()); - - qExpectedInput.push_back("\x1b[K"); - VERIFY_SUCCEEDED(engine->_EraseLine()); - - qExpectedInput.push_back("\x1b[M"); - VERIFY_SUCCEEDED(engine->_DeleteLine(1)); - - qExpectedInput.push_back("\x1b[2M"); - VERIFY_SUCCEEDED(engine->_DeleteLine(2)); - - qExpectedInput.push_back("\x1b[L"); - VERIFY_SUCCEEDED(engine->_InsertLine(1)); - - qExpectedInput.push_back("\x1b[2L"); - VERIFY_SUCCEEDED(engine->_InsertLine(2)); - - qExpectedInput.push_back("\x1b[2X"); - VERIFY_SUCCEEDED(engine->_EraseCharacter(2)); - - qExpectedInput.push_back("\x1b[2;3H"); - VERIFY_SUCCEEDED(engine->_CursorPosition({ 2, 1 })); - - qExpectedInput.push_back("\x1b[1;1H"); - VERIFY_SUCCEEDED(engine->_CursorPosition({ 0, 0 })); - - qExpectedInput.push_back("\x1b[H"); - VERIFY_SUCCEEDED(engine->_CursorHome()); - - qExpectedInput.push_back("\x1b[8;32;80t"); - VERIFY_SUCCEEDED(engine->_ResizeWindow(80, 32)); - - qExpectedInput.push_back("\x1b[2J"); - VERIFY_SUCCEEDED(engine->_ClearScreen()); - - qExpectedInput.push_back("\x1b[10C"); - VERIFY_SUCCEEDED(engine->_CursorForward(10)); -} - -void VtRendererTest::Xterm256TestInvalidate() -{ - auto hFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - auto engine = std::make_unique(std::move(hFile), SetUpViewport()); - auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2); - engine->SetTestCallback(pfn); - - VerifyFirstPaint(*engine); - - const auto view = SetUpViewport(); - - Log::Comment(NoThrowString().Format( - L"Make sure that invalidating all invalidates the whole viewport.")); - VERIFY_SUCCEEDED(engine->InvalidateAll()); - TestPaint(*engine, [&]() { - VERIFY_IS_TRUE(engine->_invalidMap.all()); - }); - - Log::Comment(NoThrowString().Format( - L"Make sure that invalidating anything only invalidates that portion")); - til::rect invalid = { 1, 1, 2, 2 }; - VERIFY_SUCCEEDED(engine->Invalidate(&invalid)); - TestPaint(*engine, [&]() { - VERIFY_IS_TRUE(engine->_invalidMap.one()); - VERIFY_ARE_EQUAL(invalid, *(engine->_invalidMap.begin())); - }); - - Log::Comment(NoThrowString().Format( - L"Make sure that scrolling only invalidates part of the viewport, and sends the right sequences")); - til::point scrollDelta = { 0, 1 }; - VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta)); - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"---- Scrolled one down, only top line is invalid. ----")); - invalid = view.ToExclusive(); - invalid.bottom = 1; - - const auto runs = engine->_invalidMap.runs(); - VERIFY_ARE_EQUAL(1u, runs.size()); - VERIFY_ARE_EQUAL(invalid, runs.front()); - qExpectedInput.push_back("\x1b[H"); // Go Home - qExpectedInput.push_back("\x1b[L"); // insert a line - - VERIFY_SUCCEEDED(engine->ScrollFrame()); - }); - - scrollDelta = { 0, 3 }; - VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta)); - - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"---- Scrolled three down, only top 3 lines are invalid. ----")); - invalid = view.ToExclusive(); - invalid.bottom = 3; - - // we should have 3 runs and build a rectangle out of them - const auto runs = engine->_invalidMap.runs(); - VERIFY_ARE_EQUAL(3u, runs.size()); - auto invalidRect = runs.front(); - for (size_t i = 1; i < runs.size(); ++i) - { - invalidRect |= runs[i]; - } - - // verify the rect matches the invalid one. - VERIFY_ARE_EQUAL(invalid, invalidRect); - // We would expect a CUP here, but the cursor is already at the home position - qExpectedInput.push_back("\x1b[3L"); // insert 3 lines - VERIFY_SUCCEEDED(engine->ScrollFrame()); - }); - - scrollDelta = { 0, -1 }; - VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta)); - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"---- Scrolled one up, only bottom line is invalid. ----")); - invalid = view.ToExclusive(); - invalid.top = invalid.bottom - 1; - - const auto runs = engine->_invalidMap.runs(); - VERIFY_ARE_EQUAL(1u, runs.size()); - VERIFY_ARE_EQUAL(invalid, runs.front()); - - qExpectedInput.push_back("\x1b[32;1H"); // Bottom of buffer - qExpectedInput.push_back("\n"); // Scroll down once - VERIFY_SUCCEEDED(engine->ScrollFrame()); - }); - - scrollDelta = { 0, -3 }; - VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta)); - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"---- Scrolled three up, only bottom 3 lines are invalid. ----")); - invalid = view.ToExclusive(); - invalid.top = invalid.bottom - 3; - - // we should have 3 runs and build a rectangle out of them - const auto runs = engine->_invalidMap.runs(); - VERIFY_ARE_EQUAL(3u, runs.size()); - auto invalidRect = runs.front(); - for (size_t i = 1; i < runs.size(); ++i) - { - invalidRect |= runs[i]; - } - - // verify the rect matches the invalid one. - VERIFY_ARE_EQUAL(invalid, invalidRect); - - // We would expect a CUP here, but we're already at the bottom from the last call. - qExpectedInput.push_back("\n\n\n"); // Scroll down three times - VERIFY_SUCCEEDED(engine->ScrollFrame()); - }); - - Log::Comment(NoThrowString().Format( - L"Multiple scrolls are coalesced")); - - scrollDelta = { 0, 1 }; - VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta)); - scrollDelta = { 0, 2 }; - VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta)); - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"---- Scrolled three down, only top 3 lines are invalid. ----")); - invalid = view.ToExclusive(); - invalid.bottom = 3; - - // we should have 3 runs and build a rectangle out of them - const auto runs = engine->_invalidMap.runs(); - VERIFY_ARE_EQUAL(3u, runs.size()); - auto invalidRect = runs.front(); - for (size_t i = 1; i < runs.size(); ++i) - { - invalidRect |= runs[i]; - } - - // verify the rect matches the invalid one. - VERIFY_ARE_EQUAL(invalid, invalidRect); - - qExpectedInput.push_back("\x1b[H"); // Go to home - qExpectedInput.push_back("\x1b[3L"); // insert 3 lines - VERIFY_SUCCEEDED(engine->ScrollFrame()); - }); - - scrollDelta = { 0, 1 }; - VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta)); - Log::Comment(engine->_invalidMap.to_string().c_str()); - - scrollDelta = { 0, -1 }; - VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta)); - Log::Comment(engine->_invalidMap.to_string().c_str()); - - qExpectedInput.push_back("\x1b[2J"); - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"---- Scrolled one down and one up, nothing should change ----" - L" But it still does for now MSFT:14169294")); - - const auto runs = engine->_invalidMap.runs(); - auto invalidRect = runs.front(); - for (size_t i = 1; i < runs.size(); ++i) - { - invalidRect |= runs[i]; - } - - // only the bottom line should be dirty. - // When we scrolled down, the bitmap looked like this: - // 1111 - // 0000 - // 0000 - // 0000 - // And then we scrolled up and the top line fell off and a bottom - // line was filled in like this: - // 0000 - // 0000 - // 0000 - // 1111 - const til::rect expected{ til::point{ view.Left(), view.BottomInclusive() }, til::size{ view.Width(), 1 } }; - VERIFY_ARE_EQUAL(expected, invalidRect); - - VERIFY_SUCCEEDED(engine->ScrollFrame()); - }); -} - -void VtRendererTest::Xterm256TestColors() -{ - auto hFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - auto engine = std::make_unique(std::move(hFile), SetUpViewport()); - auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2); - engine->SetTestCallback(pfn); - RenderSettings renderSettings; - RenderData renderData; - - VerifyFirstPaint(*engine); - - auto view = SetUpViewport(); - - Log::Comment(NoThrowString().Format( - L"Test changing the text attributes")); - - Log::Comment(NoThrowString().Format( - L"Begin by setting some test values - FG,BG = (1,2,3), (4,5,6) to start. " - L"These values were picked for ease of formatting raw COLORREF values.")); - qExpectedInput.push_back("\x1b[38;2;1;2;3m"); - qExpectedInput.push_back("\x1b[48;2;5;6;7m"); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes({ 0x00030201, 0x00070605 }, - renderSettings, - &renderData, - false, - false)); - - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"----Change only the BG----")); - qExpectedInput.push_back("\x1b[48;2;7;8;9m"); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes({ 0x00030201, 0x00090807 }, - renderSettings, - &renderData, - false, - false)); - - Log::Comment(NoThrowString().Format( - L"----Change only the FG----")); - qExpectedInput.push_back("\x1b[38;2;10;11;12m"); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes({ 0x000c0b0a, 0x00090807 }, - renderSettings, - &renderData, - false, - false)); - }); - - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"Make sure that color setting persists across EndPaint/StartPaint")); - qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes({ 0x000c0b0a, 0x00090807 }, - renderSettings, - &renderData, - false, - false)); - WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); // This will make sure nothing was written to the callback - }); - - // Now also do the body of the 16color test as well. - // However, instead of using a closest match ANSI color, we can reproduce - // the exact RGB or 256-color index value stored in the TextAttribute. - - Log::Comment(NoThrowString().Format( - L"Begin by setting the default colors")); - - qExpectedInput.push_back("\x1b[m"); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes({}, - renderSettings, - &renderData, - false, - false)); - - TestPaint(*engine, [&]() { - TextAttribute textAttributes; - - Log::Comment(NoThrowString().Format( - L"----Change only the BG----")); - textAttributes.SetIndexedBackground(TextColor::DARK_RED); - qExpectedInput.push_back("\x1b[41m"); // Background DARK_RED - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - - Log::Comment(NoThrowString().Format( - L"----Change only the FG----")); - textAttributes.SetIndexedForeground(TextColor::DARK_WHITE); - qExpectedInput.push_back("\x1b[37m"); // Foreground DARK_WHITE - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - - Log::Comment(NoThrowString().Format( - L"----Change only the BG to an RGB value----")); - textAttributes.SetBackground(RGB(19, 161, 14)); - qExpectedInput.push_back("\x1b[48;2;19;161;14m"); // Background RGB(19,161,14) - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - - Log::Comment(NoThrowString().Format( - L"----Change only the FG to an RGB value----")); - textAttributes.SetForeground(RGB(193, 156, 0)); - qExpectedInput.push_back("\x1b[38;2;193;156;0m"); // Foreground RGB(193,156,0) - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - - Log::Comment(NoThrowString().Format( - L"----Change only the BG to the 'Default' background----")); - textAttributes.SetDefaultBackground(); - qExpectedInput.push_back("\x1b[49m"); // Background default - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - - Log::Comment(NoThrowString().Format( - L"----Change only the FG to a 256-color index----")); - textAttributes.SetIndexedForeground256(TextColor::DARK_WHITE); - qExpectedInput.push_back("\x1b[38;5;7m"); // Foreground DARK_WHITE (256-Color Index) - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - - Log::Comment(NoThrowString().Format( - L"----Change only the BG to a 256-color index----")); - textAttributes.SetIndexedBackground256(TextColor::DARK_RED); - qExpectedInput.push_back("\x1b[48;5;1m"); // Background DARK_RED (256-Color Index) - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - - Log::Comment(NoThrowString().Format( - L"----Change only the FG to the 'Default' foreground----")); - textAttributes.SetDefaultForeground(); - qExpectedInput.push_back("\x1b[39m"); // Background default - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - - Log::Comment(NoThrowString().Format( - L"----Back to defaults----")); - textAttributes = {}; - qExpectedInput.push_back("\x1b[m"); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - }); - - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"Make sure that color setting persists across EndPaint/StartPaint")); - qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes({}, - renderSettings, - &renderData, - false, - false)); - WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); // This will make sure nothing was written to the callback - }); -} - -void VtRendererTest::Xterm256TestITUColors() -{ - auto hFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - auto engine = std::make_unique(std::move(hFile), SetUpViewport()); - auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2); - engine->SetTestCallback(pfn); - RenderSettings renderSettings; - RenderData renderData; - - VerifyFirstPaint(*engine); - - auto view = SetUpViewport(); - - Log::Comment(NoThrowString().Format( - L"Test changing the text attributes")); - - // Internally _lastTextAttributes starts with a fg and bg set to INVALID_COLOR(s), - // and initializing textAttributes with the default colors will output "\e[39m\e[49m" - // in the beginning. - auto textAttributes = TextAttribute{}; - qExpectedInput.push_back("\x1b[39m"); - qExpectedInput.push_back("\x1b[49m"); - - Log::Comment(NoThrowString().Format( - L"Begin by setting some test values - UL = (1,2,3) to start. " - L"This value is picked for ease of formatting raw COLORREF values.")); - qExpectedInput.push_back("\x1b[58:2::1:2:3m"); - textAttributes.SetUnderlineColor(RGB(1, 2, 3)); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"----Change the color----")); - qExpectedInput.push_back("\x1b[58:2::7:8:9m"); - textAttributes.SetUnderlineColor(RGB(7, 8, 9)); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - }); - - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"Make sure that color setting persists across EndPaint/StartPaint")); - qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); // This will make sure nothing was written to the callback - }); - - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"----Change the UL color to a 256-color index----")); - textAttributes.SetUnderlineColor(TextColor{ TextColor::DARK_RED, true }); - qExpectedInput.push_back("\x1b[58:5:1m"); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - - // to test the sequence for the default underline color, temporarily modify fg and bg to be something else. - textAttributes.SetForeground(RGB(9, 10, 11)); - qExpectedInput.push_back("\x1b[38;2;9;10;11m"); - textAttributes.SetBackground(RGB(5, 6, 7)); - qExpectedInput.push_back("\x1b[48;2;5;6;7m"); - - Log::Comment(NoThrowString().Format( - L"----Change only the UL color to the 'Default'----")); - textAttributes.SetDefaultUnderlineColor(); - qExpectedInput.push_back("\x1b[59m"); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - - Log::Comment(NoThrowString().Format( - L"----Back to defaults (all colors)----")); - textAttributes = {}; - qExpectedInput.push_back("\x1b[m"); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - }); - - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"Make sure that color setting persists across EndPaint/StartPaint")); - qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes({}, - renderSettings, - &renderData, - false, - false)); - WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); // This will make sure nothing was written to the callback - }); -} - -void VtRendererTest::Xterm256TestCursor() -{ - auto hFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - auto engine = std::make_unique(std::move(hFile), SetUpViewport()); - auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2); - engine->SetTestCallback(pfn); - - VerifyFirstPaint(*engine); - - auto view = SetUpViewport(); - - Log::Comment(NoThrowString().Format( - L"Test moving the cursor around. Every sequence should have both params to CUP explicitly.")); - TestPaint(*engine, [&]() { - qExpectedInput.push_back("\x1b[2;2H"); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 1, 1 })); - - Log::Comment(NoThrowString().Format( - L"----Only move Y coord----")); - qExpectedInput.push_back("\x1b[31;2H"); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 1, 30 })); - - Log::Comment(NoThrowString().Format( - L"----Only move X coord----")); - qExpectedInput.push_back("\x1b[29C"); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 30, 30 })); - - Log::Comment(NoThrowString().Format( - L"----Sending the same move sends nothing----")); - qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 30, 30 })); - WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); - - Log::Comment(NoThrowString().Format( - L"----moving home sends a simple sequence----")); - qExpectedInput.push_back("\x1b[H"); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 0, 0 })); - - Log::Comment(NoThrowString().Format( - L"----move into the line to test some other sequences----")); - qExpectedInput.push_back("\x1b[7C"); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 7, 0 })); - - Log::Comment(NoThrowString().Format( - L"----move down one line (x stays the same)----")); - qExpectedInput.push_back("\n"); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 7, 1 })); - - Log::Comment(NoThrowString().Format( - L"----move to the start of the next line----")); - qExpectedInput.push_back("\r\n"); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 0, 2 })); - - Log::Comment(NoThrowString().Format( - L"----move into the line to test some other sequences----")); - qExpectedInput.push_back("\x1b[2;8H"); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 7, 1 })); - - Log::Comment(NoThrowString().Format( - L"----move to the start of this line (y stays the same)----")); - qExpectedInput.push_back("\r"); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 0, 1 })); - }); - - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"Sending the same move across paint calls sends nothing." - L"The cursor's last \"real\" position was 0,0")); - qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 0, 1 })); - WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); - - Log::Comment(NoThrowString().Format( - L"Paint some text at 0,0, then try moving the cursor to where it currently is.")); - qExpectedInput.push_back("\x1b[1C"); - qExpectedInput.push_back("asdfghjkl"); - - const auto line = L"asdfghjkl"; - const unsigned char rgWidths[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }; - - std::vector clusters; - for (size_t i = 0; i < wcslen(line); i++) - { - clusters.emplace_back(std::wstring_view{ &line[i], 1 }, static_cast(rgWidths[i])); - } - - VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters.data(), clusters.size() }, { 1, 1 }, false, false)); - - qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 10, 1 })); - WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); - }); - - // Note that only PaintBufferLine updates the "Real" cursor position, which - // the cursor is moved back to at the end of each paint - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"Sending the same move across paint calls sends nothing.")); - qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 10, 1 })); - WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); - }); -} - -void VtRendererTest::Xterm256TestExtendedAttributes() -{ - // Run this test for each and every possible combination of states. - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:faint", L"{false, true}") - TEST_METHOD_PROPERTY(L"Data:underlined", L"{false, true}") - TEST_METHOD_PROPERTY(L"Data:doublyUnderlined", L"{false, true}") - TEST_METHOD_PROPERTY(L"Data:italics", L"{false, true}") - TEST_METHOD_PROPERTY(L"Data:blink", L"{false, true}") - TEST_METHOD_PROPERTY(L"Data:invisible", L"{false, true}") - TEST_METHOD_PROPERTY(L"Data:crossedOut", L"{false, true}") - END_TEST_METHOD_PROPERTIES() - - bool faint, underlined, doublyUnderlined, italics, blink, invisible, crossedOut; - VERIFY_SUCCEEDED(TestData::TryGetValue(L"faint", faint)); - VERIFY_SUCCEEDED(TestData::TryGetValue(L"underlined", underlined)); - VERIFY_SUCCEEDED(TestData::TryGetValue(L"doublyUnderlined", doublyUnderlined)); - VERIFY_SUCCEEDED(TestData::TryGetValue(L"italics", italics)); - VERIFY_SUCCEEDED(TestData::TryGetValue(L"blink", blink)); - VERIFY_SUCCEEDED(TestData::TryGetValue(L"invisible", invisible)); - VERIFY_SUCCEEDED(TestData::TryGetValue(L"crossedOut", crossedOut)); - - TextAttribute desiredAttrs; - std::vector onSequences, offSequences; - - // Collect up a VT sequence to set the state given the method properties - if (faint) - { - desiredAttrs.SetFaint(true); - onSequences.push_back("\x1b[2m"); - offSequences.push_back("\x1b[22m"); - } - - // underlined and doublyUnderlined are mutually exclusive - if (underlined) - { - desiredAttrs.SetUnderlineStyle(UnderlineStyle::DashedUnderlined); - onSequences.push_back("\x1b[4:5m"); - offSequences.push_back("\x1b[24m"); - } - else if (doublyUnderlined) - { - desiredAttrs.SetUnderlineStyle(UnderlineStyle::DoublyUnderlined); - onSequences.push_back("\x1b[21m"); - offSequences.push_back("\x1b[24m"); - } - - if (italics) - { - desiredAttrs.SetItalic(true); - onSequences.push_back("\x1b[3m"); - offSequences.push_back("\x1b[23m"); - } - if (blink) - { - desiredAttrs.SetBlinking(true); - onSequences.push_back("\x1b[5m"); - offSequences.push_back("\x1b[25m"); - } - if (invisible) - { - desiredAttrs.SetInvisible(true); - onSequences.push_back("\x1b[8m"); - offSequences.push_back("\x1b[28m"); - } - if (crossedOut) - { - desiredAttrs.SetCrossedOut(true); - onSequences.push_back("\x1b[9m"); - offSequences.push_back("\x1b[29m"); - } - - auto hFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - auto engine = std::make_unique(std::move(hFile), SetUpViewport()); - auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2); - engine->SetTestCallback(pfn); - - VerifyFirstPaint(*engine); - - auto view = SetUpViewport(); - - Log::Comment(NoThrowString().Format( - L"Test changing the text attributes")); - - Log::Comment(NoThrowString().Format( - L"----Turn the extended attributes on----")); - TestPaint(*engine, [&]() { - // Merge the "on" sequences into expected input. - std::copy(onSequences.cbegin(), onSequences.cend(), std::back_inserter(qExpectedInput)); - VERIFY_SUCCEEDED(engine->_UpdateExtendedAttrs(desiredAttrs)); - }); - - Log::Comment(NoThrowString().Format( - L"----Turn the extended attributes off----")); - TestPaint(*engine, [&]() { - std::copy(offSequences.cbegin(), offSequences.cend(), std::back_inserter(qExpectedInput)); - VERIFY_SUCCEEDED(engine->_UpdateExtendedAttrs({})); - }); - - Log::Comment(NoThrowString().Format( - L"----Turn the extended attributes back on----")); - TestPaint(*engine, [&]() { - std::copy(onSequences.cbegin(), onSequences.cend(), std::back_inserter(qExpectedInput)); - VERIFY_SUCCEEDED(engine->_UpdateExtendedAttrs(desiredAttrs)); - }); - - VerifyExpectedInputsDrained(); -} - -void VtRendererTest::Xterm256TestAttributesAcrossReset() -{ - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:renditionAttribute", L"{1, 2, 3, 4, 5, 7, 8, 9, 21, 53}") - END_TEST_METHOD_PROPERTIES() - - int renditionAttribute; - VERIFY_SUCCEEDED(TestData::TryGetValue(L"renditionAttribute", renditionAttribute)); - - std::stringstream renditionSequence; - // test underline with curly underlined - if (renditionAttribute == 4) - { - renditionSequence << "\x1b[4:3m"; - } - else - { - renditionSequence << "\x1b[" << renditionAttribute << "m"; - } - - auto hFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - auto engine = std::make_unique(std::move(hFile), SetUpViewport()); - auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2); - engine->SetTestCallback(pfn); - RenderSettings renderSettings; - RenderData renderData; - - Log::Comment(L"Make sure rendition attributes are retained when colors are reset"); - - Log::Comment(L"----Start With All Attributes Reset----"); - TextAttribute textAttributes = {}; - qExpectedInput.push_back("\x1b[m"); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, renderSettings, &renderData, false, false)); - - switch (renditionAttribute) - { - case GraphicsOptions::Intense: - Log::Comment(L"----Set Intense Attribute----"); - textAttributes.SetIntense(true); - break; - case GraphicsOptions::RGBColorOrFaint: - Log::Comment(L"----Set Faint Attribute----"); - textAttributes.SetFaint(true); - break; - case GraphicsOptions::Italics: - Log::Comment(L"----Set Italics Attribute----"); - textAttributes.SetItalic(true); - break; - case GraphicsOptions::Underline: - Log::Comment(L"----Set Underline Attribute----"); - textAttributes.SetUnderlineStyle(UnderlineStyle::CurlyUnderlined); - break; - case GraphicsOptions::DoublyUnderlined: - Log::Comment(L"----Set Doubly Underlined Attribute----"); - textAttributes.SetUnderlineStyle(UnderlineStyle::DoublyUnderlined); - break; - case GraphicsOptions::Overline: - Log::Comment(L"----Set Overline Attribute----"); - textAttributes.SetOverlined(true); - break; - case GraphicsOptions::BlinkOrXterm256Index: - Log::Comment(L"----Set Blink Attribute----"); - textAttributes.SetBlinking(true); - break; - case GraphicsOptions::Negative: - Log::Comment(L"----Set Negative Attribute----"); - textAttributes.SetReverseVideo(true); - break; - case GraphicsOptions::Invisible: - Log::Comment(L"----Set Invisible Attribute----"); - textAttributes.SetInvisible(true); - break; - case GraphicsOptions::CrossedOut: - Log::Comment(L"----Set Crossed Out Attribute----"); - textAttributes.SetCrossedOut(true); - break; - } - qExpectedInput.push_back(renditionSequence.str()); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, renderSettings, &renderData, false, false)); - - Log::Comment(L"----Set Green Foreground----"); - textAttributes.SetIndexedForeground(TextColor::DARK_GREEN); - qExpectedInput.push_back("\x1b[32m"); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, renderSettings, &renderData, false, false)); - - Log::Comment(L"----Reset Default Foreground and Retain Rendition----"); - textAttributes.SetDefaultForeground(); - qExpectedInput.push_back("\x1b[m"); - qExpectedInput.push_back(renditionSequence.str()); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, renderSettings, &renderData, false, false)); - - Log::Comment(L"----Set Green Background----"); - textAttributes.SetIndexedBackground(TextColor::DARK_GREEN); - qExpectedInput.push_back("\x1b[42m"); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, renderSettings, &renderData, false, false)); - - Log::Comment(L"----Reset Default Background and Retain Rendition----"); - textAttributes.SetDefaultBackground(); - qExpectedInput.push_back("\x1b[m"); - qExpectedInput.push_back(renditionSequence.str()); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, renderSettings, &renderData, false, false)); - - VerifyExpectedInputsDrained(); -} - -void VtRendererTest::Xterm256TestDoublyUnderlinedResetBeforeSettingStyle() -{ - auto hFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - auto engine = std::make_unique(std::move(hFile), SetUpViewport()); - auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2); - engine->SetTestCallback(pfn); - - VerifyFirstPaint(*engine); - - auto attrs = TextAttribute{}; - - Log::Comment(NoThrowString().Format( - L"----Testing Doubly underlined is properly reset before applying the new underline style----")); - - Log::Comment(NoThrowString().Format( - L"----Set Doubly Underlined----")); - TestPaint(*engine, [&]() { - attrs.SetUnderlineStyle(UnderlineStyle::DoublyUnderlined); - qExpectedInput.push_back("\x1b[21m"); - VERIFY_SUCCEEDED(engine->_UpdateExtendedAttrs(attrs)); - }); - - Log::Comment(NoThrowString().Format( - L"----Set Underline To Any Other Style----")); - TestPaint(*engine, [&]() { - attrs.SetUnderlineStyle(UnderlineStyle::CurlyUnderlined); - qExpectedInput.push_back("\x1b[24m"); - qExpectedInput.push_back("\x1b[4:3m"); - VERIFY_SUCCEEDED(engine->_UpdateExtendedAttrs(attrs)); - }); - - Log::Comment(NoThrowString().Format( - L"----Remove The Underline----")); - TestPaint(*engine, [&]() { - attrs.SetUnderlineStyle(UnderlineStyle::NoUnderline); - qExpectedInput.push_back("\x1b[24m"); - VERIFY_SUCCEEDED(engine->_UpdateExtendedAttrs(attrs)); - }); - - VerifyExpectedInputsDrained(); -} - -void VtRendererTest::XtermTestInvalidate() -{ - auto hFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - auto engine = std::make_unique(std::move(hFile), SetUpViewport(), false); - auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2); - engine->SetTestCallback(pfn); - - VerifyFirstPaint(*engine); - - auto view = SetUpViewport(); - - Log::Comment(NoThrowString().Format( - L"Make sure that invalidating all invalidates the whole viewport.")); - VERIFY_SUCCEEDED(engine->InvalidateAll()); - TestPaint(*engine, [&]() { - VERIFY_IS_TRUE(engine->_invalidMap.all()); - }); - - Log::Comment(NoThrowString().Format( - L"Make sure that invalidating anything only invalidates that portion")); - til::rect invalid = { 1, 1, 2, 2 }; - VERIFY_SUCCEEDED(engine->Invalidate(&invalid)); - TestPaint(*engine, [&]() { - VERIFY_IS_TRUE(engine->_invalidMap.one()); - VERIFY_ARE_EQUAL(invalid, *(engine->_invalidMap.begin())); - }); - - Log::Comment(NoThrowString().Format( - L"Make sure that scrolling only invalidates part of the viewport, and sends the right sequences")); - til::point scrollDelta = { 0, 1 }; - VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta)); - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"---- Scrolled one down, only top line is invalid. ----")); - invalid = view.ToExclusive(); - invalid.bottom = 1; - - const auto runs = engine->_invalidMap.runs(); - VERIFY_ARE_EQUAL(1u, runs.size()); - VERIFY_ARE_EQUAL(invalid, runs.front()); - - qExpectedInput.push_back("\x1b[H"); // Go Home - qExpectedInput.push_back("\x1b[L"); // insert a line - VERIFY_SUCCEEDED(engine->ScrollFrame()); - }); - - scrollDelta = { 0, 3 }; - VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta)); - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"---- Scrolled three down, only top 3 lines are invalid. ----")); - invalid = view.ToExclusive(); - invalid.bottom = 3; - - // we should have 3 runs and build a rectangle out of them - const auto runs = engine->_invalidMap.runs(); - VERIFY_ARE_EQUAL(3u, runs.size()); - auto invalidRect = runs.front(); - for (size_t i = 1; i < runs.size(); ++i) - { - invalidRect |= runs[i]; - } - - // verify the rect matches the invalid one. - VERIFY_ARE_EQUAL(invalid, invalidRect); - // We would expect a CUP here, but the cursor is already at the home position - qExpectedInput.push_back("\x1b[3L"); // insert 3 lines - VERIFY_SUCCEEDED(engine->ScrollFrame()); - }); - - scrollDelta = { 0, -1 }; - VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta)); - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"---- Scrolled one up, only bottom line is invalid. ----")); - invalid = view.ToExclusive(); - invalid.top = invalid.bottom - 1; - - const auto runs = engine->_invalidMap.runs(); - VERIFY_ARE_EQUAL(1u, runs.size()); - VERIFY_ARE_EQUAL(invalid, runs.front()); - - qExpectedInput.push_back("\x1b[32;1H"); // Bottom of buffer - qExpectedInput.push_back("\n"); // Scroll down once - VERIFY_SUCCEEDED(engine->ScrollFrame()); - }); - - scrollDelta = { 0, -3 }; - VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta)); - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"---- Scrolled three up, only bottom 3 lines are invalid. ----")); - invalid = view.ToExclusive(); - invalid.top = invalid.bottom - 3; - - // we should have 3 runs and build a rectangle out of them - const auto runs = engine->_invalidMap.runs(); - VERIFY_ARE_EQUAL(3u, runs.size()); - auto invalidRect = runs.front(); - for (size_t i = 1; i < runs.size(); ++i) - { - invalidRect |= runs[i]; - } - - // verify the rect matches the invalid one. - VERIFY_ARE_EQUAL(invalid, invalidRect); - - // We would expect a CUP here, but we're already at the bottom from the last call. - qExpectedInput.push_back("\n\n\n"); // Scroll down three times - VERIFY_SUCCEEDED(engine->ScrollFrame()); - }); - - Log::Comment(NoThrowString().Format( - L"Multiple scrolls are coalesced")); - - scrollDelta = { 0, 1 }; - VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta)); - scrollDelta = { 0, 2 }; - VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta)); - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"---- Scrolled three down, only top 3 lines are invalid. ----")); - invalid = view.ToExclusive(); - invalid.bottom = 3; - - // we should have 3 runs and build a rectangle out of them - const auto runs = engine->_invalidMap.runs(); - VERIFY_ARE_EQUAL(3u, runs.size()); - auto invalidRect = runs.front(); - for (size_t i = 1; i < runs.size(); ++i) - { - invalidRect |= runs[i]; - } - - // verify the rect matches the invalid one. - VERIFY_ARE_EQUAL(invalid, invalidRect); - - qExpectedInput.push_back("\x1b[H"); // Go to home - qExpectedInput.push_back("\x1b[3L"); // insert 3 lines - VERIFY_SUCCEEDED(engine->ScrollFrame()); - }); - - scrollDelta = { 0, 1 }; - VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta)); - Log::Comment(engine->_invalidMap.to_string().c_str()); - - scrollDelta = { 0, -1 }; - VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta)); - Log::Comment(engine->_invalidMap.to_string().c_str()); - - qExpectedInput.push_back("\x1b[2J"); - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"---- Scrolled one down and one up, nothing should change ----" - L" But it still does for now MSFT:14169294")); - - const auto runs = engine->_invalidMap.runs(); - auto invalidRect = runs.front(); - for (size_t i = 1; i < runs.size(); ++i) - { - invalidRect |= runs[i]; - } - - // only the bottom line should be dirty. - // When we scrolled down, the bitmap looked like this: - // 1111 - // 0000 - // 0000 - // 0000 - // And then we scrolled up and the top line fell off and a bottom - // line was filled in like this: - // 0000 - // 0000 - // 0000 - // 1111 - const til::rect expected{ til::point{ view.Left(), view.BottomInclusive() }, til::size{ view.Width(), 1 } }; - VERIFY_ARE_EQUAL(expected, invalidRect); - - VERIFY_SUCCEEDED(engine->ScrollFrame()); - }); -} - -void VtRendererTest::XtermTestColors() -{ - auto hFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - auto engine = std::make_unique(std::move(hFile), SetUpViewport(), false); - auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2); - engine->SetTestCallback(pfn); - RenderSettings renderSettings; - RenderData renderData; - - VerifyFirstPaint(*engine); - - auto view = SetUpViewport(); - - Log::Comment(NoThrowString().Format( - L"Test changing the text attributes")); - - Log::Comment(NoThrowString().Format( - L"Begin by setting the default colors")); - - qExpectedInput.push_back("\x1b[m"); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes({}, - renderSettings, - &renderData, - false, - false)); - - TestPaint(*engine, [&]() { - TextAttribute textAttributes; - - Log::Comment(NoThrowString().Format( - L"----Change only the BG----")); - textAttributes.SetIndexedBackground(TextColor::DARK_RED); - qExpectedInput.push_back("\x1b[41m"); // Background DARK_RED - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - - Log::Comment(NoThrowString().Format( - L"----Change only the FG----")); - textAttributes.SetIndexedForeground(TextColor::DARK_WHITE); - qExpectedInput.push_back("\x1b[37m"); // Foreground DARK_WHITE - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - - Log::Comment(NoThrowString().Format( - L"----Change only the BG to an RGB value----")); - textAttributes.SetBackground(RGB(19, 161, 14)); - qExpectedInput.push_back("\x1b[42m"); // Background DARK_GREEN - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - - Log::Comment(NoThrowString().Format( - L"----Change only the FG to an RGB value----")); - textAttributes.SetForeground(RGB(193, 156, 0)); - qExpectedInput.push_back("\x1b[33m"); // Foreground DARK_YELLOW - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - - Log::Comment(NoThrowString().Format( - L"----Change only the BG to the 'Default' background----")); - textAttributes.SetDefaultBackground(); - qExpectedInput.push_back("\x1b[m"); // Both foreground and background default - qExpectedInput.push_back("\x1b[33m"); // Reapply foreground DARK_YELLOW - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - - Log::Comment(NoThrowString().Format( - L"----Change only the FG to a 256-color index----")); - textAttributes.SetIndexedForeground256(TextColor::DARK_WHITE); - qExpectedInput.push_back("\x1b[37m"); // Foreground DARK_WHITE - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - - Log::Comment(NoThrowString().Format( - L"----Change only the BG to a 256-color index----")); - textAttributes.SetIndexedBackground256(TextColor::DARK_RED); - qExpectedInput.push_back("\x1b[41m"); // Background DARK_RED - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - - Log::Comment(NoThrowString().Format( - L"----Change only the FG to the 'Default' foreground----")); - textAttributes.SetDefaultForeground(); - qExpectedInput.push_back("\x1b[m"); // Both foreground and background default - qExpectedInput.push_back("\x1b[41m"); // Reapply background DARK_RED - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - - Log::Comment(NoThrowString().Format( - L"----Back to defaults----")); - textAttributes = {}; - qExpectedInput.push_back("\x1b[m"); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, - renderSettings, - &renderData, - false, - false)); - }); - - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"Make sure that color setting persists across EndPaint/StartPaint")); - qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes({}, - renderSettings, - &renderData, - false, - false)); - WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); // This will make sure nothing was written to the callback - }); -} - -void VtRendererTest::XtermTestCursor() -{ - auto hFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - auto engine = std::make_unique(std::move(hFile), SetUpViewport(), false); - auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2); - engine->SetTestCallback(pfn); - - VerifyFirstPaint(*engine); - - auto view = SetUpViewport(); - - Log::Comment(NoThrowString().Format( - L"Test moving the cursor around. Every sequence should have both params to CUP explicitly.")); - TestPaint(*engine, [&]() { - qExpectedInput.push_back("\x1b[2;2H"); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 1, 1 })); - - Log::Comment(NoThrowString().Format( - L"----Only move Y coord----")); - qExpectedInput.push_back("\x1b[31;2H"); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 1, 30 })); - - Log::Comment(NoThrowString().Format( - L"----Only move X coord----")); - qExpectedInput.push_back("\x1b[29C"); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 30, 30 })); - - Log::Comment(NoThrowString().Format( - L"----Sending the same move sends nothing----")); - qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 30, 30 })); - WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); - - Log::Comment(NoThrowString().Format( - L"----moving home sends a simple sequence----")); - qExpectedInput.push_back("\x1b[H"); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 0, 0 })); - - Log::Comment(NoThrowString().Format( - L"----move into the line to test some other sequences----")); - qExpectedInput.push_back("\x1b[7C"); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 7, 0 })); - - Log::Comment(NoThrowString().Format( - L"----move down one line (x stays the same)----")); - qExpectedInput.push_back("\n"); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 7, 1 })); - - Log::Comment(NoThrowString().Format( - L"----move to the start of the next line----")); - qExpectedInput.push_back("\r\n"); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 0, 2 })); - - Log::Comment(NoThrowString().Format( - L"----move into the line to test some other sequences----")); - qExpectedInput.push_back("\x1b[2;8H"); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 7, 1 })); - - Log::Comment(NoThrowString().Format( - L"----move to the start of this line (y stays the same)----")); - qExpectedInput.push_back("\r"); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 0, 1 })); - }); - - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"Sending the same move across paint calls sends nothing." - L"The cursor's last \"real\" position was 0,0")); - qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 0, 1 })); - WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); - - Log::Comment(NoThrowString().Format( - L"Paint some text at 0,0, then try moving the cursor to where it currently is.")); - qExpectedInput.push_back("\x1b[1C"); - qExpectedInput.push_back("asdfghjkl"); - - const auto line = L"asdfghjkl"; - const unsigned char rgWidths[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }; - - std::vector clusters; - for (size_t i = 0; i < wcslen(line); i++) - { - clusters.emplace_back(std::wstring_view{ &line[i], 1 }, static_cast(rgWidths[i])); - } - - VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters.data(), clusters.size() }, { 1, 1 }, false, false)); - - qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 10, 1 })); - WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); - }); - - // Note that only PaintBufferLine updates the "Real" cursor position, which - // the cursor is moved back to at the end of each paint - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"Sending the same move across paint calls sends nothing.")); - qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 10, 1 })); - WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); - }); -} - -void VtRendererTest::XtermTestAttributesAcrossReset() -{ - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:renditionAttribute", L"{1, 4, 7}") - END_TEST_METHOD_PROPERTIES() - - int renditionAttribute; - VERIFY_SUCCEEDED(TestData::TryGetValue(L"renditionAttribute", renditionAttribute)); - - std::stringstream renditionSequence; - renditionSequence << "\x1b[" << renditionAttribute << "m"; - - auto hFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - auto engine = std::make_unique(std::move(hFile), SetUpViewport(), false); - auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2); - engine->SetTestCallback(pfn); - RenderSettings renderSettings; - RenderData renderData; - - Log::Comment(L"Make sure rendition attributes are retained when colors are reset"); - - Log::Comment(L"----Start With All Attributes Reset----"); - TextAttribute textAttributes = {}; - qExpectedInput.push_back("\x1b[m"); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, renderSettings, &renderData, false, false)); - - switch (renditionAttribute) - { - case GraphicsOptions::Intense: - Log::Comment(L"----Set Intense Attribute----"); - textAttributes.SetIntense(true); - break; - case GraphicsOptions::Underline: - Log::Comment(L"----Set Underline Attribute----"); - textAttributes.SetUnderlineStyle(UnderlineStyle::SinglyUnderlined); - break; - case GraphicsOptions::Negative: - Log::Comment(L"----Set Negative Attribute----"); - textAttributes.SetReverseVideo(true); - break; - } - qExpectedInput.push_back(renditionSequence.str()); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, renderSettings, &renderData, false, false)); - - Log::Comment(L"----Set Green Foreground----"); - textAttributes.SetIndexedForeground(TextColor::DARK_GREEN); - qExpectedInput.push_back("\x1b[32m"); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, renderSettings, &renderData, false, false)); - - Log::Comment(L"----Reset Default Foreground and Retain Rendition----"); - textAttributes.SetDefaultForeground(); - qExpectedInput.push_back("\x1b[m"); - qExpectedInput.push_back(renditionSequence.str()); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, renderSettings, &renderData, false, false)); - - Log::Comment(L"----Set Green Background----"); - textAttributes.SetIndexedBackground(TextColor::DARK_GREEN); - qExpectedInput.push_back("\x1b[42m"); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, renderSettings, &renderData, false, false)); - - Log::Comment(L"----Reset Default Background and Retain Rendition----"); - textAttributes.SetDefaultBackground(); - qExpectedInput.push_back("\x1b[m"); - qExpectedInput.push_back(renditionSequence.str()); - VERIFY_SUCCEEDED(engine->UpdateDrawingBrushes(textAttributes, renderSettings, &renderData, false, false)); - - VerifyExpectedInputsDrained(); -} - -void VtRendererTest::TestWrapping() -{ - auto hFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - auto engine = std::make_unique(std::move(hFile), SetUpViewport()); - auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2); - engine->SetTestCallback(pfn); - - VerifyFirstPaint(*engine); - - auto view = SetUpViewport(); - - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"Make sure the cursor is at 0,0")); - qExpectedInput.push_back("\x1b[H"); - VERIFY_SUCCEEDED(engine->_MoveCursor({ 0, 0 })); - }); - - TestPaint(*engine, [&]() { - Log::Comment(NoThrowString().Format( - L"Painting a line that wrapped, then painting another line, and " - L"making sure we don't manually move the cursor between those paints.")); - qExpectedInput.push_back("asdfghjkl"); - // TODO: Undoing this behavior due to 18123777. Will come back in MSFT:16485846 - qExpectedInput.push_back("\r\n"); - qExpectedInput.push_back("zxcvbnm,."); - - const auto line1 = L"asdfghjkl"; - const auto line2 = L"zxcvbnm,."; - const unsigned char rgWidths[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1 }; - - std::vector clusters1; - for (size_t i = 0; i < wcslen(line1); i++) - { - clusters1.emplace_back(std::wstring_view{ &line1[i], 1 }, static_cast(rgWidths[i])); - } - std::vector clusters2; - for (size_t i = 0; i < wcslen(line2); i++) - { - clusters2.emplace_back(std::wstring_view{ &line2[i], 1 }, static_cast(rgWidths[i])); - } - - VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters1.data(), clusters1.size() }, { 0, 0 }, false, false)); - VERIFY_SUCCEEDED(engine->PaintBufferLine({ clusters2.data(), clusters2.size() }, { 0, 1 }, false, false)); - }); -} - -void VtRendererTest::TestResize() -{ - auto view = SetUpViewport(); - auto hFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - auto engine = std::make_unique(std::move(hFile), view); - auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2); - engine->SetTestCallback(pfn); - - // Verify the first BeginPaint emits a clear and go home - qExpectedInput.push_back("\x1b[2J"); - // Verify the first EndPaint sets the cursor state - qExpectedInput.push_back("\x1b[?25l"); - VERIFY_IS_TRUE(engine->_firstPaint); - VERIFY_IS_TRUE(engine->_suppressResizeRepaint); - - // The renderer (in Renderer@_PaintFrameForEngine..._CheckViewportAndScroll) - // will manually call UpdateViewport once before actually painting the - // first frame. Replicate that behavior here - VERIFY_SUCCEEDED(engine->UpdateViewport(view.ToInclusive())); - - TestPaint(*engine, [&]() { - VERIFY_IS_FALSE(engine->_firstPaint); - VERIFY_IS_FALSE(engine->_suppressResizeRepaint); - }); - - // Resize the viewport to 120x30 - // Everything should be invalidated, and a resize message sent. - const auto newView = Viewport::FromDimensions({ 0, 0 }, { 120, 30 }); - qExpectedInput.push_back("\x1b[8;30;120t"); - - VERIFY_SUCCEEDED(engine->UpdateViewport(newView.ToInclusive())); - - TestPaint(*engine, [&]() { - VERIFY_IS_TRUE(engine->_invalidMap.all()); - VERIFY_IS_FALSE(engine->_firstPaint); - VERIFY_IS_FALSE(engine->_suppressResizeRepaint); - }); -} - -void VtRendererTest::TestCursorVisibility() -{ - auto view = SetUpViewport(); - auto hFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - auto engine = std::make_unique(std::move(hFile), view); - auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2); - engine->SetTestCallback(pfn); - - til::point origin{ 0, 0 }; - - VERIFY_ARE_NOT_EQUAL(origin, engine->_lastText); - - CursorOptions options{}; - options.coordCursor = origin; - - // Frame 1: Paint the cursor at the home position. At the end of the frame, - // the cursor should be on. Because we're moving the cursor with CUP, we - // need to disable the cursor during this frame. - qExpectedInput.push_back("\x1b[2J"); - TestPaint(*engine, [&]() { - VERIFY_IS_FALSE(engine->_nextCursorIsVisible); - VERIFY_IS_FALSE(engine->_needToDisableCursor); - - Log::Comment(NoThrowString().Format(L"Make sure the cursor is at 0,0")); - qExpectedInput.push_back("\x1b[H"); - VERIFY_SUCCEEDED(engine->PaintCursor(options)); - - VERIFY_IS_TRUE(engine->_nextCursorIsVisible); - VERIFY_IS_TRUE(engine->_needToDisableCursor); - - // GH#12401: - // The other tests verify that the cursor is explicitly hidden on the - // first frame (VerifyFirstPaint). This test on the other hand does - // the opposite by calling PaintCursor() during the first paint cycle. - qExpectedInput.push_back("\x1b[?25h"); - }); - - VERIFY_IS_TRUE(engine->_nextCursorIsVisible); - VERIFY_IS_FALSE(engine->_needToDisableCursor); - - // Frame 2: Paint the cursor again at the home position. At the end of the - // frame, the cursor should be on, the same as before. We aren't moving the - // cursor during this frame, so _needToDisableCursor will stay false. - TestPaint(*engine, [&]() { - VERIFY_IS_FALSE(engine->_nextCursorIsVisible); - VERIFY_IS_FALSE(engine->_needToDisableCursor); - - Log::Comment(NoThrowString().Format(L"If we just paint the cursor again at the same position, the cursor should not need to be disabled")); - VERIFY_SUCCEEDED(engine->PaintCursor(options)); - - VERIFY_IS_TRUE(engine->_nextCursorIsVisible); - VERIFY_IS_FALSE(engine->_needToDisableCursor); - }); - - VERIFY_IS_TRUE(engine->_nextCursorIsVisible); - VERIFY_IS_FALSE(engine->_needToDisableCursor); - - // Frame 3: Paint the cursor at 2,2. At the end of the frame, the cursor - // should be on, the same as before. Because we're moving the cursor with - // CUP, we need to disable the cursor during this frame. - TestPaint(*engine, [&]() { - VERIFY_IS_FALSE(engine->_nextCursorIsVisible); - VERIFY_IS_FALSE(engine->_needToDisableCursor); - - Log::Comment(NoThrowString().Format(L"Move the cursor to 2,2")); - qExpectedInput.push_back("\x1b[3;3H"); - - options.coordCursor = { 2, 2 }; - - VERIFY_SUCCEEDED(engine->PaintCursor(options)); - - VERIFY_IS_TRUE(engine->_nextCursorIsVisible); - VERIFY_IS_TRUE(engine->_needToDisableCursor); - - // Because _needToDisableCursor is true, we'll insert a ?25l at the - // start of the frame. Unfortunately, we can't test to make sure that - // it's there, but we can ensure that the matching ?25h is printed: - qExpectedInput.push_back("\x1b[?25h"); - }); - - VERIFY_IS_TRUE(engine->_nextCursorIsVisible); - VERIFY_IS_FALSE(engine->_needToDisableCursor); - - // Frame 4: Don't paint the cursor. At the end of the frame, the cursor - // should be off. - Log::Comment(NoThrowString().Format(L"Painting without calling PaintCursor will hide the cursor")); - TestPaint(*engine, [&]() { - VERIFY_IS_FALSE(engine->_nextCursorIsVisible); - VERIFY_IS_FALSE(engine->_needToDisableCursor); - - qExpectedInput.push_back("\x1b[?25l"); - }); - - VERIFY_IS_FALSE(engine->_nextCursorIsVisible); - VERIFY_IS_FALSE(engine->_needToDisableCursor); -} - -void VtRendererTest::FormattedString() -{ - // This test works with a static cache variable that - // can be affected by other tests - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method") - END_TEST_METHOD_PROPERTIES(); - - static const auto format = FMT_COMPILE("\x1b[{}m"); - const auto value = 12; - - auto view = SetUpViewport(); - auto hFile = wil::unique_hfile(INVALID_HANDLE_VALUE); - auto engine = std::make_unique(std::move(hFile), view); - auto pfn = std::bind(&VtRendererTest::WriteCallback, this, std::placeholders::_1, std::placeholders::_2); - engine->SetTestCallback(pfn); - - Log::Comment(L"1.) Write it once. It should resize itself."); - qExpectedInput.push_back("\x1b[12m"); - VERIFY_SUCCEEDED(engine->_WriteFormatted(format, value)); - - Log::Comment(L"2.) Write the same thing again, should be fine."); - qExpectedInput.push_back("\x1b[12m"); - VERIFY_SUCCEEDED(engine->_WriteFormatted(format, value)); - - Log::Comment(L"3.) Now write something huge. Should resize itself and still be fine."); - static const auto bigFormat = FMT_COMPILE("\x1b[28;3;{};{};{}m"); - const auto bigValue = 500; - qExpectedInput.push_back("\x1b[28;3;500;500;500m"); - VERIFY_SUCCEEDED(engine->_WriteFormatted(bigFormat, bigValue, bigValue, bigValue)); -} diff --git a/src/host/ut_host/sources b/src/host/ut_host/sources index 799bb24a949..bb87492fd19 100644 --- a/src/host/ut_host/sources +++ b/src/host/ut_host/sources @@ -31,8 +31,6 @@ SOURCES = \ TitleTests.cpp \ InputBufferTests.cpp \ VtIoTests.cpp \ - VtRendererTests.cpp \ - ConptyOutputTests.cpp \ ViewportTests.cpp \ ConsoleArgumentsTests.cpp \ ObjectTests.cpp \ diff --git a/src/inc/LibraryIncludes.h b/src/inc/LibraryIncludes.h index 4dc9f6b21e8..41b74ccab93 100644 --- a/src/inc/LibraryIncludes.h +++ b/src/inc/LibraryIncludes.h @@ -82,16 +82,6 @@ #define ENABLE_INTSAFE_SIGNED_FUNCTIONS #include -// LibPopCnt - Fast C/C++ bit population count library (on bits in an array) -#include - -// Dynamic Bitset (optional dependency on LibPopCnt for perf at bit counting) -// Variable-size compressed-storage header-only bit flag storage library. -#pragma warning(push) -#pragma warning(disable:4702) // unreachable code -#include -#pragma warning(pop) - // {fmt}, a C++20-compatible formatting library #include #include diff --git a/src/inc/conpty-static.h b/src/inc/conpty-static.h index d05ab56e8d3..875470b1575 100644 --- a/src/inc/conpty-static.h +++ b/src/inc/conpty-static.h @@ -27,9 +27,6 @@ #ifndef PSEUDOCONSOLE_INHERIT_CURSOR #define PSEUDOCONSOLE_INHERIT_CURSOR (0x1) #endif -#ifndef PSEUDOCONSOLE_RESIZE_QUIRK -#define PSEUDOCONSOLE_RESIZE_QUIRK (0x2) -#endif #ifndef PSEUDOCONSOLE_GLYPH_WIDTH__MASK #define PSEUDOCONSOLE_GLYPH_WIDTH__MASK 0x18 #define PSEUDOCONSOLE_GLYPH_WIDTH_GRAPHEMES 0x08 diff --git a/src/inc/til.h b/src/inc/til.h index eb781634fe2..dd60656a90f 100644 --- a/src/inc/til.h +++ b/src/inc/til.h @@ -15,11 +15,11 @@ #define _TIL_INLINEPREFIX __declspec(noinline) inline #include "til/at.h" -#include "til/bitmap.h" #include "til/coalesce.h" #include "til/color.h" #include "til/enumset.h" #include "til/pmr.h" +#include "til/rect.h" #include "til/string.h" #include "til/type_traits.h" #include "til/u8u16convert.h" diff --git a/src/inc/til/bitmap.h b/src/inc/til/bitmap.h deleted file mode 100644 index 686369ade95..00000000000 --- a/src/inc/til/bitmap.h +++ /dev/null @@ -1,593 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "rect.h" - -#ifdef UNIT_TESTING -class BitmapTests; -#endif - -namespace til // Terminal Implementation Library. Also: "Today I Learned" -{ - namespace details - { - template - class _bitmap_const_iterator - { - public: - using iterator_category = std::input_iterator_tag; - using value_type = const til::rect; - using difference_type = ptrdiff_t; - using pointer = const til::rect*; - using reference = const til::rect&; - - _bitmap_const_iterator(const dynamic_bitset& values, til::rect rc, ptrdiff_t pos) : - _values(values), - _rc(rc), - _pos(pos), - _end(rc.size().area()) - { - _calculateArea(); - } - - _bitmap_const_iterator& operator++() - { - _pos = _nextPos; - _calculateArea(); - return (*this); - } - - _bitmap_const_iterator operator++(int) - { - const auto prev = *this; - ++*this; - return prev; - } - - constexpr bool operator==(const _bitmap_const_iterator& other) const noexcept - { - return _pos == other._pos && _values == other._values; - } - - constexpr bool operator!=(const _bitmap_const_iterator& other) const noexcept - { - return !(*this == other); - } - - constexpr bool operator<(const _bitmap_const_iterator& other) const noexcept - { - return _pos < other._pos; - } - - constexpr bool operator>(const _bitmap_const_iterator& other) const noexcept - { - return _pos > other._pos; - } - - constexpr reference operator*() const noexcept - { - return _run; - } - - constexpr pointer operator->() const noexcept - { - return &_run; - } - - private: - const dynamic_bitset& _values; - const til::rect _rc; - size_t _pos; - size_t _nextPos; - const size_t _end; - til::rect _run; - - // Update _run to contain the next rectangle of consecutively set bits within this bitmap. - // _calculateArea may be called repeatedly to yield all those rectangles. - void _calculateArea() - { - // The following logic first finds the next set bit in this bitmap and the next unset bit past that. - // The area in between those positions are thus all set bits and will end up being the next _run. - - // dynamic_bitset allows you to quickly find the next set bit using find_next(prev), - // where "prev" is the position _past_ which should be searched (i.e. excluding position "prev"). - // If _pos is still 0, we thus need to use the counterpart find_first(). - _nextPos = _pos == 0 ? _values.find_first() : _values.find_next(_pos - 1); - - // If we haven't reached the end yet... - if (_nextPos < _end) - { - // pos is now at the first on bit. - // If no next set bit can be found, npos is returned, which is SIZE_T_MAX. - // saturated_cast can ensure that this will be converted to CoordType's max (which is greater than _end). - const auto runStart = _rc.point_at(base::saturated_cast(_nextPos)); - - // We'll only count up until the end of this row. - // a run can be a max of one row tall. - const size_t rowEndIndex = _rc.index_of(til::point(_rc.right - 1, runStart.y)) + 1; - - // Find the length for the rectangle. - size_t runLength = 0; - - // We have at least 1 so start with a do/while. - do - { - ++_nextPos; - ++runLength; - } while (_nextPos < rowEndIndex && _values[_nextPos]); - // Keep going until we reach end of row, end of the buffer, or the next bit is off. - - // Assemble and store that run. - _run = til::rect{ runStart, til::size{ base::saturated_cast(runLength), 1 } }; - } - else - { - // If we reached the end _nextPos may be >= _end (potentially even PTRDIFF_T_MAX). - // ---> Mark the end of the iterator by updating the state with _end. - _pos = _end; - _nextPos = _end; - _run = til::rect{}; - } - } - }; - - template> - class bitmap - { - public: - using allocator_type = Allocator; - using const_iterator = details::_bitmap_const_iterator; - - private: - using run_allocator_type = typename std::allocator_traits::template rebind_alloc; - - public: - explicit bitmap(const allocator_type& allocator) noexcept : - _alloc{ allocator }, - _sz{}, - _rc{}, - _bits{ _alloc }, - _runs{ _alloc } - { - } - - bitmap() noexcept : - bitmap(allocator_type{}) - { - } - - bitmap(til::size sz) : - bitmap(sz, false, allocator_type{}) - { - } - - bitmap(til::size sz, const allocator_type& allocator) : - bitmap(sz, false, allocator) - { - } - - bitmap(til::size sz, bool fill, const allocator_type& allocator) : - _alloc{ allocator }, - _sz(sz), - _rc(sz), - _bits(_sz.area(), fill ? std::numeric_limits::max() : 0, _alloc), - _runs{ _alloc } - { - } - - bitmap(til::size sz, bool fill) : - bitmap(sz, fill, allocator_type{}) - { - } - - bitmap(const bitmap& other) : - _alloc{ std::allocator_traits::select_on_container_copy_construction(other._alloc) }, - _sz{ other._sz }, - _rc{ other._rc }, - _bits{ other._bits }, - _runs{ other._runs } - { - // copy constructor is required to call select_on_container_copy - } - - bitmap& operator=(const bitmap& other) - { - if constexpr (std::allocator_traits::propagate_on_container_copy_assignment::value) - { - _alloc = other._alloc; - } - _sz = other._sz; - _rc = other._rc; - _bits = other._bits; - _runs = other._runs; - return *this; - } - - bitmap(bitmap&& other) noexcept : - _alloc{ std::move(other._alloc) }, - _sz{ std::move(other._sz) }, - _rc{ std::move(other._rc) }, - _bits{ std::move(other._bits) }, - _runs{ std::move(other._runs) } - { - } - - bitmap& operator=(bitmap&& other) noexcept - { - if constexpr (std::allocator_traits::propagate_on_container_move_assignment::value) - { - _alloc = std::move(other._alloc); - } - _bits = std::move(other._bits); - _runs = std::move(other._runs); - _sz = std::move(other._sz); - _rc = std::move(other._rc); - return *this; - } - - ~bitmap() {} - - void swap(bitmap& other) - { - if constexpr (std::allocator_traits::propagate_on_container_swap::value) - { - std::swap(_alloc, other._alloc); - } - std::swap(_bits, other._bits); - std::swap(_runs, other._runs); - std::swap(_sz, other._sz); - std::swap(_rc, other._rc); - } - - constexpr bool operator==(const bitmap& other) const noexcept - { - return _sz == other._sz && - _rc == other._rc && - _bits == other._bits; - // _runs excluded because it's a cache of generated state. - } - - constexpr bool operator!=(const bitmap& other) const noexcept - { - return !(*this == other); - } - - const_iterator begin() const - { - return const_iterator(_bits, til::rect{ _sz }, 0); - } - - const_iterator end() const - { - return const_iterator(_bits, til::rect{ _sz }, _sz.area()); - } - - const std::span runs() const - { - // If we don't have cached runs, rebuild. - if (!_runs.has_value()) - { - _runs.emplace(begin(), end()); - } - - // Return the runs. - return _runs.value(); - } - - // optional fill the uncovered area with bits. - void translate(const til::point delta, bool fill = false) - { - if (delta.x == 0) - { - // fast path by using bit shifting - translate_y(delta.y, fill); - return; - } - - // FUTURE: PERF: GH #4015: This could use in-place walk semantics instead of a temporary. - bitmap other{ _sz, _alloc }; - - for (auto run : *this) - { - // Offset by the delta - run += delta; - - // Intersect with the bounds of our bitmap area - // as part of it could have slid out of bounds. - run &= _rc; - - // Set it into the new bitmap. - other.set(run); - } - - // If we were asked to fill... find the uncovered region. - if (fill) - { - // Original Rect of As. - // - // X <-- origin - // A A A A - // A A A A - // A A A A - // A A A A - const auto originalRect = _rc; - - // If Delta = (2, 2) - // Translated Rect of Bs. - // - // X <-- origin - // - // - // B B B B - // B B B B - // B B B B - // B B B B - const auto translatedRect = _rc + delta; - - // Subtract the B from the A one to see what wasn't filled by the move. - // C is the overlap of A and B: - // - // X <-- origin - // A A A A 1 1 1 1 - // A A A A 1 1 1 1 - // A A C C B B subtract 2 2 - // A A C C B B ---------> 2 2 - // B B B B A - B - // B B B B - // - // 1 and 2 are the spaces to fill that are "uncovered". - const auto fillRects = originalRect - translatedRect; - for (const auto& f : fillRects) - { - other.set(f); - } - } - - // Swap us with the temporary one. - std::swap(other, *this); - } - - void set(const til::point pt) - { - if (_rc.contains(pt)) - { - _runs.reset(); // reset cached runs on any non-const method - _bits.set(_rc.index_of(pt)); - } - } - - void set(til::rect rc) - { - _runs.reset(); // reset cached runs on any non-const method - - rc &= _rc; - - const auto width = rc.width(); - const auto stride = _rc.width(); - auto idx = _rc.index_of({ rc.left, rc.top }); - - for (auto row = rc.top; row < rc.bottom; ++row, idx += stride) - { - _bits.set(idx, width, true); - } - } - - void set_all() noexcept - { - _runs.reset(); // reset cached runs on any non-const method - _bits.set(); - } - - void reset_all() noexcept - { - _runs.reset(); // reset cached runs on any non-const method - _bits.reset(); - } - - // True if we resized. False if it was the same size as before. - // Set fill if you want the new region (on growing) to be marked dirty. - bool resize(til::size size, bool fill = false) - { - _runs.reset(); // reset cached runs on any non-const method - - // Don't resize if it's not different - if (_sz != size) - { - // Make a new bitmap for the other side, empty initially. - bitmap newMap{ size, false, _alloc }; - - // Copy any regions that overlap from this map to the new one. - // Just iterate our runs... - for (const auto& run : *this) - { - // intersect them with the new map - // so we don't attempt to set bits that fit outside - // the new one. - const auto intersect = run & newMap._rc; - - // and if there is still anything left, set them. - if (!intersect.empty()) - { - newMap.set(intersect); - } - } - - // Then, if we were requested to fill the new space on growing, - // find the space in the new rectangle that wasn't in the old - // and fill it up. - if (fill) - { - // A subtraction will yield anything in the new that isn't - // a part of the old. - const auto newAreas = newMap._rc - _rc; - for (const auto& area : newAreas) - { - newMap.set(area); - } - } - - // Swap and return. - std::swap(newMap, *this); - - return true; - } - else - { - return false; - } - } - - constexpr bool one() const noexcept - { - return _bits.count() == 1; - } - - constexpr bool any() const noexcept - { - return !none(); - } - - constexpr bool none() const noexcept - { - return _bits.none(); - } - - constexpr bool all() const noexcept - { - return _bits.all(); - } - - constexpr til::size size() const noexcept - { - return _sz; - } - - std::wstring to_string() const - { - auto str = fmt::format(FMT_COMPILE(L"Bitmap of size {} contains the following dirty regions:\nRuns:"), _sz.to_string()); - for (auto& item : *this) - { - fmt::format_to(std::back_inserter(str), FMT_COMPILE(L"\n\t- {}"), item.to_string()); - } - return str; - } - - private: - void translate_y(ptrdiff_t delta_y, bool fill) - { - if (delta_y == 0) - { - return; - } - - const auto bitShift = delta_y * _sz.width; - -#pragma warning(push) - // we can't depend on GSL here, so we use static_cast for explicit narrowing -#pragma warning(disable : 26472) - const auto newBits = static_cast(std::abs(bitShift)); -#pragma warning(pop) - const bool isLeftShift = bitShift > 0; - - if (newBits >= _bits.size()) - { - if (fill) - { - set_all(); - } - else - { - reset_all(); - } - return; - } - - if (isLeftShift) - { - // This operator doesn't modify the size of `_bits`: the - // new bits are set to 0. - _bits <<= newBits; - } - else - { - _bits >>= newBits; - } - - if (fill) - { - if (isLeftShift) - { - _bits.set(0, newBits, true); - } - else - { - _bits.set(_bits.size() - newBits, newBits, true); - } - } - - _runs.reset(); // reset cached runs on any non-const method - } - - allocator_type _alloc; - til::size _sz; - til::rect _rc; - dynamic_bitset _bits; - - mutable std::optional> _runs; - -#ifdef UNIT_TESTING - friend class ::BitmapTests; -#endif - }; - - } - - using bitmap = ::til::details::bitmap<>; - - namespace pmr - { - using bitmap = ::til::details::bitmap>; - } -} - -#ifdef __WEX_COMMON_H__ -namespace WEX::TestExecution -{ - template - class VerifyOutputTraits<::til::details::bitmap> - { - public: - static WEX::Common::NoThrowString ToString(const ::til::details::bitmap& rect) - { - return WEX::Common::NoThrowString(rect.to_string().c_str()); - } - }; - - template - class VerifyCompareTraits<::til::details::bitmap, ::til::details::bitmap> - { - public: - static bool AreEqual(const ::til::details::bitmap& expected, const ::til::details::bitmap& actual) noexcept - { - return expected == actual; - } - - static bool AreSame(const ::til::details::bitmap& expected, const ::til::details::bitmap& actual) noexcept - { - return &expected == &actual; - } - - static bool IsLessThan(const ::til::details::bitmap& expectedLess, const ::til::details::bitmap& expectedGreater) = delete; - - static bool IsGreaterThan(const ::til::details::bitmap& expectedGreater, const ::til::details::bitmap& expectedLess) = delete; - - static bool IsNull(const ::til::details::bitmap& object) noexcept - { - return object == til::details::bitmap{}; - } - }; - -}; -#endif diff --git a/src/interactivity/base/InteractivityFactory.cpp b/src/interactivity/base/InteractivityFactory.cpp index fe8fc55cd8c..d13f915ff59 100644 --- a/src/interactivity/base/InteractivityFactory.cpp +++ b/src/interactivity/base/InteractivityFactory.cpp @@ -497,9 +497,12 @@ void InteractivityFactory::_WritePseudoWindowCallback(bool showOrHide) // this message, if it's already minimized. If the window is maximized a // restore will restore-down the window instead. auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (const auto io = gci.GetVtIo()) + if (auto writer = gci.GetVtWriter()) { - io->SetWindowVisibility(showOrHide); + char buf[] = "\x1b[1t"; + buf[2] = showOrHide ? '1' : '2'; + writer.WriteUTF8(buf); + writer.Submit(); } } diff --git a/src/interactivity/base/ServiceLocator.cpp b/src/interactivity/base/ServiceLocator.cpp index 59000001622..f73182e8a58 100644 --- a/src/interactivity/base/ServiceLocator.cpp +++ b/src/interactivity/base/ServiceLocator.cpp @@ -67,15 +67,6 @@ void ServiceLocator::RundownAndExit(const HRESULT hr) Sleep(INFINITE); } - // MSFT:15506250 - // In VT I/O Mode, a client application might die before we've rendered - // the last bit of text they've emitted. So give the VtRenderer one - // last chance to paint before it is killed. - if (s_globals.pRender) - { - s_globals.pRender->TriggerTeardown(); - } - // MSFT:40226902 - HOTFIX shutdown on OneCore, by leaking the renderer, thereby // reducing the change for existing race conditions to turn into deadlocks. #ifndef NDEBUG diff --git a/src/interactivity/onecore/BgfxEngine.cpp b/src/interactivity/onecore/BgfxEngine.cpp index d508603d5f9..5d08e320ea5 100644 --- a/src/interactivity/onecore/BgfxEngine.cpp +++ b/src/interactivity/onecore/BgfxEngine.cpp @@ -63,12 +63,6 @@ BgfxEngine::BgfxEngine(PVOID SharedViewBase, LONG DisplayHeight, LONG DisplayWid return S_OK; } -[[nodiscard]] HRESULT BgfxEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept -{ - *pForcePaint = false; - return S_FALSE; -} - [[nodiscard]] HRESULT BgfxEngine::StartPaint() noexcept { return S_OK; diff --git a/src/interactivity/onecore/BgfxEngine.hpp b/src/interactivity/onecore/BgfxEngine.hpp index e9759ce0306..1d72dd0aaf1 100644 --- a/src/interactivity/onecore/BgfxEngine.hpp +++ b/src/interactivity/onecore/BgfxEngine.hpp @@ -38,7 +38,6 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT InvalidateSelection(const std::vector& rectangles) noexcept override; [[nodiscard]] HRESULT InvalidateScroll(const til::point* pcoordDelta) noexcept override; [[nodiscard]] HRESULT InvalidateAll() noexcept override; - [[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override; [[nodiscard]] HRESULT StartPaint() noexcept override; [[nodiscard]] HRESULT EndPaint() noexcept override; diff --git a/src/interactivity/win32/ut_interactivity_win32/Interactivity.Win32.UnitTests.vcxproj b/src/interactivity/win32/ut_interactivity_win32/Interactivity.Win32.UnitTests.vcxproj index 9f354048194..c610884be63 100644 --- a/src/interactivity/win32/ut_interactivity_win32/Interactivity.Win32.UnitTests.vcxproj +++ b/src/interactivity/win32/ut_interactivity_win32/Interactivity.Win32.UnitTests.vcxproj @@ -26,9 +26,6 @@ {8222900C-8B6C-452A-91AC-BE95DB04B95F} - - {990f2657-8580-4828-943f-5dd657d11842} - {18d09a24-8240-42d6-8cb6-236eee820263} diff --git a/src/interactivity/win32/windowio.cpp b/src/interactivity/win32/windowio.cpp index 261ed4a80c8..5afd66b9ce2 100644 --- a/src/interactivity/win32/windowio.cpp +++ b/src/interactivity/win32/windowio.cpp @@ -935,7 +935,11 @@ DWORD WINAPI ConsoleInputThreadProcWin32(LPVOID /*lpParameter*/) // VtIo's CreatePseudoWindow, which will make sure that the window is // successfully created with the owner configured when the window is // first created. See GH#13066 for details. - ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()->CreatePseudoWindow(); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + if (gci.IsInVtIoMode()) + { + gci.GetVtIo()->CreatePseudoWindow(); + } // Register the pseudoconsole window as being owned by the root process. const auto pseudoWindow = ServiceLocator::LocatePseudoWindow(); diff --git a/src/project.inc b/src/project.inc index 0b7115efcf0..1d86343d9c6 100644 --- a/src/project.inc +++ b/src/project.inc @@ -49,8 +49,6 @@ INCLUDES= \ $(INCLUDES); \ $(CONSOLE_SRC_PATH)\inc; \ $(CONSOLE_SRC_PATH)\..\..\inc; \ - $(CONSOLE_SRC_PATH)\..\oss\dynamic_bitset; \ - $(CONSOLE_SRC_PATH)\..\oss\libpopcnt; \ $(CONSOLE_SRC_PATH)\..\oss\chromium; \ $(CONSOLE_SRC_PATH)\..\oss\fmt\include; \ $(CONSOLE_SRC_PATH)\..\oss\interval_tree; \ diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp index 17e7d2f8a5e..3be016a0a60 100644 --- a/src/renderer/atlas/AtlasEngine.api.cpp +++ b/src/renderer/atlas/AtlasEngine.api.cpp @@ -160,13 +160,6 @@ constexpr HRESULT vec2_narrow(U x, U y, vec2& out) noexcept return S_OK; } -[[nodiscard]] HRESULT AtlasEngine::InvalidateFlush(_In_ const bool /*circled*/, _Out_ bool* const pForcePaint) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint); - *pForcePaint = false; - return S_OK; -} - [[nodiscard]] HRESULT AtlasEngine::InvalidateTitle(const std::wstring_view proposedTitle) noexcept { _api.invalidatedTitle = true; diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index 277d3818dc2..50889781966 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -282,13 +282,6 @@ try } CATCH_RETURN() -[[nodiscard]] HRESULT AtlasEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint); - *pForcePaint = false; - return S_OK; -} - [[nodiscard]] HRESULT AtlasEngine::ScrollFrame() noexcept { return S_OK; diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h index 135d0e692a9..68c0ca8e01b 100644 --- a/src/renderer/atlas/AtlasEngine.h +++ b/src/renderer/atlas/AtlasEngine.h @@ -27,7 +27,6 @@ namespace Microsoft::Console::Render::Atlas [[nodiscard]] bool RequiresContinuousRedraw() noexcept override; void WaitUntilCanRender() noexcept override; [[nodiscard]] HRESULT Present() noexcept override; - [[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* pForcePaint) noexcept override; [[nodiscard]] HRESULT ScrollFrame() noexcept override; [[nodiscard]] HRESULT Invalidate(const til::rect* psrRegion) noexcept override; [[nodiscard]] HRESULT InvalidateCursor(const til::rect* psrRegion) noexcept override; @@ -36,7 +35,6 @@ namespace Microsoft::Console::Render::Atlas [[nodiscard]] HRESULT InvalidateHighlight(std::span highlights, const TextBuffer& buffer) noexcept override; [[nodiscard]] HRESULT InvalidateScroll(const til::point* pcoordDelta) noexcept override; [[nodiscard]] HRESULT InvalidateAll() noexcept override; - [[nodiscard]] HRESULT InvalidateFlush(_In_ const bool circled, _Out_ bool* const pForcePaint) noexcept override; [[nodiscard]] HRESULT InvalidateTitle(std::wstring_view proposedTitle) noexcept override; [[nodiscard]] HRESULT NotifyNewText(const std::wstring_view newText) noexcept override; [[nodiscard]] HRESULT PrepareRenderInfo(RenderFrameInfo info) noexcept override; diff --git a/src/renderer/atlas/pch.h b/src/renderer/atlas/pch.h index 36a2f2143c5..f06a26c4946 100644 --- a/src/renderer/atlas/pch.h +++ b/src/renderer/atlas/pch.h @@ -36,13 +36,6 @@ #include #include -// Dynamic Bitset (optional dependency on LibPopCnt for perf at bit counting) -// Variable-size compressed-storage header-only bit flag storage library. -#pragma warning(push) -#pragma warning(disable : 4702) // unreachable code -#include -#pragma warning(pop) - // Chromium Numerics (safe math) #pragma warning(push) #pragma warning(disable : 4100) // '...': unreferenced formal parameter diff --git a/src/renderer/base/RenderEngineBase.cpp b/src/renderer/base/RenderEngineBase.cpp index dfc54432f22..cfa00b13cd0 100644 --- a/src/renderer/base/RenderEngineBase.cpp +++ b/src/renderer/base/RenderEngineBase.cpp @@ -92,19 +92,3 @@ void RenderEngineBase::WaitUntilCanRender() noexcept void RenderEngineBase::UpdateHyperlinkHoveredId(const uint16_t /*hoveredId*/) noexcept { } - -// Routine Description: -// - Notifies us that we're about to circle the buffer, giving us a chance to -// force a repaint before the buffer contents are lost. -// - The default implementation of flush, is to do nothing for most renderers. -// Arguments: -// - circled - ignored -// - pForcePaint - Always filled with false -// Return Value: -// - S_FALSE because we don't use this. -[[nodiscard]] HRESULT RenderEngineBase::InvalidateFlush(_In_ const bool /*circled*/, _Out_ bool* const pForcePaint) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint); - *pForcePaint = false; - return S_FALSE; -} diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index 8192404b9bc..3c8ff115bc5 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -316,26 +316,6 @@ void Renderer::TriggerTeardown() noexcept { // We need to shut down the paint thread on teardown. _pThread->WaitForPaintCompletionAndDisable(INFINITE); - - auto repaint = false; - - // Then walk through and do one final paint on the caller's thread. - FOREACH_ENGINE(pEngine) - { - auto fEngineRequestsRepaint = false; - auto hr = pEngine->PrepareForTeardown(&fEngineRequestsRepaint); - LOG_IF_FAILED(hr); - - repaint |= SUCCEEDED(hr) && fEngineRequestsRepaint; - } - - // BODGY: The only time repaint is true is when VtEngine is used. - // Coincidentally VtEngine always runs alone, so if repaint is true, there's only a single engine - // to repaint anyways and there's no danger is accidentally repainting an engine that didn't want to. - if (repaint) - { - LOG_IF_FAILED(_PaintFrame()); - } } // Routine Description: @@ -486,38 +466,6 @@ void Renderer::TriggerScroll(const til::point* const pcoordDelta) NotifyPaintFrame(); } -// Routine Description: -// - Called when the text buffer is about to circle its backing buffer. -// A renderer might want to get painted before that happens. -// Arguments: -// - -// Return Value: -// - -void Renderer::TriggerFlush(const bool circling) -{ - const auto rects = _GetSelectionRects(); - auto repaint = false; - - FOREACH_ENGINE(pEngine) - { - auto fEngineRequestsRepaint = false; - auto hr = pEngine->InvalidateFlush(circling, &fEngineRequestsRepaint); - LOG_IF_FAILED(hr); - - LOG_IF_FAILED(pEngine->InvalidateSelection(rects)); - - repaint |= SUCCEEDED(hr) && fEngineRequestsRepaint; - } - - // BODGY: The only time repaint is true is when VtEngine is used. - // Coincidentally VtEngine always runs alone, so if repaint is true, there's only a single engine - // to repaint anyways and there's no danger is accidentally repainting an engine that didn't want to. - if (repaint) - { - LOG_IF_FAILED(_PaintFrame()); - } -} - // Routine Description: // - Called when the title of the console window has changed. Indicates that we // should update the title on the next frame. diff --git a/src/renderer/base/renderer.hpp b/src/renderer/base/renderer.hpp index 8c4308c5a87..aed9789caff 100644 --- a/src/renderer/base/renderer.hpp +++ b/src/renderer/base/renderer.hpp @@ -23,14 +23,6 @@ Author(s): #include "../../buffer/out/textBuffer.hpp" -// fwdecl unittest classes -#ifdef UNIT_TESTING -namespace TerminalCoreUnitTests -{ - class ConptyRoundtripTests; -}; -#endif - namespace Microsoft::Console::Render { class Renderer @@ -60,7 +52,6 @@ namespace Microsoft::Console::Render void TriggerScroll(); void TriggerScroll(const til::point* const pcoordDelta); - void TriggerFlush(const bool circling); void TriggerTitleChange(); void TriggerNewTextNotification(const std::wstring_view newText); @@ -146,10 +137,5 @@ namespace Microsoft::Console::Render std::function _pfnRendererEnteredErrorState; bool _destructing = false; bool _forceUpdateViewport = false; - -#ifdef UNIT_TESTING - friend class ConptyOutputTests; - friend class TerminalCoreUnitTests::ConptyRoundtripTests; -#endif }; } diff --git a/src/renderer/dirs b/src/renderer/dirs index 65488397e89..1b3b1ee6c59 100644 --- a/src/renderer/dirs +++ b/src/renderer/dirs @@ -2,4 +2,3 @@ DIRS= \ base \ gdi \ wddmcon \ - vt \ diff --git a/src/renderer/gdi/gdirenderer.hpp b/src/renderer/gdi/gdirenderer.hpp index 48eec99a920..750226a8797 100644 --- a/src/renderer/gdi/gdirenderer.hpp +++ b/src/renderer/gdi/gdirenderer.hpp @@ -33,7 +33,6 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT Invalidate(const til::rect* const psrRegion) noexcept override; [[nodiscard]] HRESULT InvalidateCursor(const til::rect* const psrRegion) noexcept override; [[nodiscard]] HRESULT InvalidateAll() noexcept override; - [[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override; [[nodiscard]] HRESULT StartPaint() noexcept override; [[nodiscard]] HRESULT EndPaint() noexcept override; diff --git a/src/renderer/gdi/invalidate.cpp b/src/renderer/gdi/invalidate.cpp index ce2616be604..094335af029 100644 --- a/src/renderer/gdi/invalidate.cpp +++ b/src/renderer/gdi/invalidate.cpp @@ -100,21 +100,6 @@ HRESULT GdiEngine::InvalidateAll() noexcept RETURN_HR(InvalidateSystem(&rc)); } -// Method Description: -// - Notifies us that we're about to be torn down. This gives us a last chance -// to force a repaint before the buffer contents are lost. The GDI renderer -// doesn't care if we lose text - we're only painting visible text anyways, -// so we return false. -// Arguments: -// - Receives a bool indicating if we should force the repaint. -// Return Value: -// - S_FALSE - we succeeded, but the result was false. -HRESULT GdiEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept -{ - *pForcePaint = false; - return S_FALSE; -} - // Routine Description: // - Helper to combine the given rectangle into the invalid region to be updated on the next paint // Arguments: diff --git a/src/renderer/inc/IRenderEngine.hpp b/src/renderer/inc/IRenderEngine.hpp index cdbcd774855..5a25c977855 100644 --- a/src/renderer/inc/IRenderEngine.hpp +++ b/src/renderer/inc/IRenderEngine.hpp @@ -62,7 +62,6 @@ namespace Microsoft::Console::Render [[nodiscard]] virtual bool RequiresContinuousRedraw() noexcept = 0; virtual void WaitUntilCanRender() noexcept = 0; [[nodiscard]] virtual HRESULT Present() noexcept = 0; - [[nodiscard]] virtual HRESULT PrepareForTeardown(_Out_ bool* pForcePaint) noexcept = 0; [[nodiscard]] virtual HRESULT ScrollFrame() noexcept = 0; [[nodiscard]] virtual HRESULT Invalidate(const til::rect* psrRegion) noexcept = 0; [[nodiscard]] virtual HRESULT InvalidateCursor(const til::rect* psrRegion) noexcept = 0; @@ -71,7 +70,6 @@ namespace Microsoft::Console::Render [[nodiscard]] virtual HRESULT InvalidateHighlight(std::span highlights, const TextBuffer& buffer) noexcept = 0; [[nodiscard]] virtual HRESULT InvalidateScroll(const til::point* pcoordDelta) noexcept = 0; [[nodiscard]] virtual HRESULT InvalidateAll() noexcept = 0; - [[nodiscard]] virtual HRESULT InvalidateFlush(_In_ const bool circled, _Out_ bool* const pForcePaint) noexcept = 0; [[nodiscard]] virtual HRESULT InvalidateTitle(std::wstring_view proposedTitle) noexcept = 0; [[nodiscard]] virtual HRESULT NotifyNewText(const std::wstring_view newText) noexcept = 0; [[nodiscard]] virtual HRESULT PrepareRenderInfo(RenderFrameInfo info) noexcept = 0; diff --git a/src/renderer/inc/RenderEngineBase.hpp b/src/renderer/inc/RenderEngineBase.hpp index 2794fcf98da..5a7454a2a6a 100644 --- a/src/renderer/inc/RenderEngineBase.hpp +++ b/src/renderer/inc/RenderEngineBase.hpp @@ -48,8 +48,6 @@ namespace Microsoft::Console::Render [[nodiscard]] bool RequiresContinuousRedraw() noexcept override; - [[nodiscard]] HRESULT InvalidateFlush(_In_ const bool circled, _Out_ bool* const pForcePaint) noexcept override; - void WaitUntilCanRender() noexcept override; void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept override; diff --git a/src/renderer/uia/UiaRenderer.cpp b/src/renderer/uia/UiaRenderer.cpp index d1e72fbd0eb..0143a8e7b4b 100644 --- a/src/renderer/uia/UiaRenderer.cpp +++ b/src/renderer/uia/UiaRenderer.cpp @@ -191,20 +191,6 @@ try } CATCH_LOG_RETURN_HR(E_FAIL); -// Routine Description: -// - This is unused by this renderer. -// Arguments: -// - pForcePaint - always filled with false. -// Return Value: -// - S_FALSE because this is unused. -[[nodiscard]] HRESULT UiaEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint); - - *pForcePaint = false; - return S_FALSE; -} - // Routine Description: // - Prepares internal structures for a painting operation. // Arguments: diff --git a/src/renderer/uia/UiaRenderer.hpp b/src/renderer/uia/UiaRenderer.hpp index 3fd069f425a..0f7d5f1afb6 100644 --- a/src/renderer/uia/UiaRenderer.hpp +++ b/src/renderer/uia/UiaRenderer.hpp @@ -38,7 +38,6 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT EndPaint() noexcept override; void WaitUntilCanRender() noexcept override; [[nodiscard]] HRESULT Present() noexcept override; - [[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override; [[nodiscard]] HRESULT ScrollFrame() noexcept override; [[nodiscard]] HRESULT Invalidate(const til::rect* const psrRegion) noexcept override; [[nodiscard]] HRESULT InvalidateCursor(const til::rect* const psrRegion) noexcept override; diff --git a/src/renderer/vt/VtSequences.cpp b/src/renderer/vt/VtSequences.cpp deleted file mode 100644 index 612d3c06d06..00000000000 --- a/src/renderer/vt/VtSequences.cpp +++ /dev/null @@ -1,527 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "vtrenderer.hpp" - -#include "../renderer/base/renderer.hpp" -#include "../../inc/conattrs.hpp" - -#pragma hdrstop -using namespace Microsoft::Console::Render; - -// Method Description: -// - Formats and writes a sequence to stop the cursor from blinking. -// Arguments: -// - -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_StopCursorBlinking() noexcept -{ - return _Write("\x1b[?12l"); -} - -// Method Description: -// - Formats and writes a sequence to start the cursor blinking. If it's -// hidden, this won't also show it. -// Arguments: -// - -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_StartCursorBlinking() noexcept -{ - return _Write("\x1b[?12h"); -} - -// Method Description: -// - Formats and writes a sequence to hide the cursor. -// Arguments: -// - -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_HideCursor() noexcept -{ - return _Write("\x1b[?25l"); -} - -// Method Description: -// - Formats and writes a sequence to show the cursor. -// Arguments: -// - -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_ShowCursor() noexcept -{ - return _Write("\x1b[?25h"); -} - -// Method Description: -// - Formats and writes a sequence to erase the remainder of the line starting -// from the cursor position. -// Arguments: -// - -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_EraseLine() noexcept -{ - // The default no-param action of erase line is erase to the right. - // telnet client doesn't understand the parameterized version, - // so emit the implicit sequence instead. - return _Write("\x1b[K"); -} - -// Method Description: -// - Formats and writes a sequence to either insert or delete a number of lines -// into the buffer at the current cursor location. -// Delete/insert Character removes/adds N characters from/to the buffer, and -// shifts the remaining chars in the row to the left/right, while Erase -// Character replaces N characters with spaces, and leaves the rest -// untouched. -// Arguments: -// - chars: a number of characters to erase (by overwriting with space) -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_EraseCharacter(const til::CoordType chars) noexcept -{ - return _WriteFormatted(FMT_COMPILE("\x1b[{}X"), chars); -} - -// Method Description: -// - Moves the cursor forward (right) a number of characters. -// Arguments: -// - chars: a number of characters to move cursor right by. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_CursorForward(const til::CoordType chars) noexcept -{ - return _WriteFormatted(FMT_COMPILE("\x1b[{}C"), chars); -} - -// Method Description: -// - Formats and writes a sequence to erase the remainder of the line starting -// from the cursor position. -// Arguments: -// - -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_ClearScreen() noexcept -{ - return _Write("\x1b[2J"); -} - -[[nodiscard]] HRESULT VtEngine::_ClearScrollback() noexcept -{ - return _Write("\x1b[3J"); -} - -// Method Description: -// - Formats and writes a sequence to either insert or delete a number of lines -// into the buffer at the current cursor location. -// Arguments: -// - sLines: a number of lines to insert or delete -// - fInsertLine: true iff we should insert the lines, false to delete them. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_InsertDeleteLine(const til::CoordType sLines, const bool fInsertLine) noexcept -{ - if (sLines <= 0) - { - return S_OK; - } - if (sLines == 1) - { - return _Write(fInsertLine ? "\x1b[L" : "\x1b[M"); - } - - return _WriteFormatted(FMT_COMPILE("\x1b[{}{}"), sLines, fInsertLine ? 'L' : 'M'); -} - -// Method Description: -// - Formats and writes a sequence to delete a number of lines into the buffer -// at the current cursor location. -// Arguments: -// - sLines: a number of lines to insert -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_DeleteLine(const til::CoordType sLines) noexcept -{ - return _InsertDeleteLine(sLines, false); -} - -// Method Description: -// - Formats and writes a sequence to insert a number of lines into the buffer -// at the current cursor location. -// Arguments: -// - sLines: a number of lines to insert -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_InsertLine(const til::CoordType sLines) noexcept -{ - return _InsertDeleteLine(sLines, true); -} - -// Method Description: -// - Formats and writes a sequence to move the cursor to the specified -// coordinate position. The input coord should be in console coordinates, -// where origin=(0,0). -// Arguments: -// - coord: Console coordinates to move the cursor to. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_CursorPosition(const til::point coord) noexcept -{ - // VT coords start at 1,1 - auto coordVt = coord; - coordVt.x++; - coordVt.y++; - - return _WriteFormatted(FMT_COMPILE("\x1b[{};{}H"), coordVt.y, coordVt.x); -} - -// Method Description: -// - Formats and writes a sequence to move the cursor to the origin. -// Arguments: -// - -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_CursorHome() noexcept -{ - return _Write("\x1b[H"); -} - -// Method Description: -// - Formats and writes a sequence to change the current text attributes to the default. -// Arguments: -// -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_SetGraphicsDefault() noexcept -{ - return _Write("\x1b[m"); -} - -// Method Description: -// - Formats and writes a sequence to change the current text attributes to an -// indexed color from the 16-color table. -// Arguments: -// - index: color table index to emit as a VT sequence -// - fIsForeground: true if we should emit the foreground sequence, false for background -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_SetGraphicsRendition16Color(const BYTE index, - const bool fIsForeground) noexcept -{ - // Always check using the foreground flags, because the bg flags constants - // are a higher byte - // Foreground sequences are in [30,37] U [90,97] - // Background sequences are in [40,47] U [100,107] - // The "dark" sequences are in the first 7 values, the bright sequences in the second set. - // Note that text brightness and intensity are different in VT. Intensity is - // handled by _SetIntense. Here, we can emit either bright or - // dark colors. For conhost as a terminal, it can't draw bold - // characters, so it displays "intense" as bright, and in fact most - // terminals display the bright color when displaying intense text. - // By specifying the intensity and brightness separately, we'll make sure the - // terminal has an accurate representation of our buffer. - const auto prefix = WI_IsFlagSet(index, FOREGROUND_INTENSITY) ? (fIsForeground ? 90 : 100) : (fIsForeground ? 30 : 40); - return _WriteFormatted(FMT_COMPILE("\x1b[{}m"), prefix + (index & 7)); -} - -// Method Description: -// - Formats and writes a sequence to change the current text attributes to an -// indexed color from the 256-color table. -// Arguments: -// - index: color table index to emit as a VT sequence -// - fIsForeground: true if we should emit the foreground sequence, false for background -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_SetGraphicsRendition256Color(const BYTE index, - const bool fIsForeground) noexcept -{ - return _WriteFormatted(FMT_COMPILE("\x1b[{}8;5;{}m"), fIsForeground ? '3' : '4', index); -} - -// Method Description: -// - Formats and writes a sequence to change the current underline color to an -// indexed color from the 256-color table. -// - Uses sub parameters. -// Arguments: -// - index: color table index to emit as a VT sequence -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_SetGraphicsRenditionUnderline256Color(const BYTE index) noexcept -{ - return _WriteFormatted(FMT_COMPILE("\x1b[58:5:{}m"), index); -} - -// Method Description: -// - Formats and writes a sequence to change the current text attributes to an -// RGB color. -// Arguments: -// - color: The color to emit a VT sequence for -// - fIsForeground: true if we should emit the foreground sequence, false for background -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_SetGraphicsRenditionRGBColor(const COLORREF color, - const bool fIsForeground) noexcept -{ - const auto r = GetRValue(color); - const auto g = GetGValue(color); - const auto b = GetBValue(color); - return _WriteFormatted(FMT_COMPILE("\x1b[{}8;2;{};{};{}m"), fIsForeground ? '3' : '4', r, g, b); -} - -// Method Description: -// - Formats and writes a sequence to change the current underline color to an -// RGB color. -// - Uses sub parameters. -// Arguments: -// - color: The color to emit a VT sequence for. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_SetGraphicsRenditionUnderlineRGBColor(const COLORREF color) noexcept -{ - const auto r = GetRValue(color); - const auto g = GetGValue(color); - const auto b = GetBValue(color); - return _WriteFormatted(FMT_COMPILE("\x1b[58:2::{}:{}:{}m"), r, g, b); -} - -// Method Description: -// - Formats and writes a sequence to change the current text attributes to the -// default foreground or background. Does not affect the intensity of text. -// Arguments: -// - fIsForeground: true if we should emit the foreground sequence, false for background -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_SetGraphicsRenditionDefaultColor(const bool fIsForeground) noexcept -{ - return _Write(fIsForeground ? ("\x1b[39m") : ("\x1b[49m")); -} - -// Method Description: -// - Formats and writes a sequence to change the current underline color to the -// default color. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_SetGraphicsRenditionUnderlineDefaultColor() noexcept -{ - return _Write("\x1b[59m"); -} - -// Method Description: -// - Formats and writes a sequence to change the terminal's window size. -// Arguments: -// - sWidth: number of columns the terminal should display -// - sHeight: number of rows the terminal should display -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_ResizeWindow(const til::CoordType sWidth, const til::CoordType sHeight) noexcept -{ - if (sWidth < 0 || sHeight < 0) - { - return E_INVALIDARG; - } - - return _WriteFormatted(FMT_COMPILE("\x1b[8;{};{}t"), sHeight, sWidth); -} - -// Method Description: -// - Formats and writes a sequence to request the end terminal to tell us the -// cursor position. The terminal will reply back on the vt input handle. -// Arguments: -// - -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_RequestCursor() noexcept -{ - return _Write("\x1b[6n"); -} - -// Method Description: -// - Formats and writes a sequence to change the terminal's title string -// Arguments: -// - title: string to use as the new title of the window. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_ChangeTitle(_In_ const std::string& title) noexcept -{ - return _WriteFormatted(FMT_COMPILE("\x1b]0;{}\x7"), title); -} - -// Method Description: -// - Formats and writes a sequence to change the intensity of the following text. -// Arguments: -// - isIntense: If true, we'll make the text intense. Otherwise we'll remove the intensity. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_SetIntense(const bool isIntense) noexcept -{ - return _Write(isIntense ? "\x1b[1m" : "\x1b[22m"); -} - -// Method Description: -// - Formats and writes a sequence to change the faintness of the following text. -// Arguments: -// - isFaint: If true, we'll make the text faint. Otherwise we'll remove the faintness. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_SetFaint(const bool isFaint) noexcept -{ - return _Write(isFaint ? "\x1b[2m" : "\x1b[22m"); -} - -// Method Description: -// - Formats and writes a sequence to change the extended underline styling of the following text. -// - Uses backward compatible SGR 4 (without sub parameter) and SGR 21 for single and doubly underline. -// Arguments: -// - style: underline style to use. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_SetUnderlineExtended(const UnderlineStyle style) noexcept -{ - switch (style) - { - case UnderlineStyle::NoUnderline: - return _SetUnderlined(false); - case UnderlineStyle::SinglyUnderlined: - return _SetUnderlined(true); - case UnderlineStyle::DoublyUnderlined: - return _Write("\x1b[21m"); - case UnderlineStyle::CurlyUnderlined: - return _Write("\x1b[4:3m"); - case UnderlineStyle::DottedUnderlined: - return _Write("\x1b[4:4m"); - case UnderlineStyle::DashedUnderlined: - return _Write("\x1b[4:5m"); - default: - return _SetUnderlined(true); // treat unknown style as singly underlined - } -} - -// Method Description: -// - Formats and writes a sequence to change the underline of the following text. -// Arguments: -// - isUnderlined: If true, we'll underline the text. Otherwise we'll remove the underline. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_SetUnderlined(const bool isUnderlined) noexcept -{ - return _Write(isUnderlined ? "\x1b[4m" : "\x1b[24m"); -} - -// Method Description: -// - Formats and writes a sequence to change the overline of the following text. -// Arguments: -// - isOverlined: If true, we'll overline the text. Otherwise we'll remove the overline. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_SetOverlined(const bool isOverlined) noexcept -{ - return _Write(isOverlined ? "\x1b[53m" : "\x1b[55m"); -} - -// Method Description: -// - Formats and writes a sequence to change the italics of the following text. -// Arguments: -// - isItalic: If true, we'll italicize the text. Otherwise we'll remove the italics. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_SetItalic(const bool isItalic) noexcept -{ - return _Write(isItalic ? "\x1b[3m" : "\x1b[23m"); -} - -// Method Description: -// - Formats and writes a sequence to change the blinking of the following text. -// Arguments: -// - isBlinking: If true, we'll start the text blinking. Otherwise we'll stop the blinking. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_SetBlinking(const bool isBlinking) noexcept -{ - return _Write(isBlinking ? "\x1b[5m" : "\x1b[25m"); -} - -// Method Description: -// - Formats and writes a sequence to change the visibility of the following text. -// Arguments: -// - isInvisible: If true, we'll make the text invisible. Otherwise we'll make it visible. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_SetInvisible(const bool isInvisible) noexcept -{ - return _Write(isInvisible ? "\x1b[8m" : "\x1b[28m"); -} - -// Method Description: -// - Formats and writes a sequence to change the crossed out state of the following text. -// Arguments: -// - isCrossedOut: If true, we'll cross out the text. Otherwise we'll stop crossing out. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_SetCrossedOut(const bool isCrossedOut) noexcept -{ - return _Write(isCrossedOut ? "\x1b[9m" : "\x1b[29m"); -} - -// Method Description: -// - Formats and writes a sequence to change the reversed state of the following text. -// Arguments: -// - isReversed: If true, we'll reverse the text. Otherwise we'll remove the reversed state. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_SetReverseVideo(const bool isReversed) noexcept -{ - return _Write(isReversed ? "\x1b[7m" : "\x1b[27m"); -} - -// Method Description: -// - Send a sequence to the connected terminal to switch to the alternate or main screen buffer. -// Arguments: -// - useAltBuffer: if true, switch to the alt buffer, otherwise to the main buffer. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_SwitchScreenBuffer(const bool useAltBuffer) noexcept -{ - return _Write(useAltBuffer ? "\x1b[?1049h" : "\x1b[?1049l"); -} - -// Method Description: -// - Formats and writes a sequence to add a hyperlink to the terminal buffer -// Arguments: -// - The hyperlink URI -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_SetHyperlink(const std::wstring_view& uri, const std::wstring_view& customId, const uint16_t& numberId) noexcept -{ - // Opening OSC8 sequence - if (customId.empty()) - { - // This is the case of auto-assigned IDs: - // send the auto-assigned ID, prefixed with the PID of this session - // (we do this so different conpty sessions do not overwrite each other's hyperlinks) - const auto sessionID = GetCurrentProcessId(); - const auto uriStr = til::u16u8(uri); - return _WriteFormatted(FMT_COMPILE("\x1b]8;id={}-{};{}\x1b\\"), sessionID, numberId, uriStr); - } - else - { - // This is the case of user-defined IDs: - // send the user-defined ID, prefixed with a "u" - // (we do this so no application can accidentally override a user defined ID) - const auto uriStr = til::u16u8(uri); - const auto customIdStr = til::u16u8(customId); - return _WriteFormatted(FMT_COMPILE("\x1b]8;id=u-{};{}\x1b\\"), customIdStr, uriStr); - } -} - -// Method Description: -// - Formats and writes a sequence to end a hyperlink to the terminal buffer -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_EndHyperlink() noexcept -{ - // Closing OSC8 sequence - return _Write("\x1b]8;;\x1b\\"); -} diff --git a/src/renderer/vt/Xterm256Engine.cpp b/src/renderer/vt/Xterm256Engine.cpp deleted file mode 100644 index 7527db3dee7..00000000000 --- a/src/renderer/vt/Xterm256Engine.cpp +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "Xterm256Engine.hpp" -#pragma hdrstop -using namespace Microsoft::Console; -using namespace Microsoft::Console::Render; -using namespace Microsoft::Console::Types; - -Xterm256Engine::Xterm256Engine(_In_ wil::unique_hfile hPipe, - const Viewport initialViewport) : - XtermEngine(std::move(hPipe), initialViewport, false) -{ -} - -// Routine Description: -// - Write a VT sequence to change the current colors of text. Writes true RGB -// color sequences. -// Arguments: -// - textAttributes - Text attributes to use for the colors and character rendition -// - renderSettings - The color table and modes required for rendering -// - pData - The interface to console data structures required for rendering -// - usingSoftFont - Whether we're rendering characters from a soft font -// - isSettingDefaultBrushes: indicates if we should change the background color of -// the window. Unused for VT -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT Xterm256Engine::UpdateDrawingBrushes(const TextAttribute& textAttributes, - const RenderSettings& /*renderSettings*/, - const gsl::not_null pData, - const bool usingSoftFont, - const bool isSettingDefaultBrushes) noexcept -{ - RETURN_HR_IF(S_FALSE, _passthrough && isSettingDefaultBrushes); - - RETURN_IF_FAILED(VtEngine::_RgbUpdateDrawingBrushes(textAttributes)); - - RETURN_IF_FAILED(_UpdateHyperlinkAttr(textAttributes, pData)); - - // If we're using a soft font, it should have already been mapped into the - // G1 table, so we just need to switch between G0 and G1 when turning the - // soft font on and off. We don't want to do this when setting the default - // brushes, though, because that could result in an unnecessary G0 switch - // at the start of every frame. - if (usingSoftFont != _usingSoftFont && !isSettingDefaultBrushes) - { - RETURN_IF_FAILED(_Write(usingSoftFont ? "\x0E" : "\x0F")); - _usingSoftFont = usingSoftFont; - } - - // Only do extended attributes in xterm-256color, as to not break telnet.exe. - return _UpdateExtendedAttrs(textAttributes); -} - -// Routine Description: -// - Write a VT sequence to update the character rendition attributes. -// Arguments: -// - textAttributes - text attributes (intense, italic, underline, etc.) to use. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT Xterm256Engine::_UpdateExtendedAttrs(const TextAttribute& textAttributes) noexcept -{ - // Turning off Intense and Faint must be handled at the same time, - // since there is only one sequence that resets both of them. - const auto intenseTurnedOff = !textAttributes.IsIntense() && _lastTextAttributes.IsIntense(); - const auto faintTurnedOff = !textAttributes.IsFaint() && _lastTextAttributes.IsFaint(); - if (intenseTurnedOff || faintTurnedOff) - { - RETURN_IF_FAILED(_SetIntense(false)); - _lastTextAttributes.SetIntense(false); - _lastTextAttributes.SetFaint(false); - } - - // Once we've handled the cases where they need to be turned off, - // we can then check if either should be turned back on again. - if (textAttributes.IsIntense() && !_lastTextAttributes.IsIntense()) - { - RETURN_IF_FAILED(_SetIntense(true)); - _lastTextAttributes.SetIntense(true); - } - if (textAttributes.IsFaint() && !_lastTextAttributes.IsFaint()) - { - RETURN_IF_FAILED(_SetFaint(true)); - _lastTextAttributes.SetFaint(true); - } - - // We check the singly, doubly underlined and extended styling together, - // since only one of them can be active at a time. - const auto ulStyle = textAttributes.GetUnderlineStyle(); - const auto lastUlStyle = _lastTextAttributes.GetUnderlineStyle(); - if (ulStyle != lastUlStyle) - { - // Reset doubly underlined if it was previously set. Avoids an edge case - // where a pty client tracks doubly underlined and singly underlined separately, - // and setting single underlined would leave the text doubly underlined - // because it was never turned-off. - if (lastUlStyle == UnderlineStyle::DoublyUnderlined && ulStyle != UnderlineStyle::NoUnderline) - { - RETURN_IF_FAILED(_SetUnderlined(false)); - } - RETURN_IF_FAILED(_SetUnderlineExtended(ulStyle)); - _lastTextAttributes.SetUnderlineStyle(ulStyle); - } - - if (textAttributes.IsOverlined() != _lastTextAttributes.IsOverlined()) - { - RETURN_IF_FAILED(_SetOverlined(textAttributes.IsOverlined())); - _lastTextAttributes.SetOverlined(textAttributes.IsOverlined()); - } - - if (textAttributes.IsItalic() != _lastTextAttributes.IsItalic()) - { - RETURN_IF_FAILED(_SetItalic(textAttributes.IsItalic())); - _lastTextAttributes.SetItalic(textAttributes.IsItalic()); - } - - if (textAttributes.IsBlinking() != _lastTextAttributes.IsBlinking()) - { - RETURN_IF_FAILED(_SetBlinking(textAttributes.IsBlinking())); - _lastTextAttributes.SetBlinking(textAttributes.IsBlinking()); - } - - if (textAttributes.IsInvisible() != _lastTextAttributes.IsInvisible()) - { - RETURN_IF_FAILED(_SetInvisible(textAttributes.IsInvisible())); - _lastTextAttributes.SetInvisible(textAttributes.IsInvisible()); - } - - if (textAttributes.IsCrossedOut() != _lastTextAttributes.IsCrossedOut()) - { - RETURN_IF_FAILED(_SetCrossedOut(textAttributes.IsCrossedOut())); - _lastTextAttributes.SetCrossedOut(textAttributes.IsCrossedOut()); - } - - if (textAttributes.IsReverseVideo() != _lastTextAttributes.IsReverseVideo()) - { - RETURN_IF_FAILED(_SetReverseVideo(textAttributes.IsReverseVideo())); - _lastTextAttributes.SetReverseVideo(textAttributes.IsReverseVideo()); - } - - return S_OK; -} - -// Routine Description: -// - Write a VT sequence to start/stop a hyperlink -// Arguments: -// - textAttributes - Text attributes to use for the hyperlink ID -// - pData - The interface to console data structures required for rendering -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -HRESULT Microsoft::Console::Render::Xterm256Engine::_UpdateHyperlinkAttr(const TextAttribute& textAttributes, - const gsl::not_null pData) noexcept -{ - if (textAttributes.GetHyperlinkId() != _lastTextAttributes.GetHyperlinkId()) - { - if (textAttributes.IsHyperlink()) - { - const auto id = textAttributes.GetHyperlinkId(); - const auto customId = pData->GetHyperlinkCustomId(id); - RETURN_IF_FAILED(_SetHyperlink(pData->GetHyperlinkUri(id), customId, id)); - } - else - { - RETURN_IF_FAILED(_EndHyperlink()); - } - _lastTextAttributes.SetHyperlinkId(textAttributes.GetHyperlinkId()); - } - - return S_OK; -} - -// Method Description: -// - Manually emit a "Erase Scrollback" sequence to the connected terminal. We -// need to do this in certain cases that we've identified where we believe the -// client wanted the entire terminal buffer cleared, not just the viewport. -// For more information, see GH#3126. -// Arguments: -// - -// Return Value: -// - S_OK if we wrote the sequences successfully, otherwise an appropriate HRESULT -[[nodiscard]] HRESULT Xterm256Engine::ManuallyClearScrollback() noexcept -{ - return _ClearScrollback(); -} diff --git a/src/renderer/vt/Xterm256Engine.hpp b/src/renderer/vt/Xterm256Engine.hpp deleted file mode 100644 index 5c8cde61831..00000000000 --- a/src/renderer/vt/Xterm256Engine.hpp +++ /dev/null @@ -1,49 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- Xterm256Engine.hpp - -Abstract: -- This is the definition of the VT specific implementation of the renderer. - This is the xterm-256color implementation, which supports advanced sequences such as - inserting and deleting lines, and true rgb color. - -Author(s): -- Mike Griese (migrie) 01-Sept-2017 ---*/ - -#pragma once - -#include "XtermEngine.hpp" - -namespace Microsoft::Console::Render -{ - class Xterm256Engine : public XtermEngine - { - public: - Xterm256Engine(_In_ wil::unique_hfile hPipe, - const Microsoft::Console::Types::Viewport initialViewport); - - virtual ~Xterm256Engine() override = default; - - [[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, - const RenderSettings& renderSettings, - const gsl::not_null pData, - const bool usingSoftFont, - const bool isSettingDefaultBrushes) noexcept override; - - [[nodiscard]] HRESULT ManuallyClearScrollback() noexcept override; - - private: - [[nodiscard]] HRESULT _UpdateExtendedAttrs(const TextAttribute& textAttributes) noexcept; - [[nodiscard]] HRESULT _UpdateHyperlinkAttr(const TextAttribute& textAttributes, - const gsl::not_null pData) noexcept; - -#ifdef UNIT_TESTING - friend class VtRendererTest; - friend class ConptyOutputTests; -#endif - }; -} diff --git a/src/renderer/vt/XtermEngine.cpp b/src/renderer/vt/XtermEngine.cpp deleted file mode 100644 index 0dc9c8384e4..00000000000 --- a/src/renderer/vt/XtermEngine.cpp +++ /dev/null @@ -1,576 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "XtermEngine.hpp" -#include "../../types/inc/convert.hpp" -#pragma hdrstop -using namespace Microsoft::Console; -using namespace Microsoft::Console::Render; -using namespace Microsoft::Console::Types; - -XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe, - const Viewport initialViewport, - const bool fUseAsciiOnly) : - VtEngine(std::move(hPipe), initialViewport), - _fUseAsciiOnly(fUseAsciiOnly), - _needToDisableCursor(false), - // GH#12401: Ensure a DECTCEM cursor show/hide sequence - // is emitted on the first frame no matter what. - _lastCursorIsVisible(Tribool::Invalid), - _nextCursorIsVisible(true) -{ - // Set out initial cursor position to -1, -1. This will force our initial - // paint to manually move the cursor to 0, 0, not just ignore it. - _lastText = VtEngine::INVALID_COORDS; -} - -// Method Description: -// - Prepares internal structures for a painting operation. Turns the cursor -// off, so we don't see it flashing all over the client's screen as we -// paint the new contents. -// Arguments: -// - -// Return Value: -// - S_OK if we started to paint. S_FALSE if we didn't need to paint. HRESULT -// error code if painting didn't start successfully, or we failed to write -// the pipe. -[[nodiscard]] HRESULT XtermEngine::StartPaint() noexcept -{ - const auto hr = VtEngine::StartPaint(); - if (hr != S_OK) - { - // If _noFlushOnEnd was set, and we didn't return early, it would usually - // have been reset in the EndPaint call. But since that's not going to - // happen now, we need to reset it here, otherwise we may mistakenly skip - // the flush on the next frame. - if (!_noFlushOnEnd) - { - _Flush(); - } - _noFlushOnEnd = false; - return hr; - } - - _trace.TraceLastText(_lastText); - - // Prep us to think that the cursor is not visible this frame. If it _is_ - // visible, then PaintCursor will be called, and we'll set this to true - // during the frame. - _nextCursorIsVisible = false; - _startOfFrameBufferIndex = _buffer.size(); - - // Do not perform synchronization clearing in passthrough mode. - // In passthrough, the terminal leads and we follow what it is - // handling from the client application. - // (This is in contrast to full PTY mode where WE, the ConPTY, lead and - // it follows our state.) - if (_passthrough) - { - _firstPaint = false; - } - - if (_firstPaint) - { - // MSFT:17815688 - // If the caller requested to inherit the cursor, we shouldn't - // clear the screen on the first paint. Otherwise, we'll clear - // the screen on the first paint, just to make sure that the - // terminal's state is consistent with what we'll be rendering. - RETURN_IF_FAILED(_ClearScreen()); - _clearedAllThisFrame = true; - _firstPaint = false; - } - - return S_OK; -} - -// Routine Description: -// - EndPaint helper to perform the final rendering steps. Turn the cursor back -// on. -// Arguments: -// - -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT XtermEngine::EndPaint() noexcept -{ - // If during the frame we determined that the cursor needed to be disabled, - // then insert a cursor off at the start of the buffer, and re-enable - // the cursor here. - if (_needToDisableCursor) - { - // If the cursor was previously visible, let's hide it for this frame, - // by prepending a cursor off. - if (_lastCursorIsVisible != Tribool::False) - { - _buffer.insert(_startOfFrameBufferIndex, "\x1b[?25l"); - _lastCursorIsVisible = Tribool::False; - } - // If the cursor was NOT previously visible, then that's fine! we don't - // need to worry, it's already off. - } - - if (_lastCursorIsVisible != static_cast(_nextCursorIsVisible)) - { - RETURN_IF_FAILED(_nextCursorIsVisible ? _ShowCursor() : _HideCursor()); - _lastCursorIsVisible = static_cast(_nextCursorIsVisible); - } - - RETURN_IF_FAILED(VtEngine::EndPaint()); - - _needToDisableCursor = false; - - return S_OK; -} - -// Routine Description: -// - Write a VT sequence to change the current colors of text. Only writes -// 16-color attributes. -// Arguments: -// - textAttributes - Text attributes to use for the colors and character rendition -// - renderSettings - The color table and modes required for rendering -// - pData - The interface to console data structures required for rendering -// - usingSoftFont - Whether we're rendering characters from a soft font -// - isSettingDefaultBrushes: indicates if we should change the background color of -// the window. Unused for VT -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT XtermEngine::UpdateDrawingBrushes(const TextAttribute& textAttributes, - const RenderSettings& /*renderSettings*/, - const gsl::not_null /*pData*/, - const bool /*usingSoftFont*/, - const bool /*isSettingDefaultBrushes*/) noexcept -{ - // The base xterm mode only knows about 16 colors - RETURN_IF_FAILED(VtEngine::_16ColorUpdateDrawingBrushes(textAttributes)); - - // And the only supported meta attributes are reverse video and underline - if (textAttributes.IsReverseVideo() != _lastTextAttributes.IsReverseVideo()) - { - RETURN_IF_FAILED(_SetReverseVideo(textAttributes.IsReverseVideo())); - _lastTextAttributes.SetReverseVideo(textAttributes.IsReverseVideo()); - } - if (textAttributes.IsUnderlined() != _lastTextAttributes.IsUnderlined()) - { - RETURN_IF_FAILED(_SetUnderlined(textAttributes.IsUnderlined())); - _lastTextAttributes.SetUnderlineStyle(textAttributes.GetUnderlineStyle()); - } - - return S_OK; -} - -// Routine Description: -// - Draws the cursor on the screen -// Arguments: -// - options - Options that affect the presentation of the cursor -// Return Value: -// - S_OK or suitable HRESULT error from writing pipe. -[[nodiscard]] HRESULT XtermEngine::PaintCursor(const CursorOptions& options) noexcept -{ - // PaintCursor is only called when the cursor is in fact visible in a single - // frame. When this is called, mark _nextCursorIsVisible as true. At the end - // of the frame, we'll decide to either turn the cursor on or not, based - // upon the previous state. - - // When this method is not called during a frame, it's because the cursor - // was not visible. In that case, at the end of the frame, - // _nextCursorIsVisible will still be false (from when we set it during - // StartPaint) - _nextCursorIsVisible = true; - - // If we did a delayed EOL wrap because we actually wrapped the line here, - // then don't PaintCursor. When we're at the EOL because we've wrapped, our - // internal _lastText thinks the cursor is on the cell just past the right - // of the viewport (ex { 120, 0 }). However, conhost thinks the cursor is - // actually on the last cell of the row. So it'll tell us to paint the - // cursor at { 119, 0 }. If we do that movement, then we'll break line - // wrapping. - // See GH#5113, GH#1245, GH#357 - const auto nextCursorPosition = options.coordCursor; - // Only skip this paint when we think the cursor is in the cell - // immediately off the edge of the terminal, and the actual cursor is in - // the last cell of the row. We're in a deferred wrap, but the host - // thinks the cursor is actually in-frame. - // See ConptyRoundtripTests::DontWrapMoveCursorInSingleFrame - const auto cursorIsInDeferredWrap = (nextCursorPosition.x == _lastText.x - 1) && (nextCursorPosition.y == _lastText.y); - // If all three of these conditions are true, then: - // * cursorIsInDeferredWrap: The cursor is in a position where the line - // filled the last cell of the row, but the host tried to paint it in - // the last cell anyways - // - GH#5691 - If we're painting the frame because we circled the - // buffer, then the cursor might still be in the position it was - // before the text was written to the buffer to cause the buffer to - // circle. In that case, then we DON'T want to paint the cursor here - // either, because it'll cause us to manually break this line. That's - // okay though, the frame will be painted again, after the circling - // is complete. - // * _delayedEolWrap && _wrappedRow.has_value(): We think we've deferred - // the wrap of a line. - // If they're all true, DON'T manually paint the cursor this frame. - if (!((cursorIsInDeferredWrap || _circled) && _delayedEolWrap && _wrappedRow.has_value())) - { - return VtEngine::PaintCursor(options); - } - - return S_OK; -} - -// Routine Description: -// - Write a VT sequence to move the cursor to the specified coordinates. We -// also store the last place we left the cursor for future optimizations. -// If the cursor only needs to go to the origin, only write the home sequence. -// If the new cursor is only down one line from the current, only write a newline -// If the new cursor is only down one line and at the start of the line, write -// a carriage return. -// Otherwise just write the whole sequence for moving it. -// Arguments: -// - coord: location to move the cursor to. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT XtermEngine::_MoveCursor(const til::point coord) noexcept -{ - auto hr = S_OK; - const auto originalPos = _lastText; - _trace.TraceMoveCursor(_lastText, coord); - if (coord.x != _lastText.x || coord.y != _lastText.y) - { - if (coord.x == 0 && coord.y == 0) - { - _needToDisableCursor = true; - hr = _CursorHome(); - } - else if (_resized && _resizeQuirk) - { - hr = _CursorPosition(coord); - } - else if (coord.x == 0 && coord.y == (_lastText.y + 1)) - { - // Down one line, at the start of the line. - - // If the previous line wrapped, then the cursor is already at this - // position, we just don't know it yet. Don't emit anything. - auto previousLineWrapped = false; - if (_wrappedRow.has_value()) - { - previousLineWrapped = coord.y == _wrappedRow.value() + 1; - } - - if (previousLineWrapped) - { - _trace.TraceWrapped(); - hr = S_OK; - } - else - { - std::string seq = "\r\n"; - hr = _Write(seq); - } - } - else if (_delayedEolWrap) - { - // GH#1245, GH#357 - If we were in the delayed EOL wrap state, make - // sure to _manually_ position the cursor now, with a full CUP - // sequence, don't try and be clever with \b or \r or other control - // sequences. Different terminals (conhost, gnome-terminal, wt) all - // behave differently with how the cursor behaves at an end of line. - // This is the only solution that works in all of them, and also - // works wrapped lines emitted by conpty. - // - // Make sure to do this _after_ the possible \r\n branch above, - // otherwise we might accidentally break wrapped lines (GH#405) - hr = _CursorPosition(coord); - } - else if (coord.x == 0 && coord.y == _lastText.y) - { - // Start of this line - std::string seq = "\r"; - hr = _Write(seq); - } - else if (coord.x == _lastText.x && coord.y == (_lastText.y + 1)) - { - // Down one line, same X position - std::string seq = "\n"; - hr = _Write(seq); - } - else if (coord.x == (_lastText.x - 1) && coord.y == (_lastText.y)) - { - // Back one char, same Y position - std::string seq = "\b"; - hr = _Write(seq); - } - else if (coord.y == _lastText.y && coord.x > _lastText.x) - { - // Same line, forward some distance - auto distance = coord.x - _lastText.x; - hr = _CursorForward(distance); - } - else - { - _needToDisableCursor = true; - hr = _CursorPosition(coord); - } - - if (SUCCEEDED(hr)) - { - _lastText = coord; - } - } - - _deferredCursorPos = INVALID_COORDS; - - _wrappedRow = std::nullopt; - _delayedEolWrap = false; - - return hr; -} - -// Routine Description: -// - Scrolls the existing data on the in-memory frame by the scroll region -// deltas we have collectively received through the Invalidate methods -// since the last time this was called. -// Move the cursor to the origin, and insert or delete rows as appropriate. -// The inserted rows will be blank, but marked invalid by InvalidateScroll, -// so they will later be written by PaintBufferLine. -// Arguments: -// - -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT XtermEngine::ScrollFrame() noexcept -try -{ - _trace.TraceScrollFrame(_scrollDelta); - - if (_scrollDelta.x != 0) - { - // No easy way to shift left-right. Everything needs repainting. - return InvalidateAll(); - } - if (_scrollDelta.y == 0) - { - // There's nothing to do here. Do nothing. - return S_OK; - } - - const auto dy = _scrollDelta.y; - const auto absDy = abs(dy); - - // Save the old wrap state here. We're going to clear it so that - // _MoveCursor will definitely move us to the right position. We'll - // restore the state afterwards. - const auto oldWrappedRow = _wrappedRow; - const auto oldDelayedEolWrap = _delayedEolWrap; - _delayedEolWrap = false; - _wrappedRow = std::nullopt; - - if (dy < 0) - { - // TODO GH#5228 - We could optimize this by only doing this newline work - // when there's more invalid than just the bottom line. If only the - // bottom line is invalid, then the next thing the Renderer is going to - // tell us to do is print the new line at the bottom of the viewport, - // and _MoveCursor will automatically give us the newline we want. - // When that's implemented, we'll probably want to make sure to add a - // _lastText.y += dy; - // statement here. - - // Move the cursor to the bottom of the current viewport - const auto bottom = _lastViewport.BottomInclusive(); - RETURN_IF_FAILED(_MoveCursor({ 0, bottom })); - // Emit some number of newlines to create space in the buffer. - RETURN_IF_FAILED(_Write(std::string(absDy, '\n'))); - } - else if (dy > 0) - { - // If we've scrolled _down_, then move the cursor to the top of the - // buffer, and insert some newlines using the InsertLines VT sequence - RETURN_IF_FAILED(_MoveCursor({ 0, 0 })); - RETURN_IF_FAILED(_InsertLine(absDy)); - } - - // Restore our wrap state. - _wrappedRow = oldWrappedRow; - _delayedEolWrap = oldDelayedEolWrap; - - // Shift our internal tracker of the last text position according to how - // much we've scrolled. If we manually scroll the buffer right now, by - // moving the cursor to the bottom row of the viewport and emitting a - // newline, we'll cause any wrapped lines to get broken. - // - // Instead, we'll just update our internal tracker of where the buffer - // contents are. On this frame, we'll then still move the cursor correctly - // relative to the new frame contents. To do this, we'll shift our - // coordinates we're tracking, like the row that we wrapped on and the - // position we think we left the cursor. - // - // See GH#5113 - _trace.TraceLastText(_lastText); - if (_wrappedRow.has_value()) - { - _wrappedRow.value() += dy; - _trace.TraceSetWrapped(_wrappedRow.value()); - } - - if (_delayedEolWrap && _wrappedRow.has_value()) - { - // If we wrapped the last line, and we're in the middle of painting it, - // then the newline we did above just manually broke the line. What - // we're doing here is a hack: we're going to manually re-invalidate the - // last character of the wrapped row. When the PaintBufferLine calls - // come back through, we'll paint this last character again, causing us - // to get into the wrapped state once again. This is the only way to - // ensure that if a line was wrapped, and we painted the first line in - // one frame, and the second line in another frame that included other - // changes _above_ the wrapped line, that we maintain the wrap state in - // the Terminal. - const til::rect lastCellOfWrappedRow{ - til::point{ _lastViewport.RightInclusive(), _wrappedRow.value() }, - til::size{ 1, 1 } - }; - _trace.TraceInvalidate(lastCellOfWrappedRow); - _invalidMap.set(lastCellOfWrappedRow); - } - - // If the entire viewport was invalidated this frame, don't mark the bottom - // line as new. There are cases where this can cause visual artifacts - see - // GH#5039 and ConptyRoundtripTests::ClearHostTrickeryTest - const auto allInvalidated = _invalidMap.all(); - _newBottomLine = !allInvalidated; - - // GH#5502 - keep track of the BG color we had when we emitted this new - // bottom line. If the color changes by the time we get to printing that - // line, we'll need to make sure that we don't do any optimizations like - // _removing spaces_, because the background color of the spaces will be - // important information to send to the connected Terminal. - if (_newBottomLine) - { - _newBottomLineBG = _lastTextAttributes.GetBackground(); - } - - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - Notifies us that the console is attempting to scroll the existing screen -// area. Add the top or bottom rows to the invalid region, and update the -// total scroll delta accumulated this frame. -// Arguments: -// - pcoordDelta - Pointer to character dimension (til::point) of the distance the -// console would like us to move while scrolling. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for safemath failure -[[nodiscard]] HRESULT XtermEngine::InvalidateScroll(const til::point* const pcoordDelta) noexcept -try -{ - const auto delta{ *pcoordDelta }; - - if (delta != til::point{ 0, 0 }) - { - _trace.TraceInvalidateScroll(delta); - - // Scroll the current offset and invalidate the revealed area - _invalidMap.translate(delta, true); - - _scrollDelta += delta; - } - - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - Draws one line of the buffer to the screen. Writes the characters to the -// pipe, encoded in UTF-8 or ASCII only, depending on the VtIoMode. -// (See descriptions of both implementations for details.) -// Arguments: -// - clusters - text and column counts for each piece of text. -// - coord - character coordinate target to render within viewport -// - trimLeft - This specifies whether to trim one character width off the left -// side of the output. Used for drawing the right-half only of a -// double-wide character. -// - lineWrapped: true if this run we're painting is the end of a line that -// wrapped. If we're not painting the last column of a wrapped line, then this -// will be false. -// Return Value: -// - S_OK or suitable HRESULT error from writing pipe. -[[nodiscard]] HRESULT XtermEngine::PaintBufferLine(const std::span clusters, - const til::point coord, - const bool /*trimLeft*/, - const bool lineWrapped) noexcept -{ - return _fUseAsciiOnly ? - VtEngine::_PaintAsciiBufferLine(clusters, coord) : - VtEngine::_PaintUtf8BufferLine(clusters, coord, lineWrapped); -} - -// Method Description: -// - Wrapper for _Write. Write either an ascii-only, or a -// proper utf-8 string, depending on our mode. -// Arguments: -// - wstr - wstring of text to be written -// - flush - set to true if the string should be flushed immediately -// Return Value: -// - S_OK or suitable HRESULT error from either conversion or writing pipe. -[[nodiscard]] HRESULT XtermEngine::WriteTerminalW(const std::wstring_view wstr, const bool flush) noexcept -{ - RETURN_IF_FAILED(_fUseAsciiOnly ? - VtEngine::_WriteTerminalAscii(wstr) : - VtEngine::_WriteTerminalUtf8(wstr)); - // GH#4106, GH#2011, GH#13710 - WriteTerminalW is only ever called by the - // StateMachine, when we've encountered a string we don't understand. When - // this happens, we will trigger a new frame in the renderer, and - // immediately buffer this wstr (representing the sequence we didn't - // understand). We won't immediately _Flush to the terminal - that might - // cause flickering (where we've buffered some state but not the whole - // "frame" as specified by the app). We'll just immediately buffer this - // sequence, and flush it when the render thread comes around to paint the - // frame normally, unless a flush has been explicitly requested. - if (flush) - { - _flushImpl(); - } - return S_OK; -} - -// Method Description: -// - Sends a command to set the terminal's window to visible or hidden -// Arguments: -// - showOrHide - True if show; false if hide. -// Return Value: -// - S_OK or suitable HRESULT error from either conversion or writing pipe. -[[nodiscard]] HRESULT XtermEngine::SetWindowVisibility(const bool showOrHide) noexcept -{ - if (showOrHide) - { - RETURN_IF_FAILED(_Write("\x1b[1t")); - } - else - { - RETURN_IF_FAILED(_Write("\x1b[2t")); - } - _Flush(); - return S_OK; -} - -// Method Description: -// - Updates the window's title string. Emits the VT sequence to SetWindowTitle. -// Arguments: -// - newTitle: the new string to use for the title of the window -// Return Value: -// - S_OK -[[nodiscard]] HRESULT XtermEngine::_DoUpdateTitle(const std::wstring_view newTitle) noexcept -{ - // inbox telnet uses xterm-ascii as its mode. If we're in ascii mode, don't - // do anything, to maintain compatibility. - if (_fUseAsciiOnly) - { - return S_OK; - } - - try - { - const auto converted = ConvertToA(CP_UTF8, newTitle); - return VtEngine::_ChangeTitle(converted); - } - CATCH_RETURN(); -} diff --git a/src/renderer/vt/XtermEngine.hpp b/src/renderer/vt/XtermEngine.hpp deleted file mode 100644 index 7449fb3e38b..00000000000 --- a/src/renderer/vt/XtermEngine.hpp +++ /dev/null @@ -1,82 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- XtermEngine.hpp - -Abstract: -- This is the definition of the VT specific implementation of the renderer. - This is the xterm implementation, which supports advanced sequences such as - inserting and deleting lines, but only 16 colors. - - This engine supports both xterm and xterm-ascii VT modes. - The difference being that xterm-ascii will render any characters above 0x7f - as '?', in order to support older legacy tools. - -Author(s): -- Mike Griese (migrie) 01-Sept-2017 ---*/ - -#pragma once - -#include "vtrenderer.hpp" - -namespace Microsoft::Console::Render -{ - class XtermEngine : public VtEngine - { - public: - XtermEngine(_In_ wil::unique_hfile hPipe, - const Microsoft::Console::Types::Viewport initialViewport, - const bool fUseAsciiOnly); - - virtual ~XtermEngine() override = default; - - [[nodiscard]] HRESULT StartPaint() noexcept override; - [[nodiscard]] HRESULT EndPaint() noexcept override; - - [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; - - [[nodiscard]] virtual HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, - const RenderSettings& renderSettings, - const gsl::not_null pData, - const bool usingSoftFont, - const bool isSettingDefaultBrushes) noexcept override; - [[nodiscard]] HRESULT PaintBufferLine(const std::span clusters, - const til::point coord, - const bool trimLeft, - const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT ScrollFrame() noexcept override; - - [[nodiscard]] HRESULT InvalidateScroll(const til::point* const pcoordDelta) noexcept override; - - [[nodiscard]] HRESULT WriteTerminalW(const std::wstring_view str, const bool flush) noexcept override; - - [[nodiscard]] HRESULT SetWindowVisibility(const bool showOrHide) noexcept override; - - protected: - // I'm using a non-class enum here, so that the values - // are trivially convertible and comparable to bool. - enum class Tribool : uint8_t - { - False = 0, - True, - Invalid, - }; - - const bool _fUseAsciiOnly; - bool _needToDisableCursor; - Tribool _lastCursorIsVisible; - bool _nextCursorIsVisible; - - [[nodiscard]] HRESULT _MoveCursor(const til::point coord) noexcept override; - - [[nodiscard]] HRESULT _DoUpdateTitle(const std::wstring_view newTitle) noexcept override; - -#ifdef UNIT_TESTING - friend class VtRendererTest; - friend class ConptyOutputTests; -#endif - }; -} diff --git a/src/renderer/vt/dirs b/src/renderer/vt/dirs deleted file mode 100644 index 4d3cf932b5e..00000000000 --- a/src/renderer/vt/dirs +++ /dev/null @@ -1,3 +0,0 @@ -DIRS= \ - lib \ - ut_lib \ diff --git a/src/renderer/vt/invalidate.cpp b/src/renderer/vt/invalidate.cpp deleted file mode 100644 index d67ca839437..00000000000 --- a/src/renderer/vt/invalidate.cpp +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "vtrenderer.hpp" - -using namespace Microsoft::Console::Types; -#pragma hdrstop - -using namespace Microsoft::Console::Render; - -// Routine Description: -// - Notifies us that the system has requested a particular pixel area of the -// client rectangle should be redrawn. (On WM_PAINT) -// For VT, this doesn't mean anything. So do nothing. -// Arguments: -// - prcDirtyClient - Pointer to pixel area (til::rect) of client region the system -// believes is dirty -// Return Value: -// - S_OK -[[nodiscard]] HRESULT VtEngine::InvalidateSystem(const til::rect* const /*prcDirtyClient*/) noexcept -{ - return S_OK; -} - -// Routine Description: -// - Notifies us that the console has changed the selection region and would -// like it updated -// Arguments: -// - rectangles - Vector of rectangles to draw, line by line -// Return Value: -// - S_OK -[[nodiscard]] HRESULT VtEngine::InvalidateSelection(const std::vector& /*rectangles*/) noexcept -{ - // Selection shouldn't be handled bt the VT Renderer Host, it should be - // handled by the client. - - return S_OK; -} - -// Routine Description: -// - Notifies us that the console has changed the character region specified. -// - NOTE: This typically triggers on cursor or text buffer changes -// Arguments: -// - psrRegion - Character region (til::rect) that has been changed -// Return Value: -// - S_OK, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::Invalidate(const til::rect* const psrRegion) noexcept -try -{ - _trace.TraceInvalidate(*psrRegion); - _invalidMap.set(*psrRegion); - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - Notifies us that the console has changed the position of the cursor. -// Arguments: -// - psrRegion - the region covered by the cursor -// Return Value: -// - S_OK -[[nodiscard]] HRESULT VtEngine::InvalidateCursor(const til::rect* const psrRegion) noexcept -{ - // If we just inherited the cursor, we're going to get an InvalidateCursor - // for both where the old cursor was, and where the new cursor is - // (the inherited location). (See Cursor.cpp:Cursor::SetPosition) - // We should ignore the first one, but after that, if the client application - // is moving the cursor around in the viewport, move our virtual top - // up to meet their changes. - if (!_skipCursor && _virtualTop > psrRegion->top) - { - _virtualTop = psrRegion->top; - } - _skipCursor = false; - - _cursorMoved = psrRegion->origin() != _lastCursorOrigin; - _lastCursorOrigin = psrRegion->origin(); - return S_OK; -} - -// Routine Description: -// - Notifies to repaint everything. -// - NOTE: Use sparingly. Only use when something that could affect the entire -// frame simultaneously occurs. -// Arguments: -// - -// Return Value: -// - S_OK, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::InvalidateAll() noexcept -try -{ - _trace.TraceInvalidateAll(_lastViewport.ToOrigin().ToExclusive()); - _invalidMap.set_all(); - return S_OK; -} -CATCH_RETURN(); - -// Method Description: -// - Notifies us that we're about to circle the buffer, giving us a chance to -// force a repaint before the buffer contents are lost. The VT renderer -// needs to be able to render all text before it's lost, so we return true. -// Arguments: -// - Receives a bool indicating if we should force the repaint. -// Return Value: -// - S_OK -[[nodiscard]] HRESULT VtEngine::InvalidateFlush(_In_ const bool circled, _Out_ bool* const pForcePaint) noexcept -{ - *pForcePaint = true; - - // Keep track of the fact that we circled, we'll need to do some work on - // end paint to specifically handle this. - _circled = circled; - - // If we flushed for any reason other than circling (i.e, a sequence that we - // didn't understand), we don't need to push the buffer out on EndPaint. - _noFlushOnEnd = !circled; - - _trace.TraceTriggerCircling(*pForcePaint); - return S_OK; -} - -// Method Description: -// - Notifies us that we're about to be torn down. This gives us a last chance -// to force a repaint before the buffer contents are lost. The VT renderer -// needs to be able to render all text before it's lost, so we return true. -// Arguments: -// - Receives a bool indicating if we should force the repaint. -// Return Value: -// - S_OK -[[nodiscard]] HRESULT VtEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept -{ - // This must be kept in sync with RequestWin32Input(). - // It ensures that we disable the modes that we requested on startup. - // Linux shells for instance don't understand the win32-input-mode 9001. - // - // This can be here, instead of being appended at the end of this final rendering pass, - // because these two states happen to have no influence on the caller's VT parsing. - std::ignore = _Write("\033[?9001l\033[?1004l"); - - *pForcePaint = true; - return S_OK; -} diff --git a/src/renderer/vt/lib/sources b/src/renderer/vt/lib/sources deleted file mode 100644 index b2783896d4f..00000000000 --- a/src/renderer/vt/lib/sources +++ /dev/null @@ -1,8 +0,0 @@ -!include ..\sources.inc - -# ------------------------------------- -# Program Information -# ------------------------------------- - -TARGETNAME = ConRenderVt -TARGETTYPE = LIBRARY diff --git a/src/renderer/vt/lib/sources.dep b/src/renderer/vt/lib/sources.dep deleted file mode 100644 index bc61c95c6b0..00000000000 --- a/src/renderer/vt/lib/sources.dep +++ /dev/null @@ -1,3 +0,0 @@ -BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ - diff --git a/src/renderer/vt/lib/vt.vcxproj b/src/renderer/vt/lib/vt.vcxproj deleted file mode 100644 index 37598326831..00000000000 --- a/src/renderer/vt/lib/vt.vcxproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - {990F2657-8580-4828-943F-5DD657D11842} - Win32Proj - vt - RendererVt - ConRenderVt - StaticLibrary - - - - - - - - - diff --git a/src/renderer/vt/lib/vt.vcxproj.filters b/src/renderer/vt/lib/vt.vcxproj.filters deleted file mode 100644 index 31413c8a323..00000000000 --- a/src/renderer/vt/lib/vt.vcxproj.filters +++ /dev/null @@ -1,42 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - \ No newline at end of file diff --git a/src/renderer/vt/math.cpp b/src/renderer/vt/math.cpp deleted file mode 100644 index 354ca0e9925..00000000000 --- a/src/renderer/vt/math.cpp +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "vtrenderer.hpp" - -#pragma hdrstop - -using namespace Microsoft::Console::Render; -using namespace Microsoft::Console::Types; - -// Routine Description: -// - Gets the size in characters of the current dirty portion of the frame. -// Arguments: -// - area - The character dimensions of the current dirty area of the frame. -// This is an Inclusive rect. -// Return Value: -// - S_OK. -[[nodiscard]] HRESULT VtEngine::GetDirtyArea(std::span& area) noexcept -{ - area = _invalidMap.runs(); - return S_OK; -} - -// Routine Description: -// - Uses the currently selected font to determine how wide the given character will be when rendered. -// - NOTE: Only supports determining half-width/full-width status for CJK-type languages (e.g. is it 1 character wide or 2. a.k.a. is it a rectangle or square.) -// Arguments: -// - glyph - utf16 encoded codepoint to check -// - pResult - receives return value, True if it is full-width (2 wide). False if it is half-width (1 wide). -// Return Value: -// - S_FALSE: This is unsupported by the VT Renderer and should use another engine's value. -[[nodiscard]] HRESULT VtEngine::IsGlyphWideByFont(const std::wstring_view /*glyph*/, _Out_ bool* const pResult) noexcept -{ - *pResult = false; - return S_FALSE; -} - -// Routine Description: -// - Performs a "CombineRect" with the "OR" operation. -// - Basically extends the existing rect outward to also encompass the passed-in region. -// Arguments: -// - pRectExisting - Expand this rectangle to encompass the add rect. -// - pRectToOr - Add this rectangle to the existing one. -// Return Value: -// - -void VtEngine::_OrRect(_Inout_ til::inclusive_rect* const pRectExisting, const til::inclusive_rect* const pRectToOr) const -{ - pRectExisting->left = std::min(pRectExisting->left, pRectToOr->left); - pRectExisting->top = std::min(pRectExisting->top, pRectToOr->top); - pRectExisting->right = std::max(pRectExisting->right, pRectToOr->right); - pRectExisting->bottom = std::max(pRectExisting->bottom, pRectToOr->bottom); -} diff --git a/src/renderer/vt/paint.cpp b/src/renderer/vt/paint.cpp deleted file mode 100644 index 69985b27eec..00000000000 --- a/src/renderer/vt/paint.cpp +++ /dev/null @@ -1,722 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "vtrenderer.hpp" -#include "../../inc/conattrs.hpp" -#include "../../types/inc/convert.hpp" - -#pragma hdrstop -using namespace Microsoft::Console::Render; -using namespace Microsoft::Console::Types; - -// Routine Description: -// - Prepares internal structures for a painting operation. -// Arguments: -// - -// Return Value: -// - S_OK if we started to paint. S_FALSE if we didn't need to paint. -// HRESULT error code if painting didn't start successfully. -[[nodiscard]] HRESULT VtEngine::StartPaint() noexcept -{ - // When unit testing, there may be no pipe, but we still need to paint. - if (!_hFile) - { - return S_OK; - } - - // If we're using line renditions, and this is a full screen paint, we can - // potentially stop using them at the end of this frame. - _stopUsingLineRenditions = _usingLineRenditions && _AllIsInvalid(); - - // If there's nothing to do, we won't need to paint. - auto somethingToDo = _invalidMap.any() || - _scrollDelta != til::point{ 0, 0 } || - _cursorMoved || - _titleChanged; - - _trace.TraceStartPaint(!somethingToDo, - _invalidMap, - _lastViewport.ToExclusive(), - _scrollDelta, - _cursorMoved, - _wrappedRow); - - return somethingToDo ? S_OK : S_FALSE; -} - -// Routine Description: -// - EndPaint helper to perform the final cleanup after painting. If we -// returned S_FALSE from StartPaint, there's no guarantee this was called. -// That's okay however, EndPaint only zeros structs that would be zero if -// StartPaint returns S_FALSE. -// Arguments: -// - -// Return Value: -// - S_OK, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::EndPaint() noexcept -{ - _trace.TraceEndPaint(); - - _invalidMap.reset_all(); - - _scrollDelta = { 0, 0 }; - _clearedAllThisFrame = false; - _cursorMoved = false; - _firstPaint = false; - _skipCursor = false; - _resized = false; - // If we've circled the buffer this frame, move our virtual top upwards. - // We do this at the END of the frame, so that during the paint, we still - // use the original virtual top. - if (_circled) - { - if (_virtualTop > 0) - { - _virtualTop--; - } - } - _circled = false; - - // If _stopUsingLineRenditions is still true at the end of the frame, that - // means we've refreshed the entire viewport with every line being single - // width, so we can safely stop using them from now on. - if (_stopUsingLineRenditions) - { - _usingLineRenditions = false; - } - - // If we deferred a cursor movement during the frame, make sure we put the - // cursor in the right place before we end the frame. - if (_deferredCursorPos != INVALID_COORDS) - { - RETURN_IF_FAILED(_MoveCursor(_deferredCursorPos)); - } - - // If this frame was triggered because we encountered a VT sequence which - // required the buffered state to get printed, we don't want to flush this - // frame to the pipe. That might result in us rendering half the output of a - // particular frame (as emitted by the client). - // - // Instead, we'll leave this frame in _buffer, and just keep appending to - // it as needed. - if (!_noFlushOnEnd) - { - _Flush(); - } - - _noFlushOnEnd = false; - return S_OK; -} - -// Routine Description: -// - Used to perform longer running presentation steps outside the lock so the -// other threads can continue. -// - Not currently used by VtEngine. -// Arguments: -// - -// Return Value: -// - S_FALSE since we do nothing. -[[nodiscard]] HRESULT VtEngine::Present() noexcept -{ - return S_FALSE; -} - -[[nodiscard]] HRESULT VtEngine::ResetLineTransform() noexcept -{ - return S_FALSE; -} - -[[nodiscard]] HRESULT VtEngine::PrepareLineTransform(const LineRendition lineRendition, - const til::CoordType targetRow, - const til::CoordType /*viewportLeft*/) noexcept -{ - // We don't want to waste bandwidth writing out line rendition attributes - // until we know they're in use. But once they are in use, we have to keep - // applying them on every line until we know they definitely aren't being - // used anymore (we check that at the end of any fullscreen paint). - if (lineRendition != LineRendition::SingleWidth) - { - _stopUsingLineRenditions = false; - _usingLineRenditions = true; - } - // One simple optimization is that we can skip sending the line attributes - // when we're writing out a single character, which should preclude there - // being a rendition switch. - if (_usingLineRenditions && !_invalidMap.one()) - { - RETURN_IF_FAILED(_MoveCursor({ _lastText.x, targetRow })); - switch (lineRendition) - { - case LineRendition::SingleWidth: - return _Write("\x1b#5"); - case LineRendition::DoubleWidth: - return _Write("\x1b#6"); - case LineRendition::DoubleHeightTop: - return _Write("\x1b#3"); - case LineRendition::DoubleHeightBottom: - return _Write("\x1b#4"); - } - } - return S_OK; -} - -// Routine Description: -// - Paints the background of the invalid area of the frame. -// Arguments: -// - -// Return Value: -// - S_OK -[[nodiscard]] HRESULT VtEngine::PaintBackground() noexcept -{ - return S_OK; -} - -// Routine Description: -// - Draws one line of the buffer to the screen. Writes the characters to the -// pipe. If the characters are outside the ASCII range (0-0x7f), then -// instead writes a '?' -// Arguments: -// - clusters - text and column count data to be written -// - trimLeft - This specifies whether to trim one character width off the left -// side of the output. Used for drawing the right-half only of a -// double-wide character. -// - lineWrapped: true if this run we're painting is the end of a line that -// wrapped. If we're not painting the last column of a wrapped line, then this -// will be false. -// Return Value: -// - S_OK or suitable HRESULT error from writing pipe. -[[nodiscard]] HRESULT VtEngine::PaintBufferLine(const std::span clusters, - const til::point coord, - const bool /*trimLeft*/, - const bool /*lineWrapped*/) noexcept -{ - return VtEngine::_PaintAsciiBufferLine(clusters, coord); -} - -// Method Description: -// - Draws up to one line worth of grid lines on top of characters. -// Arguments: -// - lines - Enum defining which edges of the rectangle to draw -// - gridlineColor - The color to use for drawing the gridlines. -// - underlineColor - The color to use for drawing the underlines. -// - cchLine - How many characters we should draw the grid lines along (left to right in a row) -// - coordTarget - The starting X/Y position of the first character to draw on. -// Return Value: -// - S_OK -[[nodiscard]] HRESULT VtEngine::PaintBufferGridLines(const GridLineSet /*lines*/, - const COLORREF /*gridlineColor*/, - const COLORREF /*underlineColor*/, - const size_t /*cchLine*/, - const til::point /*coordTarget*/) noexcept -{ - return S_OK; -} - -// Routine Description: -// - Draws the cursor on the screen -// Arguments: -// - options - Options that affect the presentation of the cursor -// Return Value: -// - S_OK or suitable HRESULT error from writing pipe. -[[nodiscard]] HRESULT VtEngine::PaintCursor(const CursorOptions& options) noexcept -{ - _trace.TracePaintCursor(options.coordCursor); - - // GH#17270: If the wrappedRow field is set, and the target cursor position - // is at the start of the next row, it's expected that any subsequent output - // would already be written to that location, so the _MoveCursor method may - // decide it doesn't need to do anything. In this case, though, we're not - // writing anything else, so the cursor will end up in the wrong location at - // the end of the frame. Clearing the wrappedRow field fixes that. - _wrappedRow = std::nullopt; - _trace.TraceClearWrapped(); - - // MSFT:15933349 - Send the terminal the updated cursor information, if it's changed. - LOG_IF_FAILED(_MoveCursor(options.coordCursor)); - - return S_OK; -} - -// Routine Description: -// - Inverts the selected region on the current screen buffer. -// - Reads the selected area, selection mode, and active screen buffer -// from the global properties and dispatches a GDI invert on the selected text area. -// Because the selection is the responsibility of the terminal, and not the -// host, render nothing. -// Arguments: -// - rect - Rectangle to invert or highlight to make the selection area -// Return Value: -// - S_OK -[[nodiscard]] HRESULT VtEngine::PaintSelection(const til::rect& /*rect*/) noexcept -{ - return S_OK; -} - -// Routine Description: -// - Write a VT sequence to change the current colors of text. Writes true RGB -// color sequences. -// Arguments: -// - textAttributes: Text attributes to use for the colors. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_RgbUpdateDrawingBrushes(const TextAttribute& textAttributes) noexcept -{ - const auto fg = textAttributes.GetForeground(); - const auto bg = textAttributes.GetBackground(); - const auto ul = textAttributes.GetUnderlineColor(); - auto lastFg = _lastTextAttributes.GetForeground(); - auto lastBg = _lastTextAttributes.GetBackground(); - auto lastUl = _lastTextAttributes.GetUnderlineColor(); - - // If the FG, BG and UL should be the defaults, emit an SGR reset. - if (fg.IsDefault() && bg.IsDefault() && ul.IsDefault() && !(lastFg.IsDefault() && lastBg.IsDefault() && lastUl.IsDefault())) - { - // SGR Reset will clear all attributes (except hyperlink ID) - which means - // we cannot reset _lastTextAttributes by simply doing - // _lastTextAttributes = {}; - // because we want to retain the last hyperlink ID - RETURN_IF_FAILED(_SetGraphicsDefault()); - _lastTextAttributes.SetDefaultBackground(); - _lastTextAttributes.SetDefaultForeground(); - _lastTextAttributes.SetDefaultUnderlineColor(); - _lastTextAttributes.SetDefaultRenditionAttributes(); - lastFg = {}; - lastBg = {}; - lastUl = {}; - } - - if (fg != lastFg) - { - if (fg.IsDefault()) - { - RETURN_IF_FAILED(_SetGraphicsRenditionDefaultColor(true)); - } - else if (fg.IsIndex16()) - { - RETURN_IF_FAILED(_SetGraphicsRendition16Color(fg.GetIndex(), true)); - } - else if (fg.IsIndex256()) - { - RETURN_IF_FAILED(_SetGraphicsRendition256Color(fg.GetIndex(), true)); - } - else if (fg.IsRgb()) - { - RETURN_IF_FAILED(_SetGraphicsRenditionRGBColor(fg.GetRGB(), true)); - } - _lastTextAttributes.SetForeground(fg); - } - - if (bg != lastBg) - { - if (bg.IsDefault()) - { - RETURN_IF_FAILED(_SetGraphicsRenditionDefaultColor(false)); - } - else if (bg.IsIndex16()) - { - RETURN_IF_FAILED(_SetGraphicsRendition16Color(bg.GetIndex(), false)); - } - else if (bg.IsIndex256()) - { - RETURN_IF_FAILED(_SetGraphicsRendition256Color(bg.GetIndex(), false)); - } - else if (bg.IsRgb()) - { - RETURN_IF_FAILED(_SetGraphicsRenditionRGBColor(bg.GetRGB(), false)); - } - _lastTextAttributes.SetBackground(bg); - } - - if (ul != lastUl) - { - if (ul.IsDefault()) - { - RETURN_IF_FAILED(_SetGraphicsRenditionUnderlineDefaultColor()); - } - else if (ul.IsIndex16()) // underline can't be 16 color - { - /* do nothing */ - } - else if (ul.IsIndex256()) - { - RETURN_IF_FAILED(_SetGraphicsRenditionUnderline256Color(ul.GetIndex())); - } - else if (ul.IsRgb()) - { - RETURN_IF_FAILED(_SetGraphicsRenditionUnderlineRGBColor(ul.GetRGB())); - } - _lastTextAttributes.SetUnderlineColor(ul); - } - - return S_OK; -} - -// Routine Description: -// - Write a VT sequence to change the current colors of text. It will try to -// find ANSI colors that are nearest to the input colors, and write those -// indices to the pipe. -// Arguments: -// - textAttributes: Text attributes to use for the colors. -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -[[nodiscard]] HRESULT VtEngine::_16ColorUpdateDrawingBrushes(const TextAttribute& textAttributes) noexcept -{ - const auto fg = textAttributes.GetForeground(); - const auto bg = textAttributes.GetBackground(); - auto lastFg = _lastTextAttributes.GetForeground(); - auto lastBg = _lastTextAttributes.GetBackground(); - - // If either FG or BG have changed to default, emit a SGR reset. - // We can't reset FG and BG to default individually. - if ((fg.IsDefault() && !lastFg.IsDefault()) || (bg.IsDefault() && !lastBg.IsDefault())) - { - // SGR Reset will clear all attributes (except hyperlink ID) - which means - // we cannot reset _lastTextAttributes by simply doing - // _lastTextAttributes = {}; - // because we want to retain the last hyperlink ID - RETURN_IF_FAILED(_SetGraphicsDefault()); - _lastTextAttributes.SetDefaultBackground(); - _lastTextAttributes.SetDefaultForeground(); - _lastTextAttributes.SetDefaultRenditionAttributes(); - lastFg = {}; - lastBg = {}; - } - - // We use the legacy color calculations to generate an approximation of the - // colors in the Windows 16-color table, but we need to transpose those - // values to obtain an index in an ANSI-compatible order. - auto fgIndex = TextColor::TransposeLegacyIndex(fg.GetLegacyIndex(0)); - auto bgIndex = TextColor::TransposeLegacyIndex(bg.GetLegacyIndex(0)); - - // If the intense attribute is set, and the foreground can be brightened, then do so. - const auto brighten = textAttributes.IsIntense() && fg.CanBeBrightened(); - fgIndex |= (brighten ? FOREGROUND_INTENSITY : 0); - - // To actually render bright colors, though, we need to use SGR intense. - const auto needIntense = fgIndex > 7; - if (needIntense != _lastTextAttributes.IsIntense()) - { - RETURN_IF_FAILED(_SetIntense(needIntense)); - _lastTextAttributes.SetIntense(needIntense); - } - - // After which we drop the high bits, since only colors 0 to 7 are supported. - - fgIndex &= 7; - bgIndex &= 7; - - if (!fg.IsDefault() && (lastFg.IsDefault() || fgIndex != lastFg.GetIndex())) - { - RETURN_IF_FAILED(_SetGraphicsRendition16Color(fgIndex, true)); - _lastTextAttributes.SetIndexedForeground(fgIndex); - } - - if (!bg.IsDefault() && (lastBg.IsDefault() || bgIndex != lastBg.GetIndex())) - { - RETURN_IF_FAILED(_SetGraphicsRendition16Color(bgIndex, false)); - _lastTextAttributes.SetIndexedBackground(bgIndex); - } - - return S_OK; -} - -// Routine Description: -// - Draws one line of the buffer to the screen. Writes the characters to the -// pipe. If the characters are outside the ASCII range (0-0x7f), then -// instead writes a '?'. -// This is needed because the Windows internal telnet client implementation -// doesn't know how to handle >ASCII characters. The old telnetd would -// just replace them with '?' characters. If we render the >ASCII -// characters to telnet, it will likely end up drawing them wrong, which -// will make the client appear buggy and broken. -// Arguments: -// - clusters - text and column width data to be written -// - coord - character coordinate target to render within viewport -// Return Value: -// - S_OK or suitable HRESULT error from writing pipe. -[[nodiscard]] HRESULT VtEngine::_PaintAsciiBufferLine(const std::span clusters, - const til::point coord) noexcept -{ - try - { - RETURN_IF_FAILED(_MoveCursor(coord)); - - _bufferLine.clear(); - _bufferLine.reserve(clusters.size()); - - til::CoordType totalWidth = 0; - for (const auto& cluster : clusters) - { - _bufferLine.append(cluster.GetText()); - totalWidth += cluster.GetColumns(); - } - - RETURN_IF_FAILED(VtEngine::_WriteTerminalAscii(_bufferLine)); - - // Update our internal tracker of the cursor's position - _lastText.x += totalWidth; - - return S_OK; - } - CATCH_RETURN(); -} - -// Routine Description: -// - Draws one line of the buffer to the screen. Writes the characters to the -// pipe, encoded in UTF-8. -// Arguments: -// - clusters - text and column widths to be written -// - coord - character coordinate target to render within viewport -// Return Value: -// - S_OK or suitable HRESULT error from writing pipe. -[[nodiscard]] HRESULT VtEngine::_PaintUtf8BufferLine(const std::span clusters, - const til::point coord, - const bool lineWrapped) noexcept -{ - if (coord.y < _virtualTop) - { - return S_OK; - } - - _bufferLine.clear(); - _bufferLine.reserve(clusters.size()); - til::CoordType totalWidth = 0; - for (const auto& cluster : clusters) - { - _bufferLine.append(cluster.GetText()); - totalWidth += cluster.GetColumns(); - } - - // If any of the values in the buffer are C0 or C1 controls, we need to - // convert them to printable codepoints, otherwise they'll end up being - // evaluated as control characters by the receiving terminal. We use the - // DOS 437 code page for the C0 controls and DEL, and just a `?` for the - // C1 controls, since that's what you would most likely have seen in the - // legacy v1 console with raster fonts. - const auto cchLine = _bufferLine.size(); - std::for_each_n(_bufferLine.begin(), cchLine, [](auto& ch) { - static constexpr std::wstring_view C0Glyphs = L" ☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼"; - if (ch < C0Glyphs.size()) - { - ch = til::at(C0Glyphs, ch); - } - else if (ch >= L'\u007F' && ch < L'\u00A0') - { - ch = (ch == L'\u007F' ? L'⌂' : L'?'); - } - }); - - const auto spaceIndex = _bufferLine.find_last_not_of(L' '); - const auto foundNonspace = spaceIndex != decltype(_bufferLine)::npos; - const auto nonSpaceLength = foundNonspace ? spaceIndex + 1 : 0; - - // Examples: - // - " ": - // cch = 2, spaceIndex = 0, foundNonSpace = false - // cch-nonSpaceLength = 2 - // - "A " - // cch = 2, spaceIndex = 0, foundNonSpace = true - // cch-nonSpaceLength = 1 - // - "AA" - // cch = 2, spaceIndex = 1, foundNonSpace = true - // cch-nonSpaceLength = 0 - const auto numSpaces = gsl::narrow_cast(cchLine - nonSpaceLength); - - // Optimizations: - // If there are lots of spaces at the end of the line, we can try to Erase - // Character that number of spaces, then move the cursor forward (to - // where it would be if we had written the spaces) - // An erase character and move right sequence is 8 chars, and possibly 10 - // (if there are at least 10 spaces, 2 digits to print) - // ESC [ %d X ESC [ %d C - // ESC [ %d %d X ESC [ %d %d C - // So we need at least 9 spaces for the optimized sequence to make sense. - // Also, if we already erased the entire display this frame, then - // don't do ANYTHING with erasing at all. - - // Note: We're only doing these optimizations along the UTF-8 path, because - // the inbox telnet client doesn't understand the Erase Character sequence, - // and it uses xterm-ascii. This ensures that xterm and -256color consumers - // get the enhancements, and telnet isn't broken. - // - // GH#13229: ECH and EL don't fill the space with visual attributes like - // underline, reverse video, hyperlinks, etc. If these spaces had those - // attrs, then don't try and optimize them out. - const auto optimalToUseECH = numSpaces > ERASE_CHARACTER_STRING_LENGTH; - const auto useEraseChar = (optimalToUseECH) && - (!_newBottomLine) && - (!_clearedAllThisFrame) && - (!_lastTextAttributes.HasAnyVisualAttributes()); - const auto printingBottomLine = coord.y == _lastViewport.BottomInclusive(); - - // GH#5502 - If the background color of the "new bottom line" is different - // than when we emitted the line, we can't optimize out the spaces from it. - // We'll still need to emit those spaces, so that the connected terminal - // will have the same background color on those blank cells. - const auto bgMatched = _newBottomLineBG.has_value() ? (_newBottomLineBG.value() == _lastTextAttributes.GetBackground()) : true; - - // If we're not using erase char, but we did erase all at the start of the - // frame, don't add spaces at the end. - // - // GH#5161: Only removeSpaces when we're in the _newBottomLine state and the - // line we're trying to print right now _actually is the bottom line_ - // - // GH#5291: DON'T remove spaces when the row wrapped. We might need those - // spaces to preserve the wrap state of this line, or the cursor position. - // For example, vim.exe uses "~ "... to clear the line, and then leaves - // the lines _wrapped_. It doesn't care to manually break the lines, but if - // we trimmed the spaces off here, we'd print all the "~"s one after another - // on the same line. - static const TextAttribute defaultAttrs{}; - const auto removeSpaces = !lineWrapped && (useEraseChar // we determined earlier that ECH is optimal - || (_clearedAllThisFrame && _lastTextAttributes == defaultAttrs) // OR we cleared the last frame to the default attributes (specifically) - || (_newBottomLine && printingBottomLine && bgMatched)); // OR we just scrolled a new line onto the bottom of the screen with the correct attributes - const auto cchActual = removeSpaces ? nonSpaceLength : cchLine; - - const auto columnsActual = removeSpaces ? - (totalWidth - numSpaces) : - totalWidth; - - if (cchActual == 0) - { - // If the previous row wrapped, but this line is empty, then we actually - // do want to move the cursor down. Otherwise, we'll possibly end up - // accidentally erasing the last character from the previous line, as - // the cursor is still waiting on that character for the next character - // to follow it. - // - // GH#5839 - If we've emitted a wrapped row, because the cursor is - // sitting just past the last cell of the previous row, if we execute a - // EraseCharacter or EraseLine here, then the row won't actually get - // cleared here. This logic is important to make sure that the cursor is - // in the right position before we do that. - - _wrappedRow = std::nullopt; - _trace.TraceClearWrapped(); - } - - // Move the cursor to the start of this run. - RETURN_IF_FAILED(_MoveCursor(coord)); - - // Write the actual text string. If we're using a soft font, the character - // set should have already been selected, so we just need to map our internal - // representation back to ASCII (handled by the _WriteTerminalDrcs method). - if (_usingSoftFont) [[unlikely]] - { - RETURN_IF_FAILED(VtEngine::_WriteTerminalDrcs({ _bufferLine.data(), cchActual })); - } - else - { - RETURN_IF_FAILED(VtEngine::_WriteTerminalUtf8({ _bufferLine.data(), cchActual })); - } - - // GH#4415, GH#5181 - // If the renderer told us that this was a wrapped line, then mark - // that we've wrapped this line. The next time we attempt to move the - // cursor, if we're trying to move it to the start of the next line, - // we'll remember that this line was wrapped, and not manually break the - // line. - if (lineWrapped) - { - _wrappedRow = coord.y; - _trace.TraceSetWrapped(coord.y); - } - - // Update our internal tracker of the cursor's position. - // See MSFT:20266233 (which is also GH#357) - // If the cursor is at the rightmost column of the terminal, and we write a - // space, the cursor won't actually move to the next cell (which would - // be {0, _lastText.y++}). The cursor will stay visibly in that last - // cell until then next character is output. - // If in that case, we increment the cursor position here (such that the X - // position would be one past the right of the terminal), when we come - // back through to MoveCursor in the last PaintCursor of the frame, - // we'll determine that we need to emit a \b to put the cursor in the - // right position. This is wrong, and will cause us to move the cursor - // back one character more than we wanted. - // - // GH#1245: This needs to be RightExclusive, _not_ inclusive. Otherwise, we - // won't update our internal cursor position tracker correctly at the last - // character of the row. - if (_lastText.x < _lastViewport.RightExclusive()) - { - _lastText.x += columnsActual; - } - // GH#1245: If we wrote the exactly last char of the row, then we're in the - // "delayed EOL wrap" state. Different terminals (conhost, gnome-terminal, - // wt) all behave differently with how the cursor behaves at an end of line. - // Mark that we're in the delayed EOL wrap state - we don't want to be - // clever about how we move the cursor in this state, since different - // terminals will handle a backspace differently in this state. - if (_lastText.x >= _lastViewport.RightInclusive()) - { - _delayedEolWrap = true; - } - - if (useEraseChar) - { - // ECH doesn't actually move the cursor itself. However, we think that - // the cursor *should* be at the end of the area we just erased. Stash - // that position as our new deferred position. If we don't move the - // cursor somewhere else before the end of the frame, we'll move the - // cursor to the deferred position at the end of the frame, or right - // before we need to print new text. - _deferredCursorPos = { _lastText.x + numSpaces, _lastText.y }; - - if (_deferredCursorPos.x <= _lastViewport.RightInclusive()) - { - RETURN_IF_FAILED(_EraseCharacter(numSpaces)); - } - // If we're past the end of the row (i.e. in the "delayed EOL wrap" - // state), then there is no need to erase the rest of line. In fact - // if we did output an EL sequence at this point, it could reset the - // "delayed EOL wrap" state, breaking subsequent output. - else if (_lastText.x <= _lastViewport.RightInclusive()) - { - RETURN_IF_FAILED(_EraseLine()); - } - } - else if (_newBottomLine && printingBottomLine) - { - // If we're on a new line, then we don't need to erase the line. The - // line is already empty. - if (optimalToUseECH) - { - _deferredCursorPos = { _lastText.x + numSpaces, _lastText.y }; - } - else if (numSpaces > 0 && removeSpaces) // if we deleted the spaces... re-add them - { - // TODO GH#5430 - Determine why and when we would do this. - auto spaces = std::wstring(numSpaces, L' '); - RETURN_IF_FAILED(VtEngine::_WriteTerminalUtf8(spaces)); - - _lastText.x += numSpaces; - } - } - - // If we printed to the bottom line, and we previously thought that this was - // a new bottom line, it certainly isn't new any longer. - if (printingBottomLine) - { - _newBottomLine = false; - _newBottomLineBG = std::nullopt; - } - - return S_OK; -} - -// Method Description: -// - Updates the window's title string. Emits the VT sequence to SetWindowTitle. -// Because wintelnet does not understand these sequences by default, we -// don't do anything by default. Other modes can implement if they support -// the sequence. -// Arguments: -// - newTitle: the new string to use for the title of the window -// Return Value: -// - S_OK -[[nodiscard]] HRESULT VtEngine::_DoUpdateTitle(const std::wstring_view /*newTitle*/) noexcept -{ - return S_OK; -} diff --git a/src/renderer/vt/precomp.cpp b/src/renderer/vt/precomp.cpp deleted file mode 100644 index c51e9b31b2f..00000000000 --- a/src/renderer/vt/precomp.cpp +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" diff --git a/src/renderer/vt/precomp.h b/src/renderer/vt/precomp.h deleted file mode 100644 index b560c6e0ebc..00000000000 --- a/src/renderer/vt/precomp.h +++ /dev/null @@ -1,33 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- precomp.h - -Abstract: -- Contains external headers to include in the precompile phase of console build process. -- Avoid including internal project headers. Instead include them only in the classes that need them (helps with test project building). ---*/ - -#include -#include - -// This includes support libraries from the CRT, STL, WIL, and GSL -#include "LibraryIncludes.h" - -#include -#include - -#if defined(DEBUG) || defined(_DEBUG) || defined(DBG) -#define WHEN_DBG(x) x -#else -#define WHEN_DBG(x) -#endif - -// SafeMath -#pragma prefast(push) -#pragma prefast(disable : 26071, "Range violation in Intsafe. Not ours.") -#define ENABLE_INTSAFE_SIGNED_FUNCTIONS // Only unsigned intsafe math/casts available without this def -#include -#pragma prefast(pop) diff --git a/src/renderer/vt/sources.inc b/src/renderer/vt/sources.inc deleted file mode 100644 index cad591f35f8..00000000000 --- a/src/renderer/vt/sources.inc +++ /dev/null @@ -1,38 +0,0 @@ -!include ..\..\..\project.inc - -# ------------------------------------- -# Windows Console -# - Console Renderer for VT -# ------------------------------------- - -# This module provides a rendering engine implementation that -# renders the display to an outgoing VT stream. - -# ------------------------------------- -# Build System Settings -# ------------------------------------- - -# Code in the OneCore depot automatically excludes default Win32 libraries. - -# ------------------------------------- -# Sources, Headers, and Libraries -# ------------------------------------- - -PRECOMPILED_CXX = 1 -PRECOMPILED_INCLUDE = ..\precomp.h - -SOURCES = \ - ..\invalidate.cpp \ - ..\math.cpp \ - ..\paint.cpp \ - ..\state.cpp \ - ..\tracing.cpp \ - ..\XtermEngine.cpp \ - ..\Xterm256Engine.cpp \ - ..\VtSequences.cpp \ - -INCLUDES = \ - $(INCLUDES); \ - ..; \ - ..\..\..\inc; \ - $(MINWIN_INTERNAL_PRIV_SDK_INC_PATH_L); \ diff --git a/src/renderer/vt/state.cpp b/src/renderer/vt/state.cpp deleted file mode 100644 index f192536e3e6..00000000000 --- a/src/renderer/vt/state.cpp +++ /dev/null @@ -1,554 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "vtrenderer.hpp" -#include "../../inc/conattrs.hpp" -#include "../../host/VtIo.hpp" - -// For _vcprintf -#include -#include - -#pragma hdrstop - -using namespace Microsoft::Console; -using namespace Microsoft::Console::Render; -using namespace Microsoft::Console::Types; - -constexpr til::point VtEngine::INVALID_COORDS = { -1, -1 }; - -// Routine Description: -// - Creates a new VT-based rendering engine -// - NOTE: Will throw if initialization failure. Caller must catch. -// Arguments: -// - -// Return Value: -// - An instance of a Renderer. -VtEngine::VtEngine(_In_ wil::unique_hfile pipe, - const Viewport initialViewport) : - RenderEngineBase(), - _hFile(std::move(pipe)), - _usingLineRenditions(false), - _stopUsingLineRenditions(false), - _usingSoftFont(false), - _lastTextAttributes(INVALID_COLOR, INVALID_COLOR, INVALID_COLOR), - _lastViewport(initialViewport), - _pool(til::pmr::get_default_resource()), - _invalidMap(initialViewport.Dimensions(), false, &_pool), - _scrollDelta(0, 0), - _clearedAllThisFrame(false), - _cursorMoved(false), - _resized(false), - _suppressResizeRepaint(true), - _virtualTop(0), - _circled(false), - _firstPaint(true), - _skipCursor(false), - _terminalOwner{ nullptr }, - _newBottomLine{ false }, - _deferredCursorPos{ INVALID_COORDS }, - _trace{}, - _bufferLine{}, - _buffer{}, - _formatBuffer{}, - _conversionBuffer{}, - _pfnSetLookingForDSR{} -{ -#ifndef UNIT_TESTING - // When unit testing, we can instantiate a VtEngine without a pipe. - THROW_HR_IF(E_HANDLE, !_hFile); -#else - // member is only defined when UNIT_TESTING is. - _usingTestCallback = false; -#endif -} - -// Method Description: -// - Writes a fill of characters to our file handle (repeat of same character over and over) -[[nodiscard]] HRESULT VtEngine::_WriteFill(const size_t n, const char c) noexcept -try -{ - _trace.TraceStringFill(n, c); -#ifdef UNIT_TESTING - if (_usingTestCallback) - { - const std::string str(n, c); - // Try to get the last error. If that wasn't set, then the test probably - // doesn't set last error. No matter. We'll just return with E_FAIL - // then. This is a unit test, we don't particularly care. - const auto succeeded = _pfnTestCallback(str.data(), str.size()); - auto hr = E_FAIL; - if (!succeeded) - { - const auto err = ::GetLastError(); - // If there wasn't an error in GLE, just use E_FAIL - hr = SUCCEEDED_WIN32(err) ? hr : HRESULT_FROM_WIN32(err); - } - return succeeded ? S_OK : hr; - } -#endif - - // TODO GH10001: Replace me with REP - _buffer.append(n, c); - return S_OK; -} -CATCH_RETURN(); - -// Method Description: -// - Writes the characters to our file handle. If we're building the unit tests, -// we can instead write to the test callback, in order to avoid needing to -// set up pipes and threads for unit tests. -// Arguments: -// - str: The buffer to write to the pipe. Might have nulls in it. -// Return Value: -// - S_OK or suitable HRESULT error from writing pipe. -[[nodiscard]] HRESULT VtEngine::_Write(std::string_view const str) noexcept -{ - _trace.TraceString(str); -#ifdef UNIT_TESTING - if (_usingTestCallback) - { - // Try to get the last error. If that wasn't set, then the test probably - // doesn't set last error. No matter. We'll just return with E_FAIL - // then. This is a unit test, we don't particularly care. - const auto succeeded = _pfnTestCallback(str.data(), str.size()); - auto hr = E_FAIL; - if (!succeeded) - { - const auto err = ::GetLastError(); - // If there wasn't an error in GLE, just use E_FAIL - hr = SUCCEEDED_WIN32(err) ? hr : HRESULT_FROM_WIN32(err); - } - return succeeded ? S_OK : hr; - } -#endif - - try - { - _buffer.append(str); - - return S_OK; - } - CATCH_RETURN(); -} - -void VtEngine::_Flush() noexcept -{ - if (_buffer.empty()) - { - return; - } - - if (!_corked) - { - _flushImpl(); - return; - } - - // Defer the flush until someone calls Cork(false). - _flushRequested = true; -} - -// _corked is often true and separating _flushImpl() out allows _flush() to be inlined. -void VtEngine::_flushImpl() noexcept -{ - if (_hFile) - { - const auto fSuccess = WriteFile(_hFile.get(), _buffer.data(), gsl::narrow_cast(_buffer.size()), nullptr, nullptr); - _buffer.clear(); - _startOfFrameBufferIndex = 0; - if (!fSuccess) - { - LOG_LAST_ERROR(); - _hFile.reset(); - if (_terminalOwner) - { - _terminalOwner->CloseOutput(); - } - } - } -} - -// The name of this method is an analogy to TCP_CORK. It instructs -// the VT renderer to stop flushing its buffer to the output pipe. -// Don't forget to uncork it! -void VtEngine::Cork(bool corked) noexcept -{ - _corked = corked; - - // Now do the deferred flush from a previous call to _Flush(). - if (!corked && _flushRequested) - { - _flushRequested = false; - _flushImpl(); - } -} - -// Method Description: -// - Wrapper for _Write. -[[nodiscard]] HRESULT VtEngine::WriteTerminalUtf8(const std::string_view str) noexcept -{ - return _Write(str); -} - -// Method Description: -// - Writes a wstring to the tty, encoded as full utf-8. This is one -// implementation of the WriteTerminalW method. -// Arguments: -// - wstr - wstring of text to be written -// Return Value: -// - S_OK or suitable HRESULT error from either conversion or writing pipe. -[[nodiscard]] HRESULT VtEngine::_WriteTerminalUtf8(const std::wstring_view wstr) noexcept -{ - RETURN_IF_FAILED(til::u16u8(wstr, _conversionBuffer)); - return _Write(_conversionBuffer); -} - -// Method Description: -// - Writes a wstring to the tty, encoded as "utf-8" where characters that are -// outside the ASCII range are encoded as '?' -// This mainly exists to maintain compatibility with the inbox telnet client. -// This is one implementation of the WriteTerminalW method. -// Arguments: -// - wstr - wstring of text to be written -// Return Value: -// - S_OK or suitable HRESULT error from writing pipe. -[[nodiscard]] HRESULT VtEngine::_WriteTerminalAscii(const std::wstring_view wstr) noexcept -{ - std::string needed; - needed.reserve(wstr.size()); - - for (const auto& wch : wstr) - { - // We're explicitly replacing characters outside ASCII with a ? because - // that's what telnet wants. - needed.push_back((wch > L'\x7f') ? '?' : static_cast(wch)); - } - - return _Write(needed); -} - -// Method Description: -// - Writes a wstring to the tty when the characters are from the DRCS soft font. -// It is assumed that the character set has already been designated in the -// client terminal, so we just need to re-map our internal representation -// of the characters into ASCII. -// Arguments: -// - wstr - wstring of text to be written -// Return Value: -// - S_OK or suitable HRESULT error from writing pipe. -[[nodiscard]] HRESULT VtEngine::_WriteTerminalDrcs(const std::wstring_view wstr) noexcept -{ - std::string needed; - needed.reserve(wstr.size()); - - for (const auto& wch : wstr) - { - // Our DRCS characters use the range U+EF20 to U+EF7F from the Unicode - // Private Use Area. To map them back to ASCII we just mask with 7F. - needed.push_back(wch & 0x7F); - } - - return _Write(needed); -} - -// Method Description: -// - This method will update the active font on the current device context -// Does nothing for vt, the font is handed by the terminal. -// Arguments: -// - FontDesired - reference to font information we should use while instantiating a font. -// - Font - reference to font information where the chosen font information will be populated. -// Return Value: -// - HRESULT S_OK -[[nodiscard]] HRESULT VtEngine::UpdateFont(const FontInfoDesired& /*pfiFontDesired*/, - _Out_ FontInfo& /*pfiFont*/) noexcept -{ - return S_OK; -} - -// Method Description: -// - This method will modify the DPI we're using for scaling calculations. -// Does nothing for vt, the dpi is handed by the terminal. -// Arguments: -// - iDpi - The Dots Per Inch to use for scaling. We will use this relative to -// the system default DPI defined in Windows headers as a constant. -// Return Value: -// - HRESULT S_OK -[[nodiscard]] HRESULT VtEngine::UpdateDpi(const int /*iDpi*/) noexcept -{ - return S_OK; -} - -// Method Description: -// - This method will update our internal reference for how big the viewport is. -// If the viewport has changed size, then we'll need to send an update to -// the terminal. -// Arguments: -// - srNewViewport - The bounds of the new viewport. -// Return Value: -// - HRESULT S_OK -[[nodiscard]] HRESULT VtEngine::UpdateViewport(const til::inclusive_rect& srNewViewport) noexcept -{ - auto hr = S_OK; - const auto newView = Viewport::FromInclusive(srNewViewport); - const auto oldSize = _lastViewport.Dimensions(); - const auto newSize = newView.Dimensions(); - - if (oldSize != newSize) - { - // Don't emit a resize event if we've requested it be suppressed - if (!_suppressResizeRepaint) - { - hr = _ResizeWindow(newSize.width, newSize.height); - } - - if (_resizeQuirk) - { - // GH#3490 - When the viewport width changed, don't do anything extra here. - // If the buffer had areas that were invalid due to the resize, then the - // buffer will have triggered its own invalidations for what it knows is - // invalid. Previously, we'd invalidate everything if the width changed, - // because we couldn't be sure if lines were reflowed. - _invalidMap.resize(newSize); - } - else - { - if (SUCCEEDED(hr)) - { - _invalidMap.resize(newSize, true); // resize while filling in new space with repaint requests. - - // Viewport is smaller now - just update it all. - if (oldSize.height > newSize.height || oldSize.width > newSize.width) - { - hr = InvalidateAll(); - } - } - } - - _resized = true; - } - - // See MSFT:19408543 - // Always clear the suppression request, even if the new size was the same - // as the last size. We're always going to get a UpdateViewport call - // for our first frame. However, we start with _suppressResizeRepaint set, - // to prevent that first UpdateViewport call from emitting our size. - // If we only clear the flag when the new viewport is different, this can - // lead to the first _actual_ resize being suppressed. - _suppressResizeRepaint = false; - _lastViewport = newView; - - return hr; -} - -// Method Description: -// - This method will figure out what the new font should be given the starting font information and a DPI. -// - When the final font is determined, the FontInfo structure given will be updated with the actual resulting font chosen as the nearest match. -// - NOTE: It is left up to the underling rendering system to choose the nearest font. Please ask for the font dimensions if they are required using the interface. Do not use the size you requested with this structure. -// - If the intent is to immediately turn around and use this font, pass the optional handle parameter and use it immediately. -// Does nothing for vt, the font is handed by the terminal. -// Arguments: -// - FontDesired - reference to font information we should use while instantiating a font. -// - Font - reference to font information where the chosen font information will be populated. -// - iDpi - The DPI we will have when rendering -// Return Value: -// - S_FALSE: This is unsupported by the VT Renderer and should use another engine's value. -[[nodiscard]] HRESULT VtEngine::GetProposedFont(const FontInfoDesired& /*pfiFontDesired*/, - _Out_ FontInfo& /*pfiFont*/, - const int /*iDpi*/) noexcept -{ - return S_FALSE; -} - -// Method Description: -// - Retrieves the current pixel size of the font we have selected for drawing. -// Arguments: -// - pFontSize - receives the current X by Y size of the font. -// Return Value: -// - S_FALSE: This is unsupported by the VT Renderer and should use another engine's value. -[[nodiscard]] HRESULT VtEngine::GetFontSize(_Out_ til::size* const pFontSize) noexcept -{ - *pFontSize = { 1, 1 }; - return S_FALSE; -} - -// Method Description: -// - Sets the test callback for this instance. Instead of rendering to a pipe, -// this instance will instead render to a callback for testing. -// Arguments: -// - pfn: a callback to call instead of writing to the pipe. -// Return Value: -// - -void VtEngine::SetTestCallback(_In_ std::function pfn) -{ -#ifdef UNIT_TESTING - - _pfnTestCallback = pfn; - _usingTestCallback = true; - -#else - THROW_HR(E_FAIL); -#endif -} - -// Method Description: -// - Returns true if the entire viewport has been invalidated. That signals we -// should use a VT Clear Screen sequence as an optimization. -// Arguments: -// - -// Return Value: -// - true if the entire viewport has been invalidated -bool VtEngine::_AllIsInvalid() const -{ - return _invalidMap.all(); -} - -// Method Description: -// - Prevent the renderer from emitting output on the next resize. This prevents -// the host from echoing a resize to the terminal that requested it. -// Arguments: -// - -// Return Value: -// - S_OK -[[nodiscard]] HRESULT VtEngine::SuppressResizeRepaint() noexcept -{ - _suppressResizeRepaint = true; - return S_OK; -} - -// Method Description: -// - "Inherit" the cursor at the given position. We won't need to move it -// anywhere, so update where we last thought the cursor was. -// Also update our "virtual top", indicating where should clip all updates to -// (we don't want to paint the empty region above the inherited cursor). -// Also ignore the next InvalidateCursor call. -// Arguments: -// - coordCursor: The cursor position to inherit from. -// Return Value: -// - S_OK -[[nodiscard]] HRESULT VtEngine::InheritCursor(const til::point coordCursor) noexcept -{ - _virtualTop = coordCursor.y; - _lastText = coordCursor; - _skipCursor = true; - // Prevent us from clearing the entire viewport on the first paint - _firstPaint = false; - return S_OK; -} - -void VtEngine::SetTerminalOwner(Microsoft::Console::VirtualTerminal::VtIo* const terminalOwner) -{ - _terminalOwner = terminalOwner; -} - -// Method Description: -// - sends a sequence to request the end terminal to tell us the -// cursor position. The terminal will reply back on the vt input handle. -// Flushes the buffer as well, to make sure the request is sent to the terminal. -// Arguments: -// - -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -HRESULT VtEngine::RequestCursor() noexcept -{ - RETURN_IF_FAILED(_RequestCursor()); - _Flush(); - return S_OK; -} - -// Method Description: -// - Sends a notification through to the `VtInputThread` that it should -// watch for and capture the response from a DSR message we're about to send. -// This is typically `RequestCursor` at the time of writing this, but in theory -// could be another DSR as well. -// Arguments: -// - -// Return Value: -// - S_OK if all goes well. Invalid state error if no notification function is installed. -// (see `SetLookingForDSRCallback` to install one.) -[[nodiscard]] HRESULT VtEngine::_ListenForDSR() noexcept -{ - RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !_pfnSetLookingForDSR); - _pfnSetLookingForDSR(true); - return S_OK; -} - -// Method Description: -// - Configure the renderer for the resize quirk. This changes the behavior of -// conpty to _not_ InvalidateAll the entire viewport on a resize operation. -// This is used by the Windows Terminal, because it is prepared to be -// connected to a conpty, and handles its own buffer specifically for a -// conpty scenario. -// - See also: GH#3490, #4354, #4741 -// Arguments: -// - resizeQuirk - True to turn on the quirk. False otherwise. -// Return Value: -// - true iff we were started with the `--resizeQuirk` flag enabled. -void VtEngine::SetResizeQuirk(const bool resizeQuirk) -{ - _resizeQuirk = resizeQuirk; -} - -void VtEngine::SetLookingForDSRCallback(std::function pfnLooking) noexcept -{ - _pfnSetLookingForDSR = pfnLooking; -} - -void VtEngine::SetTerminalCursorTextPosition(const til::point cursor) noexcept -{ - _lastText = cursor; -} - -// Method Description: -// - Manually emit a "Erase Scrollback" sequence to the connected terminal. We -// need to do this in certain cases that we've identified where we believe the -// client wanted the entire terminal buffer cleared, not just the viewport. -// For more information, see GH#3126. -// - This is unimplemented in the win-telnet, xterm-ascii renderers - inbox -// telnet.exe doesn't know how to handle a ^[[3J. This _is_ implemented in the -// Xterm256Engine. -// Arguments: -// - -// Return Value: -// - S_OK if we wrote the sequences successfully, otherwise an appropriate HRESULT -[[nodiscard]] HRESULT VtEngine::ManuallyClearScrollback() noexcept -{ - return S_OK; -} - -// Method Description: -// - Send a sequence to the connected terminal to request win32-input-mode from -// them. This will enable the connected terminal to send us full INPUT_RECORDs -// as input. If the terminal doesn't understand this sequence, it'll just -// ignore it. -// Arguments: -// - -// Return Value: -// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. -HRESULT VtEngine::RequestWin32Input() noexcept -{ - // On startup we request the modes we require for optimal functioning - // (namely win32 input mode and focus event mode). - // - // It's important that any additional modes set here are also mirrored in - // the AdaptDispatch::HardReset method, since that needs to re-enable them - // in the connected terminal after passing through an RIS sequence. - RETURN_IF_FAILED(_Write("\033[?9001h\033[?1004h")); - _Flush(); - return S_OK; -} - -HRESULT VtEngine::SwitchScreenBuffer(const bool useAltBuffer) noexcept -{ - RETURN_IF_FAILED(_SwitchScreenBuffer(useAltBuffer)); - _Flush(); - return S_OK; -} - -HRESULT VtEngine::RequestMouseMode(const bool enable) noexcept -{ - const auto status = _WriteFormatted(FMT_COMPILE("\x1b[?1003;1006{}"), enable ? 'h' : 'l'); - _Flush(); - return status; -} diff --git a/src/renderer/vt/tracing.cpp b/src/renderer/vt/tracing.cpp deleted file mode 100644 index 21f2bc67094..00000000000 --- a/src/renderer/vt/tracing.cpp +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "tracing.hpp" -#include - -TRACELOGGING_DEFINE_PROVIDER(g_hConsoleVtRendererTraceProvider, - "Microsoft.Windows.Console.Render.VtEngine", - // tl:{c9ba2a95-d3ca-5e19-2bd6-776a0910cb9d} - (0xc9ba2a95, 0xd3ca, 0x5e19, 0x2b, 0xd6, 0x77, 0x6a, 0x09, 0x10, 0xcb, 0x9d), - TraceLoggingOptionMicrosoftTelemetry()); - -using namespace Microsoft::Console::VirtualTerminal; -using namespace Microsoft::Console::Types; - -RenderTracing::RenderTracing() -{ -#ifndef UNIT_TESTING - TraceLoggingRegister(g_hConsoleVtRendererTraceProvider); -#endif // UNIT_TESTING -} - -RenderTracing::~RenderTracing() -{ -#ifndef UNIT_TESTING - TraceLoggingUnregister(g_hConsoleVtRendererTraceProvider); -#endif // UNIT_TESTING -} - -// Function Description: -// - Convert the string to only have printable characters in it. Control -// characters are converted to hat notation, spaces are converted to "SPC" -// (to be able to see them at the end of a string), and DEL is written as -// "\x7f". -// Arguments: -// - inString: The string to convert -// Return Value: -// - a string with only printable characters in it. -std::string toPrintableString(const std::string_view& inString) -{ - std::string retval = ""; - for (size_t i = 0; i < inString.length(); i++) - { - unsigned char c = inString[i]; - if (c < '\x20') - { - retval += "^"; - char actual = (c + 0x40); - retval += std::string(1, actual); - } - else if (c == '\x7f') - { - retval += "\\x7f"; - } - else if (c == '\x20') - { - retval += "SPC"; - } - else - { - retval += std::string(1, c); - } - } - return retval; -} -void RenderTracing::TraceStringFill(const size_t n, const char c) const -{ -#ifndef UNIT_TESTING - if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) - { - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TraceStringFill", - TraceLoggingUInt64(gsl::narrow_cast(n)), - TraceLoggingChar(c), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - } -#else - UNREFERENCED_PARAMETER(n); - UNREFERENCED_PARAMETER(c); -#endif // UNIT_TESTING -} -void RenderTracing::TraceString(const std::string_view& instr) const -{ -#ifndef UNIT_TESTING - if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) - { - const auto _seq = toPrintableString(instr); - const auto seq = _seq.c_str(); - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TraceString", - TraceLoggingUtf8String(seq), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - } -#else - UNREFERENCED_PARAMETER(instr); -#endif // UNIT_TESTING -} - -void RenderTracing::TraceInvalidate(const til::rect& invalidRect) const -{ -#ifndef UNIT_TESTING - if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) - { - const auto invalidatedStr = invalidRect.to_string(); - const auto invalidated = invalidatedStr.c_str(); - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TraceInvalidate", - TraceLoggingWideString(invalidated), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - } -#else - UNREFERENCED_PARAMETER(invalidRect); -#endif // UNIT_TESTING -} - -void RenderTracing::TraceInvalidateAll(const til::rect& viewport) const -{ -#ifndef UNIT_TESTING - if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) - { - const auto invalidatedStr = viewport.to_string(); - const auto invalidatedAll = invalidatedStr.c_str(); - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TraceInvalidateAll", - TraceLoggingWideString(invalidatedAll), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - } -#else - UNREFERENCED_PARAMETER(viewport); -#endif // UNIT_TESTING -} - -void RenderTracing::TraceTriggerCircling(const bool newFrame) const -{ -#ifndef UNIT_TESTING - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TraceTriggerCircling", - TraceLoggingBool(newFrame), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); -#else - UNREFERENCED_PARAMETER(newFrame); -#endif // UNIT_TESTING -} - -void RenderTracing::TraceInvalidateScroll(const til::point scroll) const -{ -#ifndef UNIT_TESTING - if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) - { - const auto scrollDeltaStr = scroll.to_string(); - const auto scrollDelta = scrollDeltaStr.c_str(); - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TraceInvalidateScroll", - TraceLoggingWideString(scrollDelta), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - } -#else - UNREFERENCED_PARAMETER(scroll); -#endif -} - -void RenderTracing::TraceStartPaint(const bool quickReturn, - const til::pmr::bitmap& invalidMap, - const til::rect& lastViewport, - const til::point scrollDelt, - const bool cursorMoved, - const std::optional& wrappedRow) const -{ -#ifndef UNIT_TESTING - if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) - { - const auto invalidatedStr = invalidMap.to_string(); - const auto invalidated = invalidatedStr.c_str(); - const auto lastViewStr = lastViewport.to_string(); - const auto lastView = lastViewStr.c_str(); - const auto scrollDeltaStr = scrollDelt.to_string(); - const auto scrollDelta = scrollDeltaStr.c_str(); - if (wrappedRow.has_value()) - { - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TraceStartPaint", - TraceLoggingBool(quickReturn), - TraceLoggingWideString(invalidated), - TraceLoggingWideString(lastView), - TraceLoggingWideString(scrollDelta), - TraceLoggingBool(cursorMoved), - TraceLoggingValue(wrappedRow.value()), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - } - else - { - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TraceStartPaint", - TraceLoggingBool(quickReturn), - TraceLoggingWideString(invalidated), - TraceLoggingWideString(lastView), - TraceLoggingWideString(scrollDelta), - TraceLoggingBool(cursorMoved), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - } - } -#else - UNREFERENCED_PARAMETER(quickReturn); - UNREFERENCED_PARAMETER(invalidMap); - UNREFERENCED_PARAMETER(lastViewport); - UNREFERENCED_PARAMETER(scrollDelt); - UNREFERENCED_PARAMETER(cursorMoved); - UNREFERENCED_PARAMETER(wrappedRow); -#endif // UNIT_TESTING -} - -void RenderTracing::TraceEndPaint() const -{ -#ifndef UNIT_TESTING - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TraceEndPaint", - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); -#else -#endif // UNIT_TESTING -} - -void RenderTracing::TraceLastText(const til::point lastTextPos) const -{ -#ifndef UNIT_TESTING - if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) - { - const auto lastTextStr = lastTextPos.to_string(); - const auto lastText = lastTextStr.c_str(); - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TraceLastText", - TraceLoggingWideString(lastText), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - } -#else - UNREFERENCED_PARAMETER(lastTextPos); -#endif // UNIT_TESTING -} - -void RenderTracing::TraceScrollFrame(const til::point scrollDeltaPos) const -{ -#ifndef UNIT_TESTING - if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) - { - const auto scrollDeltaStr = scrollDeltaPos.to_string(); - const auto scrollDelta = scrollDeltaStr.c_str(); - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TraceScrollFrame", - TraceLoggingWideString(scrollDelta), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - } -#else - UNREFERENCED_PARAMETER(scrollDeltaPos); -#endif // UNIT_TESTING -} - -void RenderTracing::TraceMoveCursor(const til::point lastTextPos, const til::point cursor) const -{ -#ifndef UNIT_TESTING - if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) - { - const auto lastTextStr = lastTextPos.to_string(); - const auto lastText = lastTextStr.c_str(); - - const auto cursorStr = cursor.to_string(); - const auto cursorPos = cursorStr.c_str(); - - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TraceMoveCursor", - TraceLoggingWideString(lastText), - TraceLoggingWideString(cursorPos), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - } -#else - UNREFERENCED_PARAMETER(lastTextPos); - UNREFERENCED_PARAMETER(cursor); -#endif // UNIT_TESTING -} - -void RenderTracing::TraceWrapped() const -{ -#ifndef UNIT_TESTING - if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) - { - const auto* const msg = "Wrapped instead of \\r\\n"; - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TraceWrapped", - TraceLoggingString(msg), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - } -#else -#endif // UNIT_TESTING -} - -void RenderTracing::TraceSetWrapped(const til::CoordType wrappedRow) const -{ -#ifndef UNIT_TESTING - if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) - { - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TraceSetWrapped", - TraceLoggingValue(wrappedRow), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - } -#else - UNREFERENCED_PARAMETER(wrappedRow); -#endif // UNIT_TESTING -} - -void RenderTracing::TraceClearWrapped() const -{ -#ifndef UNIT_TESTING - if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) - { - const auto* const msg = "Cleared wrap state"; - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TraceClearWrapped", - TraceLoggingString(msg), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - } -#else -#endif // UNIT_TESTING -} - -void RenderTracing::TracePaintCursor(const til::point coordCursor) const -{ -#ifndef UNIT_TESTING - if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) - { - const auto cursorPosString = coordCursor.to_string(); - const auto cursorPos = cursorPosString.c_str(); - TraceLoggingWrite(g_hConsoleVtRendererTraceProvider, - "VtEngine_TracePaintCursor", - TraceLoggingWideString(cursorPos), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - } -#else - UNREFERENCED_PARAMETER(coordCursor); -#endif // UNIT_TESTING -} diff --git a/src/renderer/vt/tracing.hpp b/src/renderer/vt/tracing.hpp deleted file mode 100644 index 78515df7d48..00000000000 --- a/src/renderer/vt/tracing.hpp +++ /dev/null @@ -1,50 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- tracing.hpp - -Abstract: -- This module is used for recording tracing/debugging information to the telemetry ETW channel ---*/ - -#pragma once -#include -#include -#include -#include -#include -#include "../../types/inc/Viewport.hpp" - -TRACELOGGING_DECLARE_PROVIDER(g_hConsoleVtRendererTraceProvider); - -namespace Microsoft::Console::VirtualTerminal -{ - class RenderTracing final - { - public: - RenderTracing(); - ~RenderTracing(); - void TraceStringFill(const size_t n, const char c) const; - void TraceString(const std::string_view& str) const; - void TraceInvalidate(const til::rect& view) const; - void TraceLastText(const til::point lastText) const; - void TraceScrollFrame(const til::point scrollDelta) const; - void TraceMoveCursor(const til::point lastText, const til::point cursor) const; - void TraceSetWrapped(const til::CoordType wrappedRow) const; - void TraceClearWrapped() const; - void TraceWrapped() const; - void TracePaintCursor(const til::point coordCursor) const; - void TraceInvalidateAll(const til::rect& view) const; - void TraceTriggerCircling(const bool newFrame) const; - void TraceInvalidateScroll(const til::point scroll) const; - void TraceStartPaint(const bool quickReturn, - const til::pmr::bitmap& invalidMap, - const til::rect& lastViewport, - const til::point scrollDelta, - const bool cursorMoved, - const std::optional& wrappedRow) const; - void TraceEndPaint() const; - }; -} diff --git a/src/renderer/vt/ut_lib/sources b/src/renderer/vt/ut_lib/sources deleted file mode 100644 index f4017cca2e4..00000000000 --- a/src/renderer/vt/ut_lib/sources +++ /dev/null @@ -1,20 +0,0 @@ -!include ..\sources.inc - -# ------------------------------------- -# Program Information -# ------------------------------------- - -TARGETNAME = ConRenderVt.Unittest -TARGETTYPE = LIBRARY - -TEST_CODE = 1 - -# ------------------------------------- -# Preprocessor Settings -# ------------------------------------- - -C_DEFINES = $(C_DEFINES) -DINLINE_TEST_METHOD_MARKUP -DUNIT_TESTING - -INCLUDES = \ - $(INCLUDES); \ - $(ONECORESDKTOOLS_INTERNAL_INC_PATH_L)\wextest\cue; \ diff --git a/src/renderer/vt/ut_lib/sources.dep b/src/renderer/vt/ut_lib/sources.dep deleted file mode 100644 index bc61c95c6b0..00000000000 --- a/src/renderer/vt/ut_lib/sources.dep +++ /dev/null @@ -1,3 +0,0 @@ -BUILD_PASS1_CONSUMES= \ - onecore\windows\vcpkg|PASS1 \ - diff --git a/src/renderer/vt/ut_lib/vt.unittest.vcxproj b/src/renderer/vt/ut_lib/vt.unittest.vcxproj deleted file mode 100644 index e2aea923840..00000000000 --- a/src/renderer/vt/ut_lib/vt.unittest.vcxproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - {990F2657-8580-4828-943F-5DD657D11843} - Win32Proj - vt.unittest - RendererVt.unittest - ConRenderVt.unittest - StaticLibrary - - - - - - - - - - diff --git a/src/renderer/vt/vt-renderer-common.vcxitems b/src/renderer/vt/vt-renderer-common.vcxitems deleted file mode 100644 index f4bea3242db..00000000000 --- a/src/renderer/vt/vt-renderer-common.vcxitems +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - Create - - - - - - - - - - - - - - - diff --git a/src/renderer/vt/vtrenderer.hpp b/src/renderer/vt/vtrenderer.hpp deleted file mode 100644 index 2641614b32d..00000000000 --- a/src/renderer/vt/vtrenderer.hpp +++ /dev/null @@ -1,246 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- VtRenderer.hpp - -Abstract: -- This is the definition of the VT specific implementation of the renderer. - -Author(s): -- Michael Niksa (MiNiksa) 24-Jul-2017 -- Mike Griese (migrie) 01-Sept-2017 ---*/ - -#pragma once - -#include "../inc/RenderEngineBase.hpp" -#include "../../types/inc/Viewport.hpp" -#include "tracing.hpp" -#include -#include - -// fwdecl unittest classes -#ifdef UNIT_TESTING -namespace TerminalCoreUnitTests -{ - class ConptyRoundtripTests; -}; -class ScreenBufferTests; -#endif - -namespace Microsoft::Console::VirtualTerminal -{ - class VtIo; -} - -namespace Microsoft::Console::Render -{ - class VtEngine : public RenderEngineBase - { - public: - // See _PaintUtf8BufferLine for explanation of this value. - static const size_t ERASE_CHARACTER_STRING_LENGTH = 8; - static const til::point INVALID_COORDS; - - VtEngine(_In_ wil::unique_hfile hPipe, - const Microsoft::Console::Types::Viewport initialViewport); - - // IRenderEngine - [[nodiscard]] HRESULT StartPaint() noexcept override; - [[nodiscard]] HRESULT EndPaint() noexcept override; - [[nodiscard]] HRESULT Present() noexcept override; - [[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* pForcePaint) noexcept override; - [[nodiscard]] HRESULT Invalidate(const til::rect* psrRegion) noexcept override; - [[nodiscard]] HRESULT InvalidateCursor(const til::rect* psrRegion) noexcept override; - [[nodiscard]] HRESULT InvalidateSystem(const til::rect* prcDirtyClient) noexcept override; - [[nodiscard]] HRESULT InvalidateSelection(const std::vector& rectangles) noexcept override; - [[nodiscard]] HRESULT InvalidateAll() noexcept override; - [[nodiscard]] HRESULT InvalidateFlush(_In_ const bool circled, _Out_ bool* const pForcePaint) noexcept override; - [[nodiscard]] HRESULT ResetLineTransform() noexcept override; - [[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition, const til::CoordType targetRow, const til::CoordType viewportLeft) noexcept override; - [[nodiscard]] HRESULT PaintBackground() noexcept override; - [[nodiscard]] HRESULT PaintBufferLine(std::span clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; - [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; - [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; - [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept override; - [[nodiscard]] HRESULT UpdateDpi(int iDpi) noexcept override; - [[nodiscard]] HRESULT UpdateViewport(const til::inclusive_rect& srNewViewport) noexcept override; - [[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo, int iDpi) noexcept override; - [[nodiscard]] HRESULT GetDirtyArea(std::span& area) noexcept override; - [[nodiscard]] HRESULT GetFontSize(_Out_ til::size* pFontSize) noexcept override; - [[nodiscard]] HRESULT IsGlyphWideByFont(std::wstring_view glyph, _Out_ bool* pResult) noexcept override; - - // VtEngine - [[nodiscard]] HRESULT SuppressResizeRepaint() noexcept; - [[nodiscard]] HRESULT RequestCursor() noexcept; - [[nodiscard]] HRESULT InheritCursor(const til::point coordCursor) noexcept; - [[nodiscard]] HRESULT WriteTerminalUtf8(const std::string_view str) noexcept; - [[nodiscard]] virtual HRESULT WriteTerminalW(const std::wstring_view str, const bool flush = false) noexcept = 0; - void SetTerminalOwner(Microsoft::Console::VirtualTerminal::VtIo* const terminalOwner); - void SetResizeQuirk(const bool resizeQuirk); - void SetLookingForDSRCallback(std::function pfnLooking) noexcept; - void SetTerminalCursorTextPosition(const til::point coordCursor) noexcept; - [[nodiscard]] virtual HRESULT ManuallyClearScrollback() noexcept; - [[nodiscard]] HRESULT RequestWin32Input() noexcept; - [[nodiscard]] virtual HRESULT SetWindowVisibility(const bool showOrHide) noexcept = 0; - [[nodiscard]] HRESULT SwitchScreenBuffer(const bool useAltBuffer) noexcept; - [[nodiscard]] HRESULT RequestMouseMode(bool enable) noexcept; - void Cork(bool corked) noexcept; - - protected: - wil::unique_hfile _hFile; - std::string _buffer; - size_t _startOfFrameBufferIndex = 0; - - std::string _formatBuffer; - std::string _conversionBuffer; - - bool _usingLineRenditions; - bool _stopUsingLineRenditions; - bool _usingSoftFont; - TextAttribute _lastTextAttributes; - - std::function _pfnSetLookingForDSR; - - Microsoft::Console::Types::Viewport _lastViewport; - - std::pmr::unsynchronized_pool_resource _pool; - til::pmr::bitmap _invalidMap; - - til::point _lastText; - til::point _lastCursorOrigin; - til::point _scrollDelta; - - bool _clearedAllThisFrame; - bool _cursorMoved; - bool _resized; - - bool _suppressResizeRepaint; - - til::CoordType _virtualTop; - bool _circled; - bool _firstPaint; - bool _skipCursor; - bool _newBottomLine; - til::point _deferredCursorPos; - - Microsoft::Console::VirtualTerminal::VtIo* _terminalOwner; - - Microsoft::Console::VirtualTerminal::RenderTracing _trace; - - std::optional _wrappedRow{ std::nullopt }; - - bool _delayedEolWrap{ false }; - - bool _resizeQuirk{ false }; - bool _passthrough{ false }; - bool _noFlushOnEnd{ false }; - bool _corked{ false }; - bool _flushRequested{ false }; - std::optional _newBottomLineBG{ std::nullopt }; - - [[nodiscard]] HRESULT _WriteFill(const size_t n, const char c) noexcept; - [[nodiscard]] HRESULT _Write(std::string_view const str) noexcept; - void _Flush() noexcept; - void _flushImpl() noexcept; - - template - [[nodiscard]] HRESULT _WriteFormatted(S&& format, Args&&... args) - try - { - fmt::basic_memory_buffer buf; - fmt::format_to(std::back_inserter(buf), std::forward(format), std::forward(args)...); - return _Write({ buf.data(), buf.size() }); - } - CATCH_RETURN() - - void _OrRect(_Inout_ til::inclusive_rect* const pRectExisting, const til::inclusive_rect* const pRectToOr) const; - bool _AllIsInvalid() const; - - [[nodiscard]] HRESULT _StopCursorBlinking() noexcept; - [[nodiscard]] HRESULT _StartCursorBlinking() noexcept; - [[nodiscard]] HRESULT _HideCursor() noexcept; - [[nodiscard]] HRESULT _ShowCursor() noexcept; - [[nodiscard]] HRESULT _EraseLine() noexcept; - [[nodiscard]] HRESULT _InsertDeleteLine(const til::CoordType sLines, const bool fInsertLine) noexcept; - [[nodiscard]] HRESULT _DeleteLine(const til::CoordType sLines) noexcept; - [[nodiscard]] HRESULT _InsertLine(const til::CoordType sLines) noexcept; - [[nodiscard]] HRESULT _CursorForward(const til::CoordType chars) noexcept; - [[nodiscard]] HRESULT _EraseCharacter(const til::CoordType chars) noexcept; - [[nodiscard]] HRESULT _CursorPosition(const til::point coord) noexcept; - [[nodiscard]] HRESULT _CursorHome() noexcept; - [[nodiscard]] HRESULT _ClearScreen() noexcept; - [[nodiscard]] HRESULT _ClearScrollback() noexcept; - [[nodiscard]] HRESULT _ChangeTitle(const std::string& title) noexcept; - [[nodiscard]] HRESULT _SetGraphicsRendition16Color(const BYTE index, - const bool fIsForeground) noexcept; - [[nodiscard]] HRESULT _SetGraphicsRendition256Color(const BYTE index, - const bool fIsForeground) noexcept; - [[nodiscard]] HRESULT _SetGraphicsRenditionRGBColor(const COLORREF color, - const bool fIsForeground) noexcept; - [[nodiscard]] HRESULT _SetGraphicsRenditionDefaultColor(const bool fIsForeground) noexcept; - - [[nodiscard]] HRESULT _SetGraphicsRenditionUnderline256Color(const BYTE index) noexcept; - [[nodiscard]] HRESULT _SetGraphicsRenditionUnderlineRGBColor(const COLORREF color) noexcept; - [[nodiscard]] HRESULT _SetGraphicsRenditionUnderlineDefaultColor() noexcept; - - [[nodiscard]] HRESULT _SetGraphicsDefault() noexcept; - - [[nodiscard]] HRESULT _ResizeWindow(const til::CoordType sWidth, const til::CoordType sHeight) noexcept; - - [[nodiscard]] HRESULT _SetIntense(const bool isIntense) noexcept; - [[nodiscard]] HRESULT _SetFaint(const bool isFaint) noexcept; - [[nodiscard]] HRESULT _SetUnderlined(const bool isUnderlined) noexcept; - [[nodiscard]] HRESULT _SetUnderlineExtended(const UnderlineStyle style) noexcept; - [[nodiscard]] HRESULT _SetOverlined(const bool isOverlined) noexcept; - [[nodiscard]] HRESULT _SetItalic(const bool isItalic) noexcept; - [[nodiscard]] HRESULT _SetBlinking(const bool isBlinking) noexcept; - [[nodiscard]] HRESULT _SetInvisible(const bool isInvisible) noexcept; - [[nodiscard]] HRESULT _SetCrossedOut(const bool isCrossedOut) noexcept; - [[nodiscard]] HRESULT _SetReverseVideo(const bool isReversed) noexcept; - - [[nodiscard]] HRESULT _SetHyperlink(const std::wstring_view& uri, const std::wstring_view& customId, const uint16_t& numberId) noexcept; - [[nodiscard]] HRESULT _EndHyperlink() noexcept; - - [[nodiscard]] HRESULT _RequestCursor() noexcept; - [[nodiscard]] HRESULT _ListenForDSR() noexcept; - - [[nodiscard]] HRESULT _SwitchScreenBuffer(const bool useAltBuffer) noexcept; - - [[nodiscard]] virtual HRESULT _MoveCursor(const til::point coord) noexcept = 0; - [[nodiscard]] HRESULT _RgbUpdateDrawingBrushes(const TextAttribute& textAttributes) noexcept; - [[nodiscard]] HRESULT _16ColorUpdateDrawingBrushes(const TextAttribute& textAttributes) noexcept; - - // buffer space for these two functions to build their lines - // so they don't have to alloc/free in a tight loop - std::wstring _bufferLine; - [[nodiscard]] HRESULT _PaintUtf8BufferLine(const std::span clusters, - const til::point coord, - const bool lineWrapped) noexcept; - - [[nodiscard]] HRESULT _PaintAsciiBufferLine(const std::span clusters, - const til::point coord) noexcept; - - [[nodiscard]] HRESULT _WriteTerminalUtf8(const std::wstring_view str) noexcept; - [[nodiscard]] HRESULT _WriteTerminalAscii(const std::wstring_view str) noexcept; - [[nodiscard]] HRESULT _WriteTerminalDrcs(const std::wstring_view str) noexcept; - - [[nodiscard]] HRESULT _DoUpdateTitle(const std::wstring_view newTitle) noexcept override; - - /////////////////////////// Unit Testing Helpers /////////////////////////// -#ifdef UNIT_TESTING - std::function _pfnTestCallback; - bool _usingTestCallback; - - friend class VtRendererTest; - friend class ConptyOutputTests; - friend class ScreenBufferTests; - friend class TerminalCoreUnitTests::ConptyRoundtripTests; -#endif - - void SetTestCallback(_In_ std::function pfn); - }; -} diff --git a/src/renderer/wddmcon/WddmConRenderer.cpp b/src/renderer/wddmcon/WddmConRenderer.cpp index fda8ae6c21b..605f0a6973d 100644 --- a/src/renderer/wddmcon/WddmConRenderer.cpp +++ b/src/renderer/wddmcon/WddmConRenderer.cpp @@ -196,12 +196,6 @@ CATCH_RETURN() return S_OK; } -[[nodiscard]] HRESULT WddmConEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept -{ - *pForcePaint = false; - return S_FALSE; -} - [[nodiscard]] HRESULT WddmConEngine::StartPaint() noexcept try { diff --git a/src/renderer/wddmcon/WddmConRenderer.hpp b/src/renderer/wddmcon/WddmConRenderer.hpp index 1110c870782..79b874fd775 100644 --- a/src/renderer/wddmcon/WddmConRenderer.hpp +++ b/src/renderer/wddmcon/WddmConRenderer.hpp @@ -31,7 +31,6 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT InvalidateSelection(const std::vector& rectangles) noexcept override; [[nodiscard]] HRESULT InvalidateScroll(const til::point* const pcoordDelta) noexcept override; [[nodiscard]] HRESULT InvalidateAll() noexcept override; - [[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override; [[nodiscard]] HRESULT StartPaint() noexcept override; [[nodiscard]] HRESULT EndPaint() noexcept override; diff --git a/src/server/IoDispatchers.cpp b/src/server/IoDispatchers.cpp index 664e8ebab85..da082157569 100644 --- a/src/server/IoDispatchers.cpp +++ b/src/server/IoDispatchers.cpp @@ -5,23 +5,17 @@ #include "IoDispatchers.h" -#include "ApiSorter.h" +#include -#include "../host/conserv.h" -#include "../host/conwinuserrefs.h" +#include "ApiSorter.h" +#include "IConsoleHandoff.h" #include "../host/directio.h" #include "../host/handle.h" #include "../host/srvinit.h" - #include "../interactivity/base/HostSignalInputThread.hpp" #include "../interactivity/inc/ServiceLocator.hpp" -#include "../types/inc/utils.hpp" - -#include "IConsoleHandoff.h" - using namespace Microsoft::Console::Interactivity; -using namespace Microsoft::Console::Utils; // From ntstatus.h, which we cannot include without causing a bunch of other conflicts. So we just include the one code we need. // diff --git a/src/terminal/adapter/ITerminalApi.hpp b/src/terminal/adapter/ITerminalApi.hpp index c3978b406f7..0cb900626fc 100644 --- a/src/terminal/adapter/ITerminalApi.hpp +++ b/src/terminal/adapter/ITerminalApi.hpp @@ -82,7 +82,6 @@ namespace Microsoft::Console::VirtualTerminal virtual void PlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) = 0; virtual bool ResizeWindow(const til::CoordType width, const til::CoordType height) = 0; - virtual bool IsConsolePty() const = 0; virtual void NotifyAccessibilityChange(const til::rect& changedRect) = 0; virtual void NotifyBufferRotation(const int delta) = 0; diff --git a/src/terminal/adapter/InteractDispatch.cpp b/src/terminal/adapter/InteractDispatch.cpp index a102368ec5b..09329eb6647 100644 --- a/src/terminal/adapter/InteractDispatch.cpp +++ b/src/terminal/adapter/InteractDispatch.cpp @@ -113,11 +113,7 @@ bool InteractDispatch::WindowManipulation(const DispatchTypes::WindowManipulatio case DispatchTypes::WindowManipulationType::ResizeWindowInCharacters: // TODO:GH#1765 We should introduce a better `ResizeConpty` function to // ConhostInternalGetSet, that specifically handles a conpty resize. - if (_api.ResizeWindow(parameter2.value_or(0), parameter1.value_or(0))) - { - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - THROW_IF_FAILED(gci.GetVtIo()->SuppressResizeRepaint()); - } + _api.ResizeWindow(parameter2.value_or(0), parameter1.value_or(0)); return true; default: return false; diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 9806eb46b41..68cd77bfc47 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -1538,8 +1538,7 @@ bool AdaptDispatch::DeviceAttributes() // level of 1. The subsequent parameters identify the supported feature // extensions. // - // 1 = 132 column mode (ConHost only) - // 4 = Sixel Graphics (ConHost only) + // 4 = Sixel Graphics // 6 = Selective erase // 7 = Soft fonts // 14 = 8-bit interface architecture @@ -1551,14 +1550,7 @@ bool AdaptDispatch::DeviceAttributes() // 32 = Text macros // 42 = ISO Latin-2 character set - if (_api.IsConsolePty()) - { - _api.ReturnResponse(L"\x1b[?61;6;7;14;21;22;23;24;28;32;42c"); - } - else - { - _api.ReturnResponse(L"\x1b[?61;1;4;6;7;14;21;22;23;24;28;32;42c"); - } + _api.ReturnResponse(L"\x1b[?61;4;6;7;14;21;22;23;24;28;32;42c"); return true; } @@ -1853,7 +1845,7 @@ bool AdaptDispatch::RequestDisplayedExtent() void AdaptDispatch::_SetColumnMode(const bool enable) { // Only proceed if DECCOLM is allowed. Return true, as this is technically a successful handling. - if (_modes.test(Mode::AllowDECCOLM) && !_api.IsConsolePty()) + if (_modes.test(Mode::AllowDECCOLM)) { const auto page = _pages.VisiblePage(); const auto pageHeight = page.Height(); @@ -1893,23 +1885,6 @@ void AdaptDispatch::_SetAlternateScreenBufferMode(const bool enable) } } -// Routine Description: -// - Determines whether we need to pass through input mode requests. -// If we're a conpty, AND WE'RE IN VT INPUT MODE, always pass input mode requests -// The VT Input mode check is to work around ssh.exe v7.7, which uses VT -// output, but not Input. -// The original comment said, "Once the conpty supports these types of input, -// this check can be removed. See GH#4911". Unfortunately, time has shown -// us that SSH 7.7 _also_ requests mouse input and that can have a user interface -// impact on the actual connected terminal. We can't remove this check, -// because SSH <=7.7 is out in the wild on all versions of Windows <=2004. -// Return Value: -// - True if we should pass through. False otherwise. -bool AdaptDispatch::_PassThroughInputModes() -{ - return _api.IsConsolePty() && _api.IsVtInputEnabled(); -} - // Routine Description: // - Support routine for routing mode parameters to be set/reset as flags // Arguments: @@ -1935,7 +1910,7 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con return true; case DispatchTypes::ModeParams::DECCKM_CursorKeysMode: _terminalInput.SetInputMode(TerminalInput::Mode::CursorKey, enable); - return !_PassThroughInputModes(); + return true; case DispatchTypes::ModeParams::DECANM_AnsiMode: return SetAnsiMode(enable); case DispatchTypes::ModeParams::DECCOLM_SetNumberOfColumns: @@ -1943,11 +1918,6 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con return true; case DispatchTypes::ModeParams::DECSCNM_ScreenMode: _renderSettings.SetRenderMode(RenderSettings::Mode::ScreenReversed, enable); - // No need to force a redraw in pty mode. - if (_api.IsConsolePty()) - { - return false; - } if (_renderer) { _renderer->TriggerRedrawAll(); @@ -1968,10 +1938,10 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con return true; case DispatchTypes::ModeParams::DECARM_AutoRepeatMode: _terminalInput.SetInputMode(TerminalInput::Mode::AutoRepeat, enable); - return !_PassThroughInputModes(); + return true; case DispatchTypes::ModeParams::ATT610_StartCursorBlink: _pages.ActivePage().Cursor().SetBlinkingAllowed(enable); - return !_api.IsConsolePty(); + return true; case DispatchTypes::ModeParams::DECTCEM_TextCursorEnableMode: _pages.ActivePage().Cursor().SetIsVisible(enable); return true; @@ -1987,10 +1957,10 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con return true; case DispatchTypes::ModeParams::DECNKM_NumericKeypadMode: _terminalInput.SetInputMode(TerminalInput::Mode::Keypad, enable); - return !_PassThroughInputModes(); + return true; case DispatchTypes::ModeParams::DECBKM_BackarrowKeyMode: _terminalInput.SetInputMode(TerminalInput::Mode::BackarrowKey, enable); - return !_PassThroughInputModes(); + return true; case DispatchTypes::ModeParams::DECLRMM_LeftRightMarginMode: _modes.set(Mode::AllowDECSLRM, enable); _DoSetLeftRightScrollingMargins(0, 0); @@ -2013,33 +1983,32 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con return true; case DispatchTypes::ModeParams::VT200_MOUSE_MODE: _terminalInput.SetInputMode(TerminalInput::Mode::DefaultMouseTracking, enable); - return !_PassThroughInputModes(); + return true; case DispatchTypes::ModeParams::BUTTON_EVENT_MOUSE_MODE: _terminalInput.SetInputMode(TerminalInput::Mode::ButtonEventMouseTracking, enable); - return !_PassThroughInputModes(); + return true; case DispatchTypes::ModeParams::ANY_EVENT_MOUSE_MODE: _terminalInput.SetInputMode(TerminalInput::Mode::AnyEventMouseTracking, enable); - return !_PassThroughInputModes(); + return true; case DispatchTypes::ModeParams::UTF8_EXTENDED_MODE: _terminalInput.SetInputMode(TerminalInput::Mode::Utf8MouseEncoding, enable); - return !_PassThroughInputModes(); + return true; case DispatchTypes::ModeParams::SGR_EXTENDED_MODE: _terminalInput.SetInputMode(TerminalInput::Mode::SgrMouseEncoding, enable); - return !_PassThroughInputModes(); + return true; case DispatchTypes::ModeParams::FOCUS_EVENT_MODE: _terminalInput.SetInputMode(TerminalInput::Mode::FocusEvent, enable); - // GH#12799 - If the app requested that we disable focus events, DON'T pass - // that through. ConPTY would _always_ like to know about focus events. - return !_PassThroughInputModes() || !enable; + _api.GetStateMachine().InjectSequence(InjectionType::DECSET_FOCUS); + return true; case DispatchTypes::ModeParams::ALTERNATE_SCROLL: _terminalInput.SetInputMode(TerminalInput::Mode::AlternateScroll, enable); - return !_PassThroughInputModes(); + return true; case DispatchTypes::ModeParams::ASB_AlternateScreenBuffer: _SetAlternateScreenBufferMode(enable); return true; case DispatchTypes::ModeParams::XTERM_BracketedPasteMode: _api.SetSystemMode(ITerminalApi::Mode::BracketedPaste, enable); - return !_api.IsConsolePty(); + return true; case DispatchTypes::ModeParams::GCM_GraphemeClusterMode: return true; case DispatchTypes::ModeParams::W32IM_Win32InputMode: @@ -2113,11 +2082,7 @@ bool AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param) state = mapTemp(_api.GetStateMachine().GetParserMode(StateMachine::Mode::Ansi)); break; case DispatchTypes::ModeParams::DECCOLM_SetNumberOfColumns: - // DECCOLM is not supported in conpty mode - if (!_api.IsConsolePty()) - { - state = mapTemp(_modes.test(Mode::Column)); - } + state = mapTemp(_modes.test(Mode::Column)); break; case DispatchTypes::ModeParams::DECSCNM_ScreenMode: state = mapTemp(_renderSettings.GetRenderMode(RenderSettings::Mode::ScreenReversed)); @@ -2138,11 +2103,7 @@ bool AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param) state = mapTemp(_pages.ActivePage().Cursor().IsVisible()); break; case DispatchTypes::ModeParams::XTERM_EnableDECCOLMSupport: - // DECCOLM is not supported in conpty mode - if (!_api.IsConsolePty()) - { - state = mapTemp(_modes.test(Mode::AllowDECCOLM)); - } + state = mapTemp(_modes.test(Mode::AllowDECCOLM)); break; case DispatchTypes::ModeParams::DECPCCM_PageCursorCouplingMode: state = mapTemp(_modes.test(Mode::PageCursorCoupling)); @@ -2217,10 +2178,10 @@ bool AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param) // - applicationMode - set to true to enable Application Mode Input, false for Numeric Mode Input. // Return Value: // - True if handled successfully. False otherwise. -bool AdaptDispatch::SetKeypadMode(const bool fApplicationMode) +bool AdaptDispatch::SetKeypadMode(const bool fApplicationMode) noexcept { _terminalInput.SetInputMode(TerminalInput::Mode::Keypad, fApplicationMode); - return !_PassThroughInputModes(); + return true; } // Routine Description: @@ -3178,7 +3139,7 @@ bool AdaptDispatch::SoftReset() _sixelParser->SoftReset(); } - return !_api.IsConsolePty(); + return true; } //Routine Description: @@ -3280,20 +3241,7 @@ bool AdaptDispatch::HardReset() _macroBuffer = nullptr; } - // If we're in a conpty, we need flush this RIS sequence to the connected - // terminal application, but we also need to follow that up with a DECSET - // sequence to re-enable the modes that we require (namely win32 input mode - // and focus event mode). It's important that this is kept in sync with the - // VtEngine::RequestWin32Input method which requests the modes on startup. - if (_api.IsConsolePty()) - { - auto& stateMachine = _api.GetStateMachine(); - if (stateMachine.FlushToTerminal()) - { - auto& engine = stateMachine.Engine(); - engine.ActionPassThroughString(L"\033[?9001h\033[?1004h"); - } - } + _api.GetStateMachine().InjectSequence(InjectionType::RIS); return true; } @@ -3353,11 +3301,7 @@ bool AdaptDispatch::_EraseScrollback() cursor.SetYPosition(row - page.Top()); cursor.SetHasMoved(true); - // GH#2715 - If this succeeded, but we're in a conpty, return `false` to - // make the state machine propagate this ED sequence to the connected - // terminal application. While we're in conpty mode, we don't really - // have a scrollback, but the attached terminal might. - return !_api.IsConsolePty(); + return true; } //Routine Description: @@ -3379,7 +3323,6 @@ bool AdaptDispatch::_EraseAll() const auto pageHeight = page.Height(); const auto bufferHeight = page.BufferHeight(); auto& textBuffer = page.Buffer(); - const auto inPtyMode = _api.IsConsolePty(); // Stash away the current position of the cursor within the page. // We'll need to restore the cursor to that same relative position, after @@ -3406,10 +3349,7 @@ bool AdaptDispatch::_EraseAll() // We don't want to trigger a scroll in pty mode, because we're going to // pass through the ED sequence anyway, and this will just result in the // buffer being scrolled up by two pages instead of one. - if (!inPtyMode) - { - textBuffer.TriggerScroll({ 0, -delta }); - } + textBuffer.TriggerScroll({ 0, -delta }); } // Move the viewport if necessary. if (newPageTop != page.Top()) @@ -3427,15 +3367,7 @@ bool AdaptDispatch::_EraseAll() // Also reset the line rendition for the erased rows. textBuffer.ResetLineRenditionRange(newPageTop, newPageBottom); - // GH#5683 - If this succeeded, but we're in a conpty, return `false` to - // make the state machine propagate this ED sequence to the connected - // terminal application. While we're in conpty mode, when the client - // requests a Erase All operation, we need to manually tell the - // connected terminal to do the same thing, so that the terminal will - // move it's own buffer contents into the scrollback. But this only - // applies if we're in the active buffer, since this should have no - // visible effect for an inactive buffer. - return !(inPtyMode && textBuffer.IsActiveBuffer()); + return true; } //Routine Description: @@ -3492,9 +3424,7 @@ bool AdaptDispatch::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) cursor.SetType(actualType); cursor.SetBlinkingAllowed(fEnableBlinking); - // If we're a conpty, always return false, so that this cursor state will be - // sent to the connected terminal - return !_api.IsConsolePty(); + return true; } // Method Description: @@ -3517,12 +3447,6 @@ bool AdaptDispatch::SetCursorColor(const COLORREF cursorColor) // - True if handled successfully. False otherwise. bool AdaptDispatch::SetClipboard(const wil::zwstring_view content) { - // Return false to forward the operation to the hosting terminal, - // since ConPTY can't handle this itself. - if (_api.IsConsolePty()) - { - return false; - } _api.CopyToClipboard(content); return true; } @@ -3538,15 +3462,6 @@ bool AdaptDispatch::SetColorTableEntry(const size_t tableIndex, const DWORD dwCo { _renderSettings.SetColorTableEntry(tableIndex, dwColor); - // If we're a conpty, always return false, so that we send the updated color - // value to the terminal. Still handle the sequence so apps that use - // the API or VT to query the values of the color table still read the - // correct color. - if (_api.IsConsolePty()) - { - return false; - } - if (_renderer) { // If we're updating the background color, we need to let the renderer @@ -3613,12 +3528,6 @@ bool AdaptDispatch::AssignColor(const DispatchTypes::ColorItem item, const VTInt return false; } - // No need to force a redraw in pty mode. - if (_api.IsConsolePty()) - { - return false; - } - if (_renderer) { const auto backgroundChanged = item == DispatchTypes::ColorItem::NormalText; @@ -3727,13 +3636,6 @@ bool AdaptDispatch::EndHyperlink() // - True if handled successfully. False otherwise. bool AdaptDispatch::DoConEmuAction(const std::wstring_view string) { - // Return false to forward the operation to the hosting terminal, - // since ConPTY can't handle this itself. - if (_api.IsConsolePty()) - { - return false; - } - constexpr size_t TaskbarMaxState{ 4 }; constexpr size_t TaskbarMaxProgress{ 100 }; @@ -3835,14 +3737,6 @@ bool AdaptDispatch::DoConEmuAction(const std::wstring_view string) // - false in conhost, true for the SetMark action, otherwise false. bool AdaptDispatch::DoITerm2Action(const std::wstring_view string) { - const auto isConPty = _api.IsConsolePty(); - if (isConPty && _renderer) - { - // Flush the frame manually, to make sure marks end up on the right - // line, like the alt buffer sequence. - _renderer->TriggerFlush(false); - } - if constexpr (!Feature_ScrollbarMarks::IsEnabled()) { return false; @@ -3864,7 +3758,7 @@ bool AdaptDispatch::DoITerm2Action(const std::wstring_view string) handled = true; } - return handled && !isConPty; + return handled; } // Method Description: @@ -3879,14 +3773,6 @@ bool AdaptDispatch::DoITerm2Action(const std::wstring_view string) // - false in conhost, true for the SetMark action, otherwise false. bool AdaptDispatch::DoFinalTermAction(const std::wstring_view string) { - const auto isConPty = _api.IsConsolePty(); - if (isConPty && _renderer) - { - // Flush the frame manually, to make sure marks end up on the right - // line, like the alt buffer sequence. - _renderer->TriggerFlush(false); - } - if constexpr (!Feature_ScrollbarMarks::IsEnabled()) { return false; @@ -3955,7 +3841,7 @@ bool AdaptDispatch::DoFinalTermAction(const std::wstring_view string) // simple state machine here to track the most recently emitted mark from // this set of sequences, and which sequence was emitted last, so we can // modify the state of that mark as we go. - return handled && !isConPty; + return handled; } // Method Description: // - Performs a VsCode action @@ -3970,14 +3856,6 @@ bool AdaptDispatch::DoFinalTermAction(const std::wstring_view string) // - false in conhost, true for the SetMark action, otherwise false. bool AdaptDispatch::DoVsCodeAction(const std::wstring_view string) { - // This is not implemented in conhost. - if (_api.IsConsolePty() && _renderer) - { - // Flush the frame manually to make sure this action happens at the right time. - _renderer->TriggerFlush(false); - return false; - } - if constexpr (!Feature_ShellCompletions::IsEnabled()) { return false; @@ -4052,14 +3930,6 @@ bool AdaptDispatch::DoVsCodeAction(const std::wstring_view string) // - false in conhost, true for the CmdNotFound action, otherwise false. bool AdaptDispatch::DoWTAction(const std::wstring_view string) { - // This is not implemented in conhost. - if (_api.IsConsolePty()) - { - // Flush the frame manually to make sure this action happens at the right time. - _renderer->TriggerFlush(false); - return false; - } - const auto parts = Utils::SplitString(string, L';'); if (parts.size() < 1) @@ -4151,19 +4021,7 @@ ITermDispatch::StringHandler AdaptDispatch::DownloadDRCS(const VTInt fontNumber, return nullptr; } - // If we're a conpty, we create a special passthrough handler that will - // forward the DECDLD sequence to the conpty terminal with a hard-coded ID. - // That ID is also pre-mapped into the G1 table, so the VT engine can just - // switch to G1 when it needs to output any DRCS characters. But note that - // we still need to process the DECDLD sequence locally, so the character - // set translation is correctly handled on the host side. - const auto conptyPassthrough = _api.IsConsolePty() ? _CreateDrcsPassthroughHandler(charsetSize) : nullptr; - return [=](const auto ch) { - if (conptyPassthrough) - { - conptyPassthrough(ch); - } // We pass the data string straight through to the font buffer class // until we receive an ESC, indicating the end of the string. At that // point we can finalize the buffer, and if valid, update the renderer @@ -4197,46 +4055,6 @@ ITermDispatch::StringHandler AdaptDispatch::DownloadDRCS(const VTInt fontNumber, }; } -// Routine Description: -// - Helper method to create a string handler that can be used to pass through -// DECDLD sequences when in conpty mode. This patches the original sequence -// with a hard-coded character set ID, and pre-maps that ID into the G1 table. -// Arguments: -// - -// Return value: -// - a function to receive the data or nullptr if the initial flush fails -ITermDispatch::StringHandler AdaptDispatch::_CreateDrcsPassthroughHandler(const DispatchTypes::CharsetSize charsetSize) -{ - const auto defaultPassthrough = _CreatePassthroughHandler(); - if (defaultPassthrough) - { - auto& engine = _api.GetStateMachine().Engine(); - return [=, &engine, gotId = false](const auto ch) mutable { - // The character set ID is contained in the first characters of the - // sequence, so we just ignore that initial content until we receive - // a "final" character (i.e. in range 30 to 7E). At that point we - // pass through a hard-coded ID of "@". - if (!gotId) - { - if (ch >= 0x30 && ch <= 0x7E) - { - gotId = true; - defaultPassthrough('@'); - } - } - else if (!defaultPassthrough(ch)) - { - // Once the DECDLD sequence is finished, we also output an SCS - // sequence to map the character set into the G1 table. - const auto charset96 = charsetSize == DispatchTypes::CharsetSize::Size96; - engine.ActionPassThroughString(charset96 ? L"\033-@" : L"\033)@"); - } - return true; - }; - } - return nullptr; -} - // Method Description: // - DECRQUPSS - Request the user-preference supplemental character set. // Arguments: @@ -4367,13 +4185,6 @@ ITermDispatch::StringHandler AdaptDispatch::RestoreTerminalState(const DispatchT // - a function to parse the report data. ITermDispatch::StringHandler AdaptDispatch::_RestoreColorTable() { - // If we're a conpty, we create a passthrough string handler to forward the - // color report to the connected terminal. - if (_api.IsConsolePty()) - { - return _CreatePassthroughHandler(); - } - return [this, parameter = VTInt{}, parameters = std::vector{}](const auto ch) mutable { if (ch >= L'0' && ch <= L'9') { @@ -5049,15 +4860,6 @@ ITermDispatch::StringHandler AdaptDispatch::_RestoreTabStops() // - True if handled successfully. False otherwise. bool AdaptDispatch::PlaySounds(const VTParameters parameters) { - // If we're a conpty, we return false so the command will be passed on - // to the connected terminal. But we need to flush the current frame - // first, otherwise the visual output will lag behind the sound. - if (_api.IsConsolePty() && _renderer) - { - _renderer->TriggerFlush(false); - return false; - } - // First parameter is the volume, in the range 0 to 7. We multiply by // 127 / 7 to obtain an equivalent MIDI velocity in the range 0 to 127. const auto velocity = std::min(parameters.at(0).value_or(0), 7) * 127 / 7; @@ -5076,51 +4878,3 @@ bool AdaptDispatch::PlaySounds(const VTParameters parameters) return true; }); } - -// Routine Description: -// - Helper method to create a string handler that can be used to pass through -// DCS sequences when in conpty mode. -// Arguments: -// - -// Return value: -// - a function to receive the data or nullptr if the initial flush fails -ITermDispatch::StringHandler AdaptDispatch::_CreatePassthroughHandler() -{ - // Before we pass through any more data, we need to flush the current frame - // first, otherwise it can end up arriving out of sync. - if (_renderer) - { - _renderer->TriggerFlush(false); - } - - // Then we need to flush the sequence introducer and parameters that have - // already been parsed by the state machine. - auto& stateMachine = _api.GetStateMachine(); - if (stateMachine.FlushToTerminal()) - { - // And finally we create a StringHandler to receive the rest of the - // sequence data, and pass it through to the connected terminal. - auto& engine = stateMachine.Engine(); - return [&, buffer = std::wstring{}](const auto ch) mutable { - // To make things more efficient, we buffer the string data before - // passing it through, only flushing if the buffer gets too large, - // or we're dealing with the last character in the current output - // fragment, or we've reached the end of the string. - const auto endOfString = ch == AsciiChars::ESC; - buffer += ch; - if (buffer.length() >= 4096 || stateMachine.IsProcessingLastCharacter() || endOfString) - { - // The end of the string is signaled with an escape, but for it - // to be a valid string terminator we need to add a backslash. - if (endOfString) - { - buffer += L'\\'; - } - engine.ActionPassThroughString(buffer, true); - buffer.clear(); - } - return !endOfString; - }; - } - return nullptr; -} diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index d4e46c6feeb..c7a913e2bf7 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -95,7 +95,7 @@ namespace Microsoft::Console::VirtualTerminal bool SetMode(const DispatchTypes::ModeParams param) override; // SM, DECSET bool ResetMode(const DispatchTypes::ModeParams param) override; // RM, DECRST bool RequestMode(const DispatchTypes::ModeParams param) override; // DECRQM - bool SetKeypadMode(const bool applicationMode) override; // DECKPAM, DECKPNM + bool SetKeypadMode(const bool applicationMode) noexcept override; // DECKPAM, DECKPNM bool SetAnsiMode(const bool ansiMode) override; // DECANM bool SetTopBottomScrollingMargins(const VTInt topMargin, const VTInt bottomMargin) override; // DECSTBM @@ -265,7 +265,6 @@ namespace Microsoft::Console::VirtualTerminal void _SetColumnMode(const bool enable); void _SetAlternateScreenBufferMode(const bool enable); - bool _PassThroughInputModes(); bool _ModeParamsHelper(const DispatchTypes::ModeParams param, const bool enable); void _ClearSingleTabStop(); @@ -286,9 +285,6 @@ namespace Microsoft::Console::VirtualTerminal void _ReportTabStops(); StringHandler _RestoreTabStops(); - StringHandler _CreateDrcsPassthroughHandler(const DispatchTypes::CharsetSize charsetSize); - StringHandler _CreatePassthroughHandler(); - std::vector _tabStopColumns; bool _initDefaultTabStops = true; diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index da5a7c93cb2..ca44278b96e 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -195,12 +195,6 @@ class TestGetSet final : public ITerminalApi Log::Comment(L"PlayMidiNote MOCK called..."); } - bool IsConsolePty() const override - { - Log::Comment(L"IsConsolePty MOCK called..."); - return _isPty; - } - void NotifyAccessibilityChange(const til::rect& /*changedRect*/) override { Log::Comment(L"NotifyAccessibilityChange MOCK called..."); @@ -1710,7 +1704,7 @@ class AdapterTest _testGetSet->PrepData(); VERIFY_IS_TRUE(_pDispatch->DeviceAttributes()); - auto pwszExpectedResponse = L"\x1b[?61;1;4;6;7;14;21;22;23;24;28;32;42c"; + auto pwszExpectedResponse = L"\x1b[?61;4;6;7;14;21;22;23;24;28;32;42c"; _testGetSet->ValidateInputEvent(pwszExpectedResponse); Log::Comment(L"Test 2: Verify failure when ReturnResponse doesn't work."); diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index 0c12c5e9b1f..82576afd542 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -4,11 +4,12 @@ #include "precomp.h" #include "OutputStateMachineEngine.hpp" +#include + #include "ascii.hpp" #include "base64.hpp" #include "stateMachine.hpp" #include "../../types/inc/utils.hpp" -#include "../renderer/vt/vtrenderer.hpp" using namespace Microsoft::Console; using namespace Microsoft::Console::VirtualTerminal; @@ -16,8 +17,6 @@ using namespace Microsoft::Console::VirtualTerminal; // takes ownership of pDispatch OutputStateMachineEngine::OutputStateMachineEngine(std::unique_ptr pDispatch) : _dispatch(std::move(pDispatch)), - _pfnFlushToTerminal(nullptr), - _pTtyConnection(nullptr), _lastPrintedChar(AsciiChars::NUL) { THROW_HR_IF_NULL(E_INVALIDARG, _dispatch.get()); @@ -56,12 +55,6 @@ bool OutputStateMachineEngine::ActionExecute(const wchar_t wch) break; case AsciiChars::BEL: _dispatch->WarningBell(); - // microsoft/terminal#2952 - // If we're attached to a terminal, let's also pass the BEL through. - if (_pfnFlushToTerminal != nullptr) - { - _pfnFlushToTerminal(); - } break; case AsciiChars::BS: _dispatch->CursorBackward(1); @@ -183,18 +176,9 @@ bool OutputStateMachineEngine::ActionPrintString(const std::wstring_view string) // - flush - set to true if the string should be flushed immediately. // Return Value: // - true iff we successfully dispatched the sequence. -bool OutputStateMachineEngine::ActionPassThroughString(const std::wstring_view string, const bool flush) +bool OutputStateMachineEngine::ActionPassThroughString(const std::wstring_view /*string*/, const bool /*flush*/) noexcept { - auto success = true; - if (_pTtyConnection != nullptr) - { - const auto hr = _pTtyConnection->WriteTerminalW(string, flush); - LOG_IF_FAILED(hr); - success = SUCCEEDED(hr); - } - // If there's not a TTY connection, our previous behavior was to eat the string. - - return success; + return true; } // Routine Description: @@ -335,13 +319,6 @@ bool OutputStateMachineEngine::ActionEscDispatch(const VTID id) } } - // If we were unable to process the string, and there's a TTY attached to us, - // trigger the state machine to flush the string to the terminal. - if (_pfnFlushToTerminal != nullptr && !success) - { - success = _pfnFlushToTerminal(); - } - _ClearLastChar(); return success; @@ -693,13 +670,6 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete break; } - // If we were unable to process the string, and there's a TTY attached to us, - // trigger the state machine to flush the string to the terminal. - if (_pfnFlushToTerminal != nullptr && !success) - { - success = _pfnFlushToTerminal(); - } - _ClearLastChar(); return success; @@ -929,13 +899,6 @@ bool OutputStateMachineEngine::ActionOscDispatch(const size_t parameter, const s break; } - // If we were unable to process the string, and there's a TTY attached to us, - // trigger the state machine to flush the string to the terminal. - if (_pfnFlushToTerminal != nullptr && !success) - { - success = _pfnFlushToTerminal(); - } - _ClearLastChar(); return success; @@ -1093,26 +1056,6 @@ bool OutputStateMachineEngine::_GetOscSetColor(const std::wstring_view string, return rgbs.size() > 0; } -// Method Description: -// - Sets us up to have another terminal acting as the tty instead of conhost. -// We'll set a couple members, and if they aren't null, when we get a -// sequence we don't understand, we'll pass it along to the terminal -// instead of eating it ourselves. -// Arguments: -// - pTtyConnection: This is a TerminalOutputConnection that we can write the -// sequence we didn't understand to. -// - pfnFlushToTerminal: This is a callback to the underlying state machine to -// trigger it to call ActionPassThroughString with whatever sequence it's -// currently processing. -// Return Value: -// - -void OutputStateMachineEngine::SetTerminalConnection(Render::VtEngine* const pTtyConnection, - std::function pfnFlushToTerminal) -{ - this->_pTtyConnection = pTtyConnection; - this->_pfnFlushToTerminal = pfnFlushToTerminal; -} - // Routine Description: // - Parse OscSetClipboard parameters with the format `Pc;Pd`. Currently the first parameter `Pc` is // ignored. The second parameter `Pd` should be a valid base64 string or character `?`. diff --git a/src/terminal/parser/OutputStateMachineEngine.hpp b/src/terminal/parser/OutputStateMachineEngine.hpp index 68097473995..5be20ae2513 100644 --- a/src/terminal/parser/OutputStateMachineEngine.hpp +++ b/src/terminal/parser/OutputStateMachineEngine.hpp @@ -15,11 +15,6 @@ Module Name: #include "../adapter/termDispatch.hpp" #include "IStateMachineEngine.hpp" -namespace Microsoft::Console::Render -{ - class VtEngine; -} - namespace Microsoft::Console::VirtualTerminal { class OutputStateMachineEngine : public IStateMachineEngine @@ -38,7 +33,7 @@ namespace Microsoft::Console::VirtualTerminal bool ActionPrintString(const std::wstring_view string) override; - bool ActionPassThroughString(const std::wstring_view string, const bool flush) override; + bool ActionPassThroughString(const std::wstring_view string, const bool flush) noexcept override; bool ActionEscDispatch(const VTID id) override; @@ -56,16 +51,11 @@ namespace Microsoft::Console::VirtualTerminal bool ActionSs3Dispatch(const wchar_t wch, const VTParameters parameters) noexcept override; - void SetTerminalConnection(Microsoft::Console::Render::VtEngine* const pTtyConnection, - std::function pfnFlushToTerminal); - const ITermDispatch& Dispatch() const noexcept; ITermDispatch& Dispatch() noexcept; private: std::unique_ptr _dispatch; - Microsoft::Console::Render::VtEngine* _pTtyConnection; - std::function _pfnFlushToTerminal; wchar_t _lastPrintedChar; enum EscActionCodes : uint64_t diff --git a/src/terminal/parser/ft_fuzzer/sources b/src/terminal/parser/ft_fuzzer/sources index 6ac8fbbb39a..a8c508a25dc 100644 --- a/src/terminal/parser/ft_fuzzer/sources +++ b/src/terminal/parser/ft_fuzzer/sources @@ -67,8 +67,6 @@ SOURCES = \ INCLUDES = \ ..\..\..\inc; \ - $(CONSOLE_SRC_PATH)\..\oss\dynamic_bitset; \ - $(CONSOLE_SRC_PATH)\..\oss\libpopcnt; \ $(CONSOLE_SRC_PATH)\..\oss\chromium; \ $(CONSOLE_SRC_PATH)\..\oss\fmt\include; \ $(CONSOLE_SRC_PATH)\..\oss\interval_tree; \ diff --git a/src/terminal/parser/stateMachine.cpp b/src/terminal/parser/stateMachine.cpp index 23bc83ecc81..07918e902bc 100644 --- a/src/terminal/parser/stateMachine.cpp +++ b/src/terminal/parser/stateMachine.cpp @@ -1859,7 +1859,7 @@ void StateMachine::ProcessCharacter(const wchar_t wch) // code points that get translated as C1 controls when that is not their // intended use. In order to avoid them triggering unintentional escape // sequences, we ignore these characters by default. - if (_parserMode.any(Mode::AcceptC1, Mode::AlwaysAcceptC1)) + if (_parserMode.test(Mode::AcceptC1)) { ProcessCharacter(AsciiChars::ESC); ProcessCharacter(_c1To7Bit(wch)); @@ -1978,6 +1978,7 @@ void StateMachine::ProcessString(const std::wstring_view string) _currentString = string; _runOffset = 0; _runSize = 0; + _injections.clear(); if (_state != VTStates::Ground) { @@ -2098,6 +2099,16 @@ bool StateMachine::IsProcessingLastCharacter() const noexcept return _processingLastCharacter; } +void StateMachine::InjectSequence(const InjectionType type) +{ + _injections.emplace_back(type, _runOffset + _runSize); +} + +const til::small_vector& StateMachine::GetInjections() const noexcept +{ + return _injections; +} + // Routine Description: // - Registers a function that will be called once the current CSI action is // complete and the state machine has returned to the ground state. diff --git a/src/terminal/parser/stateMachine.hpp b/src/terminal/parser/stateMachine.hpp index 245e25fefa6..b74b9d3d474 100644 --- a/src/terminal/parser/stateMachine.hpp +++ b/src/terminal/parser/stateMachine.hpp @@ -37,6 +37,18 @@ namespace Microsoft::Console::VirtualTerminal // the their indexes. static_assert(MAX_PARAMETER_COUNT * MAX_SUBPARAMETER_COUNT <= 256); + enum class InjectionType : size_t + { + RIS, + DECSET_FOCUS, + }; + + struct Injection + { + InjectionType type; + size_t offset; + }; + class StateMachine final { #ifdef UNIT_TESTING @@ -55,7 +67,6 @@ namespace Microsoft::Console::VirtualTerminal enum class Mode : size_t { AcceptC1, - AlwaysAcceptC1, Ansi, }; @@ -66,10 +77,11 @@ namespace Microsoft::Console::VirtualTerminal void ProcessString(const std::wstring_view string); bool IsProcessingLastCharacter() const noexcept; - void OnCsiComplete(const std::function callback); + void InjectSequence(InjectionType type); + const til::small_vector& GetInjections() const noexcept; + void OnCsiComplete(const std::function callback); void ResetState() noexcept; - bool FlushToTerminal(); const IStateMachineEngine& Engine() const noexcept; @@ -212,6 +224,7 @@ namespace Microsoft::Console::VirtualTerminal IStateMachineEngine::StringHandler _dcsStringHandler; std::optional _cachedSequence; + til::small_vector _injections; // This is tracked per state machine instance so that separate calls to Process* // can start and finish a sequence. diff --git a/src/til/ut_til/BitmapTests.cpp b/src/til/ut_til/BitmapTests.cpp deleted file mode 100644 index b17bf0fff4e..00000000000 --- a/src/til/ut_til/BitmapTests.cpp +++ /dev/null @@ -1,1095 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "til/bitmap.h" - -using namespace WEX::Common; -using namespace WEX::Logging; -using namespace WEX::TestExecution; - -class BitmapTests -{ - TEST_CLASS(BitmapTests); - - template - void _checkBits(const til::rect& bitsOn, - const til::details::bitmap& map) - { - _checkBits(std::vector{ bitsOn }, map); - } - - template - void _checkBits(const std::vector& bitsOn, - const til::details::bitmap& map) - { - Log::Comment(L"Check all bits in map."); - // For every point in the map... - for (const auto pt : map._rc) - { - // If any of the rectangles we were given contains this point, we expect it should be on. - const auto expected = std::any_of(bitsOn.cbegin(), bitsOn.cend(), [&pt](auto bitRect) { return bitRect.contains(pt); }); - - // Get the actual bit out of the map. - const auto actual = map._bits[map._rc.index_of(pt)]; - - // Do it this way and not with equality so you can see it in output. - if (expected) - { - VERIFY_IS_TRUE(actual); - } - else - { - VERIFY_IS_FALSE(actual); - } - } - } - - TEST_METHOD(DefaultConstruct) - { - const til::bitmap bitmap; - const til::size expectedSize{ 0, 0 }; - const til::rect expectedRect{ 0, 0, 0, 0 }; - VERIFY_ARE_EQUAL(expectedSize, bitmap._sz); - VERIFY_ARE_EQUAL(expectedRect, bitmap._rc); - VERIFY_ARE_EQUAL(0u, bitmap._bits.size()); - - // The find will go from begin to end in the bits looking for a "true". - // It should miss so the result should be "cend" and turn out true here. - VERIFY_IS_TRUE(bitmap._bits.none()); - } - - TEST_METHOD(SizeConstruct) - { - const til::size expectedSize{ 5, 10 }; - const til::rect expectedRect{ 0, 0, 5, 10 }; - const til::bitmap bitmap{ expectedSize }; - VERIFY_ARE_EQUAL(expectedSize, bitmap._sz); - VERIFY_ARE_EQUAL(expectedRect, bitmap._rc); - VERIFY_ARE_EQUAL(50u, bitmap._bits.size()); - - // The find will go from begin to end in the bits looking for a "true". - // It should miss so the result should be "cend" and turn out true here. - VERIFY_IS_TRUE(bitmap._bits.none()); - } - - TEST_METHOD(SizeConstructWithFill) - { - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:fill", L"{true, false}") - END_TEST_METHOD_PROPERTIES() - - bool fill; - VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"fill", fill)); - - const til::size expectedSize{ 5, 10 }; - const til::rect expectedRect{ 0, 0, 5, 10 }; - const til::bitmap bitmap{ expectedSize, fill }; - VERIFY_ARE_EQUAL(expectedSize, bitmap._sz); - VERIFY_ARE_EQUAL(expectedRect, bitmap._rc); - VERIFY_ARE_EQUAL(50u, bitmap._bits.size()); - - if (!fill) - { - VERIFY_IS_TRUE(bitmap._bits.none()); - } - else - { - VERIFY_IS_TRUE(bitmap._bits.all()); - } - } - - TEST_METHOD(Equality) - { - Log::Comment(L"0.) Defaults are equal"); - { - const til::bitmap one; - const til::bitmap two; - VERIFY_IS_TRUE(one == two); - } - - Log::Comment(L"1.) Different sizes are unequal"); - { - const til::bitmap one{ til::size{ 2, 2 } }; - const til::bitmap two{ til::size{ 3, 3 } }; - VERIFY_IS_FALSE(one == two); - } - - Log::Comment(L"2.) Same bits set are equal"); - { - til::bitmap one{ til::size{ 2, 2 } }; - til::bitmap two{ til::size{ 2, 2 } }; - one.set(til::point{ 0, 1 }); - one.set(til::point{ 1, 0 }); - two.set(til::point{ 0, 1 }); - two.set(til::point{ 1, 0 }); - VERIFY_IS_TRUE(one == two); - } - - Log::Comment(L"3.) Different bits set are not equal"); - { - til::bitmap one{ til::size{ 2, 2 } }; - til::bitmap two{ til::size{ 2, 2 } }; - one.set(til::point{ 0, 1 }); - two.set(til::point{ 1, 0 }); - VERIFY_IS_FALSE(one == two); - } - } - - TEST_METHOD(Inequality) - { - Log::Comment(L"0.) Defaults are equal"); - { - const til::bitmap one; - const til::bitmap two; - VERIFY_IS_FALSE(one != two); - } - - Log::Comment(L"1.) Different sizes are unequal"); - { - const til::bitmap one{ til::size{ 2, 2 } }; - const til::bitmap two{ til::size{ 3, 3 } }; - VERIFY_IS_TRUE(one != two); - } - - Log::Comment(L"2.) Same bits set are equal"); - { - til::bitmap one{ til::size{ 2, 2 } }; - til::bitmap two{ til::size{ 2, 2 } }; - one.set(til::point{ 0, 1 }); - one.set(til::point{ 1, 0 }); - two.set(til::point{ 0, 1 }); - two.set(til::point{ 1, 0 }); - VERIFY_IS_FALSE(one != two); - } - - Log::Comment(L"3.) Different bits set are not equal"); - { - til::bitmap one{ til::size{ 2, 2 } }; - til::bitmap two{ til::size{ 2, 2 } }; - one.set(til::point{ 0, 1 }); - two.set(til::point{ 1, 0 }); - VERIFY_IS_TRUE(one != two); - } - } - - TEST_METHOD(Translate) - { - const til::size mapSize{ 4, 4 }; - til::bitmap map{ mapSize }; - - // set the middle four bits of the map. - // 0 0 0 0 - // 0 1 1 0 - // 0 1 1 0 - // 0 0 0 0 - map.set(til::rect{ til::point{ 1, 1 }, til::size{ 2, 2 } }); - - Log::Comment(L"1.) Move down and right"); - { - auto actual = map; - // Move all contents - // | - // v - // | - // v --> --> - const til::point delta{ 2, 2 }; - - til::bitmap expected{ mapSize }; - // Expected: - // 0 0 0 0 0 0 0 0 0 0 0 0 - // 0 1 1 0 0 0 0 0 0 0 0 0 - // 0 1 1 0 v --> 0 0 0 0 --> 0 0 0 0 - // 0 0 0 0 v 0 1 1 0 0 0 0 1 - // ->-> - expected.set(til::point{ 3, 3 }); - - actual.translate(delta); - - VERIFY_ARE_EQUAL(expected, actual); - } - - Log::Comment(L"2.) Move down"); - { - auto actual = map; - // Move all contents - // | - // v - // | - // v - const til::point delta{ 0, 2 }; - - til::bitmap expected{ mapSize }; - // Expected: - // 0 0 0 0 0 0 0 0 - // 0 1 1 0 0 0 0 0 - // 0 1 1 0 v --> 0 0 0 0 - // 0 0 0 0 v 0 1 1 0 - expected.set(til::rect{ til::point{ 1, 3 }, til::size{ 2, 1 } }); - - actual.translate(delta); - - VERIFY_ARE_EQUAL(expected, actual); - } - - Log::Comment(L"3.) Move down and left"); - { - auto actual = map; - // Move all contents - // | - // v - // | - // v <-- <-- - const til::point delta{ -2, 2 }; - - til::bitmap expected{ mapSize }; - // Expected: - // 0 0 0 0 0 0 0 0 0 0 0 0 - // 0 1 1 0 0 0 0 0 0 0 0 0 - // 0 1 1 0 v --> 0 0 0 0 --> 0 0 0 0 - // 0 0 0 0 v 0 1 1 0 1 0 0 0 - // <-<- - expected.set(til::point{ 0, 3 }); - - actual.translate(delta); - - VERIFY_ARE_EQUAL(expected, actual); - } - - Log::Comment(L"4.) Move left"); - { - auto actual = map; - // Move all contents - // <-- <-- - const til::point delta{ -2, 0 }; - - til::bitmap expected{ mapSize }; - // Expected: - // 0 0 0 0 0 0 0 0 - // 0 1 1 0 1 0 0 0 - // 0 1 1 0 --> 1 0 0 0 - // 0 0 0 0 0 0 0 0 - // <--<-- - expected.set(til::rect{ til::point{ 0, 1 }, til::size{ 1, 2 } }); - - actual.translate(delta); - - VERIFY_ARE_EQUAL(expected, actual); - } - - Log::Comment(L"5.) Move up and left"); - { - auto actual = map; - // Move all contents - // ^ - // | - // ^ - // | <-- <-- - const til::point delta{ -2, -2 }; - - til::bitmap expected{ mapSize }; - // Expected: - // 0 0 0 0 ^ 0 1 1 0 1 0 0 0 - // 0 1 1 0 ^ 0 0 0 0 0 0 0 0 - // 0 1 1 0 --> 0 0 0 0 --> 0 0 0 0 - // 0 0 0 0 0 0 0 0 0 0 0 0 - // <-<- - expected.set(til::point{ 0, 0 }); - - actual.translate(delta); - - VERIFY_ARE_EQUAL(expected, actual); - } - - Log::Comment(L"6.) Move up"); - { - auto actual = map; - // Move all contents - // ^ - // | - // ^ - // | - const til::point delta{ 0, -2 }; - - til::bitmap expected{ mapSize }; - // Expected: - // 0 0 0 0 ^ 0 1 1 0 - // 0 1 1 0 ^ 0 0 0 0 - // 0 1 1 0 --> 0 0 0 0 - // 0 0 0 0 0 0 0 0 - expected.set(til::rect{ til::point{ 1, 0 }, til::size{ 2, 1 } }); - - actual.translate(delta); - - VERIFY_ARE_EQUAL(expected, actual); - } - - Log::Comment(L"7.) Move up and right"); - { - auto actual = map; - // Move all contents - // ^ - // | - // ^ - // | --> --> - const til::point delta{ 2, -2 }; - - til::bitmap expected{ mapSize }; - // Expected: - // 0 0 0 0 ^ 0 1 1 0 0 0 0 1 - // 0 1 1 0 ^ 0 0 0 0 0 0 0 0 - // 0 1 1 0 --> 0 0 0 0 --> 0 0 0 0 - // 0 0 0 0 0 0 0 0 0 0 0 0 - // ->-> - expected.set(til::point{ 3, 0 }); - - actual.translate(delta); - - VERIFY_ARE_EQUAL(expected, actual); - } - - Log::Comment(L"8.) Move right"); - { - auto actual = map; - // Move all contents - // --> --> - const til::point delta{ 2, 0 }; - - til::bitmap expected{ mapSize }; - // Expected: - // 0 0 0 0 0 0 0 0 - // 0 1 1 0 0 0 0 1 - // 0 1 1 0 --> 0 0 0 1 - // 0 0 0 0 0 0 0 0 - // ->-> - expected.set(til::rect{ til::point{ 3, 1 }, til::size{ 1, 2 } }); - - actual.translate(delta); - - VERIFY_ARE_EQUAL(expected, actual); - } - } - - TEST_METHOD(TranslateWithFill) - { - const til::size mapSize{ 4, 4 }; - til::bitmap map{ mapSize }; - - // set the middle four bits of the map. - // 0 0 0 0 - // 0 1 1 0 - // 0 1 1 0 - // 0 0 0 0 - map.set(til::rect{ til::point{ 1, 1 }, til::size{ 2, 2 } }); - - Log::Comment(L"1.) Move down and right"); - { - auto actual = map; - // Move all contents - // | - // v - // | - // v --> --> - const til::point delta{ 2, 2 }; - - til::bitmap expected{ mapSize }; - // Expected: (F is filling uncovered value) - // 0 0 0 0 F F F F F F F F - // 0 1 1 0 F F F F F F F F - // 0 1 1 0 v --> 0 0 0 0 --> F F 0 0 - // 0 0 0 0 v 0 1 1 0 F F 0 1 - // ->-> - expected.set(til::rect{ til::point{ 0, 0 }, til::size{ 4, 2 } }); - expected.set(til::rect{ til::point{ 0, 2 }, til::size{ 2, 2 } }); - expected.set(til::point{ 3, 3 }); - - actual.translate(delta, true); - - VERIFY_ARE_EQUAL(expected, actual); - } - - Log::Comment(L"2.) Move down"); - { - auto actual = map; - // Move all contents - // | - // v - // | - // v - const til::point delta{ 0, 2 }; - - til::bitmap expected{ mapSize }; - // Expected: (F is filling uncovered value) - // 0 0 0 0 F F F F - // 0 1 1 0 F F F F - // 0 1 1 0 v --> 0 0 0 0 - // 0 0 0 0 v 0 1 1 0 - expected.set(til::rect{ til::point{ 0, 0 }, til::size{ 4, 2 } }); - expected.set(til::rect{ til::point{ 1, 3 }, til::size{ 2, 1 } }); - - actual.translate(delta, true); - - VERIFY_ARE_EQUAL(expected, actual); - } - - Log::Comment(L"3.) Move down and left"); - { - auto actual = map; - // Move all contents - // | - // v - // | - // v <-- <-- - const til::point delta{ -2, 2 }; - - til::bitmap expected{ mapSize }; - // Expected: (F is filling uncovered value) - // 0 0 0 0 F F F F F F F F - // 0 1 1 0 F F F F F F F F - // 0 1 1 0 v --> 0 0 0 0 --> 0 0 F F - // 0 0 0 0 v 0 1 1 0 1 0 F F - // <-<- - expected.set(til::rect{ til::point{ 0, 0 }, til::size{ 4, 2 } }); - expected.set(til::rect{ til::point{ 2, 2 }, til::size{ 2, 2 } }); - expected.set(til::point{ 0, 3 }); - - actual.translate(delta, true); - - VERIFY_ARE_EQUAL(expected, actual); - } - - Log::Comment(L"4.) Move left"); - { - auto actual = map; - // Move all contents - // <-- <-- - const til::point delta{ -2, 0 }; - - til::bitmap expected{ mapSize }; - // Expected: (F is filling uncovered value) - // 0 0 0 0 0 0 F F - // 0 1 1 0 1 0 F F - // 0 1 1 0 --> 1 0 F F - // 0 0 0 0 0 0 F F - // <--<-- - expected.set(til::rect{ til::point{ 2, 0 }, til::size{ 2, 4 } }); - expected.set(til::rect{ til::point{ 0, 1 }, til::size{ 1, 2 } }); - - actual.translate(delta, true); - - VERIFY_ARE_EQUAL(expected, actual); - } - - Log::Comment(L"5.) Move up and left"); - { - auto actual = map; - // Move all contents - // ^ - // | - // ^ - // | <-- <-- - const til::point delta{ -2, -2 }; - - til::bitmap expected{ mapSize }; - // Expected: (F is filling uncovered value) - // 0 0 0 0 ^ 0 1 1 0 1 0 F F - // 0 1 1 0 ^ 0 0 0 0 0 0 F F - // 0 1 1 0 --> F F F F --> F F F F - // 0 0 0 0 F F F F F F F F - // <-<- - expected.set(til::rect{ til::point{ 2, 0 }, til::size{ 2, 2 } }); - expected.set(til::rect{ til::point{ 0, 2 }, til::size{ 4, 2 } }); - expected.set(til::point{ 0, 0 }); - - actual.translate(delta, true); - - VERIFY_ARE_EQUAL(expected, actual); - } - - Log::Comment(L"6.) Move up"); - { - auto actual = map; - // Move all contents - // ^ - // | - // ^ - // | - const til::point delta{ 0, -2 }; - - til::bitmap expected{ mapSize }; - // Expected: (F is filling uncovered value) - // 0 0 0 0 ^ 0 1 1 0 - // 0 1 1 0 ^ 0 0 0 0 - // 0 1 1 0 --> F F F F - // 0 0 0 0 F F F F - expected.set(til::rect{ til::point{ 1, 0 }, til::size{ 2, 1 } }); - expected.set(til::rect{ til::point{ 0, 2 }, til::size{ 4, 2 } }); - - actual.translate(delta, true); - - VERIFY_ARE_EQUAL(expected, actual); - } - - Log::Comment(L"7.) Move up and right"); - { - auto actual = map; - // Move all contents - // ^ - // | - // ^ - // | --> --> - const til::point delta{ 2, -2 }; - - til::bitmap expected{ mapSize }; - // Expected: (F is filling uncovered value) - // 0 0 0 0 ^ 0 1 1 0 F F 0 1 - // 0 1 1 0 ^ 0 0 0 0 F F 0 0 - // 0 1 1 0 --> F F F F --> F F F F - // 0 0 0 0 F F F F F F F F - // ->-> - expected.set(til::point{ 3, 0 }); - expected.set(til::rect{ til::point{ 0, 2 }, til::size{ 4, 2 } }); - expected.set(til::rect{ til::point{ 0, 0 }, til::size{ 2, 2 } }); - - actual.translate(delta, true); - - VERIFY_ARE_EQUAL(expected, actual); - } - - Log::Comment(L"8.) Move right"); - { - auto actual = map; - // Move all contents - // --> --> - const til::point delta{ 2, 0 }; - - til::bitmap expected{ mapSize }; - // Expected: (F is filling uncovered value) - // 0 0 0 0 F F 0 0 - // 0 1 1 0 F F 0 1 - // 0 1 1 0 --> F F 0 1 - // 0 0 0 0 F F 0 0 - // ->-> - expected.set(til::rect{ til::point{ 3, 1 }, til::size{ 1, 2 } }); - expected.set(til::rect{ til::point{ 0, 0 }, til::size{ 2, 4 } }); - - actual.translate(delta, true); - - VERIFY_ARE_EQUAL(expected, actual); - } - } - - TEST_METHOD(SetReset) - { - const til::size sz{ 4, 4 }; - til::bitmap bitmap{ sz }; - - // Every bit should be false. - Log::Comment(L"All bits false on creation."); - VERIFY_IS_TRUE(bitmap._bits.none()); - - const til::point point{ 2, 2 }; - bitmap.set(point); - - std::vector expectedSet; - expectedSet.emplace_back(til::rect{ 2, 2, 3, 3 }); - - // Run through every bit. Only the one we set should be true. - Log::Comment(L"Only the bit we set should be true."); - _checkBits(expectedSet, bitmap); - - Log::Comment(L"Setting all should mean they're all true."); - bitmap.set_all(); - - expectedSet.clear(); - expectedSet.emplace_back(til::rect{ bitmap._rc }); - _checkBits(expectedSet, bitmap); - - Log::Comment(L"Now reset them all."); - bitmap.reset_all(); - - expectedSet.clear(); - _checkBits(expectedSet, bitmap); - - til::rect totalZone{ sz }; - Log::Comment(L"Set a rectangle of bits and test they went on."); - // 0 0 0 0 |1 1|0 0 - // 0 0 0 0 --\ |1 1|0 0 - // 0 0 0 0 --/ |1 1|0 0 - // 0 0 0 0 0 0 0 0 - til::rect setZone{ til::point{ 0, 0 }, til::size{ 2, 3 } }; - bitmap.set(setZone); - - expectedSet.clear(); - expectedSet.emplace_back(setZone); - _checkBits(expectedSet, bitmap); - - Log::Comment(L"Reset all."); - bitmap.reset_all(); - - expectedSet.clear(); - _checkBits(expectedSet, bitmap); - } - - TEST_METHOD(SetResetOutOfBounds) - { - til::bitmap map{ til::size{ 4, 4 } }; - Log::Comment(L"1.) SetPoint out of bounds."); - map.set(til::point{ 10, 10 }); - - Log::Comment(L"2.) SetRectangle out of bounds."); - map.set(til::rect{ til::point{ 2, 2 }, til::size{ 10, 10 } }); - - const auto runs = map.runs(); - VERIFY_ARE_EQUAL(2u, runs.size()); - VERIFY_ARE_EQUAL(til::rect(2, 2, 4, 3), runs[0]); - VERIFY_ARE_EQUAL(til::rect(2, 3, 4, 4), runs[1]); - } - - TEST_METHOD(Resize) - { - Log::Comment(L"Set up a bitmap with every location flagged."); - const til::size originalSize{ 2, 2 }; - til::bitmap bitmap{ originalSize, true }; - - std::vector expectedFillRects; - - // 1 1 - // 1 1 - expectedFillRects.emplace_back(til::rect{ originalSize }); - _checkBits(expectedFillRects, bitmap); - - Log::Comment(L"Attempt resize to the same size."); - VERIFY_IS_FALSE(bitmap.resize(originalSize)); - - // 1 1 - // 1 1 - _checkBits(expectedFillRects, bitmap); - - Log::Comment(L"Attempt resize to a new size where both dimensions grow and we didn't ask for fill."); - VERIFY_IS_TRUE(bitmap.resize(til::size{ 3, 3 })); - - // 1 1 0 - // 1 1 0 - // 0 0 0 - _checkBits(expectedFillRects, bitmap); - - Log::Comment(L"Set a bit out in the new space and check it."); - const til::point spaceBit{ 1, 2 }; - expectedFillRects.emplace_back(til::rect{ 1, 2, 2, 3 }); - bitmap.set(spaceBit); - - // 1 1 0 - // 1 1 0 - // 0 1 0 - _checkBits(expectedFillRects, bitmap); - - Log::Comment(L"Grow vertically and shrink horizontally at the same time. Fill any new space."); - expectedFillRects.emplace_back(til::rect{ til::point{ 0, 3 }, til::size{ 2, 1 } }); - bitmap.resize(til::size{ 2, 4 }, true); - - // 1 1 - // 1 1 - // 0 1 - // 1 1 - _checkBits(expectedFillRects, bitmap); - } - - TEST_METHOD(One) - { - Log::Comment(L"When created, it should be not be one."); - til::bitmap bitmap{ til::size{ 2, 2 } }; - VERIFY_IS_FALSE(bitmap.one()); - - Log::Comment(L"When a single point is set, it should be one."); - bitmap.set(til::point{ 1, 0 }); - VERIFY_IS_TRUE(bitmap.one()); - - Log::Comment(L"Setting the same point again, should still be one."); - bitmap.set(til::point{ 1, 0 }); - VERIFY_IS_TRUE(bitmap.one()); - - Log::Comment(L"Setting another point, it should no longer be one."); - bitmap.set(til::point{ 0, 0 }); - VERIFY_IS_FALSE(bitmap.one()); - - Log::Comment(L"Clearing it, still not one."); - bitmap.reset_all(); - VERIFY_IS_FALSE(bitmap.one()); - - Log::Comment(L"Set one point, one again."); - bitmap.set(til::point{ 1, 0 }); - VERIFY_IS_TRUE(bitmap.one()); - - Log::Comment(L"And setting all will no longer be one again."); - bitmap.set_all(); - VERIFY_IS_FALSE(bitmap.one()); - } - - TEST_METHOD(Any) - { - Log::Comment(L"When created, it should be not be any."); - til::bitmap bitmap{ til::size{ 2, 2 } }; - VERIFY_IS_FALSE(bitmap.any()); - - Log::Comment(L"When a single point is set, it should be any."); - bitmap.set(til::point{ 1, 0 }); - VERIFY_IS_TRUE(bitmap.any()); - - Log::Comment(L"Setting the same point again, should still be any."); - bitmap.set(til::point{ 1, 0 }); - VERIFY_IS_TRUE(bitmap.any()); - - Log::Comment(L"Setting another point, it should still be any."); - bitmap.set(til::point{ 0, 0 }); - VERIFY_IS_TRUE(bitmap.any()); - - Log::Comment(L"Clearing it, no longer any."); - bitmap.reset_all(); - VERIFY_IS_FALSE(bitmap.any()); - - Log::Comment(L"Set one point, one again, it's any."); - bitmap.set(til::point{ 1, 0 }); - VERIFY_IS_TRUE(bitmap.any()); - - Log::Comment(L"And setting all will be any as well."); - bitmap.set_all(); - VERIFY_IS_TRUE(bitmap.any()); - } - - TEST_METHOD(None) - { - Log::Comment(L"When created, it should be none."); - til::bitmap bitmap{ til::size{ 2, 2 } }; - VERIFY_IS_TRUE(bitmap.none()); - - Log::Comment(L"When it is modified with a set, it should no longer be none."); - bitmap.set(til::point{ 0, 0 }); - VERIFY_IS_FALSE(bitmap.none()); - - Log::Comment(L"Resetting all, it will report none again."); - bitmap.reset_all(); - VERIFY_IS_TRUE(bitmap.none()); - - Log::Comment(L"And setting all will no longer be none again."); - bitmap.set_all(); - VERIFY_IS_FALSE(bitmap.none()); - } - - TEST_METHOD(All) - { - Log::Comment(L"When created, it should be not be all."); - til::bitmap bitmap{ til::size{ 2, 2 } }; - VERIFY_IS_FALSE(bitmap.all()); - - Log::Comment(L"When a single point is set, it should not be all."); - bitmap.set(til::point{ 1, 0 }); - VERIFY_IS_FALSE(bitmap.all()); - - Log::Comment(L"Setting the same point again, should still not be all."); - bitmap.set(til::point{ 1, 0 }); - VERIFY_IS_FALSE(bitmap.all()); - - Log::Comment(L"Setting another point, it should still not be all."); - bitmap.set(til::point{ 0, 0 }); - VERIFY_IS_FALSE(bitmap.all()); - - Log::Comment(L"Clearing it, still not all."); - bitmap.reset_all(); - VERIFY_IS_FALSE(bitmap.all()); - - Log::Comment(L"Set one point, one again, not all."); - bitmap.set(til::point{ 1, 0 }); - VERIFY_IS_FALSE(bitmap.all()); - - Log::Comment(L"And setting all will finally be all."); - bitmap.set_all(); - VERIFY_IS_TRUE(bitmap.all()); - - Log::Comment(L"Clearing it, back to not all."); - bitmap.reset_all(); - VERIFY_IS_FALSE(bitmap.all()); - } - - TEST_METHOD(Size) - { - til::size sz{ 5, 10 }; - til::bitmap map{ sz }; - - VERIFY_ARE_EQUAL(sz, map.size()); - } - - TEST_METHOD(Runs) - { - // This map --> Those runs - // 1 1 0 1 A A _ B - // 1 0 1 1 C _ D D - // 0 0 1 0 _ _ E _ - // 0 1 1 0 _ F F _ - Log::Comment(L"Set up a bitmap with some runs."); - - til::bitmap map{ til::size{ 4, 4 }, false }; - - // 0 0 0 0 |1 1|0 0 - // 0 0 0 0 0 0 0 0 - // 0 0 0 0 --> 0 0 0 0 - // 0 0 0 0 0 0 0 0 - map.set(til::rect{ til::point{ 0, 0 }, til::size{ 2, 1 } }); - - // 1 1 0 0 1 1 0 0 - // 0 0 0 0 0 0|1|0 - // 0 0 0 0 --> 0 0|1|0 - // 0 0 0 0 0 0|1|0 - map.set(til::rect{ til::point{ 2, 1 }, til::size{ 1, 3 } }); - - // 1 1 0 0 1 1 0|1| - // 0 0 1 0 0 0 1|1| - // 0 0 1 0 --> 0 0 1 0 - // 0 0 1 0 0 0 1 0 - map.set(til::rect{ til::point{ 3, 0 }, til::size{ 1, 2 } }); - - // 1 1 0 1 1 1 0 1 - // 0 0 1 1 |1|0 1 1 - // 0 0 1 0 --> 0 0 1 0 - // 0 0 1 0 0 0 1 0 - map.set(til::point{ 0, 1 }); - - // 1 1 0 1 1 1 0 1 - // 1 0 1 1 1 0 1 1 - // 0 0 1 0 --> 0 0 1 0 - // 0 0 1 0 0|1|1 0 - map.set(til::point{ 1, 3 }); - - Log::Comment(L"Building the expected run rectangles."); - - // Reminder, we're making 6 rectangle runs A-F like this: - // A A _ B - // C _ D D - // _ _ E _ - // _ F F _ - std::vector expected; - expected.push_back(til::rect{ til::point{ 0, 0 }, til::size{ 2, 1 } }); - expected.push_back(til::rect{ til::point{ 3, 0 }, til::size{ 1, 1 } }); - expected.push_back(til::rect{ til::point{ 0, 1 }, til::size{ 1, 1 } }); - expected.push_back(til::rect{ til::point{ 2, 1 }, til::size{ 2, 1 } }); - expected.push_back(til::rect{ til::point{ 2, 2 }, til::size{ 1, 1 } }); - expected.push_back(til::rect{ til::point{ 1, 3 }, til::size{ 2, 1 } }); - - Log::Comment(L"Run the iterator and collect the runs."); - std::vector actual; - for (auto run : map.runs()) - { - actual.push_back(run); - } - - Log::Comment(L"Verify they match what we expected."); - VERIFY_ARE_EQUAL(expected, actual); - - Log::Comment(L"Clear the map and iterate and make sure we get no results."); - map.reset_all(); - - expected.clear(); - actual.clear(); - for (auto run : map.runs()) - { - actual.push_back(run); - } - - Log::Comment(L"Verify they're empty."); - VERIFY_ARE_EQUAL(expected, actual); - - Log::Comment(L"Set point and validate runs updated."); - const til::point setPoint{ 2, 2 }; - expected.push_back(til::rect{ 2, 2, 3, 3 }); - map.set(setPoint); - - for (auto run : map.runs()) - { - actual.push_back(run); - } - VERIFY_ARE_EQUAL(expected, actual); - - Log::Comment(L"Set rectangle and validate runs updated."); - const til::rect setRect{ setPoint, til::size{ 2, 2 } }; - expected.clear(); - expected.push_back(til::rect{ til::point{ 2, 2 }, til::size{ 2, 1 } }); - expected.push_back(til::rect{ til::point{ 2, 3 }, til::size{ 2, 1 } }); - map.set(setRect); - - actual.clear(); - for (auto run : map.runs()) - { - actual.push_back(run); - } - VERIFY_ARE_EQUAL(expected, actual); - - Log::Comment(L"Set all and validate runs updated."); - expected.clear(); - expected.push_back(til::rect{ til::point{ 0, 0 }, til::size{ 4, 1 } }); - expected.push_back(til::rect{ til::point{ 0, 1 }, til::size{ 4, 1 } }); - expected.push_back(til::rect{ til::point{ 0, 2 }, til::size{ 4, 1 } }); - expected.push_back(til::rect{ til::point{ 0, 3 }, til::size{ 4, 1 } }); - map.set_all(); - - actual.clear(); - for (auto run : map.runs()) - { - actual.push_back(run); - } - VERIFY_ARE_EQUAL(expected, actual); - - Log::Comment(L"Resize and validate runs updated."); - const til::size newSize{ 3, 3 }; - expected.clear(); - expected.push_back(til::rect{ til::point{ 0, 0 }, til::size{ 3, 1 } }); - expected.push_back(til::rect{ til::point{ 0, 1 }, til::size{ 3, 1 } }); - expected.push_back(til::rect{ til::point{ 0, 2 }, til::size{ 3, 1 } }); - map.resize(newSize); - - actual.clear(); - for (auto run : map.runs()) - { - actual.push_back(run); - } - VERIFY_ARE_EQUAL(expected, actual); - } - - TEST_METHOD(RunsWithPmr) - { - // This is a copy of the above test, but with a pmr::bitmap. - std::pmr::unsynchronized_pool_resource pool{ til::pmr::get_default_resource() }; - - // This map --> Those runs - // 1 1 0 1 A A _ B - // 1 0 1 1 C _ D D - // 0 0 1 0 _ _ E _ - // 0 1 1 0 _ F F _ - Log::Comment(L"Set up a PMR bitmap with some runs."); - - til::pmr::bitmap map{ til::size{ 4, 4 }, false, &pool }; - - // 0 0 0 0 |1 1|0 0 - // 0 0 0 0 0 0 0 0 - // 0 0 0 0 --> 0 0 0 0 - // 0 0 0 0 0 0 0 0 - map.set(til::rect{ til::point{ 0, 0 }, til::size{ 2, 1 } }); - - // 1 1 0 0 1 1 0 0 - // 0 0 0 0 0 0|1|0 - // 0 0 0 0 --> 0 0|1|0 - // 0 0 0 0 0 0|1|0 - map.set(til::rect{ til::point{ 2, 1 }, til::size{ 1, 3 } }); - - // 1 1 0 0 1 1 0|1| - // 0 0 1 0 0 0 1|1| - // 0 0 1 0 --> 0 0 1 0 - // 0 0 1 0 0 0 1 0 - map.set(til::rect{ til::point{ 3, 0 }, til::size{ 1, 2 } }); - - // 1 1 0 1 1 1 0 1 - // 0 0 1 1 |1|0 1 1 - // 0 0 1 0 --> 0 0 1 0 - // 0 0 1 0 0 0 1 0 - map.set(til::point{ 0, 1 }); - - // 1 1 0 1 1 1 0 1 - // 1 0 1 1 1 0 1 1 - // 0 0 1 0 --> 0 0 1 0 - // 0 0 1 0 0|1|1 0 - map.set(til::point{ 1, 3 }); - - Log::Comment(L"Building the expected run rectangles."); - - // Reminder, we're making 6 rectangle runs A-F like this: - // A A _ B - // C _ D D - // _ _ E _ - // _ F F _ - std::vector expected; - expected.push_back(til::rect{ til::point{ 0, 0 }, til::size{ 2, 1 } }); - expected.push_back(til::rect{ til::point{ 3, 0 }, til::size{ 1, 1 } }); - expected.push_back(til::rect{ til::point{ 0, 1 }, til::size{ 1, 1 } }); - expected.push_back(til::rect{ til::point{ 2, 1 }, til::size{ 2, 1 } }); - expected.push_back(til::rect{ til::point{ 2, 2 }, til::size{ 1, 1 } }); - expected.push_back(til::rect{ til::point{ 1, 3 }, til::size{ 2, 1 } }); - - Log::Comment(L"Run the iterator and collect the runs."); - std::vector actual; - for (auto run : map.runs()) - { - actual.push_back(run); - } - - Log::Comment(L"Verify they match what we expected."); - VERIFY_ARE_EQUAL(expected, actual); - - Log::Comment(L"Clear the map and iterate and make sure we get no results."); - map.reset_all(); - - expected.clear(); - actual.clear(); - for (auto run : map.runs()) - { - actual.push_back(run); - } - - Log::Comment(L"Verify they're empty."); - VERIFY_ARE_EQUAL(expected, actual); - - Log::Comment(L"Set point and validate runs updated."); - const til::point setPoint{ 2, 2 }; - expected.push_back(til::rect{ 2, 2, 3, 3 }); - map.set(setPoint); - - for (auto run : map.runs()) - { - actual.push_back(run); - } - VERIFY_ARE_EQUAL(expected, actual); - - Log::Comment(L"Set rectangle and validate runs updated."); - const til::rect setRect{ setPoint, til::size{ 2, 2 } }; - expected.clear(); - expected.push_back(til::rect{ til::point{ 2, 2 }, til::size{ 2, 1 } }); - expected.push_back(til::rect{ til::point{ 2, 3 }, til::size{ 2, 1 } }); - map.set(setRect); - - actual.clear(); - for (auto run : map.runs()) - { - actual.push_back(run); - } - VERIFY_ARE_EQUAL(expected, actual); - - Log::Comment(L"Set all and validate runs updated."); - expected.clear(); - expected.push_back(til::rect{ til::point{ 0, 0 }, til::size{ 4, 1 } }); - expected.push_back(til::rect{ til::point{ 0, 1 }, til::size{ 4, 1 } }); - expected.push_back(til::rect{ til::point{ 0, 2 }, til::size{ 4, 1 } }); - expected.push_back(til::rect{ til::point{ 0, 3 }, til::size{ 4, 1 } }); - map.set_all(); - - actual.clear(); - for (auto run : map.runs()) - { - actual.push_back(run); - } - VERIFY_ARE_EQUAL(expected, actual); - - Log::Comment(L"Resize and validate runs updated."); - const til::size newSize{ 3, 3 }; - expected.clear(); - expected.push_back(til::rect{ til::point{ 0, 0 }, til::size{ 3, 1 } }); - expected.push_back(til::rect{ til::point{ 0, 1 }, til::size{ 3, 1 } }); - expected.push_back(til::rect{ til::point{ 0, 2 }, til::size{ 3, 1 } }); - map.resize(newSize); - - actual.clear(); - for (auto run : map.runs()) - { - actual.push_back(run); - } - VERIFY_ARE_EQUAL(expected, actual); - } -}; diff --git a/src/til/ut_til/sources b/src/til/ut_til/sources index 2e39e378a84..ec02bb53cb0 100644 --- a/src/til/ut_til/sources +++ b/src/til/ut_til/sources @@ -15,7 +15,6 @@ DLLDEF = SOURCES = \ $(SOURCES) \ BaseTests.cpp \ - BitmapTests.cpp \ CoalesceTests.cpp \ ColorTests.cpp \ EnumSetTests.cpp \ diff --git a/src/til/ut_til/til.unit.tests.vcxproj b/src/til/ut_til/til.unit.tests.vcxproj index c4c2b87eefe..8219d1d98e7 100644 --- a/src/til/ut_til/til.unit.tests.vcxproj +++ b/src/til/ut_til/til.unit.tests.vcxproj @@ -15,7 +15,6 @@ Create - @@ -43,7 +42,6 @@ - diff --git a/src/til/ut_til/til.unit.tests.vcxproj.filters b/src/til/ut_til/til.unit.tests.vcxproj.filters index 15928ddb533..b85b195c077 100644 --- a/src/til/ut_til/til.unit.tests.vcxproj.filters +++ b/src/til/ut_til/til.unit.tests.vcxproj.filters @@ -6,7 +6,6 @@ - @@ -41,9 +40,6 @@ inc - - inc - inc diff --git a/src/winconpty/winconpty.cpp b/src/winconpty/winconpty.cpp index e3a915447a6..441d4d2639f 100644 --- a/src/winconpty/winconpty.cpp +++ b/src/winconpty/winconpty.cpp @@ -136,7 +136,6 @@ HRESULT _CreatePseudoConsole(const HANDLE hToken, // This is plenty of space to hold the formatted string wchar_t cmd[MAX_PATH]{}; const BOOL bInheritCursor = (dwFlags & PSEUDOCONSOLE_INHERIT_CURSOR) == PSEUDOCONSOLE_INHERIT_CURSOR; - const BOOL bResizeQuirk = (dwFlags & PSEUDOCONSOLE_RESIZE_QUIRK) == PSEUDOCONSOLE_RESIZE_QUIRK; const wchar_t* textMeasurement; switch (dwFlags & PSEUDOCONSOLE_GLYPH_WIDTH__MASK) @@ -157,10 +156,9 @@ HRESULT _CreatePseudoConsole(const HANDLE hToken, swprintf_s(cmd, MAX_PATH, - L"\"%s\" --headless %s%s%s--width %hd --height %hd --signal 0x%tx --server 0x%tx", + L"\"%s\" --headless %s%s--width %hd --height %hd --signal 0x%tx --server 0x%tx", _ConsoleHostPath(), bInheritCursor ? L"--inheritcursor " : L"", - bResizeQuirk ? L"--resizeQuirk " : L"", textMeasurement, size.X, size.Y, diff --git a/src/winconpty/winconpty.h b/src/winconpty/winconpty.h index bd1a6cd1909..11503a2494b 100644 --- a/src/winconpty/winconpty.h +++ b/src/winconpty/winconpty.h @@ -52,9 +52,6 @@ typedef struct _PseudoConsole #ifndef PSEUDOCONSOLE_INHERIT_CURSOR #define PSEUDOCONSOLE_INHERIT_CURSOR (0x1) #endif -#ifndef PSEUDOCONSOLE_RESIZE_QUIRK -#define PSEUDOCONSOLE_RESIZE_QUIRK (0x2) -#endif #ifndef PSEUDOCONSOLE_GLYPH_WIDTH__MASK #define PSEUDOCONSOLE_GLYPH_WIDTH__MASK 0x18 #define PSEUDOCONSOLE_GLYPH_WIDTH_GRAPHEMES 0x08