Repository: microsoft/terminal (language: cpp)

Issue:
Add support for resetting tab stops (DECST8C)
# Description of the new feature/enhancement

[`DECST8C`](https://vt100.net/docs/vt510-rm/DECST8C.html) is an escape sequence which resets the tab stops to their default values, i.e. every 8 columns. It's especially useful for a modern terminal because it's the only practical way to restore the default values over an "infinite" width. Without this you'd have to set the positions manually, which limits you to the current screen width (technically you could also perform a hard reset with `RIS`, but that's not something an application should be doing).

# Proposed technical implementation details (optional)

We actually already have a method in `AdaptDispatch` that performs a tab reset (it's used in the hard reset implementation). We just need to make it public, and hook it up to the relevant escape sequence in the state machine.

It's a little bit more complicated than I'm making out, because the escape sequence takes a parameter value, and `DECST8C` is only applicable for one specific value, but that's easy enough to handle.


Discussion / hints:

Fix patch:
diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt
index 6ae7a458582..93c3870c933 100644
--- a/.github/actions/spelling/expect/expect.txt
+++ b/.github/actions/spelling/expect/expect.txt
@@ -443,6 +443,7 @@ DECSLPP
 DECSLRM
 DECSMKR
 DECSR
+DECST
 DECSTBM
 DECSTGLT
 DECSTR
diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp
index 985fcadf343..d6687e19bd6 100644
--- a/src/host/ut_host/ScreenBufferTests.cpp
+++ b/src/host/ut_host/ScreenBufferTests.cpp
@@ -604,6 +604,16 @@ void ScreenBufferTests::TestResetClearTabStops()
     stateMachine.ProcessString(resetToInitialState);
     expectedStops = { 8, 16, 24, 32, 40, 48, 56, 64, 72 };
     VERIFY_ARE_EQUAL(expectedStops, _GetTabStops(screenInfo));
+
+    Log::Comment(L"DECST8C with 5 parameter resets tabs to defaults.");
+    stateMachine.ProcessString(clearTabStops);
+    stateMachine.ProcessString(L"\033[?5W");
+    VERIFY_ARE_EQUAL(expectedStops, _GetTabStops(screenInfo));
+
+    Log::Comment(L"DECST8C with omitted parameter resets tabs to defaults.");
+    stateMachine.ProcessString(clearTabStops);
+    stateMachine.ProcessString(L"\033[?W");
+    VERIFY_ARE_EQUAL(expectedStops, _GetTabStops(screenInfo));
 }
 
 void ScreenBufferTests::TestAddTabStop()
diff --git a/src/terminal/adapter/DispatchTypes.hpp b/src/terminal/adapter/DispatchTypes.hpp
index 612f82c093f..254bde75c21 100644
--- a/src/terminal/adapter/DispatchTypes.hpp
+++ b/src/terminal/adapter/DispatchTypes.hpp
@@ -558,6 +558,11 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
         ClearAllColumns = 3
     };
 
+    enum TabSetType : VTInt
+    {
+        SetEvery8Columns = 5
+    };
+
     enum WindowManipulationType : VTInt
     {
         Invalid = 0,
diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp
index 9221ea3cb4f..1b847d32ba0 100644
--- a/src/terminal/adapter/ITermDispatch.hpp
+++ b/src/terminal/adapter/ITermDispatch.hpp
@@ -68,6 +68,7 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch
     virtual bool ForwardTab(const VTInt numTabs) = 0; // CHT, HT
     virtual bool BackwardsTab(const VTInt numTabs) = 0; // CBT
     virtual bool TabClear(const DispatchTypes::TabClearType clearType) = 0; // TBC
+    virtual bool TabSet(const VTParameter setType) = 0; // DECST8C
     virtual bool SetColorTableEntry(const size_t tableIndex, const DWORD color) = 0; // OSCColorTable
     virtual bool SetDefaultForeground(const DWORD color) = 0; // OSCDefaultForeground
     virtual bool SetDefaultBackground(const DWORD color) = 0; // OSCDefaultBackground
diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp
index 31edacae21d..4f2dfc41387 100644
--- a/src/terminal/adapter/adaptDispatch.cpp
+++ b/src/terminal/adapter/adaptDispatch.cpp
@@ -2793,16 +2793,23 @@ void AdaptDispatch::_ClearAllTabStops() noexcept
 }
 
 // Routine Description:
-// - Clears all tab stops and sets the _initDefaultTabStops flag to indicate
+// - DECST8C - If the parameter is SetEvery8Columns or is omitted, then this
+//    clears all tab stops and sets the _initDefaultTabStops flag to indicate
 //    that the default positions should be reinitialized when needed.
 // Arguments:
-// - <none>
+// - setType - only SetEvery8Columns is supported
 // Return value:
-// - <none>
-void AdaptDispatch::_ResetTabStops() noexcept
+// - True if handled successfully. False otherwise.
+bool AdaptDispatch::TabSet(const VTParameter setType) noexcept
 {
-    _tabStopColumns.clear();
-    _initDefaultTabStops = true;
+    constexpr auto SetEvery8Columns = DispatchTypes::TabSetType::SetEvery8Columns;
+    if (setType.value_or(SetEvery8Columns) == SetEvery8Columns)
+    {
+        _tabStopColumns.clear();
+        _initDefaultTabStops = true;
+        return true;
+    }
+    return false;
 }
 
 // Routine Description:
@@ -3076,7 +3083,7 @@ bool AdaptDispatch::HardReset()
     _api.GetTextBuffer().GetCursor().SetBlinkingAllowed(true);
 
     // Delete all current tab stops and reapply
-    _ResetTabStops();
+    TabSet(DispatchTypes::TabSetType::SetEvery8Columns);
 
     // Clear the soft font in the renderer and delete the font buffer.
     _renderer.UpdateSoftFont({}, {}, false);
diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp
index 4b145f1b8ca..3154a390d36 100644
--- a/src/terminal/adapter/adaptDispatch.hpp
+++ b/src/terminal/adapter/adaptDispatch.hpp
@@ -105,6 +105,7 @@ namespace Microsoft::Console::VirtualTerminal
         bool ForwardTab(const VTInt numTabs) override; // CHT, HT
         bool BackwardsTab(const VTInt numTabs) override; // CBT
         bool TabClear(const DispatchTypes::TabClearType clearType) override; // TBC
+        bool TabSet(const VTParameter setType) noexcept override; // DECST8C
         bool DesignateCodingSystem(const VTID codingSystem) override; // DOCS
         bool Designate94Charset(const VTInt gsetNumber, const VTID charset) override; // SCS
         bool Designate96Charset(const VTInt gsetNumber, const VTID charset) override; // SCS
@@ -251,7 +252,6 @@ namespace Microsoft::Console::VirtualTerminal
 
         void _ClearSingleTabStop();
         void _ClearAllTabStops() noexcept;
-        void _ResetTabStops() noexcept;
         void _InitTabStopsForWidth(const VTInt width);
 
         StringHandler _RestoreColorTable();
@@ -271,7 +271,7 @@ namespace Microsoft::Console::VirtualTerminal
         StringHandler _CreateDrcsPassthroughHandler(const DispatchTypes::DrcsCharsetSize charsetSize);
         StringHandler _CreatePassthroughHandler();
 
-        std::vector<bool> _tabStopColumns;
+        std::vector<uint8_t> _tabStopColumns;
         bool _initDefaultTabStops = true;
 
         ITerminalApi& _api;
diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp
index 4f11ae8bb93..122b48820bb 100644
--- a/src/terminal/adapter/termDispatch.hpp
+++ b/src/terminal/adapter/termDispatch.hpp
@@ -61,6 +61,7 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons
     bool ForwardTab(const VTInt /*numTabs*/) override { return false; } // CHT, HT
     bool BackwardsTab(const VTInt /*numTabs*/) override { return false; } // CBT
     bool TabClear(const DispatchTypes::TabClearType /*clearType*/) override { return false; } // TBC
+    bool TabSet(const VTParameter /*setType*/) override { return false; } // DECST8C
     bool SetColorTableEntry(const size_t /*tableIndex*/, const DWORD /*color*/) override { return false; } // OSCColorTable
     bool SetDefaultForeground(const DWORD /*color*/) override { return false; } // OSCDefaultForeground
     bool SetDefaultBackground(const DWORD /*color*/) override { return false; } // OSCDefaultBackground
diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp
index 68a45916df4..58e02c351cb 100644
--- a/src/terminal/parser/OutputStateMachineEngine.cpp
+++ b/src/terminal/parser/OutputStateMachineEngine.cpp
@@ -566,6 +566,11 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete
             return _dispatch->TabClear(clearType);
         });
         break;
+    case CsiActionCodes::DECST8C_SetTabEvery8Columns:
+        success = parameters.for_each([&](const auto setType) {
+            return _dispatch->TabSet(setType);
+        });
+        break;
     case CsiActionCodes::ECH_EraseCharacters:
         success = _dispatch->EraseCharacters(parameters.at(0));
         break;
diff --git a/src/terminal/parser/OutputStateMachineEngine.hpp b/src/terminal/parser/OutputStateMachineEngine.hpp
index 8f3f72b9cb6..32105133bf3 100644
--- a/src/terminal/parser/OutputStateMachineEngine.hpp
+++ b/src/terminal/parser/OutputStateMachineEngine.hpp
@@ -121,6 +121,7 @@ namespace Microsoft::Console::VirtualTerminal
             DCH_DeleteCharacter = VTID("P"),
             SU_ScrollUp = VTID("S"),
             SD_ScrollDown = VTID("T"),
+            DECST8C_SetTabEvery8Columns = VTID("?W"),
             ECH_EraseCharacters = VTID("X"),
             CBT_CursorBackTab = VTID("Z"),
             HPA_HorizontalPositionAbsolute = VTID("`"),


Test patch: