Initial Implementation for tab stops in TerminalDispatch (#9597)

* [x] Supports #1883
* [X] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [X] Tests added/passed
This commit is contained in:
Chester Liu 2021-04-17 00:26:28 +08:00 committed by GitHub
parent 05e7ea1423
commit b68ee23bf8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 492 additions and 2 deletions

View file

@ -4,6 +4,7 @@
#include "../../terminal/adapter/DispatchTypes.hpp"
#include "../../buffer/out/TextAttribute.hpp"
#include "../../types/inc/Viewport.hpp"
namespace Microsoft::Terminal::Core
{
@ -22,6 +23,7 @@ namespace Microsoft::Terminal::Core
virtual TextAttribute GetTextAttributes() const noexcept = 0;
virtual void SetTextAttributes(const TextAttribute& attrs) noexcept = 0;
virtual Microsoft::Console::Types::Viewport GetBufferSize() noexcept = 0;
virtual bool SetCursorPosition(short x, short y) noexcept = 0;
virtual COORD GetCursorPosition() noexcept = 0;
virtual bool SetCursorVisibility(const bool visible) noexcept = 0;

View file

@ -86,6 +86,7 @@ public:
bool ExecuteChar(wchar_t wch) noexcept override;
TextAttribute GetTextAttributes() const noexcept override;
void SetTextAttributes(const TextAttribute& attrs) noexcept override;
Microsoft::Console::Types::Viewport GetBufferSize() noexcept override;
bool SetCursorPosition(short x, short y) noexcept override;
COORD GetCursorPosition() noexcept override;
bool SetCursorVisibility(const bool visible) noexcept override;

View file

@ -36,6 +36,11 @@ void Terminal::SetTextAttributes(const TextAttribute& attrs) noexcept
_buffer->SetCurrentAttributes(attrs);
}
Viewport Terminal::GetBufferSize() noexcept
{
return _buffer->GetSize();
}
bool Terminal::SetCursorPosition(short x, short y) noexcept
try
{

View file

@ -134,6 +134,74 @@ try
}
CATCH_LOG_RETURN_FALSE()
bool TerminalDispatch::HorizontalTabSet() noexcept
{
const auto width = _terminalApi.GetBufferSize().Dimensions().X;
const auto column = _terminalApi.GetCursorPosition().X;
_InitTabStopsForWidth(width);
_tabStopColumns.at(column) = true;
return true;
}
bool TerminalDispatch::ForwardTab(const size_t numTabs) noexcept
{
const auto width = _terminalApi.GetBufferSize().Dimensions().X;
const auto cursorPosition = _terminalApi.GetCursorPosition();
auto column = cursorPosition.X;
const auto row = cursorPosition.Y;
auto tabsPerformed = 0u;
_InitTabStopsForWidth(width);
while (column + 1 < width && tabsPerformed < numTabs)
{
column++;
if (til::at(_tabStopColumns, column))
{
tabsPerformed++;
}
}
return _terminalApi.SetCursorPosition(column, row);
}
bool TerminalDispatch::BackwardsTab(const size_t numTabs) noexcept
{
const auto width = _terminalApi.GetBufferSize().Dimensions().X;
const auto cursorPosition = _terminalApi.GetCursorPosition();
auto column = cursorPosition.X;
const auto row = cursorPosition.Y;
auto tabsPerformed = 0u;
_InitTabStopsForWidth(width);
while (column > 0 && tabsPerformed < numTabs)
{
column--;
if (til::at(_tabStopColumns, column))
{
tabsPerformed++;
}
}
return _terminalApi.SetCursorPosition(column, row);
}
bool TerminalDispatch::TabClear(const DispatchTypes::TabClearType clearType) noexcept
{
bool success = false;
switch (clearType)
{
case DispatchTypes::TabClearType::ClearCurrentColumn:
success = _ClearSingleTabStop();
break;
case DispatchTypes::TabClearType::ClearAllColumns:
success = _ClearAllTabStops();
break;
default:
success = false;
break;
}
return success;
}
// Method Description:
// - Sets a single entry of the colortable to a new value
// Arguments:
@ -550,6 +618,48 @@ bool TerminalDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param,
return success;
}
bool TerminalDispatch::_ClearSingleTabStop() noexcept
{
const auto width = _terminalApi.GetBufferSize().Dimensions().X;
const auto column = _terminalApi.GetCursorPosition().X;
_InitTabStopsForWidth(width);
_tabStopColumns.at(column) = false;
return true;
}
bool TerminalDispatch::_ClearAllTabStops() noexcept
{
_tabStopColumns.clear();
_initDefaultTabStops = false;
return true;
}
void TerminalDispatch::_ResetTabStops() noexcept
{
_tabStopColumns.clear();
_initDefaultTabStops = true;
}
void TerminalDispatch::_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;
}
}
}
}
}
bool TerminalDispatch::SoftReset() noexcept
{
// TODO:GH#1883 much of this method is not yet implemented in the Terminal,
@ -623,8 +733,8 @@ bool TerminalDispatch::HardReset() noexcept
// Cursor to 1,1 - the Soft Reset guarantees this is absolute
success = CursorPosition(1, 1) && success;
// // Delete all current tab stops and reapply
// _ResetTabStops();
// Delete all current tab stops and reapply
_ResetTabStops();
return success;
}

View file

@ -40,6 +40,11 @@ public:
bool CarriageReturn() noexcept override;
bool SetWindowTitle(std::wstring_view title) noexcept override;
bool HorizontalTabSet() noexcept override; // HTS
bool ForwardTab(const size_t numTabs) noexcept override; // CHT, HT
bool BackwardsTab(const size_t numTabs) noexcept override; // CBT
bool TabClear(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::TabClearType clearType) noexcept override; // TBC
bool SetColorTableEntry(const size_t tableIndex, const DWORD color) noexcept override;
bool SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle) noexcept override;
bool SetCursorColor(const DWORD color) noexcept override;
@ -79,9 +84,17 @@ public:
private:
::Microsoft::Terminal::Core::ITerminalApi& _terminalApi;
std::vector<bool> _tabStopColumns;
bool _initDefaultTabStops = true;
size_t _SetRgbColorsHelper(const ::Microsoft::Console::VirtualTerminal::VTParameters options,
TextAttribute& attr,
const bool isForeground) noexcept;
bool _ModeParamsHelper(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::ModeParams param, const bool enable) noexcept;
bool _ClearSingleTabStop() noexcept;
bool _ClearAllTabStops() noexcept;
void _ResetTabStops() noexcept;
void _InitTabStopsForWidth(const size_t width);
};

View file

@ -41,6 +41,16 @@ class TerminalCoreUnitTests::TerminalBufferTests final
TEST_METHOD(DontSnapToOutputTest);
TEST_METHOD(TestResetClearTabStops);
TEST_METHOD(TestAddTabStop);
TEST_METHOD(TestClearTabStop);
TEST_METHOD(TestGetForwardTab);
TEST_METHOD(TestGetReverseTab);
TEST_METHOD_SETUP(MethodSetup)
{
// STEP 1: Set up the Terminal
@ -56,6 +66,9 @@ class TerminalCoreUnitTests::TerminalBufferTests final
}
private:
void _SetTabStops(std::list<short> columns, bool replace);
std::list<short> _GetTabStops();
DummyRenderTarget emptyRT;
std::unique_ptr<Terminal> term;
};
@ -233,3 +246,349 @@ void TerminalBufferTests::DontSnapToOutputTest()
VERIFY_ARE_EQUAL(TerminalViewHeight, seventhView.BottomExclusive());
VERIFY_ARE_EQUAL(TerminalHistoryLength, term->_scrollOffset);
}
void TerminalBufferTests::_SetTabStops(std::list<short> columns, bool replace)
{
auto& termTb = *term->_buffer;
auto& termSm = *term->_stateMachine;
auto& cursor = termTb.GetCursor();
const auto clearTabStops = L"\033[3g";
const auto addTabStop = L"\033H";
if (replace)
{
termSm.ProcessString(clearTabStops);
}
for (auto column : columns)
{
cursor.SetXPosition(column);
termSm.ProcessString(addTabStop);
}
}
std::list<short> TerminalBufferTests::_GetTabStops()
{
std::list<short> columns;
auto& termTb = *term->_buffer;
auto& termSm = *term->_stateMachine;
const auto initialView = term->GetViewport();
const auto lastColumn = initialView.RightInclusive();
auto& cursor = termTb.GetCursor();
cursor.SetPosition({ 0, 0 });
for (;;)
{
termSm.ProcessCharacter(L'\t');
auto column = cursor.GetPosition().X;
if (column >= lastColumn)
{
break;
}
columns.push_back(column);
}
return columns;
}
void TerminalBufferTests::TestResetClearTabStops()
{
auto& termSm = *term->_stateMachine;
const auto initialView = term->GetViewport();
const auto clearTabStops = L"\033[3g";
const auto resetToInitialState = L"\033c";
Log::Comment(L"Default tabs every 8 columns.");
std::list<short> expectedStops{ 8, 16, 24, 32, 40, 48, 56, 64, 72 };
VERIFY_ARE_EQUAL(expectedStops, _GetTabStops());
Log::Comment(L"Clear all tabs.");
termSm.ProcessString(clearTabStops);
expectedStops = {};
VERIFY_ARE_EQUAL(expectedStops, _GetTabStops());
Log::Comment(L"RIS resets tabs to defaults.");
termSm.ProcessString(resetToInitialState);
expectedStops = { 8, 16, 24, 32, 40, 48, 56, 64, 72 };
VERIFY_ARE_EQUAL(expectedStops, _GetTabStops());
}
void TerminalBufferTests::TestAddTabStop()
{
auto& termTb = *term->_buffer;
auto& termSm = *term->_stateMachine;
auto& cursor = termTb.GetCursor();
const auto clearTabStops = L"\033[3g";
const auto addTabStop = L"\033H";
Log::Comment(L"Clear all tabs.");
termSm.ProcessString(clearTabStops);
std::list<short> expectedStops{};
VERIFY_ARE_EQUAL(expectedStops, _GetTabStops());
Log::Comment(L"Add tab to empty list.");
cursor.SetXPosition(12);
termSm.ProcessString(addTabStop);
expectedStops.push_back(12);
VERIFY_ARE_EQUAL(expectedStops, _GetTabStops());
Log::Comment(L"Add tab to head of existing list.");
cursor.SetXPosition(4);
termSm.ProcessString(addTabStop);
expectedStops.push_front(4);
VERIFY_ARE_EQUAL(expectedStops, _GetTabStops());
Log::Comment(L"Add tab to tail of existing list.");
cursor.SetXPosition(30);
termSm.ProcessString(addTabStop);
expectedStops.push_back(30);
VERIFY_ARE_EQUAL(expectedStops, _GetTabStops());
Log::Comment(L"Add tab to middle of existing list.");
cursor.SetXPosition(24);
termSm.ProcessString(addTabStop);
expectedStops.push_back(24);
expectedStops.sort();
VERIFY_ARE_EQUAL(expectedStops, _GetTabStops());
Log::Comment(L"Add tab that duplicates an item in the existing list.");
cursor.SetXPosition(24);
termSm.ProcessString(addTabStop);
VERIFY_ARE_EQUAL(expectedStops, _GetTabStops());
}
void TerminalBufferTests::TestClearTabStop()
{
auto& termTb = *term->_buffer;
auto& termSm = *term->_stateMachine;
auto& cursor = termTb.GetCursor();
const auto clearTabStops = L"\033[3g";
const auto clearTabStop = L"\033[0g";
const auto addTabStop = L"\033H";
Log::Comment(L"Start with all tabs cleared.");
{
termSm.ProcessString(clearTabStops);
VERIFY_IS_TRUE(_GetTabStops().empty());
}
Log::Comment(L"Try to clear nonexistent list.");
{
cursor.SetXPosition(0);
termSm.ProcessString(clearTabStop);
VERIFY_IS_TRUE(_GetTabStops().empty(), L"List should remain empty");
}
Log::Comment(L"Allocate 1 list item and clear it.");
{
cursor.SetXPosition(0);
termSm.ProcessString(addTabStop);
termSm.ProcessString(clearTabStop);
VERIFY_IS_TRUE(_GetTabStops().empty());
}
Log::Comment(L"Allocate 1 list item and clear nonexistent.");
{
cursor.SetXPosition(1);
termSm.ProcessString(addTabStop);
Log::Comment(L"Free greater");
cursor.SetXPosition(2);
termSm.ProcessString(clearTabStop);
VERIFY_IS_FALSE(_GetTabStops().empty());
Log::Comment(L"Free less than");
cursor.SetXPosition(0);
termSm.ProcessString(clearTabStop);
VERIFY_IS_FALSE(_GetTabStops().empty());
// clear all tab stops
termSm.ProcessString(clearTabStops);
}
Log::Comment(L"Allocate many (5) list items and clear head.");
{
std::list<short> inputData = { 3, 5, 6, 10, 15, 17 };
_SetTabStops(inputData, false);
cursor.SetXPosition(inputData.front());
termSm.ProcessString(clearTabStop);
inputData.pop_front();
VERIFY_ARE_EQUAL(inputData, _GetTabStops());
// clear all tab stops
termSm.ProcessString(clearTabStops);
}
Log::Comment(L"Allocate many (5) list items and clear middle.");
{
std::list<short> inputData = { 3, 5, 6, 10, 15, 17 };
_SetTabStops(inputData, false);
cursor.SetXPosition(*std::next(inputData.begin()));
termSm.ProcessString(clearTabStop);
inputData.erase(std::next(inputData.begin()));
VERIFY_ARE_EQUAL(inputData, _GetTabStops());
// clear all tab stops
termSm.ProcessString(clearTabStops);
}
Log::Comment(L"Allocate many (5) list items and clear tail.");
{
std::list<short> inputData = { 3, 5, 6, 10, 15, 17 };
_SetTabStops(inputData, false);
cursor.SetXPosition(inputData.back());
termSm.ProcessString(clearTabStop);
inputData.pop_back();
VERIFY_ARE_EQUAL(inputData, _GetTabStops());
// clear all tab stops
termSm.ProcessString(clearTabStops);
}
Log::Comment(L"Allocate many (5) list items and clear nonexistent item.");
{
std::list<short> inputData = { 3, 5, 6, 10, 15, 17 };
_SetTabStops(inputData, false);
cursor.SetXPosition(0);
termSm.ProcessString(clearTabStop);
VERIFY_ARE_EQUAL(inputData, _GetTabStops());
// clear all tab stops
termSm.ProcessString(clearTabStops);
}
}
void TerminalBufferTests::TestGetForwardTab()
{
auto& termTb = *term->_buffer;
auto& termSm = *term->_stateMachine;
const auto initialView = term->GetViewport();
auto& cursor = termTb.GetCursor();
const auto nextForwardTab = L"\033[I";
std::list<short> inputData = { 3, 5, 6, 10, 15, 17 };
_SetTabStops(inputData, true);
const COORD coordScreenBufferSize = initialView.Dimensions();
Log::Comment(L"Find next tab from before front.");
{
cursor.SetXPosition(0);
COORD coordCursorExpected = cursor.GetPosition();
coordCursorExpected.X = inputData.front();
termSm.ProcessString(nextForwardTab);
COORD const coordCursorResult = cursor.GetPosition();
VERIFY_ARE_EQUAL(coordCursorExpected,
coordCursorResult,
L"Cursor advanced to first tab stop from sample list.");
}
Log::Comment(L"Find next tab from in the middle.");
{
cursor.SetXPosition(6);
COORD coordCursorExpected = cursor.GetPosition();
coordCursorExpected.X = *std::next(inputData.begin(), 3);
termSm.ProcessString(nextForwardTab);
COORD const coordCursorResult = cursor.GetPosition();
VERIFY_ARE_EQUAL(coordCursorExpected,
coordCursorResult,
L"Cursor advanced to middle tab stop from sample list.");
}
Log::Comment(L"Find next tab from end.");
{
cursor.SetXPosition(30);
COORD coordCursorExpected = cursor.GetPosition();
coordCursorExpected.X = coordScreenBufferSize.X - 1;
termSm.ProcessString(nextForwardTab);
COORD const coordCursorResult = cursor.GetPosition();
VERIFY_ARE_EQUAL(coordCursorExpected,
coordCursorResult,
L"Cursor advanced to end of screen buffer.");
}
Log::Comment(L"Find next tab from rightmost column.");
{
cursor.SetXPosition(coordScreenBufferSize.X - 1);
COORD coordCursorExpected = cursor.GetPosition();
termSm.ProcessString(nextForwardTab);
COORD const coordCursorResult = cursor.GetPosition();
VERIFY_ARE_EQUAL(coordCursorExpected,
coordCursorResult,
L"Cursor remains in rightmost column.");
}
}
void TerminalBufferTests::TestGetReverseTab()
{
auto& termTb = *term->_buffer;
auto& termSm = *term->_stateMachine;
auto& cursor = termTb.GetCursor();
const auto nextReverseTab = L"\033[Z";
std::list<short> inputData = { 3, 5, 6, 10, 15, 17 };
_SetTabStops(inputData, true);
Log::Comment(L"Find previous tab from before front.");
{
cursor.SetXPosition(1);
COORD coordCursorExpected = cursor.GetPosition();
coordCursorExpected.X = 0;
termSm.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.");
}
Log::Comment(L"Find previous tab from in the middle.");
{
cursor.SetXPosition(6);
COORD coordCursorExpected = cursor.GetPosition();
coordCursorExpected.X = *std::next(inputData.begin());
termSm.ProcessString(nextReverseTab);
COORD const coordCursorResult = cursor.GetPosition();
VERIFY_ARE_EQUAL(coordCursorExpected,
coordCursorResult,
L"Cursor adjusted back one tab spot from middle of sample list.");
}
Log::Comment(L"Find next tab from end.");
{
cursor.SetXPosition(30);
COORD coordCursorExpected = cursor.GetPosition();
coordCursorExpected.X = inputData.back();
termSm.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.");
}
}