From fec64563a06de67464d67c60e197f10cc98100cf Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 3 Jul 2024 03:36:02 +0200 Subject: [PATCH 01/60] A minor ConPTY refactoring: Goodbye VtEngine Edition --- .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/OutputCellIterator.hpp | 1 + src/buffer/out/OutputCellView.hpp | 1 + src/buffer/out/textBuffer.cpp | 7 - .../TerminalConnection/ConptyConnection.cpp | 2 +- src/cascadia/TerminalCore/Terminal.hpp | 2 - .../UnitTests_Control/ControlCoreTests.cpp | 6 - .../ConptyRoundtripTests.cpp | 5057 ----------------- .../UnitTests_TerminalCore/ScrollTest.cpp | 1 - .../UnitTests_TerminalCore/UnitTests.vcxproj | 40 - src/common.build.pre.props | 2 +- src/host/ConsoleArguments.cpp | 23 +- src/host/ConsoleArguments.hpp | 12 - src/host/CursorBlinker.cpp | 3 +- src/host/PtySignalInputThread.cpp | 8 +- src/host/VtInputThread.cpp | 22 +- src/host/VtInputThread.hpp | 6 +- src/host/VtIo.cpp | 541 +- src/host/VtIo.hpp | 103 +- src/host/_output.cpp | 362 +- src/host/_stream.cpp | 174 +- src/host/_stream.h | 1 + src/host/consoleInformation.cpp | 15 +- src/host/directio.cpp | 136 +- src/host/directio.h | 7 +- src/host/exe/Host.EXE.vcxproj | 3 - src/host/ft_fuzzer/Host.FuzzWrapper.vcxproj | 3 - src/host/getset.cpp | 264 +- src/host/globals.cpp | 16 - src/host/globals.h | 4 - src/host/inputBuffer.hpp | 1 - src/host/output.cpp | 17 +- src/host/outputStream.cpp | 6 +- src/host/screenInfo.cpp | 73 +- src/host/screenInfo.hpp | 18 +- src/host/server.h | 4 +- src/host/settings.cpp | 2 +- src/host/srvinit.cpp | 52 +- src/host/ut_host/ApiRoutinesTests.cpp | 1 - src/host/ut_host/ClipboardTests.cpp | 1 - src/host/ut_host/ConptyOutputTests.cpp | 467 -- src/host/ut_host/ConsoleArgumentsTests.cpp | 121 +- src/host/ut_host/Host.UnitTests.vcxproj | 5 - .../ut_host/Host.UnitTests.vcxproj.filters | 6 - src/host/ut_host/ObjectTests.cpp | 1 - src/host/ut_host/ScreenBufferTests.cpp | 29 - src/host/ut_host/SearchTests.cpp | 1 - src/host/ut_host/SelectionTests.cpp | 2 - src/host/ut_host/TextBufferIteratorTests.cpp | 1 - src/host/ut_host/TextBufferTests.cpp | 1 - src/host/ut_host/UtilsTests.cpp | 1 - src/host/ut_host/VtIoTests.cpp | 846 +-- src/host/ut_host/VtRendererTests.cpp | 1757 ------ src/host/ut_host/sources | 2 - src/inc/LibraryIncludes.h | 10 - src/inc/VtIoModes.hpp | 17 - src/inc/conpty-static.h | 14 - src/inc/test/CommonState.hpp | 16 +- src/inc/til.h | 2 +- src/inc/til/bitmap.h | 593 -- src/inc/til/rand.h | 8 +- .../base/InteractivityFactory.cpp | 6 +- src/interactivity/base/ServiceLocator.cpp | 9 - src/interactivity/onecore/BgfxEngine.cpp | 6 - src/interactivity/onecore/BgfxEngine.hpp | 1 - .../Interactivity.Win32.UnitTests.vcxproj | 3 - .../UiaTextRangeTests.cpp | 1 - src/interactivity/win32/windowio.cpp | 5 +- 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/InteractDispatch.cpp | 8 +- src/terminal/adapter/adaptDispatch.cpp | 217 +- src/terminal/adapter/adaptDispatch.hpp | 3 - .../parser/InputStateMachineEngine.cpp | 8 +- .../parser/InputStateMachineEngine.hpp | 3 +- .../parser/OutputStateMachineEngine.cpp | 65 +- .../parser/OutputStateMachineEngine.hpp | 12 +- src/terminal/parser/ft_fuzzer/sources | 2 - src/til/ut_til/BitmapTests.cpp | 1095 ---- src/til/ut_til/til.unit.tests.vcxproj | 1 - src/til/ut_til/til.unit.tests.vcxproj.filters | 3 - src/types/viewport.cpp | 9 +- src/winconpty/winconpty.cpp | 4 +- src/winconpty/winconpty.h | 4 +- 137 files changed, 1602 insertions(+), 17695 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/VtIoModes.hpp 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/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 6495be1be48..38da43c29c4 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 @@ -65,7 +64,6 @@ ARRAYSIZE ARROWKEYS asan ASBSET -asdfghjkl ASetting ASingle ASYNCDONTCARE @@ -124,6 +122,7 @@ BKCOLOR BKGND Bksp Blt +blu BLUESCROLL bmi BODGY @@ -143,7 +142,6 @@ bufferout buffersize buflen buildtransitive -burriter BValue bytebuffer cac @@ -208,7 +206,6 @@ cmw CNL cnn Codeflow -codenav codepages codepath coinit @@ -360,6 +357,7 @@ DBGFONTS DBGOUTPUT dbh dblclk +Dcd DColor DCOLORVALUE dcommon @@ -377,7 +375,7 @@ DECALN DECANM DECARM DECAUPSS -DECAWM +decawm DECBI DECBKM DECCARA @@ -473,7 +471,6 @@ DEFPUSHBUTTON defterm DELAYLOAD DELETEONRELEASE -Delt depersist deprioritized deserializers @@ -554,7 +551,6 @@ Efast efghijklmn EHsc EINS -EJO ELEMENTNOTAVAILABLE EMPTYBOX enabledelayedexpansion @@ -624,7 +620,6 @@ FINDDOWN FINDREGEX FINDSTRINGEXACT FINDUP -FIter FITZPATRICK FIXEDFILEINFO Flg @@ -721,6 +716,7 @@ GETWHEELSCROLLLINES Gfun gfx GGI +GHgh GHIJK GHIJKL gitcheckin @@ -946,7 +942,6 @@ LCONTROL LCTRL lcx LEFTALIGN -libpopcnt libsancov libtickit licate @@ -964,7 +959,7 @@ llx LMENU lnkd lnkfile -LNM +lnm LOADONCALL LOBYTE localappdata @@ -1044,7 +1039,6 @@ MAPBITMAP MAPVIRTUALKEY MAPVK MAXDIMENSTRING -maxing MAXSHORT maxval maxversiontested @@ -1501,7 +1495,6 @@ REGSTR RELBINPATH remoting renamer -renderengine rendersize reparented reparenting @@ -1841,7 +1834,6 @@ Trd TREX triaged triaging -Tribool TRIMZEROHEADINGS trx tsa @@ -1935,7 +1927,6 @@ uxtheme Vanara vararg vclib -vcprintf vcxitems vectorize VERCTRL @@ -1979,7 +1970,6 @@ vtio vtmode vtpipeterm vtpt -vtrenderer VTRGB VTRGBTo vtseq @@ -2001,6 +1991,7 @@ wcswidth wddm wddmcon WDDMCONSOLECONTEXT +WDK wdm webpage websites @@ -2068,7 +2059,6 @@ Winperf WInplace winres winrt -wintelnet winternl winuser winuserp @@ -2169,7 +2159,6 @@ XTWINOPS xunit xutr XVIRTUALSCREEN -XWalk yact YCast YCENTER @@ -2178,7 +2167,6 @@ YLimit YPan YSubstantial YVIRTUALSCREEN -YWalk Zab zabcd Zabcdefghijklmn @@ -2186,6 +2174,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 411f5ca0ed6..44780dd6c10 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}" @@ -1119,29 +1111,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 @@ -1212,28 +1181,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 @@ -2472,11 +2419,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/OutputCellIterator.hpp b/src/buffer/out/OutputCellIterator.hpp index 0199b7c4aa5..ed7c22b98ea 100644 --- a/src/buffer/out/OutputCellIterator.hpp +++ b/src/buffer/out/OutputCellIterator.hpp @@ -33,6 +33,7 @@ class OutputCellIterator final using pointer = OutputCellView*; using reference = OutputCellView&; + OutputCellIterator() = default; OutputCellIterator(const wchar_t& wch, const size_t fillLimit = 0) noexcept; OutputCellIterator(const TextAttribute& attr, const size_t fillLimit = 0) noexcept; OutputCellIterator(const wchar_t& wch, const TextAttribute& attr, const size_t fillLimit = 0) noexcept; diff --git a/src/buffer/out/OutputCellView.hpp b/src/buffer/out/OutputCellView.hpp index e32d866bbbb..59f08cb8337 100644 --- a/src/buffer/out/OutputCellView.hpp +++ b/src/buffer/out/OutputCellView.hpp @@ -25,6 +25,7 @@ Revision History: class OutputCellView { public: + OutputCellView() = default; OutputCellView(const std::wstring_view view, const DbcsAttribute dbcsAttr, const TextAttribute textAttr, diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 4ee701982eb..bf9523cf9b0 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -715,13 +715,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 c70a85e1dcd..daf449bd37c 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -260,7 +260,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 diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 663a649dc28..6614784ec6d 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 @@ -480,7 +479,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/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 af174f0497d..00000000000 --- a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp +++ /dev/null @@ -1,5057 +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->PrepareGlobalFont(); - 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->CleanupGlobalFont(); - 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 702df6425a4..887a0c69ddb 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\gsl\include;$(SolutionDir)\dep\Win32K;$(SolutionDir)\oss\chromium;$(SolutionDir)\oss\dynamic_bitset;$(SolutionDir)\oss\fmt\include;$(SolutionDir)\oss\interval_tree;$(SolutionDir)\oss\libpopcnt;$(SolutionDir)\oss\pcg\include;%(AdditionalIncludeDirectories); + $(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\gsl\include;$(SolutionDir)\dep\Win32K;$(SolutionDir)\oss\chromium;$(SolutionDir)\oss\fmt\include;$(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 422f563bd40..16d7f0a4290 100644 --- a/src/host/ConsoleArguments.cpp +++ b/src/host/ConsoleArguments.cpp @@ -19,7 +19,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"; @@ -112,7 +111,6 @@ ConsoleArguments::ConsoleArguments(const std::wstring& commandline, _vtOutHandle(hStdOut) { _clientCommandline = L""; - _vtMode = L""; _headless = false; _runAsComServer = false; _createServerHandle = true; @@ -138,7 +136,6 @@ ConsoleArguments& ConsoleArguments::operator=(const ConsoleArguments& other) _clientCommandline = other._clientCommandline; _vtInHandle = other._vtInHandle; _vtOutHandle = other._vtOutHandle; - _vtMode = other._vtMode; _headless = other._headless; _createServerHandle = other._createServerHandle; _serverHandle = other._serverHandle; @@ -476,7 +473,10 @@ void ConsoleArguments::s_ConsumeArg(_Inout_ std::vector& args, _In } else if (arg == VT_MODE_ARG) { - hr = s_GetArgumentValue(args, i, &_vtMode); + // The --vtmode flag was hardcoded into telnet.exe to force ConPTY to filter non-ASCII characters. + // The filtering was moved into telnet, because no one else ever used this functionality. + s_ConsumeArg(args, i); + hr = S_OK; } else if (arg == WIDTH_ARG) { @@ -502,12 +502,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); @@ -630,11 +624,6 @@ std::wstring ConsoleArguments::GetClientCommandline() const return _clientCommandline; } -std::wstring ConsoleArguments::GetVtMode() const -{ - return _vtMode; -} - const std::wstring& ConsoleArguments::GetTextMeasurement() const { return _textMeasurement; @@ -664,10 +653,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 3d26ae64905..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; @@ -84,7 +81,6 @@ class ConsoleArguments const std::wstring clientCommandline, const HANDLE vtInHandle, const HANDLE vtOutHandle, - const std::wstring vtMode, const short width, const short height, const bool forceV1, @@ -99,7 +95,6 @@ class ConsoleArguments _clientCommandline(clientCommandline), _vtInHandle(vtInHandle), _vtOutHandle(vtOutHandle), - _vtMode(vtMode), _width(width), _height(height), _forceV1(forceV1), @@ -109,7 +104,6 @@ class ConsoleArguments _serverHandle(serverHandle), _signalHandle(signalHandle), _inheritCursor(inheritCursor), - _resizeQuirk(false), _runAsComServer{ runAsComServer } { } @@ -123,7 +117,6 @@ class ConsoleArguments HANDLE _vtOutHandle; - std::wstring _vtMode; std::wstring _textMeasurement; bool _forceNoHandoff; @@ -138,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, @@ -178,7 +170,6 @@ namespace WEX L"Use VT Handles: '%ws',\r\n" L"VT In Handle: '0x%x',\r\n" L"VT Out Handle: '0x%x',\r\n" - L"Vt Mode: '%ws',\r\n" L"WidthxHeight: '%dx%d',\r\n" L"ForceV1: '%ws',\r\n" L"Headless: '%ws',\r\n" @@ -192,7 +183,6 @@ namespace WEX s_ToBoolString(ci.HasVtHandles()), ci.GetVtInHandle(), ci.GetVtOutHandle(), - ci.GetVtMode().c_str(), ci.GetWidth(), ci.GetHeight(), s_ToBoolString(ci.GetForceV1()), @@ -222,7 +212,6 @@ namespace WEX expected.HasVtHandles() == actual.HasVtHandles() && expected.GetVtInHandle() == actual.GetVtInHandle() && expected.GetVtOutHandle() == actual.GetVtOutHandle() && - expected.GetVtMode() == actual.GetVtMode() && expected.GetWidth() == actual.GetWidth() && expected.GetHeight() == actual.GetHeight() && expected.GetForceV1() == actual.GetForceV1() && @@ -249,7 +238,6 @@ namespace WEX return object.GetClientCommandline().empty() && (object.GetVtInHandle() == 0 || object.GetVtInHandle() == INVALID_HANDLE_VALUE) && (object.GetVtOutHandle() == 0 || object.GetVtOutHandle() == INVALID_HANDLE_VALUE) && - object.GetVtMode().empty() && !object.GetForceV1() && (object.GetWidth() == 0) && (object.GetHeight() == 0) && diff --git a/src/host/CursorBlinker.cpp b/src/host/CursorBlinker.cpp index 78f072ccc94..f4b9327f436 100644 --- a/src/host/CursorBlinker.cpp +++ b/src/host/CursorBlinker.cpp @@ -82,10 +82,9 @@ 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 (gci.GetVtIo(nullptr) || !WI_IsFlagSet(gci.Flags, CONSOLE_HAS_FOCUS)) { goto DoScroll; } diff --git a/src/host/PtySignalInputThread.cpp b/src/host/PtySignalInputThread.cpp index 534a6d3ae87..a0b96200ece 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 @@ -351,5 +347,5 @@ void PtySignalInputThread::_DoSetWindowParent(const SetParentData& data) void PtySignalInputThread::_Shutdown() { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - gci.GetVtIo()->SendCloseEvent(); + gci.GetVtIoNoCheck()->SendCloseEvent(); } diff --git a/src/host/VtInputThread.cpp b/src/host/VtInputThread.cpp index 9b2f435e192..659e412df32 100644 --- a/src/host/VtInputThread.cpp +++ b/src/host/VtInputThread.cpp @@ -28,8 +28,7 @@ VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe, _hFile{ std::move(hPipe) }, _hThread{}, _u8State{}, - _dwThreadId{ 0 }, - _pfnSetLookingForDSR{} + _dwThreadId{ 0 } { THROW_HR_IF(E_HANDLE, _hFile.get() == INVALID_HANDLE_VALUE); @@ -44,9 +43,6 @@ VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe, // we need this callback to be able to flush an unknown input sequence to the app auto flushCallback = [capture0 = _pInputStateMachine.get()] { return capture0->FlushToTerminal(); }; engineRef->SetFlushToInputQueueCallback(flushCallback); - - // we need this callback to capture the reply if someone requests a status from the terminal - _pfnSetLookingForDSR = [engineRef](auto&& PH1) { engineRef->SetLookingForDSR(std::forward(PH1)); }; } // Function Description: @@ -112,14 +108,6 @@ bool VtInputThread::DoReadInput() return true; } -void VtInputThread::SetLookingForDSR(const bool looking) noexcept -{ - if (_pfnSetLookingForDSR) - { - _pfnSetLookingForDSR(looking); - } -} - // Method Description: // - The ThreadProc for the VT Input Thread. Reads input from the pipe, and // passes it to _HandleRunInput to be processed by the @@ -129,7 +117,7 @@ void VtInputThread::_InputThread() while (DoReadInput()) { } - ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()->CloseInput(); + ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIoNoCheck()->CloseInput(); } // Method Description: @@ -156,3 +144,9 @@ void VtInputThread::_InputThread() return S_OK; } + +bool VtInputThread::IsLookingForDSR() const noexcept +{ + const auto& engine = static_cast( _pInputStateMachine->Engine()); + return engine.IsLookingForDSR(); +} diff --git a/src/host/VtInputThread.hpp b/src/host/VtInputThread.hpp index d058c425bc4..10c7b00339e 100644 --- a/src/host/VtInputThread.hpp +++ b/src/host/VtInputThread.hpp @@ -24,19 +24,17 @@ namespace Microsoft::Console VtInputThread(_In_ wil::unique_hfile hPipe, const bool inheritCursor); [[nodiscard]] HRESULT Start(); - static DWORD WINAPI StaticVtInputThreadProc(_In_ LPVOID lpParameter); bool DoReadInput(); - void SetLookingForDSR(const bool looking) noexcept; + bool IsLookingForDSR() const noexcept; private: + static DWORD WINAPI StaticVtInputThreadProc(_In_ LPVOID lpParameter); void _InputThread(); wil::unique_hfile _hFile; wil::unique_handle _hThread; DWORD _dwThreadId; - std::function _pfnSetLookingForDSR; - std::unique_ptr _pInputStateMachine; til::u8state _u8State; std::wstring _wstr; diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index cd2cead0f5a..13722aab9d2 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -3,17 +3,30 @@ #include "precomp.h" #include "VtIo.hpp" -#include "../interactivity/inc/ServiceLocator.hpp" -#include "../renderer/vt/XtermEngine.hpp" -#include "../renderer/vt/Xterm256Engine.hpp" +#include +#include "handle.h" // LockConsole +#include "output.h" // CloseConsoleProcessState +#include "../interactivity/inc/ServiceLocator.hpp" #include "../renderer/base/renderer.hpp" #include "../types/inc/CodepointWidthDetector.hpp" #include "../types/inc/utils.hpp" -#include "handle.h" // LockConsole -#include "input.h" // ProcessCtrlEvents -#include "output.h" // CloseConsoleProcessState + +// Some additional imports from ntifs.h which aren't in the regular Windows SDK (they're in the driver SDK (WDK)). +__kernel_entry NTSYSCALLAPI NTSTATUS NtQueryInformationFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _Out_ PVOID FileInformation, + _In_ ULONG Length, + _In_ FILE_INFORMATION_CLASS FileInformationClass); + +#define FileModeInformation (FILE_INFORMATION_CLASS)16 + +typedef struct _FILE_MODE_INFORMATION +{ + ULONG Mode; +} FILE_MODE_INFORMATION, *PFILE_MODE_INFORMATION; using namespace Microsoft::Console; using namespace Microsoft::Console::Render; @@ -22,54 +35,25 @@ using namespace Microsoft::Console::Types; using namespace Microsoft::Console::Utils; using namespace Microsoft::Console::Interactivity; -VtIo::VtIo() : - _initialized(false), - _lookingForCursorPosition(false), - _IoMode(VtIoMode::INVALID) -{ -} - -// Routine Description: -// Tries to get the VtIoMode from the given string. If it's not one of the -// *_STRING constants in VtIoMode.hpp, then it returns E_INVALIDARG. -// Arguments: -// VtIoMode: A string containing the console's requested VT mode. This can be -// any of the strings in VtIoModes.hpp -// pIoMode: receives the VtIoMode that the string represents if it's a valid -// IO mode string -// Return Value: -// S_OK if we parsed the string successfully, otherwise E_INVALIDARG indicating failure. -[[nodiscard]] HRESULT VtIo::ParseIoMode(const std::wstring& VtMode, _Out_ VtIoMode& ioMode) +static bool handleWantsOverlappedIo(HANDLE handle) noexcept { - ioMode = VtIoMode::INVALID; + const auto ntdll = GetModuleHandleW(L"ntdll.dll"); + const auto pNtQueryInformationFile = GetProcAddressByFunctionDeclaration(ntdll, NtQueryInformationFile); - if (VtMode == XTERM_256_STRING) - { - ioMode = VtIoMode::XTERM_256; - } - else if (VtMode == XTERM_STRING) - { - ioMode = VtIoMode::XTERM; - } - else if (VtMode == XTERM_ASCII_STRING) + if (!pNtQueryInformationFile) { - ioMode = VtIoMode::XTERM_ASCII; + return false; } - else if (VtMode == DEFAULT_STRING) - { - ioMode = VtIoMode::XTERM_256; - } - else - { - return E_INVALIDARG; - } - return S_OK; + + IO_STATUS_BLOCK statusBlock; + FILE_MODE_INFORMATION modeInfo; + const auto status = pNtQueryInformationFile(handle, &statusBlock, &modeInfo, sizeof(modeInfo), FileModeInformation); + return status == 0 && WI_AreAllFlagsClear(modeInfo.Mode, FILE_SYNCHRONOUS_IO_ALERT | FILE_SYNCHRONOUS_IO_NONALERT); } [[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()) @@ -96,7 +80,7 @@ VtIo::VtIo() : CodepointWidthDetector::Singleton().Reset(mode); } - return _Initialize(pArgs->GetVtInHandle(), pArgs->GetVtOutHandle(), pArgs->GetVtMode(), pArgs->GetSignalHandle()); + return _Initialize(pArgs->GetVtInHandle(), pArgs->GetVtOutHandle(), pArgs->GetSignalHandle()); } // Didn't need to initialize if we didn't have VT stuff. It's still OK, but report we did nothing. else @@ -116,8 +100,6 @@ VtIo::VtIo() : // input events. // OutHandle: a valid file handle. The console // will be "rendered" to this pipe using VT sequences -// VtIoMode: A string containing the console's requested VT mode. This can be -// any of the strings in VtIoModes.hpp // SignalHandle: an optional file handle that will be used to send signals into the console. // This represents the ability to send signals to a *nix tty/pty. // Return Value: @@ -125,17 +107,23 @@ VtIo::VtIo() : // indicating failure. [[nodiscard]] HRESULT VtIo::_Initialize(const HANDLE InHandle, const HANDLE OutHandle, - const std::wstring& VtMode, _In_opt_ const HANDLE SignalHandle) { FAIL_FAST_IF_MSG(_initialized, "Someone attempted to double-_Initialize VtIo"); - RETURN_IF_FAILED(ParseIoMode(VtMode, _IoMode)); - _hInput.reset(InHandle); _hOutput.reset(OutHandle); _hSignal.reset(SignalHandle); + if (handleWantsOverlappedIo(_hOutput.get())) + { + _overlappedEvent.reset(CreateEventExW(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS)); + THROW_LAST_ERROR_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; @@ -143,7 +131,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: @@ -158,11 +146,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 { @@ -170,44 +156,6 @@ VtIo::VtIo() : { _pVtInputThread = std::make_unique(std::move(_hInput), _lookingForCursorPosition); } - - if (IsValidHandle(_hOutput.get())) - { - auto initialViewport = Viewport::FromDimensions({ 0, 0 }, gci.GetWindowSize()); - switch (_IoMode) - { - case VtIoMode::XTERM_256: - { - auto xterm256Engine = std::make_unique(std::move(_hOutput), - initialViewport); - _pVtRenderEngine = std::move(xterm256Engine); - break; - } - case VtIoMode::XTERM: - { - _pVtRenderEngine = std::make_unique(std::move(_hOutput), - initialViewport, - false); - break; - } - case VtIoMode::XTERM_ASCII: - { - _pVtRenderEngine = std::make_unique(std::move(_hOutput), - initialViewport, - true); - break; - } - default: - { - return E_FAIL; - } - } - if (_pVtRenderEngine) - { - _pVtRenderEngine->SetTerminalOwner(this); - _pVtRenderEngine->SetResizeQuirk(_resizeQuirk); - } - } } CATCH_RETURN(); @@ -236,21 +184,6 @@ bool VtIo::IsUsingVt() const { return S_FALSE; } - auto& g = ServiceLocator::LocateGlobals(); - - if (_pVtRenderEngine) - { - 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(); - } // MSFT: 15813316 // If the terminal application wants us to inherit the cursor position, @@ -262,10 +195,10 @@ bool VtIo::IsUsingVt() const // We need both handles for this initialization to work. If we don't have // both, we'll skip it. They either aren't going to be reading output // (so they can't get the DSR) or they can't write the response to us. - if (_lookingForCursorPosition && _pVtRenderEngine && _pVtInputThread) + if (_pVtInputThread && _pVtInputThread->IsLookingForDSR()) { - LOG_IF_FAILED(_pVtRenderEngine->RequestCursor()); - while (_lookingForCursorPosition && _pVtInputThread->DoReadInput()) + WriteUTF8("\x1b[6n"); // Cursor Position Report (DSR CPR) + while (_pVtInputThread->DoReadInput() && _pVtInputThread->IsLookingForDSR()) { } } @@ -274,7 +207,14 @@ bool VtIo::IsUsingVt() const // 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()); + + // By default, DISABLE_NEWLINE_AUTO_RETURN is reset. This implies LNM being set, + // which is not the default in terminals, so we have to do that explicitly. + WriteUTF8( + "\x1b[20h" // Line Feed / New Line Mode (LNM) + "\033[?9001h" // Win32 Input Mode + "\033[?1004h" // Focus Event Mode + ); if (_pVtInputThread) { @@ -318,25 +258,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 @@ -373,148 +294,308 @@ 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() +void VtIo::CloseInput() +{ + _pVtInputThread = nullptr; + SendCloseEvent(); +} + +void VtIo::CloseOutput() { - auto hr = S_OK; - if (_pVtRenderEngine) + _hOutput.reset(); +} + +void VtIo::SendCloseEvent() +{ + LockConsole(); + const auto unlock = wil::scope_exit([] { UnlockConsole(); }); + + // This function is called when the ConPTY signal pipe is closed (PtySignalInputThread) and when the input + // pipe is closed (VtIo). Usually these two happen at about the same time. This if condition is a bit of + // a premature optimization and prevents us from sending out a CTRL_CLOSE_EVENT right after another. + if (!std::exchange(_closeEventSent, true)) { - hr = _pVtRenderEngine->SuppressResizeRepaint(); + CloseConsoleProcessState(); } - 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) +// Returns true for C0 characters and C1 [single-character] CSI. +// A copy of isActionableFromGround() from stateMachine.cpp. +bool VtIo::IsControlCharacter(wchar_t wch) noexcept +{ + // 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); +} + +static size_t formatAttributes(char (&buffer)[16], WORD attributes) noexcept { - auto hr = S_OK; - if (_lookingForCursorPosition) + const uint8_t rv = WI_IsFlagSet(attributes, COMMON_LVB_REVERSE_VIDEO) ? 7 : 27; + uint8_t fg = 39; + uint8_t bg = 49; + + // `attributes` of exactly `FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED` + // are often used to indicate the default colors in Windows Console applications. + // Thus, we translate them to 39/49 (default foreground/background). + if ((attributes & (FG_ATTRS | BG_ATTRS)) != (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED)) { - if (_pVtRenderEngine) - { - hr = _pVtRenderEngine->InheritCursor(coordCursor); - } + // The Console API represents colors in BGR order, but VT represents them in RGB order. + // This LUT transposes them. This is for foreground colors. Add +10 to get the background ones. + static constexpr uint8_t lut[] = { 30, 34, 32, 36, 31, 35, 33, 37, 90, 94, 92, 96, 91, 95, 93, 97 }; + fg = lut[attributes & 0xf]; + bg = lut[(attributes >> 4) & 0xf] + 10; + } + + return fmt::format_to(&buffer[0], FMT_COMPILE("\x1b[{};{};{}m"), rv, fg, bg) - &buffer[0]; +} + +void VtIo::FormatAttributes(std::string& target, WORD attributes) +{ + char buf[16]; + const auto len = formatAttributes(buf, attributes); + target.append(buf, len); +} + +void VtIo::FormatAttributes(std::wstring& target, WORD attributes) +{ + char buf[16]; + const auto len = formatAttributes(buf, attributes); - _lookingForCursorPosition = false; + wchar_t bufW[16]; + for (size_t i = 0; i < len; i++) + { + bufW[i] = buf[i]; } - return hr; + + target.append(bufW, len); } -[[nodiscard]] HRESULT VtIo::SwitchScreenBuffer(const bool useAltBuffer) +void VtIo::WriteUTF8(std::string_view str) { - auto hr = S_OK; - if (_pVtRenderEngine) + if (str.empty() || !_hOutput) { - hr = _pVtRenderEngine->SwitchScreenBuffer(useAltBuffer); + return; } - return hr; + + // Always appending to _buffer instead of directly writing `str` to the pipe, even if we aren't even + // using overlapped IO, is a bit of a waste, but it makes the retry in _flush() quite a bit simpler. + // Coincidentally, most VtIo translations rely on buffer corking and so we'd have to append to _buffer anyway. + _back.append(str); + _flush(); } -void VtIo::CloseInput() +void VtIo::WriteUTF16(std::wstring_view str) { - _pVtInputThread = nullptr; - SendCloseEvent(); + if (str.empty() || !_hOutput) + { + return; + } + + const auto str8 = til::u16u8(str); + WriteUTF8(str8); } -void VtIo::CloseOutput() +void VtIo::WriteUCS2(wchar_t ch) { - auto& g = ServiceLocator::LocateGlobals(); - g.getConsoleInformation().GetActiveOutputBuffer().SetTerminalConnection(nullptr); + 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)); + } + + WriteUTF8({ &buf[0], len }); } -void VtIo::SendCloseEvent() +void VtIo::WriteCUP(til::point position) { - LockConsole(); - const auto unlock = wil::scope_exit([] { UnlockConsole(); }); + WriteFormat("\x1b[{};{}H", position.y + 1, position.x + 1); +} - // This function is called when the ConPTY signal pipe is closed (PtySignalInputThread) and when the input - // pipe is closed (VtIo). Usually these two happen at about the same time. This if condition is a bit of - // a premature optimization and prevents us from sending out a CTRL_CLOSE_EVENT right after another. - if (!std::exchange(_closeEventSent, true)) +void VtIo::WriteAttributes(WORD attributes) +{ + FormatAttributes(_back, attributes); + _flush(); +} + +void VtIo::WriteInfos(til::point target, std::span infos) +{ + const auto beg = infos.begin(); + const auto end = infos.end(); + const auto last = end - 1; + const auto cork = Cork(); + WORD attributes = 0xffff; + + WriteCUP(target); + + for (auto it = beg; it != end; ++it) { - CloseConsoleProcessState(); + 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(attributes); + } + + const auto isSurrogate = til::is_surrogate(ch); + const auto isControl = IsControlCharacter(ch); + int repeat = 1; + if (isSurrogate || isControl) + { + ch = isSurrogate ? UNICODE_REPLACEMENT : L' '; + // Space and U+FFFD are narrow characters, so if the caller intended + // for a wide glyph we need to emit two U+FFFD characters. + repeat = wide ? 2 : 1; + } + + do + { + WriteUCS2(ch); + } while (--repeat); } } -// 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 +VtIo::CorkLock VtIo::Cork() noexcept { - _pVtRenderEngine->Cork(corked); + _corked += 1; + return CorkLock{ this }; } -#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) +VtIo::CorkLock::CorkLock(VtIo* io) noexcept : + _io{ io } { - _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 +VtIo::CorkLock::~CorkLock() noexcept { - return _resizeQuirk; + if (_io) + { + _io->_uncork(); + } } -// 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 +VtIo::CorkLock::CorkLock(CorkLock&& other) noexcept : + _io{ std::exchange(other._io, nullptr) } +{ +} + +VtIo::CorkLock& VtIo::CorkLock::operator=(CorkLock&& other) noexcept { - if (_pVtRenderEngine) + if (this != &other) { - return _pVtRenderEngine->ManuallyClearScrollback(); + this->~CorkLock(); + _io = std::exchange(other._io, nullptr); } - return S_OK; + return *this; +} + +void VtIo::_uncork() +{ + _corked -= 1; + _flush(); } -[[nodiscard]] HRESULT VtIo::RequestMouseMode(bool enable) const noexcept +void VtIo::_flush() { - if (_pVtRenderEngine) + if (_corked <= 0) { - return _pVtRenderEngine->RequestMouseMode(enable); + _flushNow(); } - return S_OK; +} + +void VtIo::_flushNow() +{ + if (_overlappedPending) + { + _overlappedPending = false; + std::ignore = _overlappedEvent.wait(); + } + + _front.clear(); + _front.swap(_back); + + // If it's >64KiB 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 > 64 * 1024 && cap > _front.capacity() / 2) + { + _back = std::string{}; + } + + for (;;) + { + if (WriteFile(_hOutput.get(), _front.data(), gsl::narrow_cast(_front.size()), nullptr, _overlapped)) + { + return; + } + + switch (const auto gle = GetLastError()) + { + case ERROR_BROKEN_PIPE: + CloseOutput(); + return; + case ERROR_IO_PENDING: + _overlappedPending = true; + return; + default: + LOG_WIN32(gle); + return; + } + } +} + +bool VtIo::BufferHasContent() const noexcept +{ + return !_back.empty(); } diff --git a/src/host/VtIo.hpp b/src/host/VtIo.hpp index eccdb06aaac..6079dc61204 100644 --- a/src/host/VtIo.hpp +++ b/src/host/VtIo.hpp @@ -3,27 +3,56 @@ #pragma once -#include "../inc/VtIoModes.hpp" -#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: - VtIo(); + struct CorkLock + { + CorkLock() = default; + CorkLock(VtIo* io) noexcept; - [[nodiscard]] HRESULT Initialize(const ConsoleArguments* const pArgs); + ~CorkLock() noexcept; + + CorkLock(const CorkLock&) = delete; + CorkLock& operator=(const CorkLock&) = delete; + CorkLock(CorkLock&& other) noexcept; + CorkLock& operator=(CorkLock&& other) noexcept; + + private: + VtIo* _io = nullptr; + }; + + struct CursorRestore + { + CursorRestore() = default; + CursorRestore(VtIo* io, til::point position) noexcept; + + ~CursorRestore() noexcept; + + CursorRestore(const CursorRestore&) = delete; + CursorRestore& operator=(const CursorRestore&) = delete; + CursorRestore(CursorRestore&& other) noexcept; + CursorRestore& operator=(CursorRestore&& other) noexcept; + + private: + VtIo* _io = nullptr; + til::point _position; + }; + + friend struct CorkLock; + + static bool IsControlCharacter(wchar_t wch) noexcept; + static void FormatAttributes(std::string& target, WORD attributes); + static void FormatAttributes(std::wstring& target, WORD attributes); + [[nodiscard]] HRESULT Initialize(const ConsoleArguments* const pArgs); [[nodiscard]] HRESULT CreateAndStartSignalThread() noexcept; [[nodiscard]] HRESULT CreateIoHandlers() noexcept; @@ -31,49 +60,55 @@ namespace Microsoft::Console::VirtualTerminal [[nodiscard]] HRESULT StartIfNeeded(); - [[nodiscard]] static HRESULT ParseIoMode(const std::wstring& VtMode, _Out_ VtIoMode& ioMode); - [[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; + void CreatePseudoWindow(); -#ifdef UNIT_TESTING - void EnableConptyModeForTests(std::unique_ptr vtRenderEngine, const bool resizeQuirk = false); -#endif + CorkLock Cork() noexcept; + bool BufferHasContent() const noexcept; + + void WriteFormat(auto&&... args) + { + fmt::format_to(std::back_inserter(_back), std::forward(args)...); + _flush(); + } + void WriteUTF8(std::string_view str); + void WriteUTF16(std::wstring_view str); + void WriteUCS2(wchar_t ch); + void WriteCUP(til::point position); + void WriteAttributes(WORD attributes); + void WriteInfos(til::point target, std::span infos); - bool IsResizeQuirkEnabled() const; + private: + [[nodiscard]] HRESULT _Initialize(const HANDLE InHandle, const HANDLE OutHandle, _In_opt_ const HANDLE SignalHandle); - [[nodiscard]] HRESULT ManuallyClearScrollback() const noexcept; - [[nodiscard]] HRESULT RequestMouseMode(bool enable) const noexcept; + void _uncork(); + void _flush(); + void _flushNow(); - void CreatePseudoWindow(); - void SetWindowVisibility(bool showOrHide) noexcept; - - private: // 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; - VtIoMode _IoMode; - - 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, const std::wstring& VtMode, _In_opt_ const HANDLE SignalHandle); + std::string _front; + std::string _back; + OVERLAPPED* _overlapped = nullptr; + OVERLAPPED _overlappedBuf{}; + wil::unique_event _overlappedEvent; + bool _overlappedPending = 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 9db5ea4c8d6..154138b724a 100644 --- a/src/host/_output.cpp +++ b/src/host/_output.cpp @@ -5,18 +5,16 @@ #include "_output.h" -#include "dbcs.h" +#include "directio.h" #include "handle.h" #include "misc.h" +#include "_stream.h" #include "../interactivity/inc/ServiceLocator.hpp" -#include "../types/inc/Viewport.hpp" #include "../types/inc/convert.hpp" - -#include -#include - -#pragma hdrstop +#include "../types/inc/GlyphWidth.hpp" +#include "../types/inc/Viewport.hpp" +#include "til/unicode.h" using namespace Microsoft::Console::Types; using Microsoft::Console::Interactivity::ServiceLocator; @@ -53,6 +51,195 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region) } } +enum class FillConsoleMode +{ + WriteAttribute, + WriteCharacter, + FillAttribute, + FillCharacter, +}; + +struct FillConsoleResult +{ + size_t lengthRead = 0; + til::CoordType cellsModified = 0; +}; + +static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillConsoleMode mode, const uint16_t* data, const size_t lengthToWrite, const til::point startingCoordinate) +{ + if (lengthToWrite == 0) + { + return {}; + } + + LockConsole(); + const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); + + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + const auto routines = ServiceLocator::LocateGlobals().api; + auto& screenBuffer = screenInfo.GetActiveBuffer(); + const auto bufferSize = screenBuffer.GetBufferSize(); + FillConsoleResult result; + + if (!bufferSize.IsInBounds(startingCoordinate)) + { + return {}; + } + + if (const auto io = gci.GetVtIo(&screenInfo)) + { + const auto corkLock = io->Cork(); + + const auto h = bufferSize.Height(); + const auto w = bufferSize.Width(); + auto y = startingCoordinate.y; + til::CoordType end = 0; + auto remaining = lengthToWrite; + + til::small_vector infos; + infos.resize(gsl::narrow_cast(w) + 1); + + Viewport unused; + + while (y < h && remaining > 0) + { + const auto beg = y == startingCoordinate.y ? startingCoordinate.x : 0; + auto len = std::min(remaining, gsl::narrow_cast(w - beg)); + end = beg + gsl::narrow_cast(len); + + auto viewport = Viewport::FromInclusive({ beg, y, end - 1, y }); + THROW_IF_FAILED(routines->ReadConsoleOutputWImpl(screenInfo, infos, viewport, unused)); + + switch (mode) + { + case FillConsoleMode::WriteAttribute: + for (size_t i = 0; i < len; ++i) + { + infos[i].Attributes = *data++; + } + break; + case FillConsoleMode::WriteCharacter: + for (size_t i = 0; i < len;) + { + const auto ch = *data++; + + auto& lead = infos[i++]; + lead.Char.UnicodeChar = ch; + lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE); + + if (IsGlyphFullWidth(ch)) + { + lead.Attributes |= COMMON_LVB_LEADING_BYTE; + + auto& trail = infos[i++]; + trail.Char.UnicodeChar = ch; + trail.Attributes = trail.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE) | COMMON_LVB_LEADING_BYTE; + } + } + break; + case FillConsoleMode::FillAttribute: + { + const auto attr = *data; + for (size_t i = 0; i < len; ++i) + { + infos[i].Attributes = attr; + } + break; + } + case FillConsoleMode::FillCharacter: + { + const auto ch = *data; + + if (IsGlyphFullWidth(ch)) + { + for (size_t i = 0; i < len;) + { + auto& lead = infos[i++]; + lead.Char.UnicodeChar = ch; + lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE) | COMMON_LVB_LEADING_BYTE; + + auto& trail = infos[i++]; + trail.Char.UnicodeChar = ch; + trail.Attributes = trail.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE) | COMMON_LVB_LEADING_BYTE; + } + } + else + { + for (size_t i = 0; i < len;) + { + auto& lead = infos[i++]; + lead.Char.UnicodeChar = ch; + lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE); + } + } + + break; + } + } + + if (auto& last = infos[len - 1]; last.Attributes & COMMON_LVB_LEADING_BYTE) + { + len--; + end--; + } + + viewport = Viewport::FromInclusive({ beg, y, end - 1, y }); + THROW_IF_FAILED(WriteConsoleOutputWImplHelper(screenInfo, infos, viewport.Width(), viewport, unused)); + + y += 1; + remaining -= len; + } + + result.lengthRead = lengthToWrite - remaining; + result.cellsModified = std::max(0, (y - startingCoordinate.y - 1) * w + end - startingCoordinate.x); + + if (io && io->BufferHasContent()) + { + io->WriteCUP(screenInfo.GetTextBuffer().GetCursor().GetPosition()); + io->WriteAttributes(screenInfo.GetAttributes().GetLegacyAttributes()); + } + } + else + { + OutputCellIterator it; + + switch (mode) + { + case FillConsoleMode::WriteAttribute: + it = OutputCellIterator(TextAttribute(*data), lengthToWrite); + break; + case FillConsoleMode::WriteCharacter: + it = OutputCellIterator(*data, lengthToWrite); + break; + case FillConsoleMode::FillAttribute: + it = OutputCellIterator(TextAttribute(*data), lengthToWrite); + break; + case FillConsoleMode::FillCharacter: + it = OutputCellIterator(*data, lengthToWrite); + break; + default: + __assume(false); + } + + const auto done = screenBuffer.Write(it, startingCoordinate, false); + result.lengthRead = done.GetInputDistance(it); + result.cellsModified = done.GetCellDistance(it); + + // If we've overwritten image content, it needs to be erased. + ImageSlice::EraseCells(screenInfo.GetTextBuffer(), startingCoordinate, result.cellsModified); + } + + if (screenBuffer.HasAccessibilityEventing()) + { + // Notify accessibility + auto endingCoordinate = startingCoordinate; + bufferSize.WalkInBounds(endingCoordinate, result.cellsModified); + screenBuffer.NotifyAccessibilityEventing(startingCoordinate.x, startingCoordinate.y, endingCoordinate.x, endingCoordinate.y); + } + + return result; +} + // Routine Description: // - writes text attributes to the screen // Arguments: @@ -75,22 +262,15 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region) return S_OK; } - LockConsole(); - auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); - - auto& screenInfo = OutContext.GetActiveBuffer(); - const auto bufferSize = screenInfo.GetBufferSize(); - if (!bufferSize.IsInBounds(target)) + try { - return E_INVALIDARG; - } - - const OutputCellIterator it(attrs); - const auto done = screenInfo.Write(it, target); - - used = done.GetCellDistance(it); + LockConsole(); + const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); - return S_OK; + used = FillConsoleImpl(OutContext, FillConsoleMode::WriteAttribute, attrs.data(), attrs.size(), target).cellsModified; + return S_OK; + } + CATCH_RETURN(); } // Routine Description: @@ -115,23 +295,12 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region) return S_OK; } - LockConsole(); - auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); - - auto& screenInfo = OutContext.GetActiveBuffer(); - const auto bufferSize = screenInfo.GetBufferSize(); - if (!bufferSize.IsInBounds(target)) - { - return E_INVALIDARG; - } - try { - OutputCellIterator it(chars); - const auto finished = screenInfo.Write(it, target); - used = finished.GetInputDistance(it); - // If we've overwritten image content, it needs to be erased. - ImageSlice::EraseCells(screenInfo.GetTextBuffer(), target, used); + LockConsole(); + const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); + + used = FillConsoleImpl(OutContext, FillConsoleMode::WriteCharacter, reinterpret_cast(chars.data()), chars.size(), target).lengthRead; } CATCH_RETURN(); @@ -194,44 +363,15 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region) const til::point startingCoordinate, size_t& cellsModified) noexcept { - // Set modified cells to 0 from the beginning. - cellsModified = 0; - - if (lengthToWrite == 0) - { - return S_OK; - } - - LockConsole(); - auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); - - auto& screenBuffer = OutContext.GetActiveBuffer(); - const auto bufferSize = screenBuffer.GetBufferSize(); - if (!bufferSize.IsInBounds(startingCoordinate)) - { - return S_OK; - } - try { - TextAttribute useThisAttr(attribute); - const OutputCellIterator it(useThisAttr, lengthToWrite); - const auto done = screenBuffer.Write(it, startingCoordinate); - const auto cellsModifiedCoord = done.GetCellDistance(it); + LockConsole(); + const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); - cellsModified = cellsModifiedCoord; - - if (screenBuffer.HasAccessibilityEventing()) - { - // Notify accessibility - auto endingCoordinate = startingCoordinate; - bufferSize.WalkInBounds(endingCoordinate, cellsModifiedCoord); - screenBuffer.NotifyAccessibilityEventing(startingCoordinate.x, startingCoordinate.y, endingCoordinate.x, endingCoordinate.y); - } + cellsModified = FillConsoleImpl(OutContext, FillConsoleMode::FillAttribute, &attribute, lengthToWrite, startingCoordinate).cellsModified; + return S_OK; } CATCH_RETURN(); - - return S_OK; } // Routine Description: @@ -254,76 +394,40 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region) size_t& cellsModified, const bool enablePowershellShim) noexcept { - // Set modified cells to 0 from the beginning. - cellsModified = 0; - - if (lengthToWrite == 0) - { - return S_OK; - } - - LockConsole(); - auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); - - // TODO: does this even need to be here or will it exit quickly? - auto& screenInfo = OutContext.GetActiveBuffer(); - const auto bufferSize = screenInfo.GetBufferSize(); - if (!bufferSize.IsInBounds(startingCoordinate)) - { - return S_OK; - } - - auto hr = S_OK; try { - const OutputCellIterator it(character, lengthToWrite); - - // when writing to the buffer, specifically unset wrap if we get to the last column. - // a fill operation should UNSET wrap in that scenario. See GH #1126 for more details. - const auto done = screenInfo.Write(it, startingCoordinate, false); - const auto cellsModifiedCoord = done.GetInputDistance(it); - - cellsModified = cellsModifiedCoord; + LockConsole(); + const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); - // If we've overwritten image content, it needs to be erased. - ImageSlice::EraseCells(screenInfo.GetTextBuffer(), startingCoordinate, cellsModified); - - // Notify accessibility - if (screenInfo.HasAccessibilityEventing()) - { - auto endingCoordinate = startingCoordinate; - bufferSize.WalkInBounds(endingCoordinate, cellsModifiedCoord); - screenInfo.NotifyAccessibilityEventing(startingCoordinate.x, startingCoordinate.y, endingCoordinate.x, endingCoordinate.y); - } - - // 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. auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (enablePowershellShim && gci.IsInVtIoMode()) + if (const auto io = gci.GetVtIo(&OutContext)) { - const auto currentBufferDimensions{ screenInfo.GetBufferSize().Dimensions() }; - - const auto wroteWholeBuffer = lengthToWrite == (currentBufferDimensions.area()); - const auto startedAtOrigin = startingCoordinate == til::point{ 0, 0 }; - const auto wroteSpaces = character == UNICODE_SPACE; - - if (wroteWholeBuffer && startedAtOrigin && wroteSpaces) + // 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 Full Reset (RIS). + if (enablePowershellShim) { - // 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(); + const auto currentBufferDimensions{ OutContext.GetBufferSize().Dimensions() }; + const auto wroteWholeBuffer = lengthToWrite == (currentBufferDimensions.area()); + const auto startedAtOrigin = startingCoordinate == til::point{ 0, 0 }; + const auto wroteSpaces = character == UNICODE_SPACE; + + if (wroteWholeBuffer && startedAtOrigin && wroteSpaces) + { + WriteCharsVT(OutContext, L"\033c"); + cellsModified = lengthToWrite; + return S_OK; + } } } + + cellsModified = FillConsoleImpl(OutContext, FillConsoleMode::FillCharacter, reinterpret_cast(&character), lengthToWrite, startingCoordinate).lengthRead; + return S_OK; } CATCH_RETURN(); - - return hr; } // Routine Description: diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index e030cec03c7..17ac6c9bc03 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,26 @@ #include "../interactivity/inc/ServiceLocator.hpp" -#pragma hdrstop using namespace Microsoft::Console::Types; 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; +} + +// This is a copy of the same function in stateMachine.cpp. +// Returns true for C0 characters and C1 [single-character] CSI. +constexpr bool isActionableFromGround(const wchar_t wch) noexcept +{ + // 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); +} // Routine Description: // - This routine updates the cursor position. Its input is the non-special @@ -109,11 +124,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 +143,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 +157,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. @@ -156,12 +175,13 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t auto it = text.begin(); const auto end = text.end(); - // 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. + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + const auto io = gci.GetVtIo(&screenInfo); + const auto corkLock = io ? io->Cork() : Microsoft::Console::VirtualTerminal::VtIo::CorkLock{}; + + // 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(); @@ -179,43 +199,86 @@ 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 (io) + { + // 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. + // + // BODGY: The const_cast is ""safe"", because the backing memory of `text` comes from `_CONSOLE_API_MSG::_inputBuffer`. + // This allows us to replace the control characters without copying potentially Megabytes of data. + // Ideally the parameter simply wouldn't be a string_view (= const char), but oh well. + const auto begMut = const_cast(text.data()); + const auto endMut = begMut + text.size(); + std::replace_if(begMut, endMut, isActionableFromGround, L' '); + + io->WriteUTF16(text); + if (lastCharWrapped) + { + io->WriteUTF8(" \r"); + } + } + + 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 (io) + { + io->WriteUTF16(chunk); + if (lastCharWrapped) + { + io->WriteUTF8(" \r"); + } + } + } + + if (it == end) + { + break; } - for (; it != end && !IS_GLYPH_CHAR(*it); ++it) + do { - switch (*it) + 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: { @@ -228,30 +291,60 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t 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 (io) { - _writeCharsLegacyUnprocessed(screenInfo, { &wch, 1 }, psScrollY); + if (wch) + { + io->WriteUTF16({ &wch, 1 }); + } + if (lastCharWrapped) + { + io->WriteUTF8(" \r"); + } } - } + + ++it; + } while (it != end && controlCharPredicate(*it)); + } +} + +void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str) +{ + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + + screenInfo.GetStateMachine().ProcessString(str); + + if (const auto io = gci.GetVtIo(&screenInfo)) + { + io->WriteUTF16(str); } } @@ -277,7 +370,7 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t 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, @@ -288,25 +381,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) }; @@ -316,7 +400,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 79eb84585dc..3897abf22fb 100644 --- a/src/host/_stream.h +++ b/src/host/_stream.h @@ -20,6 +20,7 @@ Revision History: #include "writeData.hpp" void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str, til::CoordType* psScrollY); +void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str); // 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..353d7f92e9f 100644 --- a/src/host/consoleInformation.cpp +++ b/src/host/consoleInformation.cpp @@ -45,6 +45,11 @@ ULONG CONSOLE_INFORMATION::GetCSRecursionCount() const noexcept return _lock.recursion_depth(); } +Microsoft::Console::VirtualTerminal::VtIo* CONSOLE_INFORMATION::GetVtIoNoCheck() +{ + return &_vtIo; +} + // Routine Description: // - This routine allocates and initialized a console and its associated // data - input buffer and screen buffer. @@ -118,14 +123,10 @@ ULONG CONSOLE_INFORMATION::GetCSRecursionCount() const noexcept return Status; } -VtIo* CONSOLE_INFORMATION::GetVtIo() +VtIo* CONSOLE_INFORMATION::GetVtIo(const SCREEN_INFORMATION* context) { - return &_vtIo; -} - -bool CONSOLE_INFORMATION::IsInVtIoMode() const -{ - return _vtIo.IsUsingVt(); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + return _vtIo.IsUsingVt() && (context == nullptr || context == &gci.GetActiveOutputBuffer()) ? &_vtIo : nullptr; } bool CONSOLE_INFORMATION::HasPendingCookedRead() const noexcept diff --git a/src/host/directio.cpp b/src/host/directio.cpp index 8bc0bf36fd2..6e876bbf93c 100644 --- a/src/host/directio.cpp +++ b/src/host/directio.cpp @@ -485,12 +485,9 @@ CATCH_RETURN(); // The buffer given should be big enough to hold the dimensions of the request. const auto targetArea = targetSize.area(); - RETURN_HR_IF(E_INVALIDARG, targetArea < targetBuffer.size()); + RETURN_HR_IF(E_INVALIDARG, targetArea > targetBuffer.size()); - // Clip the request rectangle to the size of the storage buffer auto clip = requestRectangle.ToExclusive(); - clip.right = std::min(clip.right, storageSize.width); - clip.bottom = std::min(clip.bottom, storageSize.height); // Find the target point (where to write the user's buffer) // It will either be 0,0 or offset into the buffer by the inverse of the negative values. @@ -502,9 +499,19 @@ CATCH_RETURN(); clip.left = std::max(clip.left, 0); clip.top = std::max(clip.top, 0); + // Clip the request rectangle to the size of the storage buffer + clip.right = std::clamp(clip.right, clip.left, storageSize.width); + clip.bottom = std::clamp(clip.bottom, clip.top, storageSize.height); + // The final "request rectangle" or the area inside the buffer we want to read, is the clipped dimensions. const auto clippedRequestRectangle = Viewport::FromExclusive(clip); + if (!clippedRequestRectangle.IsValid()) + { + readRectangle = Viewport::FromDimensions(clippedRequestRectangle.Origin(), { 0, 0 }); + return S_OK; + } + // We will start reading the buffer at the point of the top left corner (origin) of the (potentially adjusted) request const auto sourcePoint = clippedRequestRectangle.Origin(); @@ -599,96 +606,50 @@ CATCH_RETURN(); CATCH_RETURN(); } -[[nodiscard]] static HRESULT _WriteConsoleOutputWImplHelper(SCREEN_INFORMATION& context, - std::span buffer, - const Viewport& requestRectangle, - Viewport& writtenRectangle) noexcept +[[nodiscard]] HRESULT WriteConsoleOutputWImplHelper(SCREEN_INFORMATION& context, + std::span buffer, + size_t bufferStride, + const Viewport& requestRectangle, + Viewport& writtenRectangle) noexcept { try { + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + + const auto io = gci.GetVtIo(&context); + auto& storageBuffer = context.GetActiveBuffer(); const auto storageRectangle = storageBuffer.GetBufferSize(); - const auto storageSize = storageRectangle.Dimensions(); - - const auto sourceSize = requestRectangle.Dimensions(); + const auto writeRectangle = storageRectangle.Clamp(requestRectangle); - // If either dimension of the request is too small, return an empty rectangle as the read and exit early. - if (sourceSize.width <= 0 || sourceSize.height <= 0) + if (!writeRectangle.IsValid()) { writtenRectangle = Viewport::FromDimensions(requestRectangle.Origin(), { 0, 0 }); return S_OK; } - // If the top and left of the destination we're trying to write it outside the buffer, - // give the original request rectangle back and exit early OK. - if (requestRectangle.Left() >= storageSize.width || requestRectangle.Top() >= storageSize.height) - { - writtenRectangle = requestRectangle; - return S_OK; - } - - // Do clipping according to the legacy patterns. - auto writeRegion = requestRectangle.ToInclusive(); - til::inclusive_rect sourceRect; - if (writeRegion.right > storageSize.width - 1) - { - writeRegion.right = storageSize.width - 1; - } - sourceRect.right = writeRegion.right - writeRegion.left; - if (writeRegion.bottom > storageSize.height - 1) - { - writeRegion.bottom = storageSize.height - 1; - } - sourceRect.bottom = writeRegion.bottom - writeRegion.top; - - if (writeRegion.left < 0) - { - sourceRect.left = -writeRegion.left; - writeRegion.left = 0; - } - else - { - sourceRect.left = 0; - } - - if (writeRegion.top < 0) - { - sourceRect.top = -writeRegion.top; - writeRegion.top = 0; - } - else - { - sourceRect.top = 0; - } - - if (sourceRect.left > sourceRect.right || sourceRect.top > sourceRect.bottom) - { - return E_INVALIDARG; - } - - const auto writeRectangle = Viewport::FromInclusive(writeRegion); - + const auto offsetY = writeRectangle.Top() - requestRectangle.Top(); + const auto offsetX = writeRectangle.Left() - requestRectangle.Left(); + auto totalOffset = offsetY * bufferStride + offsetX; auto target = writeRectangle.Origin(); // For every row in the request, create a view into the clamped portion of just the one line to write. // This allows us to restrict the width of the call without allocating/copying any memory by just making // a smaller view over the existing big blob of data from the original call. - for (; target.y < writeRectangle.BottomExclusive(); target.y++) + for (; target.y <= writeRectangle.BottomInclusive(); target.y++) { - // We find the offset into the original buffer by the dimensions of the original request rectangle. - const auto rowOffset = (target.y - requestRectangle.Top()) * requestRectangle.Width(); - const auto colOffset = target.x - requestRectangle.Left(); - const auto totalOffset = rowOffset + colOffset; - // Now we make a subspan starting from that offset for as much of the original request as would fit - const auto subspan = buffer.subspan(totalOffset, writeRectangle.Width()); - - // Convert to a CHAR_INFO view to fit into the iterator - const auto charInfos = std::span(subspan.data(), subspan.size()); + const auto charInfos = buffer.subspan(totalOffset, writeRectangle.Width()); // Make the iterator and write to the target position. - OutputCellIterator it(charInfos); - storageBuffer.Write(it, target); + storageBuffer.Write(OutputCellIterator(charInfos), target); + + if (io) + { + io->WriteInfos(target, charInfos); + } + + totalOffset += bufferStride; } // If we've overwritten image content, it needs to be erased. @@ -712,11 +673,20 @@ CATCH_RETURN(); try { - const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + const auto io = gci.GetVtIo(&context); + const auto corkLock = io ? io->Cork() : Microsoft::Console::VirtualTerminal::VtIo::CorkLock{}; + const auto codepage = gci.OutputCP; LOG_IF_FAILED(_ConvertCellsToWInplace(codepage, buffer, requestRectangle)); - RETURN_IF_FAILED(_WriteConsoleOutputWImplHelper(context, buffer, requestRectangle, writtenRectangle)); + RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, buffer, requestRectangle.Width(), requestRectangle, writtenRectangle)); + + if (io && io->BufferHasContent()) + { + io->WriteCUP(context.GetTextBuffer().GetCursor().GetPosition()); + io->WriteAttributes(context.GetAttributes().GetLegacyAttributes()); + } return S_OK; } @@ -733,16 +703,26 @@ CATCH_RETURN(); try { + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + const auto io = gci.GetVtIo(&context); + const auto corkLock = io ? io->Cork() : Microsoft::Console::VirtualTerminal::VtIo::CorkLock{}; + 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. // This can be removed when raster font support is removed. auto translated = _ConvertCellsToMungedW(buffer, requestRectangle); - RETURN_IF_FAILED(_WriteConsoleOutputWImplHelper(context, translated, requestRectangle, writtenRectangle)); + RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, translated, requestRectangle.Width(), requestRectangle, writtenRectangle)); } else { - RETURN_IF_FAILED(_WriteConsoleOutputWImplHelper(context, buffer, requestRectangle, writtenRectangle)); + RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, buffer, requestRectangle.Width(), requestRectangle, writtenRectangle)); + } + + if (io && io->BufferHasContent()) + { + io->WriteCUP(context.GetTextBuffer().GetCursor().GetPosition()); + io->WriteAttributes(context.GetAttributes().GetLegacyAttributes()); } return S_OK; diff --git a/src/host/directio.h b/src/host/directio.h index e8cd977d515..0307fbe0c83 100644 --- a/src/host/directio.h +++ b/src/host/directio.h @@ -17,10 +17,15 @@ Revision History: #pragma once #include "conapi.h" -#include "inputBuffer.hpp" class SCREEN_INFORMATION; +[[nodiscard]] HRESULT WriteConsoleOutputWImplHelper(SCREEN_INFORMATION& context, + std::span buffer, + size_t bufferStride, + const Microsoft::Console::Types::Viewport& requestRectangle, + Microsoft::Console::Types::Viewport& writtenRectangle) noexcept; + [[nodiscard]] NTSTATUS ConsoleCreateScreenBuffer(std::unique_ptr& handle, _In_ PCONSOLE_API_MSG Message, _In_ PCD_CREATE_OBJECT_INFORMATION Information, 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..b04a9837b27 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -5,21 +5,17 @@ #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 "../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,18 +363,28 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept WI_ClearFlag(gci.Flags, CONSOLE_USE_PRIVATE_FLAGS); } - if (gci.IsInVtIoMode()) + if (const auto io = gci.GetVtIo(nullptr)) { + 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)); + const auto cork = io->Cork(); + + if (WI_IsFlagSet(diff, ENABLE_MOUSE_INPUT)) + { + char buf[] = "\x1b[?1003;1006h"; // Any Event Mouse + SGR Extended Mode + buf[std::size(buf) - 2] = WI_IsFlagSet(mode, ENABLE_MOUSE_INPUT) ? 'h' : 'l'; + io->WriteUTF8(buf); + } } } @@ -416,7 +422,6 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept { try { - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); @@ -426,6 +431,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 +443,33 @@ 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 (const auto io = gci.GetVtIo(&context)) + { + if (WI_IsFlagSet(diff, ENABLE_WRAP_AT_EOL_OUTPUT)) + { + char buf[] = "\x1b[?7h"; // Autowrap Mode (DECAWM) + buf[std::size(buf) - 2] = WI_IsFlagSet(dwNewMode, ENABLE_WRAP_AT_EOL_OUTPUT) ? 'h' : 'l'; + io->WriteUTF8(buf); + } + + if (WI_IsFlagSet(diff, DISABLE_NEWLINE_AUTO_RETURN)) + { + char buf[] = "\x1b[20h"; // Line Feed / New Line Mode (LNM) + buf[std::size(buf) - 2] = WI_IsFlagClear(mode, DISABLE_NEWLINE_AUTO_RETURN) ? 'h' : 'l'; + io->WriteUTF8(buf); + } + } + return S_OK; } CATCH_RETURN(); @@ -466,6 +486,58 @@ void ApiRoutines::SetConsoleActiveScreenBufferImpl(SCREEN_INFORMATION& newContex LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + if (const auto io = gci.GetVtIo(nullptr)) + { + const auto cork = io->Cork(); + + 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); + } + + io->WriteUTF8("\x1b[?1049l"); + + til::small_vector infos; + infos.resize(area, CHAR_INFO{ L' ', FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED }); + + Viewport read; + THROW_IF_FAILED(ReadConsoleOutputWImpl(main, infos, viewport, read)); + for (til::CoordType i = 0; i < size.height; i++) + { + io->WriteInfos({ 0, i }, { infos.begin() + i * size.width, static_cast(size.width) }); + } + + io->WriteCUP(main.GetTextBuffer().GetCursor().GetPosition()); + io->WriteAttributes(main.GetAttributes().GetLegacyAttributes()); + + if (hasAltBuffer) + { + io->WriteUTF8("\x1b[?1049h"); + + THROW_IF_FAILED(ReadConsoleOutputWImpl(alt, infos, viewport, read)); + for (til::CoordType i = 0; i < size.height; i++) + { + io->WriteInfos({ 0, i }, { infos.begin() + i * size.width, static_cast(size.width) }); + } + + io->WriteCUP(alt.GetTextBuffer().GetCursor().GetPosition()); + io->WriteAttributes(alt.GetAttributes().GetLegacyAttributes()); + } + } + SetActiveScreenBuffer(newContext.GetActiveBuffer()); } CATCH_LOG(); @@ -565,6 +637,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 +695,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 +755,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 +789,10 @@ 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 (const auto io = gci.GetVtIo(&context)) + { + io->WriteFormat(FMT_COMPILE("\x1b[{};{}H"), position.y + 1, position.x + 1); + } RETURN_IF_NTSTATUS_FAILED(buffer.SetCursorPosition(position, true)); @@ -789,6 +868,14 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont context.SetCursorInformation(size, isVisible); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + if (const auto io = gci.GetVtIo(&context)) + { + char buf[] = "\x1b[?25l"; + buf[std::size(buf) - 2] = isVisible ? 'h' : 'l'; + io->WriteUTF8(buf); + } + return S_OK; } CATCH_RETURN(); @@ -835,7 +922,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont // if we're headless, not so much. However, GetMaxWindowSizeInCharacters // will only return the buffer size, so we can't use that to clip the arg here. // So only clip the requested size if we're not headless - if (g.getConsoleInformation().IsInVtIoMode()) + if (g.getConsoleInformation().GetVtIo(nullptr)) { // SetViewportRect doesn't cause the buffer to resize. Manually resize the buffer. RETURN_IF_NTSTATUS_FAILED(context.ResizeScreenBuffer(Viewport::FromInclusive(Window).Dimensions(), false)); @@ -854,14 +941,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 +993,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 +1002,76 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont LockConsole(); auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); - auto& buffer = context.GetActiveBuffer(); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + if (const auto io = gci.GetVtIo(&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()) + { + io->WriteUTF8("\033c"); + 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 corkLock = io->Cork(); + + 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 }); + + RETURN_IF_FAILED(ReadConsoleOutputWImpl(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)); + + if (io && io->BufferHasContent()) + { + io->WriteCUP(context.GetTextBuffer().GetCursor().GetPosition()); + io->WriteAttributes(context.GetAttributes().GetLegacyAttributes()); } } + else + { + auto& buffer = context.GetActiveBuffer(); + TextAttribute useThisAttr(fillAttribute); + ScrollRegion(buffer, source, clip, target, fillCharacter, useThisAttr); + } - return hr; + return S_OK; } CATCH_RETURN(); } @@ -984,6 +1096,12 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont const TextAttribute attr{ attribute }; context.SetAttributes(attr); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + if (const auto io = gci.GetVtIo(&context)) + { + io->WriteAttributes(attribute); + } + return S_OK; } CATCH_RETURN(); @@ -1103,7 +1221,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,7 +1232,8 @@ 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. - if (gci.IsInVtIoMode()) + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + if (gci.GetVtIo(nullptr)) { hwnd = ServiceLocator::LocatePseudoWindow(); } @@ -1516,6 +1634,14 @@ 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 (const auto io = gci.GetVtIo(nullptr)) + { + const auto title8 = til::u16u8(title); + io->WriteFormat(FMT_COMPILE("\x1b]0;{}\x7"), title8); + } + 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/output.cpp b/src/host/output.cpp index e57412f5310..f368bb12c79 100644 --- a/src/host/output.cpp +++ b/src/host/output.cpp @@ -316,8 +316,8 @@ void ScrollRegion(SCREEN_INFORMATION& screenInfo, const til::inclusive_rect scrollRectGiven, const std::optional clipRectGiven, const til::point destinationOriginGiven, - const wchar_t fillCharGiven, - const TextAttribute fillAttrsGiven) + wchar_t fillCharGiven, + TextAttribute fillAttrsGiven) { // ------ 1. PREP SOURCE ------ // Set up the source viewport. @@ -357,17 +357,18 @@ void ScrollRegion(SCREEN_INFORMATION& screenInfo, return; } - // Determine the cell we will use to fill in any revealed/uncovered space. - // We generally use exactly what was given to us. - OutputCellIterator fillData(fillCharGiven, fillAttrsGiven); - // 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 (fillCharGiven == UNICODE_NULL && fillAttrsGiven == TextAttribute{ 0 }) { - fillData = OutputCellIterator(UNICODE_SPACE, screenInfo.GetAttributes()); + fillCharGiven = UNICODE_SPACE; + fillAttrsGiven = screenInfo.GetAttributes(); } + // Determine the cell we will use to fill in any revealed/uncovered space. + // We generally use exactly what was given to us. + OutputCellIterator fillData(fillCharGiven, fillAttrsGiven); + // ------ 4. PREP TARGET ------ // Now it's time to think about the target. We're only given the origin of the target // because it is assumed that it will have the same relative dimensions as the original source. @@ -459,7 +460,7 @@ void SetActiveScreenBuffer(SCREEN_INFORMATION& screenInfo) // mode, then the cursor will remain off until they print text. This can // lead to alignment problems in the terminal, because we won't move the // terminal's cursor in this _exact_ scenario. - screenInfo.GetTextBuffer().GetCursor().SetIsOn(gci.IsInVtIoMode()); + screenInfo.GetTextBuffer().GetCursor().SetIsOn(gci.GetVtIo(nullptr)); // set font screenInfo.RefreshFontWithRenderer(); diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index 59b81b40e1d..4ed82977fb8 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -202,8 +202,8 @@ CursorType ConhostInternalGetSet::GetUserDefaultCursorStyle() const // - void ConhostInternalGetSet::ShowWindow(bool showOrHide) { - const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto hwnd = gci.IsInVtIoMode() ? ServiceLocator::LocatePseudoWindow() : ServiceLocator::LocateConsoleWindow()->GetWindowHandle(); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + const auto hwnd = gci.GetVtIo(nullptr) ? ServiceLocator::LocatePseudoWindow() : ServiceLocator::LocateConsoleWindow()->GetWindowHandle(); // GH#13301 - When we send this ShowWindow message, if we send it to the // conhost HWND, it's going to need to get processed by the window message @@ -361,7 +361,7 @@ bool ConhostInternalGetSet::ResizeWindow(const til::CoordType sColumns, const ti // - true if we're in pty mode. bool ConhostInternalGetSet::IsConsolePty() const { - return ServiceLocator::LocateGlobals().getConsoleInformation().IsInVtIoMode(); + return ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo(nullptr); } // Routine Description: diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index 6a823b3b39f..6e7b146fd1a 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -1208,8 +1208,8 @@ void SCREEN_INFORMATION::_InternalSetViewportSize(const til::size* const pcoordS // 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. - const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (gci.IsInVtIoMode() && ServiceLocator::LocateGlobals().pRender) + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + if (gci.GetVtIo(nullptr) && ServiceLocator::LocateGlobals().pRender) { ServiceLocator::LocateGlobals().pRender->TriggerScroll(); } @@ -1902,15 +1902,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 @@ -1938,15 +1929,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 @@ -1995,8 +1977,8 @@ bool SCREEN_INFORMATION::_IsAltBuffer() const // - true iff this buffer has a main buffer. bool SCREEN_INFORMATION::_IsInPtyMode() const { - const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - return _IsAltBuffer() || gci.IsInVtIoMode(); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + return _IsAltBuffer() || gci.GetVtIo(nullptr); } // Routine Description: @@ -2085,8 +2067,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(); @@ -2102,10 +2082,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(); - } // If we're an alt buffer, also update our main buffer. if (_psiMainBuffer) @@ -2181,52 +2158,14 @@ void SCREEN_INFORMATION::SetViewport(const Viewport& newViewport, // - S_OK [[nodiscard]] HRESULT SCREEN_INFORMATION::ClearBuffer() { - // Rotate the buffer to bring the cursor row to the top of the viewport. - const auto cursorPos = _textBuffer->GetCursor().GetPosition(); - for (auto i = 0; i < cursorPos.y; i++) - { - _textBuffer->IncrementCircularBuffer(); - } - - // Erase everything below that point. - RETURN_IF_FAILED(SetCursorPosition({ 0, 1 }, false)); - auto& engine = reinterpret_cast(_stateMachine->Engine()); - engine.Dispatch().EraseInDisplay(DispatchTypes::EraseType::ToEnd); - + _textBuffer->Reset(); // Restore the original cursor x offset, but now on the first row. - RETURN_IF_FAILED(SetCursorPosition({ cursorPos.x, 0 }, false)); - + _textBuffer->GetCursor().SetYPosition(0); _textBuffer->TriggerRedrawAll(); 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 035529e0d6e..686c9beb8d4 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: @@ -212,8 +204,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; @@ -225,6 +215,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, @@ -248,9 +241,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(); @@ -296,7 +286,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..0c5fa849533 100644 --- a/src/host/server.h +++ b/src/host/server.h @@ -103,7 +103,8 @@ class CONSOLE_INFORMATION : bool IsConsoleLocked() const noexcept; ULONG GetCSRecursionCount() const noexcept; - Microsoft::Console::VirtualTerminal::VtIo* GetVtIo(); + Microsoft::Console::VirtualTerminal::VtIo* GetVtIoNoCheck(); + Microsoft::Console::VirtualTerminal::VtIo* GetVtIo(const SCREEN_INFORMATION* context); SCREEN_INFORMATION& GetActiveOutputBuffer() override; const SCREEN_INFORMATION& GetActiveOutputBuffer() const override; @@ -112,7 +113,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/settings.cpp b/src/host/settings.cpp index e0b07e38f75..5c89099121a 100644 --- a/src/host/settings.cpp +++ b/src/host/settings.cpp @@ -181,7 +181,7 @@ void Settings::ApplyCommandlineArguments(const ConsoleArguments& consoleArgs) _dwScreenBufferSize.Y = height; _dwWindowSize.Y = height; } - else if (ServiceLocator::LocateGlobals().getConsoleInformation().IsInVtIoMode()) + else if (ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo(nullptr)) { // If we're a PTY but we weren't explicitly told a size, use the window size as the buffer size. _dwScreenBufferSize = _dwWindowSize; diff --git a/src/host/srvinit.cpp b/src/host/srvinit.cpp index a29c31ad6fb..5fbdb32c8e1 100644 --- a/src/host/srvinit.cpp +++ b/src/host/srvinit.cpp @@ -380,8 +380,8 @@ HRESULT ConsoleCreateIoThread(_In_ HANDLE Server, // The conpty i/o threads need an actual client to be connected before they // can start, so they're started below, in ConsoleAllocateConsole auto& gci = g.getConsoleInformation(); - RETURN_IF_FAILED(gci.GetVtIo()->Initialize(args)); - RETURN_IF_FAILED(gci.GetVtIo()->CreateAndStartSignalThread()); + RETURN_IF_FAILED(gci.GetVtIoNoCheck()->Initialize(args)); + RETURN_IF_FAILED(gci.GetVtIoNoCheck()->CreateAndStartSignalThread()); return S_OK; } @@ -585,7 +585,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()); @@ -852,24 +852,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.GetVtIo(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); + }); + } } catch (...) { @@ -887,7 +888,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)) { @@ -952,7 +956,7 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand, // We'll need the size of the screen buffer in the vt i/o initialization if (SUCCEEDED_NTSTATUS(Status)) { - auto hr = gci.GetVtIo()->CreateIoHandlers(); + auto hr = gci.GetVtIoNoCheck()->CreateIoHandlers(); if (hr == S_FALSE) { // We're not in VT I/O mode, this is fine. @@ -960,7 +964,7 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand, else if (SUCCEEDED(hr)) { // Actually start the VT I/O threads - hr = gci.GetVtIo()->StartIfNeeded(); + hr = gci.GetVtIoNoCheck()->StartIfNeeded(); // Don't convert S_FALSE to an NTSTATUS - the equivalent NTSTATUS // is treated as an error if (hr != S_FALSE) diff --git a/src/host/ut_host/ApiRoutinesTests.cpp b/src/host/ut_host/ApiRoutinesTests.cpp index 611eb55f14c..e45cf3cccb9 100644 --- a/src/host/ut_host/ApiRoutinesTests.cpp +++ b/src/host/ut_host/ApiRoutinesTests.cpp @@ -54,7 +54,6 @@ class ApiRoutinesTests m_state->CleanupGlobalInputBuffer(); m_state->CleanupGlobalScreenBuffer(); - m_state->CleanupGlobalFont(); m_state.reset(nullptr); diff --git a/src/host/ut_host/ClipboardTests.cpp b/src/host/ut_host/ClipboardTests.cpp index 30b45179095..e7f00c403bc 100644 --- a/src/host/ut_host/ClipboardTests.cpp +++ b/src/host/ut_host/ClipboardTests.cpp @@ -49,7 +49,6 @@ class ClipboardTests { m_state->CleanupGlobalInputBuffer(); m_state->CleanupGlobalScreenBuffer(); - m_state->CleanupGlobalFont(); return true; } diff --git a/src/host/ut_host/ConptyOutputTests.cpp b/src/host/ut_host/ConptyOutputTests.cpp deleted file mode 100644 index 68ea772e4b3..00000000000 --- a/src/host/ut_host/ConptyOutputTests.cpp +++ /dev/null @@ -1,467 +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->PrepareGlobalFont(); - m_state->PrepareGlobalInputBuffer(); - m_state->PrepareGlobalScreenBuffer(TerminalViewWidth, TerminalViewHeight, TerminalViewWidth, TerminalViewHeight); - - return true; - } - - TEST_CLASS_CLEANUP(ClassCleanup) - { - m_state->CleanupGlobalScreenBuffer(); - m_state->CleanupGlobalFont(); - 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/ConsoleArgumentsTests.cpp b/src/host/ut_host/ConsoleArgumentsTests.cpp index d640469f236..a9148dfd2e5 100644 --- a/src/host/ut_host/ConsoleArgumentsTests.cpp +++ b/src/host/ut_host/ConsoleArgumentsTests.cpp @@ -73,7 +73,6 @@ void ConsoleArgumentsTests::ArgSplittingTests() L"this is the commandline", // clientCommandLine, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -95,7 +94,6 @@ void ConsoleArgumentsTests::ArgSplittingTests() L"\"this is the commandline\"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -117,7 +115,6 @@ void ConsoleArgumentsTests::ArgSplittingTests() L"\"--vtmode bar this is the commandline\"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -139,7 +136,6 @@ void ConsoleArgumentsTests::ArgSplittingTests() L"this is the commandline", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -152,7 +148,7 @@ void ConsoleArgumentsTests::ArgSplittingTests() false), // runAsComServer true); // successful parse? - commandline = L"conhost.exe --headless\t--vtmode\txterm\tthis\tis\tthe\tcommandline"; + commandline = L"conhost.exe --headless\tthis\tis\tthe\tcommandline"; ArgTestsRunner(L"#5\ttab\tdelimit", commandline, INVALID_HANDLE_VALUE, @@ -161,7 +157,6 @@ void ConsoleArgumentsTests::ArgSplittingTests() L"this is the commandline", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"xterm", // vtMode 0, // width 0, // height false, // forceV1 @@ -183,7 +178,6 @@ void ConsoleArgumentsTests::ArgSplittingTests() L"--headless\\ foo\\ --outpipe\\ bar\\ this\\ is\\ the\\ commandline", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -205,29 +199,6 @@ void ConsoleArgumentsTests::ArgSplittingTests() L"--headless\\ foo\\ --outpipe\\ bar\\ this\\ is\\ the\\ commandline", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode - 0, // width - 0, // height - false, // forceV1 - false, // forceNoHandoff - false, // headless - true, // createServerHandle - 0, // serverHandle - 0, // signalHandle - false, // inheritCursor - false), // runAsComServer - true); // successful parse? - - commandline = L"conhost.exe --vtmode a\\\\\\\\\"b c\" d e"; - ArgTestsRunner(L"#8 Combo of backslashes and quotes from msdn", - commandline, - INVALID_HANDLE_VALUE, - INVALID_HANDLE_VALUE, - ConsoleArguments(commandline, - L"d e", // clientCommandLine - INVALID_HANDLE_VALUE, - INVALID_HANDLE_VALUE, - L"a\\\\b c", // vtMode 0, // width 0, // height false, // forceV1 @@ -249,7 +220,6 @@ void ConsoleArgumentsTests::ArgSplittingTests() L"this is the commandline", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -276,7 +246,6 @@ void ConsoleArgumentsTests::ClientCommandlineTests() L"foo", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -298,7 +267,6 @@ void ConsoleArgumentsTests::ClientCommandlineTests() L"foo", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -320,7 +288,6 @@ void ConsoleArgumentsTests::ClientCommandlineTests() L"foo -- bar", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -333,7 +300,7 @@ void ConsoleArgumentsTests::ClientCommandlineTests() false), // runAsComServer true); // successful parse? - commandline = L"conhost.exe --vtmode foo foo -- bar"; + commandline = L"conhost.exe foo -- bar"; ArgTestsRunner(L"#4 Check that a implicit commandline with other expected args is treated as a whole client commandline (2)", commandline, INVALID_HANDLE_VALUE, @@ -342,7 +309,6 @@ void ConsoleArgumentsTests::ClientCommandlineTests() L"foo -- bar", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"foo", // vtMode 0, // width 0, // height false, // forceV1 @@ -364,7 +330,6 @@ void ConsoleArgumentsTests::ClientCommandlineTests() L"console --vtmode foo foo -- bar", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -386,7 +351,6 @@ void ConsoleArgumentsTests::ClientCommandlineTests() L"console --vtmode foo --outpipe foo -- bar", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -399,7 +363,7 @@ void ConsoleArgumentsTests::ClientCommandlineTests() false), // runAsComServer true); // successful parse? - commandline = L"conhost.exe --vtmode foo -- --outpipe foo bar"; + commandline = L"conhost.exe -- --outpipe foo bar"; ArgTestsRunner(L"#7 Check splitting vt pipes across the explicit commandline does not pull both pipe names out", commandline, INVALID_HANDLE_VALUE, @@ -408,7 +372,6 @@ void ConsoleArgumentsTests::ClientCommandlineTests() L"--outpipe foo bar", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"foo", // vtMode 0, // width 0, // height false, // forceV1 @@ -421,28 +384,6 @@ void ConsoleArgumentsTests::ClientCommandlineTests() false), // runAsComServer true); // successful parse? - commandline = L"conhost.exe --vtmode -- --headless bar"; - ArgTestsRunner(L"#8 Let -- be used as a value of a parameter", - commandline, - INVALID_HANDLE_VALUE, - INVALID_HANDLE_VALUE, - ConsoleArguments(commandline, - L"bar", // clientCommandLine - INVALID_HANDLE_VALUE, - INVALID_HANDLE_VALUE, - L"--", // vtMode - 0, // width - 0, // height - false, // forceV1 - false, // forceNoHandoff - true, // headless - true, // createServerHandle - 0, // serverHandle - 0, // signalHandle - false, // inheritCursor - false), // runAsComServer - true); // successful parse? - commandline = L"conhost.exe --"; ArgTestsRunner(L"#9 -- by itself does nothing successfully", commandline, @@ -452,7 +393,6 @@ void ConsoleArgumentsTests::ClientCommandlineTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -474,7 +414,6 @@ void ConsoleArgumentsTests::ClientCommandlineTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -501,7 +440,6 @@ void ConsoleArgumentsTests::LegacyFormatsTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -523,7 +461,6 @@ void ConsoleArgumentsTests::LegacyFormatsTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -545,7 +482,6 @@ void ConsoleArgumentsTests::LegacyFormatsTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -567,7 +503,6 @@ void ConsoleArgumentsTests::LegacyFormatsTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -589,7 +524,6 @@ void ConsoleArgumentsTests::LegacyFormatsTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -611,7 +545,6 @@ void ConsoleArgumentsTests::LegacyFormatsTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -633,7 +566,6 @@ void ConsoleArgumentsTests::LegacyFormatsTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height true, // forceV1 @@ -655,7 +587,6 @@ void ConsoleArgumentsTests::LegacyFormatsTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height true, // forceV1 @@ -677,7 +608,6 @@ void ConsoleArgumentsTests::LegacyFormatsTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -699,7 +629,6 @@ void ConsoleArgumentsTests::LegacyFormatsTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -750,29 +679,6 @@ void ConsoleArgumentsTests::CombineVtPipeHandleTests() L"", // clientCommandLine hInSample, hOutSample, - L"", // vtMode - 0, // width - 0, // height - false, // forceV1 - false, // forceNoHandoff - false, // headless - true, // createServerHandle - 0ul, // serverHandle - 0, // signalHandle - false, // inheritCursor - false), // runAsComServer - true); // successful parse? - - commandline = L"conhost.exe --vtmode xterm-256color"; - ArgTestsRunner(L"#2 Check that handles with mode is OK", - commandline, - hInSample, - hOutSample, - ConsoleArguments(commandline, - L"", // clientCommandLine - hInSample, - hOutSample, - L"xterm-256color", // vtMode 0, // width 0, // height false, // forceV1 @@ -809,7 +715,6 @@ void ConsoleArgumentsTests::InitialSizeTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 120, // width 30, // height false, // forceV1 @@ -831,7 +736,6 @@ void ConsoleArgumentsTests::InitialSizeTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 120, // width 0, // height false, // forceV1 @@ -853,7 +757,6 @@ void ConsoleArgumentsTests::InitialSizeTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 30, // height false, // forceV1 @@ -875,7 +778,6 @@ void ConsoleArgumentsTests::InitialSizeTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -897,7 +799,6 @@ void ConsoleArgumentsTests::InitialSizeTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode -1, // width 0, // height false, // forceV1 @@ -919,7 +820,6 @@ void ConsoleArgumentsTests::InitialSizeTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -941,7 +841,6 @@ void ConsoleArgumentsTests::InitialSizeTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -963,7 +862,6 @@ void ConsoleArgumentsTests::InitialSizeTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -990,7 +888,6 @@ void ConsoleArgumentsTests::HeadlessArgTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -1012,7 +909,6 @@ void ConsoleArgumentsTests::HeadlessArgTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -1034,7 +930,6 @@ void ConsoleArgumentsTests::HeadlessArgTests() L"", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -1056,7 +951,6 @@ void ConsoleArgumentsTests::HeadlessArgTests() L"foo.exe --headless", // clientCommandLine INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -1087,7 +981,6 @@ void ConsoleArgumentsTests::SignalHandleTests() L"", hInSample, hOutSample, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -1109,7 +1002,6 @@ void ConsoleArgumentsTests::SignalHandleTests() L"", hInSample, hOutSample, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -1131,7 +1023,6 @@ void ConsoleArgumentsTests::SignalHandleTests() L"", hInSample, hOutSample, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -1162,7 +1053,6 @@ void ConsoleArgumentsTests::FeatureArgTests() L"", hInSample, hOutSample, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -1183,7 +1073,6 @@ void ConsoleArgumentsTests::FeatureArgTests() L"", hInSample, hOutSample, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -1205,7 +1094,6 @@ void ConsoleArgumentsTests::FeatureArgTests() L"", hInSample, hOutSample, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -1227,7 +1115,6 @@ void ConsoleArgumentsTests::FeatureArgTests() L"", hInSample, hOutSample, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -1249,7 +1136,6 @@ void ConsoleArgumentsTests::FeatureArgTests() L"", hInSample, hOutSample, - L"", // vtMode 0, // width 0, // height false, // forceV1 @@ -1271,7 +1157,6 @@ void ConsoleArgumentsTests::FeatureArgTests() L"", hInSample, hOutSample, - L"", // vtMode 0, // width 0, // height false, // forceV1 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/ObjectTests.cpp b/src/host/ut_host/ObjectTests.cpp index 9069149e6ff..89f200fd2eb 100644 --- a/src/host/ut_host/ObjectTests.cpp +++ b/src/host/ut_host/ObjectTests.cpp @@ -33,7 +33,6 @@ class ObjectTests TEST_CLASS_CLEANUP(ClassCleanup) { m_state->CleanupGlobalScreenBuffer(); - m_state->CleanupGlobalFont(); m_state->CleanupGlobalInputBuffer(); delete m_state; diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index 4016152a9af..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" @@ -55,7 +54,6 @@ class ScreenBufferTests { m_state->CleanupGlobalScreenBuffer(); m_state->CleanupGlobalRenderer(); - m_state->CleanupGlobalFont(); m_state->CleanupGlobalInputBuffer(); delete m_state; @@ -7932,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 @@ -7947,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/SearchTests.cpp b/src/host/ut_host/SearchTests.cpp index e81231f9082..2a2229a2e48 100644 --- a/src/host/ut_host/SearchTests.cpp +++ b/src/host/ut_host/SearchTests.cpp @@ -35,7 +35,6 @@ class SearchTests { m_state->CleanupGlobalScreenBuffer(); m_state->CleanupGlobalRenderer(); - m_state->CleanupGlobalFont(); delete m_state; diff --git a/src/host/ut_host/SelectionTests.cpp b/src/host/ut_host/SelectionTests.cpp index f33889992bb..aeefd4bf288 100644 --- a/src/host/ut_host/SelectionTests.cpp +++ b/src/host/ut_host/SelectionTests.cpp @@ -41,7 +41,6 @@ class SelectionTests m_pSelection = nullptr; m_state->CleanupGlobalScreenBuffer(); - m_state->CleanupGlobalFont(); delete m_state; @@ -373,7 +372,6 @@ class SelectionInputTests CommandHistory::s_Free(nullptr); m_pHistory = nullptr; m_state->CleanupGlobalScreenBuffer(); - m_state->CleanupGlobalFont(); m_state->CleanupGlobalInputBuffer(); delete m_state; diff --git a/src/host/ut_host/TextBufferIteratorTests.cpp b/src/host/ut_host/TextBufferIteratorTests.cpp index 52026b71e0a..8fd2b9c82e7 100644 --- a/src/host/ut_host/TextBufferIteratorTests.cpp +++ b/src/host/ut_host/TextBufferIteratorTests.cpp @@ -89,7 +89,6 @@ class TextBufferIteratorTests TEST_CLASS_CLEANUP(ClassCleanup) { m_state->CleanupGlobalScreenBuffer(); - m_state->CleanupGlobalFont(); delete m_state; diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp index 7b963fbd91a..d79cba040ee 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -46,7 +46,6 @@ class TextBufferTests TEST_CLASS_CLEANUP(ClassCleanup) { m_state->CleanupGlobalScreenBuffer(); - m_state->CleanupGlobalFont(); delete m_state; diff --git a/src/host/ut_host/UtilsTests.cpp b/src/host/ut_host/UtilsTests.cpp index ad889a291c7..d3c057e4056 100644 --- a/src/host/ut_host/UtilsTests.cpp +++ b/src/host/ut_host/UtilsTests.cpp @@ -42,7 +42,6 @@ class UtilsTests TEST_CLASS_CLEANUP(ClassCleanup) { m_state->CleanupGlobalScreenBuffer(); - m_state->CleanupGlobalFont(); delete m_state; diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index 4644a4deb5a..49cd23110a6 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -2,452 +2,534 @@ // Licensed under the MIT license. #include "precomp.h" -#include -#include "../../inc/consoletaeftemplates.hpp" -#include "../../types/inc/Viewport.hpp" +#include + +#include "CommonState.hpp" +#include "directio.h" #include "../VtIo.hpp" #include "../../interactivity/inc/ServiceLocator.hpp" #include "../../renderer/base/Renderer.hpp" -#include "../../renderer/vt/Xterm256Engine.hpp" -#include "../../renderer/vt/XtermEngine.hpp" +#include "../../types/inc/Viewport.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(ModeParsingTest); - - 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() +constexpr CHAR_INFO red(wchar_t ch) noexcept { - 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()); + return { ch, FOREGROUND_RED }; } -void VtIoTests::ModeParsingTest() +constexpr CHAR_INFO blu(wchar_t ch) noexcept { - VtIoMode mode; - VERIFY_SUCCEEDED(VtIo::ParseIoMode(L"xterm", mode)); - VERIFY_ARE_EQUAL(mode, VtIoMode::XTERM); - - VERIFY_SUCCEEDED(VtIo::ParseIoMode(L"xterm-256color", mode)); - VERIFY_ARE_EQUAL(mode, VtIoMode::XTERM_256); - - VERIFY_SUCCEEDED(VtIo::ParseIoMode(L"xterm-ascii", mode)); - VERIFY_ARE_EQUAL(mode, VtIoMode::XTERM_ASCII); - - VERIFY_SUCCEEDED(VtIo::ParseIoMode(L"", mode)); - VERIFY_ARE_EQUAL(mode, VtIoMode::XTERM_256); - - VERIFY_FAILED(VtIo::ParseIoMode(L"garbage", mode)); - VERIFY_ARE_EQUAL(mode, VtIoMode::INVALID); + return { ch, FOREGROUND_BLUE }; } -Viewport SetUpViewport() -{ - til::inclusive_rect view; - view.top = view.left = 0; - view.bottom = 31; - view.right = 79; - - return Viewport::FromInclusive(view); -} +#pragma warning(disable : 4505) -void VtIoTests::DtorTestJustEngine() +static std::pair createOverlappedPipe(DWORD bufferSize) { - 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.")); - } + const auto rnd = til::gen_random(); + const auto name = fmt::format(FMT_COMPILE(LR"(\\.\pipe\{:016x})"), rnd); + wil::unique_hfile rx{ CreateNamedPipeW(name.c_str(), PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_WAIT, 1, bufferSize, bufferSize, 0, nullptr) }; + THROW_LAST_ERROR_IF(!rx); + wil::unique_hfile tx{ CreateFileW(name.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, nullptr) }; + THROW_LAST_ERROR_IF(!tx); + return { std::move(tx), std::move(rx) }; } -void VtIoTests::DtorTestDeleteVtio() -{ - 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.")); - } -} - -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" +#define decawm(h) "\x1b[?7" #h +#define lnm(h) "\x1b[20" #h + +// The escape sequences that red() / blu() result in. +#define sgr_red(s) "\x1b[27;31;40m" s +#define sgr_blu(s) "\x1b[27;34;40m" s +// What the default attributes `FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED` result in. +#define sgr_rst() "\x1b[27;39;49m" + +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 {}; - } + BEGIN_TEST_CLASS(VtIoTests) + TEST_CLASS_PROPERTY(L"IsolationLevel", L"Class") + //TEST_CLASS_PROPERTY(L"TestTimeout", L"0:1:0") // 1min + END_TEST_CLASS() - TextBuffer& GetTextBuffer() const noexcept override - { - FAIL_FAST_HR(E_NOTIMPL); - } + CommonState commonState; + ApiRoutines routines; + SCREEN_INFORMATION* screenInfo = nullptr; + wil::unique_hfile rx; + char rxBuf[4096]; - const FontInfo& GetFontInfo() const noexcept override + std::string_view readOutput() noexcept { - FAIL_FAST_HR(E_NOTIMPL); + DWORD read = 0; + ReadFile(rx.get(), &rxBuf[0], sizeof(rxBuf), &read, nullptr); + return { &rxBuf[0], read }; } - std::vector GetSelectionRects() noexcept override + void setupInitialContents() const { - return std::vector{}; + auto& sm = screenInfo->GetStateMachine(); + sm.ProcessString(L"\033c"); + sm.ProcessString(s_initialContentVT); + sm.ProcessString(L"\x1b[H" sgr_rst()); } - void LockConsole() noexcept override + void resetContents() const { + auto& sm = screenInfo->GetStateMachine(); + sm.ProcessString(L"\033c"); } - void UnlockConsole() noexcept override + TEST_CLASS_SETUP(ClassSetup) { - } + wil::unique_hfile tx; + //std::tie(tx, rx) = createOverlappedPipe(16 * 1024); + THROW_IF_WIN32_BOOL_FALSE(CreatePipe(rx.addressof(), tx.addressof(), nullptr, 16 * 1024)); - std::pair GetAttributeColors(const TextAttribute& /*attr*/) const noexcept override - { - return std::make_pair(COLORREF{}, COLORREF{}); - } + DWORD mode = PIPE_READMODE_BYTE | PIPE_NOWAIT; + THROW_IF_WIN32_BOOL_FALSE(SetNamedPipeHandleState(rx.get(), &mode, nullptr, nullptr)); - til::point GetCursorPosition() const noexcept override - { - return {}; - } + commonState.PrepareGlobalInputBuffer(); + commonState.PrepareGlobalScreenBuffer(8, 4, 8, 4); - bool IsCursorVisible() const noexcept override - { - return false; - } + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + THROW_IF_FAILED(gci.GetVtIoNoCheck()->_Initialize(nullptr, tx.release(), nullptr)); - bool IsCursorOn() const noexcept override - { - return false; - } - - ULONG GetCursorHeight() const noexcept override - { - return 42ul; + screenInfo = &gci.GetActiveOutputBuffer(); + return true; } - CursorType GetCursorStyle() const noexcept override + TEST_METHOD(SetConsoleCursorPosition) { - return CursorType::FullBox; - } + 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 })); - ULONG GetCursorPixelWidth() const noexcept override - { - return 12ul; + const auto expected = cup(4, 3) cup(1, 1) cup(4, 8) cup(3, 4); + const auto actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); } - bool IsCursorDoubleWidth() const override + TEST_METHOD(SetConsoleOutputMode) { - return false; - } + const auto initialMode = screenInfo->OutputMode; + const auto cleanup = wil::scope_exit([=]() { + screenInfo->OutputMode = initialMode; + }); - const bool IsGridLineDrawingAllowed() noexcept override - { - return false; - } + screenInfo->OutputMode = 0; - const std::wstring_view GetConsoleTitle() const noexcept override - { - return std::wstring_view{}; - } + 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 ✔️ LNM ✔️ + THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfo, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)); // DECAWM ✔️ LNM ✖️ + THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfo, ENABLE_PROCESSED_OUTPUT | DISABLE_NEWLINE_AUTO_RETURN | ENABLE_LVB_GRID_WORLDWIDE)); // DECAWM ✖️ LNM ✔️ + THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfo, 0)); // DECAWM ✖️ LNM ✖️ + THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfo, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | DISABLE_NEWLINE_AUTO_RETURN | ENABLE_LVB_GRID_WORLDWIDE)); // DECAWM ✔️ LNM ✖️ + 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 ✔️ LNM ✔️ - const bool IsSelectionActive() const override - { - return false; + const auto expected = + decawm(h) lnm(l) // DECAWM ✔️ LNM ✔️ + lnm(h) // DECAWM ✔️ LNM ✖️ + decawm(l) lnm(l) // DECAWM ✖️ LNM ✔️ + lnm(h) // DECAWM ✖️ LNM ✖️ + decawm(h) // DECAWM ✔️ LNM ✖️ + lnm(l); // DECAWM ✔️ LNM ✔️ + const auto actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); } - const bool IsBlockSelection() const noexcept override + TEST_METHOD(SetConsoleTitleW) { - return false; - } + THROW_IF_FAILED(routines.SetConsoleTitleWImpl(L"foobar")); - void ClearSelection() override - { + const auto expected = "\x1b]0;foobar\a"; + const auto actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); } - void SelectNewRegion(const til::point /*coordStart*/, const til::point /*coordEnd*/) override + TEST_METHOD(SetConsoleCursorInfo) { - } + THROW_IF_FAILED(routines.SetConsoleCursorInfoImpl(*screenInfo, 25, false)); + THROW_IF_FAILED(routines.SetConsoleCursorInfoImpl(*screenInfo, 25, true)); - std::span GetSearchHighlights() const noexcept override - { - return {}; + const auto expected = "\x1b[?25l" + "\x1b[?25h"; + const auto actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); } - const til::point_span* GetSearchHighlightFocused() const noexcept override + TEST_METHOD(SetConsoleTextAttribute) { - return nullptr; - } - - const til::point GetSelectionAnchor() const noexcept - { - return {}; - } - - const til::point GetSelectionEnd() const noexcept - { - return {}; - } - - const bool IsUiaDataInitialized() const noexcept - { - return true; - } - - const std::wstring GetHyperlinkUri(uint16_t /*id*/) const - { - return {}; - } + for (WORD i = 0; i < 16; i++) + { + THROW_IF_FAILED(routines.SetConsoleTextAttributeImpl(*screenInfo, i)); + } - const std::wstring GetHyperlinkCustomId(uint16_t /*id*/) const - { - return {}; - } + for (WORD i = 0; i < 16; i++) + { + THROW_IF_FAILED(routines.SetConsoleTextAttributeImpl(*screenInfo, i << 4)); + } - 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[27;30;40m" + "\x1b[27;34;40m" + "\x1b[27;32;40m" + "\x1b[27;36;40m" + "\x1b[27;31;40m" + "\x1b[27;35;40m" + "\x1b[27;33;40m" + "\x1b[27;39;49m" // <-- FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED gets translated to the default colors + "\x1b[27;90;40m" + "\x1b[27;94;40m" + "\x1b[27;92;40m" + "\x1b[27;96;40m" + "\x1b[27;91;40m" + "\x1b[27;95;40m" + "\x1b[27;93;40m" + "\x1b[27;97;40m" + // 16 background colors + "\x1b[27;30;40m" + "\x1b[27;30;44m" + "\x1b[27;30;42m" + "\x1b[27;30;46m" + "\x1b[27;30;41m" + "\x1b[27;30;45m" + "\x1b[27;30;43m" + "\x1b[27;30;47m" + "\x1b[27;30;100m" + "\x1b[27;30;104m" + "\x1b[27;30;102m" + "\x1b[27;30;106m" + "\x1b[27;30;101m" + "\x1b[27;30;105m" + "\x1b[27;30;103m" + "\x1b[27;30;107m" + // The remaining two calls + "\x1b[7;95;42m" + "\x1b[7;39;49m"; + const auto actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(WriteConsoleW) + { + resetContents(); + + size_t written; + std::unique_ptr waiter; + const char* expected = nullptr; + std::string_view actual; + + THROW_IF_FAILED(routines.WriteConsoleWImpl(*screenInfo, L"", written, false, waiter)); + expected = ""; + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + THROW_IF_FAILED(routines.WriteConsoleWImpl(*screenInfo, L"aaaaaaaa", written, false, waiter)); + expected = "aaaaaaaa \r"; + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + THROW_IF_FAILED(routines.WriteConsoleWImpl(*screenInfo, L"a\t\r\nb", written, false, waiter)); + expected = "a\t \r\r\nb"; + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(WriteConsoleOutputW) + { + resetContents(); + + std::array payload{ red('a'), red('b'), blu('A'), 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 = cup(2, 2) sgr_red("ab") sgr_blu("AB") cup(1, 1) sgr_rst(); + const auto actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(WriteConsoleOutputAttribute) + { + setupInitialContents(); + + static constexpr std::array payload{ FOREGROUND_RED, FOREGROUND_BLUE, FOREGROUND_RED, FOREGROUND_BLUE }; + static constexpr til::point target{ 6, 1 }; + size_t written; + THROW_IF_FAILED(routines.WriteConsoleOutputAttributeImpl(*screenInfo, payload, target, written)); + + const auto expected = + cup(2, 7) sgr_red("g") sgr_blu("h") // + cup(3, 1) sgr_red("i") sgr_blu("j") // + cup(1, 1) sgr_rst(); + const auto actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(WriteConsoleOutputCharacterW) + { + setupInitialContents(); + + static constexpr std::wstring_view payload{ L"foobar" }; + static constexpr til::point target{ 5, 1 }; + size_t written; + THROW_IF_FAILED(routines.WriteConsoleOutputCharacterWImpl(*screenInfo, payload, target, written)); + + const auto expected = + cup(2, 6) sgr_red("f") sgr_blu("oo") // + cup(3, 1) sgr_blu("ba") sgr_red("r") // + cup(1, 1) sgr_rst(); + const auto actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(FillConsoleOutputAttribute) + { + setupInitialContents(); + + size_t cellsModified = 0; + const char* expected = nullptr; + std::string_view actual; + + // Writing nothing should produce nothing. + THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, FOREGROUND_RED, 0, {}, cellsModified)); + expected = ""; + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // Writing at the start of a line. + THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, FOREGROUND_RED, 3, { 0, 0 }, cellsModified)); + expected = cup(1, 1) sgr_red("ABa") // + cup(1, 1) sgr_rst(); + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // Writing at the end of a line. + THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, FOREGROUND_RED, 3, { 5, 0 }, cellsModified)); + expected = cup(1, 6) sgr_red("Dcd") // + cup(1, 1) sgr_rst(); + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // Writing across 2 lines. + THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, FOREGROUND_BLUE, 8, { 4, 1 }, cellsModified)); + expected = + cup(2, 5) sgr_blu("GHgh") // + cup(3, 1) sgr_blu("ijIJ") // + cup(1, 1) sgr_rst(); + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(FillConsoleOutputCharacterW) + { + setupInitialContents(); + + size_t cellsModified = 0; + const char* expected = nullptr; + 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 = cup(1, 1) sgr_red("aa") sgr_blu("a") // + cup(1, 1) sgr_rst(); + 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 = cup(1, 6) sgr_red("b") sgr_blu("bb") // + cup(1, 1) sgr_rst(); + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + // Writing across 2 lines. + THROW_IF_FAILED(routines.FillConsoleOutputCharacterWImpl(*screenInfo, L'c', 8, { 4, 1 }, cellsModified)); + expected = + cup(2, 5) sgr_red("cc") sgr_blu("cc") // + cup(3, 1) sgr_blu("cc") sgr_red("cc") // + cup(1, 1) sgr_rst(); + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(ScrollConsoleScreenBufferW) + { + const char* expected = nullptr; + 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' ', FOREGROUND_RED, false)); + expected = + cup(1, 1) sgr_red(" ") // + cup(2, 1) sgr_red(" ") // + cup(1, 1) sgr_rst(); + 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 = "\033c"; + 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', FOREGROUND_RED, false)); + expected = + 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") // + cup(1, 1) sgr_rst(); + 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', FOREGROUND_BLUE, false)); + expected = + cup(2, 2) sgr_blu("zz") // + cup(3, 2) sgr_blu("zz") // + cup(3, 7) sgr_red("E") // + cup(4, 7) sgr_blu("i") // + cup(1, 1) sgr_rst(); + 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', FOREGROUND_RED, false)); + expected = + cup(1, 8) sgr_red("Y") // + cup(2, 8) sgr_red("Y") // + cup(3, 5) sgr_blu("d") // + cup(4, 5) sgr_blu("h") // + cup(1, 1) sgr_rst(); + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + static constexpr std::array expectedContents{ { + // clang-format off + red('A'), red('Z'), red('Z'), blu('b'), red('C'), red('D'), blu('c'), red('Y'), + red('E'), blu('z'), blu('z'), blu('f'), red('G'), red('H'), blu('g'), red('Y'), + blu('i'), blu('z'), blu('z'), red('J'), blu('d'), red('B'), red('E'), red('L'), + blu('m'), blu('n'), red('M'), red('N'), blu('h'), red('F'), blu('i'), 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(); + readOutput(); + + routines.SetConsoleActiveScreenBufferImpl(*screenInfo); + + const auto expected = + "\x1b[?1049l" // + 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(); + 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(), L"", 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/VtIoModes.hpp b/src/inc/VtIoModes.hpp deleted file mode 100644 index dee75ab86a4..00000000000 --- a/src/inc/VtIoModes.hpp +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -enum class VtIoMode -{ - INVALID, - XTERM, - XTERM_256, - XTERM_ASCII -}; - -const wchar_t* const XTERM_STRING = L"xterm"; -const wchar_t* const XTERM_256_STRING = L"xterm-256color"; -const wchar_t* const XTERM_ASCII_STRING = L"xterm-ascii"; -const wchar_t* const DEFAULT_STRING = L""; diff --git a/src/inc/conpty-static.h b/src/inc/conpty-static.h index d05ab56e8d3..4e4ce7c089c 100644 --- a/src/inc/conpty-static.h +++ b/src/inc/conpty-static.h @@ -23,20 +23,6 @@ #endif #endif -// CreatePseudoConsole Flags -#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 -#define PSEUDOCONSOLE_GLYPH_WIDTH_WCSWIDTH 0x10 -#define PSEUDOCONSOLE_GLYPH_WIDTH_CONSOLE 0x18 -#endif - CONPTY_EXPORT HRESULT WINAPI ConptyCreatePseudoConsole(COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC); CONPTY_EXPORT HRESULT WINAPI ConptyCreatePseudoConsoleAsUser(HANDLE hToken, COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC); diff --git a/src/inc/test/CommonState.hpp b/src/inc/test/CommonState.hpp index 4c002700469..e79bc74d3e8 100644 --- a/src/inc/test/CommonState.hpp +++ b/src/inc/test/CommonState.hpp @@ -35,7 +35,7 @@ class CommonState CommonState() : m_heap(GetProcessHeap()), m_hrTextBufferInfo(E_FAIL), - m_pFontInfo(nullptr), + m_pFontInfo{ L"Consolas", 0, 0, { 8, 12 }, 0 }, m_backupTextBufferInfo(), m_readHandle(nullptr) { @@ -63,15 +63,7 @@ class CommonState void PrepareGlobalFont(const til::size coordFontSize = { 8, 12 }) { - m_pFontInfo = new FontInfo(L"Consolas", 0, 0, coordFontSize, 0); - } - - void CleanupGlobalFont() - { - if (m_pFontInfo != nullptr) - { - delete m_pFontInfo; - } + m_pFontInfo = { L"Consolas", 0, 0, coordFontSize, 0 }; } void PrepareGlobalRenderer() @@ -106,7 +98,7 @@ class CommonState UINT uiCursorSize = 12; THROW_IF_FAILED(SCREEN_INFORMATION::CreateInstance(coordWindowSize, - *m_pFontInfo, + m_pFontInfo, coordScreenBufferSize, TextAttribute{}, TextAttribute{ FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_RED }, @@ -252,7 +244,7 @@ class CommonState private: HANDLE m_heap; HRESULT m_hrTextBufferInfo; - FontInfo* m_pFontInfo; + FontInfo m_pFontInfo; std::unique_ptr m_backupTextBufferInfo; std::unique_ptr m_readHandle; 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/inc/til/rand.h b/src/inc/til/rand.h index da403a96e1c..b8b97eef868 100644 --- a/src/inc/til/rand.h +++ b/src/inc/til/rand.h @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -#include +#pragma once namespace til { @@ -28,7 +28,7 @@ namespace til FAIL_FAST_LAST_ERROR_IF(!proc); } - inline void operator()(PVOID RandomBuffer, ULONG RandomBufferLength) + void operator()(PVOID RandomBuffer, ULONG RandomBufferLength) const noexcept { proc(RandomBuffer, RandomBufferLength); } @@ -39,14 +39,14 @@ namespace til }; } - inline void gen_random(void* data, uint32_t length) + inline void gen_random(void* data, uint32_t length) noexcept { static details::RtlGenRandomLoader loader; loader(data, length); } template>> - T gen_random() + T gen_random() noexcept { T value; gen_random(&value, sizeof(T)); diff --git a/src/interactivity/base/InteractivityFactory.cpp b/src/interactivity/base/InteractivityFactory.cpp index fe8fc55cd8c..a2c9aaf2f58 100644 --- a/src/interactivity/base/InteractivityFactory.cpp +++ b/src/interactivity/base/InteractivityFactory.cpp @@ -497,9 +497,11 @@ 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 (const auto io = gci.GetVtIo(nullptr)) { - io->SetWindowVisibility(showOrHide); + char buf[] = "\x1b[1t"; + buf[2] = showOrHide ? '1' : '2'; + io->WriteUTF8(buf); } } 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/ut_interactivity_win32/UiaTextRangeTests.cpp b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp index 8096b878c84..4fc0d455a77 100644 --- a/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp +++ b/src/interactivity/win32/ut_interactivity_win32/UiaTextRangeTests.cpp @@ -377,7 +377,6 @@ class UiaTextRangeTests { _state->CleanupNewTextBufferInfo(); _state->CleanupGlobalScreenBuffer(); - _state->CleanupGlobalFont(); delete _state; delete _range; diff --git a/src/interactivity/win32/windowio.cpp b/src/interactivity/win32/windowio.cpp index 261ed4a80c8..728cc0dba26 100644 --- a/src/interactivity/win32/windowio.cpp +++ b/src/interactivity/win32/windowio.cpp @@ -935,7 +935,10 @@ 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(); + if (const auto io = ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo(nullptr)) + { + io->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 15535ad89f0..43dfa97f958 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 7bc2755721b..cacfc73fc89 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -273,13 +273,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 e022a38c3fa..b722d0ed968 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 07cda4d9ee6..ba8ad6d6253 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: @@ -485,38 +465,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/InteractDispatch.cpp b/src/terminal/adapter/InteractDispatch.cpp index a102368ec5b..a5bc381f8c0 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; @@ -177,7 +173,7 @@ bool InteractDispatch::FocusChanged(const bool focused) const // This should likely always be true - we shouldn't ever have an // InteractDispatch outside ConPTY mode, but just in case... - if (gci.IsInVtIoMode()) + if (gci.GetVtIo(nullptr)) { auto shouldActuallyFocus = false; diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 9806eb46b41..2e8f17716a0 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -1943,11 +1943,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(); @@ -1971,7 +1966,7 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con return !_PassThroughInputModes(); 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; @@ -2039,7 +2034,7 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con 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: @@ -3178,7 +3173,7 @@ bool AdaptDispatch::SoftReset() _sixelParser->SoftReset(); } - return !_api.IsConsolePty(); + return true; } //Routine Description: @@ -3353,11 +3348,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 +3370,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 +3396,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 +3414,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 +3471,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 +3494,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 +3509,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 +3575,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 +3683,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 +3784,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 +3805,7 @@ bool AdaptDispatch::DoITerm2Action(const std::wstring_view string) handled = true; } - return handled && !isConPty; + return handled; } // Method Description: @@ -3879,14 +3820,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 +3888,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 +3903,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 +3977,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 +4068,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 +4102,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 +4232,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 +4907,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 +4925,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..3de48d4e78e 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -286,9 +286,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/parser/InputStateMachineEngine.cpp b/src/terminal/parser/InputStateMachineEngine.cpp index 4a25d056f49..3afe031e6f8 100644 --- a/src/terminal/parser/InputStateMachineEngine.cpp +++ b/src/terminal/parser/InputStateMachineEngine.cpp @@ -102,14 +102,14 @@ InputStateMachineEngine::InputStateMachineEngine(std::unique_ptr pDispatch, const bool lookingForDSR); + bool IsLookingForDSR() const noexcept; + bool EncounteredWin32InputModeSequence() const noexcept override; - void SetLookingForDSR(const bool looking) noexcept; bool ActionExecute(const wchar_t wch) override; bool ActionExecuteFromEscape(const wchar_t wch) override; 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/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/til.unit.tests.vcxproj b/src/til/ut_til/til.unit.tests.vcxproj index d6b183d1282..68af04f763b 100644 --- a/src/til/ut_til/til.unit.tests.vcxproj +++ b/src/til/ut_til/til.unit.tests.vcxproj @@ -43,7 +43,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..e44350639f7 100644 --- a/src/til/ut_til/til.unit.tests.vcxproj.filters +++ b/src/til/ut_til/til.unit.tests.vcxproj.filters @@ -41,9 +41,6 @@ inc - - inc - inc diff --git a/src/types/viewport.cpp b/src/types/viewport.cpp index 6a64ecf7f62..ad276f8ecbe 100644 --- a/src/types/viewport.cpp +++ b/src/types/viewport.cpp @@ -198,14 +198,7 @@ void Viewport::Clamp(til::point& pos) const // - Clamped viewport Viewport Viewport::Clamp(const Viewport& other) const noexcept { - auto clampMe = other.ToInclusive(); - - clampMe.left = std::clamp(clampMe.left, Left(), RightInclusive()); - clampMe.right = std::clamp(clampMe.right, Left(), RightInclusive()); - clampMe.top = std::clamp(clampMe.top, Top(), BottomInclusive()); - clampMe.bottom = std::clamp(clampMe.bottom, Top(), BottomInclusive()); - - return Viewport::FromInclusive(clampMe); + return Viewport::FromExclusive(ToExclusive() & other.ToExclusive()); } // Method Description: 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..603a1bbe3e1 100644 --- a/src/winconpty/winconpty.h +++ b/src/winconpty/winconpty.h @@ -52,8 +52,8 @@ typedef struct _PseudoConsole #ifndef PSEUDOCONSOLE_INHERIT_CURSOR #define PSEUDOCONSOLE_INHERIT_CURSOR (0x1) #endif -#ifndef PSEUDOCONSOLE_RESIZE_QUIRK -#define PSEUDOCONSOLE_RESIZE_QUIRK (0x2) +#ifndef PSEUDOCONSOLE_WIN32_INPUT_MODE +#define PSEUDOCONSOLE_WIN32_INPUT_MODE (0x4) #endif #ifndef PSEUDOCONSOLE_GLYPH_WIDTH__MASK #define PSEUDOCONSOLE_GLYPH_WIDTH__MASK 0x18 From 17d39eb3eb41b6534ed4cc613547b7bd930bf0f6 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 3 Jul 2024 04:02:36 +0200 Subject: [PATCH 02/60] Fix build, Fix tests --- src/cascadia/UnitTests_Remoting/RemotingTests.cpp | 6 +++--- .../UnitTests_SettingsModel/TerminalSettingsTests.cpp | 1 + src/host/VtInputThread.cpp | 2 +- src/host/screenInfo.cpp | 2 +- src/host/ut_host/ViewportTests.cpp | 7 +------ src/inc/conpty-static.h | 11 +++++++++++ src/terminal/parser/ut_parser/Base64Test.cpp | 1 + src/winconpty/winconpty.h | 3 --- 8 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/cascadia/UnitTests_Remoting/RemotingTests.cpp b/src/cascadia/UnitTests_Remoting/RemotingTests.cpp index 8fc12f493db..ed4a1ae94e5 100644 --- a/src/cascadia/UnitTests_Remoting/RemotingTests.cpp +++ b/src/cascadia/UnitTests_Remoting/RemotingTests.cpp @@ -2586,7 +2586,6 @@ namespace RemotingUnitTests try { const auto result = m1->ProposeCommandline(args); - VERIFY_IS_FALSE(true, L"This should have thrown"); } catch (const winrt::hresult_error& e) { @@ -2596,9 +2595,10 @@ namespace RemotingUnitTests // This is the same check in WindowManager::_proposeToMonarch. VERIFY_IS_TRUE(e.code() == RPC_SERVER_UNAVAILABLE_HR || e.code() == RPC_CALL_FAILED_HR); + return; } - // just don't catch other types of exceptions. They'll take out - // TAEF, which will count as a failure. + + VERIFY_FAIL(L"This should have thrown"); } } diff --git a/src/cascadia/UnitTests_SettingsModel/TerminalSettingsTests.cpp b/src/cascadia/UnitTests_SettingsModel/TerminalSettingsTests.cpp index 051d722f941..6733603c06e 100644 --- a/src/cascadia/UnitTests_SettingsModel/TerminalSettingsTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/TerminalSettingsTests.cpp @@ -4,6 +4,7 @@ #include "pch.h" #include +#include #include "../TerminalSettingsModel/CascadiaSettings.h" #include "../TerminalSettingsModel/TerminalSettings.h" diff --git a/src/host/VtInputThread.cpp b/src/host/VtInputThread.cpp index 659e412df32..502ac4dcf9d 100644 --- a/src/host/VtInputThread.cpp +++ b/src/host/VtInputThread.cpp @@ -147,6 +147,6 @@ void VtInputThread::_InputThread() bool VtInputThread::IsLookingForDSR() const noexcept { - const auto& engine = static_cast( _pInputStateMachine->Engine()); + const auto& engine = static_cast(_pInputStateMachine->Engine()); return engine.IsLookingForDSR(); } diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index 6e7b146fd1a..3f6c5cacc65 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -2082,7 +2082,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. - _textBuffer->TriggerRedrawAll(); + _textBuffer->TriggerRedrawAll(); // If we're an alt buffer, also update our main buffer. if (_psiMainBuffer) diff --git a/src/host/ut_host/ViewportTests.cpp b/src/host/ut_host/ViewportTests.cpp index 04947a8debb..606279be44c 100644 --- a/src/host/ut_host/ViewportTests.cpp +++ b/src/host/ut_host/ViewportTests.cpp @@ -425,12 +425,7 @@ class ViewportTests testView = Viewport::FromInclusive(testRect); Log::Comment(L"We expect it to be pulled back so each coordinate is in bounds, but the rectangle is still invalid (since left will be > right)."); - til::inclusive_rect expected; - expected.top = rect.bottom; - expected.bottom = rect.top; - expected.left = rect.right; - expected.right = rect.left; - const auto expectedView = Viewport::FromInclusive(expected); + const auto expectedView = Viewport::Empty(); actual = view.Clamp(testView); VERIFY_ARE_EQUAL(expectedView, actual, L"Every dimension should be pulled just inside the clamping rectangle."); diff --git a/src/inc/conpty-static.h b/src/inc/conpty-static.h index 4e4ce7c089c..875470b1575 100644 --- a/src/inc/conpty-static.h +++ b/src/inc/conpty-static.h @@ -23,6 +23,17 @@ #endif #endif +// CreatePseudoConsole Flags +#ifndef PSEUDOCONSOLE_INHERIT_CURSOR +#define PSEUDOCONSOLE_INHERIT_CURSOR (0x1) +#endif +#ifndef PSEUDOCONSOLE_GLYPH_WIDTH__MASK +#define PSEUDOCONSOLE_GLYPH_WIDTH__MASK 0x18 +#define PSEUDOCONSOLE_GLYPH_WIDTH_GRAPHEMES 0x08 +#define PSEUDOCONSOLE_GLYPH_WIDTH_WCSWIDTH 0x10 +#define PSEUDOCONSOLE_GLYPH_WIDTH_CONSOLE 0x18 +#endif + CONPTY_EXPORT HRESULT WINAPI ConptyCreatePseudoConsole(COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC); CONPTY_EXPORT HRESULT WINAPI ConptyCreatePseudoConsoleAsUser(HANDLE hToken, COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC); diff --git a/src/terminal/parser/ut_parser/Base64Test.cpp b/src/terminal/parser/ut_parser/Base64Test.cpp index 137d0a7f451..2bf9375b038 100644 --- a/src/terminal/parser/ut_parser/Base64Test.cpp +++ b/src/terminal/parser/ut_parser/Base64Test.cpp @@ -5,6 +5,7 @@ #include "WexTestClass.h" #include +#include #include #include "base64.hpp" diff --git a/src/winconpty/winconpty.h b/src/winconpty/winconpty.h index 603a1bbe3e1..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_WIN32_INPUT_MODE -#define PSEUDOCONSOLE_WIN32_INPUT_MODE (0x4) -#endif #ifndef PSEUDOCONSOLE_GLYPH_WIDTH__MASK #define PSEUDOCONSOLE_GLYPH_WIDTH__MASK 0x18 #define PSEUDOCONSOLE_GLYPH_WIDTH_GRAPHEMES 0x08 From 656a0492d7118346db0455d7370d12c314e385ea Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 3 Jul 2024 23:21:57 +0200 Subject: [PATCH 03/60] Restore win32im/focus on RIS, Clean up some stuff --- src/host/VtIo.cpp | 64 ++++++++++++++++++++++++++++++---- src/host/VtIo.hpp | 5 +++ src/host/_output.cpp | 1 - src/host/_stream.cpp | 34 ++++++++++++++++-- src/host/getset.cpp | 58 +++++++++++++----------------- src/host/ut_host/VtIoTests.cpp | 14 +++++--- 6 files changed, 129 insertions(+), 47 deletions(-) diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index 13722aab9d2..c552fac891c 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -212,8 +212,8 @@ bool VtIo::IsUsingVt() const // which is not the default in terminals, so we have to do that explicitly. WriteUTF8( "\x1b[20h" // Line Feed / New Line Mode (LNM) - "\033[?9001h" // Win32 Input Mode "\033[?1004h" // Focus Event Mode + "\033[?9001h" // Win32 Input Mode ); if (_pVtInputThread) @@ -380,9 +380,6 @@ void VtIo::WriteUTF8(std::string_view str) return; } - // Always appending to _buffer instead of directly writing `str` to the pipe, even if we aren't even - // using overlapped IO, is a bit of a waste, but it makes the retry in _flush() quite a bit simpler. - // Coincidentally, most VtIo translations rely on buffer corking and so we'd have to append to _buffer anyway. _back.append(str); _flush(); } @@ -394,8 +391,18 @@ void VtIo::WriteUTF16(std::wstring_view str) return; } - const auto str8 = til::u16u8(str); - WriteUTF8(str8); + const auto existingUTF8Len = _back.size(); + const auto incomingUTF16Len = gsl::narrow(str.size()); + // When converting from UTF-16 to UTF-8 the worst case is 3 bytes per UTF-16 code unit. + const auto totalUTF8Cap = gsl::narrow(gsl::narrow_cast(incomingUTF16Len) * 3 + existingUTF8Len); + const auto incomingUTF8Cap = gsl::narrow(totalUTF8Cap - existingUTF8Len); + + _back._Resize_and_overwrite(totalUTF8Cap, [=](char* ptr, const size_t) noexcept { + const auto len = WideCharToMultiByte(CP_UTF8, 0, str.data(), incomingUTF16Len, ptr + existingUTF8Len, incomingUTF8Cap, nullptr, nullptr); + return existingUTF8Len + std::max(0, len); + }); + + _flush(); } void VtIo::WriteUCS2(wchar_t ch) @@ -403,6 +410,10 @@ void VtIo::WriteUCS2(wchar_t ch) char buf[4]; size_t len = 0; + if (ch < L' ') + { + ch = UNICODE_SPACE; + } if (til::is_surrogate(ch)) { ch = UNICODE_REPLACEMENT; @@ -427,11 +438,52 @@ void VtIo::WriteUCS2(wchar_t ch) WriteUTF8({ &buf[0], len }); } +// CUP: Cursor Position void VtIo::WriteCUP(til::point position) { WriteFormat("\x1b[{};{}H", position.y + 1, position.x + 1); } +// DECTCEM: Text Cursor Enable +void VtIo::WriteDECTCEM(bool enabled) +{ + char buf[] = "\x1b[?25h"; + buf[std::size(buf) - 2] = enabled ? 'h' : 'l'; + WriteUTF8({ buf, std::size(buf) - 1 }); +} + +// SGR 1006: SGR Extended Mouse Mode +void VtIo::WriteSGR1006(bool enabled) +{ + char buf[] = "\x1b[?1003;1006h"; + buf[std::size(buf) - 2] = enabled ? 'h' : 'l'; + WriteUTF8({ buf, std::size(buf) - 1 }); +} + +// DECAWM: Autowrap Mode +void VtIo::WriteDECAWM(bool enabled) +{ + char buf[] = "\x1b[?7h"; + buf[std::size(buf) - 2] = enabled ? 'h' : 'l'; + WriteUTF8({ buf, std::size(buf) - 1 }); +} + +// LNM: Line Feed / New Line Mode +void VtIo::WriteLNM(bool enabled) +{ + char buf[] = "\x1b[20h"; + buf[std::size(buf) - 2] = enabled ? 'h' : 'l'; + WriteUTF8({ buf, std::size(buf) - 1 }); +} + +// ASB: Alternate Screen Buffer +void VtIo::WriteASB(bool enabled) +{ + char buf[] = "\x1b[?1049h"; + buf[std::size(buf) - 2] = enabled ? 'h' : 'l'; + WriteUTF8({ buf, std::size(buf) - 1 }); +} + void VtIo::WriteAttributes(WORD attributes) { FormatAttributes(_back, attributes); diff --git a/src/host/VtIo.hpp b/src/host/VtIo.hpp index 6079dc61204..7971af907cd 100644 --- a/src/host/VtIo.hpp +++ b/src/host/VtIo.hpp @@ -79,6 +79,11 @@ namespace Microsoft::Console::VirtualTerminal void WriteUTF16(std::wstring_view str); void WriteUCS2(wchar_t ch); void WriteCUP(til::point position); + void WriteDECTCEM(bool enabled); + void WriteSGR1006(bool enabled); + void WriteDECAWM(bool enabled); + void WriteLNM(bool enabled); + void WriteASB(bool enabled); void WriteAttributes(WORD attributes); void WriteInfos(til::point target, std::span infos); diff --git a/src/host/_output.cpp b/src/host/_output.cpp index 154138b724a..c10763ea792 100644 --- a/src/host/_output.cpp +++ b/src/host/_output.cpp @@ -14,7 +14,6 @@ #include "../types/inc/convert.hpp" #include "../types/inc/GlyphWidth.hpp" #include "../types/inc/Viewport.hpp" -#include "til/unicode.h" using namespace Microsoft::Console::Types; using Microsoft::Console::Interactivity::ServiceLocator; diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index 17ac6c9bc03..84397c5411d 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -340,11 +340,41 @@ void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str) { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - screenInfo.GetStateMachine().ProcessString(str); - if (const auto io = gci.GetVtIo(&screenInfo)) { + using Mode = Microsoft::Console::VirtualTerminal::TerminalInput::Mode; + + auto& terminalInput = gci.GetActiveInputBuffer()->GetTerminalInput(); + + // These two modes are ones that should always stay enabled when we're ConPTY. + // To figure out whether some VT sequence disabled them (primarily RIS = ESC c), + // we temporarily enable them, restore them, and check if they got reset in between. + const auto beforeFocusEvent = terminalInput.GetInputMode(Mode::FocusEvent); + const auto beforeWin32 = terminalInput.GetInputMode(Mode::Win32); + terminalInput.SetInputMode(Mode::FocusEvent, true); + terminalInput.SetInputMode(Mode::Win32, true); + + screenInfo.GetStateMachine().ProcessString(str); + + const auto afterFocusEvent = terminalInput.GetInputMode(Mode::FocusEvent); + const auto afterWin32 = terminalInput.GetInputMode(Mode::Win32); + terminalInput.SetInputMode(Mode::FocusEvent, beforeFocusEvent); + terminalInput.SetInputMode(Mode::Win32, beforeWin32); + + const auto cork = io->Cork(); io->WriteUTF16(str); + if (!afterFocusEvent) + { + io->WriteUTF8("\033[?1004h"); + } + if (!afterWin32) + { + io->WriteUTF8("\033[?9001h"); + } + } + else + { + screenInfo.GetStateMachine().ProcessString(str); } } diff --git a/src/host/getset.cpp b/src/host/getset.cpp index b04a9837b27..b9f03fe2b4c 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -2,7 +2,6 @@ // Licensed under the MIT license. #include "precomp.h" - #include "getset.h" #include "ApiRoutines.h" @@ -11,6 +10,7 @@ #include "misc.h" #include "output.h" #include "_output.h" +#include "_stream.h" #include "../interactivity/inc/ServiceLocator.hpp" #include "../types/inc/convert.hpp" @@ -381,9 +381,7 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept if (WI_IsFlagSet(diff, ENABLE_MOUSE_INPUT)) { - char buf[] = "\x1b[?1003;1006h"; // Any Event Mouse + SGR Extended Mode - buf[std::size(buf) - 2] = WI_IsFlagSet(mode, ENABLE_MOUSE_INPUT) ? 'h' : 'l'; - io->WriteUTF8(buf); + io->WriteSGR1006(WI_IsFlagSet(mode, ENABLE_MOUSE_INPUT)); } } } @@ -457,16 +455,12 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept { if (WI_IsFlagSet(diff, ENABLE_WRAP_AT_EOL_OUTPUT)) { - char buf[] = "\x1b[?7h"; // Autowrap Mode (DECAWM) - buf[std::size(buf) - 2] = WI_IsFlagSet(dwNewMode, ENABLE_WRAP_AT_EOL_OUTPUT) ? 'h' : 'l'; - io->WriteUTF8(buf); + io->WriteDECAWM(WI_IsFlagSet(dwNewMode, ENABLE_WRAP_AT_EOL_OUTPUT)); } if (WI_IsFlagSet(diff, DISABLE_NEWLINE_AUTO_RETURN)) { - char buf[] = "\x1b[20h"; // Line Feed / New Line Mode (LNM) - buf[std::size(buf) - 2] = WI_IsFlagClear(mode, DISABLE_NEWLINE_AUTO_RETURN) ? 'h' : 'l'; - io->WriteUTF8(buf); + io->WriteLNM(WI_IsFlagClear(dwNewMode, DISABLE_NEWLINE_AUTO_RETURN)); } } @@ -508,33 +502,31 @@ void ApiRoutines::SetConsoleActiveScreenBufferImpl(SCREEN_INFORMATION& newContex alt.SetViewportSize(&size); } - io->WriteUTF8("\x1b[?1049l"); - + Viewport read; til::small_vector infos; infos.resize(area, CHAR_INFO{ L' ', FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED }); - Viewport read; - THROW_IF_FAILED(ReadConsoleOutputWImpl(main, infos, viewport, read)); - for (til::CoordType i = 0; i < size.height; i++) - { - io->WriteInfos({ 0, i }, { infos.begin() + i * size.width, static_cast(size.width) }); - } - - io->WriteCUP(main.GetTextBuffer().GetCursor().GetPosition()); - io->WriteAttributes(main.GetAttributes().GetLegacyAttributes()); - - if (hasAltBuffer) - { - io->WriteUTF8("\x1b[?1049h"); - - THROW_IF_FAILED(ReadConsoleOutputWImpl(alt, infos, viewport, read)); + const auto dumpScreenInfo = [&](SCREEN_INFORMATION& screenInfo) { + THROW_IF_FAILED(ReadConsoleOutputWImpl(screenInfo, infos, viewport, read)); for (til::CoordType i = 0; i < size.height; i++) { io->WriteInfos({ 0, i }, { infos.begin() + i * size.width, static_cast(size.width) }); } - io->WriteCUP(alt.GetTextBuffer().GetCursor().GetPosition()); - io->WriteAttributes(alt.GetAttributes().GetLegacyAttributes()); + io->WriteCUP(screenInfo.GetTextBuffer().GetCursor().GetPosition()); + io->WriteAttributes(screenInfo.GetAttributes().GetLegacyAttributes()); + io->WriteDECTCEM(screenInfo.GetTextBuffer().GetCursor().IsVisible()); + io->WriteDECAWM(WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT)); + io->WriteLNM(WI_IsFlagClear(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN)); + }; + + io->WriteASB(false); + dumpScreenInfo(main); + + if (hasAltBuffer) + { + io->WriteASB(true); + dumpScreenInfo(alt); } } @@ -791,7 +783,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); if (const auto io = gci.GetVtIo(&context)) { - io->WriteFormat(FMT_COMPILE("\x1b[{};{}H"), position.y + 1, position.x + 1); + io->WriteCUP(position); } RETURN_IF_NTSTATUS_FAILED(buffer.SetCursorPosition(position, true)); @@ -871,9 +863,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); if (const auto io = gci.GetVtIo(&context)) { - char buf[] = "\x1b[?25l"; - buf[std::size(buf) - 2] = isVisible ? 'h' : 'l'; - io->WriteUTF8(buf); + io->WriteDECTCEM(isVisible); } return S_OK; @@ -1029,7 +1019,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont !clip && fillCharacter == UNICODE_SPACE && fillAttribute == buffer.GetAttributes().GetLegacyAttributes()) { - io->WriteUTF8("\033c"); + WriteCharsVT(context, L"\033c"); return S_OK; } diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index 49cd23110a6..3af2ba6a23e 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -6,7 +6,6 @@ #include #include "CommonState.hpp" -#include "directio.h" #include "../VtIo.hpp" #include "../../interactivity/inc/ServiceLocator.hpp" #include "../../renderer/base/Renderer.hpp" @@ -55,6 +54,9 @@ static std::pair createOverlappedPipe(DWOR // What the default attributes `FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED` result in. #define sgr_rst() "\x1b[27;39;49m" +// Any RIS sequence should re-enable our required ConPTY modes Focus Event Mode and Win32 Input Mode. +#define ris() "\033c\x1b[?1004h\x1b[?9001h" + static constexpr std::wstring_view s_initialContentVT{ // clang-format off L"" @@ -409,7 +411,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests // 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 = "\033c"; + expected = ris(); actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); @@ -518,17 +520,21 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests 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" // + "\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(); + cup(1, 1) sgr_rst() // + "\x1b[?25h" // DECTCEM (Text Cursor Enable) + "\x1b[?7h" // DECAWM (Autowrap Mode) + "\x1b[20h"; // LNM (Line Feed / New Line Mode) const auto actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); } From 73e3dd31b5d935814c6e978c95cf60c764d7a257 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 4 Jul 2024 02:33:20 +0200 Subject: [PATCH 04/60] Fix support for vim --- src/host/VtIo.cpp | 2 +- src/host/consoleInformation.cpp | 19 ++++++++++++------- src/host/server.h | 2 ++ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index c552fac891c..7b72f363b69 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -349,7 +349,7 @@ static size_t formatAttributes(char (&buffer)[16], WORD attributes) noexcept bg = lut[(attributes >> 4) & 0xf] + 10; } - return fmt::format_to(&buffer[0], FMT_COMPILE("\x1b[{};{};{}m"), rv, fg, bg) - &buffer[0]; + return fmt::format_to(&buffer[0], FMT_COMPILE("\x1b[0;{};{};{}m"), rv, fg, bg) - &buffer[0]; } void VtIo::FormatAttributes(std::string& target, WORD attributes) diff --git a/src/host/consoleInformation.cpp b/src/host/consoleInformation.cpp index 353d7f92e9f..e874c1aaadb 100644 --- a/src/host/consoleInformation.cpp +++ b/src/host/consoleInformation.cpp @@ -45,11 +45,6 @@ ULONG CONSOLE_INFORMATION::GetCSRecursionCount() const noexcept return _lock.recursion_depth(); } -Microsoft::Console::VirtualTerminal::VtIo* CONSOLE_INFORMATION::GetVtIoNoCheck() -{ - return &_vtIo; -} - // Routine Description: // - This routine allocates and initialized a console and its associated // data - input buffer and screen buffer. @@ -123,10 +118,14 @@ Microsoft::Console::VirtualTerminal::VtIo* CONSOLE_INFORMATION::GetVtIoNoCheck() return Status; } +VtIo* CONSOLE_INFORMATION::GetVtIoNoCheck() +{ + return &_vtIo; +} + VtIo* CONSOLE_INFORMATION::GetVtIo(const SCREEN_INFORMATION* context) { - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - return _vtIo.IsUsingVt() && (context == nullptr || context == &gci.GetActiveOutputBuffer()) ? &_vtIo : nullptr; + return _vtIo.IsUsingVt() && (context == nullptr || context == pCurrentMainScreenBuffer || context == pCurrentScreenBuffer) ? &_vtIo : nullptr; } bool CONSOLE_INFORMATION::HasPendingCookedRead() const noexcept @@ -180,6 +179,11 @@ const SCREEN_INFORMATION& CONSOLE_INFORMATION::GetActiveOutputBuffer() const return *pCurrentScreenBuffer; } +const SCREEN_INFORMATION& CONSOLE_INFORMATION::GetActiveMainOutputBuffer() const +{ + return *pCurrentMainScreenBuffer; +} + void CONSOLE_INFORMATION::SetActiveOutputBuffer(SCREEN_INFORMATION& screenBuffer) { if (pCurrentScreenBuffer) @@ -188,6 +192,7 @@ void CONSOLE_INFORMATION::SetActiveOutputBuffer(SCREEN_INFORMATION& screenBuffer } pCurrentScreenBuffer = &screenBuffer; pCurrentScreenBuffer->GetTextBuffer().SetAsActiveBuffer(true); + pCurrentMainScreenBuffer = &screenBuffer.GetMainBuffer(); } bool CONSOLE_INFORMATION::HasActiveOutputBuffer() const diff --git a/src/host/server.h b/src/host/server.h index 0c5fa849533..89a9f4c7273 100644 --- a/src/host/server.h +++ b/src/host/server.h @@ -108,6 +108,7 @@ class CONSOLE_INFORMATION : SCREEN_INFORMATION& GetActiveOutputBuffer() override; const SCREEN_INFORMATION& GetActiveOutputBuffer() const override; + const SCREEN_INFORMATION& GetActiveMainOutputBuffer() const; void SetActiveOutputBuffer(SCREEN_INFORMATION& screenBuffer); bool HasActiveOutputBuffer() const; @@ -153,6 +154,7 @@ class CONSOLE_INFORMATION : std::wstring _OriginalTitle; std::wstring _LinkTitle; // Path to .lnk file SCREEN_INFORMATION* pCurrentScreenBuffer = nullptr; + SCREEN_INFORMATION* pCurrentMainScreenBuffer = nullptr; COOKED_READ_DATA* _cookedReadData = nullptr; // non-ownership pointer bool _bracketedPasteMode = false; From 89b4c09e501e9e502344d306c81be0ee38bef961 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 4 Jul 2024 02:46:10 +0200 Subject: [PATCH 05/60] Fix support for far --- src/host/getset.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/host/getset.cpp b/src/host/getset.cpp index b9f03fe2b4c..9080c01356c 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -381,7 +381,7 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept if (WI_IsFlagSet(diff, ENABLE_MOUSE_INPUT)) { - io->WriteSGR1006(WI_IsFlagSet(mode, ENABLE_MOUSE_INPUT)); + io->WriteSGR1006(WI_IsFlagSet(newMode, ENABLE_MOUSE_INPUT)); } } } From eec17eb805a3499233f0c7c5d9bb05def2136225 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 4 Jul 2024 03:48:13 +0200 Subject: [PATCH 06/60] Slightly more elegant formatAttributes --- src/host/VtIo.cpp | 22 ++++++---- src/host/ut_host/VtIoTests.cpp | 74 +++++++++++++++++----------------- 2 files changed, 52 insertions(+), 44 deletions(-) diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index 7b72f363b69..77054332cf2 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -333,23 +333,31 @@ bool VtIo::IsControlCharacter(wchar_t wch) noexcept static size_t formatAttributes(char (&buffer)[16], WORD attributes) noexcept { - const uint8_t rv = WI_IsFlagSet(attributes, COMMON_LVB_REVERSE_VIDEO) ? 7 : 27; - uint8_t fg = 39; - uint8_t bg = 49; + auto end = &buffer[0]; + memcpy(end, "\x1b[0", 4); + end += 3; + + if (attributes & COMMON_LVB_REVERSE_VIDEO) + { + memcpy(end, ";7", 2); + end += 2; + } // `attributes` of exactly `FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED` // are often used to indicate the default colors in Windows Console applications. - // Thus, we translate them to 39/49 (default foreground/background). + // Since we always emit SGR 0 (reset all attributes), we simply need to skip this branch. if ((attributes & (FG_ATTRS | BG_ATTRS)) != (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED)) { // The Console API represents colors in BGR order, but VT represents them in RGB order. // This LUT transposes them. This is for foreground colors. Add +10 to get the background ones. static constexpr uint8_t lut[] = { 30, 34, 32, 36, 31, 35, 33, 37, 90, 94, 92, 96, 91, 95, 93, 97 }; - fg = lut[attributes & 0xf]; - bg = lut[(attributes >> 4) & 0xf] + 10; + const uint8_t fg = lut[attributes & 0xf]; + const uint8_t bg = lut[(attributes >> 4) & 0xf] + 10; + end = fmt::format_to(end, FMT_COMPILE(";{};{}"), fg, bg); } - return fmt::format_to(&buffer[0], FMT_COMPILE("\x1b[0;{};{};{}m"), rv, fg, bg) - &buffer[0]; + *end++ = 'm'; + return end - &buffer[0]; } void VtIo::FormatAttributes(std::string& target, WORD attributes) diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index 3af2ba6a23e..041dd7c463f 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -49,10 +49,10 @@ static std::pair createOverlappedPipe(DWOR #define lnm(h) "\x1b[20" #h // The escape sequences that red() / blu() result in. -#define sgr_red(s) "\x1b[27;31;40m" s -#define sgr_blu(s) "\x1b[27;34;40m" s +#define sgr_red(s) "\x1b[0;31;40m" s +#define sgr_blu(s) "\x1b[0;34;40m" s // What the default attributes `FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED` result in. -#define sgr_rst() "\x1b[27;39;49m" +#define sgr_rst() "\x1b[0m" // Any RIS sequence should re-enable our required ConPTY modes Focus Event Mode and Win32 Input Mode. #define ris() "\033c\x1b[?1004h\x1b[?9001h" @@ -196,42 +196,42 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests const auto expected = // 16 foreground colors - "\x1b[27;30;40m" - "\x1b[27;34;40m" - "\x1b[27;32;40m" - "\x1b[27;36;40m" - "\x1b[27;31;40m" - "\x1b[27;35;40m" - "\x1b[27;33;40m" - "\x1b[27;39;49m" // <-- FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED gets translated to the default colors - "\x1b[27;90;40m" - "\x1b[27;94;40m" - "\x1b[27;92;40m" - "\x1b[27;96;40m" - "\x1b[27;91;40m" - "\x1b[27;95;40m" - "\x1b[27;93;40m" - "\x1b[27;97;40m" + "\x1b[0;30;40m" + "\x1b[0;34;40m" + "\x1b[0;32;40m" + "\x1b[0;36;40m" + "\x1b[0;31;40m" + "\x1b[0;35;40m" + "\x1b[0;33;40m" + "\x1b[0m" // <-- FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED gets translated to the default colors + "\x1b[0;90;40m" + "\x1b[0;94;40m" + "\x1b[0;92;40m" + "\x1b[0;96;40m" + "\x1b[0;91;40m" + "\x1b[0;95;40m" + "\x1b[0;93;40m" + "\x1b[0;97;40m" // 16 background colors - "\x1b[27;30;40m" - "\x1b[27;30;44m" - "\x1b[27;30;42m" - "\x1b[27;30;46m" - "\x1b[27;30;41m" - "\x1b[27;30;45m" - "\x1b[27;30;43m" - "\x1b[27;30;47m" - "\x1b[27;30;100m" - "\x1b[27;30;104m" - "\x1b[27;30;102m" - "\x1b[27;30;106m" - "\x1b[27;30;101m" - "\x1b[27;30;105m" - "\x1b[27;30;103m" - "\x1b[27;30;107m" + "\x1b[0;30;40m" + "\x1b[0;30;44m" + "\x1b[0;30;42m" + "\x1b[0;30;46m" + "\x1b[0;30;41m" + "\x1b[0;30;45m" + "\x1b[0;30;43m" + "\x1b[0;30;47m" + "\x1b[0;30;100m" + "\x1b[0;30;104m" + "\x1b[0;30;102m" + "\x1b[0;30;106m" + "\x1b[0;30;101m" + "\x1b[0;30;105m" + "\x1b[0;30;103m" + "\x1b[0;30;107m" // The remaining two calls - "\x1b[7;95;42m" - "\x1b[7;39;49m"; + "\x1b[0;7;95;42m" + "\x1b[0;7m"; const auto actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); } From db2b762221dc77f9838992d7c2755bcf75291edf Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 4 Jul 2024 03:49:40 +0200 Subject: [PATCH 07/60] Remove references to deleted tests --- src/til/ut_til/sources | 1 - src/til/ut_til/til.unit.tests.vcxproj | 1 - src/til/ut_til/til.unit.tests.vcxproj.filters | 1 - 3 files changed, 3 deletions(-) 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 68af04f763b..7922c993bcc 100644 --- a/src/til/ut_til/til.unit.tests.vcxproj +++ b/src/til/ut_til/til.unit.tests.vcxproj @@ -15,7 +15,6 @@ Create - diff --git a/src/til/ut_til/til.unit.tests.vcxproj.filters b/src/til/ut_til/til.unit.tests.vcxproj.filters index e44350639f7..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 @@ - From 9e3d3983d5ed3b30d7bc0be4d4bb33376093ddbe Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 4 Jul 2024 17:26:28 +0200 Subject: [PATCH 08/60] Some leftover VtEngine logic --- src/terminal/adapter/adaptDispatch.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 2e8f17716a0..c4f7bee3b60 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -3275,20 +3275,6 @@ 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"); - } - } return true; } From 8de1a7543f0ccf7851d2f4780819e23f90615304 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 4 Jul 2024 17:27:17 +0200 Subject: [PATCH 09/60] Move OVERLAPPED helpers to Types --- src/host/VtIo.cpp | 40 ++++------------------------- src/host/ut_host/VtIoTests.cpp | 19 -------------- src/types/inc/utils.hpp | 10 +++++++- src/types/utils.cpp | 47 +++++++++++++++++++++++++++++++--- 4 files changed, 57 insertions(+), 59 deletions(-) diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index 77054332cf2..bd6083f666c 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -13,21 +13,6 @@ #include "../types/inc/CodepointWidthDetector.hpp" #include "../types/inc/utils.hpp" -// Some additional imports from ntifs.h which aren't in the regular Windows SDK (they're in the driver SDK (WDK)). -__kernel_entry NTSYSCALLAPI NTSTATUS NtQueryInformationFile( - _In_ HANDLE FileHandle, - _Out_ PIO_STATUS_BLOCK IoStatusBlock, - _Out_ PVOID FileInformation, - _In_ ULONG Length, - _In_ FILE_INFORMATION_CLASS FileInformationClass); - -#define FileModeInformation (FILE_INFORMATION_CLASS)16 - -typedef struct _FILE_MODE_INFORMATION -{ - ULONG Mode; -} FILE_MODE_INFORMATION, *PFILE_MODE_INFORMATION; - using namespace Microsoft::Console; using namespace Microsoft::Console::Render; using namespace Microsoft::Console::VirtualTerminal; @@ -35,22 +20,6 @@ using namespace Microsoft::Console::Types; using namespace Microsoft::Console::Utils; using namespace Microsoft::Console::Interactivity; -static bool handleWantsOverlappedIo(HANDLE handle) noexcept -{ - const auto ntdll = GetModuleHandleW(L"ntdll.dll"); - const auto pNtQueryInformationFile = GetProcAddressByFunctionDeclaration(ntdll, NtQueryInformationFile); - - if (!pNtQueryInformationFile) - { - return false; - } - - IO_STATUS_BLOCK statusBlock; - FILE_MODE_INFORMATION modeInfo; - const auto status = pNtQueryInformationFile(handle, &statusBlock, &modeInfo, sizeof(modeInfo), FileModeInformation); - return status == 0 && WI_AreAllFlagsClear(modeInfo.Mode, FILE_SYNCHRONOUS_IO_ALERT | FILE_SYNCHRONOUS_IO_NONALERT); -} - [[nodiscard]] HRESULT VtIo::Initialize(const ConsoleArguments* const pArgs) { _lookingForCursorPosition = pArgs->GetInheritCursor(); @@ -115,13 +84,14 @@ static bool handleWantsOverlappedIo(HANDLE handle) noexcept _hOutput.reset(OutHandle); _hSignal.reset(SignalHandle); - if (handleWantsOverlappedIo(_hOutput.get())) + if (Utils::HandleWantsOverlappedIo(_hOutput.get())) { _overlappedEvent.reset(CreateEventExW(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS)); - THROW_LAST_ERROR_IF(!_overlappedEvent); - + if (_overlappedEvent) + { _overlappedBuf.hEvent = _overlappedEvent.get(); _overlapped = &_overlappedBuf; + } } // The only way we're initialized is if the args said we're in conpty mode. @@ -609,7 +579,7 @@ void VtIo::_uncork() void VtIo::_flush() { - if (_corked <= 0) + if (_corked <= 0 && !_back.empty()) { _flushNow(); } diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index 041dd7c463f..3b7d08f0c64 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -3,13 +3,7 @@ #include "precomp.h" -#include - #include "CommonState.hpp" -#include "../VtIo.hpp" -#include "../../interactivity/inc/ServiceLocator.hpp" -#include "../../renderer/base/Renderer.hpp" -#include "../../types/inc/Viewport.hpp" using namespace WEX::Common; using namespace WEX::Logging; @@ -31,19 +25,6 @@ constexpr CHAR_INFO blu(wchar_t ch) noexcept return { ch, FOREGROUND_BLUE }; } -#pragma warning(disable : 4505) - -static std::pair createOverlappedPipe(DWORD bufferSize) -{ - const auto rnd = til::gen_random(); - const auto name = fmt::format(FMT_COMPILE(LR"(\\.\pipe\{:016x})"), rnd); - wil::unique_hfile rx{ CreateNamedPipeW(name.c_str(), PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_WAIT, 1, bufferSize, bufferSize, 0, nullptr) }; - THROW_LAST_ERROR_IF(!rx); - wil::unique_hfile tx{ CreateFileW(name.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, nullptr) }; - THROW_LAST_ERROR_IF(!tx); - return { std::move(tx), std::move(rx) }; -} - #define cup(y, x) "\x1b[" #y ";" #x "H" #define decawm(h) "\x1b[?7" #h #define lnm(h) "\x1b[20" #h diff --git a/src/types/inc/utils.hpp b/src/types/inc/utils.hpp index 477229c51f9..3d062ffe7fb 100644 --- a/src/types/inc/utils.hpp +++ b/src/types/inc/utils.hpp @@ -15,6 +15,12 @@ Author(s): namespace Microsoft::Console::Utils { + struct OverlappedPipe + { + wil::unique_hfile tx; + wil::unique_hfile rx; + }; + // Function Description: // - Returns -1, 0 or +1 to indicate the sign of the passed-in value. template @@ -24,6 +30,8 @@ namespace Microsoft::Console::Utils } bool IsValidHandle(const HANDLE handle) noexcept; + bool HandleWantsOverlappedIo(HANDLE handle) noexcept; + OverlappedPipe CreateOverlappedPipe(DWORD bufferSize); // Function Description: // - Clamps a long in between `min` and `SHRT_MAX` @@ -85,7 +93,7 @@ namespace Microsoft::Console::Utils constexpr unsigned long EndianSwap(unsigned long value) { - return gsl::narrow_cast(EndianSwap(gsl::narrow_cast(value))); + return EndianSwap(static_cast(value)); } constexpr GUID EndianSwap(GUID value) diff --git a/src/types/utils.cpp b/src/types/utils.cpp index 53cc3d9505b..93f9f9caf35 100644 --- a/src/types/utils.cpp +++ b/src/types/utils.cpp @@ -4,13 +4,12 @@ #include "precomp.h" #include "inc/utils.hpp" -#include +#include +#include +#include #include "inc/colorTable.hpp" -#include -#include - using namespace Microsoft::Console; // Routine Description: @@ -631,6 +630,46 @@ bool Utils::IsValidHandle(const HANDLE handle) noexcept return handle != nullptr && handle != INVALID_HANDLE_VALUE; } +// From ntifs.h, which isn't part of the regular Windows SDK (they're in the driver SDK (WDK)). +__kernel_entry NTSYSCALLAPI NTSTATUS NtQueryInformationFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _Out_ PVOID FileInformation, + _In_ ULONG Length, + _In_ FILE_INFORMATION_CLASS FileInformationClass); + +#define FileModeInformation (FILE_INFORMATION_CLASS)16 + +typedef struct _FILE_MODE_INFORMATION +{ + ULONG Mode; +} FILE_MODE_INFORMATION, *PFILE_MODE_INFORMATION; + +bool Utils::HandleWantsOverlappedIo(HANDLE handle) noexcept +{ + static const auto pNtQueryInformationFile = GetProcAddressByFunctionDeclaration(GetModuleHandleW(L"ntdll.dll"), NtQueryInformationFile); + if (!pNtQueryInformationFile) + { + return false; + } + + IO_STATUS_BLOCK statusBlock; + FILE_MODE_INFORMATION modeInfo; + const auto status = pNtQueryInformationFile(handle, &statusBlock, &modeInfo, sizeof(modeInfo), FileModeInformation); + return status == 0 && WI_AreAllFlagsClear(modeInfo.Mode, FILE_SYNCHRONOUS_IO_ALERT | FILE_SYNCHRONOUS_IO_NONALERT); +} + +Utils::OverlappedPipe Utils::CreateOverlappedPipe(DWORD bufferSize) +{ + const auto rnd = til::gen_random(); + const auto name = fmt::format(FMT_COMPILE(LR"(\\.\pipe\{:016x})"), rnd); + wil::unique_hfile rx{ CreateNamedPipeW(name.c_str(), PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_WAIT, 1, bufferSize, bufferSize, 0, nullptr) }; + THROW_LAST_ERROR_IF(!rx); + wil::unique_hfile tx{ CreateFileW(name.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, nullptr) }; + THROW_LAST_ERROR_IF(!tx); + return { std::move(tx), std::move(rx) }; +} + // Function Description: // - Generate a Version 5 UUID (specified in RFC4122 4.3) // v5 UUIDs are stable given the same namespace and "name". From 78ae6dda8078560f733617aa31ae01122883996e Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 4 Jul 2024 17:28:12 +0200 Subject: [PATCH 10/60] OVERLAPPED for stdin, Timeouts for PSEUDOCONSOLE_INHERIT_CURSOR --- src/host/VtInputThread.cpp | 156 ++++++++++-------- src/host/VtInputThread.hpp | 5 +- src/host/VtIo.cpp | 65 ++++---- src/inc/til/u8u16convert.h | 2 +- .../parser/InputStateMachineEngine.cpp | 17 +- .../parser/InputStateMachineEngine.hpp | 4 +- 6 files changed, 136 insertions(+), 113 deletions(-) diff --git a/src/host/VtInputThread.cpp b/src/host/VtInputThread.cpp index 502ac4dcf9d..fe19d9bcad0 100644 --- a/src/host/VtInputThread.cpp +++ b/src/host/VtInputThread.cpp @@ -2,17 +2,15 @@ // Licensed under the MIT license. #include "precomp.h" - #include "VtInputThread.hpp" +#include "handle.h" +#include "output.h" +#include "server.h" #include "../interactivity/inc/ServiceLocator.hpp" -#include "input.h" -#include "../terminal/parser/InputStateMachineEngine.hpp" #include "../terminal/adapter/InteractDispatch.hpp" -#include "../types/inc/convert.hpp" -#include "server.h" -#include "output.h" -#include "handle.h" +#include "../terminal/parser/InputStateMachineEngine.hpp" +#include "../types/inc/utils.hpp" using namespace Microsoft::Console; using namespace Microsoft::Console::Interactivity; @@ -23,26 +21,18 @@ using namespace Microsoft::Console::VirtualTerminal; // - hPipe - a handle to the file representing the read end of the VT pipe. // - inheritCursor - a bool indicating if the state machine should expect a // cursor positioning sequence. See MSFT:15681311. -VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe, - const bool inheritCursor) : - _hFile{ std::move(hPipe) }, - _hThread{}, - _u8State{}, - _dwThreadId{ 0 } +VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe, const bool inheritCursor) : + _hFile{ std::move(hPipe) } { THROW_HR_IF(E_HANDLE, _hFile.get() == INVALID_HANDLE_VALUE); auto dispatch = std::make_unique(); - auto engine = std::make_unique(std::move(dispatch), inheritCursor); - auto engineRef = engine.get(); + // we need this callback to be able to flush an unknown input sequence to the app + engine->SetFlushToInputQueueCallback([this] { return _pInputStateMachine->FlushToTerminal(); }); _pInputStateMachine = std::make_unique(std::move(engine)); - - // we need this callback to be able to flush an unknown input sequence to the app - auto flushCallback = [capture0 = _pInputStateMachine.get()] { return capture0->FlushToTerminal(); }; - engineRef->SetFlushToInputQueueCallback(flushCallback); } // Function Description: @@ -53,70 +43,94 @@ VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe, // - The return value of the underlying instance's _InputThread DWORD WINAPI VtInputThread::StaticVtInputThreadProc(_In_ LPVOID lpParameter) { - const auto pInstance = reinterpret_cast(lpParameter); + const auto pInstance = static_cast(lpParameter); pInstance->_InputThread(); return S_OK; } // Method Description: -// - Do a single ReadFile from our pipe, and try and handle it. If handling -// failed, throw or log, depending on what the caller wants. -// Return Value: -// - true if you should continue reading -bool VtInputThread::DoReadInput() +// - The ThreadProc for the VT Input Thread. Reads input from the pipe, and +// passes it to _HandleRunInput to be processed by the +// InputStateMachineEngine. +void VtInputThread::_InputThread() { char buffer[4096]; - DWORD dwRead = 0; - 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) - { - return false; - } - // 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))) - { - return true; - } + OVERLAPPED* overlapped = nullptr; + OVERLAPPED overlappedBuf{}; + wil::unique_event overlappedEvent; + bool overlappedPending = false; + DWORD read = 0; - try + if (Utils::HandleWantsOverlappedIo(_hFile.get())) { - // 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); + overlappedEvent.reset(CreateEventExW(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS)); + if (overlappedEvent) + { + overlappedBuf.hEvent = overlappedEvent.get(); + overlapped = &overlappedBuf; + } } - CATCH_LOG(); - - return true; -} -// Method Description: -// - The ThreadProc for the VT Input Thread. Reads input from the pipe, and -// passes it to _HandleRunInput to be processed by the -// InputStateMachineEngine. -void VtInputThread::_InputThread() -{ - while (DoReadInput()) + for (;;) { + if (!ReadFile(_hFile.get(), &buffer[0], sizeof(buffer), &read, overlapped)) + { + if (GetLastError() == ERROR_IO_PENDING) + { + overlappedPending = true; + } + else + { + break; + } + } + + // _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(); + } + + // If we're using overlapped IO, on the first iteration we'll skip the ProcessString() + // and call GetOverlappedResult() which blocks until it's done. + // On every other iteration we'll call ProcessString() while the IO is already queued up. + if (overlappedPending) + { + overlappedPending = false; + if (!GetOverlappedResult(_hFile.get(), overlapped, &read, TRUE)) + { + break; + } + } + + // 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) + { + break; + } + + // 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, gsl::narrow_cast(read) }, _wstr, _u8State)); } + ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIoNoCheck()->CloseInput(); } @@ -145,8 +159,8 @@ void VtInputThread::_InputThread() return S_OK; } -bool VtInputThread::IsLookingForDSR() const noexcept +void VtInputThread::WaitUntilDSR(DWORD timeout) const noexcept { const auto& engine = static_cast(_pInputStateMachine->Engine()); - return engine.IsLookingForDSR(); + engine.WaitUntilDSR(timeout); } diff --git a/src/host/VtInputThread.hpp b/src/host/VtInputThread.hpp index 10c7b00339e..7c4cff15eef 100644 --- a/src/host/VtInputThread.hpp +++ b/src/host/VtInputThread.hpp @@ -24,8 +24,7 @@ namespace Microsoft::Console VtInputThread(_In_ wil::unique_hfile hPipe, const bool inheritCursor); [[nodiscard]] HRESULT Start(); - bool DoReadInput(); - bool IsLookingForDSR() const noexcept; + void WaitUntilDSR(DWORD timeout) const noexcept; private: static DWORD WINAPI StaticVtInputThreadProc(_In_ LPVOID lpParameter); @@ -33,7 +32,7 @@ namespace Microsoft::Console wil::unique_hfile _hFile; wil::unique_handle _hThread; - DWORD _dwThreadId; + DWORD _dwThreadId = 0; std::unique_ptr _pInputStateMachine; til::u8state _u8State; diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index bd6083f666c..8edd5b66a21 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -89,8 +89,8 @@ using namespace Microsoft::Console::Interactivity; _overlappedEvent.reset(CreateEventExW(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS)); if (_overlappedEvent) { - _overlappedBuf.hEvent = _overlappedEvent.get(); - _overlapped = &_overlappedBuf; + _overlappedBuf.hEvent = _overlappedEvent.get(); + _overlapped = &_overlappedBuf; } } @@ -155,40 +155,43 @@ bool VtIo::IsUsingVt() const return S_FALSE; } - // 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 - // read input until we get a response. Terminals who request this behavior - // but don't respond will hang. - // If we get a response, the InteractDispatch will call SetCursorPosition, - // which will call to our VtIo::SetCursorPosition method. - // We need both handles for this initialization to work. If we don't have - // both, we'll skip it. They either aren't going to be reading output - // (so they can't get the DSR) or they can't write the response to us. - if (_pVtInputThread && _pVtInputThread->IsLookingForDSR()) - { - WriteUTF8("\x1b[6n"); // Cursor Position Report (DSR CPR) - while (_pVtInputThread->DoReadInput() && _pVtInputThread->IsLookingForDSR()) + if (_pVtInputThread) + { + LOG_IF_FAILED(_pVtInputThread->Start()); + } + + { + const auto cork = Cork(); + + // 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. + + // By default, DISABLE_NEWLINE_AUTO_RETURN is reset. This implies LNM being set, + // which is not the default in terminals, so we have to do that explicitly. + WriteUTF8( + "\x1b[20h" // Line Feed / New Line Mode (LNM) + "\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) { + WriteUTF8("\x1b[6n"); // Cursor Position Report (DSR CPR) } } - // 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. - - // By default, DISABLE_NEWLINE_AUTO_RETURN is reset. This implies LNM being set, - // which is not the default in terminals, so we have to do that explicitly. - WriteUTF8( - "\x1b[20h" // Line Feed / New Line Mode (LNM) - "\033[?1004h" // Focus Event Mode - "\033[?9001h" // Win32 Input Mode - ); - - if (_pVtInputThread) + if (_lookingForCursorPosition) { - LOG_IF_FAILED(_pVtInputThread->Start()); + _lookingForCursorPosition = false; + _pVtInputThread->WaitUntilDSR(1000); } if (_pPtySignalInputThread) diff --git a/src/inc/til/u8u16convert.h b/src/inc/til/u8u16convert.h index 69ee6b0558c..b0262204140 100644 --- a/src/inc/til/u8u16convert.h +++ b/src/inc/til/u8u16convert.h @@ -26,7 +26,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" // state structure for maintenance of UTF-8 partials struct u8state { - char partials[4]; + char partials[4]{}; uint8_t have{}; uint8_t want{}; diff --git a/src/terminal/parser/InputStateMachineEngine.cpp b/src/terminal/parser/InputStateMachineEngine.cpp index 3afe031e6f8..b82161ee89d 100644 --- a/src/terminal/parser/InputStateMachineEngine.cpp +++ b/src/terminal/parser/InputStateMachineEngine.cpp @@ -6,8 +6,9 @@ #include "stateMachine.hpp" #include "InputStateMachineEngine.hpp" +#include + #include "../../inc/unicode.hpp" -#include "ascii.hpp" #include "../../interactivity/inc/VtApiRedirection.hpp" using namespace Microsoft::Console::VirtualTerminal; @@ -102,9 +103,14 @@ InputStateMachineEngine::InputStateMachineEngine(std::unique_ptrMoveCursor(parameters.at(0), parameters.at(1)); // Right now we're only looking for on initial cursor // position response. After that, only look for F3. - _lookingForDSR = false; + _lookingForDSR.store(false, std::memory_order::relaxed); + til::atomic_notify_all(_lookingForDSR); break; } [[fallthrough]]; diff --git a/src/terminal/parser/InputStateMachineEngine.hpp b/src/terminal/parser/InputStateMachineEngine.hpp index 2cbbf240856..6da17b5bd82 100644 --- a/src/terminal/parser/InputStateMachineEngine.hpp +++ b/src/terminal/parser/InputStateMachineEngine.hpp @@ -132,7 +132,7 @@ namespace Microsoft::Console::VirtualTerminal InputStateMachineEngine(std::unique_ptr pDispatch, const bool lookingForDSR); - bool IsLookingForDSR() const noexcept; + void WaitUntilDSR(DWORD timeout) const noexcept; bool EncounteredWin32InputModeSequence() const noexcept override; @@ -166,7 +166,7 @@ namespace Microsoft::Console::VirtualTerminal private: const std::unique_ptr _pDispatch; std::function _pfnFlushToInputQueue; - bool _lookingForDSR; + std::atomic _lookingForDSR; bool _encounteredWin32InputModeSequence = false; DWORD _mouseButtonState = 0; std::chrono::milliseconds _doubleClickTime; From 7462a200979f23cdf02e8c97fcb5dff8a5dc4241 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 4 Jul 2024 17:28:50 +0200 Subject: [PATCH 11/60] Fix midi crash, Fix double DSR response --- src/host/outputStream.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index 4ed82977fb8..8e242a36a28 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.GetVtIo(nullptr)) + { + 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 @@ -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)); From 7c6c1824eba0924e203a9bb2a2c9fb67639586a6 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 4 Jul 2024 17:33:15 +0200 Subject: [PATCH 12/60] Spel --- src/terminal/parser/InputStateMachineEngine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/terminal/parser/InputStateMachineEngine.cpp b/src/terminal/parser/InputStateMachineEngine.cpp index b82161ee89d..cafbaa9b16e 100644 --- a/src/terminal/parser/InputStateMachineEngine.cpp +++ b/src/terminal/parser/InputStateMachineEngine.cpp @@ -106,7 +106,7 @@ InputStateMachineEngine::InputStateMachineEngine(std::unique_ptr Date: Fri, 5 Jul 2024 14:19:28 +0200 Subject: [PATCH 13/60] Fix synchronous reads --- src/host/VtInputThread.cpp | 56 ++++++++++++++++++++++++++------------ src/host/VtInputThread.hpp | 2 -- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/host/VtInputThread.cpp b/src/host/VtInputThread.cpp index fe19d9bcad0..746833dc26e 100644 --- a/src/host/VtInputThread.cpp +++ b/src/host/VtInputThread.cpp @@ -54,14 +54,16 @@ DWORD WINAPI VtInputThread::StaticVtInputThreadProc(_In_ LPVOID lpParameter) // InputStateMachineEngine. void VtInputThread::_InputThread() { - char buffer[4096]; - OVERLAPPED* overlapped = nullptr; OVERLAPPED overlappedBuf{}; wil::unique_event overlappedEvent; bool overlappedPending = false; + char buffer[4096]; 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)); @@ -72,24 +74,31 @@ void VtInputThread::_InputThread() } } + // 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 (;;) { - if (!ReadFile(_hFile.get(), &buffer[0], sizeof(buffer), &read, overlapped)) + // 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) { - if (GetLastError() == ERROR_IO_PENDING) + if (!ReadFile(_hFile.get(), &buffer[0], sizeof(buffer), &read, overlapped)) { + if (GetLastError() != ERROR_IO_PENDING) + { + break; + } overlappedPending = true; } - else - { - break; - } } - // _wstr can be empty in two situations: + // 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()) + if (!wstr.empty()) { try { @@ -101,18 +110,29 @@ void VtInputThread::_InputThread() LockConsole(); const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); - _pInputStateMachine->ProcessString(_wstr); + _pInputStateMachine->ProcessString(wstr); } CATCH_LOG(); } - // If we're using overlapped IO, on the first iteration we'll skip the ProcessString() - // and call GetOverlappedResult() which blocks until it's done. - // On every other iteration we'll call ProcessString() while the IO is already queued up. - if (overlappedPending) + // 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) + { + if (overlappedPending) + { + overlappedPending = false; + if (!GetOverlappedResult(_hFile.get(), overlapped, &read, TRUE)) + { + break; + } + } + } + else { - overlappedPending = false; - if (!GetOverlappedResult(_hFile.get(), overlapped, &read, TRUE)) + if (!ReadFile(_hFile.get(), &buffer[0], sizeof(buffer), &read, overlapped)) { break; } @@ -128,7 +148,7 @@ void VtInputThread::_InputThread() } // 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, gsl::narrow_cast(read) }, _wstr, _u8State)); + FAILED_LOG(til::u8u16({ buffer, gsl::narrow_cast(read) }, wstr, u8State)); } ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIoNoCheck()->CloseInput(); diff --git a/src/host/VtInputThread.hpp b/src/host/VtInputThread.hpp index 7c4cff15eef..e6f15e2ab68 100644 --- a/src/host/VtInputThread.hpp +++ b/src/host/VtInputThread.hpp @@ -35,7 +35,5 @@ namespace Microsoft::Console DWORD _dwThreadId = 0; std::unique_ptr _pInputStateMachine; - til::u8state _u8State; - std::wstring _wstr; }; } From c3d5ce14dda44749792b76a6338a00aca2a3ce7d Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 5 Jul 2024 14:27:50 +0200 Subject: [PATCH 14/60] Fix AuditMode, Unstage some unnecessary changes --- src/inc/til/rand.h | 1 + src/types/inc/utils.hpp | 9 +-------- src/types/utils.cpp | 12 ------------ 3 files changed, 2 insertions(+), 20 deletions(-) diff --git a/src/inc/til/rand.h b/src/inc/til/rand.h index b8b97eef868..64c2604b8c1 100644 --- a/src/inc/til/rand.h +++ b/src/inc/til/rand.h @@ -23,6 +23,7 @@ namespace til // cryptbase, instead it was using LoadLibrary()/GetProcAddress() on every call. // * advapi32.dll doesn't exist on MinWin, cryptbase.dll however does. module{ LoadLibraryExW(L"cryptbase.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32) }, +#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1). proc{ reinterpret_cast(GetProcAddress(module.get(), "SystemFunction036")) } { FAIL_FAST_LAST_ERROR_IF(!proc); diff --git a/src/types/inc/utils.hpp b/src/types/inc/utils.hpp index 3d062ffe7fb..a17d76a6550 100644 --- a/src/types/inc/utils.hpp +++ b/src/types/inc/utils.hpp @@ -15,12 +15,6 @@ Author(s): namespace Microsoft::Console::Utils { - struct OverlappedPipe - { - wil::unique_hfile tx; - wil::unique_hfile rx; - }; - // Function Description: // - Returns -1, 0 or +1 to indicate the sign of the passed-in value. template @@ -31,7 +25,6 @@ namespace Microsoft::Console::Utils bool IsValidHandle(const HANDLE handle) noexcept; bool HandleWantsOverlappedIo(HANDLE handle) noexcept; - OverlappedPipe CreateOverlappedPipe(DWORD bufferSize); // Function Description: // - Clamps a long in between `min` and `SHRT_MAX` @@ -93,7 +86,7 @@ namespace Microsoft::Console::Utils constexpr unsigned long EndianSwap(unsigned long value) { - return EndianSwap(static_cast(value)); + return gsl::narrow_cast(EndianSwap(gsl::narrow_cast(value))); } constexpr GUID EndianSwap(GUID value) diff --git a/src/types/utils.cpp b/src/types/utils.cpp index 93f9f9caf35..c053621a2ff 100644 --- a/src/types/utils.cpp +++ b/src/types/utils.cpp @@ -4,7 +4,6 @@ #include "precomp.h" #include "inc/utils.hpp" -#include #include #include @@ -659,17 +658,6 @@ bool Utils::HandleWantsOverlappedIo(HANDLE handle) noexcept return status == 0 && WI_AreAllFlagsClear(modeInfo.Mode, FILE_SYNCHRONOUS_IO_ALERT | FILE_SYNCHRONOUS_IO_NONALERT); } -Utils::OverlappedPipe Utils::CreateOverlappedPipe(DWORD bufferSize) -{ - const auto rnd = til::gen_random(); - const auto name = fmt::format(FMT_COMPILE(LR"(\\.\pipe\{:016x})"), rnd); - wil::unique_hfile rx{ CreateNamedPipeW(name.c_str(), PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_WAIT, 1, bufferSize, bufferSize, 0, nullptr) }; - THROW_LAST_ERROR_IF(!rx); - wil::unique_hfile tx{ CreateFileW(name.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, nullptr) }; - THROW_LAST_ERROR_IF(!tx); - return { std::move(tx), std::move(rx) }; -} - // Function Description: // - Generate a Version 5 UUID (specified in RFC4122 4.3) // v5 UUIDs are stable given the same namespace and "name". From 0e51acd5d9c19b1cdde0fabcd024759f9bd941a1 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 5 Jul 2024 16:01:16 +0200 Subject: [PATCH 15/60] Fix conhost feature tests --- src/host/_output.cpp | 4 ++-- src/host/directio.cpp | 11 ++++++++--- src/host/ft_host/API_OutputTests.cpp | 23 +++++++++-------------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/host/_output.cpp b/src/host/_output.cpp index c10763ea792..f5e867eca67 100644 --- a/src/host/_output.cpp +++ b/src/host/_output.cpp @@ -205,10 +205,10 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon switch (mode) { case FillConsoleMode::WriteAttribute: - it = OutputCellIterator(TextAttribute(*data), lengthToWrite); + it = OutputCellIterator({ data, lengthToWrite }); break; case FillConsoleMode::WriteCharacter: - it = OutputCellIterator(*data, lengthToWrite); + it = OutputCellIterator({ reinterpret_cast(data), lengthToWrite }); break; case FillConsoleMode::FillAttribute: it = OutputCellIterator(TextAttribute(*data), lengthToWrite); diff --git a/src/host/directio.cpp b/src/host/directio.cpp index 6e876bbf93c..5608a70f422 100644 --- a/src/host/directio.cpp +++ b/src/host/directio.cpp @@ -615,7 +615,6 @@ CATCH_RETURN(); try { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto io = gci.GetVtIo(&context); auto& storageBuffer = context.GetActiveBuffer(); @@ -630,6 +629,7 @@ CATCH_RETURN(); const auto offsetY = writeRectangle.Top() - requestRectangle.Top(); const auto offsetX = writeRectangle.Left() - requestRectangle.Left(); + const auto width = writeRectangle.Width(); auto totalOffset = offsetY * bufferStride + offsetX; auto target = writeRectangle.Origin(); @@ -638,8 +638,13 @@ CATCH_RETURN(); // a smaller view over the existing big blob of data from the original call. for (; target.y <= writeRectangle.BottomInclusive(); target.y++) { - // Now we make a subspan starting from that offset for as much of the original request as would fit - const auto charInfos = buffer.subspan(totalOffset, writeRectangle.Width()); + // subspan() does not perform any bounds checks. + if ((totalOffset + width) > buffer.size()) + { + return E_INVALIDARG; + } + + const auto charInfos = buffer.subspan(totalOffset, width); // Make the iterator and write to the target position. storageBuffer.Write(OutputCellIterator(charInfos), target); diff --git a/src/host/ft_host/API_OutputTests.cpp b/src/host/ft_host/API_OutputTests.cpp index baa539c22e9..5b8d4cd571e 100644 --- a/src/host/ft_host/API_OutputTests.cpp +++ b/src/host/ft_host/API_OutputTests.cpp @@ -159,15 +159,16 @@ void OutputTests::WriteConsoleOutputWOutsideBuffer() // Call the API and confirm results. // move outside in X and Y directions - auto shiftedRegion = region; - shiftedRegion.Left += bufferSize.X; - shiftedRegion.Right += bufferSize.X; - shiftedRegion.Top += bufferSize.Y; - shiftedRegion.Bottom += bufferSize.Y; - - auto affected = shiftedRegion; + auto affected = region; + affected.Left += bufferSize.X; + affected.Right += bufferSize.X; + affected.Top += bufferSize.Y; + affected.Bottom += bufferSize.Y; + auto expected = affected; + expected.Right = expected.Left - 1; + expected.Bottom = expected.Top - 1; VERIFY_WIN32_BOOL_SUCCEEDED(WriteConsoleOutputW(consoleOutputHandle, buffer.data(), regionDimensions, regionOrigin, &affected)); - VERIFY_ARE_EQUAL(shiftedRegion, affected); + VERIFY_ARE_EQUAL(expected, affected); // Read the entire buffer back and validate that we didn't write anything anywhere const auto readBack = std::make_unique(sbiex.dwSize.X * sbiex.dwSize.Y); @@ -363,12 +364,6 @@ void OutputTests::WriteConsoleOutputWNegativePositions() VERIFY_ARE_EQUAL(expectedItem, readItem); } } - - // Set the region so the left will end up past the right - adjustedRegion = region; - adjustedRegion.Left = -(adjustedRegion.Right + 1); - affected = adjustedRegion; - VERIFY_WIN32_BOOL_FAILED(WriteConsoleOutputW(consoleOutputHandle, buffer.data(), regionDimensions, regionOrigin, &affected)); } void OutputTests::WriteConsoleOutputCharacterWRunoff() From 6a13e5aabc79d95041a368af8ca5f23bd750685f Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 5 Jul 2024 17:58:41 +0200 Subject: [PATCH 16/60] A common C++ L --- src/host/_output.cpp | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/host/_output.cpp b/src/host/_output.cpp index f5e867eca67..c2e08cd65e0 100644 --- a/src/host/_output.cpp +++ b/src/host/_output.cpp @@ -64,7 +64,7 @@ struct FillConsoleResult til::CoordType cellsModified = 0; }; -static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillConsoleMode mode, const uint16_t* data, const size_t lengthToWrite, const til::point startingCoordinate) +static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillConsoleMode mode, const void* data, const size_t lengthToWrite, const til::point startingCoordinate) { if (lengthToWrite == 0) { @@ -80,6 +80,20 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon const auto bufferSize = screenBuffer.GetBufferSize(); FillConsoleResult result; + // 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. + // + // Constructing an OutputCellIterator with a `wchar_t` takes the `wchar_t` by reference, so that it can reference + // it in a `wstring_view` forever. That's of course really bad because passing a `const uint16_t&` to a + // `const wchar_t&` argument implicitly converts the types. To do so, the implicit conversion allocates a + // `wchar_t` value on the stack. The lifetime of that copy DOES NOT get extended beyond the constructor call. + // The result is that OutputCellIterator would read random data from the stack. + // + // Don't ever assume the lifetime of implicitly convertible types given by reference. + // Ironically that's a bug that cannot happen with C pointers. To no ones surprise, C keeps on winning. + auto attrs = static_cast(data); + auto chars = static_cast(data); + if (!bufferSize.IsInBounds(startingCoordinate)) { return {}; @@ -114,13 +128,13 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon case FillConsoleMode::WriteAttribute: for (size_t i = 0; i < len; ++i) { - infos[i].Attributes = *data++; + infos[i].Attributes = *attrs++; } break; case FillConsoleMode::WriteCharacter: for (size_t i = 0; i < len;) { - const auto ch = *data++; + const auto ch = *chars++; auto& lead = infos[i++]; lead.Char.UnicodeChar = ch; @@ -138,7 +152,7 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon break; case FillConsoleMode::FillAttribute: { - const auto attr = *data; + const auto attr = *attrs; for (size_t i = 0; i < len; ++i) { infos[i].Attributes = attr; @@ -147,7 +161,7 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon } case FillConsoleMode::FillCharacter: { - const auto ch = *data; + const auto ch = *chars; if (IsGlyphFullWidth(ch)) { @@ -205,16 +219,16 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon switch (mode) { case FillConsoleMode::WriteAttribute: - it = OutputCellIterator({ data, lengthToWrite }); + it = OutputCellIterator({ attrs, lengthToWrite }); break; case FillConsoleMode::WriteCharacter: - it = OutputCellIterator({ reinterpret_cast(data), lengthToWrite }); + it = OutputCellIterator({ chars, lengthToWrite }); break; case FillConsoleMode::FillAttribute: - it = OutputCellIterator(TextAttribute(*data), lengthToWrite); + it = OutputCellIterator(TextAttribute(*attrs), lengthToWrite); break; case FillConsoleMode::FillCharacter: - it = OutputCellIterator(*data, lengthToWrite); + it = OutputCellIterator(*chars, lengthToWrite); break; default: __assume(false); @@ -299,7 +313,7 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon LockConsole(); const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); - used = FillConsoleImpl(OutContext, FillConsoleMode::WriteCharacter, reinterpret_cast(chars.data()), chars.size(), target).lengthRead; + used = FillConsoleImpl(OutContext, FillConsoleMode::WriteCharacter, chars.data(), chars.size(), target).lengthRead; } CATCH_RETURN(); @@ -423,7 +437,7 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon } } - cellsModified = FillConsoleImpl(OutContext, FillConsoleMode::FillCharacter, reinterpret_cast(&character), lengthToWrite, startingCoordinate).lengthRead; + cellsModified = FillConsoleImpl(OutContext, FillConsoleMode::FillCharacter, &character, lengthToWrite, startingCoordinate).lengthRead; return S_OK; } CATCH_RETURN(); From a3f5d59ebea11e26889b4119f181e2f870b48dc4 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 5 Jul 2024 18:48:11 +0200 Subject: [PATCH 17/60] x86 wants its __stdcall back --- src/types/utils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/utils.cpp b/src/types/utils.cpp index c053621a2ff..ef5943798f6 100644 --- a/src/types/utils.cpp +++ b/src/types/utils.cpp @@ -630,7 +630,7 @@ bool Utils::IsValidHandle(const HANDLE handle) noexcept } // From ntifs.h, which isn't part of the regular Windows SDK (they're in the driver SDK (WDK)). -__kernel_entry NTSYSCALLAPI NTSTATUS NtQueryInformationFile( +NTSYSAPI NTSTATUS NTAPI NtQueryInformationFile( _In_ HANDLE FileHandle, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _Out_ PVOID FileInformation, From a138b284a4d38033bb9c79893ebc55e7c6723e5a Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sat, 6 Jul 2024 02:52:02 +0200 Subject: [PATCH 18/60] Fix spelling --- .github/actions/spelling/allow/apis.txt | 3 ++- src/types/utils.cpp | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) 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/src/types/utils.cpp b/src/types/utils.cpp index ef5943798f6..e7dd163f68d 100644 --- a/src/types/utils.cpp +++ b/src/types/utils.cpp @@ -630,10 +630,10 @@ bool Utils::IsValidHandle(const HANDLE handle) noexcept } // From ntifs.h, which isn't part of the regular Windows SDK (they're in the driver SDK (WDK)). -NTSYSAPI NTSTATUS NTAPI NtQueryInformationFile( +__kernel_entry NTSYSCALLAPI NTSTATUS NTAPI NtQueryInformationFile( _In_ HANDLE FileHandle, _Out_ PIO_STATUS_BLOCK IoStatusBlock, - _Out_ PVOID FileInformation, + _Out_writes_bytes_(Length) PVOID FileInformation, _In_ ULONG Length, _In_ FILE_INFORMATION_CLASS FileInformationClass); From 1047ed4f1a4a52a8f4434d67337f169cc573709d Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sat, 6 Jul 2024 03:42:15 +0200 Subject: [PATCH 19/60] A way simpler ReadConsoleOutputWImplHelper, because why not --- src/host/_output.cpp | 3 +- src/host/directio.cpp | 144 +++++++++++++++--------------------------- src/host/directio.h | 7 +- src/host/getset.cpp | 2 +- 4 files changed, 60 insertions(+), 96 deletions(-) diff --git a/src/host/_output.cpp b/src/host/_output.cpp index c2e08cd65e0..62ce62e7067 100644 --- a/src/host/_output.cpp +++ b/src/host/_output.cpp @@ -75,7 +75,6 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto routines = ServiceLocator::LocateGlobals().api; auto& screenBuffer = screenInfo.GetActiveBuffer(); const auto bufferSize = screenBuffer.GetBufferSize(); FillConsoleResult result; @@ -121,7 +120,7 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon end = beg + gsl::narrow_cast(len); auto viewport = Viewport::FromInclusive({ beg, y, end - 1, y }); - THROW_IF_FAILED(routines->ReadConsoleOutputWImpl(screenInfo, infos, viewport, unused)); + THROW_IF_FAILED(ReadConsoleOutputWImplHelper(screenInfo, infos, viewport, unused)); switch (mode) { diff --git a/src/host/directio.cpp b/src/host/directio.cpp index 5608a70f422..96be5baaa45 100644 --- a/src/host/directio.cpp +++ b/src/host/directio.cpp @@ -463,98 +463,53 @@ CATCH_RETURN(); return result; } -[[nodiscard]] static HRESULT _ReadConsoleOutputWImplHelper(const SCREEN_INFORMATION& context, - std::span targetBuffer, - const Microsoft::Console::Types::Viewport& requestRectangle, - Microsoft::Console::Types::Viewport& readRectangle) noexcept +[[nodiscard]] HRESULT ReadConsoleOutputWImplHelper(const SCREEN_INFORMATION& context, + std::span targetBuffer, + const Viewport& requestRectangle, + Viewport& readRectangle) noexcept { try { const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto& storageBuffer = context.GetActiveBuffer().GetTextBuffer(); - const auto storageSize = storageBuffer.GetSize().Dimensions(); - - const auto targetSize = requestRectangle.Dimensions(); + auto& storageBuffer = context.GetActiveBuffer(); + const auto storageRectangle = storageBuffer.GetBufferSize(); + const auto clippedRectangle = storageRectangle.Clamp(requestRectangle); - // If either dimension of the request is too small, return an empty rectangle as read and exit early. - if (targetSize.width <= 0 || targetSize.height <= 0) + if (!clippedRectangle.IsValid()) { readRectangle = Viewport::FromDimensions(requestRectangle.Origin(), { 0, 0 }); return S_OK; } - // The buffer given should be big enough to hold the dimensions of the request. - const auto targetArea = targetSize.area(); - RETURN_HR_IF(E_INVALIDARG, targetArea > targetBuffer.size()); - - auto clip = requestRectangle.ToExclusive(); - - // Find the target point (where to write the user's buffer) - // It will either be 0,0 or offset into the buffer by the inverse of the negative values. - til::point targetPoint; - targetPoint.x = clip.left < 0 ? -clip.left : 0; - targetPoint.y = clip.top < 0 ? -clip.top : 0; - - // The clipped rect must be inside the buffer size, so it has a minimum value of 0. (max of itself and 0) - clip.left = std::max(clip.left, 0); - clip.top = std::max(clip.top, 0); - - // Clip the request rectangle to the size of the storage buffer - clip.right = std::clamp(clip.right, clip.left, storageSize.width); - clip.bottom = std::clamp(clip.bottom, clip.top, storageSize.height); - - // The final "request rectangle" or the area inside the buffer we want to read, is the clipped dimensions. - const auto clippedRequestRectangle = Viewport::FromExclusive(clip); + const auto bufferStride = gsl::narrow_cast(std::max(0, requestRectangle.Width())); + const auto width = gsl::narrow_cast(clippedRectangle.Width()); + const auto offsetY = clippedRectangle.Top() - requestRectangle.Top(); + const auto offsetX = clippedRectangle.Left() - requestRectangle.Left(); + // We always write the intersection between the valid `storageRectangle` and the given `requestRectangle`. + // This means that if the `requestRectangle` is -3 rows above the top of the buffer, we'll start + // reading from `buffer` at row offset 3, because the first 3 are outside the valid range. + // clippedRectangle.Top/Left() cannot be negative due to the previous Clamp() call. + auto totalOffset = offsetY * bufferStride + offsetX; - if (!clippedRequestRectangle.IsValid()) + if (bufferStride <= 0 || targetBuffer.size() < (clippedRectangle.Height() * bufferStride)) { - readRectangle = Viewport::FromDimensions(clippedRequestRectangle.Origin(), { 0, 0 }); - return S_OK; + return E_INVALIDARG; } - // We will start reading the buffer at the point of the top left corner (origin) of the (potentially adjusted) request - const auto sourcePoint = clippedRequestRectangle.Origin(); - - // Get an iterator to the beginning of the return buffer - // We might have to seek this forward or skip around if we clipped the request. - auto targetIter = targetBuffer.begin(); - til::point targetPos; - const auto targetLimit = Viewport::FromDimensions(targetPoint, clippedRequestRectangle.Dimensions()); - - // Get an iterator to the beginning of the request inside the screen buffer - // This should walk exactly along every cell of the clipped request. - auto sourceIter = storageBuffer.GetCellDataAt(sourcePoint, clippedRequestRectangle); - - // Walk through every cell of the target, advancing the buffer. - // Validate that we always still have a valid iterator to the backing store, - // that we always are writing inside the user's buffer (before the end) - // and we're always targeting the user's buffer inside its original bounds. - while (sourceIter && targetIter < targetBuffer.end()) + for (til::CoordType y = clippedRectangle.Top(); y <= clippedRectangle.BottomInclusive(); y++) { - // If the point we're trying to write is inside the limited buffer write zone... - if (targetLimit.IsInBounds(targetPos)) - { - // Copy the data into position... - *targetIter = gci.AsCharInfo(*sourceIter); - // ... and advance the read iterator. - ++sourceIter; - } + auto it = storageBuffer.GetCellDataAt({ clippedRectangle.Left(), y }); - // Always advance the write iterator, we might have skipped it due to clipping. - ++targetIter; - - // Increment the target - targetPos.x++; - if (targetPos.x >= targetSize.width) + for (size_t i = 0; i < width; i++) { - targetPos.x = 0; - targetPos.y++; + targetBuffer[totalOffset + i] = gci.AsCharInfo(*it); + ++it; } - } - // Reply with the region we read out of the backing buffer (potentially clipped) - readRectangle = clippedRequestRectangle; + totalOffset += bufferStride; + } + readRectangle = clippedRectangle; return S_OK; } CATCH_RETURN(); @@ -573,7 +528,7 @@ CATCH_RETURN(); const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto codepage = gci.OutputCP; - RETURN_IF_FAILED(_ReadConsoleOutputWImplHelper(context, buffer, sourceRectangle, readRectangle)); + RETURN_IF_FAILED(ReadConsoleOutputWImplHelper(context, buffer, sourceRectangle, readRectangle)); LOG_IF_FAILED(_ConvertCellsToAInplace(codepage, buffer, readRectangle)); @@ -592,7 +547,7 @@ CATCH_RETURN(); try { - RETURN_IF_FAILED(_ReadConsoleOutputWImplHelper(context, buffer, sourceRectangle, readRectangle)); + RETURN_IF_FAILED(ReadConsoleOutputWImplHelper(context, buffer, sourceRectangle, readRectangle)); if (!context.GetActiveBuffer().GetCurrentFont().IsTrueTypeFont()) { @@ -608,43 +563,48 @@ CATCH_RETURN(); [[nodiscard]] HRESULT WriteConsoleOutputWImplHelper(SCREEN_INFORMATION& context, std::span buffer, - size_t bufferStride, + til::CoordType bufferStride, const Viewport& requestRectangle, Viewport& writtenRectangle) noexcept { try { + if (bufferStride <= 0) + { + return E_INVALIDARG; + } + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); const auto io = gci.GetVtIo(&context); auto& storageBuffer = context.GetActiveBuffer(); const auto storageRectangle = storageBuffer.GetBufferSize(); - const auto writeRectangle = storageRectangle.Clamp(requestRectangle); + const auto clippedRectangle = storageRectangle.Clamp(requestRectangle); - if (!writeRectangle.IsValid()) + if (!clippedRectangle.IsValid()) { writtenRectangle = Viewport::FromDimensions(requestRectangle.Origin(), { 0, 0 }); return S_OK; } - const auto offsetY = writeRectangle.Top() - requestRectangle.Top(); - const auto offsetX = writeRectangle.Left() - requestRectangle.Left(); - const auto width = writeRectangle.Width(); + const auto width = clippedRectangle.Width(); + // We always write the intersection between the valid `storageRectangle` and the given `requestRectangle`. + // This means that if the `requestRectangle` is -3 rows above the top of the buffer, we'll start + // reading from `buffer` at row offset 3, because the first 3 are outside the valid range. + // clippedRectangle.Top/Left() cannot be negative due to the previous Clamp() call. + const auto offsetY = clippedRectangle.Top() - requestRectangle.Top(); + const auto offsetX = clippedRectangle.Left() - requestRectangle.Left(); auto totalOffset = offsetY * bufferStride + offsetX; - auto target = writeRectangle.Origin(); - // For every row in the request, create a view into the clamped portion of just the one line to write. - // This allows us to restrict the width of the call without allocating/copying any memory by just making - // a smaller view over the existing big blob of data from the original call. - for (; target.y <= writeRectangle.BottomInclusive(); target.y++) + if (bufferStride <= 0 || buffer.size() < (clippedRectangle.Height() * bufferStride)) { - // subspan() does not perform any bounds checks. - if ((totalOffset + width) > buffer.size()) - { - return E_INVALIDARG; - } + return E_INVALIDARG; + } + for (til::CoordType y = clippedRectangle.Top(); y <= clippedRectangle.BottomInclusive(); y++) + { const auto charInfos = buffer.subspan(totalOffset, width); + const til::point target{ clippedRectangle.Left(), y }; // Make the iterator and write to the target position. storageBuffer.Write(OutputCellIterator(charInfos), target); @@ -658,10 +618,10 @@ CATCH_RETURN(); } // If we've overwritten image content, it needs to be erased. - ImageSlice::EraseBlock(storageBuffer.GetTextBuffer(), writeRectangle.ToExclusive()); + ImageSlice::EraseBlock(storageBuffer.GetTextBuffer(), clippedRectangle.ToExclusive()); // Since we've managed to write part of the request, return the clamped part that we actually used. - writtenRectangle = writeRectangle; + writtenRectangle = clippedRectangle; return S_OK; } diff --git a/src/host/directio.h b/src/host/directio.h index 0307fbe0c83..d4864f501f2 100644 --- a/src/host/directio.h +++ b/src/host/directio.h @@ -20,9 +20,14 @@ Revision History: class SCREEN_INFORMATION; +[[nodiscard]] HRESULT ReadConsoleOutputWImplHelper(const SCREEN_INFORMATION& context, + std::span targetBuffer, + const Microsoft::Console::Types::Viewport& requestRectangle, + Microsoft::Console::Types::Viewport& readRectangle) noexcept; + [[nodiscard]] HRESULT WriteConsoleOutputWImplHelper(SCREEN_INFORMATION& context, std::span buffer, - size_t bufferStride, + til::CoordType bufferStride, const Microsoft::Console::Types::Viewport& requestRectangle, Microsoft::Console::Types::Viewport& writtenRectangle) noexcept; diff --git a/src/host/getset.cpp b/src/host/getset.cpp index 9080c01356c..a8b41cadd5f 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -1044,7 +1044,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont backup.resize(a, CHAR_INFO{ fillCharacter, fillAttribute }); fill.resize(a, CHAR_INFO{ fillCharacter, fillAttribute }); - RETURN_IF_FAILED(ReadConsoleOutputWImpl(context, backup, sourceViewport, readViewport)); + 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)); From b78e5bc0449ccc8e508c6a15ca95d797797efe66 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sun, 7 Jul 2024 03:44:48 +0200 Subject: [PATCH 20/60] Fix x86 build, Fix a missing FMT_COMPILE --- src/host/VtIo.cpp | 2 +- src/host/directio.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index 8edd5b66a21..ba9b0d3589f 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -422,7 +422,7 @@ void VtIo::WriteUCS2(wchar_t ch) // CUP: Cursor Position void VtIo::WriteCUP(til::point position) { - WriteFormat("\x1b[{};{}H", position.y + 1, position.x + 1); + WriteFormat(FMT_COMPILE("\x1b[{};{}H"), position.y + 1, position.x + 1); } // DECTCEM: Text Cursor Enable diff --git a/src/host/directio.cpp b/src/host/directio.cpp index 96be5baaa45..0636baca068 100644 --- a/src/host/directio.cpp +++ b/src/host/directio.cpp @@ -491,7 +491,7 @@ CATCH_RETURN(); // clippedRectangle.Top/Left() cannot be negative due to the previous Clamp() call. auto totalOffset = offsetY * bufferStride + offsetX; - if (bufferStride <= 0 || targetBuffer.size() < (clippedRectangle.Height() * bufferStride)) + if (bufferStride <= 0 || targetBuffer.size() < gsl::narrow_cast(clippedRectangle.Height() * bufferStride)) { return E_INVALIDARG; } @@ -596,7 +596,7 @@ CATCH_RETURN(); const auto offsetX = clippedRectangle.Left() - requestRectangle.Left(); auto totalOffset = offsetY * bufferStride + offsetX; - if (bufferStride <= 0 || buffer.size() < (clippedRectangle.Height() * bufferStride)) + if (bufferStride <= 0 || buffer.size() < gsl::narrow_cast(clippedRectangle.Height() * bufferStride)) { return E_INVALIDARG; } From 6b254492be60f0aeca86c8adfde66642b1161ba0 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sun, 7 Jul 2024 17:38:57 +0200 Subject: [PATCH 21/60] Remove AlwaysAcceptC1 --- src/cascadia/TerminalCore/Terminal.cpp | 8 -------- src/terminal/parser/stateMachine.cpp | 2 +- src/terminal/parser/stateMachine.hpp | 1 - 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index c6301dd1eeb..dc3afe4d4d2 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/terminal/parser/stateMachine.cpp b/src/terminal/parser/stateMachine.cpp index 38dea307072..2e9fb9f5e7d 100644 --- a/src/terminal/parser/stateMachine.cpp +++ b/src/terminal/parser/stateMachine.cpp @@ -1858,7 +1858,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)); diff --git a/src/terminal/parser/stateMachine.hpp b/src/terminal/parser/stateMachine.hpp index 245e25fefa6..3ecbbebaebe 100644 --- a/src/terminal/parser/stateMachine.hpp +++ b/src/terminal/parser/stateMachine.hpp @@ -55,7 +55,6 @@ namespace Microsoft::Console::VirtualTerminal enum class Mode : size_t { AcceptC1, - AlwaysAcceptC1, Ansi, }; From d3b4592b7188cc157a747d0e2cd252f1b9f126ab Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sun, 7 Jul 2024 17:42:01 +0200 Subject: [PATCH 22/60] Remove IsConsolePty --- src/cascadia/TerminalCore/Terminal.hpp | 1 - src/cascadia/TerminalCore/TerminalApi.cpp | 5 -- src/host/outputStream.cpp | 13 +--- src/host/outputStream.hpp | 1 - src/terminal/adapter/ITerminalApi.hpp | 1 - src/terminal/adapter/adaptDispatch.cpp | 60 ++++--------------- src/terminal/adapter/adaptDispatch.hpp | 1 - .../adapter/ut_adapter/adapterTest.cpp | 6 -- 8 files changed, 14 insertions(+), 74 deletions(-) diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 6614784ec6d..2c4b72fe9d3 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -149,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; 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/host/outputStream.cpp b/src/host/outputStream.cpp index 8e242a36a28..4af547db2a2 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -350,7 +350,7 @@ bool ConhostInternalGetSet::ResizeWindow(const til::CoordType sColumns, const ti // to do this in pty mode, because the conpty resize operation is dependent // on the viewport *not* being adjusted. const auto cursorOverflow = csbiex.dwCursorPosition.Y - newViewport.BottomInclusive(); - if (cursorOverflow > 0 && !IsConsolePty()) + if (cursorOverflow > 0) { newViewport = Viewport::Offset(newViewport, { 0, cursorOverflow }); } @@ -367,17 +367,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().GetVtIo(nullptr); -} - // 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/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/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index c4f7bee3b60..0c11a05c157 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -1551,14 +1551,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"); - } return true; } @@ -1853,7 +1846,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 +1886,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 +1911,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: @@ -1963,7 +1939,7 @@ 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 true; @@ -1982,10 +1958,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); @@ -2008,27 +1984,25 @@ 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; + 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; @@ -2108,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)); - } break; case DispatchTypes::ModeParams::DECSCNM_ScreenMode: state = mapTemp(_renderSettings.GetRenderMode(RenderSettings::Mode::ScreenReversed)); @@ -2133,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)); - } break; case DispatchTypes::ModeParams::DECPCCM_PageCursorCouplingMode: state = mapTemp(_modes.test(Mode::PageCursorCoupling)); @@ -2215,7 +2181,7 @@ bool AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param) bool AdaptDispatch::SetKeypadMode(const bool fApplicationMode) { _terminalInput.SetInputMode(TerminalInput::Mode::Keypad, fApplicationMode); - return !_PassThroughInputModes(); + return true; } // Routine Description: diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 3de48d4e78e..941449925eb 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -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(); diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index da5a7c93cb2..a61167ece99 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..."); From 976343e1abc7327c1bf8f252e61a184667f80929 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sun, 7 Jul 2024 17:42:09 +0200 Subject: [PATCH 23/60] Fix the cursor reset when entering the ASB twice --- src/cascadia/TerminalCore/TerminalApi.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 5982d184c52..93cb5bdd034 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -193,7 +193,10 @@ void Terminal::PlayMidiNote(const int noteNumber, const int velocity, const std: void Terminal::UseAlternateScreenBuffer(const TextAttribute& attrs) { - _assertLocked(); + if (_inAltBuffer()) + { + return; + } // the new alt buffer is exactly the size of the viewport. _altBufferSize = _mutableViewport.Dimensions(); From eaf364ec37484f7b687c2e3da26bee811ddc5327 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sun, 7 Jul 2024 17:42:57 +0200 Subject: [PATCH 24/60] Fix the injection of ConPTY modes --- src/host/_stream.cpp | 58 ++++++++++++-------------- src/terminal/adapter/adaptDispatch.cpp | 8 ++-- src/terminal/parser/stateMachine.cpp | 11 +++++ src/terminal/parser/stateMachine.hpp | 18 +++++++- 4 files changed, 59 insertions(+), 36 deletions(-) diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index 84397c5411d..f1e2e6cf036 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -23,8 +23,8 @@ #include "../interactivity/inc/ServiceLocator.hpp" using namespace Microsoft::Console::Types; +using namespace Microsoft::Console::VirtualTerminal; using Microsoft::Console::Interactivity::ServiceLocator; -using Microsoft::Console::VirtualTerminal::StateMachine; constexpr bool controlCharPredicate(wchar_t wch) { @@ -339,42 +339,38 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str) { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto& stateMachine = screenInfo.GetStateMachine(); + + stateMachine.ProcessString(str); if (const auto io = gci.GetVtIo(&screenInfo)) { - using Mode = Microsoft::Console::VirtualTerminal::TerminalInput::Mode; - - auto& terminalInput = gci.GetActiveInputBuffer()->GetTerminalInput(); - - // These two modes are ones that should always stay enabled when we're ConPTY. - // To figure out whether some VT sequence disabled them (primarily RIS = ESC c), - // we temporarily enable them, restore them, and check if they got reset in between. - const auto beforeFocusEvent = terminalInput.GetInputMode(Mode::FocusEvent); - const auto beforeWin32 = terminalInput.GetInputMode(Mode::Win32); - terminalInput.SetInputMode(Mode::FocusEvent, true); - terminalInput.SetInputMode(Mode::Win32, true); - - screenInfo.GetStateMachine().ProcessString(str); - - const auto afterFocusEvent = terminalInput.GetInputMode(Mode::FocusEvent); - const auto afterWin32 = terminalInput.GetInputMode(Mode::Win32); - terminalInput.SetInputMode(Mode::FocusEvent, beforeFocusEvent); - terminalInput.SetInputMode(Mode::Win32, beforeWin32); - const auto cork = io->Cork(); - io->WriteUTF16(str); - if (!afterFocusEvent) - { - io->WriteUTF8("\033[?1004h"); - } - if (!afterWin32) + const auto& injections = stateMachine.GetInjections(); + size_t offset = 0; + + for (const auto& injection : injections) { - io->WriteUTF8("\033[?9001h"); + io->WriteUTF16(til::safe_slice_abs(str, offset, injection.offset)); + offset = injection.offset; + + switch (injection.type) + { + case InjectionType::RIS: + io->WriteUTF8( + "\033[?1004h" // Focus Event Mode + "\033[?9001h" // Win32 Input Mode + ); + break; + case InjectionType::DECSET_FOCUS: + io->WriteUTF8( + "\033[?1004h" // Focus Event Mode + ); + break; + } } - } - else - { - screenInfo.GetStateMachine().ProcessString(str); + + io->WriteUTF16(til::safe_slice_abs(str, offset, std::wstring_view::npos)); } } diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 0c11a05c157..b727444d0c2 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -1551,7 +1551,7 @@ bool AdaptDispatch::DeviceAttributes() // 32 = Text macros // 42 = ISO Latin-2 character set - _api.ReturnResponse(L"\x1b[?61;1;4;6;7;14;21;22;23;24;28;32;42c"); + _api.ReturnResponse(L"\x1b[?61;1;4;6;7;14;21;22;23;24;28;32;42c"); return true; } @@ -1999,6 +1999,7 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con return true; case DispatchTypes::ModeParams::FOCUS_EVENT_MODE: _terminalInput.SetInputMode(TerminalInput::Mode::FocusEvent, enable); + _api.GetStateMachine().InjectSequence(InjectionType::DECSET_FOCUS); return true; case DispatchTypes::ModeParams::ALTERNATE_SCROLL: _terminalInput.SetInputMode(TerminalInput::Mode::AlternateScroll, enable); @@ -2082,7 +2083,7 @@ bool AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param) state = mapTemp(_api.GetStateMachine().GetParserMode(StateMachine::Mode::Ansi)); break; case DispatchTypes::ModeParams::DECCOLM_SetNumberOfColumns: - 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)); @@ -2103,7 +2104,7 @@ bool AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param) state = mapTemp(_pages.ActivePage().Cursor().IsVisible()); break; case DispatchTypes::ModeParams::XTERM_EnableDECCOLMSupport: - state = mapTemp(_modes.test(Mode::AllowDECCOLM)); + state = mapTemp(_modes.test(Mode::AllowDECCOLM)); break; case DispatchTypes::ModeParams::DECPCCM_PageCursorCouplingMode: state = mapTemp(_modes.test(Mode::PageCursorCoupling)); @@ -3241,6 +3242,7 @@ bool AdaptDispatch::HardReset() _macroBuffer = nullptr; } + _api.GetStateMachine().InjectSequence(InjectionType::RIS); return true; } diff --git a/src/terminal/parser/stateMachine.cpp b/src/terminal/parser/stateMachine.cpp index 2e9fb9f5e7d..8d197ae50bf 100644 --- a/src/terminal/parser/stateMachine.cpp +++ b/src/terminal/parser/stateMachine.cpp @@ -2090,6 +2090,7 @@ void StateMachine::ProcessString(const std::wstring_view string) _currentString = string; _runOffset = 0; _runSize = 0; + _injections.clear(); if (_state != VTStates::Ground) { @@ -2206,6 +2207,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 3ecbbebaebe..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 @@ -65,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; @@ -211,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. From c92c5cf716574f6ca780e65d05922139db62b277 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sun, 7 Jul 2024 18:07:34 +0200 Subject: [PATCH 25/60] Fixed cls->RIS translation, Way faster pwsh cls --- src/host/ApiRoutines.h | 3 ++- src/host/_output.cpp | 30 ++++++++++++++++++++++++++++-- src/host/_stream.cpp | 15 +++++++++++++++ src/host/_stream.h | 1 + src/host/getset.cpp | 2 +- src/server/ApiDispatchers.cpp | 3 ++- src/server/IApiRoutines.h | 3 ++- 7 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/host/ApiRoutines.h b/src/host/ApiRoutines.h index a46f1397ec6..7301c172194 100644 --- a/src/host/ApiRoutines.h +++ b/src/host/ApiRoutines.h @@ -93,7 +93,8 @@ class ApiRoutines : public IApiRoutines const WORD attribute, const size_t lengthToWrite, const til::point startingCoordinate, - size_t& cellsModified) noexcept override; + size_t& cellsModified, + const bool enablePowershellShim = false) noexcept override; [[nodiscard]] HRESULT FillConsoleOutputCharacterAImpl(IConsoleOutputObject& OutContext, const char character, diff --git a/src/host/_output.cpp b/src/host/_output.cpp index 62ce62e7067..1b1655f1c77 100644 --- a/src/host/_output.cpp +++ b/src/host/_output.cpp @@ -373,13 +373,39 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon const WORD attribute, const size_t lengthToWrite, const til::point startingCoordinate, - size_t& cellsModified) noexcept + size_t& cellsModified, + const bool enablePowershellShim) noexcept { try { LockConsole(); const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + if (const auto io = gci.GetVtIo(&OutContext)) + { + // 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 Full Reset (RIS). + if (enablePowershellShim) + { + 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; } @@ -429,7 +455,7 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon if (wroteWholeBuffer && startedAtOrigin && wroteSpaces) { - WriteCharsVT(OutContext, L"\033c"); + WriteClearScreen(OutContext); cellsModified = lengthToWrite; return S_OK; } diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index f1e2e6cf036..f8a7e54c4a7 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -374,6 +374,21 @@ void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str) } } +void WriteClearScreen(SCREEN_INFORMATION& screenInfo) +{ + std::wstring buffer; + buffer.append(L"\033c"); + if (WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT)) + { + buffer.append(L"\x1b[?7h"); // DECAWM: Autowrap Mode + } + if (WI_IsFlagClear(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN)) + { + buffer.append(L"\x1b[20h"); // LNM: Line Feed / New Line Mode + } + WriteCharsVT(screenInfo, buffer); +} + // Routine Description: // - Takes the given text and inserts it into the given screen buffer. // Note: 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/getset.cpp b/src/host/getset.cpp index a8b41cadd5f..bf40cb18d41 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -1019,7 +1019,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont !clip && fillCharacter == UNICODE_SPACE && fillAttribute == buffer.GetAttributes().GetLegacyAttributes()) { - WriteCharsVT(context, L"\033c"); + WriteClearScreen(context); return S_OK; } diff --git a/src/server/ApiDispatchers.cpp b/src/server/ApiDispatchers.cpp index 9009f71c19f..3ae2b735e91 100644 --- a/src/server/ApiDispatchers.cpp +++ b/src/server/ApiDispatchers.cpp @@ -465,7 +465,8 @@ constexpr T saturate(auto val) a->Element, fill, til::wrap_coord(a->WriteCoord), - amountWritten); + amountWritten, + m->GetProcessHandle()->GetShimPolicy().IsPowershellExe()); break; } case CONSOLE_REAL_UNICODE: diff --git a/src/server/IApiRoutines.h b/src/server/IApiRoutines.h index 9c99136c2c3..72386c4981e 100644 --- a/src/server/IApiRoutines.h +++ b/src/server/IApiRoutines.h @@ -101,7 +101,8 @@ class __declspec(novtable) IApiRoutines const WORD attribute, const size_t lengthToWrite, const til::point startingCoordinate, - size_t& cellsModified) noexcept = 0; + size_t& cellsModified, + const bool enablePowershellShim) noexcept = 0; [[nodiscard]] virtual HRESULT FillConsoleOutputCharacterAImpl(IConsoleOutputObject& OutContext, const char character, From 92087fbd23fae9aa4a37635d968d5baaf6b9f606 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sun, 7 Jul 2024 18:25:03 +0200 Subject: [PATCH 26/60] AuditMode fix --- src/terminal/adapter/adaptDispatch.cpp | 2 +- src/terminal/adapter/adaptDispatch.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index b727444d0c2..ddf41d7f731 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -2179,7 +2179,7 @@ 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 true; diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 941449925eb..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 From 29766619f04321e74baaed6145f44b8ea3b28920 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sun, 7 Jul 2024 19:31:38 +0200 Subject: [PATCH 27/60] Remove DECCOLM from DA1 --- src/terminal/adapter/adaptDispatch.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index ddf41d7f731..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,7 +1550,7 @@ bool AdaptDispatch::DeviceAttributes() // 32 = Text macros // 42 = ISO Latin-2 character set - _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; } From 9c42bfcb8de7ec7c29c73158f936c2496fe671c0 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sun, 7 Jul 2024 19:34:34 +0200 Subject: [PATCH 28/60] Fix VtIoTests --- src/host/ut_host/VtIoTests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index 3b7d08f0c64..97464ed2904 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -36,7 +36,7 @@ constexpr CHAR_INFO blu(wchar_t ch) noexcept #define sgr_rst() "\x1b[0m" // Any RIS sequence should re-enable our required ConPTY modes Focus Event Mode and Win32 Input Mode. -#define ris() "\033c\x1b[?1004h\x1b[?9001h" +#define ris() "\033c\x1b[?1004h\x1b[?9001h\x1b[?7h\x1b[20h" static constexpr std::wstring_view s_initialContentVT{ // clang-format off From 9535e4951862887907115e41281b4c7de8e544a7 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sun, 7 Jul 2024 22:34:50 +0200 Subject: [PATCH 29/60] Uh... --- src/terminal/adapter/ut_adapter/adapterTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index a61167ece99..ca44278b96e 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -1704,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."); From e9e6227a4e6ff34ec96bdd9aa9f3dafe9ec077cf Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 8 Jul 2024 00:00:49 +0200 Subject: [PATCH 30/60] Better GetVtIo naming, Less VtEngine code, Fix control char sanitization --- src/host/CursorBlinker.cpp | 9 +- src/host/VtIo.cpp | 24 ++++ src/host/VtIo.hpp | 1 + src/host/_output.cpp | 6 +- src/host/_stream.cpp | 26 +--- src/host/consoleInformation.cpp | 30 ++--- src/host/directio.cpp | 6 +- src/host/getset.cpp | 26 ++-- src/host/output.cpp | 2 +- src/host/outputStream.cpp | 8 +- src/host/screenInfo.cpp | 17 +-- src/host/server.h | 6 +- src/host/settings.cpp | 2 +- src/host/srvinit.cpp | 2 +- src/host/ut_host/VtIoTests.cpp | 27 +++- .../base/InteractivityFactory.cpp | 2 +- src/interactivity/win32/windowio.cpp | 2 +- src/terminal/adapter/InteractDispatch.cpp | 2 +- src/terminal/parser/stateMachine.cpp | 122 +----------------- src/types/inc/utils.hpp | 3 + src/types/utils.cpp | 121 +++++++++++++++++ 21 files changed, 236 insertions(+), 208 deletions(-) diff --git a/src/host/CursorBlinker.cpp b/src/host/CursorBlinker.cpp index f4b9327f436..0714d0461e8 100644 --- a/src/host/CursorBlinker.cpp +++ b/src/host/CursorBlinker.cpp @@ -83,8 +83,7 @@ void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo) const noexcept auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); auto* const pAccessibilityNotifier = ServiceLocator::LocateAccessibilityNotifier(); - // GH#2988: ConPTY can now be focused, but it doesn't need to do any of this work either. - if (gci.GetVtIo(nullptr) || !WI_IsFlagSet(gci.Flags, CONSOLE_HAS_FOCUS)) + if (!WI_IsFlagSet(gci.Flags, CONSOLE_HAS_FOCUS)) { goto DoScroll; } @@ -164,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.IsConPTY()) + { + return; + } + using filetime_duration = std::chrono::duration>; static constexpr DWORD dwDefTimeout = 0x212; diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index ba9b0d3589f..62c578e236a 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -386,6 +386,30 @@ void VtIo::WriteUTF16(std::wstring_view str) _flush(); } +// 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::WriteUTF16StripControlChars(std::wstring_view str) +{ + const auto cork = Cork(); + 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 = Microsoft::Console::Utils::FindActionableControlCharacter(it, end - it); + const auto begRegularChars = Microsoft::Console::Utils::FindNonActionableRegularCharacter(begControlChars, end - begControlChars); + + WriteUTF16({ it, begControlChars }); // First we append the regular characters. + _back.append(begRegularChars - begControlChars, ' '); // And then as many spaces as control characters. + + it = begRegularChars; + } + + _flush(); +} + void VtIo::WriteUCS2(wchar_t ch) { char buf[4]; diff --git a/src/host/VtIo.hpp b/src/host/VtIo.hpp index 7971af907cd..a768e2af83f 100644 --- a/src/host/VtIo.hpp +++ b/src/host/VtIo.hpp @@ -77,6 +77,7 @@ namespace Microsoft::Console::VirtualTerminal } void WriteUTF8(std::string_view str); void WriteUTF16(std::wstring_view str); + void WriteUTF16StripControlChars(std::wstring_view str); void WriteUCS2(wchar_t ch); void WriteCUP(til::point position); void WriteDECTCEM(bool enabled); diff --git a/src/host/_output.cpp b/src/host/_output.cpp index 1b1655f1c77..19f3330068e 100644 --- a/src/host/_output.cpp +++ b/src/host/_output.cpp @@ -98,7 +98,7 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon return {}; } - if (const auto io = gci.GetVtIo(&screenInfo)) + if (const auto io = gci.GetVtIoForBuffer(&screenInfo)) { const auto corkLock = io->Cork(); @@ -382,7 +382,7 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (const auto io = gci.GetVtIo(&OutContext)) + if (const auto io = gci.GetVtIoForBuffer(&OutContext)) { // GH#3126 - This is a shim for powershell's `Clear-Host` function. In // the vintage console, `Clear-Host` is supposed to clear the entire @@ -438,7 +438,7 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (const auto io = gci.GetVtIo(&OutContext)) + if (const auto io = gci.GetVtIoForBuffer(&OutContext)) { // GH#3126 - This is a shim for powershell's `Clear-Host` function. In // the vintage console, `Clear-Host` is supposed to clear the entire diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index f8a7e54c4a7..ae14ba42624 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -31,18 +31,6 @@ constexpr bool controlCharPredicate(wchar_t wch) return wch < L' ' || wch == 0x007F; } -// This is a copy of the same function in stateMachine.cpp. -// Returns true for C0 characters and C1 [single-character] CSI. -constexpr bool isActionableFromGround(const wchar_t wch) noexcept -{ - // 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); -} - // Routine Description: // - This routine updates the cursor position. Its input is the non-special // cased new location of the cursor. For example, if the cursor were being @@ -176,7 +164,7 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t const auto end = text.end(); auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto io = gci.GetVtIo(&screenInfo); + const auto io = gci.GetVtIoForBuffer(&screenInfo); const auto corkLock = io ? io->Cork() : Microsoft::Console::VirtualTerminal::VtIo::CorkLock{}; // If we enter this if condition, then someone wrote text in VT mode and now switched to non-VT mode. @@ -205,15 +193,7 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t { // 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. - // - // BODGY: The const_cast is ""safe"", because the backing memory of `text` comes from `_CONSOLE_API_MSG::_inputBuffer`. - // This allows us to replace the control characters without copying potentially Megabytes of data. - // Ideally the parameter simply wouldn't be a string_view (= const char), but oh well. - const auto begMut = const_cast(text.data()); - const auto endMut = begMut + text.size(); - std::replace_if(begMut, endMut, isActionableFromGround, L' '); - - io->WriteUTF16(text); + io->WriteUTF16StripControlChars(text); if (lastCharWrapped) { io->WriteUTF8(" \r"); @@ -343,7 +323,7 @@ void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str) stateMachine.ProcessString(str); - if (const auto io = gci.GetVtIo(&screenInfo)) + if (const auto io = gci.GetVtIoForBuffer(&screenInfo)) { const auto cork = io->Cork(); const auto& injections = stateMachine.GetInjections(); diff --git a/src/host/consoleInformation.cpp b/src/host/consoleInformation.cpp index e874c1aaadb..439edd6ab82 100644 --- a/src/host/consoleInformation.cpp +++ b/src/host/consoleInformation.cpp @@ -118,14 +118,24 @@ ULONG CONSOLE_INFORMATION::GetCSRecursionCount() const noexcept return Status; } -VtIo* CONSOLE_INFORMATION::GetVtIoNoCheck() +VtIo* CONSOLE_INFORMATION::GetVtIoNoCheck() noexcept { return &_vtIo; } -VtIo* CONSOLE_INFORMATION::GetVtIo(const SCREEN_INFORMATION* context) +VtIo* CONSOLE_INFORMATION::GetVtIo() noexcept { - return _vtIo.IsUsingVt() && (context == nullptr || context == pCurrentMainScreenBuffer || context == pCurrentScreenBuffer) ? &_vtIo : nullptr; + return _vtIo.IsUsingVt() ? &_vtIo : nullptr; +} + +VtIo* CONSOLE_INFORMATION::GetVtIoForBuffer(const SCREEN_INFORMATION* context) noexcept +{ + return _vtIo.IsUsingVt() && (context == pCurrentMainScreenBuffer || context == pCurrentScreenBuffer) ? &_vtIo : nullptr; +} + +bool CONSOLE_INFORMATION::IsConPTY() const noexcept +{ + return _vtIo.IsUsingVt(); } bool CONSOLE_INFORMATION::HasPendingCookedRead() const noexcept @@ -221,20 +231,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 0636baca068..02f5f862afe 100644 --- a/src/host/directio.cpp +++ b/src/host/directio.cpp @@ -575,7 +575,7 @@ CATCH_RETURN(); } auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto io = gci.GetVtIo(&context); + const auto io = gci.GetVtIoForBuffer(&context); auto& storageBuffer = context.GetActiveBuffer(); const auto storageRectangle = storageBuffer.GetBufferSize(); @@ -639,7 +639,7 @@ CATCH_RETURN(); try { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto io = gci.GetVtIo(&context); + const auto io = gci.GetVtIoForBuffer(&context); const auto corkLock = io ? io->Cork() : Microsoft::Console::VirtualTerminal::VtIo::CorkLock{}; const auto codepage = gci.OutputCP; @@ -669,7 +669,7 @@ CATCH_RETURN(); try { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto io = gci.GetVtIo(&context); + const auto io = gci.GetVtIoForBuffer(&context); const auto corkLock = io ? io->Cork() : Microsoft::Console::VirtualTerminal::VtIo::CorkLock{}; if (!context.GetActiveBuffer().GetCurrentFont().IsTrueTypeFont()) diff --git a/src/host/getset.cpp b/src/host/getset.cpp index bf40cb18d41..19a09cf159d 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -363,7 +363,7 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept WI_ClearFlag(gci.Flags, CONSOLE_USE_PRIVATE_FLAGS); } - if (const auto io = gci.GetVtIo(nullptr)) + if (const auto io = gci.GetVtIo()) { auto oldMode = context.InputMode; auto newMode = mode; @@ -451,7 +451,7 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept } auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (const auto io = gci.GetVtIo(&context)) + if (const auto io = gci.GetVtIoForBuffer(&context)) { if (WI_IsFlagSet(diff, ENABLE_WRAP_AT_EOL_OUTPUT)) { @@ -481,7 +481,7 @@ void ApiRoutines::SetConsoleActiveScreenBufferImpl(SCREEN_INFORMATION& newContex auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (const auto io = gci.GetVtIo(nullptr)) + if (const auto io = gci.GetVtIo()) { const auto cork = io->Cork(); @@ -781,7 +781,7 @@ 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(); - if (const auto io = gci.GetVtIo(&context)) + if (const auto io = gci.GetVtIoForBuffer(&context)) { io->WriteCUP(position); } @@ -861,7 +861,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont context.SetCursorInformation(size, isVisible); auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (const auto io = gci.GetVtIo(&context)) + if (const auto io = gci.GetVtIoForBuffer(&context)) { io->WriteDECTCEM(isVisible); } @@ -912,7 +912,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont // if we're headless, not so much. However, GetMaxWindowSizeInCharacters // will only return the buffer size, so we can't use that to clip the arg here. // So only clip the requested size if we're not headless - if (g.getConsoleInformation().GetVtIo(nullptr)) + if (g.getConsoleInformation().IsConPTY()) { // SetViewportRect doesn't cause the buffer to resize. Manually resize the buffer. RETURN_IF_NTSTATUS_FAILED(context.ResizeScreenBuffer(Viewport::FromInclusive(Window).Dimensions(), false)); @@ -993,7 +993,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (const auto io = gci.GetVtIo(&context)) + if (const auto io = gci.GetVtIoForBuffer(&context)) { auto& buffer = context.GetActiveBuffer(); @@ -1087,7 +1087,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont context.SetAttributes(attr); auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (const auto io = gci.GetVtIo(&context)) + if (const auto io = gci.GetVtIoForBuffer(&context)) { io->WriteAttributes(attribute); } @@ -1223,7 +1223,7 @@ void ApiRoutines::GetConsoleWindowImpl(HWND& hwnd) noexcept // console, so that they know that this console is in fact a real // console window. auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (gci.GetVtIo(nullptr)) + if (gci.IsConPTY()) { hwnd = ServiceLocator::LocatePseudoWindow(); } @@ -1627,10 +1627,12 @@ void ApiRoutines::GetConsoleDisplayModeImpl(ULONG& flags) noexcept auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); gci.SetTitle(title); - if (const auto io = gci.GetVtIo(nullptr)) + if (const auto io = gci.GetVtIo()) { - const auto title8 = til::u16u8(title); - io->WriteFormat(FMT_COMPILE("\x1b]0;{}\x7"), title8); + const auto cork = io->Cork(); + io->WriteUTF8("\x1b]0;"); + io->WriteUTF16StripControlChars(title); + io->WriteUTF8("\x7"); } return S_OK; diff --git a/src/host/output.cpp b/src/host/output.cpp index f368bb12c79..aa276832391 100644 --- a/src/host/output.cpp +++ b/src/host/output.cpp @@ -460,7 +460,7 @@ void SetActiveScreenBuffer(SCREEN_INFORMATION& screenInfo) // mode, then the cursor will remain off until they print text. This can // lead to alignment problems in the terminal, because we won't move the // terminal's cursor in this _exact_ scenario. - screenInfo.GetTextBuffer().GetCursor().SetIsOn(gci.GetVtIo(nullptr)); + screenInfo.GetTextBuffer().GetCursor().SetIsOn(gci.IsConPTY()); // set font screenInfo.RefreshFontWithRenderer(); diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index 4af547db2a2..beccff5d586 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -36,7 +36,7 @@ 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.GetVtIo(nullptr)) + if (gci.IsConPTY()) { return; } @@ -211,7 +211,7 @@ CursorType ConhostInternalGetSet::GetUserDefaultCursorStyle() const void ConhostInternalGetSet::ShowWindow(bool showOrHide) { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto hwnd = gci.GetVtIo(nullptr) ? ServiceLocator::LocatePseudoWindow() : ServiceLocator::LocateConsoleWindow()->GetWindowHandle(); + const auto hwnd = gci.IsConPTY() ? ServiceLocator::LocatePseudoWindow() : ServiceLocator::LocateConsoleWindow()->GetWindowHandle(); // GH#13301 - When we send this ShowWindow message, if we send it to the // conhost HWND, it's going to need to get processed by the window message @@ -346,9 +346,7 @@ 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) { diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index 3f6c5cacc65..f20aeb9c359 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -1198,21 +1198,6 @@ 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.GetVtIo(nullptr) && ServiceLocator::LocateGlobals().pRender) - { - ServiceLocator::LocateGlobals().pRender->TriggerScroll(); - } } // Routine Description: @@ -1978,7 +1963,7 @@ bool SCREEN_INFORMATION::_IsAltBuffer() const bool SCREEN_INFORMATION::_IsInPtyMode() const { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - return _IsAltBuffer() || gci.GetVtIo(nullptr); + return _IsAltBuffer() || gci.IsConPTY(); } // Routine Description: diff --git a/src/host/server.h b/src/host/server.h index 89a9f4c7273..58ad6207608 100644 --- a/src/host/server.h +++ b/src/host/server.h @@ -103,8 +103,10 @@ class CONSOLE_INFORMATION : bool IsConsoleLocked() const noexcept; ULONG GetCSRecursionCount() const noexcept; - Microsoft::Console::VirtualTerminal::VtIo* GetVtIoNoCheck(); - Microsoft::Console::VirtualTerminal::VtIo* GetVtIo(const SCREEN_INFORMATION* context); + Microsoft::Console::VirtualTerminal::VtIo* GetVtIoNoCheck() noexcept; + Microsoft::Console::VirtualTerminal::VtIo* GetVtIo() noexcept; + Microsoft::Console::VirtualTerminal::VtIo* GetVtIoForBuffer(const SCREEN_INFORMATION* context) noexcept; + bool IsConPTY() const noexcept; SCREEN_INFORMATION& GetActiveOutputBuffer() override; const SCREEN_INFORMATION& GetActiveOutputBuffer() const override; diff --git a/src/host/settings.cpp b/src/host/settings.cpp index 5c89099121a..f799811f891 100644 --- a/src/host/settings.cpp +++ b/src/host/settings.cpp @@ -181,7 +181,7 @@ void Settings::ApplyCommandlineArguments(const ConsoleArguments& consoleArgs) _dwScreenBufferSize.Y = height; _dwWindowSize.Y = height; } - else if (ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo(nullptr)) + else if (ServiceLocator::LocateGlobals().getConsoleInformation().IsConPTY()) { // If we're a PTY but we weren't explicitly told a size, use the window size as the buffer size. _dwScreenBufferSize = _dwWindowSize; diff --git a/src/host/srvinit.cpp b/src/host/srvinit.cpp index 5fbdb32c8e1..710827bd988 100644 --- a/src/host/srvinit.cpp +++ b/src/host/srvinit.cpp @@ -852,7 +852,7 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand, // No matter what, create a renderer. try { - if (!gci.GetVtIo(nullptr)) + if (!gci.IsConPTY()) { auto renderThread = std::make_unique(); // stash a local pointer to the thread here - diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index 97464ed2904..15baa1c25f2 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -52,7 +52,6 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests { BEGIN_TEST_CLASS(VtIoTests) TEST_CLASS_PROPERTY(L"IsolationLevel", L"Class") - //TEST_CLASS_PROPERTY(L"TestTimeout", L"0:1:0") // 1min END_TEST_CLASS() CommonState commonState; @@ -142,10 +141,30 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests TEST_METHOD(SetConsoleTitleW) { - THROW_IF_FAILED(routines.SetConsoleTitleWImpl(L"foobar")); + const char* expected = nullptr; + std::string_view actual; - const auto expected = "\x1b]0;foobar\a"; - const auto actual = readOutput(); + THROW_IF_FAILED(routines.SetConsoleTitleWImpl( + L"foobar")); + expected = "\x1b]0;foobar\a"; + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + THROW_IF_FAILED(routines.SetConsoleTitleWImpl( + L"foo" + "\u0001\u001f" + "bar")); + expected = "\x1b]0;foo bar\a"; + actual = readOutput(); + VERIFY_ARE_EQUAL(expected, actual); + + 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); } diff --git a/src/interactivity/base/InteractivityFactory.cpp b/src/interactivity/base/InteractivityFactory.cpp index a2c9aaf2f58..74dac545c6d 100644 --- a/src/interactivity/base/InteractivityFactory.cpp +++ b/src/interactivity/base/InteractivityFactory.cpp @@ -497,7 +497,7 @@ 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(nullptr)) + if (const auto io = gci.GetVtIo()) { char buf[] = "\x1b[1t"; buf[2] = showOrHide ? '1' : '2'; diff --git a/src/interactivity/win32/windowio.cpp b/src/interactivity/win32/windowio.cpp index 728cc0dba26..a1337a46aef 100644 --- a/src/interactivity/win32/windowio.cpp +++ b/src/interactivity/win32/windowio.cpp @@ -935,7 +935,7 @@ 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. - if (const auto io = ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo(nullptr)) + if (const auto io = ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()) { io->CreatePseudoWindow(); } diff --git a/src/terminal/adapter/InteractDispatch.cpp b/src/terminal/adapter/InteractDispatch.cpp index a5bc381f8c0..0a54a25a305 100644 --- a/src/terminal/adapter/InteractDispatch.cpp +++ b/src/terminal/adapter/InteractDispatch.cpp @@ -173,7 +173,7 @@ bool InteractDispatch::FocusChanged(const bool focused) const // This should likely always be true - we shouldn't ever have an // InteractDispatch outside ConPTY mode, but just in case... - if (gci.GetVtIo(nullptr)) + if (gci.IsConPTY()) { auto shouldActuallyFocus = false; diff --git a/src/terminal/parser/stateMachine.cpp b/src/terminal/parser/stateMachine.cpp index 8d197ae50bf..07918e902bc 100644 --- a/src/terminal/parser/stateMachine.cpp +++ b/src/terminal/parser/stateMachine.cpp @@ -5,6 +5,7 @@ #include "stateMachine.hpp" +#include "../../types/inc/utils.hpp" #include "ascii.hpp" using namespace Microsoft::Console::VirtualTerminal; @@ -1962,119 +1963,6 @@ bool StateMachine::FlushToTerminal() return success; } -// Disable vectorization-unfriendly warnings. -#pragma warning(push) -#pragma warning(disable : 26429) // Symbol '...' is never tested for nullness, it can be marked as not_null (f.23). -#pragma warning(disable : 26472) // Don't use a static_cast for arithmetic conversions. Use brace initialization, gsl::narrow_cast or gsl::narrow (type.1). -#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1). -#pragma warning(disable : 26490) // Don't use reinterpret_cast (type.1). - -// Returns true for C0 characters and C1 [single-character] CSI. -constexpr bool isActionableFromGround(const wchar_t wch) noexcept -{ - // 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); -} - -[[msvc::forceinline]] static size_t findActionableFromGroundPlain(const wchar_t* beg, const wchar_t* end, const wchar_t* it) noexcept -{ -#pragma loop(no_vector) - for (; it < end && !isActionableFromGround(*it); ++it) - { - } - return it - beg; -} - -static size_t findActionableFromGround(const wchar_t* data, size_t count) noexcept -{ - // The following vectorized code replicates isActionableFromGround which is equivalent to: - // (wch <= 0x1f) || (wch >= 0x7f && wch <= 0x9f) - // or rather its more machine friendly equivalent: - // (wch <= 0x1f) | ((wch - 0x7f) <= 0x20) -#if defined(TIL_SSE_INTRINSICS) - - auto it = data; - - for (const auto end = data + (count & ~size_t{ 7 }); it < end; it += 8) - { - const auto wch = _mm_loadu_si128(reinterpret_cast(it)); - const auto z = _mm_setzero_si128(); - - // Dealing with unsigned numbers in SSE2 is annoying because it has poor support for that. - // We'll use subtractions with saturation ("SubS") to work around that. A check like - // a < b can be implemented as "max(0, a - b) == 0" and "max(0, a - b)" is what "SubS" is. - - // Check for (wch < 0x20) - auto a = _mm_subs_epu16(wch, _mm_set1_epi16(0x1f)); - // Check for "((wch - 0x7f) <= 0x20)" by adding 0x10000-0x7f, which overflows to a - // negative number if "wch >= 0x7f" and then subtracting 0x9f-0x7f with saturation to an - // unsigned number (= can't go lower than 0), which results in all numbers up to 0x9f to be 0. - auto b = _mm_subs_epu16(_mm_add_epi16(wch, _mm_set1_epi16(static_cast(0xff81))), _mm_set1_epi16(0x20)); - a = _mm_cmpeq_epi16(a, z); - b = _mm_cmpeq_epi16(b, z); - - const auto c = _mm_or_si128(a, b); - const auto mask = _mm_movemask_epi8(c); - - if (mask) - { - unsigned long offset; - _BitScanForward(&offset, mask); - it += offset / 2; - return it - data; - } - } - - return findActionableFromGroundPlain(data, data + count, it); - -#elif defined(TIL_ARM_NEON_INTRINSICS) - - auto it = data; - uint64_t mask; - - for (const auto end = data + (count & ~size_t{ 7 }); it < end;) - { - const auto wch = vld1q_u16(it); - const auto a = vcleq_u16(wch, vdupq_n_u16(0x1f)); - const auto b = vcleq_u16(vsubq_u16(wch, vdupq_n_u16(0x7f)), vdupq_n_u16(0x20)); - const auto c = vorrq_u16(a, b); - - mask = vgetq_lane_u64(c, 0); - if (mask) - { - goto exitWithMask; - } - it += 4; - - mask = vgetq_lane_u64(c, 1); - if (mask) - { - goto exitWithMask; - } - it += 4; - } - - return findActionableFromGroundPlain(data, data + count, it); - -exitWithMask: - unsigned long offset; - _BitScanForward64(&offset, mask); - it += offset / 16; - return it - data; - -#else - - return findActionableFromGroundPlain(data, data + count, p); - -#endif -} - -#pragma warning(pop) - // Routine Description: // - Helper for entry to the state machine. Will take an array of characters // and print as many as it can without encountering a character indicating @@ -2102,10 +1990,14 @@ void StateMachine::ProcessString(const std::wstring_view string) while (i < string.size()) { { - _runOffset = i; // Pointer arithmetic is perfectly fine for our hot path. #pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).) - _runSize = findActionableFromGround(string.data() + i, string.size() - i); + const auto beg = string.data() + i; + const auto len = string.size() - i; + const auto it = Microsoft::Console::Utils::FindActionableControlCharacter(beg, len); + + _runOffset = i; + _runSize = it - beg; if (_runSize) { diff --git a/src/types/inc/utils.hpp b/src/types/inc/utils.hpp index a17d76a6550..6574a2cf663 100644 --- a/src/types/inc/utils.hpp +++ b/src/types/inc/utils.hpp @@ -115,6 +115,9 @@ namespace Microsoft::Console::Utils // testing easier. std::wstring_view TrimPaste(std::wstring_view textView) noexcept; + const wchar_t* FindActionableControlCharacter(const wchar_t* beg, const size_t len) noexcept; + const wchar_t* FindNonActionableRegularCharacter(const wchar_t* beg, const size_t len) noexcept; + // Same deal, but in TerminalPage::_evaluatePathForCwd std::wstring EvaluateStartingDirectory(std::wstring_view cwd, std::wstring_view startingDirectory); diff --git a/src/types/utils.cpp b/src/types/utils.cpp index e7dd163f68d..e7d224f47cf 100644 --- a/src/types/utils.cpp +++ b/src/types/utils.cpp @@ -874,6 +874,127 @@ std::wstring_view Utils::TrimPaste(std::wstring_view textView) noexcept return textView.substr(0, lastNonSpace + 1); } +// Disable vectorization-unfriendly warnings. +#pragma warning(push) +#pragma warning(disable : 26429) // Symbol '...' is never tested for nullness, it can be marked as not_null (f.23). +#pragma warning(disable : 26472) // Don't use a static_cast for arithmetic conversions. Use brace initialization, gsl::narrow_cast or gsl::narrow (type.1). +#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1). +#pragma warning(disable : 26490) // Don't use reinterpret_cast (type.1). + +// Returns true for C0 characters and C1 [single-character] CSI. +constexpr bool isActionableFromGround(const wchar_t wch) noexcept +{ + // 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); +} + +const wchar_t* Utils::FindActionableControlCharacter(const wchar_t* beg, const size_t len) noexcept +{ + auto it = beg; + + // The following vectorized code replicates isActionableFromGround which is equivalent to: + // (wch <= 0x1f) || (wch >= 0x7f && wch <= 0x9f) + // or rather its more machine friendly equivalent: + // (wch <= 0x1f) | ((wch - 0x7f) <= 0x20) +#if defined(TIL_SSE_INTRINSICS) + + for (const auto end = beg + (len & ~size_t{ 7 }); it < end; it += 8) + { + const auto wch = _mm_loadu_si128(reinterpret_cast(it)); + const auto z = _mm_setzero_si128(); + + // Dealing with unsigned numbers in SSE2 is annoying because it has poor support for that. + // We'll use subtractions with saturation ("SubS") to work around that. A check like + // a < b can be implemented as "max(0, a - b) == 0" and "max(0, a - b)" is what "SubS" is. + + // Check for (wch < 0x20) + auto a = _mm_subs_epu16(wch, _mm_set1_epi16(0x1f)); + // Check for "((wch - 0x7f) <= 0x20)" by adding 0x10000-0x7f, which overflows to a + // negative number if "wch >= 0x7f" and then subtracting 0x9f-0x7f with saturation to an + // unsigned number (= can't go lower than 0), which results in all numbers up to 0x9f to be 0. + auto b = _mm_subs_epu16(_mm_add_epi16(wch, _mm_set1_epi16(static_cast(0xff81))), _mm_set1_epi16(0x20)); + a = _mm_cmpeq_epi16(a, z); + b = _mm_cmpeq_epi16(b, z); + + const auto c = _mm_or_si128(a, b); + const auto mask = _mm_movemask_epi8(c); + + if (mask) + { + unsigned long offset; + _BitScanForward(&offset, mask); + it += offset / 2; + return it; + } + } + +#elif defined(TIL_ARM_NEON_INTRINSICS) + + uint64_t mask; + + for (const auto end = beg + (len & ~size_t{ 7 });;) + { + if (it >= end) + { + goto plainSearch; + } + + const auto wch = vld1q_u16(it); + const auto a = vcleq_u16(wch, vdupq_n_u16(0x1f)); + const auto b = vcleq_u16(vsubq_u16(wch, vdupq_n_u16(0x7f)), vdupq_n_u16(0x20)); + const auto c = vorrq_u16(a, b); + + mask = vgetq_lane_u64(c, 0); + if (mask) + { + break; + } + it += 4; + + mask = vgetq_lane_u64(c, 1); + if (mask) + { + break; + } + it += 4; + } + + unsigned long offset; + _BitScanForward64(&offset, mask); + it += offset / 16; + return it; + +plainSearch: + +#endif + +#pragma loop(no_vector) + for (const auto end = beg + len; it < end && !isActionableFromGround(*it); ++it) + { + } + + return it; +} + +const wchar_t* Utils::FindNonActionableRegularCharacter(const wchar_t* beg, const size_t len) noexcept +{ + auto it = beg; + const auto end = beg + len; + +#pragma loop(no_vector) + for (; it < end && isActionableFromGround(*it); ++it) + { + } + + return it; +} + +#pragma warning(pop) + std::wstring Utils::EvaluateStartingDirectory( std::wstring_view currentDirectory, std::wstring_view startingDirectory) From edd4c758d7af885d319867167a15fcc3d28efc0e Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 8 Jul 2024 01:26:12 +0200 Subject: [PATCH 31/60] Fix GetVtIo for alt buffers --- src/host/_stream.cpp | 5 ++++- src/host/consoleInformation.cpp | 8 +------- src/host/screenInfo.cpp | 5 +++++ src/host/screenInfo.hpp | 2 +- src/host/server.h | 2 -- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index ae14ba42624..17c36e9c5be 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -320,10 +320,13 @@ void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& 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. + const auto io = gci.GetVtIoForBuffer(&screenInfo); stateMachine.ProcessString(str); - if (const auto io = gci.GetVtIoForBuffer(&screenInfo)) + if (io) { const auto cork = io->Cork(); const auto& injections = stateMachine.GetInjections(); diff --git a/src/host/consoleInformation.cpp b/src/host/consoleInformation.cpp index 439edd6ab82..d7718cd4820 100644 --- a/src/host/consoleInformation.cpp +++ b/src/host/consoleInformation.cpp @@ -130,7 +130,7 @@ VtIo* CONSOLE_INFORMATION::GetVtIo() noexcept VtIo* CONSOLE_INFORMATION::GetVtIoForBuffer(const SCREEN_INFORMATION* context) noexcept { - return _vtIo.IsUsingVt() && (context == pCurrentMainScreenBuffer || context == pCurrentScreenBuffer) ? &_vtIo : nullptr; + return _vtIo.IsUsingVt() && (pCurrentScreenBuffer == context || pCurrentScreenBuffer == context->GetAltBuffer()) ? &_vtIo : nullptr; } bool CONSOLE_INFORMATION::IsConPTY() const noexcept @@ -189,11 +189,6 @@ const SCREEN_INFORMATION& CONSOLE_INFORMATION::GetActiveOutputBuffer() const return *pCurrentScreenBuffer; } -const SCREEN_INFORMATION& CONSOLE_INFORMATION::GetActiveMainOutputBuffer() const -{ - return *pCurrentMainScreenBuffer; -} - void CONSOLE_INFORMATION::SetActiveOutputBuffer(SCREEN_INFORMATION& screenBuffer) { if (pCurrentScreenBuffer) @@ -202,7 +197,6 @@ void CONSOLE_INFORMATION::SetActiveOutputBuffer(SCREEN_INFORMATION& screenBuffer } pCurrentScreenBuffer = &screenBuffer; pCurrentScreenBuffer->GetTextBuffer().SetAsActiveBuffer(true); - pCurrentMainScreenBuffer = &screenBuffer.GetMainBuffer(); } bool CONSOLE_INFORMATION::HasActiveOutputBuffer() const diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index f20aeb9c359..3f955e7d111 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -1747,6 +1747,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 diff --git a/src/host/screenInfo.hpp b/src/host/screenInfo.hpp index 686c9beb8d4..cf212ca51b6 100644 --- a/src/host/screenInfo.hpp +++ b/src/host/screenInfo.hpp @@ -190,7 +190,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; diff --git a/src/host/server.h b/src/host/server.h index 58ad6207608..92679de8992 100644 --- a/src/host/server.h +++ b/src/host/server.h @@ -110,7 +110,6 @@ class CONSOLE_INFORMATION : SCREEN_INFORMATION& GetActiveOutputBuffer() override; const SCREEN_INFORMATION& GetActiveOutputBuffer() const override; - const SCREEN_INFORMATION& GetActiveMainOutputBuffer() const; void SetActiveOutputBuffer(SCREEN_INFORMATION& screenBuffer); bool HasActiveOutputBuffer() const; @@ -156,7 +155,6 @@ class CONSOLE_INFORMATION : std::wstring _OriginalTitle; std::wstring _LinkTitle; // Path to .lnk file SCREEN_INFORMATION* pCurrentScreenBuffer = nullptr; - SCREEN_INFORMATION* pCurrentMainScreenBuffer = nullptr; COOKED_READ_DATA* _cookedReadData = nullptr; // non-ownership pointer bool _bracketedPasteMode = false; From c8d0a934fb54dcec1c00115d52978e41f7a8b8da Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 8 Jul 2024 01:28:44 +0200 Subject: [PATCH 32/60] Revert incorrect ASB fix --- src/cascadia/TerminalCore/TerminalApi.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 93cb5bdd034..21658608141 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -193,11 +193,6 @@ void Terminal::PlayMidiNote(const int noteNumber, const int velocity, const std: void Terminal::UseAlternateScreenBuffer(const TextAttribute& attrs) { - if (_inAltBuffer()) - { - return; - } - // the new alt buffer is exactly the size of the viewport. _altBufferSize = _mutableViewport.Dimensions(); From 669a5302044991c68630735f9304673398dd6fca Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 8 Jul 2024 01:49:19 +0200 Subject: [PATCH 33/60] Don't use RIS for cls --- src/host/_stream.cpp | 12 +----------- src/host/ut_host/VtIoTests.cpp | 5 +---- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index 17c36e9c5be..a7d1778e4d0 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -359,17 +359,7 @@ void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str) void WriteClearScreen(SCREEN_INFORMATION& screenInfo) { - std::wstring buffer; - buffer.append(L"\033c"); - if (WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT)) - { - buffer.append(L"\x1b[?7h"); // DECAWM: Autowrap Mode - } - if (WI_IsFlagClear(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN)) - { - buffer.append(L"\x1b[20h"); // LNM: Line Feed / New Line Mode - } - WriteCharsVT(screenInfo, buffer); + WriteCharsVT(screenInfo, L"\x1b[H\x1b[2J\x1b[3J"); } // Routine Description: diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index 15baa1c25f2..ed89acef7eb 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -35,9 +35,6 @@ constexpr CHAR_INFO blu(wchar_t ch) noexcept // What the default attributes `FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED` result in. #define sgr_rst() "\x1b[0m" -// Any RIS sequence should re-enable our required ConPTY modes Focus Event Mode and Win32 Input Mode. -#define ris() "\033c\x1b[?1004h\x1b[?9001h\x1b[?7h\x1b[20h" - static constexpr std::wstring_view s_initialContentVT{ // clang-format off L"" @@ -411,7 +408,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests // 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 = ris(); + expected = "\x1b[H\x1b[2J\x1b[3J"; actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); From f35127abbe2391e2c73085483cc466e96d110b8f Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 11 Jul 2024 18:04:44 +0200 Subject: [PATCH 34/60] Improve comments --- src/host/VtIo.hpp | 3 +++ src/host/_stream.cpp | 8 +++++++- src/terminal/adapter/InteractDispatch.cpp | 2 -- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/host/VtIo.hpp b/src/host/VtIo.hpp index a768e2af83f..977e71d2078 100644 --- a/src/host/VtIo.hpp +++ b/src/host/VtIo.hpp @@ -104,6 +104,9 @@ namespace Microsoft::Console::VirtualTerminal std::unique_ptr _pVtInputThread; std::unique_ptr _pPtySignalInputThread; + // 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; diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index a7d1778e4d0..9958a2a039f 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -357,9 +357,15 @@ void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str) } } +// Erases all contents of the given screenInfo, including the current screen and scrollback. void WriteClearScreen(SCREEN_INFORMATION& screenInfo) { - WriteCharsVT(screenInfo, L"\x1b[H\x1b[2J\x1b[3J"); + 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: diff --git a/src/terminal/adapter/InteractDispatch.cpp b/src/terminal/adapter/InteractDispatch.cpp index 0a54a25a305..5752e592a5e 100644 --- a/src/terminal/adapter/InteractDispatch.cpp +++ b/src/terminal/adapter/InteractDispatch.cpp @@ -111,8 +111,6 @@ bool InteractDispatch::WindowManipulation(const DispatchTypes::WindowManipulatio _api.GetBufferAndViewport().buffer.TriggerRedrawAll(); return true; case DispatchTypes::WindowManipulationType::ResizeWindowInCharacters: - // TODO:GH#1765 We should introduce a better `ResizeConpty` function to - // ConhostInternalGetSet, that specifically handles a conpty resize. _api.ResizeWindow(parameter2.value_or(0), parameter1.value_or(0)); return true; default: From f0db29ddd1493b30dc5cb13b60e58b25e2e15f3b Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 11 Jul 2024 18:08:36 +0200 Subject: [PATCH 35/60] Fix exception correctness, CRLF translation, C1 pictograms, Line wrap, Wide chars --- src/host/VtIo.cpp | 472 +++++++++++------- src/host/VtIo.hpp | 97 ++-- src/host/_output.cpp | 196 ++++---- src/host/_stream.cpp | 77 +-- src/host/consoleInformation.cpp | 8 +- src/host/directio.cpp | 41 +- src/host/getset.cpp | 76 ++- src/host/server.h | 4 +- src/host/ut_host/VtIoTests.cpp | 261 ++++++---- .../base/InteractivityFactory.cpp | 5 +- src/interactivity/win32/windowio.cpp | 5 +- src/types/inc/utils.hpp | 1 - src/types/utils.cpp | 13 - 13 files changed, 698 insertions(+), 558 deletions(-) diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index 62c578e236a..360df3b382d 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -161,7 +161,7 @@ bool VtIo::IsUsingVt() const } { - const auto cork = Cork(); + 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 @@ -170,7 +170,7 @@ bool VtIo::IsUsingVt() const // By default, DISABLE_NEWLINE_AUTO_RETURN is reset. This implies LNM being set, // which is not the default in terminals, so we have to do that explicitly. - WriteUTF8( + writer.WriteUTF8( "\x1b[20h" // Line Feed / New Line Mode (LNM) "\033[?1004h" // Focus Event Mode "\033[?9001h" // Win32 Input Mode @@ -184,8 +184,10 @@ bool VtIo::IsUsingVt() const // which will call to our VtIo::SetCursorPosition method. if (_lookingForCursorPosition) { - WriteUTF8("\x1b[6n"); // Cursor Position Report (DSR CPR) + writer.WriteUTF8("\x1b[6n"); // Cursor Position Report (DSR CPR) } + + writer.Submit(); } if (_lookingForCursorPosition) @@ -273,11 +275,6 @@ void VtIo::CloseInput() SendCloseEvent(); } -void VtIo::CloseOutput() -{ - _hOutput.reset(); -} - void VtIo::SendCloseEvent() { LockConsole(); @@ -294,7 +291,7 @@ void VtIo::SendCloseEvent() // Returns true for C0 characters and C1 [single-character] CSI. // A copy of isActionableFromGround() from stateMachine.cpp. -bool VtIo::IsControlCharacter(wchar_t wch) noexcept +static constexpr bool IsControlCharacter(wchar_t wch) noexcept { // This is equivalent to: // return (wch <= 0x1f) || (wch >= 0x7f && wch <= 0x9f); @@ -304,48 +301,61 @@ bool VtIo::IsControlCharacter(wchar_t wch) noexcept return (wch <= 0x1f) | (static_cast(wch - 0x7f) <= 0x20); } -static size_t formatAttributes(char (&buffer)[16], WORD attributes) noexcept +// Formats the given console attributes to their closest VT equivalent. +// `out` must refer to at least `formatAttributesMaxLen` characters of valid memory. +// Returns a pointer past the end. +static constexpr size_t formatAttributesMaxLen = 16; +static char* formatAttributes(char* out, const TextAttribute& attributes) noexcept { - auto end = &buffer[0]; - memcpy(end, "\x1b[0", 4); - end += 3; + static uint8_t sgr[] = { 30, 31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97 }; + + // Applications expect that SetConsoleTextAttribute() completely replaces whatever attributes are currently set, + // including any potential VT-exclusive attributes. Since we don't know what those are, we must always emit a SGR 0. + // Copying 4 bytes instead of the correct 3 means we need just 1 DWORD mov. Neat. + // + // 3 bytes. + memcpy(out, "\x1b[0", 4); + out += 3; + + // 2 bytes. + if (attributes.IsReverseVideo()) + { + memcpy(out, ";7", 2); + out += 2; + } - if (attributes & COMMON_LVB_REVERSE_VIDEO) + // 3 bytes (";97"). + if (attributes.GetForeground().IsLegacy()) { - memcpy(end, ";7", 2); - end += 2; + const uint8_t index = sgr[attributes.GetForeground().GetIndex()]; + out = fmt::format_to(out, FMT_COMPILE(";{}"), index); } - // `attributes` of exactly `FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED` - // are often used to indicate the default colors in Windows Console applications. - // Since we always emit SGR 0 (reset all attributes), we simply need to skip this branch. - if ((attributes & (FG_ATTRS | BG_ATTRS)) != (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED)) + // 4 bytes (";107"). + if (attributes.GetBackground().IsLegacy()) { - // The Console API represents colors in BGR order, but VT represents them in RGB order. - // This LUT transposes them. This is for foreground colors. Add +10 to get the background ones. - static constexpr uint8_t lut[] = { 30, 34, 32, 36, 31, 35, 33, 37, 90, 94, 92, 96, 91, 95, 93, 97 }; - const uint8_t fg = lut[attributes & 0xf]; - const uint8_t bg = lut[(attributes >> 4) & 0xf] + 10; - end = fmt::format_to(end, FMT_COMPILE(";{};{}"), fg, bg); + const uint8_t index = sgr[attributes.GetBackground().GetIndex()] + 10; + out = fmt::format_to(out, FMT_COMPILE(";{}"), index); } - *end++ = 'm'; - return end - &buffer[0]; + // 1 byte. + *out++ = 'm'; + return out; } -void VtIo::FormatAttributes(std::string& target, WORD attributes) +void VtIo::FormatAttributes(std::string& target, const TextAttribute& attributes) { - char buf[16]; - const auto len = formatAttributes(buf, attributes); + char buf[formatAttributesMaxLen]; + const size_t len = formatAttributes(&buf[0], attributes) - &buf[0]; target.append(buf, len); } -void VtIo::FormatAttributes(std::wstring& target, WORD attributes) +void VtIo::FormatAttributes(std::wstring& target, const TextAttribute& attributes) { - char buf[16]; - const auto len = formatAttributes(buf, attributes); + char buf[formatAttributesMaxLen]; + const size_t len = formatAttributes(&buf[0], attributes) - &buf[0]; - wchar_t bufW[16]; + wchar_t bufW[formatAttributesMaxLen]; for (size_t i = 0; i < len; i++) { bufW[i] = buf[i]; @@ -354,71 +364,241 @@ void VtIo::FormatAttributes(std::wstring& target, WORD attributes) target.append(bufW, len); } -void VtIo::WriteUTF8(std::string_view str) +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() +{ + _io->_uncork(); + _io = nullptr; +} + +void VtIo::_uncork() +{ + _corked -= 1; + if (_corked <= 0) + { + _flushNow(); + } +} + +void VtIo::_flushNow() { - if (str.empty() || !_hOutput) + size_t minSize = 0; + + if (_writerRestoreCursor) + { + minSize = 4; + _writerRestoreCursor = false; + _back.append("\x1b\x38"); // DECRC: DEC Restore Cursor (+ attributes) + } + + if (_overlappedPending) + { + _overlappedPending = false; + std::ignore = _overlappedEvent.wait(); + } + + _front.clear(); + _front.swap(_back); + + // If it's >64KiB 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 > 64 * 1024 && cap > _front.capacity() / 2) + { + _back = std::string{}; + } + + // We encountered an exception and shouldn't flush the broken pieces. + if (_writerTainted) { + _writerTainted = false; return; } - _back.append(str); - _flush(); + // 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; + } + + for (;;) + { + if (WriteFile(_hOutput.get(), _front.data(), gsl::narrow_cast(_front.size()), nullptr, _overlapped)) + { + return; + } + + switch (const auto gle = GetLastError()) + { + case ERROR_BROKEN_PIPE: + _hOutput.reset(); + 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::WriteUTF16(std::wstring_view str) +void VtIo::Writer::WriteUTF16(std::wstring_view str) const { - if (str.empty() || !_hOutput) + if (str.empty()) { return; } - const auto existingUTF8Len = _back.size(); - const auto incomingUTF16Len = gsl::narrow(str.size()); + 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 totalUTF8Cap = gsl::narrow(gsl::narrow_cast(incomingUTF16Len) * 3 + existingUTF8Len); - const auto incomingUTF8Cap = gsl::narrow(totalUTF8Cap - existingUTF8Len); + 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"); + } - _back._Resize_and_overwrite(totalUTF8Cap, [=](char* ptr, const size_t) noexcept { - const auto len = WideCharToMultiByte(CP_UTF8, 0, str.data(), incomingUTF16Len, ptr + existingUTF8Len, incomingUTF8Cap, nullptr, nullptr); + // 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; - _flush(); + // 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::WriteUTF16StripControlChars(std::wstring_view str) +void VtIo::Writer::WriteUTF16StripControlChars(std::wstring_view str) const { - const auto cork = Cork(); 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 = Microsoft::Console::Utils::FindActionableControlCharacter(it, end - it); - const auto begRegularChars = Microsoft::Console::Utils::FindNonActionableRegularCharacter(begControlChars, end - begControlChars); + const auto begControlChars = FindActionableControlCharacter(it, end - it); - WriteUTF16({ it, begControlChars }); // First we append the regular characters. - _back.append(begRegularChars - begControlChars, ' '); // And then as many spaces as control characters. + WriteUTF16({ it, begControlChars }); - it = begRegularChars; + for (it = begControlChars; it != end && IsControlCharacter(*it); ++it) + { + WriteUCS2StripControlChars(*it); + } } - - _flush(); } -void VtIo::WriteUCS2(wchar_t ch) +void VtIo::Writer::WriteUCS2(wchar_t ch) const { char buf[4]; size_t len = 0; - if (ch < L' ') - { - ch = UNICODE_SPACE; - } if (til::is_surrogate(ch)) { ch = UNICODE_REPLACEMENT; @@ -440,67 +620,81 @@ void VtIo::WriteUCS2(wchar_t ch) buf[len++] = static_cast(0x80 | (ch & 0x3f)); } - WriteUTF8({ &buf[0], len }); + _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::WriteCUP(til::point position) +void VtIo::Writer::WriteCUP(til::point position) const { WriteFormat(FMT_COMPILE("\x1b[{};{}H"), position.y + 1, position.x + 1); } // DECTCEM: Text Cursor Enable -void VtIo::WriteDECTCEM(bool enabled) +void VtIo::Writer::WriteDECTCEM(bool enabled) const { char buf[] = "\x1b[?25h"; buf[std::size(buf) - 2] = enabled ? 'h' : 'l'; - WriteUTF8({ buf, std::size(buf) - 1 }); + _io->_back.append(&buf[0], std::size(buf) - 1); } // SGR 1006: SGR Extended Mouse Mode -void VtIo::WriteSGR1006(bool enabled) +void VtIo::Writer::WriteSGR1006(bool enabled) const { char buf[] = "\x1b[?1003;1006h"; buf[std::size(buf) - 2] = enabled ? 'h' : 'l'; - WriteUTF8({ buf, std::size(buf) - 1 }); + _io->_back.append(&buf[0], std::size(buf) - 1); } // DECAWM: Autowrap Mode -void VtIo::WriteDECAWM(bool enabled) +void VtIo::Writer::WriteDECAWM(bool enabled) const { char buf[] = "\x1b[?7h"; buf[std::size(buf) - 2] = enabled ? 'h' : 'l'; - WriteUTF8({ buf, std::size(buf) - 1 }); -} - -// LNM: Line Feed / New Line Mode -void VtIo::WriteLNM(bool enabled) -{ - char buf[] = "\x1b[20h"; - buf[std::size(buf) - 2] = enabled ? 'h' : 'l'; - WriteUTF8({ buf, std::size(buf) - 1 }); + _io->_back.append(&buf[0], std::size(buf) - 1); } // ASB: Alternate Screen Buffer -void VtIo::WriteASB(bool enabled) +void VtIo::Writer::WriteASB(bool enabled) const { char buf[] = "\x1b[?1049h"; buf[std::size(buf) - 2] = enabled ? 'h' : 'l'; - WriteUTF8({ buf, std::size(buf) - 1 }); + _io->_back.append(&buf[0], std::size(buf) - 1); } -void VtIo::WriteAttributes(WORD attributes) +void VtIo::Writer::WriteAttributes(const TextAttribute& attributes) const { - FormatAttributes(_back, attributes); - _flush(); + FormatAttributes(_io->_back, attributes); } -void VtIo::WriteInfos(til::point target, std::span infos) +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; - const auto cork = Cork(); WORD attributes = 0xffff; WriteCUP(target); @@ -543,116 +737,20 @@ void VtIo::WriteInfos(til::point target, std::span infos) if (attributes != ci.Attributes) { attributes = ci.Attributes; - WriteAttributes(attributes); + WriteAttributes(TextAttribute{ attributes }); } - const auto isSurrogate = til::is_surrogate(ch); - const auto isControl = IsControlCharacter(ch); int repeat = 1; - if (isSurrogate || isControl) + if (wide && (til::is_surrogate(ch) || IsControlCharacter(ch))) { - ch = isSurrogate ? UNICODE_REPLACEMENT : L' '; - // Space and U+FFFD are narrow characters, so if the caller intended - // for a wide glyph we need to emit two U+FFFD characters. - repeat = wide ? 2 : 1; + // 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 { - WriteUCS2(ch); + WriteUCS2StripControlChars(ch); } while (--repeat); } } - -VtIo::CorkLock VtIo::Cork() noexcept -{ - _corked += 1; - return CorkLock{ this }; -} - -VtIo::CorkLock::CorkLock(VtIo* io) noexcept : - _io{ io } -{ -} - -VtIo::CorkLock::~CorkLock() noexcept -{ - if (_io) - { - _io->_uncork(); - } -} - -VtIo::CorkLock::CorkLock(CorkLock&& other) noexcept : - _io{ std::exchange(other._io, nullptr) } -{ -} - -VtIo::CorkLock& VtIo::CorkLock::operator=(CorkLock&& other) noexcept -{ - if (this != &other) - { - this->~CorkLock(); - _io = std::exchange(other._io, nullptr); - } - return *this; -} - -void VtIo::_uncork() -{ - _corked -= 1; - _flush(); -} - -void VtIo::_flush() -{ - if (_corked <= 0 && !_back.empty()) - { - _flushNow(); - } -} - -void VtIo::_flushNow() -{ - if (_overlappedPending) - { - _overlappedPending = false; - std::ignore = _overlappedEvent.wait(); - } - - _front.clear(); - _front.swap(_back); - - // If it's >64KiB 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 > 64 * 1024 && cap > _front.capacity() / 2) - { - _back = std::string{}; - } - - for (;;) - { - if (WriteFile(_hOutput.get(), _front.data(), gsl::narrow_cast(_front.size()), nullptr, _overlapped)) - { - return; - } - - switch (const auto gle = GetLastError()) - { - case ERROR_BROKEN_PIPE: - CloseOutput(); - return; - case ERROR_IO_PENDING: - _overlappedPending = true; - return; - default: - LOG_WIN32(gle); - return; - } - } -} - -bool VtIo::BufferHasContent() const noexcept -{ - return !_back.empty(); -} diff --git a/src/host/VtIo.hpp b/src/host/VtIo.hpp index 977e71d2078..0a8cf05fb4d 100644 --- a/src/host/VtIo.hpp +++ b/src/host/VtIo.hpp @@ -13,86 +13,65 @@ namespace Microsoft::Console::VirtualTerminal class VtIo { public: - struct CorkLock + struct Writer { - CorkLock() = default; - CorkLock(VtIo* io) noexcept; - - ~CorkLock() noexcept; - - CorkLock(const CorkLock&) = delete; - CorkLock& operator=(const CorkLock&) = delete; - CorkLock(CorkLock&& other) noexcept; - CorkLock& operator=(CorkLock&& other) noexcept; + 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 WriteFormat(auto&&... args) const + { + fmt::format_to(std::back_inserter(_io->_back), std::forward(args)...); + } + 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; }; - struct CursorRestore - { - CursorRestore() = default; - CursorRestore(VtIo* io, til::point position) noexcept; - - ~CursorRestore() noexcept; - - CursorRestore(const CursorRestore&) = delete; - CursorRestore& operator=(const CursorRestore&) = delete; - CursorRestore(CursorRestore&& other) noexcept; - CursorRestore& operator=(CursorRestore&& other) noexcept; - - private: - VtIo* _io = nullptr; - til::point _position; - }; + friend struct Writer; - friend struct CorkLock; - - static bool IsControlCharacter(wchar_t wch) noexcept; - static void FormatAttributes(std::string& target, WORD attributes); - static void FormatAttributes(std::wstring& target, WORD attributes); + static void FormatAttributes(std::string& target, const TextAttribute& attributes); + static void FormatAttributes(std::wstring& target, const TextAttribute& attributes); [[nodiscard]] HRESULT Initialize(const ConsoleArguments* const pArgs); [[nodiscard]] HRESULT CreateAndStartSignalThread() noexcept; [[nodiscard]] HRESULT CreateIoHandlers() noexcept; bool IsUsingVt() const; - [[nodiscard]] HRESULT StartIfNeeded(); - void SendCloseEvent(); - void CloseInput(); - void CloseOutput(); - void CreatePseudoWindow(); - - CorkLock Cork() noexcept; - bool BufferHasContent() const noexcept; - - void WriteFormat(auto&&... args) - { - fmt::format_to(std::back_inserter(_back), std::forward(args)...); - _flush(); - } - void WriteUTF8(std::string_view str); - void WriteUTF16(std::wstring_view str); - void WriteUTF16StripControlChars(std::wstring_view str); - void WriteUCS2(wchar_t ch); - void WriteCUP(til::point position); - void WriteDECTCEM(bool enabled); - void WriteSGR1006(bool enabled); - void WriteDECAWM(bool enabled); - void WriteLNM(bool enabled); - void WriteASB(bool enabled); - void WriteAttributes(WORD attributes); - void WriteInfos(til::point target, std::span infos); + Writer GetWriter() noexcept; private: [[nodiscard]] HRESULT _Initialize(const HANDLE InHandle, const HANDLE OutHandle, _In_opt_ const HANDLE SignalHandle); void _uncork(); - void _flush(); void _flushNow(); // After CreateIoHandlers is called, these will be invalid. @@ -113,6 +92,8 @@ namespace Microsoft::Console::VirtualTerminal OVERLAPPED _overlappedBuf{}; wil::unique_event _overlappedEvent; bool _overlappedPending = false; + bool _writerRestoreCursor = false; + bool _writerTainted = false; bool _initialized = false; bool _lookingForCursorPosition = false; diff --git a/src/host/_output.cpp b/src/host/_output.cpp index 19f3330068e..9240880bcf8 100644 --- a/src/host/_output.cpp +++ b/src/host/_output.cpp @@ -53,8 +53,8 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region) enum class FillConsoleMode { WriteAttribute, - WriteCharacter, FillAttribute, + WriteCharacter, FillCharacter, }; @@ -79,140 +79,142 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon const auto bufferSize = screenBuffer.GetBufferSize(); FillConsoleResult result; - // 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. - // - // Constructing an OutputCellIterator with a `wchar_t` takes the `wchar_t` by reference, so that it can reference - // it in a `wstring_view` forever. That's of course really bad because passing a `const uint16_t&` to a - // `const wchar_t&` argument implicitly converts the types. To do so, the implicit conversion allocates a - // `wchar_t` value on the stack. The lifetime of that copy DOES NOT get extended beyond the constructor call. - // The result is that OutputCellIterator would read random data from the stack. - // - // Don't ever assume the lifetime of implicitly convertible types given by reference. - // Ironically that's a bug that cannot happen with C pointers. To no ones surprise, C keeps on winning. - auto attrs = static_cast(data); - auto chars = static_cast(data); - if (!bufferSize.IsInBounds(startingCoordinate)) { return {}; } - if (const auto io = gci.GetVtIoForBuffer(&screenInfo)) + if (auto writer = gci.GetVtWriterForBuffer(&screenInfo)) { - const auto corkLock = io->Cork(); + writer.BackupCursor(); const auto h = bufferSize.Height(); const auto w = bufferSize.Width(); auto y = startingCoordinate.y; - til::CoordType end = 0; - auto remaining = lengthToWrite; - - til::small_vector infos; - infos.resize(gsl::narrow_cast(w) + 1); - + auto input = static_cast(data); + size_t inputPos = 0; + til::small_vector infoBuffer; Viewport unused; - while (y < h && remaining > 0) + infoBuffer.resize(gsl::narrow_cast(w)); + + while (y < h && inputPos < lengthToWrite) { const auto beg = y == startingCoordinate.y ? startingCoordinate.x : 0; - auto len = std::min(remaining, gsl::narrow_cast(w - beg)); - end = beg + gsl::narrow_cast(len); + const auto columnsAvailable = w - beg; + til::CoordType columns = 0; - auto viewport = Viewport::FromInclusive({ beg, y, end - 1, y }); - THROW_IF_FAILED(ReadConsoleOutputWImplHelper(screenInfo, infos, viewport, unused)); + const auto readViewport = Viewport::FromInclusive({ beg, y, w - 1, y }); + THROW_IF_FAILED(ReadConsoleOutputWImplHelper(screenInfo, infoBuffer, readViewport, unused)); switch (mode) { case FillConsoleMode::WriteAttribute: - for (size_t i = 0; i < len; ++i) + for (; columns < columnsAvailable && inputPos < lengthToWrite; ++columns, ++inputPos) { - infos[i].Attributes = *attrs++; + 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 (size_t i = 0; i < len;) + for (; columns < columnsAvailable && inputPos < lengthToWrite; ++inputPos) { - const auto ch = *chars++; - - auto& lead = infos[i++]; - lead.Char.UnicodeChar = ch; - lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE); - - if (IsGlyphFullWidth(ch)) + const auto ch = input[inputPos]; + if (ch >= 0x80 && IsGlyphFullWidth(ch)) { - lead.Attributes |= COMMON_LVB_LEADING_BYTE; + // 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 = infos[i++]; + auto& trail = infoBuffer[columns++]; trail.Char.UnicodeChar = ch; - trail.Attributes = trail.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE) | COMMON_LVB_LEADING_BYTE; + 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::FillAttribute: - { - const auto attr = *attrs; - for (size_t i = 0; i < len; ++i) - { - infos[i].Attributes = attr; - } - break; - } case FillConsoleMode::FillCharacter: - { - const auto ch = *chars; - - if (IsGlyphFullWidth(ch)) + // Identical to WriteCharacter above, but with the if() and for() swapped. + if (const auto ch = input[0]; ch >= 0x80 && IsGlyphFullWidth(ch)) { - for (size_t i = 0; i < len;) + for (; columns < columnsAvailable && inputPos < lengthToWrite; ++inputPos) { - auto& lead = infos[i++]; + // 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_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE) | COMMON_LVB_LEADING_BYTE; + lead.Attributes = lead.Attributes & ~COMMON_LVB_TRAILING_BYTE | COMMON_LVB_LEADING_BYTE; - auto& trail = infos[i++]; + auto& trail = infoBuffer[columns++]; trail.Char.UnicodeChar = ch; - trail.Attributes = trail.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE) | COMMON_LVB_LEADING_BYTE; + trail.Attributes = trail.Attributes & ~COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE; } } else { - for (size_t i = 0; i < len;) + for (; columns < columnsAvailable && inputPos < lengthToWrite; ++inputPos) { - auto& lead = infos[i++]; + auto& lead = infoBuffer[columns++]; lead.Char.UnicodeChar = ch; lead.Attributes = lead.Attributes & ~(COMMON_LVB_LEADING_BYTE | COMMON_LVB_TRAILING_BYTE); } } - break; } - } - - if (auto& last = infos[len - 1]; last.Attributes & COMMON_LVB_LEADING_BYTE) - { - len--; - end--; - } - viewport = Viewport::FromInclusive({ beg, y, end - 1, y }); - THROW_IF_FAILED(WriteConsoleOutputWImplHelper(screenInfo, infos, viewport.Width(), viewport, unused)); + const auto writeViewport = Viewport::FromInclusive({ beg, y, beg + columns - 1, y }); + THROW_IF_FAILED(WriteConsoleOutputWImplHelper(screenInfo, infoBuffer, w, writeViewport, unused)); y += 1; - remaining -= len; + result.cellsModified += columns; } - result.lengthRead = lengthToWrite - remaining; - result.cellsModified = std::max(0, (y - startingCoordinate.y - 1) * w + end - startingCoordinate.x); + result.lengthRead = inputPos; - if (io && io->BufferHasContent()) - { - io->WriteCUP(screenInfo.GetTextBuffer().GetCursor().GetPosition()); - io->WriteAttributes(screenInfo.GetAttributes().GetLegacyAttributes()); - } + 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. + // + // Constructing an OutputCellIterator with a `wchar_t` takes the `wchar_t` by reference, so that it can reference + // it in a `wstring_view` forever. That's of course really bad because passing a `const uint16_t&` to a + // `const wchar_t&` argument implicitly converts the types. To do so, the implicit conversion allocates a + // `wchar_t` value on the stack. The lifetime of that copy DOES NOT get extended beyond the constructor call. + // The result is that OutputCellIterator would read random data from the stack. + // + // Don't ever assume the lifetime of implicitly convertible types given by reference. + // Ironically that's a bug that cannot happen with C pointers. To no ones surprise, C keeps on winning. + auto attrs = static_cast(data); + auto chars = static_cast(data); + OutputCellIterator it; switch (mode) @@ -381,16 +383,16 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon LockConsole(); const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (const auto io = gci.GetVtIoForBuffer(&OutContext)) + // 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 Full Reset (RIS). + if (enablePowershellShim) { - // 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 Full Reset (RIS). - 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()); @@ -437,16 +439,16 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon LockConsole(); const auto unlock = wil::scope_exit([&] { UnlockConsole(); }); - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (const auto io = gci.GetVtIoForBuffer(&OutContext)) + // 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 Full Reset (RIS). + if (enablePowershellShim) { - // 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 Full Reset (RIS). - 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()); diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index 9958a2a039f..43f664c51ee 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -160,12 +160,12 @@ 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(); - const auto io = gci.GetVtIoForBuffer(&screenInfo); - const auto corkLock = io ? io->Cork() : Microsoft::Console::VirtualTerminal::VtIo::CorkLock{}; + auto writer = gci.GetVtWriterForBuffer(&screenInfo); // 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 @@ -189,15 +189,16 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t { const auto lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, text, psScrollY); - if (io) + 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. - io->WriteUTF16StripControlChars(text); + writer.WriteUTF16StripControlChars(text); if (lastCharWrapped) { - io->WriteUTF8(" \r"); + writer.WriteUTF8("\r\n"); } + writer.Submit(); } return; @@ -212,12 +213,12 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t const auto lastCharWrapped = _writeCharsLegacyUnprocessed(screenInfo, chunk, psScrollY); it = nextControlChar; - if (io) + if (writer) { - io->WriteUTF16(chunk); + writer.WriteUTF16(chunk); if (lastCharWrapped) { - io->WriteUTF8(" \r"); + writer.WriteUTF8("\r\n"); } } } @@ -263,9 +264,12 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t 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); @@ -299,21 +303,26 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t } } - if (io) + if (writer) { if (wch) { - io->WriteUTF16({ &wch, 1 }); + writer.WriteUCS2(wch); } if (lastCharWrapped) { - io->WriteUTF8(" \r"); + writer.WriteUTF8("\r\n"); } } ++it; } while (it != end && controlCharPredicate(*it)); } + + if (writer) + { + writer.Submit(); + } } void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str) @@ -322,38 +331,42 @@ void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str) 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. - const auto io = gci.GetVtIoForBuffer(&screenInfo); + auto writer = gci.GetVtWriterForBuffer(&screenInfo); stateMachine.ProcessString(str); - if (io) + if (writer) { - const auto cork = io->Cork(); 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) { - io->WriteUTF16(til::safe_slice_abs(str, offset, injection.offset)); + write(offset, injection.offset); offset = injection.offset; - switch (injection.type) - { - case InjectionType::RIS: - io->WriteUTF8( - "\033[?1004h" // Focus Event Mode - "\033[?9001h" // Win32 Input Mode - ); - break; - case InjectionType::DECSET_FOCUS: - io->WriteUTF8( - "\033[?1004h" // Focus Event Mode - ); - break; - } + 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)]); } - io->WriteUTF16(til::safe_slice_abs(str, offset, std::wstring_view::npos)); + write(offset, std::wstring_view::npos); + writer.Submit(); } } diff --git a/src/host/consoleInformation.cpp b/src/host/consoleInformation.cpp index d7718cd4820..de12eae7ad2 100644 --- a/src/host/consoleInformation.cpp +++ b/src/host/consoleInformation.cpp @@ -123,14 +123,14 @@ VtIo* CONSOLE_INFORMATION::GetVtIoNoCheck() noexcept return &_vtIo; } -VtIo* CONSOLE_INFORMATION::GetVtIo() noexcept +VtIo::Writer CONSOLE_INFORMATION::GetVtWriter() noexcept { - return _vtIo.IsUsingVt() ? &_vtIo : nullptr; + return _vtIo.IsUsingVt() ? _vtIo.GetWriter() : VtIo::Writer{}; } -VtIo* CONSOLE_INFORMATION::GetVtIoForBuffer(const SCREEN_INFORMATION* context) noexcept +VtIo::Writer CONSOLE_INFORMATION::GetVtWriterForBuffer(const SCREEN_INFORMATION* context) noexcept { - return _vtIo.IsUsingVt() && (pCurrentScreenBuffer == context || pCurrentScreenBuffer == context->GetAltBuffer()) ? &_vtIo : nullptr; + return _vtIo.IsUsingVt() && (pCurrentScreenBuffer == context || pCurrentScreenBuffer == context->GetAltBuffer()) ? _vtIo.GetWriter() : VtIo::Writer{}; } bool CONSOLE_INFORMATION::IsConPTY() const noexcept diff --git a/src/host/directio.cpp b/src/host/directio.cpp index 02f5f862afe..442236cf424 100644 --- a/src/host/directio.cpp +++ b/src/host/directio.cpp @@ -574,9 +574,6 @@ CATCH_RETURN(); return E_INVALIDARG; } - auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto io = gci.GetVtIoForBuffer(&context); - auto& storageBuffer = context.GetActiveBuffer(); const auto storageRectangle = storageBuffer.GetBufferSize(); const auto clippedRectangle = storageRectangle.Clamp(requestRectangle); @@ -601,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); @@ -609,9 +609,9 @@ CATCH_RETURN(); // Make the iterator and write to the target position. storageBuffer.Write(OutputCellIterator(charInfos), target); - if (io) + if (writer) { - io->WriteInfos(target, charInfos); + writer.WriteInfos(target, charInfos); } totalOffset += bufferStride; @@ -623,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(); @@ -639,18 +644,21 @@ CATCH_RETURN(); try { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto io = gci.GetVtIoForBuffer(&context); - const auto corkLock = io ? io->Cork() : Microsoft::Console::VirtualTerminal::VtIo::CorkLock{}; + 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 (io && io->BufferHasContent()) + if (writer) { - io->WriteCUP(context.GetTextBuffer().GetCursor().GetPosition()); - io->WriteAttributes(context.GetAttributes().GetLegacyAttributes()); + writer.Submit(); } return S_OK; @@ -669,8 +677,12 @@ CATCH_RETURN(); try { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto io = gci.GetVtIoForBuffer(&context); - const auto corkLock = io ? io->Cork() : Microsoft::Console::VirtualTerminal::VtIo::CorkLock{}; + auto writer = gci.GetVtWriterForBuffer(&context); + + if (writer) + { + writer.BackupCursor(); + } if (!context.GetActiveBuffer().GetCurrentFont().IsTrueTypeFont()) { @@ -684,10 +696,9 @@ CATCH_RETURN(); RETURN_IF_FAILED(WriteConsoleOutputWImplHelper(context, buffer, requestRectangle.Width(), requestRectangle, writtenRectangle)); } - if (io && io->BufferHasContent()) + if (writer) { - io->WriteCUP(context.GetTextBuffer().GetCursor().GetPosition()); - io->WriteAttributes(context.GetAttributes().GetLegacyAttributes()); + writer.Submit(); } return S_OK; diff --git a/src/host/getset.cpp b/src/host/getset.cpp index 19a09cf159d..749a170142e 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -363,7 +363,7 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept WI_ClearFlag(gci.Flags, CONSOLE_USE_PRIVATE_FLAGS); } - if (const auto io = gci.GetVtIo()) + if (auto writer = gci.GetVtWriter()) { auto oldMode = context.InputMode; auto newMode = mode; @@ -377,13 +377,13 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept if (const auto diff = oldMode ^ newMode) { - const auto cork = io->Cork(); - if (WI_IsFlagSet(diff, ENABLE_MOUSE_INPUT)) { - io->WriteSGR1006(WI_IsFlagSet(newMode, ENABLE_MOUSE_INPUT)); + writer.WriteSGR1006(WI_IsFlagSet(newMode, ENABLE_MOUSE_INPUT)); } } + + writer.Submit(); } context.InputMode = mode; @@ -451,17 +451,14 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept } auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (const auto io = gci.GetVtIoForBuffer(&context)) + if (auto writer = gci.GetVtWriterForBuffer(&context)) { if (WI_IsFlagSet(diff, ENABLE_WRAP_AT_EOL_OUTPUT)) { - io->WriteDECAWM(WI_IsFlagSet(dwNewMode, ENABLE_WRAP_AT_EOL_OUTPUT)); + writer.WriteDECAWM(WI_IsFlagSet(dwNewMode, ENABLE_WRAP_AT_EOL_OUTPUT)); } - if (WI_IsFlagSet(diff, DISABLE_NEWLINE_AUTO_RETURN)) - { - io->WriteLNM(WI_IsFlagClear(dwNewMode, DISABLE_NEWLINE_AUTO_RETURN)); - } + writer.Submit(); } return S_OK; @@ -481,10 +478,8 @@ void ApiRoutines::SetConsoleActiveScreenBufferImpl(SCREEN_INFORMATION& newContex auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (const auto io = gci.GetVtIo()) + if (auto writer = gci.GetVtWriter()) { - const auto cork = io->Cork(); - const auto viewport = gci.GetActiveOutputBuffer().GetBufferSize(); const auto size = viewport.Dimensions(); const auto area = static_cast(viewport.Width() * viewport.Height()); @@ -510,24 +505,25 @@ void ApiRoutines::SetConsoleActiveScreenBufferImpl(SCREEN_INFORMATION& newContex THROW_IF_FAILED(ReadConsoleOutputWImpl(screenInfo, infos, viewport, read)); for (til::CoordType i = 0; i < size.height; i++) { - io->WriteInfos({ 0, i }, { infos.begin() + i * size.width, static_cast(size.width) }); + writer.WriteInfos({ 0, i }, { infos.begin() + i * size.width, static_cast(size.width) }); } - io->WriteCUP(screenInfo.GetTextBuffer().GetCursor().GetPosition()); - io->WriteAttributes(screenInfo.GetAttributes().GetLegacyAttributes()); - io->WriteDECTCEM(screenInfo.GetTextBuffer().GetCursor().IsVisible()); - io->WriteDECAWM(WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT)); - io->WriteLNM(WI_IsFlagClear(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN)); + 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)); }; - io->WriteASB(false); + writer.WriteASB(false); dumpScreenInfo(main); if (hasAltBuffer) { - io->WriteASB(true); + writer.WriteASB(true); dumpScreenInfo(alt); } + + writer.Submit(); } SetActiveScreenBuffer(newContext.GetActiveBuffer()); @@ -781,9 +777,10 @@ 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(); - if (const auto io = gci.GetVtIoForBuffer(&context)) + if (auto writer = gci.GetVtWriterForBuffer(&context)) { - io->WriteCUP(position); + writer.WriteCUP(position); + writer.Submit(); } RETURN_IF_NTSTATUS_FAILED(buffer.SetCursorPosition(position, true)); @@ -861,9 +858,10 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont context.SetCursorInformation(size, isVisible); auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (const auto io = gci.GetVtIoForBuffer(&context)) + if (auto writer = gci.GetVtWriterForBuffer(&context)) { - io->WriteDECTCEM(isVisible); + writer.WriteDECTCEM(isVisible); + writer.Submit(); } return S_OK; @@ -993,7 +991,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont auto Unlock = wil::scope_exit([&] { UnlockConsole(); }); auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (const auto io = gci.GetVtIoForBuffer(&context)) + if (auto writer = gci.GetVtWriterForBuffer(&context)) { auto& buffer = context.GetActiveBuffer(); @@ -1020,11 +1018,10 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont fillCharacter == UNICODE_SPACE && fillAttribute == buffer.GetAttributes().GetLegacyAttributes()) { WriteClearScreen(context); + writer.Submit(); return S_OK; } - const auto corkLock = io->Cork(); - const auto clipViewport = clip ? Viewport::FromInclusive(*clip) : bufferSize; const auto sourceViewport = Viewport::FromInclusive(source); Viewport readViewport; @@ -1044,15 +1041,13 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont 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)); - if (io && io->BufferHasContent()) - { - io->WriteCUP(context.GetTextBuffer().GetCursor().GetPosition()); - io->WriteAttributes(context.GetAttributes().GetLegacyAttributes()); - } + writer.Submit(); } else { @@ -1087,9 +1082,10 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont context.SetAttributes(attr); auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (const auto io = gci.GetVtIoForBuffer(&context)) + if (auto writer = gci.GetVtWriterForBuffer(&context)) { - io->WriteAttributes(attribute); + writer.WriteAttributes(attr); + writer.Submit(); } return S_OK; @@ -1627,12 +1623,12 @@ void ApiRoutines::GetConsoleDisplayModeImpl(ULONG& flags) noexcept auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); gci.SetTitle(title); - if (const auto io = gci.GetVtIo()) + if (auto writer = gci.GetVtWriter()) { - const auto cork = io->Cork(); - io->WriteUTF8("\x1b]0;"); - io->WriteUTF16StripControlChars(title); - io->WriteUTF8("\x7"); + writer.WriteUTF8("\x1b]0;"); + writer.WriteUTF16StripControlChars(title); + writer.WriteUTF8("\x7"); + writer.Submit(); } return S_OK; diff --git a/src/host/server.h b/src/host/server.h index 92679de8992..03b47d03652 100644 --- a/src/host/server.h +++ b/src/host/server.h @@ -104,8 +104,8 @@ class CONSOLE_INFORMATION : ULONG GetCSRecursionCount() const noexcept; Microsoft::Console::VirtualTerminal::VtIo* GetVtIoNoCheck() noexcept; - Microsoft::Console::VirtualTerminal::VtIo* GetVtIo() noexcept; - Microsoft::Console::VirtualTerminal::VtIo* GetVtIoForBuffer(const SCREEN_INFORMATION* context) noexcept; + Microsoft::Console::VirtualTerminal::VtIo::Writer GetVtWriter() noexcept; + Microsoft::Console::VirtualTerminal::VtIo::Writer GetVtWriterForBuffer(const SCREEN_INFORMATION* context) noexcept; bool IsConPTY() const noexcept; SCREEN_INFORMATION& GetActiveOutputBuffer() override; diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index ed89acef7eb..ddd4af6c1b5 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -15,23 +15,27 @@ using namespace Microsoft::Console::VirtualTerminal; using namespace Microsoft::Console::Render; using namespace Microsoft::Console::Types; -constexpr CHAR_INFO red(wchar_t ch) noexcept +static constexpr WORD red = FOREGROUND_RED | BACKGROUND_GREEN; +static constexpr WORD blu = FOREGROUND_BLUE | BACKGROUND_GREEN; + +constexpr CHAR_INFO ci_red(wchar_t ch) noexcept { - return { ch, FOREGROUND_RED }; + return { ch, red }; } -constexpr CHAR_INFO blu(wchar_t ch) noexcept +constexpr CHAR_INFO ci_blu(wchar_t ch) noexcept { - return { ch, FOREGROUND_BLUE }; + return { ch, blu }; } -#define cup(y, x) "\x1b[" #y ";" #x "H" -#define decawm(h) "\x1b[?7" #h -#define lnm(h) "\x1b[20" #h +#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 red() / blu() result in. -#define sgr_red(s) "\x1b[0;31;40m" s -#define sgr_blu(s) "\x1b[0;34;40m" s +// 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" @@ -118,27 +122,22 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests screenInfo->OutputMode = 0; - 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 ✔️ LNM ✔️ - THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfo, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)); // DECAWM ✔️ LNM ✖️ - THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfo, ENABLE_PROCESSED_OUTPUT | DISABLE_NEWLINE_AUTO_RETURN | ENABLE_LVB_GRID_WORLDWIDE)); // DECAWM ✖️ LNM ✔️ - THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfo, 0)); // DECAWM ✖️ LNM ✖️ - THROW_IF_FAILED(routines.SetConsoleOutputModeImpl(*screenInfo, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | DISABLE_NEWLINE_AUTO_RETURN | ENABLE_LVB_GRID_WORLDWIDE)); // DECAWM ✔️ LNM ✖️ - 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 ✔️ LNM ✔️ + 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 ✔️ const auto expected = - decawm(h) lnm(l) // DECAWM ✔️ LNM ✔️ - lnm(h) // DECAWM ✔️ LNM ✖️ - decawm(l) lnm(l) // DECAWM ✖️ LNM ✔️ - lnm(h) // DECAWM ✖️ LNM ✖️ - decawm(h) // DECAWM ✔️ LNM ✖️ - lnm(l); // DECAWM ✔️ LNM ✔️ + decawm(h) // DECAWM ✔️ + decawm(l) // DECAWM ✖️ + decawm(h); // DECAWM ✔️ const auto actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); } TEST_METHOD(SetConsoleTitleW) { - const char* expected = nullptr; + std::string_view expected; std::string_view actual; THROW_IF_FAILED(routines.SetConsoleTitleWImpl( @@ -151,7 +150,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests L"foo" "\u0001\u001f" "bar")); - expected = "\x1b]0;foo bar\a"; + expected = "\x1b]0;foo☺▼bar\a"; actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); @@ -160,7 +159,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests "\u0001\u001f" "bar" "\u007f\u009f")); - expected = "\x1b]0;foo bar \a"; + expected = "\x1b]0;foo☺▼bar⌂?\a"; actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); } @@ -180,12 +179,12 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests { for (WORD i = 0; i < 16; i++) { - THROW_IF_FAILED(routines.SetConsoleTextAttributeImpl(*screenInfo, i)); + THROW_IF_FAILED(routines.SetConsoleTextAttributeImpl(*screenInfo, i | BACKGROUND_RED)); } for (WORD i = 0; i < 16; i++) { - THROW_IF_FAILED(routines.SetConsoleTextAttributeImpl(*screenInfo, i << 4)); + THROW_IF_FAILED(routines.SetConsoleTextAttributeImpl(*screenInfo, i << 4 | FOREGROUND_RED)); } THROW_IF_FAILED(routines.SetConsoleTextAttributeImpl(*screenInfo, FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_GREEN | COMMON_LVB_REVERSE_VIDEO)); @@ -193,39 +192,39 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests const auto expected = // 16 foreground colors - "\x1b[0;30;40m" - "\x1b[0;34;40m" - "\x1b[0;32;40m" - "\x1b[0;36;40m" - "\x1b[0;31;40m" - "\x1b[0;35;40m" - "\x1b[0;33;40m" - "\x1b[0m" // <-- FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED gets translated to the default colors - "\x1b[0;90;40m" - "\x1b[0;94;40m" - "\x1b[0;92;40m" - "\x1b[0;96;40m" - "\x1b[0;91;40m" - "\x1b[0;95;40m" - "\x1b[0;93;40m" - "\x1b[0;97;40m" - // 16 background colors - "\x1b[0;30;40m" - "\x1b[0;30;44m" - "\x1b[0;30;42m" - "\x1b[0;30;46m" "\x1b[0;30;41m" - "\x1b[0;30;45m" - "\x1b[0;30;43m" - "\x1b[0;30;47m" - "\x1b[0;30;100m" - "\x1b[0;30;104m" - "\x1b[0;30;102m" - "\x1b[0;30;106m" - "\x1b[0;30;101m" - "\x1b[0;30;105m" - "\x1b[0;30;103m" - "\x1b[0;30;107m" + "\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"; @@ -239,7 +238,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests size_t written; std::unique_ptr waiter; - const char* expected = nullptr; + std::string_view expected; std::string_view actual; THROW_IF_FAILED(routines.WriteConsoleWImpl(*screenInfo, L"", written, false, waiter)); @@ -247,13 +246,15 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests 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"; + 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\r\nb"; + expected = "a\t\r\n\r\nb"; actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); } @@ -262,13 +263,12 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests { resetContents(); - std::array payload{ red('a'), red('b'), blu('A'), blu('B') }; + 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 = cup(2, 2) sgr_red("ab") sgr_blu("AB") cup(1, 1) sgr_rst(); + const auto expected = decsc() cup(2, 2) sgr_red("ab") sgr_blu("AB") decrc(); const auto actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); } @@ -277,15 +277,16 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests { setupInitialContents(); - static constexpr std::array payload{ FOREGROUND_RED, FOREGROUND_BLUE, FOREGROUND_RED, FOREGROUND_BLUE }; + 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") // - cup(1, 1) sgr_rst(); + decrc(); const auto actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); } @@ -294,16 +295,39 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests { setupInitialContents(); - static constexpr std::wstring_view payload{ L"foobar" }; - static constexpr til::point target{ 5, 1 }; - size_t written; - THROW_IF_FAILED(routines.WriteConsoleOutputCharacterWImpl(*screenInfo, payload, target, written)); + size_t written = 0; + std::string_view expected; + std::string_view actual; - const auto expected = + 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") // - cup(1, 1) sgr_rst(); - const auto actual = readOutput(); + 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); } @@ -312,36 +336,45 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests setupInitialContents(); size_t cellsModified = 0; - const char* expected = nullptr; + std::string_view expected; std::string_view actual; // Writing nothing should produce nothing. - THROW_IF_FAILED(routines.FillConsoleOutputAttributeImpl(*screenInfo, FOREGROUND_RED, 0, {}, cellsModified)); + 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, FOREGROUND_RED, 3, { 0, 0 }, cellsModified)); - expected = cup(1, 1) sgr_red("ABa") // - cup(1, 1) sgr_rst(); + 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, FOREGROUND_RED, 3, { 5, 0 }, cellsModified)); - expected = cup(1, 6) sgr_red("Dcd") // - cup(1, 1) sgr_rst(); + 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, FOREGROUND_BLUE, 8, { 4, 1 }, cellsModified)); + 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") // - cup(1, 1) sgr_rst(); + decrc(); actual = readOutput(); + VERIFY_ARE_EQUAL(8u, cellsModified); VERIFY_ARE_EQUAL(expected, actual); } @@ -350,7 +383,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests setupInitialContents(); size_t cellsModified = 0; - const char* expected = nullptr; + std::string_view expected; std::string_view actual; // Writing nothing should produce nothing. @@ -361,31 +394,46 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests // Writing at the start of a line. THROW_IF_FAILED(routines.FillConsoleOutputCharacterWImpl(*screenInfo, L'a', 3, { 0, 0 }, cellsModified)); - expected = cup(1, 1) sgr_red("aa") sgr_blu("a") // - cup(1, 1) sgr_rst(); + 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 = cup(1, 6) sgr_red("b") sgr_blu("bb") // - cup(1, 1) sgr_rst(); + 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") // - cup(1, 1) sgr_rst(); + 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) { - const char* expected = nullptr; + std::string_view expected; std::string_view actual; setupInitialContents(); @@ -398,11 +446,12 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests 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' ', FOREGROUND_RED, false)); + 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(" ") // - cup(1, 1) sgr_rst(); + decrc(); actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); @@ -434,13 +483,14 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests // | 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', FOREGROUND_RED, false)); + 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") // - cup(1, 1) sgr_rst(); + decrc(); actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); @@ -456,13 +506,14 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests // 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', FOREGROUND_BLUE, false)); + 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") // - cup(1, 1) sgr_rst(); + decrc(); actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); @@ -478,22 +529,23 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests // |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', FOREGROUND_RED, false)); + 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") // - cup(1, 1) sgr_rst(); + decrc(); actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); static constexpr std::array expectedContents{ { // clang-format off - red('A'), red('Z'), red('Z'), blu('b'), red('C'), red('D'), blu('c'), red('Y'), - red('E'), blu('z'), blu('z'), blu('f'), red('G'), red('H'), blu('g'), red('Y'), - blu('i'), blu('z'), blu('z'), red('J'), blu('d'), red('B'), red('E'), red('L'), - blu('m'), blu('n'), red('M'), red('N'), blu('h'), red('F'), blu('i'), red('P'), + 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{}; @@ -530,8 +582,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests 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) - "\x1b[20h"; // LNM (Line Feed / New Line Mode) + "\x1b[?7h"; // DECAWM (Autowrap Mode) const auto actual = readOutput(); VERIFY_ARE_EQUAL(expected, actual); } diff --git a/src/interactivity/base/InteractivityFactory.cpp b/src/interactivity/base/InteractivityFactory.cpp index 74dac545c6d..d13f915ff59 100644 --- a/src/interactivity/base/InteractivityFactory.cpp +++ b/src/interactivity/base/InteractivityFactory.cpp @@ -497,11 +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()) { char buf[] = "\x1b[1t"; buf[2] = showOrHide ? '1' : '2'; - io->WriteUTF8(buf); + writer.WriteUTF8(buf); + writer.Submit(); } } diff --git a/src/interactivity/win32/windowio.cpp b/src/interactivity/win32/windowio.cpp index a1337a46aef..4629fd44fd4 100644 --- a/src/interactivity/win32/windowio.cpp +++ b/src/interactivity/win32/windowio.cpp @@ -935,9 +935,10 @@ 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. - if (const auto io = ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()) + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + if (gci.IsConPTY()) { - io->CreatePseudoWindow(); + gci.GetVtIoNoCheck()->CreatePseudoWindow(); } // Register the pseudoconsole window as being owned by the root process. diff --git a/src/types/inc/utils.hpp b/src/types/inc/utils.hpp index 6574a2cf663..d00b94d374d 100644 --- a/src/types/inc/utils.hpp +++ b/src/types/inc/utils.hpp @@ -116,7 +116,6 @@ namespace Microsoft::Console::Utils std::wstring_view TrimPaste(std::wstring_view textView) noexcept; const wchar_t* FindActionableControlCharacter(const wchar_t* beg, const size_t len) noexcept; - const wchar_t* FindNonActionableRegularCharacter(const wchar_t* beg, const size_t len) noexcept; // Same deal, but in TerminalPage::_evaluatePathForCwd std::wstring EvaluateStartingDirectory(std::wstring_view cwd, std::wstring_view startingDirectory); diff --git a/src/types/utils.cpp b/src/types/utils.cpp index e7d224f47cf..cad31ba72d6 100644 --- a/src/types/utils.cpp +++ b/src/types/utils.cpp @@ -980,19 +980,6 @@ const wchar_t* Utils::FindActionableControlCharacter(const wchar_t* beg, const s return it; } -const wchar_t* Utils::FindNonActionableRegularCharacter(const wchar_t* beg, const size_t len) noexcept -{ - auto it = beg; - const auto end = beg + len; - -#pragma loop(no_vector) - for (; it < end && isActionableFromGround(*it); ++it) - { - } - - return it; -} - #pragma warning(pop) std::wstring Utils::EvaluateStartingDirectory( From d65bb806cd1f231db838ffe213880373464bdd74 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 11 Jul 2024 18:12:31 +0200 Subject: [PATCH 36/60] Forgot to remove LNM --- .github/actions/spelling/expect/expect.txt | 2 ++ src/host/VtIo.cpp | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 38da43c29c4..85983fc82b9 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -417,6 +417,7 @@ DECPCCM DECPCTERM DECPS DECRARA +decrc DECRC DECREQTPARM DECRLM @@ -432,6 +433,7 @@ DECRSPS decrst DECSACE DECSASD +decsc DECSC DECSCA DECSCNM diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index 360df3b382d..4789a22aa27 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -168,10 +168,7 @@ bool VtIo::IsUsingVt() const // send us full INPUT_RECORDs as input. If the terminal doesn't understand // this sequence, it'll just ignore it. - // By default, DISABLE_NEWLINE_AUTO_RETURN is reset. This implies LNM being set, - // which is not the default in terminals, so we have to do that explicitly. writer.WriteUTF8( - "\x1b[20h" // Line Feed / New Line Mode (LNM) "\033[?1004h" // Focus Event Mode "\033[?9001h" // Win32 Input Mode ); From 8b5955115188ffa343974c07ca55878d1f8d05b3 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 11 Jul 2024 18:33:13 +0200 Subject: [PATCH 37/60] Forgot to call Submit() --- src/host/_output.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/host/_output.cpp b/src/host/_output.cpp index 9240880bcf8..96519352528 100644 --- a/src/host/_output.cpp +++ b/src/host/_output.cpp @@ -448,7 +448,7 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon if (enablePowershellShim) { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (const auto writer = gci.GetVtWriterForBuffer(&OutContext)) + if (auto writer = gci.GetVtWriterForBuffer(&OutContext)) { const auto currentBufferDimensions{ OutContext.GetBufferSize().Dimensions() }; const auto wroteWholeBuffer = lengthToWrite == (currentBufferDimensions.area()); @@ -458,6 +458,7 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon if (wroteWholeBuffer && startedAtOrigin && wroteSpaces) { WriteClearScreen(OutContext); + writer.Submit(); cellsModified = lengthToWrite; return S_OK; } From 512468d47ff5e4b67cc70035f42f349550aac952 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 11 Jul 2024 18:38:20 +0200 Subject: [PATCH 38/60] LNM --- .github/actions/spelling/expect/expect.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 85983fc82b9..af85004faf5 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -961,7 +961,7 @@ llx LMENU lnkd lnkfile -lnm +LNM LOADONCALL LOBYTE localappdata From d3ee58b9fce08b50b1d307ebc012850966ef6b24 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 12 Jul 2024 00:19:42 +0200 Subject: [PATCH 39/60] Add a missing force-wrap, Minor cleanup --- src/host/VtIo.cpp | 6 +++--- src/host/VtIo.hpp | 4 ---- src/host/_stream.cpp | 5 +++++ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index 4789a22aa27..98988bad588 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -405,8 +405,8 @@ VtIo::Writer::operator bool() const noexcept void VtIo::Writer::Submit() { - _io->_uncork(); - _io = nullptr; + const auto io = std::exchange(_io, nullptr); + io->_uncork(); } void VtIo::_uncork() @@ -647,7 +647,7 @@ void VtIo::Writer::WriteUCS2StripControlChars(wchar_t ch) const // CUP: Cursor Position void VtIo::Writer::WriteCUP(til::point position) const { - WriteFormat(FMT_COMPILE("\x1b[{};{}H"), position.y + 1, position.x + 1); + fmt::format_to(std::back_inserter(_io->_back), FMT_COMPILE("\x1b[{};{}H"), position.y + 1, position.x + 1); } // DECTCEM: Text Cursor Enable diff --git a/src/host/VtIo.hpp b/src/host/VtIo.hpp index 0a8cf05fb4d..8129c0caba8 100644 --- a/src/host/VtIo.hpp +++ b/src/host/VtIo.hpp @@ -30,10 +30,6 @@ namespace Microsoft::Console::VirtualTerminal void Submit(); void BackupCursor() const; - void WriteFormat(auto&&... args) const - { - fmt::format_to(std::back_inserter(_io->_back), std::forward(args)...); - } void WriteUTF8(std::string_view str) const; void WriteUTF16(std::wstring_view str) const; void WriteUTF16TranslateCRLF(std::wstring_view str) const; diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index 7f030b256a0..9c3d3378a80 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -180,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"); + } } } From cc31a596699d5f1133eec822746b956f0218a5cb Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 12 Jul 2024 21:32:18 +0200 Subject: [PATCH 40/60] Use overlapped pipes for WT --- .../TerminalConnection/ConptyConnection.cpp | 31 +++++-------------- src/types/inc/utils.hpp | 8 +++++ src/types/utils.cpp | 19 ++++++++++++ 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index daf449bd37c..2ce3b6562a8 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -32,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 @@ -345,7 +322,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation // handoff from an already-started PTY process. if (!_inPipe) { - THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(til::unwrap_coord_size(dimensions), _flags, &_inPipe, &_outPipe, &_hPC)); + auto out = Utils::CreateOverlappedPipe(64 * 1024); + auto in = Utils::CreateOverlappedPipe(64 * 1024); + + THROW_IF_FAILED(ConptyCreatePseudoConsole(til::unwrap_coord_size(dimensions), in.rx.get(), out.tx.get(), _flags, &_hPC)); + + _inPipe = std::move(in.tx); + _outPipe = std::move(out.rx); if (_initialParentHwnd != 0) { diff --git a/src/types/inc/utils.hpp b/src/types/inc/utils.hpp index d00b94d374d..49d673960ee 100644 --- a/src/types/inc/utils.hpp +++ b/src/types/inc/utils.hpp @@ -15,6 +15,12 @@ Author(s): namespace Microsoft::Console::Utils { + struct Pipe + { + wil::unique_hfile tx; + wil::unique_hfile rx; + }; + // Function Description: // - Returns -1, 0 or +1 to indicate the sign of the passed-in value. template @@ -25,6 +31,8 @@ namespace Microsoft::Console::Utils bool IsValidHandle(const HANDLE handle) noexcept; bool HandleWantsOverlappedIo(HANDLE handle) noexcept; + Pipe CreatePipe(DWORD bufferSize); + Pipe CreateOverlappedPipe(DWORD bufferSize); // Function Description: // - Clamps a long in between `min` and `SHRT_MAX` diff --git a/src/types/utils.cpp b/src/types/utils.cpp index cad31ba72d6..f2236b69196 100644 --- a/src/types/utils.cpp +++ b/src/types/utils.cpp @@ -4,6 +4,7 @@ #include "precomp.h" #include "inc/utils.hpp" +#include #include #include @@ -658,6 +659,24 @@ bool Utils::HandleWantsOverlappedIo(HANDLE handle) noexcept return status == 0 && WI_AreAllFlagsClear(modeInfo.Mode, FILE_SYNCHRONOUS_IO_ALERT | FILE_SYNCHRONOUS_IO_NONALERT); } +Utils::Pipe Utils::CreatePipe(DWORD bufferSize) +{ + wil::unique_hfile rx, tx; + THROW_IF_WIN32_BOOL_FALSE(::CreatePipe(rx.addressof(), tx.addressof(), nullptr, bufferSize)); + return { std::move(tx), std::move(rx) }; +} + +Utils::Pipe Utils::CreateOverlappedPipe(DWORD bufferSize) +{ + const auto rnd = til::gen_random(); + const auto name = fmt::format(FMT_COMPILE(LR"(\\.\pipe\{:016x})"), rnd); + wil::unique_hfile rx{ CreateNamedPipeW(name.c_str(), PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_WAIT, 1, bufferSize, bufferSize, 0, nullptr) }; + THROW_LAST_ERROR_IF(!rx); + wil::unique_hfile tx{ CreateFileW(name.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, nullptr) }; + THROW_LAST_ERROR_IF(!tx); + return { std::move(tx), std::move(rx) }; +} + // Function Description: // - Generate a Version 5 UUID (specified in RFC4122 4.3) // v5 UUIDs are stable given the same namespace and "name". From 495c3e5295a501572a27288881aeb6645bf057e8 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 12 Jul 2024 22:03:24 +0200 Subject: [PATCH 41/60] 10% faster for 128KiB of memory. Good deal. --- src/cascadia/TerminalConnection/ConptyConnection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 2ce3b6562a8..37472f24ca0 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -322,8 +322,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation // handoff from an already-started PTY process. if (!_inPipe) { - auto out = Utils::CreateOverlappedPipe(64 * 1024); - auto in = Utils::CreateOverlappedPipe(64 * 1024); + auto out = Utils::CreateOverlappedPipe(128 * 1024); + auto in = Utils::CreateOverlappedPipe(128 * 1024); THROW_IF_FAILED(ConptyCreatePseudoConsole(til::unwrap_coord_size(dimensions), in.rx.get(), out.tx.get(), _flags, &_hPC)); From 93930bb3fa99d4e6986a85e7950caefb717af910 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sat, 13 Jul 2024 23:27:23 +0200 Subject: [PATCH 42/60] They said anonymous overlapped pipes can't be done --- .../TerminalConnection/ConptyConnection.cpp | 9 +- src/types/inc/utils.hpp | 8 +- src/types/utils.cpp | 139 +++++++++++++++--- 3 files changed, 129 insertions(+), 27 deletions(-) diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 37472f24ca0..51a9e531cad 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -322,13 +322,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation // handoff from an already-started PTY process. if (!_inPipe) { - auto out = Utils::CreateOverlappedPipe(128 * 1024); - auto in = Utils::CreateOverlappedPipe(128 * 1024); + auto duplex = Utils::CreateOverlappedDuplexPipe(128 * 1024); - THROW_IF_FAILED(ConptyCreatePseudoConsole(til::unwrap_coord_size(dimensions), in.rx.get(), out.tx.get(), _flags, &_hPC)); + THROW_IF_FAILED(ConptyCreatePseudoConsole(til::unwrap_coord_size(dimensions), duplex.alice.get(), duplex.alice.get(), _flags, &_hPC)); - _inPipe = std::move(in.tx); - _outPipe = std::move(out.rx); + _inPipe = std::move(duplex.bob); + THROW_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(), _inPipe.get(), GetCurrentProcess(), _outPipe.addressof(), 0, FALSE, DUPLICATE_SAME_ACCESS)); if (_initialParentHwnd != 0) { diff --git a/src/types/inc/utils.hpp b/src/types/inc/utils.hpp index 49d673960ee..d33f5a545f6 100644 --- a/src/types/inc/utils.hpp +++ b/src/types/inc/utils.hpp @@ -21,6 +21,12 @@ namespace Microsoft::Console::Utils wil::unique_hfile rx; }; + struct DuplexPipe + { + wil::unique_hfile alice; + wil::unique_hfile bob; + }; + // Function Description: // - Returns -1, 0 or +1 to indicate the sign of the passed-in value. template @@ -32,7 +38,7 @@ namespace Microsoft::Console::Utils bool IsValidHandle(const HANDLE handle) noexcept; bool HandleWantsOverlappedIo(HANDLE handle) noexcept; Pipe CreatePipe(DWORD bufferSize); - Pipe CreateOverlappedPipe(DWORD bufferSize); + DuplexPipe CreateOverlappedDuplexPipe(DWORD bufferSize); // Function Description: // - Clamps a long in between `min` and `SHRT_MAX` diff --git a/src/types/utils.cpp b/src/types/utils.cpp index f2236b69196..bfad7098a30 100644 --- a/src/types/utils.cpp +++ b/src/types/utils.cpp @@ -631,15 +631,35 @@ bool Utils::IsValidHandle(const HANDLE handle) noexcept } // From ntifs.h, which isn't part of the regular Windows SDK (they're in the driver SDK (WDK)). -__kernel_entry NTSYSCALLAPI NTSTATUS NTAPI NtQueryInformationFile( - _In_ HANDLE FileHandle, - _Out_ PIO_STATUS_BLOCK IoStatusBlock, - _Out_writes_bytes_(Length) PVOID FileInformation, - _In_ ULONG Length, - _In_ FILE_INFORMATION_CLASS FileInformationClass); +extern "C" NTSTATUS NTAPI NtQueryInformationFile( + HANDLE FileHandle, + PIO_STATUS_BLOCK IoStatusBlock, + PVOID FileInformation, + ULONG Length, + FILE_INFORMATION_CLASS FileInformationClass); #define FileModeInformation (FILE_INFORMATION_CLASS)16 +extern "C" NTSTATUS NTAPI NtCreateNamedPipeFile( + PHANDLE FileHandle, + ULONG DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes, + PIO_STATUS_BLOCK IoStatusBlock, + ULONG ShareAccess, + ULONG CreateDisposition, + ULONG CreateOptions, + ULONG NamedPipeType, + ULONG ReadMode, + ULONG CompletionMode, + ULONG MaximumInstances, + ULONG InboundQuota, + ULONG OutboundQuota, + PLARGE_INTEGER DefaultTimeout); + +#define FILE_PIPE_BYTE_STREAM_TYPE 0x00000000 +#define FILE_PIPE_BYTE_STREAM_MODE 0x00000000 +#define FILE_PIPE_QUEUE_OPERATION 0x00000000 + typedef struct _FILE_MODE_INFORMATION { ULONG Mode; @@ -647,15 +667,9 @@ typedef struct _FILE_MODE_INFORMATION bool Utils::HandleWantsOverlappedIo(HANDLE handle) noexcept { - static const auto pNtQueryInformationFile = GetProcAddressByFunctionDeclaration(GetModuleHandleW(L"ntdll.dll"), NtQueryInformationFile); - if (!pNtQueryInformationFile) - { - return false; - } - IO_STATUS_BLOCK statusBlock; FILE_MODE_INFORMATION modeInfo; - const auto status = pNtQueryInformationFile(handle, &statusBlock, &modeInfo, sizeof(modeInfo), FileModeInformation); + const auto status = NtQueryInformationFile(handle, &statusBlock, &modeInfo, sizeof(modeInfo), FileModeInformation); return status == 0 && WI_AreAllFlagsClear(modeInfo.Mode, FILE_SYNCHRONOUS_IO_ALERT | FILE_SYNCHRONOUS_IO_NONALERT); } @@ -666,15 +680,98 @@ Utils::Pipe Utils::CreatePipe(DWORD bufferSize) return { std::move(tx), std::move(rx) }; } -Utils::Pipe Utils::CreateOverlappedPipe(DWORD bufferSize) +// Creates an overlapped anonymous pipe. +// This variant returns a full duplex pipe and so the two sides are simply named Alice and Bob. +// +// I know, I know. MSDN infamously says +// > Asynchronous (overlapped) read and write operations are not supported by anonymous pipes. +// but that's a lie. The only reason they're not supported is because the Win32 +// API doesn't have a parameter where you could pass FILE_FLAG_OVERLAPPED! +// So, we'll simply use the underlying NT APIs instead. +// +// Most code on the internet suggests creating named pipes with a random name, +// but usually conveniently forgets to mention that named pipes require strict ACLs. +// https://stackoverflow.com/q/60645 for instance contains a lot of poor advice. +// Anonymous pipes also cannot be discovered via NtQueryDirectoryFile inside the NPFS driver, +// whereas running a tool like Sysinternals' PipeList will return all those semi-named pipes. +// +// The code below contains comments to create unidirectional pipes. +Utils::DuplexPipe Utils::CreateOverlappedDuplexPipe(DWORD bufferSize) { - const auto rnd = til::gen_random(); - const auto name = fmt::format(FMT_COMPILE(LR"(\\.\pipe\{:016x})"), rnd); - wil::unique_hfile rx{ CreateNamedPipeW(name.c_str(), PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_WAIT, 1, bufferSize, bufferSize, 0, nullptr) }; - THROW_LAST_ERROR_IF(!rx); - wil::unique_hfile tx{ CreateFileW(name.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, nullptr) }; - THROW_LAST_ERROR_IF(!tx); - return { std::move(tx), std::move(rx) }; + // Cache a handle to the pipe driver. + static const auto pipeDirectory = []() { + UNICODE_STRING path = RTL_CONSTANT_STRING(L"\\Device\\NamedPipe\\"); + + OBJECT_ATTRIBUTES objectAttributes; + InitializeObjectAttributes(&objectAttributes, &path, 0, nullptr, nullptr); + + wil::unique_hfile dir; + IO_STATUS_BLOCK statusBlock; + THROW_IF_NTSTATUS_FAILED(NtOpenFile(dir.addressof(), GENERIC_READ | SYNCHRONIZE, &objectAttributes, &statusBlock, FILE_SHARE_READ | FILE_SHARE_WRITE, 0)); + + return dir; + }(); + + LARGE_INTEGER timeout = { .QuadPart = -10'0000'0000 }; // 1 second + UNICODE_STRING emptyPath{}; + IO_STATUS_BLOCK statusBlock; + OBJECT_ATTRIBUTES objectAttributes; + + wil::unique_hfile alice; + InitializeObjectAttributes(&objectAttributes, &emptyPath, OBJ_CASE_INSENSITIVE, pipeDirectory.get(), nullptr); + THROW_IF_NTSTATUS_FAILED(NtCreateNamedPipeFile( + /* FileHandle */ alice.addressof(), + // Should always include SYNCHRONIZE. + // PIPE_ACCESS_INBOUND = GENERIC_READ (commonly combined with FILE_WRITE_ATTRIBUTES to create a read-only pipe) + // PIPE_ACCESS_OUTBOUND = GENERIC_WRITE (commonly combined with FILE_READ_ATTRIBUTES to create a write-only pipe) + // PIPE_ACCESS_DUPLEX = GENERIC_READ | GENERIC_WRITE + /* DesiredAccess */ SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE | FILE_WRITE_ATTRIBUTES, + /* ObjectAttributes */ &objectAttributes, + /* IoStatusBlock */ &statusBlock, + // PIPE_ACCESS_INBOUND = FILE_SHARE_WRITE + // PIPE_ACCESS_OUTBOUND = FILE_SHARE_READ + // PIPE_ACCESS_DUPLEX = FILE_SHARE_READ | FILE_SHARE_WRITE + /* ShareAccess */ FILE_SHARE_READ | FILE_SHARE_WRITE, + /* CreateDisposition */ FILE_CREATE, + // FILE_FLAG_OVERLAPPED = 0 + // otherwise = FILE_SYNCHRONOUS_IO_NONALERT + /* CreateOptions */ 0, + /* NamedPipeType */ FILE_PIPE_BYTE_STREAM_TYPE, + /* ReadMode */ FILE_PIPE_BYTE_STREAM_MODE, + // PIPE_NOWAIT = FILE_PIPE_COMPLETE_OPERATION + // otherwise = FILE_PIPE_QUEUE_OPERATION + /* CompletionMode */ FILE_PIPE_QUEUE_OPERATION, + /* MaximumInstances */ 1, + /* InboundQuota */ bufferSize, + /* OutboundQuota */ bufferSize, + /* DefaultTimeout */ &timeout)); + + wil::unique_hfile bob; + InitializeObjectAttributes(&objectAttributes, &emptyPath, OBJ_CASE_INSENSITIVE, alice.get(), nullptr); + THROW_IF_NTSTATUS_FAILED(NtCreateFile( + /* FileHandle */ bob.addressof(), + // Should always include SYNCHRONIZE. + // PIPE_ACCESS_INBOUND = GENERIC_WRITE (commonly combined with FILE_READ_ATTRIBUTES to create a write-only pipe) + // PIPE_ACCESS_OUTBOUND = GENERIC_READ (commonly combined with FILE_WRITE_ATTRIBUTES to create a read-only pipe) + // PIPE_ACCESS_DUPLEX = GENERIC_READ | GENERIC_WRITE + /* DesiredAccess */ SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE, + /* ObjectAttributes */ &objectAttributes, + /* IoStatusBlock */ &statusBlock, + /* AllocationSize */ 0, + /* FileAttributes */ 0, + // PIPE_ACCESS_INBOUND = FILE_SHARE_READ + // PIPE_ACCESS_OUTBOUND = FILE_SHARE_WRITE + // PIPE_ACCESS_DUPLEX = FILE_SHARE_READ | FILE_SHARE_WRITE + /* ShareAccess */ FILE_SHARE_READ | FILE_SHARE_WRITE, + /* CreateDisposition */ FILE_OPEN, + // Should always include FILE_NON_DIRECTORY_FILE for correctness reasons. + // FILE_FLAG_OVERLAPPED = 0 + // otherwise = FILE_SYNCHRONOUS_IO_NONALERT + /* CreateOptions */ FILE_NON_DIRECTORY_FILE, + /* EaBuffer */ nullptr, + /* EaLength */ 0)); + + return { std::move(alice), std::move(bob) }; } // Function Description: From 2c1d7fcd28c4f7ed66763b8e67bcd2e88929223a Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sat, 13 Jul 2024 23:30:13 +0200 Subject: [PATCH 43/60] Spell fix --- .github/actions/spelling/expect/expect.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index af85004faf5..4039a27a1b8 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1201,6 +1201,7 @@ nouicompat nounihan NOYIELD NOZORDER +NPFS nrcs NSTATUS ntapi From e1eb9db11541d1a4528b5f4af690ee075bebd503 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sat, 13 Jul 2024 23:49:32 +0200 Subject: [PATCH 44/60] Make AuditMode happy, Improve comments --- src/types/utils.cpp | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/types/utils.cpp b/src/types/utils.cpp index bfad7098a30..d358d27f315 100644 --- a/src/types/utils.cpp +++ b/src/types/utils.cpp @@ -702,8 +702,10 @@ Utils::DuplexPipe Utils::CreateOverlappedDuplexPipe(DWORD bufferSize) static const auto pipeDirectory = []() { UNICODE_STRING path = RTL_CONSTANT_STRING(L"\\Device\\NamedPipe\\"); - OBJECT_ATTRIBUTES objectAttributes; - InitializeObjectAttributes(&objectAttributes, &path, 0, nullptr, nullptr); + OBJECT_ATTRIBUTES objectAttributes{ + .Length = sizeof(OBJECT_ATTRIBUTES), + .ObjectName = &path, + }; wil::unique_hfile dir; IO_STATUS_BLOCK statusBlock; @@ -715,30 +717,38 @@ Utils::DuplexPipe Utils::CreateOverlappedDuplexPipe(DWORD bufferSize) LARGE_INTEGER timeout = { .QuadPart = -10'0000'0000 }; // 1 second UNICODE_STRING emptyPath{}; IO_STATUS_BLOCK statusBlock; - OBJECT_ATTRIBUTES objectAttributes; + OBJECT_ATTRIBUTES objectAttributes{ + .Length = sizeof(OBJECT_ATTRIBUTES), + .ObjectName = &emptyPath, + .Attributes = OBJ_CASE_INSENSITIVE, + }; wil::unique_hfile alice; - InitializeObjectAttributes(&objectAttributes, &emptyPath, OBJ_CASE_INSENSITIVE, pipeDirectory.get(), nullptr); + objectAttributes.RootDirectory = pipeDirectory.get(); THROW_IF_NTSTATUS_FAILED(NtCreateNamedPipeFile( /* FileHandle */ alice.addressof(), - // Should always include SYNCHRONIZE. - // PIPE_ACCESS_INBOUND = GENERIC_READ (commonly combined with FILE_WRITE_ATTRIBUTES to create a read-only pipe) + // DesiredAccess: + // * Should always include SYNCHRONIZE. + // * PIPE_ACCESS_INBOUND = GENERIC_READ (commonly combined with FILE_WRITE_ATTRIBUTES to create a read-only pipe) // PIPE_ACCESS_OUTBOUND = GENERIC_WRITE (commonly combined with FILE_READ_ATTRIBUTES to create a write-only pipe) // PIPE_ACCESS_DUPLEX = GENERIC_READ | GENERIC_WRITE /* DesiredAccess */ SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE | FILE_WRITE_ATTRIBUTES, /* ObjectAttributes */ &objectAttributes, /* IoStatusBlock */ &statusBlock, - // PIPE_ACCESS_INBOUND = FILE_SHARE_WRITE + // ShareAccess: + // * PIPE_ACCESS_INBOUND = FILE_SHARE_WRITE // PIPE_ACCESS_OUTBOUND = FILE_SHARE_READ // PIPE_ACCESS_DUPLEX = FILE_SHARE_READ | FILE_SHARE_WRITE /* ShareAccess */ FILE_SHARE_READ | FILE_SHARE_WRITE, /* CreateDisposition */ FILE_CREATE, - // FILE_FLAG_OVERLAPPED = 0 + // CreateOptions: + // * FILE_FLAG_OVERLAPPED = 0 // otherwise = FILE_SYNCHRONOUS_IO_NONALERT /* CreateOptions */ 0, /* NamedPipeType */ FILE_PIPE_BYTE_STREAM_TYPE, /* ReadMode */ FILE_PIPE_BYTE_STREAM_MODE, - // PIPE_NOWAIT = FILE_PIPE_COMPLETE_OPERATION + // CompletionMode: + // * PIPE_NOWAIT = FILE_PIPE_COMPLETE_OPERATION // otherwise = FILE_PIPE_QUEUE_OPERATION /* CompletionMode */ FILE_PIPE_QUEUE_OPERATION, /* MaximumInstances */ 1, @@ -747,11 +757,12 @@ Utils::DuplexPipe Utils::CreateOverlappedDuplexPipe(DWORD bufferSize) /* DefaultTimeout */ &timeout)); wil::unique_hfile bob; - InitializeObjectAttributes(&objectAttributes, &emptyPath, OBJ_CASE_INSENSITIVE, alice.get(), nullptr); + objectAttributes.RootDirectory = alice.get(); THROW_IF_NTSTATUS_FAILED(NtCreateFile( /* FileHandle */ bob.addressof(), - // Should always include SYNCHRONIZE. - // PIPE_ACCESS_INBOUND = GENERIC_WRITE (commonly combined with FILE_READ_ATTRIBUTES to create a write-only pipe) + // DesiredAccess: + // * Should always include SYNCHRONIZE. + // * PIPE_ACCESS_INBOUND = GENERIC_WRITE (commonly combined with FILE_READ_ATTRIBUTES to create a write-only pipe) // PIPE_ACCESS_OUTBOUND = GENERIC_READ (commonly combined with FILE_WRITE_ATTRIBUTES to create a read-only pipe) // PIPE_ACCESS_DUPLEX = GENERIC_READ | GENERIC_WRITE /* DesiredAccess */ SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE, @@ -759,12 +770,14 @@ Utils::DuplexPipe Utils::CreateOverlappedDuplexPipe(DWORD bufferSize) /* IoStatusBlock */ &statusBlock, /* AllocationSize */ 0, /* FileAttributes */ 0, - // PIPE_ACCESS_INBOUND = FILE_SHARE_READ + // ShareAccess: + // * PIPE_ACCESS_INBOUND = FILE_SHARE_READ // PIPE_ACCESS_OUTBOUND = FILE_SHARE_WRITE // PIPE_ACCESS_DUPLEX = FILE_SHARE_READ | FILE_SHARE_WRITE /* ShareAccess */ FILE_SHARE_READ | FILE_SHARE_WRITE, /* CreateDisposition */ FILE_OPEN, - // Should always include FILE_NON_DIRECTORY_FILE for correctness reasons. + // CreateOptions: + // * Should always include FILE_NON_DIRECTORY_FILE for correctness reasons. // FILE_FLAG_OVERLAPPED = 0 // otherwise = FILE_SYNCHRONOUS_IO_NONALERT /* CreateOptions */ FILE_NON_DIRECTORY_FILE, From c3f48da21db925aad25d2ec8b2a1ba40bea30308 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sat, 13 Jul 2024 23:57:41 +0200 Subject: [PATCH 45/60] Another one --- src/types/utils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/utils.cpp b/src/types/utils.cpp index d358d27f315..5e1b66ab74b 100644 --- a/src/types/utils.cpp +++ b/src/types/utils.cpp @@ -768,7 +768,7 @@ Utils::DuplexPipe Utils::CreateOverlappedDuplexPipe(DWORD bufferSize) /* DesiredAccess */ SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE, /* ObjectAttributes */ &objectAttributes, /* IoStatusBlock */ &statusBlock, - /* AllocationSize */ 0, + /* AllocationSize */ nullptr, /* FileAttributes */ 0, // ShareAccess: // * PIPE_ACCESS_INBOUND = FILE_SHARE_READ From 245279d74af5eefe6400612bf779b8e4253db334 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sun, 14 Jul 2024 00:00:50 +0200 Subject: [PATCH 46/60] Eh, NtCreateFile for consistency --- src/types/utils.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/types/utils.cpp b/src/types/utils.cpp index 5e1b66ab74b..48f5e9db166 100644 --- a/src/types/utils.cpp +++ b/src/types/utils.cpp @@ -709,7 +709,18 @@ Utils::DuplexPipe Utils::CreateOverlappedDuplexPipe(DWORD bufferSize) wil::unique_hfile dir; IO_STATUS_BLOCK statusBlock; - THROW_IF_NTSTATUS_FAILED(NtOpenFile(dir.addressof(), GENERIC_READ | SYNCHRONIZE, &objectAttributes, &statusBlock, FILE_SHARE_READ | FILE_SHARE_WRITE, 0)); + THROW_IF_NTSTATUS_FAILED(NtCreateFile( + /* FileHandle */ dir.addressof(), + /* DesiredAccess */ SYNCHRONIZE | GENERIC_READ, + /* ObjectAttributes */ &objectAttributes, + /* IoStatusBlock */ &statusBlock, + /* AllocationSize */ nullptr, + /* FileAttributes */ 0, + /* ShareAccess */ FILE_SHARE_READ | FILE_SHARE_WRITE, + /* CreateDisposition */ FILE_OPEN, + /* CreateOptions */ 0, + /* EaBuffer */ nullptr, + /* EaLength */ 0)); return dir; }(); From d67b70da8d9d57f7e39c7c0752521a7bd6424e4b Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 15 Jul 2024 00:15:24 +0200 Subject: [PATCH 47/60] More flexible CreateOverlappedPipe --- src/types/inc/utils.hpp | 13 ++-- src/types/utils.cpp | 146 ++++++++++++++++++++++++---------------- 2 files changed, 93 insertions(+), 66 deletions(-) diff --git a/src/types/inc/utils.hpp b/src/types/inc/utils.hpp index d33f5a545f6..ef29689fa6b 100644 --- a/src/types/inc/utils.hpp +++ b/src/types/inc/utils.hpp @@ -17,14 +17,8 @@ namespace Microsoft::Console::Utils { struct Pipe { - wil::unique_hfile tx; - wil::unique_hfile rx; - }; - - struct DuplexPipe - { - wil::unique_hfile alice; - wil::unique_hfile bob; + wil::unique_hfile server; + wil::unique_hfile client; }; // Function Description: @@ -38,7 +32,8 @@ namespace Microsoft::Console::Utils bool IsValidHandle(const HANDLE handle) noexcept; bool HandleWantsOverlappedIo(HANDLE handle) noexcept; Pipe CreatePipe(DWORD bufferSize); - DuplexPipe CreateOverlappedDuplexPipe(DWORD bufferSize); + Pipe CreateOverlappedPipe(DWORD openMode, DWORD bufferSize); + HRESULT GetOverlappedResultSameThread(const OVERLAPPED* overlapped, DWORD* bytesTransferred); // Function Description: // - Clamps a long in between `min` and `SHRT_MAX` diff --git a/src/types/utils.cpp b/src/types/utils.cpp index 48f5e9db166..86e113ad3c0 100644 --- a/src/types/utils.cpp +++ b/src/types/utils.cpp @@ -630,7 +630,17 @@ bool Utils::IsValidHandle(const HANDLE handle) noexcept return handle != nullptr && handle != INVALID_HANDLE_VALUE; } -// From ntifs.h, which isn't part of the regular Windows SDK (they're in the driver SDK (WDK)). +#define FileModeInformation (FILE_INFORMATION_CLASS)16 + +#define FILE_PIPE_BYTE_STREAM_TYPE 0x00000000 +#define FILE_PIPE_BYTE_STREAM_MODE 0x00000000 +#define FILE_PIPE_QUEUE_OPERATION 0x00000000 + +typedef struct _FILE_MODE_INFORMATION +{ + ULONG Mode; +} FILE_MODE_INFORMATION, *PFILE_MODE_INFORMATION; + extern "C" NTSTATUS NTAPI NtQueryInformationFile( HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, @@ -638,8 +648,6 @@ extern "C" NTSTATUS NTAPI NtQueryInformationFile( ULONG Length, FILE_INFORMATION_CLASS FileInformationClass); -#define FileModeInformation (FILE_INFORMATION_CLASS)16 - extern "C" NTSTATUS NTAPI NtCreateNamedPipeFile( PHANDLE FileHandle, ULONG DesiredAccess, @@ -656,15 +664,6 @@ extern "C" NTSTATUS NTAPI NtCreateNamedPipeFile( ULONG OutboundQuota, PLARGE_INTEGER DefaultTimeout); -#define FILE_PIPE_BYTE_STREAM_TYPE 0x00000000 -#define FILE_PIPE_BYTE_STREAM_MODE 0x00000000 -#define FILE_PIPE_QUEUE_OPERATION 0x00000000 - -typedef struct _FILE_MODE_INFORMATION -{ - ULONG Mode; -} FILE_MODE_INFORMATION, *PFILE_MODE_INFORMATION; - bool Utils::HandleWantsOverlappedIo(HANDLE handle) noexcept { IO_STATUS_BLOCK statusBlock; @@ -673,15 +672,19 @@ bool Utils::HandleWantsOverlappedIo(HANDLE handle) noexcept return status == 0 && WI_AreAllFlagsClear(modeInfo.Mode, FILE_SYNCHRONOUS_IO_ALERT | FILE_SYNCHRONOUS_IO_NONALERT); } +// Creates an anonymous pipe. Behaves like PIPE_ACCESS_INBOUND, +// meaning the .server is for reading and the .client is for writing. Utils::Pipe Utils::CreatePipe(DWORD bufferSize) { wil::unique_hfile rx, tx; THROW_IF_WIN32_BOOL_FALSE(::CreatePipe(rx.addressof(), tx.addressof(), nullptr, bufferSize)); - return { std::move(tx), std::move(rx) }; + return { std::move(rx), std::move(tx) }; } -// Creates an overlapped anonymous pipe. -// This variant returns a full duplex pipe and so the two sides are simply named Alice and Bob. +// Creates an overlapped anonymous pipe. openMode should be either: +// * PIPE_ACCESS_INBOUND +// * PIPE_ACCESS_OUTBOUND +// * PIPE_ACCESS_DUPLEX // // I know, I know. MSDN infamously says // > Asynchronous (overlapped) read and write operations are not supported by anonymous pipes. @@ -696,7 +699,7 @@ Utils::Pipe Utils::CreatePipe(DWORD bufferSize) // whereas running a tool like Sysinternals' PipeList will return all those semi-named pipes. // // The code below contains comments to create unidirectional pipes. -Utils::DuplexPipe Utils::CreateOverlappedDuplexPipe(DWORD bufferSize) +Utils::Pipe Utils::CreateOverlappedPipe(DWORD openMode, DWORD bufferSize) { // Cache a handle to the pipe driver. static const auto pipeDirectory = []() { @@ -718,7 +721,7 @@ Utils::DuplexPipe Utils::CreateOverlappedDuplexPipe(DWORD bufferSize) /* FileAttributes */ 0, /* ShareAccess */ FILE_SHARE_READ | FILE_SHARE_WRITE, /* CreateDisposition */ FILE_OPEN, - /* CreateOptions */ 0, + /* CreateOptions */ FILE_SYNCHRONOUS_IO_NONALERT, /* EaBuffer */ nullptr, /* EaLength */ 0)); @@ -733,69 +736,98 @@ Utils::DuplexPipe Utils::CreateOverlappedDuplexPipe(DWORD bufferSize) .ObjectName = &emptyPath, .Attributes = OBJ_CASE_INSENSITIVE, }; + DWORD desiredAccess = 0; + DWORD shareAccess = 0; + + switch (openMode) + { + case PIPE_ACCESS_INBOUND: + desiredAccess = SYNCHRONIZE | GENERIC_READ | FILE_WRITE_ATTRIBUTES; + shareAccess = FILE_SHARE_WRITE; + break; + case PIPE_ACCESS_OUTBOUND: + desiredAccess = SYNCHRONIZE | GENERIC_WRITE | FILE_READ_ATTRIBUTES; + shareAccess = FILE_SHARE_READ; + break; + default: + desiredAccess = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE; + shareAccess = FILE_SHARE_READ | FILE_SHARE_WRITE; + break; + } - wil::unique_hfile alice; + wil::unique_hfile server; objectAttributes.RootDirectory = pipeDirectory.get(); THROW_IF_NTSTATUS_FAILED(NtCreateNamedPipeFile( - /* FileHandle */ alice.addressof(), - // DesiredAccess: - // * Should always include SYNCHRONIZE. - // * PIPE_ACCESS_INBOUND = GENERIC_READ (commonly combined with FILE_WRITE_ATTRIBUTES to create a read-only pipe) - // PIPE_ACCESS_OUTBOUND = GENERIC_WRITE (commonly combined with FILE_READ_ATTRIBUTES to create a write-only pipe) - // PIPE_ACCESS_DUPLEX = GENERIC_READ | GENERIC_WRITE - /* DesiredAccess */ SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE | FILE_WRITE_ATTRIBUTES, + /* FileHandle */ server.addressof(), + /* DesiredAccess */ desiredAccess, /* ObjectAttributes */ &objectAttributes, /* IoStatusBlock */ &statusBlock, - // ShareAccess: - // * PIPE_ACCESS_INBOUND = FILE_SHARE_WRITE - // PIPE_ACCESS_OUTBOUND = FILE_SHARE_READ - // PIPE_ACCESS_DUPLEX = FILE_SHARE_READ | FILE_SHARE_WRITE /* ShareAccess */ FILE_SHARE_READ | FILE_SHARE_WRITE, /* CreateDisposition */ FILE_CREATE, - // CreateOptions: - // * FILE_FLAG_OVERLAPPED = 0 - // otherwise = FILE_SYNCHRONOUS_IO_NONALERT - /* CreateOptions */ 0, + /* CreateOptions */ 0, // would be FILE_SYNCHRONOUS_IO_NONALERT for a synchronous pipe /* NamedPipeType */ FILE_PIPE_BYTE_STREAM_TYPE, /* ReadMode */ FILE_PIPE_BYTE_STREAM_MODE, - // CompletionMode: - // * PIPE_NOWAIT = FILE_PIPE_COMPLETE_OPERATION - // otherwise = FILE_PIPE_QUEUE_OPERATION - /* CompletionMode */ FILE_PIPE_QUEUE_OPERATION, + /* CompletionMode */ FILE_PIPE_QUEUE_OPERATION, // would be FILE_PIPE_COMPLETE_OPERATION for PIPE_NOWAIT /* MaximumInstances */ 1, /* InboundQuota */ bufferSize, /* OutboundQuota */ bufferSize, /* DefaultTimeout */ &timeout)); - wil::unique_hfile bob; - objectAttributes.RootDirectory = alice.get(); + switch (openMode) + { + case PIPE_ACCESS_INBOUND: + desiredAccess = SYNCHRONIZE | GENERIC_WRITE | FILE_READ_ATTRIBUTES; + shareAccess = FILE_SHARE_READ; + break; + case PIPE_ACCESS_OUTBOUND: + desiredAccess = SYNCHRONIZE | GENERIC_READ | FILE_WRITE_ATTRIBUTES; + shareAccess = FILE_SHARE_WRITE; + break; + default: + desiredAccess = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE; + shareAccess = FILE_SHARE_READ | FILE_SHARE_WRITE; + break; + } + + wil::unique_hfile client; + objectAttributes.RootDirectory = server.get(); THROW_IF_NTSTATUS_FAILED(NtCreateFile( - /* FileHandle */ bob.addressof(), - // DesiredAccess: - // * Should always include SYNCHRONIZE. - // * PIPE_ACCESS_INBOUND = GENERIC_WRITE (commonly combined with FILE_READ_ATTRIBUTES to create a write-only pipe) - // PIPE_ACCESS_OUTBOUND = GENERIC_READ (commonly combined with FILE_WRITE_ATTRIBUTES to create a read-only pipe) - // PIPE_ACCESS_DUPLEX = GENERIC_READ | GENERIC_WRITE - /* DesiredAccess */ SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE, + /* FileHandle */ client.addressof(), + /* DesiredAccess */ desiredAccess, /* ObjectAttributes */ &objectAttributes, /* IoStatusBlock */ &statusBlock, /* AllocationSize */ nullptr, /* FileAttributes */ 0, - // ShareAccess: - // * PIPE_ACCESS_INBOUND = FILE_SHARE_READ - // PIPE_ACCESS_OUTBOUND = FILE_SHARE_WRITE - // PIPE_ACCESS_DUPLEX = FILE_SHARE_READ | FILE_SHARE_WRITE - /* ShareAccess */ FILE_SHARE_READ | FILE_SHARE_WRITE, + /* ShareAccess */ shareAccess, /* CreateDisposition */ FILE_OPEN, - // CreateOptions: - // * Should always include FILE_NON_DIRECTORY_FILE for correctness reasons. - // FILE_FLAG_OVERLAPPED = 0 - // otherwise = FILE_SYNCHRONOUS_IO_NONALERT - /* CreateOptions */ FILE_NON_DIRECTORY_FILE, + /* CreateOptions */ FILE_NON_DIRECTORY_FILE, // would include FILE_SYNCHRONOUS_IO_NONALERT for a synchronous pipe /* EaBuffer */ nullptr, /* EaLength */ 0)); - return { std::move(alice), std::move(bob) }; + return { std::move(server), std::move(client) }; +} + +// GetOverlappedResult() for professionals! Only for single-threaded use. +// +// GetOverlappedResult() used to have a neat optimization where it would only call WaitForSingleObject() if the state was STATUS_PENDING. +// That got removed in Windows 7, because people kept starting a read/write on one thread and called GetOverlappedResult() on another. +// When the OS sets Internal from STATUS_PENDING to 0 (= done) and then flags the hEvent, that doesn't happen atomically. +// This results in a race condition if a OVERLAPPED is used across threads. +HRESULT Utils::GetOverlappedResultSameThread(const OVERLAPPED* overlapped, DWORD* bytesTransferred) +{ + if (overlapped->Internal == STATUS_PENDING) + { + if (WaitForSingleObjectEx(overlapped->hEvent, INFINITE, FALSE) != WAIT_OBJECT_0) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + } + + // Assuming no multi-threading as per the function contract and + // now that we ensured that hEvent is set (= read/write done), + // we can safely read whatever want because nothing will set these concurrently. + *bytesTransferred = static_cast(overlapped->InternalHigh); + return HRESULT_FROM_NT(overlapped->Internal); } // Function Description: From 730d6873a39f8bb118e0a7ca349c57211583182e Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 15 Jul 2024 00:16:57 +0200 Subject: [PATCH 48/60] VtIo tracing, More robust shutdown --- src/host/VtInputThread.cpp | 18 ++++++++++++++---- src/host/VtIo.cpp | 31 +++++++++++++++++++++---------- src/host/VtIo.hpp | 1 - 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/host/VtInputThread.cpp b/src/host/VtInputThread.cpp index 746833dc26e..f0d9b0ef5d6 100644 --- a/src/host/VtInputThread.cpp +++ b/src/host/VtInputThread.cpp @@ -54,6 +54,11 @@ DWORD WINAPI VtInputThread::StaticVtInputThreadProc(_In_ LPVOID lpParameter) // InputStateMachineEngine. void VtInputThread::_InputThread() { + const auto cleanup = wil::scope_exit([this]() { + _hFile.reset(); + ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIoNoCheck()->SendCloseEvent(); + }); + OVERLAPPED* overlapped = nullptr; OVERLAPPED overlappedBuf{}; wil::unique_event overlappedEvent; @@ -124,7 +129,7 @@ void VtInputThread::_InputThread() if (overlappedPending) { overlappedPending = false; - if (!GetOverlappedResult(_hFile.get(), overlapped, &read, TRUE)) + if (FAILED(Utils::GetOverlappedResultSameThread(overlapped, &read))) { break; } @@ -147,11 +152,16 @@ void VtInputThread::_InputThread() break; } + 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, gsl::narrow_cast(read) }, wstr, u8State)); + FAILED_LOG(til::u8u16({ &buffer[0], gsl::narrow_cast(read) }, wstr, u8State)); } - - ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIoNoCheck()->CloseInput(); } // Method Description: diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index 98988bad588..5cfabfce298 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -266,12 +266,6 @@ void VtIo::CreatePseudoWindow() return S_OK; } -void VtIo::CloseInput() -{ - _pVtInputThread = nullptr; - SendCloseEvent(); -} - void VtIo::SendCloseEvent() { LockConsole(); @@ -432,15 +426,22 @@ void VtIo::_flushNow() if (_overlappedPending) { _overlappedPending = false; - std::ignore = _overlappedEvent.wait(); + + 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 >64KiB large and twice as large as the previous buffer, free the memory. + // 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 > 64 * 1024 && cap > _front.capacity() / 2) + if (const auto cap = _back.capacity(); cap > 128 * 1024 && cap / 2 > _front.size()) { _back = std::string{}; } @@ -466,9 +467,18 @@ void VtIo::_flushNow() 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(), gsl::narrow_cast(_front.size()), nullptr, _overlapped)) + if (WriteFile(_hOutput.get(), _front.data(), write, nullptr, _overlapped)) { return; } @@ -477,6 +487,7 @@ void VtIo::_flushNow() { case ERROR_BROKEN_PIPE: _hOutput.reset(); + SendCloseEvent(); return; case ERROR_IO_PENDING: _overlappedPending = true; diff --git a/src/host/VtIo.hpp b/src/host/VtIo.hpp index 8129c0caba8..b4eb34c9849 100644 --- a/src/host/VtIo.hpp +++ b/src/host/VtIo.hpp @@ -60,7 +60,6 @@ namespace Microsoft::Console::VirtualTerminal bool IsUsingVt() const; [[nodiscard]] HRESULT StartIfNeeded(); void SendCloseEvent(); - void CloseInput(); void CreatePseudoWindow(); Writer GetWriter() noexcept; From 316b91a2ff7b31d33ce97223975ee94e84022d96 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 15 Jul 2024 00:28:35 +0200 Subject: [PATCH 49/60] Proper overlapped IO, ITerminalHandoff3 --- .../CascadiaPackage/Package-Can.appxmanifest | 3 +- .../CascadiaPackage/Package-Dev.appxmanifest | 3 +- .../CascadiaPackage/Package-Pre.appxmanifest | 3 +- .../CascadiaPackage/Package.appxmanifest | 3 +- .../TerminalConnection/CTerminalHandoff.cpp | 29 +- .../TerminalConnection/CTerminalHandoff.h | 12 +- .../TerminalConnection/ConptyConnection.cpp | 281 +++++++++++------- .../TerminalConnection/ConptyConnection.h | 28 +- src/host/proxy/IConsoleHandoff.idl | 3 +- src/host/proxy/ITerminalHandoff.idl | 26 +- src/host/srvinit.cpp | 23 +- 11 files changed, 231 insertions(+), 183 deletions(-) diff --git a/src/cascadia/CascadiaPackage/Package-Can.appxmanifest b/src/cascadia/CascadiaPackage/Package-Can.appxmanifest index 72b0a648789..e8ff744104b 100644 --- a/src/cascadia/CascadiaPackage/Package-Can.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Can.appxmanifest @@ -111,8 +111,7 @@ - - + diff --git a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest index d8ceefaeef6..9f68b6f3e4f 100644 --- a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest @@ -111,8 +111,7 @@ - - + diff --git a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest index 3a7bf26e0f7..665b5b7dde6 100644 --- a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest @@ -200,8 +200,7 @@ - - + diff --git a/src/cascadia/CascadiaPackage/Package.appxmanifest b/src/cascadia/CascadiaPackage/Package.appxmanifest index f04863da27c..c7ed197075a 100644 --- a/src/cascadia/CascadiaPackage/Package.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package.appxmanifest @@ -200,8 +200,7 @@ - - + diff --git a/src/cascadia/TerminalConnection/CTerminalHandoff.cpp b/src/cascadia/TerminalConnection/CTerminalHandoff.cpp index cf33c4002a1..a6edb232430 100644 --- a/src/cascadia/TerminalConnection/CTerminalHandoff.cpp +++ b/src/cascadia/TerminalConnection/CTerminalHandoff.cpp @@ -72,20 +72,6 @@ HRESULT CTerminalHandoff::s_StopListeningLocked() return S_OK; } -// Routine Description: -// - Helper to duplicate a handle to ourselves so we can keep holding onto it -// after the caller frees the original one. -// Arguments: -// - in - Handle to duplicate -// - out - Where to place the duplicated value -// Return Value: -// - S_OK or Win32 error from `::DuplicateHandle` -static HRESULT _duplicateHandle(const HANDLE in, HANDLE& out) noexcept -{ - RETURN_IF_WIN32_BOOL_FALSE(::DuplicateHandle(GetCurrentProcess(), in, GetCurrentProcess(), &out, 0, FALSE, DUPLICATE_SAME_ACCESS)); - return S_OK; -} - // Routine Description: // - Receives the terminal handoff via COM from the other process, // duplicates handles as COM will free those given on the way out, @@ -102,7 +88,7 @@ static HRESULT _duplicateHandle(const HANDLE in, HANDLE& out) noexcept // - E_NOT_VALID_STATE if a event handler is not registered before calling. `::DuplicateHandle` // error codes if we cannot manage to make our own copy of handles to retain. Or S_OK/error // from the registered handler event function. -HRESULT CTerminalHandoff::EstablishPtyHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client, TERMINAL_STARTUP_INFO startupInfo) +HRESULT CTerminalHandoff::EstablishPtyHandoff(HANDLE* in, HANDLE* out, HANDLE signal, HANDLE reference, HANDLE server, HANDLE client, const TERMINAL_STARTUP_INFO* startupInfo) { try { @@ -120,19 +106,8 @@ HRESULT CTerminalHandoff::EstablishPtyHandoff(HANDLE in, HANDLE out, HANDLE sign // Report an error if no one registered a handoff function before calling this. THROW_HR_IF_NULL(E_NOT_VALID_STATE, localPfnHandoff); - // Duplicate the handles from what we received. - // The contract with COM specifies that any HANDLEs we receive from the caller belong - // to the caller and will be freed when we leave the scope of this method. - // Making our own duplicate copy ensures they hang around in our lifetime. - THROW_IF_FAILED(_duplicateHandle(in, in)); - THROW_IF_FAILED(_duplicateHandle(out, out)); - THROW_IF_FAILED(_duplicateHandle(signal, signal)); - THROW_IF_FAILED(_duplicateHandle(ref, ref)); - THROW_IF_FAILED(_duplicateHandle(server, server)); - THROW_IF_FAILED(_duplicateHandle(client, client)); - // Call registered handler from when we started listening. - THROW_IF_FAILED(localPfnHandoff(in, out, signal, ref, server, client, startupInfo)); + THROW_IF_FAILED(localPfnHandoff(in, out, signal, reference, server, client, startupInfo)); #pragma warning(suppress : 26477) TraceLoggingWrite( diff --git a/src/cascadia/TerminalConnection/CTerminalHandoff.h b/src/cascadia/TerminalConnection/CTerminalHandoff.h index 3d6c3d19876..440a2636f20 100644 --- a/src/cascadia/TerminalConnection/CTerminalHandoff.h +++ b/src/cascadia/TerminalConnection/CTerminalHandoff.h @@ -28,19 +28,13 @@ Author(s): #define __CLSID_CTerminalHandoff "051F34EE-C1FD-4B19-AF75-9BA54648434C" #endif -using NewHandoffFunction = HRESULT (*)(HANDLE, HANDLE, HANDLE, HANDLE, HANDLE, HANDLE, TERMINAL_STARTUP_INFO); +using NewHandoffFunction = HRESULT (*)(HANDLE* in, HANDLE* out, HANDLE signal, HANDLE reference, HANDLE server, HANDLE client, const TERMINAL_STARTUP_INFO* startupInfo); struct __declspec(uuid(__CLSID_CTerminalHandoff)) - CTerminalHandoff : public Microsoft::WRL::RuntimeClass, ITerminalHandoff2> + CTerminalHandoff : public Microsoft::WRL::RuntimeClass, ITerminalHandoff3> { #pragma region ITerminalHandoff - STDMETHODIMP EstablishPtyHandoff(HANDLE in, - HANDLE out, - HANDLE signal, - HANDLE ref, - HANDLE server, - HANDLE client, - TERMINAL_STARTUP_INFO startupInfo) override; + STDMETHODIMP EstablishPtyHandoff(HANDLE* in, HANDLE* out, HANDLE signal, HANDLE reference, HANDLE server, HANDLE client, const TERMINAL_STARTUP_INFO* startupInfo) override; #pragma endregion diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 51a9e531cad..974ff975500 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -5,7 +5,7 @@ #include "ConptyConnection.h" #include -#include +#include #include "CTerminalHandoff.h" #include "LibraryResources.h" @@ -150,33 +150,17 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } CATCH_RETURN(); - ConptyConnection::ConptyConnection(const HANDLE hSig, - const HANDLE hIn, - const HANDLE hOut, - const HANDLE hRef, - const HANDLE hServerProcess, - const HANDLE hClientProcess, - const TERMINAL_STARTUP_INFO& startupInfo) : - _rows{ 25 }, - _cols{ 80 }, - _inPipe{ hIn }, - _outPipe{ hOut } + static wil::unique_hfile duplicateHandle(const HANDLE in) noexcept { - _sessionId = Utils::CreateGuid(); - - THROW_IF_FAILED(ConptyPackPseudoConsole(hServerProcess, hRef, hSig, &_hPC)); - _piClient.hProcess = hClientProcess; - - _startupInfo.title = winrt::hstring{ startupInfo.pszTitle, SysStringLen(startupInfo.pszTitle) }; - _startupInfo.iconPath = winrt::hstring{ startupInfo.pszIconPath, SysStringLen(startupInfo.pszIconPath) }; - _startupInfo.iconIndex = startupInfo.iconIndex; - _startupInfo.showWindow = startupInfo.wShowWindow; + wil::unique_hfile h; + THROW_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(), in, GetCurrentProcess(), h.addressof(), 0, FALSE, DUPLICATE_SAME_ACCESS)); + return h; + } - try - { - _commandline = _commandlineFromProcess(hClientProcess); - } - CATCH_LOG() + ConptyConnection::ConptyConnection() : + _writeOverlappedEvent{ CreateEventExW(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS) } + { + THROW_LAST_ERROR_IF(!_writeOverlappedEvent); } // Function Description: @@ -296,6 +280,41 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } } + void ConptyConnection::InitializeFromHandoff(HANDLE* in, HANDLE* out, HANDLE signal, HANDLE reference, HANDLE server, HANDLE client, const TERMINAL_STARTUP_INFO* startupInfo) + { + _sessionId = Utils::CreateGuid(); + + auto pipe = Utils::CreateOverlappedPipe(PIPE_ACCESS_DUPLEX, 128 * 1024); + auto pipeClientClone = duplicateHandle(pipe.client.get()); + + auto ownedSignal = duplicateHandle(signal); + auto ownedReference = duplicateHandle(reference); + auto ownedServer = duplicateHandle(server); + auto ownedClient = duplicateHandle(client); + + THROW_IF_FAILED(ConptyPackPseudoConsole(ownedServer.get(), ownedReference.get(), ownedSignal.get(), &_hPC)); + ownedServer.release(); + ownedReference.release(); + ownedSignal.release(); + + _piClient.hProcess = ownedClient.release(); + + _startupInfo.title = winrt::hstring{ startupInfo->pszTitle, SysStringLen(startupInfo->pszTitle) }; + _startupInfo.iconPath = winrt::hstring{ startupInfo->pszIconPath, SysStringLen(startupInfo->pszIconPath) }; + _startupInfo.iconIndex = startupInfo->iconIndex; + _startupInfo.showWindow = startupInfo->wShowWindow; + + try + { + _commandline = _commandlineFromProcess(_piClient.hProcess); + } + CATCH_LOG() + + _pipe = std::move(pipe.server); + *in = pipe.client.release(); + *out = pipeClientClone.release(); + } + winrt::hstring ConptyConnection::Commandline() const { return _commandline; @@ -320,14 +339,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) { - auto duplex = Utils::CreateOverlappedDuplexPipe(128 * 1024); - - THROW_IF_FAILED(ConptyCreatePseudoConsole(til::unwrap_coord_size(dimensions), duplex.alice.get(), duplex.alice.get(), _flags, &_hPC)); - - _inPipe = std::move(duplex.bob); - THROW_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(), _inPipe.get(), GetCurrentProcess(), _outPipe.addressof(), 0, FALSE, DUPLICATE_SAME_ACCESS)); + 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) { @@ -441,28 +457,64 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation void ConptyConnection::_LastConPtyClientDisconnected() noexcept try { - DWORD exitCode{ 0 }; + DWORD exitCode = 0; GetExitCodeProcess(_piClient.hProcess, &exitCode); // Signal the closing or failure of the process. // exitCode might be STILL_ACTIVE if a client has called FreeConsole() and // thus caused the tab to close, even though the CLI app is still running. - _transitionToState(exitCode == 0 || exitCode == STILL_ACTIVE ? ConnectionState::Closed : ConnectionState::Failed); - _indicateExitWithStatus(exitCode); + const auto success = exitCode == 0 || exitCode == STILL_ACTIVE || exitCode == STATUS_CONTROL_C_EXIT; + const auto state = success ? ConnectionState::Closed : ConnectionState::Failed; + + if (!success) + { + _indicateExitWithStatus(exitCode); + } + + _transitionToState(state); } CATCH_LOG() - void ConptyConnection::WriteInput(const hstring& data) + __declspec(noinline) void ConptyConnection::WriteInput(const hstring& data) { if (!_isConnected()) { 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)); + if (_writePending) + { + _writePending = false; + + DWORD read; + if (FAILED_LOG(Utils::GetOverlappedResultSameThread(&_writeOverlapped, &read))) + { + // Not much we can do when the wait fails. This will kill the connection. + _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) @@ -521,24 +573,15 @@ 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()); - // 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 // we don't call a destroyed event handler asynchronously from a background thread (GH#13880). @@ -549,15 +592,18 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } LOG_LAST_ERROR(); + + // The output thread may be stuck waiting for the OVERLAPPED to be signaled. + CancelIoEx(_pipe.get(), nullptr); } } - // 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've already transitioned us to Closed. + // This exists just in case there was no output thread. _transitionToState(ConnectionState::Closed); } CATCH_LOG() @@ -600,68 +646,102 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation // won't wait for us, and the known exit points _do_. auto strongThis{ get_strong() }; - // process the data of the output pipe in a loop - while (true) - { - DWORD read{}; + const auto cleanup = wil::scope_exit([this]() { + _LastConPtyClientDisconnected(); + }); - const auto readFail{ !ReadFile(_outPipe.get(), _buffer.data(), gsl::narrow_cast(_buffer.size()), &read, nullptr) }; + 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; - // When we call CancelSynchronousIo() in Close() this is the branch that's taken and gets us out of here. - if (_isStateAtOrBeyond(ConnectionState::Closing)) + til::u8state u8State; + std::wstring wstr; + + // 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 (;;) + { + // 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.data(), read }, _u16Str, _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 (_u16Str.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(_u16Str); + 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, gsl::narrow_cast(read) }, wstr, u8State)); } return 0; @@ -677,11 +757,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation ::ConptyClosePseudoConsoleTimeout(hPC, 0); } - HRESULT ConptyConnection::NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client, TERMINAL_STARTUP_INFO startupInfo) noexcept + HRESULT ConptyConnection::NewHandoff(HANDLE* in, HANDLE* out, HANDLE signal, HANDLE reference, HANDLE server, HANDLE client, const TERMINAL_STARTUP_INFO* startupInfo) noexcept try { - _newConnectionHandlers(winrt::make(signal, in, out, ref, server, client, startupInfo)); - + auto conn = winrt::make_self(); + conn->InitializeFromHandoff(in, out, signal, reference, server, client, startupInfo); + _newConnectionHandlers(*std::move(conn)); return S_OK; } CATCH_RETURN() diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h index 1ce3b72ba92..c27d995bae9 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.h +++ b/src/cascadia/TerminalConnection/ConptyConnection.h @@ -13,16 +13,9 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation { struct ConptyConnection : ConptyConnectionT, BaseTerminalConnection { - ConptyConnection(const HANDLE hSig, - const HANDLE hIn, - const HANDLE hOut, - const HANDLE hRef, - const HANDLE hServerProcess, - const HANDLE hClientProcess, - const TERMINAL_STARTUP_INFO& startupInfo); - - ConptyConnection() noexcept = default; + explicit ConptyConnection(); void Initialize(const Windows::Foundation::Collections::ValueSet& settings); + void InitializeFromHandoff(HANDLE* in, HANDLE* out, HANDLE signal, HANDLE reference, HANDLE server, HANDLE client, const TERMINAL_STARTUP_INFO* startupInfo); static winrt::fire_and_forget final_release(std::unique_ptr connection); @@ -61,15 +54,15 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation private: static void closePseudoConsoleAsync(HPCON hPC) noexcept; - static HRESULT NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client, TERMINAL_STARTUP_INFO startupInfo) noexcept; + static HRESULT NewHandoff(HANDLE* in, HANDLE* out, HANDLE signal, HANDLE reference, HANDLE server, HANDLE client, const TERMINAL_STARTUP_INFO* startupInfo) noexcept; static winrt::hstring _commandlineFromProcess(HANDLE process); HRESULT _LaunchAttachedClient() noexcept; void _indicateExitWithStatus(unsigned int status) noexcept; void _LastConPtyClientDisconnected() noexcept; - til::CoordType _rows{}; - til::CoordType _cols{}; + til::CoordType _rows = 120; + til::CoordType _cols = 30; uint64_t _initialParentHwnd{ 0 }; hstring _commandline{}; hstring _startingDirectory{}; @@ -81,15 +74,16 @@ 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::u8state _u8State{}; - std::wstring _u16Str{}; - std::array _buffer{}; + wil::unique_event _writeOverlappedEvent; + OVERLAPPED _writeOverlapped{}; + std::string _writeBuffer; + bool _writePending = false; + DWORD _flags{ 0 }; til::env _initialEnv{}; diff --git a/src/host/proxy/IConsoleHandoff.idl b/src/host/proxy/IConsoleHandoff.idl index 3a07dd84691..f81999986e5 100644 --- a/src/host/proxy/IConsoleHandoff.idl +++ b/src/host/proxy/IConsoleHandoff.idl @@ -1,8 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import "oaidl.idl"; -import "ocidl.idl"; +import "unknwn.idl"; typedef struct _CONSOLE_PORTABLE_ATTACH_MSG { diff --git a/src/host/proxy/ITerminalHandoff.idl b/src/host/proxy/ITerminalHandoff.idl index 37906770f0d..f48bc10f691 100644 --- a/src/host/proxy/ITerminalHandoff.idl +++ b/src/host/proxy/ITerminalHandoff.idl @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import "oaidl.idl"; -import "ocidl.idl"; - +import "unknwn.idl"; typedef struct _TERMINAL_STARTUP_INFO { @@ -33,6 +31,7 @@ typedef struct _TERMINAL_STARTUP_INFO // versions of interfaces in the file here, even if the old version is no longer // in use. +// This was the original prototype. The reasons for changes to it are explained below. [ object, uuid(59D55CCE-FC8A-48B4-ACE8-0A9286C6557F) @@ -47,6 +46,9 @@ typedef struct _TERMINAL_STARTUP_INFO [in, system_handle(sh_process)] HANDLE client); }; +// We didn't consider the need for TERMINAL_STARTUP_INFO, because Windows Terminal never had support for launching +// .lnk files in the first place. They aren't executables after all, but rather a shell thing. +// Its need quickly became apparent right after releasing ITerminalHandoff, which is why it was very short-lived. [ object, uuid(AA6B364F-4A50-4176-9002-0AE755E7B5EF) @@ -60,3 +62,21 @@ typedef struct _TERMINAL_STARTUP_INFO [in, system_handle(sh_process)] HANDLE client, [in] TERMINAL_STARTUP_INFO startupInfo); }; + +// Quite a while later, we realized that passing the in/out handles as [in] instead of [out] has always been flawed. +// It prevents the terminal from making choices over the pipe buffer size and whether to use overlapped IO or not. +// The other handles remain [in] parameters because they have always been created internally by ConPTY. +// Additionally, passing TERMINAL_STARTUP_INFO by-value was unusual under COM as structs are usually given by-ref. +[ + object, + uuid(6F23DA90-15C5-4203-9DB0-64E73F1B1B00) +] interface ITerminalHandoff3 : IUnknown +{ + HRESULT EstablishPtyHandoff([out, system_handle(sh_pipe)] HANDLE* in, + [out, system_handle(sh_pipe)] HANDLE* out, + [in, system_handle(sh_pipe)] HANDLE signal, + [in, system_handle(sh_file)] HANDLE reference, + [in, system_handle(sh_process)] HANDLE server, + [in, system_handle(sh_process)] HANDLE client, + [in] const TERMINAL_STARTUP_INFO* startupInfo); +}; diff --git a/src/host/srvinit.cpp b/src/host/srvinit.cpp index 710827bd988..4f17a03f411 100644 --- a/src/host/srvinit.cpp +++ b/src/host/srvinit.cpp @@ -457,19 +457,8 @@ try wil::unique_handle signalPipeTheirSide; wil::unique_handle signalPipeOurSide; - - wil::unique_handle inPipeTheirSide; - wil::unique_handle inPipeOurSide; - - wil::unique_handle outPipeTheirSide; - wil::unique_handle outPipeOurSide; - RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(signalPipeOurSide.addressof(), signalPipeTheirSide.addressof(), nullptr, 0)); - RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(inPipeOurSide.addressof(), inPipeTheirSide.addressof(), nullptr, 0)); - - RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(outPipeTheirSide.addressof(), outPipeOurSide.addressof(), nullptr, 0)); - TraceLoggingWrite(g_hConhostV2EventTraceProvider, "SrvInit_ReceiveHandoff_OpenedPipes", TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), @@ -491,7 +480,7 @@ try const auto serverProcess = GetCurrentProcess(); - ::Microsoft::WRL::ComPtr handoff; + ::Microsoft::WRL::ComPtr handoff; TraceLoggingWrite(g_hConhostV2EventTraceProvider, "SrvInit_PrepareToCreateDelegationTerminal", @@ -566,21 +555,21 @@ try myStartupInfo.wShowWindow = settings.GetShowWindow(); - RETURN_IF_FAILED(handoff->EstablishPtyHandoff(inPipeTheirSide.get(), - outPipeTheirSide.get(), + wil::unique_handle inPipeOurSide; + wil::unique_handle outPipeOurSide; + RETURN_IF_FAILED(handoff->EstablishPtyHandoff(inPipeOurSide.addressof(), + outPipeOurSide.addressof(), signalPipeTheirSide.get(), refHandle.get(), serverProcess, clientProcess.get(), - myStartupInfo)); + &myStartupInfo)); TraceLoggingWrite(g_hConhostV2EventTraceProvider, "SrvInit_DelegateToTerminalSucceeded", TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - inPipeTheirSide.reset(); - outPipeTheirSide.reset(); signalPipeTheirSide.reset(); // GH#13211 - Make sure the terminal obeys the resizing quirk. Otherwise, From 119026afafe4cbf19c5ef8554bfbb5284fbba5c9 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 15 Jul 2024 00:46:17 +0200 Subject: [PATCH 50/60] Right, we can't remove those --- src/cascadia/CascadiaPackage/Package-Can.appxmanifest | 1 + src/cascadia/CascadiaPackage/Package-Dev.appxmanifest | 1 + src/cascadia/CascadiaPackage/Package-Pre.appxmanifest | 1 + src/cascadia/CascadiaPackage/Package.appxmanifest | 1 + 4 files changed, 4 insertions(+) diff --git a/src/cascadia/CascadiaPackage/Package-Can.appxmanifest b/src/cascadia/CascadiaPackage/Package-Can.appxmanifest index e8ff744104b..37138f18282 100644 --- a/src/cascadia/CascadiaPackage/Package-Can.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Can.appxmanifest @@ -111,6 +111,7 @@ + diff --git a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest index 9f68b6f3e4f..d75faca0d3e 100644 --- a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest @@ -111,6 +111,7 @@ + diff --git a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest index 665b5b7dde6..a080e85eb2c 100644 --- a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest @@ -200,6 +200,7 @@ + diff --git a/src/cascadia/CascadiaPackage/Package.appxmanifest b/src/cascadia/CascadiaPackage/Package.appxmanifest index c7ed197075a..2f98d2176f8 100644 --- a/src/cascadia/CascadiaPackage/Package.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package.appxmanifest @@ -200,6 +200,7 @@ + From 34857bc3e7c59fc3f920df442ba484723251cc5b Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 15 Jul 2024 00:47:07 +0200 Subject: [PATCH 51/60] should've --- src/cascadia/TerminalConnection/ConptyConnection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 974ff975500..3a95a670bc7 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -602,7 +602,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation _piClient.reset(); _pipe.reset(); - // The output thread should've already transitioned us to Closed. + // The output thread should have already transitioned us to Closed. // This exists just in case there was no output thread. _transitionToState(ConnectionState::Closed); } From 44a538d7e52deb82dc203967d084ed7e24bb30d7 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 15 Jul 2024 01:13:37 +0200 Subject: [PATCH 52/60] Address AuditMode issues --- src/types/inc/utils.hpp | 2 +- src/types/utils.cpp | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/types/inc/utils.hpp b/src/types/inc/utils.hpp index ef29689fa6b..18221bac2da 100644 --- a/src/types/inc/utils.hpp +++ b/src/types/inc/utils.hpp @@ -33,7 +33,7 @@ namespace Microsoft::Console::Utils bool HandleWantsOverlappedIo(HANDLE handle) noexcept; Pipe CreatePipe(DWORD bufferSize); Pipe CreateOverlappedPipe(DWORD openMode, DWORD bufferSize); - HRESULT GetOverlappedResultSameThread(const OVERLAPPED* overlapped, DWORD* bytesTransferred); + HRESULT GetOverlappedResultSameThread(const OVERLAPPED* overlapped, DWORD* bytesTransferred) noexcept; // Function Description: // - Clamps a long in between `min` and `SHRT_MAX` diff --git a/src/types/utils.cpp b/src/types/utils.cpp index 86e113ad3c0..69d7878f57c 100644 --- a/src/types/utils.cpp +++ b/src/types/utils.cpp @@ -813,8 +813,16 @@ Utils::Pipe Utils::CreateOverlappedPipe(DWORD openMode, DWORD bufferSize) // That got removed in Windows 7, because people kept starting a read/write on one thread and called GetOverlappedResult() on another. // When the OS sets Internal from STATUS_PENDING to 0 (= done) and then flags the hEvent, that doesn't happen atomically. // This results in a race condition if a OVERLAPPED is used across threads. -HRESULT Utils::GetOverlappedResultSameThread(const OVERLAPPED* overlapped, DWORD* bytesTransferred) +HRESULT Utils::GetOverlappedResultSameThread(const OVERLAPPED* overlapped, DWORD* bytesTransferred) noexcept { + assert(overlapped != nullptr); + assert(overlapped->hEvent != nullptr); + assert(bytesTransferred != nullptr); + + __assume(overlapped != nullptr); + __assume(overlapped->hEvent != nullptr); + __assume(bytesTransferred != nullptr); + if (overlapped->Internal == STATUS_PENDING) { if (WaitForSingleObjectEx(overlapped->hEvent, INFINITE, FALSE) != WAIT_OBJECT_0) @@ -826,7 +834,7 @@ HRESULT Utils::GetOverlappedResultSameThread(const OVERLAPPED* overlapped, DWORD // Assuming no multi-threading as per the function contract and // now that we ensured that hEvent is set (= read/write done), // we can safely read whatever want because nothing will set these concurrently. - *bytesTransferred = static_cast(overlapped->InternalHigh); + *bytesTransferred = gsl::narrow_cast(overlapped->InternalHigh); return HRESULT_FROM_NT(overlapped->Internal); } From 04aa614d6663f7db5853b1d636141ae9ab449e01 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 15 Jul 2024 01:39:43 +0200 Subject: [PATCH 53/60] More AuditMode fixes --- src/cascadia/TerminalConnection/ConptyConnection.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 3a95a670bc7..623b7bc0fd8 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -150,13 +150,15 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } CATCH_RETURN(); - static wil::unique_hfile duplicateHandle(const HANDLE in) noexcept + static wil::unique_hfile duplicateHandle(const HANDLE in) { wil::unique_hfile h; THROW_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(), in, GetCurrentProcess(), h.addressof(), 0, FALSE, DUPLICATE_SAME_ACCESS)); return h; } + // Who decided that? +#pragma warning(suppress : 26455) // Default constructor should not throw. Declare it 'noexcept' (f.6). ConptyConnection::ConptyConnection() : _writeOverlappedEvent{ CreateEventExW(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS) } { @@ -280,8 +282,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } } + // Misdiagnosis: out is being tested right in the first line. +#pragma warning(suppress : 26430) // Symbol 'out' is not tested for nullness on all paths (f.23). void ConptyConnection::InitializeFromHandoff(HANDLE* in, HANDLE* out, HANDLE signal, HANDLE reference, HANDLE server, HANDLE client, const TERMINAL_STARTUP_INFO* startupInfo) { + THROW_HR_IF(E_UNEXPECTED, !in || !out || !startupInfo); + _sessionId = Utils::CreateGuid(); auto pipe = Utils::CreateOverlappedPipe(PIPE_ACCESS_DUPLEX, 128 * 1024); @@ -646,7 +652,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation // won't wait for us, and the known exit points _do_. auto strongThis{ get_strong() }; - const auto cleanup = wil::scope_exit([this]() { + const auto cleanup = wil::scope_exit([this]() noexcept { _LastConPtyClientDisconnected(); }); @@ -741,7 +747,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation 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, gsl::narrow_cast(read) }, wstr, u8State)); + FAILED_LOG(til::u8u16({ &buffer[0], gsl::narrow_cast(read) }, wstr, u8State)); } return 0; From a7158fb26ae13fb8d941c174e184e61a42581292 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 17 Jul 2024 18:13:19 +0200 Subject: [PATCH 54/60] Fix bad merge --- src/host/VtIo.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index 31b63a1e607..06fb4309371 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -192,7 +192,8 @@ bool VtIo::IsUsingVt() const _lookingForCursorPosition = false; // 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); } From 21b13ca4615a1407758e5df8bdcde7f7d16792d4 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 18 Jul 2024 04:29:46 +0200 Subject: [PATCH 55/60] Avoid emitting VT for no-op SetConsoleActiveScreenBuffer calls --- src/host/getset.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/host/getset.cpp b/src/host/getset.cpp index 749a170142e..fa690745781 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -478,6 +478,12 @@ void ApiRoutines::SetConsoleActiveScreenBufferImpl(SCREEN_INFORMATION& newContex 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(); From 784f3f8780457c24658bcc91d4b74ad292903169 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sat, 20 Jul 2024 03:02:13 +0200 Subject: [PATCH 56/60] Bad merge? --- .github/actions/spelling/expect/expect.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 9a481bd39b8..e0c9fd972d1 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -143,6 +143,7 @@ bufferout buffersize buflen buildtransitive +buildsystems BValue bytebuffer cac From 975d258284a3893f965c2e9c5a65ae13f2513472 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 23 Jul 2024 22:04:37 +0200 Subject: [PATCH 57/60] Revert a few unnecessary changes --- src/cascadia/TerminalCore/TerminalApi.cpp | 2 ++ src/host/CursorBlinker.cpp | 2 +- src/host/consoleInformation.cpp | 2 +- src/host/getset.cpp | 4 ++-- src/host/output.cpp | 2 +- src/host/outputStream.cpp | 4 ++-- src/host/screenInfo.cpp | 2 +- src/host/server.h | 2 +- src/host/settings.cpp | 2 +- src/host/srvinit.cpp | 2 +- src/interactivity/win32/windowio.cpp | 2 +- src/terminal/adapter/InteractDispatch.cpp | 2 +- 12 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 21658608141..5982d184c52 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -193,6 +193,8 @@ void Terminal::PlayMidiNote(const int noteNumber, const int velocity, const std: void Terminal::UseAlternateScreenBuffer(const TextAttribute& attrs) { + _assertLocked(); + // the new alt buffer is exactly the size of the viewport. _altBufferSize = _mutableViewport.Dimensions(); diff --git a/src/host/CursorBlinker.cpp b/src/host/CursorBlinker.cpp index 0714d0461e8..68250b5f423 100644 --- a/src/host/CursorBlinker.cpp +++ b/src/host/CursorBlinker.cpp @@ -164,7 +164,7 @@ void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo) const noexcept void CursorBlinker::SetCaretTimer() const noexcept { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (gci.IsConPTY()) + if (gci.IsInVtIoMode()) { return; } diff --git a/src/host/consoleInformation.cpp b/src/host/consoleInformation.cpp index de12eae7ad2..c92b7caddcf 100644 --- a/src/host/consoleInformation.cpp +++ b/src/host/consoleInformation.cpp @@ -133,7 +133,7 @@ VtIo::Writer CONSOLE_INFORMATION::GetVtWriterForBuffer(const SCREEN_INFORMATION* return _vtIo.IsUsingVt() && (pCurrentScreenBuffer == context || pCurrentScreenBuffer == context->GetAltBuffer()) ? _vtIo.GetWriter() : VtIo::Writer{}; } -bool CONSOLE_INFORMATION::IsConPTY() const noexcept +bool CONSOLE_INFORMATION::IsInVtIoMode() const noexcept { return _vtIo.IsUsingVt(); } diff --git a/src/host/getset.cpp b/src/host/getset.cpp index fa690745781..42ea0885517 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -916,7 +916,7 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont // if we're headless, not so much. However, GetMaxWindowSizeInCharacters // will only return the buffer size, so we can't use that to clip the arg here. // So only clip the requested size if we're not headless - if (g.getConsoleInformation().IsConPTY()) + if (g.getConsoleInformation().IsInVtIoMode()) { // SetViewportRect doesn't cause the buffer to resize. Manually resize the buffer. RETURN_IF_NTSTATUS_FAILED(context.ResizeScreenBuffer(Viewport::FromInclusive(Window).Dimensions(), false)); @@ -1225,7 +1225,7 @@ void ApiRoutines::GetConsoleWindowImpl(HWND& hwnd) noexcept // console, so that they know that this console is in fact a real // console window. auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (gci.IsConPTY()) + if (gci.IsInVtIoMode()) { hwnd = ServiceLocator::LocatePseudoWindow(); } diff --git a/src/host/output.cpp b/src/host/output.cpp index aa276832391..7537292ba19 100644 --- a/src/host/output.cpp +++ b/src/host/output.cpp @@ -460,7 +460,7 @@ void SetActiveScreenBuffer(SCREEN_INFORMATION& screenInfo) // mode, then the cursor will remain off until they print text. This can // lead to alignment problems in the terminal, because we won't move the // terminal's cursor in this _exact_ scenario. - screenInfo.GetTextBuffer().GetCursor().SetIsOn(gci.IsConPTY()); + screenInfo.GetTextBuffer().GetCursor().SetIsOn(gci.IsInVtIoMode()); // set font screenInfo.RefreshFontWithRenderer(); diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index beccff5d586..a719f7a63c6 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -36,7 +36,7 @@ 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.IsConPTY()) + if (gci.IsInVtIoMode()) { return; } @@ -211,7 +211,7 @@ CursorType ConhostInternalGetSet::GetUserDefaultCursorStyle() const void ConhostInternalGetSet::ShowWindow(bool showOrHide) { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto hwnd = gci.IsConPTY() ? ServiceLocator::LocatePseudoWindow() : ServiceLocator::LocateConsoleWindow()->GetWindowHandle(); + const auto hwnd = gci.IsInVtIoMode() ? ServiceLocator::LocatePseudoWindow() : ServiceLocator::LocateConsoleWindow()->GetWindowHandle(); // GH#13301 - When we send this ShowWindow message, if we send it to the // conhost HWND, it's going to need to get processed by the window message diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index f0b6e7937e3..42fa6a9f5c2 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -1969,7 +1969,7 @@ bool SCREEN_INFORMATION::_IsAltBuffer() const bool SCREEN_INFORMATION::_IsInPtyMode() const { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - return _IsAltBuffer() || gci.IsConPTY(); + return _IsAltBuffer() || gci.IsInVtIoMode(); } // Routine Description: diff --git a/src/host/server.h b/src/host/server.h index 03b47d03652..dddc8c7d8c3 100644 --- a/src/host/server.h +++ b/src/host/server.h @@ -106,7 +106,7 @@ class CONSOLE_INFORMATION : Microsoft::Console::VirtualTerminal::VtIo* GetVtIoNoCheck() noexcept; Microsoft::Console::VirtualTerminal::VtIo::Writer GetVtWriter() noexcept; Microsoft::Console::VirtualTerminal::VtIo::Writer GetVtWriterForBuffer(const SCREEN_INFORMATION* context) noexcept; - bool IsConPTY() const noexcept; + bool IsInVtIoMode() const noexcept; SCREEN_INFORMATION& GetActiveOutputBuffer() override; const SCREEN_INFORMATION& GetActiveOutputBuffer() const override; diff --git a/src/host/settings.cpp b/src/host/settings.cpp index f799811f891..e0b07e38f75 100644 --- a/src/host/settings.cpp +++ b/src/host/settings.cpp @@ -181,7 +181,7 @@ void Settings::ApplyCommandlineArguments(const ConsoleArguments& consoleArgs) _dwScreenBufferSize.Y = height; _dwWindowSize.Y = height; } - else if (ServiceLocator::LocateGlobals().getConsoleInformation().IsConPTY()) + else if (ServiceLocator::LocateGlobals().getConsoleInformation().IsInVtIoMode()) { // If we're a PTY but we weren't explicitly told a size, use the window size as the buffer size. _dwScreenBufferSize = _dwWindowSize; diff --git a/src/host/srvinit.cpp b/src/host/srvinit.cpp index 4f17a03f411..a2d5de61588 100644 --- a/src/host/srvinit.cpp +++ b/src/host/srvinit.cpp @@ -841,7 +841,7 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand, // No matter what, create a renderer. try { - if (!gci.IsConPTY()) + if (!gci.IsInVtIoMode()) { auto renderThread = std::make_unique(); // stash a local pointer to the thread here - diff --git a/src/interactivity/win32/windowio.cpp b/src/interactivity/win32/windowio.cpp index 4629fd44fd4..50a46b9b3df 100644 --- a/src/interactivity/win32/windowio.cpp +++ b/src/interactivity/win32/windowio.cpp @@ -936,7 +936,7 @@ DWORD WINAPI ConsoleInputThreadProcWin32(LPVOID /*lpParameter*/) // successfully created with the owner configured when the window is // first created. See GH#13066 for details. auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - if (gci.IsConPTY()) + if (gci.IsInVtIoMode()) { gci.GetVtIoNoCheck()->CreatePseudoWindow(); } diff --git a/src/terminal/adapter/InteractDispatch.cpp b/src/terminal/adapter/InteractDispatch.cpp index 5752e592a5e..f920eedf795 100644 --- a/src/terminal/adapter/InteractDispatch.cpp +++ b/src/terminal/adapter/InteractDispatch.cpp @@ -171,7 +171,7 @@ bool InteractDispatch::FocusChanged(const bool focused) const // This should likely always be true - we shouldn't ever have an // InteractDispatch outside ConPTY mode, but just in case... - if (gci.IsConPTY()) + if (gci.IsInVtIoMode()) { auto shouldActuallyFocus = false; From 1421736a6c122c6074dc838fe16747daa1e93c51 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 23 Jul 2024 22:11:10 +0200 Subject: [PATCH 58/60] Revert another pointless change --- src/host/output.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/host/output.cpp b/src/host/output.cpp index 7537292ba19..e57412f5310 100644 --- a/src/host/output.cpp +++ b/src/host/output.cpp @@ -316,8 +316,8 @@ void ScrollRegion(SCREEN_INFORMATION& screenInfo, const til::inclusive_rect scrollRectGiven, const std::optional clipRectGiven, const til::point destinationOriginGiven, - wchar_t fillCharGiven, - TextAttribute fillAttrsGiven) + const wchar_t fillCharGiven, + const TextAttribute fillAttrsGiven) { // ------ 1. PREP SOURCE ------ // Set up the source viewport. @@ -357,18 +357,17 @@ void ScrollRegion(SCREEN_INFORMATION& screenInfo, return; } + // Determine the cell we will use to fill in any revealed/uncovered space. + // We generally use exactly what was given to us. + OutputCellIterator fillData(fillCharGiven, fillAttrsGiven); + // 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 (fillCharGiven == UNICODE_NULL && fillAttrsGiven == TextAttribute{ 0 }) { - fillCharGiven = UNICODE_SPACE; - fillAttrsGiven = screenInfo.GetAttributes(); + fillData = OutputCellIterator(UNICODE_SPACE, screenInfo.GetAttributes()); } - // Determine the cell we will use to fill in any revealed/uncovered space. - // We generally use exactly what was given to us. - OutputCellIterator fillData(fillCharGiven, fillAttrsGiven); - // ------ 4. PREP TARGET ------ // Now it's time to think about the target. We're only given the origin of the target // because it is assumed that it will have the same relative dimensions as the original source. From a8104aaa50cdb650e05d442bcc67fa525836cab2 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 23 Jul 2024 23:34:05 +0200 Subject: [PATCH 59/60] Thread safety, Fix overlapped write event, Fix shutdown log --- .../TerminalConnection/ConptyConnection.cpp | 16 ++++++++++------ .../TerminalConnection/ConptyConnection.h | 4 +++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 9ecd91146fc..5b8cb22028a 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -156,6 +156,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation _writeOverlappedEvent{ CreateEventExW(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS) } { THROW_LAST_ERROR_IF(!_writeOverlappedEvent); + _writeOverlapped.hEvent = _writeOverlappedEvent.get(); } // Function Description: @@ -481,14 +482,19 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation return; } + // 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 (FAILED_LOG(Utils::GetOverlappedResultSameThread(&_writeOverlapped, &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; } @@ -581,6 +587,9 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation // Loop around `CancelIoEx()` just in case the signal to shut down was missed. for (;;) { + // 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 // we don't call a destroyed event handler asynchronously from a background thread (GH#13880). @@ -589,11 +598,6 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation { break; } - - LOG_LAST_ERROR(); - - // The output thread may be stuck waiting for the OVERLAPPED to be signaled. - CancelIoEx(_pipe.get(), nullptr); } } diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h index c27d995bae9..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 { @@ -79,6 +80,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation wil::unique_process_information _piClient; wil::unique_any _hPC; + til::ticket_lock _writeLock; wil::unique_event _writeOverlappedEvent; OVERLAPPED _writeOverlapped{}; std::string _writeBuffer; From c87bbedfe57cd3a9201ec9c8c5a6296074764333 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 30 Jul 2024 16:10:36 +0200 Subject: [PATCH 60/60] Address feedback --- src/host/PtySignalInputThread.cpp | 2 +- src/host/VtInputThread.cpp | 2 +- src/host/consoleInformation.cpp | 2 +- src/host/screenInfo.cpp | 16 ++++++++++++++-- src/host/server.h | 2 +- src/host/srvinit.cpp | 8 ++++---- src/host/ut_host/VtIoTests.cpp | 2 +- src/interactivity/win32/windowio.cpp | 2 +- src/terminal/adapter/InteractDispatch.cpp | 2 ++ 9 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/host/PtySignalInputThread.cpp b/src/host/PtySignalInputThread.cpp index a0b96200ece..45860f9c1a0 100644 --- a/src/host/PtySignalInputThread.cpp +++ b/src/host/PtySignalInputThread.cpp @@ -347,5 +347,5 @@ void PtySignalInputThread::_DoSetWindowParent(const SetParentData& data) void PtySignalInputThread::_Shutdown() { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - gci.GetVtIoNoCheck()->SendCloseEvent(); + gci.GetVtIo()->SendCloseEvent(); } diff --git a/src/host/VtInputThread.cpp b/src/host/VtInputThread.cpp index f0d9b0ef5d6..65c62abf25e 100644 --- a/src/host/VtInputThread.cpp +++ b/src/host/VtInputThread.cpp @@ -56,7 +56,7 @@ void VtInputThread::_InputThread() { const auto cleanup = wil::scope_exit([this]() { _hFile.reset(); - ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIoNoCheck()->SendCloseEvent(); + ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo()->SendCloseEvent(); }); OVERLAPPED* overlapped = nullptr; diff --git a/src/host/consoleInformation.cpp b/src/host/consoleInformation.cpp index c92b7caddcf..3051e00a153 100644 --- a/src/host/consoleInformation.cpp +++ b/src/host/consoleInformation.cpp @@ -118,7 +118,7 @@ ULONG CONSOLE_INFORMATION::GetCSRecursionCount() const noexcept return Status; } -VtIo* CONSOLE_INFORMATION::GetVtIoNoCheck() noexcept +VtIo* CONSOLE_INFORMATION::GetVtIo() noexcept { return &_vtIo; } diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index 42fa6a9f5c2..0084dc5bced 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -2149,9 +2149,21 @@ void SCREEN_INFORMATION::SetViewport(const Viewport& newViewport, // - S_OK [[nodiscard]] HRESULT SCREEN_INFORMATION::ClearBuffer() { - _textBuffer->Reset(); + // Rotate the buffer to bring the cursor row to the top of the viewport. + const auto cursorPos = _textBuffer->GetCursor().GetPosition(); + for (auto i = 0; i < cursorPos.y; i++) + { + _textBuffer->IncrementCircularBuffer(); + } + + // Erase everything below that point. + RETURN_IF_FAILED(SetCursorPosition({ 0, 1 }, false)); + auto& engine = reinterpret_cast(_stateMachine->Engine()); + engine.Dispatch().EraseInDisplay(DispatchTypes::EraseType::ToEnd); + // Restore the original cursor x offset, but now on the first row. - _textBuffer->GetCursor().SetYPosition(0); + RETURN_IF_FAILED(SetCursorPosition({ cursorPos.x, 0 }, false)); + _textBuffer->TriggerRedrawAll(); return S_OK; diff --git a/src/host/server.h b/src/host/server.h index dddc8c7d8c3..1e14a94b731 100644 --- a/src/host/server.h +++ b/src/host/server.h @@ -103,7 +103,7 @@ class CONSOLE_INFORMATION : bool IsConsoleLocked() const noexcept; ULONG GetCSRecursionCount() const noexcept; - Microsoft::Console::VirtualTerminal::VtIo* GetVtIoNoCheck() noexcept; + 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; diff --git a/src/host/srvinit.cpp b/src/host/srvinit.cpp index a2d5de61588..5b10705a631 100644 --- a/src/host/srvinit.cpp +++ b/src/host/srvinit.cpp @@ -380,8 +380,8 @@ HRESULT ConsoleCreateIoThread(_In_ HANDLE Server, // The conpty i/o threads need an actual client to be connected before they // can start, so they're started below, in ConsoleAllocateConsole auto& gci = g.getConsoleInformation(); - RETURN_IF_FAILED(gci.GetVtIoNoCheck()->Initialize(args)); - RETURN_IF_FAILED(gci.GetVtIoNoCheck()->CreateAndStartSignalThread()); + RETURN_IF_FAILED(gci.GetVtIo()->Initialize(args)); + RETURN_IF_FAILED(gci.GetVtIo()->CreateAndStartSignalThread()); return S_OK; } @@ -945,7 +945,7 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand, // We'll need the size of the screen buffer in the vt i/o initialization if (SUCCEEDED_NTSTATUS(Status)) { - auto hr = gci.GetVtIoNoCheck()->CreateIoHandlers(); + auto hr = gci.GetVtIo()->CreateIoHandlers(); if (hr == S_FALSE) { // We're not in VT I/O mode, this is fine. @@ -953,7 +953,7 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand, else if (SUCCEEDED(hr)) { // Actually start the VT I/O threads - hr = gci.GetVtIoNoCheck()->StartIfNeeded(); + hr = gci.GetVtIo()->StartIfNeeded(); // Don't convert S_FALSE to an NTSTATUS - the equivalent NTSTATUS // is treated as an error if (hr != S_FALSE) diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index ddd4af6c1b5..1a6085b498c 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -95,7 +95,7 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests commonState.PrepareGlobalScreenBuffer(8, 4, 8, 4); auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - THROW_IF_FAILED(gci.GetVtIoNoCheck()->_Initialize(nullptr, tx.release(), nullptr)); + THROW_IF_FAILED(gci.GetVtIo()->_Initialize(nullptr, tx.release(), nullptr)); screenInfo = &gci.GetActiveOutputBuffer(); return true; diff --git a/src/interactivity/win32/windowio.cpp b/src/interactivity/win32/windowio.cpp index 50a46b9b3df..5afd66b9ce2 100644 --- a/src/interactivity/win32/windowio.cpp +++ b/src/interactivity/win32/windowio.cpp @@ -938,7 +938,7 @@ DWORD WINAPI ConsoleInputThreadProcWin32(LPVOID /*lpParameter*/) auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); if (gci.IsInVtIoMode()) { - gci.GetVtIoNoCheck()->CreatePseudoWindow(); + gci.GetVtIo()->CreatePseudoWindow(); } // Register the pseudoconsole window as being owned by the root process. diff --git a/src/terminal/adapter/InteractDispatch.cpp b/src/terminal/adapter/InteractDispatch.cpp index f920eedf795..09329eb6647 100644 --- a/src/terminal/adapter/InteractDispatch.cpp +++ b/src/terminal/adapter/InteractDispatch.cpp @@ -111,6 +111,8 @@ bool InteractDispatch::WindowManipulation(const DispatchTypes::WindowManipulatio _api.GetBufferAndViewport().buffer.TriggerRedrawAll(); return true; case DispatchTypes::WindowManipulationType::ResizeWindowInCharacters: + // TODO:GH#1765 We should introduce a better `ResizeConpty` function to + // ConhostInternalGetSet, that specifically handles a conpty resize. _api.ResizeWindow(parameter2.value_or(0), parameter1.value_or(0)); return true; default: