From 9a0b6e3b6980e9a7df1e576e2c5cc053907ace09 Mon Sep 17 00:00:00 2001 From: James Holderness Date: Wed, 1 Apr 2020 13:49:27 +0100 Subject: [PATCH] Reimplement the VT tab stop functionality (#5173) ## Summary of the Pull Request This is essentially a rewrite of the VT tab stop functionality, implemented entirely within the `AdaptDispatch` class. This significantly simplifies the `ConGetSet` interface, and should hopefully make it easier to share the functionality with the Windows Terminal VT implementation in the future. By removing the dependence on the `SCREEN_INFORMATION` class, it fixes the problem of the the tab state not being preserved when switching between the main and alternate buffers. And the new architecture also fixes problems with the tabs not being correctly initialized when the screen is resized. ## References This fixes one aspect of issue #3545. It also supersedes the fix for #411 (PR #2816). I'm hoping the simplification of `ConGetSet` will help with #3849. ## PR Checklist * [x] Closes #4669 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [x] Tests added/passed * [ ] Requires documentation to be updated * [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx ## Detailed Description of the Pull Request / Additional comments In the new tab architecture, there is now a `vector` (__tabStopColumns_), which tracks whether any particular column is a tab stop or not. There is also a __initDefaultTabStops_ flag indicating whether the default tab stop positions need to be initialised when the screen is resized. The way this works, the vector is initially empty, and only initialized (to the current width of the screen) when it needs to be used. When the vector grows in size, the __initDefaultTabStops_ flag determines whether the new columns are set to false, or if every 8th column is set to true. By default we want the latter behaviour - newly revealed columns should have default tab stops assigned to them - so __initDefaultTabStops_ is set to true. However, after a `TBC 3` operation (i.e. we've cleared all tab stops), there should be no tab stops in any newly revealed columns, so __initDefaultTabStops_ is set to false. Note that the __tabStopColumns_ vector is never made smaller when the window is shrunk, and that way it can preserve the state of tab stops that are off screen, but which may come into range if the window is made bigger again. However, we can can still reset the vector completely after an `RIS` or `TBC 3` operation, since the state can then be reconstructed automatically based on just the __initDefaultTabStops_ flag. ## Validation Steps Performed The original screen buffer tests had to be rewritten to set and query the tab stop state using escape sequences rather than interacting with the `SCREEN_INFORMATION` class directly, but otherwise the structure of most tests remained largely the same. However, the alt buffer test was significantly rewritten, since the original behaviour was incorrect, and the initialization test was dropped completely, since it was no longer applicable. The adapter tests were also dropped, since they were testing the `ConGetSet` interface which has now been removed. I also had to make an addition to the method setup of the screen buffer tests (making sure the viewport was appropriately initialized), since there were some tests (unrelated to tab stops) that were previously dependent on the state being set in the tab initialization test which has now been removed. I've manually tested the issue described in #4669 and confirmed that the tabs now produce the correct spacing after a resize. --- src/host/getset.cpp | 107 ----- src/host/getset.h | 6 - src/host/outputStream.cpp | 74 ---- src/host/outputStream.hpp | 6 - src/host/screenInfo.cpp | 138 ------- src/host/screenInfo.hpp | 10 - src/host/ut_host/ScreenBufferTests.cpp | 377 +++++++++--------- src/terminal/adapter/adaptDispatch.cpp | 143 ++++++- src/terminal/adapter/adaptDispatch.hpp | 8 + src/terminal/adapter/conGetSet.hpp | 5 - .../adapter/ut_adapter/adapterTest.cpp | 72 ---- 11 files changed, 344 insertions(+), 602 deletions(-) diff --git a/src/host/getset.cpp b/src/host/getset.cpp index e55ba97c8f1..c8d81f92670 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -404,13 +404,6 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept { // jiggle the handle screenInfo.GetStateMachine().ResetState(); - screenInfo.ClearTabStops(); - } - // if we're moving from VT off->on - else if (WI_IsFlagSet(dwNewMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING) && - WI_IsFlagClear(dwOldMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING)) - { - screenInfo.SetDefaultVtTabStops(); } gci.SetVirtTermLevel(WI_IsFlagSet(dwNewMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING) ? 1 : 0); @@ -1522,99 +1515,6 @@ void DoSrvPrivateUseMainScreenBuffer(SCREEN_INFORMATION& screenInfo) screenInfo.GetActiveBuffer().UseMainScreenBuffer(); } -// Routine Description: -// - A private API call for setting a VT tab stop in the cursor's current column. -// Parameters: -// -// Return value: -// - STATUS_SUCCESS if handled successfully. Otherwise, an appropriate status code indicating the error. -[[nodiscard]] NTSTATUS DoSrvPrivateHorizontalTabSet() -{ - CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - SCREEN_INFORMATION& _screenBuffer = gci.GetActiveOutputBuffer().GetActiveBuffer(); - - const COORD cursorPos = _screenBuffer.GetTextBuffer().GetCursor().GetPosition(); - try - { - _screenBuffer.AddTabStop(cursorPos.X); - } - catch (...) - { - return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException()); - } - return STATUS_SUCCESS; -} - -// Routine Description: -// - A private helper for executing a number of tabs. -// Parameters: -// sNumTabs - The number of tabs to execute -// fForward - whether to tab forward or backwards -// Return value: -// - STATUS_SUCCESS if handled successfully. Otherwise, an appropriate status code indicating the error. -[[nodiscard]] NTSTATUS DoPrivateTabHelper(const SHORT sNumTabs, _In_ bool fForward) -{ - CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - SCREEN_INFORMATION& _screenBuffer = gci.GetActiveOutputBuffer().GetActiveBuffer(); - COORD cursorPos = _screenBuffer.GetTextBuffer().GetCursor().GetPosition(); - - FAIL_FAST_IF(!(sNumTabs >= 0)); - for (SHORT sTabsExecuted = 0; sTabsExecuted < sNumTabs; sTabsExecuted++) - { - cursorPos = (fForward) ? _screenBuffer.GetForwardTab(cursorPos) : _screenBuffer.GetReverseTab(cursorPos); - } - - return AdjustCursorPosition(_screenBuffer, cursorPos, TRUE, nullptr); -} - -// Routine Description: -// - A private API call for performing a forwards tab. This will take the -// cursor to the tab stop following its current location. If there are no -// more tabs in this row, it will take it to the right side of the window. -// If it's already in the last column of the row, it will move it to the next line. -// Parameters: -// - sNumTabs - The number of tabs to perform. -// Return value: -// - STATUS_SUCCESS if handled successfully. Otherwise, an appropriate status code indicating the error. -[[nodiscard]] NTSTATUS DoSrvPrivateForwardTab(const SHORT sNumTabs) -{ - return DoPrivateTabHelper(sNumTabs, true); -} - -// Routine Description: -// - A private API call for performing a backwards tab. This will take the -// cursor to the tab stop previous to its current location. It will not reverse line feed. -// Parameters: -// - sNumTabs - The number of tabs to perform. -// Return value: -// - STATUS_SUCCESS if handled successfully. Otherwise, an appropriate status code indicating the error. -[[nodiscard]] NTSTATUS DoSrvPrivateBackwardsTab(const SHORT sNumTabs) -{ - return DoPrivateTabHelper(sNumTabs, false); -} - -// Routine Description: -// - A private API call for clearing the VT tabs that have been set. -// Parameters: -// - fClearAll - If false, only clears the tab in the current column (if it exists) -// otherwise clears all set tabs. (and reverts to legacy 8-char tabs behavior.) -// Return value: -// - None -void DoSrvPrivateTabClear(const bool fClearAll) -{ - CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - SCREEN_INFORMATION& screenBuffer = gci.GetActiveOutputBuffer().GetActiveBuffer(); - if (fClearAll) - { - screenBuffer.ClearTabStops(); - } - else - { - const COORD cursorPos = screenBuffer.GetTextBuffer().GetCursor().GetPosition(); - screenBuffer.ClearTabStop(cursorPos.X); - } -} - // Routine Description: // - A private API call for enabling VT200 style mouse mode. // Parameters: @@ -2080,13 +1980,6 @@ void DoSrvIsConsolePty(bool& isPty) isPty = gci.IsInVtIoMode(); } -// Routine Description: -// - a private API call for setting the default tab stops in the active screen buffer. -void DoSrvPrivateSetDefaultTabStops() -{ - ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer().GetActiveBuffer().SetDefaultVtTabStops(); -} - // Routine Description: // - internal logic for adding or removing lines in the active screen buffer // this also moves the cursor to the left margin, which is expected behavior for IL and DL diff --git a/src/host/getset.h b/src/host/getset.h index 3106c0af300..f84eee084db 100644 --- a/src/host/getset.h +++ b/src/host/getset.h @@ -42,11 +42,6 @@ void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool [[nodiscard]] NTSTATUS DoSrvPrivateUseAlternateScreenBuffer(SCREEN_INFORMATION& screenInfo); void DoSrvPrivateUseMainScreenBuffer(SCREEN_INFORMATION& screenInfo); -[[nodiscard]] NTSTATUS DoSrvPrivateHorizontalTabSet(); -[[nodiscard]] NTSTATUS DoSrvPrivateForwardTab(const SHORT sNumTabs); -[[nodiscard]] NTSTATUS DoSrvPrivateBackwardsTab(const SHORT sNumTabs); -void DoSrvPrivateTabClear(const bool fClearAll); - void DoSrvPrivateEnableVT200MouseMode(const bool fEnable); void DoSrvPrivateEnableUTF8ExtendedMouseMode(const bool fEnable); void DoSrvPrivateEnableSGRExtendedMouseMode(const bool fEnable); @@ -84,7 +79,6 @@ void DoSrvGetConsoleOutputCodePage(unsigned int& codepage); void DoSrvIsConsolePty(bool& isPty); -void DoSrvPrivateSetDefaultTabStops(); void DoSrvPrivateDeleteLines(const size_t count); void DoSrvPrivateInsertLines(const size_t count); diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index dfac1d27f46..2b50c5ce24b 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -491,80 +491,6 @@ bool ConhostInternalGetSet::PrivateUseMainScreenBuffer() return true; } -// - Connects the PrivateHorizontalTabSet call directly into our Driver Message servicing call inside Conhost.exe -// PrivateHorizontalTabSet is an internal-only "API" call that the vt commands can execute, -// but it is not represented as a function call on out public API surface. -// Arguments: -// -// Return Value: -// - true if successful (see PrivateHorizontalTabSet). false otherwise. -bool ConhostInternalGetSet::PrivateHorizontalTabSet() -{ - return NT_SUCCESS(DoSrvPrivateHorizontalTabSet()); -} - -// Routine Description: -// - Connects the PrivateForwardTab call directly into our Driver Message servicing call inside Conhost.exe -// PrivateForwardTab is an internal-only "API" call that the vt commands can execute, -// but it is not represented as a function call on out public API surface. -// Arguments: -// - sNumTabs - the number of tabs to execute -// Return Value: -// - true if successful (see PrivateForwardTab). false otherwise. -bool ConhostInternalGetSet::PrivateForwardTab(const size_t numTabs) -{ - SHORT tabs; - if (FAILED(SizeTToShort(numTabs, &tabs))) - { - return false; - } - - return NT_SUCCESS(DoSrvPrivateForwardTab(tabs)); -} - -// Routine Description: -// - Connects the PrivateBackwardsTab call directly into our Driver Message servicing call inside Conhost.exe -// PrivateBackwardsTab is an internal-only "API" call that the vt commands can execute, -// but it is not represented as a function call on out public API surface. -// Arguments: -// - numTabs - the number of tabs to execute -// Return Value: -// - true if successful (see PrivateBackwardsTab). false otherwise. -bool ConhostInternalGetSet::PrivateBackwardsTab(const size_t numTabs) -{ - SHORT tabs; - if (FAILED(SizeTToShort(numTabs, &tabs))) - { - return false; - } - - return NT_SUCCESS(DoSrvPrivateBackwardsTab(tabs)); -} - -// Routine Description: -// - Connects the PrivateTabClear call directly into our Driver Message servicing call inside Conhost.exe -// PrivateTabClear is an internal-only "API" call that the vt commands can execute, -// but it is not represented as a function call on out public API surface. -// Arguments: -// - clearAll - set to true to enable blinking, false to disable -// Return Value: -// - true if successful (see PrivateTabClear). false otherwise. -bool ConhostInternalGetSet::PrivateTabClear(const bool clearAll) -{ - DoSrvPrivateTabClear(clearAll); - return true; -} - -// Routine Description: -// - Connects the PrivateSetDefaultTabStops call directly into the private api point -// Return Value: -// - true -bool ConhostInternalGetSet::PrivateSetDefaultTabStops() -{ - DoSrvPrivateSetDefaultTabStops(); - return true; -} - // Routine Description: // - Connects the PrivateEnableVT200MouseMode call directly into our Driver Message servicing call inside Conhost.exe // PrivateEnableVT200MouseMode 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 7544041213a..ea900d83bd1 100644 --- a/src/host/outputStream.hpp +++ b/src/host/outputStream.hpp @@ -113,12 +113,6 @@ class ConhostInternalGetSet final : public Microsoft::Console::VirtualTerminal:: bool PrivateUseMainScreenBuffer() override; - bool PrivateHorizontalTabSet(); - bool PrivateForwardTab(const size_t numTabs) override; - bool PrivateBackwardsTab(const size_t numTabs) override; - bool PrivateTabClear(const bool clearAll) override; - bool PrivateSetDefaultTabStops() override; - bool PrivateEnableVT200MouseMode(const bool enabled) override; bool PrivateEnableUTF8ExtendedMouseMode(const bool enabled) override; bool PrivateEnableSGRExtendedMouseMode(const bool enabled) override; diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index ba1de0e99e0..18942c5a27c 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -55,7 +55,6 @@ SCREEN_INFORMATION::SCREEN_INFORMATION( _rcAltSavedClientOld{ 0 }, _fAltWindowChanged{ false }, _PopupAttributes{ popupAttributes }, - _tabStops{}, _virtualBottom{ 0 }, _renderTarget{ *this }, _currentFont{ fontInfo }, @@ -127,16 +126,6 @@ SCREEN_INFORMATION::~SCREEN_INFORMATION() const NTSTATUS status = pScreen->_InitializeOutputStateMachine(); - if (pScreen->_IsInVTMode()) - { - // microsoft/terminal#411: If VT mode is enabled, lets construct the - // VT tab stops. Without this line, if a user has - // VirtualTerminalLevel set, then - // SetConsoleMode(ENABLE_VIRTUAL_TERMINAL_PROCESSING) won't set our - // tab stops, because we're never going from vt off -> on - pScreen->SetDefaultVtTabStops(); - } - if (NT_SUCCESS(status)) { *ppScreen = pScreen; @@ -1839,9 +1828,6 @@ const SCREEN_INFORMATION& SCREEN_INFORMATION::GetMainBuffer() const // Set up the new buffers references to our current state machine, dispatcher, getset, etc. createdBuffer->_stateMachine = _stateMachine; - - // Setup the alt buffer's tabs stops with the default tab stop settings - createdBuffer->SetDefaultVtTabStops(); } return Status; } @@ -1964,130 +1950,6 @@ bool SCREEN_INFORMATION::_IsInVTMode() const return WI_IsFlagSet(OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING); } -// Routine Description: -// - Sets a VT tab stop in the column sColumn. If there is already a tab there, it does nothing. -// Parameters: -// - sColumn: the column to add a tab stop to. -// Return value: -// - none -// Note: may throw exception on allocation error -void SCREEN_INFORMATION::AddTabStop(const SHORT sColumn) -{ - if (std::find(_tabStops.begin(), _tabStops.end(), sColumn) == _tabStops.end()) - { - _tabStops.push_back(sColumn); - _tabStops.sort(); - } -} - -// Routine Description: -// - Clears all of the VT tabs that have been set. This also deletes them. -// Parameters: -// -// Return value: -// -void SCREEN_INFORMATION::ClearTabStops() noexcept -{ - _tabStops.clear(); -} - -// Routine Description: -// - Clears the VT tab in the column sColumn (if one has been set). Also deletes it from the heap. -// Parameters: -// - sColumn - The column to clear the tab stop for. -// Return value: -// -void SCREEN_INFORMATION::ClearTabStop(const SHORT sColumn) noexcept -{ - _tabStops.remove(sColumn); -} - -// Routine Description: -// - Places the location that a forwards tab would take cCurrCursorPos to into pcNewCursorPos -// Parameters: -// - cCurrCursorPos - The initial cursor location -// Return value: -// - -COORD SCREEN_INFORMATION::GetForwardTab(const COORD cCurrCursorPos) const noexcept -{ - COORD cNewCursorPos = cCurrCursorPos; - SHORT sWidth = GetBufferSize().RightInclusive(); - if (_tabStops.empty() || cCurrCursorPos.X >= _tabStops.back()) - { - cNewCursorPos.X = sWidth; - } - else - { - // search for next tab stop - for (auto it = _tabStops.cbegin(); it != _tabStops.cend(); ++it) - { - if (*it > cCurrCursorPos.X) - { - // make sure we don't exceed the width of the buffer - cNewCursorPos.X = std::min(*it, sWidth); - break; - } - } - } - return cNewCursorPos; -} - -// Routine Description: -// - Places the location that a backwards tab would take cCurrCursorPos to into pcNewCursorPos -// Parameters: -// - cCurrCursorPos - The initial cursor location -// Return value: -// - -COORD SCREEN_INFORMATION::GetReverseTab(const COORD cCurrCursorPos) const noexcept -{ - COORD cNewCursorPos = cCurrCursorPos; - // if we're at 0, or there are NO tabs, or the first tab is farther right than where we are - if (cCurrCursorPos.X == 0 || _tabStops.empty() || _tabStops.front() >= cCurrCursorPos.X) - { - cNewCursorPos.X = 0; - } - else - { - for (auto it = _tabStops.crbegin(); it != _tabStops.crend(); ++it) - { - if (*it < cCurrCursorPos.X) - { - cNewCursorPos.X = *it; - break; - } - } - } - return cNewCursorPos; -} - -// Routine Description: -// - Returns true if any VT-style tab stops have been set (with AddTabStop) -// Parameters: -// -// Return value: -// - true if any VT-style tab stops have been set -bool SCREEN_INFORMATION::AreTabsSet() const noexcept -{ - return !_tabStops.empty(); -} - -// Routine Description: -// - adds default tab stops for vt mode -void SCREEN_INFORMATION::SetDefaultVtTabStops() -{ - _tabStops.clear(); - const int width = GetBufferSize().RightInclusive(); - FAIL_FAST_IF(width < 0); - for (int pos = 0; pos <= width; pos += TAB_SIZE) - { - _tabStops.push_back(gsl::narrow(pos)); - } - if (_tabStops.back() != width) - { - _tabStops.push_back(gsl::narrow(width)); - } -} - // Routine Description: // - Returns the value of the attributes // Parameters: diff --git a/src/host/screenInfo.hpp b/src/host/screenInfo.hpp index 4750aa4857c..0f6f6f958b3 100644 --- a/src/host/screenInfo.hpp +++ b/src/host/screenInfo.hpp @@ -213,14 +213,6 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console SCREEN_INFORMATION& GetActiveBuffer(); const SCREEN_INFORMATION& GetActiveBuffer() const; - void AddTabStop(const SHORT sColumn); - void ClearTabStops() noexcept; - void ClearTabStop(const SHORT sColumn) noexcept; - COORD GetForwardTab(const COORD cCurrCursorPos) const noexcept; - COORD GetReverseTab(const COORD cCurrCursorPos) const noexcept; - bool AreTabsSet() const noexcept; - void SetDefaultVtTabStops(); - TextAttribute GetAttributes() const; const TextAttribute* const GetPopupAttributes() const; @@ -296,8 +288,6 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console RECT _rcAltSavedClientOld; bool _fAltWindowChanged; - std::list _tabStops; - TextAttribute _PopupAttributes; FontInfo _currentFont; diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index 956dfc0af8c..eb475258ce0 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -69,6 +69,9 @@ class ScreenBufferTests // 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)); + // Make sure the viewport always starts off at the default size. + auto defaultSize = COORD{ CommonState::s_csWindowWidth, CommonState::s_csWindowHeight }; + currentBuffer.SetViewport(Viewport::FromDimensions(defaultSize), true); VERIFY_ARE_EQUAL(COORD({ 0, 0 }), currentBuffer.GetTextBuffer().GetCursor().GetPosition()); return true; @@ -89,9 +92,9 @@ class ScreenBufferTests TEST_METHOD(TestReverseLineFeed); - TEST_METHOD(TestAddTabStop); + TEST_METHOD(TestResetClearTabStops); - TEST_METHOD(TestClearTabStops); + TEST_METHOD(TestAddTabStop); TEST_METHOD(TestClearTabStop); @@ -99,9 +102,7 @@ class ScreenBufferTests TEST_METHOD(TestGetReverseTab); - TEST_METHOD(TestAreTabsSet); - - TEST_METHOD(TestAltBufferDefaultTabStops); + TEST_METHOD(TestAltBufferTabStops); TEST_METHOD(EraseAllTests); @@ -194,8 +195,6 @@ class ScreenBufferTests TEST_METHOD(ClearAlternateBuffer); - TEST_METHOD(InitializeTabStopsInVTMode); - TEST_METHOD(TestExtendedTextAttributes); TEST_METHOD(TestExtendedTextAttributesWithColors); @@ -417,147 +416,229 @@ void ScreenBufferTests::TestReverseLineFeed() VERIFY_ARE_EQUAL(c.Y, 6); } +void _SetTabStops(SCREEN_INFORMATION& screenInfo, std::list columns, bool replace) +{ + auto& stateMachine = screenInfo.GetStateMachine(); + auto& cursor = screenInfo.GetTextBuffer().GetCursor(); + const auto clearTabStops = L"\033[3g"; + const auto addTabStop = L"\033H"; + + if (replace) + { + stateMachine.ProcessString(clearTabStops); + } + + for (auto column : columns) + { + cursor.SetXPosition(column); + stateMachine.ProcessString(addTabStop); + } +} + +std::list _GetTabStops(SCREEN_INFORMATION& screenInfo) +{ + std::list columns; + + const auto lastColumn = screenInfo.GetBufferSize().RightInclusive(); + auto& stateMachine = screenInfo.GetStateMachine(); + auto& cursor = screenInfo.GetTextBuffer().GetCursor(); + + cursor.SetPosition({ 0, 0 }); + for (;;) + { + stateMachine.ProcessCharacter(L'\t'); + auto column = cursor.GetPosition().X; + if (column >= lastColumn) + { + break; + } + columns.push_back(column); + } + + return columns; +} + +void ScreenBufferTests::TestResetClearTabStops() +{ + // Reset the screen buffer to test the defaults. + m_state->CleanupGlobalScreenBuffer(); + m_state->PrepareGlobalScreenBuffer(); + + CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + SCREEN_INFORMATION& screenInfo = gci.GetActiveOutputBuffer(); + auto& stateMachine = screenInfo.GetStateMachine(); + + const auto clearTabStops = L"\033[3g"; + const auto resetToInitialState = L"\033c"; + + Log::Comment(L"Default tabs every 8 columns."); + std::list expectedStops{ 8, 16, 24, 32, 40, 48, 56, 64, 72 }; + VERIFY_ARE_EQUAL(expectedStops, _GetTabStops(screenInfo)); + + Log::Comment(L"Clear all tabs."); + stateMachine.ProcessString(clearTabStops); + expectedStops = {}; + VERIFY_ARE_EQUAL(expectedStops, _GetTabStops(screenInfo)); + + Log::Comment(L"RIS resets tabs to defaults."); + stateMachine.ProcessString(resetToInitialState); + expectedStops = { 8, 16, 24, 32, 40, 48, 56, 64, 72 }; + VERIFY_ARE_EQUAL(expectedStops, _GetTabStops(screenInfo)); +} + void ScreenBufferTests::TestAddTabStop() { CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); SCREEN_INFORMATION& screenInfo = gci.GetActiveOutputBuffer(); - screenInfo.ClearTabStops(); - auto scopeExit = wil::scope_exit([&]() { screenInfo.ClearTabStops(); }); + auto& stateMachine = screenInfo.GetStateMachine(); + auto& cursor = screenInfo.GetTextBuffer().GetCursor(); + + const auto clearTabStops = L"\033[3g"; + const auto addTabStop = L"\033H"; + + Log::Comment(L"Clear all tabs."); + stateMachine.ProcessString(clearTabStops); + std::list expectedStops{}; + VERIFY_ARE_EQUAL(expectedStops, _GetTabStops(screenInfo)); - std::list expectedStops{ 12 }; Log::Comment(L"Add tab to empty list."); - screenInfo.AddTabStop(12); - VERIFY_ARE_EQUAL(expectedStops, screenInfo._tabStops); + cursor.SetXPosition(12); + stateMachine.ProcessString(addTabStop); + expectedStops.push_back(12); + VERIFY_ARE_EQUAL(expectedStops, _GetTabStops(screenInfo)); Log::Comment(L"Add tab to head of existing list."); - screenInfo.AddTabStop(4); + cursor.SetXPosition(4); + stateMachine.ProcessString(addTabStop); expectedStops.push_front(4); - VERIFY_ARE_EQUAL(expectedStops, screenInfo._tabStops); + VERIFY_ARE_EQUAL(expectedStops, _GetTabStops(screenInfo)); Log::Comment(L"Add tab to tail of existing list."); - screenInfo.AddTabStop(30); + cursor.SetXPosition(30); + stateMachine.ProcessString(addTabStop); expectedStops.push_back(30); - VERIFY_ARE_EQUAL(expectedStops, screenInfo._tabStops); + VERIFY_ARE_EQUAL(expectedStops, _GetTabStops(screenInfo)); Log::Comment(L"Add tab to middle of existing list."); - screenInfo.AddTabStop(24); + cursor.SetXPosition(24); + stateMachine.ProcessString(addTabStop); expectedStops.push_back(24); expectedStops.sort(); - VERIFY_ARE_EQUAL(expectedStops, screenInfo._tabStops); + VERIFY_ARE_EQUAL(expectedStops, _GetTabStops(screenInfo)); Log::Comment(L"Add tab that duplicates an item in the existing list."); - screenInfo.AddTabStop(24); - VERIFY_ARE_EQUAL(expectedStops, screenInfo._tabStops); + cursor.SetXPosition(24); + stateMachine.ProcessString(addTabStop); + VERIFY_ARE_EQUAL(expectedStops, _GetTabStops(screenInfo)); } -void ScreenBufferTests::TestClearTabStops() +void ScreenBufferTests::TestClearTabStop() { CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); SCREEN_INFORMATION& screenInfo = gci.GetActiveOutputBuffer(); + auto& stateMachine = screenInfo.GetStateMachine(); + auto& cursor = screenInfo.GetTextBuffer().GetCursor(); - Log::Comment(L"Clear nonexistent tab stops."); - { - screenInfo.ClearTabStops(); - VERIFY_IS_TRUE(screenInfo._tabStops.empty()); - } + const auto clearTabStops = L"\033[3g"; + const auto clearTabStop = L"\033[0g"; + const auto addTabStop = L"\033H"; - Log::Comment(L"Clear handful of tab stops."); + Log::Comment(L"Start with all tabs cleared."); { - for (auto x : { 3, 6, 13, 2, 25 }) - { - screenInfo.AddTabStop(gsl::narrow(x)); - } - VERIFY_IS_FALSE(screenInfo._tabStops.empty()); - screenInfo.ClearTabStops(); - VERIFY_IS_TRUE(screenInfo._tabStops.empty()); - } -} + stateMachine.ProcessString(clearTabStops); -void ScreenBufferTests::TestClearTabStop() -{ - CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - SCREEN_INFORMATION& screenInfo = gci.GetActiveOutputBuffer(); + VERIFY_IS_TRUE(_GetTabStops(screenInfo).empty()); + } Log::Comment(L"Try to clear nonexistent list."); { - screenInfo.ClearTabStop(0); + cursor.SetXPosition(0); + stateMachine.ProcessString(clearTabStop); - VERIFY_IS_TRUE(screenInfo._tabStops.empty(), L"List should remain empty"); + VERIFY_IS_TRUE(_GetTabStops(screenInfo).empty(), L"List should remain empty"); } Log::Comment(L"Allocate 1 list item and clear it."); { - screenInfo._tabStops.push_back(0); - screenInfo.ClearTabStop(0); + cursor.SetXPosition(0); + stateMachine.ProcessString(addTabStop); + stateMachine.ProcessString(clearTabStop); - VERIFY_IS_TRUE(screenInfo._tabStops.empty()); + VERIFY_IS_TRUE(_GetTabStops(screenInfo).empty()); } Log::Comment(L"Allocate 1 list item and clear nonexistent."); { - screenInfo._tabStops.push_back(0); + cursor.SetXPosition(1); + stateMachine.ProcessString(addTabStop); Log::Comment(L"Free greater"); - screenInfo.ClearTabStop(1); - VERIFY_IS_FALSE(screenInfo._tabStops.empty()); + cursor.SetXPosition(2); + stateMachine.ProcessString(clearTabStop); + VERIFY_IS_FALSE(_GetTabStops(screenInfo).empty()); Log::Comment(L"Free less than"); - screenInfo.ClearTabStop(-1); - VERIFY_IS_FALSE(screenInfo._tabStops.empty()); + cursor.SetXPosition(0); + stateMachine.ProcessString(clearTabStop); + VERIFY_IS_FALSE(_GetTabStops(screenInfo).empty()); // clear all tab stops - screenInfo._tabStops.clear(); + stateMachine.ProcessString(clearTabStops); } Log::Comment(L"Allocate many (5) list items and clear head."); { std::list inputData = { 3, 5, 6, 10, 15, 17 }; - screenInfo._tabStops = inputData; - screenInfo.ClearTabStop(inputData.front()); + _SetTabStops(screenInfo, inputData, false); + cursor.SetXPosition(inputData.front()); + stateMachine.ProcessString(clearTabStop); inputData.pop_front(); - VERIFY_ARE_EQUAL(inputData, screenInfo._tabStops); + VERIFY_ARE_EQUAL(inputData, _GetTabStops(screenInfo)); // clear all tab stops - screenInfo._tabStops.clear(); + stateMachine.ProcessString(clearTabStops); } Log::Comment(L"Allocate many (5) list items and clear middle."); { std::list inputData = { 3, 5, 6, 10, 15, 17 }; - screenInfo._tabStops = inputData; - screenInfo.ClearTabStop(*std::next(inputData.begin())); + _SetTabStops(screenInfo, inputData, false); + cursor.SetXPosition(*std::next(inputData.begin())); + stateMachine.ProcessString(clearTabStop); inputData.erase(std::next(inputData.begin())); - VERIFY_ARE_EQUAL(inputData, screenInfo._tabStops); + VERIFY_ARE_EQUAL(inputData, _GetTabStops(screenInfo)); // clear all tab stops - screenInfo._tabStops.clear(); + stateMachine.ProcessString(clearTabStops); } Log::Comment(L"Allocate many (5) list items and clear tail."); { std::list inputData = { 3, 5, 6, 10, 15, 17 }; - screenInfo._tabStops = inputData; - screenInfo.ClearTabStop(inputData.back()); + _SetTabStops(screenInfo, inputData, false); + cursor.SetXPosition(inputData.back()); + stateMachine.ProcessString(clearTabStop); inputData.pop_back(); - VERIFY_ARE_EQUAL(inputData, screenInfo._tabStops); + VERIFY_ARE_EQUAL(inputData, _GetTabStops(screenInfo)); // clear all tab stops - screenInfo._tabStops.clear(); + stateMachine.ProcessString(clearTabStops); } Log::Comment(L"Allocate many (5) list items and clear nonexistent item."); { std::list inputData = { 3, 5, 6, 10, 15, 17 }; - screenInfo._tabStops = inputData; - screenInfo.ClearTabStop(9000); + _SetTabStops(screenInfo, inputData, false); + cursor.SetXPosition(0); + stateMachine.ProcessString(clearTabStop); - VERIFY_ARE_EQUAL(inputData, screenInfo._tabStops); + VERIFY_ARE_EQUAL(inputData, _GetTabStops(screenInfo)); // clear all tab stops - screenInfo._tabStops.clear(); + stateMachine.ProcessString(clearTabStops); } } @@ -565,23 +646,25 @@ void ScreenBufferTests::TestGetForwardTab() { CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); SCREEN_INFORMATION& si = gci.GetActiveOutputBuffer(); + auto& stateMachine = si.GetStateMachine(); + auto& cursor = si.GetTextBuffer().GetCursor(); + + const auto nextForwardTab = L"\033[I"; std::list inputData = { 3, 5, 6, 10, 15, 17 }; - si._tabStops = inputData; + _SetTabStops(si, inputData, true); const COORD coordScreenBufferSize = si.GetBufferSize().Dimensions(); - COORD coordCursor; - coordCursor.Y = coordScreenBufferSize.Y / 2; // in the middle of the buffer, it doesn't make a difference. Log::Comment(L"Find next tab from before front."); { - coordCursor.X = 0; + cursor.SetXPosition(0); - COORD coordCursorExpected; - coordCursorExpected = coordCursor; + COORD coordCursorExpected = cursor.GetPosition(); coordCursorExpected.X = inputData.front(); - COORD const coordCursorResult = si.GetForwardTab(coordCursor); + stateMachine.ProcessString(nextForwardTab); + COORD const coordCursorResult = cursor.GetPosition(); VERIFY_ARE_EQUAL(coordCursorExpected, coordCursorResult, L"Cursor advanced to first tab stop from sample list."); @@ -589,13 +672,13 @@ void ScreenBufferTests::TestGetForwardTab() Log::Comment(L"Find next tab from in the middle."); { - coordCursor.X = 6; + cursor.SetXPosition(6); - COORD coordCursorExpected; - coordCursorExpected = coordCursor; + COORD coordCursorExpected = cursor.GetPosition(); coordCursorExpected.X = *std::next(inputData.begin(), 3); - COORD const coordCursorResult = si.GetForwardTab(coordCursor); + stateMachine.ProcessString(nextForwardTab); + COORD const coordCursorResult = cursor.GetPosition(); VERIFY_ARE_EQUAL(coordCursorExpected, coordCursorResult, L"Cursor advanced to middle tab stop from sample list."); @@ -603,13 +686,13 @@ void ScreenBufferTests::TestGetForwardTab() Log::Comment(L"Find next tab from end."); { - coordCursor.X = 30; + cursor.SetXPosition(30); - COORD coordCursorExpected; - coordCursorExpected = coordCursor; + COORD coordCursorExpected = cursor.GetPosition(); coordCursorExpected.X = coordScreenBufferSize.X - 1; - COORD const coordCursorResult = si.GetForwardTab(coordCursor); + stateMachine.ProcessString(nextForwardTab); + COORD const coordCursorResult = cursor.GetPosition(); VERIFY_ARE_EQUAL(coordCursorExpected, coordCursorResult, L"Cursor advanced to end of screen buffer."); @@ -617,41 +700,39 @@ void ScreenBufferTests::TestGetForwardTab() Log::Comment(L"Find next tab from rightmost column."); { - coordCursor.X = coordScreenBufferSize.X - 1; + cursor.SetXPosition(coordScreenBufferSize.X - 1); - COORD coordCursorExpected; - coordCursorExpected = coordCursor; + COORD coordCursorExpected = cursor.GetPosition(); - COORD const coordCursorResult = si.GetForwardTab(coordCursor); + stateMachine.ProcessString(nextForwardTab); + COORD const coordCursorResult = cursor.GetPosition(); VERIFY_ARE_EQUAL(coordCursorExpected, coordCursorResult, L"Cursor remains in rightmost column."); } - - si._tabStops.clear(); } void ScreenBufferTests::TestGetReverseTab() { CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); SCREEN_INFORMATION& si = gci.GetActiveOutputBuffer(); + auto& stateMachine = si.GetStateMachine(); + auto& cursor = si.GetTextBuffer().GetCursor(); - std::list inputData = { 3, 5, 6, 10, 15, 17 }; - si._tabStops = inputData; + const auto nextReverseTab = L"\033[Z"; - COORD coordCursor; - // in the middle of the buffer, it doesn't make a difference. - coordCursor.Y = si.GetBufferSize().Height() / 2; + std::list inputData = { 3, 5, 6, 10, 15, 17 }; + _SetTabStops(si, inputData, true); Log::Comment(L"Find previous tab from before front."); { - coordCursor.X = 1; + cursor.SetXPosition(1); - COORD coordCursorExpected; - coordCursorExpected = coordCursor; + COORD coordCursorExpected = cursor.GetPosition(); coordCursorExpected.X = 0; - COORD const coordCursorResult = si.GetReverseTab(coordCursor); + stateMachine.ProcessString(nextReverseTab); + COORD const coordCursorResult = cursor.GetPosition(); VERIFY_ARE_EQUAL(coordCursorExpected, coordCursorResult, L"Cursor adjusted to beginning of the buffer when it started before sample list."); @@ -659,13 +740,13 @@ void ScreenBufferTests::TestGetReverseTab() Log::Comment(L"Find previous tab from in the middle."); { - coordCursor.X = 6; + cursor.SetXPosition(6); - COORD coordCursorExpected; - coordCursorExpected = coordCursor; + COORD coordCursorExpected = cursor.GetPosition(); coordCursorExpected.X = *std::next(inputData.begin()); - COORD const coordCursorResult = si.GetReverseTab(coordCursor); + stateMachine.ProcessString(nextReverseTab); + COORD const coordCursorResult = cursor.GetPosition(); VERIFY_ARE_EQUAL(coordCursorExpected, coordCursorResult, L"Cursor adjusted back one tab spot from middle of sample list."); @@ -673,34 +754,20 @@ void ScreenBufferTests::TestGetReverseTab() Log::Comment(L"Find next tab from end."); { - coordCursor.X = 30; + cursor.SetXPosition(30); - COORD coordCursorExpected; - coordCursorExpected = coordCursor; + COORD coordCursorExpected = cursor.GetPosition(); coordCursorExpected.X = inputData.back(); - COORD const coordCursorResult = si.GetReverseTab(coordCursor); + stateMachine.ProcessString(nextReverseTab); + COORD const coordCursorResult = cursor.GetPosition(); VERIFY_ARE_EQUAL(coordCursorExpected, coordCursorResult, L"Cursor adjusted to last item in the sample list from position beyond end."); } - - si._tabStops.clear(); -} - -void ScreenBufferTests::TestAreTabsSet() -{ - CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - SCREEN_INFORMATION& si = gci.GetActiveOutputBuffer(); - - si._tabStops.clear(); - VERIFY_IS_FALSE(si.AreTabsSet()); - - si.AddTabStop(1); - VERIFY_IS_TRUE(si.AreTabsSet()); } -void ScreenBufferTests::TestAltBufferDefaultTabStops() +void ScreenBufferTests::TestAltBufferTabStops() { CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); gci.LockConsole(); // Lock must be taken to swap buffers. @@ -711,8 +778,10 @@ void ScreenBufferTests::TestAltBufferDefaultTabStops() WI_SetFlag(mainBuffer.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING); VERIFY_IS_TRUE(WI_IsFlagSet(mainBuffer.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING)); - mainBuffer.SetDefaultVtTabStops(); - VERIFY_IS_TRUE(mainBuffer.AreTabsSet()); + Log::Comment(L"Add an initial set of tab in the main buffer."); + std::list expectedStops = { 3, 5, 6, 10, 15, 17 }; + _SetTabStops(mainBuffer, expectedStops, true); + VERIFY_ARE_EQUAL(expectedStops, _GetTabStops(mainBuffer)); VERIFY_SUCCEEDED(mainBuffer.UseAlternateScreenBuffer()); SCREEN_INFORMATION& altBuffer = gci.GetActiveOutputBuffer(); @@ -724,39 +793,19 @@ void ScreenBufferTests::TestAltBufferDefaultTabStops() WI_SetFlag(altBuffer.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING); VERIFY_IS_TRUE(WI_IsFlagSet(altBuffer.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING)); - VERIFY_IS_TRUE(altBuffer.AreTabsSet()); - VERIFY_IS_TRUE(altBuffer._tabStops.size() > 3); - const COORD origin{ 0, 0 }; - auto& cursor = altBuffer.GetTextBuffer().GetCursor(); - cursor.SetPosition(origin); - auto& stateMachine = altBuffer.GetStateMachine(); + Log::Comment(L"Make sure the tabs are still set in the alt buffer."); + VERIFY_ARE_EQUAL(expectedStops, _GetTabStops(altBuffer)); - Log::Comment(NoThrowString().Format( - L"Tab a few times - make sure the cursor is where we expect.")); - - stateMachine.ProcessString(L"\t"); - COORD expected{ 8, 0 }; - VERIFY_ARE_EQUAL(expected, cursor.GetPosition()); - - stateMachine.ProcessString(L"\t"); - expected = { 16, 0 }; - VERIFY_ARE_EQUAL(expected, cursor.GetPosition()); - - stateMachine.ProcessString(L"\n"); - expected = { 0, 1 }; - VERIFY_ARE_EQUAL(expected, cursor.GetPosition()); - - altBuffer.ClearTabStops(); - VERIFY_IS_FALSE(altBuffer.AreTabsSet()); - stateMachine.ProcessString(L"\t"); - expected = { altBuffer.GetBufferSize().Width() - 1, 1 }; - - VERIFY_ARE_EQUAL(expected, cursor.GetPosition()); + Log::Comment(L"Add a new set of tabs in the alt buffer."); + expectedStops = { 4, 8, 12, 16 }; + _SetTabStops(altBuffer, expectedStops, true); + VERIFY_ARE_EQUAL(expectedStops, _GetTabStops(altBuffer)); + Log::Comment(L"Make sure the tabs are still set in the main buffer."); useMain.release(); altBuffer.UseMainScreenBuffer(); - VERIFY_IS_TRUE(mainBuffer.AreTabsSet()); + VERIFY_ARE_EQUAL(expectedStops, _GetTabStops(mainBuffer)); } void ScreenBufferTests::EraseAllTests() @@ -4938,34 +4987,6 @@ void ScreenBufferTests::ClearAlternateBuffer() VerifyText(siMain.GetTextBuffer()); } -void ScreenBufferTests::InitializeTabStopsInVTMode() -{ - // This is a test for microsoft/terminal#411. Refer to that issue for more - // context. - - // Run this test in isolation - Let's not pollute the VT level for other - // tests, or go blowing away other test's buffers - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method") - END_TEST_METHOD_PROPERTIES() - - auto& g = ServiceLocator::LocateGlobals(); - auto& gci = g.getConsoleInformation(); - - VERIFY_IS_FALSE(gci.GetActiveOutputBuffer().AreTabsSet()); - - // Enable VT mode before we construct the buffer. This emulates setting the - // VirtualTerminalLevel reg key before launching the console. - gci.SetVirtTermLevel(1); - - // Clean up the old buffer, and re-create it. This new buffer will be - // created as if the VT mode was always on. - m_state->CleanupGlobalScreenBuffer(); - m_state->PrepareGlobalScreenBuffer(); - - VERIFY_IS_TRUE(gci.GetActiveOutputBuffer().AreTabsSet()); -} - void ScreenBufferTests::TestExtendedTextAttributes() { // This is a test for microsoft/terminal#2554. Refer to that issue for more diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index de859d96634..6c8bfba7cb0 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -1362,7 +1362,18 @@ bool AdaptDispatch::UseMainScreenBuffer() // True if handled successfully. False otherwise. bool AdaptDispatch::HorizontalTabSet() { - return _pConApi->PrivateHorizontalTabSet(); + CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 }; + csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); + const bool success = _pConApi->GetConsoleScreenBufferInfoEx(csbiex); + if (success) + { + const auto width = csbiex.dwSize.X; + const auto column = csbiex.dwCursorPosition.X; + + _InitTabStopsForWidth(width); + _tabStopColumns.at(column) = true; + } + return success; } //Routine Description: @@ -1376,7 +1387,29 @@ bool AdaptDispatch::HorizontalTabSet() // True if handled successfully. False otherwise. bool AdaptDispatch::ForwardTab(const size_t numTabs) { - return _pConApi->PrivateForwardTab(numTabs); + CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 }; + csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); + bool success = _pConApi->GetConsoleScreenBufferInfoEx(csbiex); + if (success) + { + const auto width = csbiex.dwSize.X; + const auto row = csbiex.dwCursorPosition.Y; + auto column = csbiex.dwCursorPosition.X; + auto tabsPerformed = 0u; + + _InitTabStopsForWidth(width); + while (column + 1 < width && tabsPerformed < numTabs) + { + column++; + if (til::at(_tabStopColumns, column)) + { + tabsPerformed++; + } + } + + success = _pConApi->SetConsoleCursorPosition({ column, row }); + } + return success; } //Routine Description: @@ -1388,7 +1421,29 @@ bool AdaptDispatch::ForwardTab(const size_t numTabs) // True if handled successfully. False otherwise. bool AdaptDispatch::BackwardsTab(const size_t numTabs) { - return _pConApi->PrivateBackwardsTab(numTabs); + CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 }; + csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); + bool success = _pConApi->GetConsoleScreenBufferInfoEx(csbiex); + if (success) + { + const auto width = csbiex.dwSize.X; + const auto row = csbiex.dwCursorPosition.Y; + auto column = csbiex.dwCursorPosition.X; + auto tabsPerformed = 0u; + + _InitTabStopsForWidth(width); + while (column > 0 && tabsPerformed < numTabs) + { + column--; + if (til::at(_tabStopColumns, column)) + { + tabsPerformed++; + } + } + + success = _pConApi->SetConsoleCursorPosition({ column, row }); + } + return success; } //Routine Description: @@ -1405,15 +1460,91 @@ bool AdaptDispatch::TabClear(const size_t clearType) switch (clearType) { case DispatchTypes::TabClearType::ClearCurrentColumn: - success = _pConApi->PrivateTabClear(false); + success = _ClearSingleTabStop(); break; case DispatchTypes::TabClearType::ClearAllColumns: - success = _pConApi->PrivateTabClear(true); + success = _ClearAllTabStops(); break; } return success; } +// Routine Description: +// - Clears the tab stop in the cursor's current column, if there is one. +// Arguments: +// - +// Return value: +// - True if handled successfully. False otherwise. +bool AdaptDispatch::_ClearSingleTabStop() +{ + CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 }; + csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); + const bool success = _pConApi->GetConsoleScreenBufferInfoEx(csbiex); + if (success) + { + const auto width = csbiex.dwSize.X; + const auto column = csbiex.dwCursorPosition.X; + + _InitTabStopsForWidth(width); + _tabStopColumns.at(column) = false; + } + return success; +} + +// Routine Description: +// - Clears all tab stops and resets the _initDefaultTabStops flag to indicate +// that they shouldn't be reinitialized at the default positions. +// Arguments: +// - +// Return value: +// - True if handled successfully. False otherwise. +bool AdaptDispatch::_ClearAllTabStops() noexcept +{ + _tabStopColumns.clear(); + _initDefaultTabStops = false; + return true; +} + +// Routine Description: +// - Clears all tab stops and sets the _initDefaultTabStops flag to indicate +// that the default positions should be reinitialized when needed. +// Arguments: +// - +// Return value: +// - +void AdaptDispatch::_ResetTabStops() noexcept +{ + _tabStopColumns.clear(); + _initDefaultTabStops = true; +} + +// Routine Description: +// - Resizes the _tabStopColumns table so it's large enough to support the +// current screen width, initializing tab stops every 8 columns in the +// newly allocated space, iff the _initDefaultTabStops flag is set. +// Arguments: +// - width - the width of the screen buffer that we need to accomodate +// Return value: +// - +void AdaptDispatch::_InitTabStopsForWidth(const size_t width) +{ + const auto initialWidth = _tabStopColumns.size(); + if (width > initialWidth) + { + _tabStopColumns.resize(width); + if (_initDefaultTabStops) + { + for (auto column = 8u; column < _tabStopColumns.size(); column += 8) + { + if (column >= initialWidth) + { + til::at(_tabStopColumns, column) = true; + } + } + } + } +} + //Routine Description: // Designate Charset - Sets the active charset to be the one mapped to wch. // See DispatchTypes::VTCharacterSets for a list of supported charsets. @@ -1559,7 +1690,7 @@ bool AdaptDispatch::HardReset() } // delete all current tab stops and reapply - _pConApi->PrivateSetDefaultTabStops(); + _ResetTabStops(); // GH#2715 - If all this succeeded, but we're in a conpty, return `false` to // make the state machine propagate this RIS sequence to the connected diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index aa67e373180..259c14940f5 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -152,6 +152,14 @@ namespace Microsoft::Console::VirtualTerminal bool _PrivateModeParamsHelper(const DispatchTypes::PrivateModeParams param, const bool enable); bool _DoDECCOLMHelper(const size_t columns); + bool _ClearSingleTabStop(); + bool _ClearAllTabStops() noexcept; + void _ResetTabStops() noexcept; + void _InitTabStopsForWidth(const size_t width); + + std::vector _tabStopColumns; + bool _initDefaultTabStops = true; + std::unique_ptr _pConApi; std::unique_ptr _pDefaults; TerminalOutput _termOutput; diff --git a/src/terminal/adapter/conGetSet.hpp b/src/terminal/adapter/conGetSet.hpp index 6516db3881a..229bb74bf59 100644 --- a/src/terminal/adapter/conGetSet.hpp +++ b/src/terminal/adapter/conGetSet.hpp @@ -74,11 +74,6 @@ namespace Microsoft::Console::VirtualTerminal virtual bool SetConsoleTitleW(const std::wstring_view title) = 0; virtual bool PrivateUseAlternateScreenBuffer() = 0; virtual bool PrivateUseMainScreenBuffer() = 0; - virtual bool PrivateHorizontalTabSet() = 0; - virtual bool PrivateForwardTab(const size_t numTabs) = 0; - virtual bool PrivateBackwardsTab(const size_t numTabs) = 0; - virtual bool PrivateTabClear(const bool clearAll) = 0; - virtual bool PrivateSetDefaultTabStops() = 0; virtual bool PrivateEnableVT200MouseMode(const bool enabled) = 0; virtual bool PrivateEnableUTF8ExtendedMouseMode(const bool enabled) = 0; diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index 761cdfe53b1..a513dd41908 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -445,49 +445,6 @@ class TestGetSet final : public ConGetSet return true; } - bool PrivateHorizontalTabSet() override - { - Log::Comment(L"PrivateHorizontalTabSet MOCK called..."); - // We made it through the adapter, woo! Return true. - return TRUE; - } - - bool PrivateForwardTab(const size_t numTabs) override - { - Log::Comment(L"PrivateForwardTab MOCK called..."); - if (_privateForwardTabResult) - { - VERIFY_ARE_EQUAL(_expectedNumTabs, numTabs); - } - return TRUE; - } - - bool PrivateBackwardsTab(const size_t numTabs) override - { - Log::Comment(L"PrivateBackwardsTab MOCK called..."); - if (_privateBackwardsTabResult) - { - VERIFY_ARE_EQUAL(_expectedNumTabs, numTabs); - } - return TRUE; - } - - bool PrivateTabClear(const bool clearAll) override - { - Log::Comment(L"PrivateTabClear MOCK called..."); - if (_privateTabClearResult) - { - VERIFY_ARE_EQUAL(_expectedClearAll, clearAll); - } - return TRUE; - } - - bool PrivateSetDefaultTabStops() override - { - Log::Comment(L"PrivateSetDefaultTabStops MOCK called..."); - return TRUE; - } - bool PrivateEnableVT200MouseMode(const bool enabled) override { Log::Comment(L"PrivateEnableVT200MouseMode MOCK called..."); @@ -924,12 +881,6 @@ class TestGetSet final : public ConGetSet bool _setConsoleTitleWResult = false; std::wstring_view _expectedWindowTitle{}; - bool _privateHorizontalTabSetResult = false; - bool _privateForwardTabResult = false; - bool _privateBackwardsTabResult = false; - size_t _expectedNumTabs = 0; - bool _privateTabClearResult = false; - bool _expectedClearAll = false; bool _expectedMouseEnabled = false; bool _expectedAlternateScrollEnabled = false; bool _privateEnableVT200MouseModeResult = false; @@ -2051,29 +2002,6 @@ class AdapterTest VERIFY_IS_TRUE(_pDispatch.get()->LineFeed(DispatchTypes::LineFeedType::DependsOnMode)); } - TEST_METHOD(TabSetClearTests) - { - Log::Comment(L"Starting test..."); - - _testGetSet->_privateHorizontalTabSetResult = TRUE; - VERIFY_IS_TRUE(_pDispatch.get()->HorizontalTabSet()); - - _testGetSet->_expectedNumTabs = 16; - - _testGetSet->_privateForwardTabResult = TRUE; - VERIFY_IS_TRUE(_pDispatch.get()->ForwardTab(16)); - - _testGetSet->_privateBackwardsTabResult = TRUE; - VERIFY_IS_TRUE(_pDispatch.get()->BackwardsTab(16)); - - _testGetSet->_privateTabClearResult = TRUE; - _testGetSet->_expectedClearAll = true; - VERIFY_IS_TRUE(_pDispatch.get()->TabClear(DispatchTypes::TabClearType::ClearAllColumns)); - - _testGetSet->_expectedClearAll = false; - VERIFY_IS_TRUE(_pDispatch.get()->TabClear(DispatchTypes::TabClearType::ClearCurrentColumn)); - } - TEST_METHOD(SetConsoleTitleTest) { Log::Comment(L"Starting test...");