9aa4a115aa
This commit renames `Base64::s_Decode` into `Base64::Decode` and improves its average performance on short strings of less than 200 characters by 4.5x. This is achieved by implementing a classic base64 decoder that reads 4 characters at a time and produces 3 output bytes. Furthermore a small 128 byte lookup table is used to quickly map characters to values. ## PR Checklist * [x] I work here * [x] Tests added/passed ## Validation Steps Performed * Run WSL in Windows Terminal * Run `printf "\033]52;c;aHR0cHM6Ly9naXRodWIuY29tL21pY3Jvc29mdC90ZXJtaW5hbC9wdWxsLzExNDY3\a"` * Clipboard contains `https://github.com/microsoft/terminal/pull/11467` ✔️
3374 lines
126 KiB
C++
3374 lines
126 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "precomp.h"
|
|
#include <wextestclass.h>
|
|
#include "../../inc/consoletaeftemplates.hpp"
|
|
|
|
#include "stateMachine.hpp"
|
|
#include "OutputStateMachineEngine.hpp"
|
|
|
|
#include "ascii.hpp"
|
|
|
|
using namespace Microsoft::Console::VirtualTerminal;
|
|
|
|
using namespace WEX::Common;
|
|
using namespace WEX::Logging;
|
|
using namespace WEX::TestExecution;
|
|
|
|
namespace Microsoft
|
|
{
|
|
namespace Console
|
|
{
|
|
namespace VirtualTerminal
|
|
{
|
|
class OutputEngineTest;
|
|
}
|
|
}
|
|
}
|
|
|
|
// From VT100.net...
|
|
// 9999-10000 is the classic boundary for most parsers parameter values.
|
|
// 16383-16384 is the boundary for DECSR commands according to EK-VT520-RM section 4.3.3.2
|
|
// 32767-32768 is our boundary SHORT_MAX for the Windows console
|
|
#define PARAM_VALUES L"{0, 1, 2, 1000, 9999, 10000, 16383, 16384, 32767, 32768, 50000, 999999999}"
|
|
|
|
class DummyDispatch final : public TermDispatch
|
|
{
|
|
public:
|
|
virtual void Execute(const wchar_t /*wchControl*/) override
|
|
{
|
|
}
|
|
|
|
virtual void Print(const wchar_t /*wchPrintable*/) override
|
|
{
|
|
}
|
|
|
|
virtual void PrintString(const std::wstring_view /*string*/) override
|
|
{
|
|
}
|
|
};
|
|
|
|
class Microsoft::Console::VirtualTerminal::OutputEngineTest final
|
|
{
|
|
TEST_CLASS(OutputEngineTest);
|
|
|
|
TEST_METHOD(TestEscapePath)
|
|
{
|
|
BEGIN_TEST_METHOD_PROPERTIES()
|
|
TEST_METHOD_PROPERTY(L"Data:uiTest", L"{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17}") // one value for each type of state test below.
|
|
END_TEST_METHOD_PROPERTIES()
|
|
|
|
size_t uiTest;
|
|
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiTest", uiTest));
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
// The OscString state shouldn't escape out after an ESC.
|
|
// Same for DcsPassThrough and SosPmApcString state.
|
|
bool shouldEscapeOut = true;
|
|
|
|
switch (uiTest)
|
|
{
|
|
case 0:
|
|
{
|
|
Log::Comment(L"Escape from Ground.");
|
|
mach._state = StateMachine::VTStates::Ground;
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
Log::Comment(L"Escape from Escape.");
|
|
mach._state = StateMachine::VTStates::Escape;
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
Log::Comment(L"Escape from Escape Intermediate");
|
|
mach._state = StateMachine::VTStates::EscapeIntermediate;
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
Log::Comment(L"Escape from CsiEntry");
|
|
mach._state = StateMachine::VTStates::CsiEntry;
|
|
break;
|
|
}
|
|
case 4:
|
|
{
|
|
Log::Comment(L"Escape from CsiIgnore");
|
|
mach._state = StateMachine::VTStates::CsiIgnore;
|
|
break;
|
|
}
|
|
case 5:
|
|
{
|
|
Log::Comment(L"Escape from CsiParam");
|
|
mach._state = StateMachine::VTStates::CsiParam;
|
|
break;
|
|
}
|
|
case 6:
|
|
{
|
|
Log::Comment(L"Escape from CsiIntermediate");
|
|
mach._state = StateMachine::VTStates::CsiIntermediate;
|
|
break;
|
|
}
|
|
case 7:
|
|
{
|
|
Log::Comment(L"Escape from OscParam");
|
|
mach._state = StateMachine::VTStates::OscParam;
|
|
break;
|
|
}
|
|
case 8:
|
|
{
|
|
Log::Comment(L"Escape from OscString");
|
|
shouldEscapeOut = false;
|
|
mach._state = StateMachine::VTStates::OscString;
|
|
break;
|
|
}
|
|
case 9:
|
|
{
|
|
Log::Comment(L"Escape from OscTermination");
|
|
mach._state = StateMachine::VTStates::OscTermination;
|
|
break;
|
|
}
|
|
case 10:
|
|
{
|
|
Log::Comment(L"Escape from Ss3Entry");
|
|
mach._state = StateMachine::VTStates::Ss3Entry;
|
|
break;
|
|
}
|
|
case 11:
|
|
{
|
|
Log::Comment(L"Escape from Ss3Param");
|
|
mach._state = StateMachine::VTStates::Ss3Param;
|
|
break;
|
|
}
|
|
case 12:
|
|
{
|
|
Log::Comment(L"Escape from DcsEntry");
|
|
mach._state = StateMachine::VTStates::DcsEntry;
|
|
break;
|
|
}
|
|
case 13:
|
|
{
|
|
Log::Comment(L"Escape from DcsIgnore");
|
|
mach._state = StateMachine::VTStates::DcsIgnore;
|
|
break;
|
|
}
|
|
case 14:
|
|
{
|
|
Log::Comment(L"Escape from DcsIntermediate");
|
|
mach._state = StateMachine::VTStates::DcsIntermediate;
|
|
break;
|
|
}
|
|
case 15:
|
|
{
|
|
Log::Comment(L"Escape from DcsParam");
|
|
mach._state = StateMachine::VTStates::DcsParam;
|
|
break;
|
|
}
|
|
case 16:
|
|
{
|
|
Log::Comment(L"Escape from DcsPassThrough");
|
|
shouldEscapeOut = false;
|
|
mach._state = StateMachine::VTStates::DcsPassThrough;
|
|
mach._dcsStringHandler = [](const auto) { return true; };
|
|
break;
|
|
}
|
|
case 17:
|
|
{
|
|
Log::Comment(L"Escape from SosPmApcString");
|
|
shouldEscapeOut = false;
|
|
mach._state = StateMachine::VTStates::SosPmApcString;
|
|
break;
|
|
}
|
|
}
|
|
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
if (shouldEscapeOut)
|
|
{
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
}
|
|
}
|
|
|
|
TEST_METHOD(TestEscapeImmediatePath)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'#');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::EscapeIntermediate);
|
|
mach.ProcessCharacter(L'(');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::EscapeIntermediate);
|
|
mach.ProcessCharacter(L')');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::EscapeIntermediate);
|
|
mach.ProcessCharacter(L'#');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::EscapeIntermediate);
|
|
mach.ProcessCharacter(L'6');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestEscapeThenC0Path)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
// When we see a C0 control char in the escape state, the Output engine
|
|
// should execute it, without interrupting the sequence it's currently
|
|
// processing
|
|
mach.ProcessCharacter(L'\x03');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'[');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
|
|
mach.ProcessCharacter(L'3');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
|
mach.ProcessCharacter(L'1');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
|
mach.ProcessCharacter(L'm');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestGroundPrint)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(L'a');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestCsiEntry)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'[');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
|
|
mach.ProcessCharacter(L'm');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestC1CsiEntry)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(L'\x9b');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
|
|
mach.ProcessCharacter(L'm');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestCsiImmediate)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'[');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
|
|
mach.ProcessCharacter(L'$');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiIntermediate);
|
|
mach.ProcessCharacter(L'#');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiIntermediate);
|
|
mach.ProcessCharacter(L'%');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiIntermediate);
|
|
mach.ProcessCharacter(L'v');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestCsiParam)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'[');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
|
|
mach.ProcessCharacter(L';');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
|
mach.ProcessCharacter(L'3');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
|
mach.ProcessCharacter(L'2');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
|
mach.ProcessCharacter(L'4');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
|
mach.ProcessCharacter(L';');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
|
mach.ProcessCharacter(L';');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
|
mach.ProcessCharacter(L'8');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
|
mach.ProcessCharacter(L'J');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
|
|
VERIFY_ARE_EQUAL(mach._parameters.size(), 4u);
|
|
VERIFY_IS_FALSE(mach._parameters.at(0).has_value());
|
|
VERIFY_ARE_EQUAL(mach._parameters.at(1), 324u);
|
|
VERIFY_IS_FALSE(mach._parameters.at(2).has_value());
|
|
VERIFY_ARE_EQUAL(mach._parameters.at(3), 8u);
|
|
}
|
|
|
|
TEST_METHOD(TestCsiMaxParamCount)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
Log::Comment(L"Output a sequence with 100 parameters");
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'[');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
|
|
for (size_t i = 0; i < 100; i++)
|
|
{
|
|
if (i > 0)
|
|
{
|
|
mach.ProcessCharacter(L';');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
|
}
|
|
mach.ProcessCharacter(L'0' + i % 10);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
|
}
|
|
mach.ProcessCharacter(L'J');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
|
|
Log::Comment(L"Only MAX_PARAMETER_COUNT (32) parameters should be stored");
|
|
VERIFY_ARE_EQUAL(mach._parameters.size(), MAX_PARAMETER_COUNT);
|
|
for (size_t i = 0; i < MAX_PARAMETER_COUNT; i++)
|
|
{
|
|
VERIFY_IS_TRUE(mach._parameters.at(i).has_value());
|
|
VERIFY_ARE_EQUAL(mach._parameters.at(i).value(), i % 10);
|
|
}
|
|
}
|
|
|
|
TEST_METHOD(TestLeadingZeroCsiParam)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'[');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
|
|
for (int i = 0; i < 50; i++) // Any number of leading zeros should be supported
|
|
{
|
|
mach.ProcessCharacter(L'0');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
|
}
|
|
for (int i = 0; i < 5; i++) // We're only expecting to be able to keep 5 digits max
|
|
{
|
|
mach.ProcessCharacter((wchar_t)(L'1' + i));
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
|
}
|
|
VERIFY_ARE_EQUAL(mach._parameters.back(), 12345u);
|
|
mach.ProcessCharacter(L'J');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestCsiIgnore)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'[');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
|
|
mach.ProcessCharacter(L':');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiIgnore);
|
|
mach.ProcessCharacter(L'3');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiIgnore);
|
|
mach.ProcessCharacter(L'q');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'[');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
|
|
mach.ProcessCharacter(L'4');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
|
mach.ProcessCharacter(L';');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
|
mach.ProcessCharacter(L':');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiIgnore);
|
|
mach.ProcessCharacter(L'8');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiIgnore);
|
|
mach.ProcessCharacter(L'J');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'[');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
|
|
mach.ProcessCharacter(L'4');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
|
mach.ProcessCharacter(L'#');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiIntermediate);
|
|
mach.ProcessCharacter(L':');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiIgnore);
|
|
mach.ProcessCharacter(L'8');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiIgnore);
|
|
mach.ProcessCharacter(L'J');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestC1Osc)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(L'\x9d');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
|
mach.ProcessCharacter(AsciiChars::BEL);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestOscStringSimple)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L']');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
|
mach.ProcessCharacter(L'0');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
|
mach.ProcessCharacter(L';');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L's');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L'o');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L'm');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L'e');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L' ');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L't');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L'e');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L'x');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L't');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(AsciiChars::BEL);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L']');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
|
mach.ProcessCharacter(L'0');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
|
mach.ProcessCharacter(L';');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L's');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L'o');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L'm');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L'e');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L' ');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L't');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L'e');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L'x');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L't');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscTermination);
|
|
mach.ProcessCharacter(L'\\');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestLongOscString)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L']');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
|
mach.ProcessCharacter(L'0');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
|
mach.ProcessCharacter(L';');
|
|
for (int i = 0; i < 260u; i++) // The buffer is only 256 long, so any longer value should work :P
|
|
{
|
|
mach.ProcessCharacter(L's');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
}
|
|
VERIFY_ARE_EQUAL(mach._oscString.size(), 260u);
|
|
mach.ProcessCharacter(AsciiChars::BEL);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(NormalTestOscParam)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L']');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
|
for (int i = 0; i < 5; i++) // We're only expecting to be able to keep 5 digits max
|
|
{
|
|
mach.ProcessCharacter((wchar_t)(L'1' + i));
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
|
}
|
|
VERIFY_ARE_EQUAL(mach._oscParameter, 12345u);
|
|
mach.ProcessCharacter(L';');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L's');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(AsciiChars::BEL);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestLeadingZeroOscParam)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L']');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
|
for (int i = 0; i < 50; i++) // Any number of leading zeros should be supported
|
|
{
|
|
mach.ProcessCharacter(L'0');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
|
}
|
|
for (int i = 0; i < 5; i++) // We're only expecting to be able to keep 5 digits max
|
|
{
|
|
mach.ProcessCharacter((wchar_t)(L'1' + i));
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
|
}
|
|
VERIFY_ARE_EQUAL(mach._oscParameter, 12345u);
|
|
mach.ProcessCharacter(L';');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L's');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(AsciiChars::BEL);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestLongOscParam)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L']');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
|
constexpr auto sizeMax = std::numeric_limits<size_t>::max();
|
|
const auto sizeMaxStr = wil::str_printf<std::wstring>(L"%zu", sizeMax);
|
|
for (auto& wch : sizeMaxStr)
|
|
{
|
|
mach.ProcessCharacter(wch);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
|
}
|
|
VERIFY_ARE_EQUAL(mach._oscParameter, MAX_PARAMETER_VALUE);
|
|
mach.ProcessCharacter(L';');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L's');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(AsciiChars::BEL);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L']');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
|
for (const auto& wch : sizeMaxStr)
|
|
{
|
|
mach.ProcessCharacter(wch);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
|
}
|
|
VERIFY_ARE_EQUAL(mach._oscParameter, MAX_PARAMETER_VALUE);
|
|
mach.ProcessCharacter(L';');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L's');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(AsciiChars::BEL);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestOscStringInvalidTermination)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L']');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
|
mach.ProcessCharacter(L'1');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
|
mach.ProcessCharacter(L';');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L's');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscTermination);
|
|
mach.ProcessCharacter(L'['); // This is not a string terminator.
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
|
|
mach.ProcessCharacter(L'4');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
|
mach.ProcessCharacter(L';');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
|
mach.ProcessCharacter(L'm');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestDcsEntry)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'P');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsEntry);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'\\');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestC1DcsEntry)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(L'\x90');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsEntry);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'\\');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestDcsImmediate)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'P');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsEntry);
|
|
mach.ProcessCharacter(L' ');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIntermediate);
|
|
mach.ProcessCharacter(L'#');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIntermediate);
|
|
mach.ProcessCharacter(L'%');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIntermediate);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'\\');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestDcsIgnore)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'P');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsEntry);
|
|
mach.ProcessCharacter(L':');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIgnore);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'\\');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestDcsParam)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'P');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsEntry);
|
|
mach.ProcessCharacter(L';');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsParam);
|
|
mach.ProcessCharacter(L'3');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsParam);
|
|
mach.ProcessCharacter(L'2');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsParam);
|
|
mach.ProcessCharacter(L'4');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsParam);
|
|
mach.ProcessCharacter(L';');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsParam);
|
|
mach.ProcessCharacter(L';');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsParam);
|
|
mach.ProcessCharacter(L'8');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsParam);
|
|
|
|
VERIFY_ARE_EQUAL(mach._parameters.size(), 4u);
|
|
VERIFY_IS_FALSE(mach._parameters.at(0).has_value());
|
|
VERIFY_ARE_EQUAL(mach._parameters.at(1), 324u);
|
|
VERIFY_IS_FALSE(mach._parameters.at(2).has_value());
|
|
VERIFY_ARE_EQUAL(mach._parameters.at(3), 8u);
|
|
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'\\');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestDcsIntermediateAndPassThrough)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'P');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsEntry);
|
|
mach.ProcessCharacter(L' ');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIntermediate);
|
|
mach.ProcessCharacter(L'x');
|
|
// Note that without a dispatcher the pass through data is instead ignored.
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIgnore);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'\\');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestDcsLongStringPassThrough)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'P');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsEntry);
|
|
mach.ProcessCharacter(L'q');
|
|
// Note that without a dispatcher the pass through state is instead ignored.
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIgnore);
|
|
mach.ProcessCharacter(L'#');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIgnore);
|
|
mach.ProcessCharacter(L'1');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIgnore);
|
|
mach.ProcessCharacter(L'N');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIgnore);
|
|
mach.ProcessCharacter(L'N');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIgnore);
|
|
mach.ProcessCharacter(L'N');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIgnore);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'\\');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestDcsInvalidTermination)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'P');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsEntry);
|
|
mach.ProcessCharacter(L'q');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIgnore);
|
|
mach.ProcessCharacter(L'#');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIgnore);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'['); // This is not a string terminator.
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
|
|
mach.ProcessCharacter(L'4');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
|
mach.ProcessCharacter(L';');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
|
|
mach.ProcessCharacter(L'm');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestSosPmApcString)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'X');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
|
mach.ProcessCharacter(L'1');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
|
mach.ProcessCharacter(L'2');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'\\');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'^');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
|
mach.ProcessCharacter(L'3');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
|
mach.ProcessCharacter(L'4');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'\\');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'_');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
|
mach.ProcessCharacter(L'5');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
|
mach.ProcessCharacter(L'6');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'\\');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
|
|
TEST_METHOD(TestC1StringTerminator)
|
|
{
|
|
auto dispatch = std::make_unique<DummyDispatch>();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
// C1 ST should terminate OSC string.
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L']');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
|
mach.ProcessCharacter(L'1');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
|
|
mach.ProcessCharacter(L';');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L's');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
|
|
mach.ProcessCharacter(L'\x9c');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
|
|
// C1 ST should terminate DCS passthrough string.
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'P');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsEntry);
|
|
mach.ProcessCharacter(L'q');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIgnore);
|
|
mach.ProcessCharacter(L'#');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsIgnore);
|
|
mach.ProcessCharacter(L'1');
|
|
mach.ProcessCharacter(L'\x9c');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
|
|
// C1 ST should terminate SOS/PM/APC string.
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'X');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
|
mach.ProcessCharacter(L'1');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
|
mach.ProcessCharacter(L'\x9c');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'^');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
|
mach.ProcessCharacter(L'2');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
|
mach.ProcessCharacter(L'\x9c');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
|
|
mach.ProcessCharacter(L'_');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
|
mach.ProcessCharacter(L'3');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
|
|
mach.ProcessCharacter(L'\x9c');
|
|
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
|
|
}
|
|
};
|
|
|
|
class StatefulDispatch final : public TermDispatch
|
|
{
|
|
public:
|
|
virtual void Execute(const wchar_t /*wchControl*/) override
|
|
{
|
|
}
|
|
|
|
virtual void Print(const wchar_t /*wchPrintable*/) override
|
|
{
|
|
}
|
|
|
|
virtual void PrintString(const std::wstring_view /*string*/) override
|
|
{
|
|
}
|
|
|
|
StatefulDispatch() :
|
|
_cursorDistance{ 0 },
|
|
_line{ 0 },
|
|
_column{ 0 },
|
|
_cursorUp{ false },
|
|
_cursorDown{ false },
|
|
_cursorBackward{ false },
|
|
_cursorForward{ false },
|
|
_cursorNextLine{ false },
|
|
_cursorPreviousLine{ false },
|
|
_cursorHorizontalPositionAbsolute{ false },
|
|
_verticalLinePositionAbsolute{ false },
|
|
_horizontalPositionRelative{ false },
|
|
_verticalPositionRelative{ false },
|
|
_cursorPosition{ false },
|
|
_cursorSave{ false },
|
|
_cursorLoad{ false },
|
|
_cursorVisible{ true },
|
|
_eraseDisplay{ false },
|
|
_eraseLine{ false },
|
|
_insertCharacter{ false },
|
|
_deleteCharacter{ false },
|
|
_eraseType{ (DispatchTypes::EraseType)-1 },
|
|
_eraseTypes{},
|
|
_setGraphics{ false },
|
|
_statusReportType{ (DispatchTypes::AnsiStatusType)-1 },
|
|
_deviceStatusReport{ false },
|
|
_deviceAttributes{ false },
|
|
_secondaryDeviceAttributes{ false },
|
|
_tertiaryDeviceAttributes{ false },
|
|
_vt52DeviceAttributes{ false },
|
|
_requestTerminalParameters{ false },
|
|
_reportingPermission{ (DispatchTypes::ReportingPermission)-1 },
|
|
_isAltBuffer{ false },
|
|
_cursorKeysMode{ false },
|
|
_cursorBlinking{ true },
|
|
_isInAnsiMode{ true },
|
|
_isScreenModeReversed{ false },
|
|
_isOriginModeRelative{ false },
|
|
_isAutoWrapEnabled{ true },
|
|
_warningBell{ false },
|
|
_carriageReturn{ false },
|
|
_lineFeed{ false },
|
|
_lineFeedType{ (DispatchTypes::LineFeedType)-1 },
|
|
_reverseLineFeed{ false },
|
|
_forwardTab{ false },
|
|
_numTabs{ 0 },
|
|
_tabClear{ false },
|
|
_tabClearTypes{},
|
|
_isDECCOLMAllowed{ false },
|
|
_windowWidth{ 80 },
|
|
_bracketedPasteMode{ false },
|
|
_win32InputMode{ false },
|
|
_setDefaultForeground(false),
|
|
_defaultForegroundColor{ RGB(0, 0, 0) },
|
|
_setDefaultBackground(false),
|
|
_defaultBackgroundColor{ RGB(0, 0, 0) },
|
|
_setDefaultCursorColor(false),
|
|
_defaultCursorColor{ RGB(0, 0, 0) },
|
|
_hyperlinkMode{ false },
|
|
_options{ s_cMaxOptions, static_cast<DispatchTypes::GraphicsOptions>(s_uiGraphicsCleared) }, // fill with cleared option
|
|
_colorTable{},
|
|
_setColorTableEntry{ false }
|
|
{
|
|
}
|
|
|
|
void ClearState()
|
|
{
|
|
StatefulDispatch dispatch;
|
|
*this = dispatch;
|
|
}
|
|
|
|
bool CursorUp(_In_ size_t const uiDistance) noexcept override
|
|
{
|
|
_cursorUp = true;
|
|
_cursorDistance = uiDistance;
|
|
return true;
|
|
}
|
|
|
|
bool CursorDown(_In_ size_t const uiDistance) noexcept override
|
|
{
|
|
_cursorDown = true;
|
|
_cursorDistance = uiDistance;
|
|
return true;
|
|
}
|
|
|
|
bool CursorBackward(_In_ size_t const uiDistance) noexcept override
|
|
{
|
|
_cursorBackward = true;
|
|
_cursorDistance = uiDistance;
|
|
return true;
|
|
}
|
|
|
|
bool CursorForward(_In_ size_t const uiDistance) noexcept override
|
|
{
|
|
_cursorForward = true;
|
|
_cursorDistance = uiDistance;
|
|
return true;
|
|
}
|
|
|
|
bool CursorNextLine(_In_ size_t const uiDistance) noexcept override
|
|
{
|
|
_cursorNextLine = true;
|
|
_cursorDistance = uiDistance;
|
|
return true;
|
|
}
|
|
|
|
bool CursorPrevLine(_In_ size_t const uiDistance) noexcept override
|
|
{
|
|
_cursorPreviousLine = true;
|
|
_cursorDistance = uiDistance;
|
|
return true;
|
|
}
|
|
|
|
bool CursorHorizontalPositionAbsolute(_In_ size_t const uiPosition) noexcept override
|
|
{
|
|
_cursorHorizontalPositionAbsolute = true;
|
|
_cursorDistance = uiPosition;
|
|
return true;
|
|
}
|
|
|
|
bool VerticalLinePositionAbsolute(_In_ size_t const uiPosition) noexcept override
|
|
{
|
|
_verticalLinePositionAbsolute = true;
|
|
_cursorDistance = uiPosition;
|
|
return true;
|
|
}
|
|
|
|
bool HorizontalPositionRelative(_In_ size_t const uiDistance) noexcept override
|
|
{
|
|
_horizontalPositionRelative = true;
|
|
_cursorDistance = uiDistance;
|
|
return true;
|
|
}
|
|
|
|
bool VerticalPositionRelative(_In_ size_t const uiDistance) noexcept override
|
|
{
|
|
_verticalPositionRelative = true;
|
|
_cursorDistance = uiDistance;
|
|
return true;
|
|
}
|
|
|
|
bool CursorPosition(_In_ size_t const uiLine, _In_ size_t const uiColumn) noexcept override
|
|
{
|
|
_cursorPosition = true;
|
|
_line = uiLine;
|
|
_column = uiColumn;
|
|
return true;
|
|
}
|
|
|
|
bool CursorSaveState() noexcept override
|
|
{
|
|
_cursorSave = true;
|
|
return true;
|
|
}
|
|
|
|
bool CursorRestoreState() noexcept override
|
|
{
|
|
_cursorLoad = true;
|
|
return true;
|
|
}
|
|
|
|
bool EraseInDisplay(const DispatchTypes::EraseType eraseType) noexcept override
|
|
{
|
|
_eraseDisplay = true;
|
|
_eraseType = eraseType;
|
|
_eraseTypes.push_back(eraseType);
|
|
return true;
|
|
}
|
|
|
|
bool EraseInLine(const DispatchTypes::EraseType eraseType) noexcept override
|
|
{
|
|
_eraseLine = true;
|
|
_eraseType = eraseType;
|
|
_eraseTypes.push_back(eraseType);
|
|
return true;
|
|
}
|
|
|
|
bool InsertCharacter(_In_ size_t const uiCount) noexcept override
|
|
{
|
|
_insertCharacter = true;
|
|
_cursorDistance = uiCount;
|
|
return true;
|
|
}
|
|
|
|
bool DeleteCharacter(_In_ size_t const uiCount) noexcept override
|
|
{
|
|
_deleteCharacter = true;
|
|
_cursorDistance = uiCount;
|
|
return true;
|
|
}
|
|
|
|
bool CursorVisibility(const bool fIsVisible) noexcept override
|
|
{
|
|
_cursorVisible = fIsVisible;
|
|
return true;
|
|
}
|
|
|
|
bool SetGraphicsRendition(const VTParameters options) noexcept override
|
|
try
|
|
{
|
|
_options.clear();
|
|
for (size_t i = 0; i < options.size(); i++)
|
|
{
|
|
_options.push_back(options.at(i));
|
|
}
|
|
_setGraphics = true;
|
|
return true;
|
|
}
|
|
CATCH_LOG_RETURN_FALSE()
|
|
|
|
bool DeviceStatusReport(const DispatchTypes::AnsiStatusType statusType) noexcept override
|
|
{
|
|
_deviceStatusReport = true;
|
|
_statusReportType = statusType;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DeviceAttributes() noexcept override
|
|
{
|
|
_deviceAttributes = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SecondaryDeviceAttributes() noexcept override
|
|
{
|
|
_secondaryDeviceAttributes = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TertiaryDeviceAttributes() noexcept override
|
|
{
|
|
_tertiaryDeviceAttributes = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Vt52DeviceAttributes() noexcept override
|
|
{
|
|
_vt52DeviceAttributes = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool RequestTerminalParameters(const DispatchTypes::ReportingPermission permission) noexcept override
|
|
{
|
|
_requestTerminalParameters = true;
|
|
_reportingPermission = permission;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _ModeParamsHelper(_In_ DispatchTypes::ModeParams const param, const bool fEnable)
|
|
{
|
|
bool fSuccess = false;
|
|
switch (param)
|
|
{
|
|
case DispatchTypes::ModeParams::DECCKM_CursorKeysMode:
|
|
// set - Enable Application Mode, reset - Numeric/normal mode
|
|
fSuccess = SetVirtualTerminalInputMode(fEnable);
|
|
break;
|
|
case DispatchTypes::ModeParams::DECANM_AnsiMode:
|
|
fSuccess = SetAnsiMode(fEnable);
|
|
break;
|
|
case DispatchTypes::ModeParams::DECCOLM_SetNumberOfColumns:
|
|
fSuccess = SetColumns(static_cast<size_t>(fEnable ? DispatchTypes::s_sDECCOLMSetColumns : DispatchTypes::s_sDECCOLMResetColumns));
|
|
break;
|
|
case DispatchTypes::ModeParams::DECSCNM_ScreenMode:
|
|
fSuccess = SetScreenMode(fEnable);
|
|
break;
|
|
case DispatchTypes::ModeParams::DECOM_OriginMode:
|
|
// The cursor is also moved to the new home position when the origin mode is set or reset.
|
|
fSuccess = SetOriginMode(fEnable) && CursorPosition(1, 1);
|
|
break;
|
|
case DispatchTypes::ModeParams::DECAWM_AutoWrapMode:
|
|
fSuccess = SetAutoWrapMode(fEnable);
|
|
break;
|
|
case DispatchTypes::ModeParams::ATT610_StartCursorBlink:
|
|
fSuccess = EnableCursorBlinking(fEnable);
|
|
break;
|
|
case DispatchTypes::ModeParams::DECTCEM_TextCursorEnableMode:
|
|
fSuccess = CursorVisibility(fEnable);
|
|
break;
|
|
case DispatchTypes::ModeParams::XTERM_EnableDECCOLMSupport:
|
|
fSuccess = EnableDECCOLMSupport(fEnable);
|
|
break;
|
|
case DispatchTypes::ModeParams::ASB_AlternateScreenBuffer:
|
|
fSuccess = fEnable ? UseAlternateScreenBuffer() : UseMainScreenBuffer();
|
|
break;
|
|
case DispatchTypes::ModeParams::XTERM_BracketedPasteMode:
|
|
fSuccess = EnableXtermBracketedPasteMode(fEnable);
|
|
break;
|
|
case DispatchTypes::ModeParams::W32IM_Win32InputMode:
|
|
fSuccess = EnableWin32InputMode(fEnable);
|
|
break;
|
|
default:
|
|
// If no functions to call, overall dispatch was a failure.
|
|
fSuccess = false;
|
|
break;
|
|
}
|
|
return fSuccess;
|
|
}
|
|
|
|
bool SetMode(const DispatchTypes::ModeParams param) noexcept override
|
|
{
|
|
return _ModeParamsHelper(param, true);
|
|
}
|
|
|
|
bool ResetMode(const DispatchTypes::ModeParams param) noexcept override
|
|
{
|
|
return _ModeParamsHelper(param, false);
|
|
}
|
|
|
|
bool SetColumns(_In_ size_t const uiColumns) noexcept override
|
|
{
|
|
_windowWidth = uiColumns;
|
|
return true;
|
|
}
|
|
|
|
bool SetVirtualTerminalInputMode(const bool fApplicationMode) noexcept
|
|
{
|
|
_cursorKeysMode = fApplicationMode;
|
|
return true;
|
|
}
|
|
|
|
bool EnableXtermBracketedPasteMode(const bool enable) noexcept
|
|
{
|
|
_bracketedPasteMode = enable;
|
|
return true;
|
|
}
|
|
|
|
bool EnableWin32InputMode(const bool enable) noexcept
|
|
{
|
|
_win32InputMode = enable;
|
|
return true;
|
|
}
|
|
|
|
bool EnableCursorBlinking(const bool bEnable) noexcept override
|
|
{
|
|
_cursorBlinking = bEnable;
|
|
return true;
|
|
}
|
|
|
|
bool SetAnsiMode(const bool ansiMode) noexcept override
|
|
{
|
|
_isInAnsiMode = ansiMode;
|
|
return true;
|
|
}
|
|
|
|
bool SetScreenMode(const bool reverseMode) noexcept override
|
|
{
|
|
_isScreenModeReversed = reverseMode;
|
|
return true;
|
|
}
|
|
|
|
bool SetOriginMode(const bool fRelativeMode) noexcept override
|
|
{
|
|
_isOriginModeRelative = fRelativeMode;
|
|
return true;
|
|
}
|
|
|
|
bool SetAutoWrapMode(const bool wrapAtEOL) noexcept override
|
|
{
|
|
_isAutoWrapEnabled = wrapAtEOL;
|
|
return true;
|
|
}
|
|
|
|
bool WarningBell() noexcept override
|
|
{
|
|
_warningBell = true;
|
|
return true;
|
|
}
|
|
|
|
bool CarriageReturn() noexcept override
|
|
{
|
|
_carriageReturn = true;
|
|
return true;
|
|
}
|
|
|
|
bool LineFeed(const DispatchTypes::LineFeedType lineFeedType) noexcept override
|
|
{
|
|
_lineFeed = true;
|
|
_lineFeedType = lineFeedType;
|
|
return true;
|
|
}
|
|
|
|
bool ReverseLineFeed() noexcept override
|
|
{
|
|
_reverseLineFeed = true;
|
|
return true;
|
|
}
|
|
|
|
bool ForwardTab(const size_t numTabs) noexcept override
|
|
{
|
|
_forwardTab = true;
|
|
_numTabs = numTabs;
|
|
return true;
|
|
}
|
|
|
|
bool TabClear(const DispatchTypes::TabClearType clearType) noexcept override
|
|
{
|
|
_tabClear = true;
|
|
_tabClearTypes.push_back(clearType);
|
|
return true;
|
|
}
|
|
|
|
bool EnableDECCOLMSupport(const bool fEnabled) noexcept override
|
|
{
|
|
_isDECCOLMAllowed = fEnabled;
|
|
return true;
|
|
}
|
|
|
|
bool UseAlternateScreenBuffer() noexcept override
|
|
{
|
|
_isAltBuffer = true;
|
|
return true;
|
|
}
|
|
|
|
bool UseMainScreenBuffer() noexcept override
|
|
{
|
|
_isAltBuffer = false;
|
|
return true;
|
|
}
|
|
|
|
bool SetColorTableEntry(const size_t tableIndex, const COLORREF color) noexcept override
|
|
{
|
|
_setColorTableEntry = true;
|
|
_colorTable.at(tableIndex) = color;
|
|
return true;
|
|
}
|
|
|
|
bool SetDefaultForeground(const DWORD color) noexcept override
|
|
{
|
|
_setDefaultForeground = true;
|
|
_defaultForegroundColor = color;
|
|
return true;
|
|
}
|
|
|
|
bool SetDefaultBackground(const DWORD color) noexcept override
|
|
{
|
|
_setDefaultBackground = true;
|
|
_defaultBackgroundColor = color;
|
|
return true;
|
|
}
|
|
|
|
bool SetCursorColor(const DWORD color) noexcept override
|
|
{
|
|
_setDefaultCursorColor = true;
|
|
_defaultCursorColor = color;
|
|
return true;
|
|
}
|
|
|
|
bool SetClipboard(std::wstring_view content) noexcept override
|
|
{
|
|
_copyContent = { content.begin(), content.end() };
|
|
return true;
|
|
}
|
|
|
|
bool AddHyperlink(std::wstring_view uri, std::wstring_view params) noexcept override
|
|
{
|
|
_hyperlinkMode = true;
|
|
_uri = uri;
|
|
if (!params.empty())
|
|
{
|
|
_customId = params;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EndHyperlink() noexcept override
|
|
{
|
|
_hyperlinkMode = false;
|
|
_uri.clear();
|
|
_customId.clear();
|
|
return true;
|
|
}
|
|
|
|
bool DoConEmuAction(const std::wstring_view /*string*/) noexcept override
|
|
{
|
|
return true;
|
|
}
|
|
|
|
size_t _cursorDistance;
|
|
size_t _line;
|
|
size_t _column;
|
|
bool _cursorUp;
|
|
bool _cursorDown;
|
|
bool _cursorBackward;
|
|
bool _cursorForward;
|
|
bool _cursorNextLine;
|
|
bool _cursorPreviousLine;
|
|
bool _cursorHorizontalPositionAbsolute;
|
|
bool _verticalLinePositionAbsolute;
|
|
bool _horizontalPositionRelative;
|
|
bool _verticalPositionRelative;
|
|
bool _cursorPosition;
|
|
bool _cursorSave;
|
|
bool _cursorLoad;
|
|
bool _cursorVisible;
|
|
bool _eraseDisplay;
|
|
bool _eraseLine;
|
|
bool _insertCharacter;
|
|
bool _deleteCharacter;
|
|
DispatchTypes::EraseType _eraseType;
|
|
std::vector<DispatchTypes::EraseType> _eraseTypes;
|
|
bool _setGraphics;
|
|
DispatchTypes::AnsiStatusType _statusReportType;
|
|
bool _deviceStatusReport;
|
|
bool _deviceAttributes;
|
|
bool _secondaryDeviceAttributes;
|
|
bool _tertiaryDeviceAttributes;
|
|
bool _vt52DeviceAttributes;
|
|
bool _requestTerminalParameters;
|
|
DispatchTypes::ReportingPermission _reportingPermission;
|
|
bool _isAltBuffer;
|
|
bool _cursorKeysMode;
|
|
bool _cursorBlinking;
|
|
bool _isInAnsiMode;
|
|
bool _isScreenModeReversed;
|
|
bool _isOriginModeRelative;
|
|
bool _isAutoWrapEnabled;
|
|
bool _warningBell;
|
|
bool _carriageReturn;
|
|
bool _lineFeed;
|
|
DispatchTypes::LineFeedType _lineFeedType;
|
|
bool _reverseLineFeed;
|
|
bool _forwardTab;
|
|
size_t _numTabs;
|
|
bool _tabClear;
|
|
std::vector<DispatchTypes::TabClearType> _tabClearTypes;
|
|
bool _isDECCOLMAllowed;
|
|
size_t _windowWidth;
|
|
bool _bracketedPasteMode;
|
|
bool _win32InputMode;
|
|
bool _setDefaultForeground;
|
|
DWORD _defaultForegroundColor;
|
|
bool _setDefaultBackground;
|
|
DWORD _defaultBackgroundColor;
|
|
bool _setDefaultCursorColor;
|
|
DWORD _defaultCursorColor;
|
|
bool _setColorTableEntry;
|
|
bool _hyperlinkMode;
|
|
std::wstring _copyContent;
|
|
std::wstring _uri;
|
|
std::wstring _customId;
|
|
|
|
static const size_t s_cMaxOptions = 16;
|
|
static const size_t s_uiGraphicsCleared = UINT_MAX;
|
|
static const size_t XTERM_COLOR_TABLE_SIZE = 256;
|
|
std::vector<DispatchTypes::GraphicsOptions> _options;
|
|
std::array<COLORREF, XTERM_COLOR_TABLE_SIZE> _colorTable;
|
|
};
|
|
|
|
class StateMachineExternalTest final
|
|
{
|
|
TEST_CLASS(StateMachineExternalTest);
|
|
|
|
TEST_METHOD_SETUP(SetupState)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void InsertNumberToMachine(StateMachine* const pMachine, size_t number)
|
|
{
|
|
static const size_t cchBufferMax = 20;
|
|
|
|
wchar_t pwszDistance[cchBufferMax];
|
|
int cchDistance = swprintf_s(pwszDistance, cchBufferMax, L"%zu", number);
|
|
|
|
if (cchDistance > 0 && cchDistance < cchBufferMax)
|
|
{
|
|
for (int i = 0; i < cchDistance; i++)
|
|
{
|
|
pMachine->ProcessCharacter(pwszDistance[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ApplyParameterBoundary(size_t* uiExpected, size_t uiGiven)
|
|
{
|
|
// 0 and 1 should be 1. Use the preset value.
|
|
// 1-MAX_PARAMETER_VALUE should be what we set.
|
|
// > MAX_PARAMETER_VALUE should be MAX_PARAMETER_VALUE.
|
|
if (uiGiven <= 1)
|
|
{
|
|
*uiExpected = 1u;
|
|
}
|
|
else if (uiGiven > 1 && uiGiven <= MAX_PARAMETER_VALUE)
|
|
{
|
|
*uiExpected = uiGiven;
|
|
}
|
|
else if (uiGiven > MAX_PARAMETER_VALUE)
|
|
{
|
|
*uiExpected = MAX_PARAMETER_VALUE; // 32767 is our max value.
|
|
}
|
|
}
|
|
|
|
void TestCsiCursorMovement(wchar_t const wchCommand,
|
|
size_t const uiDistance,
|
|
const bool fUseDistance,
|
|
const bool fAddExtraParam,
|
|
const bool* const pfFlag,
|
|
StateMachine& mach,
|
|
StatefulDispatch& dispatch)
|
|
{
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
|
|
if (fUseDistance)
|
|
{
|
|
InsertNumberToMachine(&mach, uiDistance);
|
|
|
|
// Extraneous parameters should be ignored.
|
|
if (fAddExtraParam)
|
|
{
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'9');
|
|
}
|
|
}
|
|
|
|
mach.ProcessCharacter(wchCommand);
|
|
|
|
VERIFY_IS_TRUE(*pfFlag);
|
|
|
|
size_t uiExpectedDistance = 1u;
|
|
|
|
if (fUseDistance)
|
|
{
|
|
ApplyParameterBoundary(&uiExpectedDistance, uiDistance);
|
|
}
|
|
|
|
VERIFY_ARE_EQUAL(dispatch._cursorDistance, uiExpectedDistance);
|
|
}
|
|
|
|
TEST_METHOD(TestCsiCursorMovementWithValues)
|
|
{
|
|
BEGIN_TEST_METHOD_PROPERTIES()
|
|
TEST_METHOD_PROPERTY(L"Data:uiDistance", PARAM_VALUES)
|
|
TEST_METHOD_PROPERTY(L"Data:fExtraParam", L"{false,true}")
|
|
END_TEST_METHOD_PROPERTIES()
|
|
|
|
size_t uiDistance;
|
|
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiDistance", uiDistance));
|
|
bool fExtra;
|
|
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"fExtraParam", fExtra));
|
|
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
TestCsiCursorMovement(L'A', uiDistance, true, fExtra, &pDispatch->_cursorUp, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'B', uiDistance, true, fExtra, &pDispatch->_cursorDown, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'C', uiDistance, true, fExtra, &pDispatch->_cursorForward, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'D', uiDistance, true, fExtra, &pDispatch->_cursorBackward, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'E', uiDistance, true, fExtra, &pDispatch->_cursorNextLine, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'F', uiDistance, true, fExtra, &pDispatch->_cursorPreviousLine, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'G', uiDistance, true, fExtra, &pDispatch->_cursorHorizontalPositionAbsolute, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'`', uiDistance, true, fExtra, &pDispatch->_cursorHorizontalPositionAbsolute, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'd', uiDistance, true, fExtra, &pDispatch->_verticalLinePositionAbsolute, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'a', uiDistance, true, fExtra, &pDispatch->_horizontalPositionRelative, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'e', uiDistance, true, fExtra, &pDispatch->_verticalPositionRelative, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'@', uiDistance, true, fExtra, &pDispatch->_insertCharacter, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'P', uiDistance, true, fExtra, &pDispatch->_deleteCharacter, mach, *pDispatch);
|
|
}
|
|
|
|
TEST_METHOD(TestCsiCursorMovementWithoutValues)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
size_t uiDistance = 9999; // this value should be ignored with the false below.
|
|
TestCsiCursorMovement(L'A', uiDistance, false, false, &pDispatch->_cursorUp, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'B', uiDistance, false, false, &pDispatch->_cursorDown, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'C', uiDistance, false, false, &pDispatch->_cursorForward, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'D', uiDistance, false, false, &pDispatch->_cursorBackward, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'E', uiDistance, false, false, &pDispatch->_cursorNextLine, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'F', uiDistance, false, false, &pDispatch->_cursorPreviousLine, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'G', uiDistance, false, false, &pDispatch->_cursorHorizontalPositionAbsolute, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'`', uiDistance, false, false, &pDispatch->_cursorHorizontalPositionAbsolute, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'd', uiDistance, false, false, &pDispatch->_verticalLinePositionAbsolute, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'a', uiDistance, false, false, &pDispatch->_horizontalPositionRelative, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'e', uiDistance, false, false, &pDispatch->_verticalPositionRelative, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'@', uiDistance, false, false, &pDispatch->_insertCharacter, mach, *pDispatch);
|
|
pDispatch->ClearState();
|
|
TestCsiCursorMovement(L'P', uiDistance, false, false, &pDispatch->_deleteCharacter, mach, *pDispatch);
|
|
}
|
|
|
|
TEST_METHOD(TestCsiCursorPosition)
|
|
{
|
|
BEGIN_TEST_METHOD_PROPERTIES()
|
|
TEST_METHOD_PROPERTY(L"Data:uiRow", PARAM_VALUES)
|
|
TEST_METHOD_PROPERTY(L"Data:uiCol", PARAM_VALUES)
|
|
END_TEST_METHOD_PROPERTIES()
|
|
|
|
size_t uiRow;
|
|
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiRow", uiRow));
|
|
size_t uiCol;
|
|
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiCol", uiCol));
|
|
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
|
|
InsertNumberToMachine(&mach, uiRow);
|
|
mach.ProcessCharacter(L';');
|
|
InsertNumberToMachine(&mach, uiCol);
|
|
mach.ProcessCharacter(L'H');
|
|
|
|
// bound the row/col values by the max we expect
|
|
ApplyParameterBoundary(&uiRow, uiRow);
|
|
ApplyParameterBoundary(&uiCol, uiCol);
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_cursorPosition);
|
|
VERIFY_ARE_EQUAL(pDispatch->_line, uiRow);
|
|
VERIFY_ARE_EQUAL(pDispatch->_column, uiCol);
|
|
}
|
|
|
|
TEST_METHOD(TestCsiCursorPositionWithOnlyRow)
|
|
{
|
|
BEGIN_TEST_METHOD_PROPERTIES()
|
|
TEST_METHOD_PROPERTY(L"Data:uiRow", PARAM_VALUES)
|
|
END_TEST_METHOD_PROPERTIES()
|
|
|
|
size_t uiRow;
|
|
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiRow", uiRow));
|
|
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
|
|
InsertNumberToMachine(&mach, uiRow);
|
|
mach.ProcessCharacter(L'H');
|
|
|
|
// bound the row/col values by the max we expect
|
|
ApplyParameterBoundary(&uiRow, uiRow);
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_cursorPosition);
|
|
VERIFY_ARE_EQUAL(pDispatch->_line, uiRow);
|
|
VERIFY_ARE_EQUAL(pDispatch->_column, (size_t)1); // Without the second param, the column should always be the default
|
|
}
|
|
|
|
TEST_METHOD(TestCursorSaveLoad)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'7');
|
|
VERIFY_IS_TRUE(pDispatch->_cursorSave);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'8');
|
|
VERIFY_IS_TRUE(pDispatch->_cursorLoad);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
mach.ProcessCharacter(L's');
|
|
VERIFY_IS_TRUE(pDispatch->_cursorSave);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
mach.ProcessCharacter(L'u');
|
|
VERIFY_IS_TRUE(pDispatch->_cursorLoad);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestCursorKeysMode)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
mach.ProcessString(L"\x1b[?1h");
|
|
VERIFY_IS_TRUE(pDispatch->_cursorKeysMode);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\x1b[?1l");
|
|
VERIFY_IS_FALSE(pDispatch->_cursorKeysMode);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestAnsiMode)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
mach.ProcessString(L"\x1b[?2l");
|
|
VERIFY_IS_FALSE(pDispatch->_isInAnsiMode);
|
|
|
|
pDispatch->ClearState();
|
|
pDispatch->_isInAnsiMode = false;
|
|
mach.SetAnsiMode(false);
|
|
|
|
mach.ProcessString(L"\x1b<");
|
|
VERIFY_IS_TRUE(pDispatch->_isInAnsiMode);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestSetNumberOfColumns)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
mach.ProcessString(L"\x1b[?3h");
|
|
VERIFY_ARE_EQUAL(pDispatch->_windowWidth, static_cast<size_t>(DispatchTypes::s_sDECCOLMSetColumns));
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\x1b[?3l");
|
|
VERIFY_ARE_EQUAL(pDispatch->_windowWidth, static_cast<size_t>(DispatchTypes::s_sDECCOLMResetColumns));
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestScreenMode)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
mach.ProcessString(L"\x1b[?5h");
|
|
VERIFY_IS_TRUE(pDispatch->_isScreenModeReversed);
|
|
|
|
pDispatch->ClearState();
|
|
pDispatch->_isScreenModeReversed = true;
|
|
|
|
mach.ProcessString(L"\x1b[?5l");
|
|
VERIFY_IS_FALSE(pDispatch->_isScreenModeReversed);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestOriginMode)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
mach.ProcessString(L"\x1b[?6h");
|
|
VERIFY_IS_TRUE(pDispatch->_isOriginModeRelative);
|
|
VERIFY_IS_TRUE(pDispatch->_cursorPosition);
|
|
VERIFY_ARE_EQUAL(pDispatch->_line, 1u);
|
|
VERIFY_ARE_EQUAL(pDispatch->_column, 1u);
|
|
|
|
pDispatch->ClearState();
|
|
pDispatch->_isOriginModeRelative = true;
|
|
|
|
mach.ProcessString(L"\x1b[?6l");
|
|
VERIFY_IS_FALSE(pDispatch->_isOriginModeRelative);
|
|
VERIFY_IS_TRUE(pDispatch->_cursorPosition);
|
|
VERIFY_ARE_EQUAL(pDispatch->_line, 1u);
|
|
VERIFY_ARE_EQUAL(pDispatch->_column, 1u);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestAutoWrapMode)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
mach.ProcessString(L"\x1b[?7l");
|
|
VERIFY_IS_FALSE(pDispatch->_isAutoWrapEnabled);
|
|
|
|
pDispatch->ClearState();
|
|
pDispatch->_isAutoWrapEnabled = false;
|
|
|
|
mach.ProcessString(L"\x1b[?7h");
|
|
VERIFY_IS_TRUE(pDispatch->_isAutoWrapEnabled);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestCursorBlinking)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
mach.ProcessString(L"\x1b[?12h");
|
|
VERIFY_IS_TRUE(pDispatch->_cursorBlinking);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\x1b[?12l");
|
|
VERIFY_IS_FALSE(pDispatch->_cursorBlinking);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestCursorVisibility)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
mach.ProcessString(L"\x1b[?25h");
|
|
VERIFY_IS_TRUE(pDispatch->_cursorVisible);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\x1b[?25l");
|
|
VERIFY_IS_FALSE(pDispatch->_cursorVisible);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestAltBufferSwapping)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
mach.ProcessString(L"\x1b[?1049h");
|
|
VERIFY_IS_TRUE(pDispatch->_isAltBuffer);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\x1b[?1049h");
|
|
VERIFY_IS_TRUE(pDispatch->_isAltBuffer);
|
|
mach.ProcessString(L"\x1b[?1049h");
|
|
VERIFY_IS_TRUE(pDispatch->_isAltBuffer);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\x1b[?1049l");
|
|
VERIFY_IS_FALSE(pDispatch->_isAltBuffer);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\x1b[?1049h");
|
|
VERIFY_IS_TRUE(pDispatch->_isAltBuffer);
|
|
mach.ProcessString(L"\x1b[?1049l");
|
|
VERIFY_IS_FALSE(pDispatch->_isAltBuffer);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\x1b[?1049l");
|
|
VERIFY_IS_FALSE(pDispatch->_isAltBuffer);
|
|
mach.ProcessString(L"\x1b[?1049l");
|
|
VERIFY_IS_FALSE(pDispatch->_isAltBuffer);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestEnableDECCOLMSupport)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
mach.ProcessString(L"\x1b[?40h");
|
|
VERIFY_IS_TRUE(pDispatch->_isDECCOLMAllowed);
|
|
|
|
pDispatch->ClearState();
|
|
pDispatch->_isDECCOLMAllowed = true;
|
|
|
|
mach.ProcessString(L"\x1b[?40l");
|
|
VERIFY_IS_FALSE(pDispatch->_isDECCOLMAllowed);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestMultipleModes)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
mach.ProcessString(L"\x1b[?5;1;6h");
|
|
VERIFY_IS_TRUE(pDispatch->_isScreenModeReversed);
|
|
VERIFY_IS_TRUE(pDispatch->_cursorKeysMode);
|
|
VERIFY_IS_TRUE(pDispatch->_isOriginModeRelative);
|
|
|
|
pDispatch->ClearState();
|
|
pDispatch->_isScreenModeReversed = true;
|
|
pDispatch->_cursorKeysMode = true;
|
|
pDispatch->_isOriginModeRelative = true;
|
|
|
|
mach.ProcessString(L"\x1b[?5;1;6l");
|
|
VERIFY_IS_FALSE(pDispatch->_isScreenModeReversed);
|
|
VERIFY_IS_FALSE(pDispatch->_cursorKeysMode);
|
|
VERIFY_IS_FALSE(pDispatch->_isOriginModeRelative);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestErase)
|
|
{
|
|
BEGIN_TEST_METHOD_PROPERTIES()
|
|
TEST_METHOD_PROPERTY(L"Data:uiEraseOperation", L"{0, 1}") // for "display" and "line" type erase operations
|
|
TEST_METHOD_PROPERTY(L"Data:uiDispatchTypes::EraseType", L"{0, 1, 2, 10}") // maps to DispatchTypes::EraseType enum class options.
|
|
END_TEST_METHOD_PROPERTIES()
|
|
|
|
size_t uiEraseOperation;
|
|
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiEraseOperation", uiEraseOperation));
|
|
size_t uiDispatchTypes;
|
|
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiDispatchTypes::EraseType", uiDispatchTypes));
|
|
|
|
WCHAR wchOp = L'\0';
|
|
bool* pfOperationCallback = nullptr;
|
|
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
switch (uiEraseOperation)
|
|
{
|
|
case 0:
|
|
wchOp = L'J';
|
|
pfOperationCallback = &pDispatch->_eraseDisplay;
|
|
break;
|
|
case 1:
|
|
wchOp = L'K';
|
|
pfOperationCallback = &pDispatch->_eraseLine;
|
|
break;
|
|
default:
|
|
VERIFY_FAIL(L"Unknown erase operation permutation.");
|
|
}
|
|
|
|
VERIFY_IS_NOT_NULL(wchOp);
|
|
VERIFY_IS_NOT_NULL(pfOperationCallback);
|
|
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
|
|
DispatchTypes::EraseType expectedDispatchTypes;
|
|
|
|
switch (uiDispatchTypes)
|
|
{
|
|
case 0:
|
|
expectedDispatchTypes = DispatchTypes::EraseType::ToEnd;
|
|
InsertNumberToMachine(&mach, uiDispatchTypes);
|
|
break;
|
|
case 1:
|
|
expectedDispatchTypes = DispatchTypes::EraseType::FromBeginning;
|
|
InsertNumberToMachine(&mach, uiDispatchTypes);
|
|
break;
|
|
case 2:
|
|
expectedDispatchTypes = DispatchTypes::EraseType::All;
|
|
InsertNumberToMachine(&mach, uiDispatchTypes);
|
|
break;
|
|
case 10:
|
|
// Do nothing. Default case of 10 should be like a 0 to the end.
|
|
expectedDispatchTypes = DispatchTypes::EraseType::ToEnd;
|
|
break;
|
|
}
|
|
|
|
mach.ProcessCharacter(wchOp);
|
|
|
|
VERIFY_IS_TRUE(*pfOperationCallback);
|
|
VERIFY_ARE_EQUAL(expectedDispatchTypes, pDispatch->_eraseType);
|
|
}
|
|
|
|
TEST_METHOD(TestMultipleErase)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
mach.ProcessString(L"\x1b[3;2J");
|
|
auto expectedEraseTypes = std::vector{ DispatchTypes::EraseType::Scrollback, DispatchTypes::EraseType::All };
|
|
VERIFY_IS_TRUE(pDispatch->_eraseDisplay);
|
|
VERIFY_ARE_EQUAL(expectedEraseTypes, pDispatch->_eraseTypes);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\x1b[0;1K");
|
|
expectedEraseTypes = std::vector{ DispatchTypes::EraseType::ToEnd, DispatchTypes::EraseType::FromBeginning };
|
|
VERIFY_IS_TRUE(pDispatch->_eraseLine);
|
|
VERIFY_ARE_EQUAL(expectedEraseTypes, pDispatch->_eraseTypes);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
void VerifyDispatchTypes(const gsl::span<const DispatchTypes::GraphicsOptions> expectedOptions,
|
|
const StatefulDispatch& dispatch)
|
|
{
|
|
VERIFY_ARE_EQUAL(expectedOptions.size(), dispatch._options.size());
|
|
bool optionsValid = true;
|
|
|
|
for (size_t i = 0; i < dispatch._options.size(); i++)
|
|
{
|
|
auto expectedOption = (DispatchTypes::GraphicsOptions)dispatch.s_uiGraphicsCleared;
|
|
|
|
if (i < expectedOptions.size())
|
|
{
|
|
expectedOption = til::at(expectedOptions, i);
|
|
}
|
|
|
|
optionsValid = expectedOption == til::at(dispatch._options, i);
|
|
|
|
if (!optionsValid)
|
|
{
|
|
Log::Comment(NoThrowString().Format(L"Graphics option match failed, index [%zu]. Expected: '%d' Actual: '%d'", i, expectedOption, til::at(dispatch._options, i)));
|
|
break;
|
|
}
|
|
}
|
|
|
|
VERIFY_IS_TRUE(optionsValid);
|
|
}
|
|
|
|
TEST_METHOD(TestSetGraphicsRendition)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
DispatchTypes::GraphicsOptions rgExpected[17];
|
|
|
|
Log::Comment(L"Test 1: Check default case.");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
mach.ProcessCharacter(L'm');
|
|
VERIFY_IS_TRUE(pDispatch->_setGraphics);
|
|
|
|
rgExpected[0] = DispatchTypes::GraphicsOptions::Off;
|
|
VerifyDispatchTypes({ rgExpected, 1 }, *pDispatch);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Test 2: Check clear/0 case.");
|
|
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
mach.ProcessCharacter(L'0');
|
|
mach.ProcessCharacter(L'm');
|
|
VERIFY_IS_TRUE(pDispatch->_setGraphics);
|
|
|
|
rgExpected[0] = DispatchTypes::GraphicsOptions::Off;
|
|
VerifyDispatchTypes({ rgExpected, 1 }, *pDispatch);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Test 3: Check 'handful of options' case.");
|
|
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
mach.ProcessCharacter(L'1');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'4');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'7');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'3');
|
|
mach.ProcessCharacter(L'0');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'4');
|
|
mach.ProcessCharacter(L'5');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'5');
|
|
mach.ProcessCharacter(L'3');
|
|
mach.ProcessCharacter(L'm');
|
|
VERIFY_IS_TRUE(pDispatch->_setGraphics);
|
|
|
|
rgExpected[0] = DispatchTypes::GraphicsOptions::BoldBright;
|
|
rgExpected[1] = DispatchTypes::GraphicsOptions::Underline;
|
|
rgExpected[2] = DispatchTypes::GraphicsOptions::Negative;
|
|
rgExpected[3] = DispatchTypes::GraphicsOptions::ForegroundBlack;
|
|
rgExpected[4] = DispatchTypes::GraphicsOptions::BackgroundMagenta;
|
|
rgExpected[5] = DispatchTypes::GraphicsOptions::Overline;
|
|
VerifyDispatchTypes({ rgExpected, 6 }, *pDispatch);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Test 4: Check 'many options' (>16) case.");
|
|
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
mach.ProcessCharacter(L'1');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'4');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'1');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'4');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'1');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'4');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'1');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'4');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'1');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'4');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'1');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'4');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'1');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'4');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'1');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'4');
|
|
mach.ProcessCharacter(L';');
|
|
mach.ProcessCharacter(L'1');
|
|
mach.ProcessCharacter(L'm');
|
|
VERIFY_IS_TRUE(pDispatch->_setGraphics);
|
|
|
|
rgExpected[0] = DispatchTypes::GraphicsOptions::BoldBright;
|
|
rgExpected[1] = DispatchTypes::GraphicsOptions::Underline;
|
|
rgExpected[2] = DispatchTypes::GraphicsOptions::BoldBright;
|
|
rgExpected[3] = DispatchTypes::GraphicsOptions::Underline;
|
|
rgExpected[4] = DispatchTypes::GraphicsOptions::BoldBright;
|
|
rgExpected[5] = DispatchTypes::GraphicsOptions::Underline;
|
|
rgExpected[6] = DispatchTypes::GraphicsOptions::BoldBright;
|
|
rgExpected[7] = DispatchTypes::GraphicsOptions::Underline;
|
|
rgExpected[8] = DispatchTypes::GraphicsOptions::BoldBright;
|
|
rgExpected[9] = DispatchTypes::GraphicsOptions::Underline;
|
|
rgExpected[10] = DispatchTypes::GraphicsOptions::BoldBright;
|
|
rgExpected[11] = DispatchTypes::GraphicsOptions::Underline;
|
|
rgExpected[12] = DispatchTypes::GraphicsOptions::BoldBright;
|
|
rgExpected[13] = DispatchTypes::GraphicsOptions::Underline;
|
|
rgExpected[14] = DispatchTypes::GraphicsOptions::BoldBright;
|
|
rgExpected[15] = DispatchTypes::GraphicsOptions::Underline;
|
|
rgExpected[16] = DispatchTypes::GraphicsOptions::BoldBright;
|
|
VerifyDispatchTypes({ rgExpected, 17 }, *pDispatch);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Test 5.a: Test an empty param at the end of a sequence");
|
|
|
|
std::wstring sequence = L"\x1b[1;m";
|
|
mach.ProcessString(sequence);
|
|
VERIFY_IS_TRUE(pDispatch->_setGraphics);
|
|
|
|
rgExpected[0] = DispatchTypes::GraphicsOptions::BoldBright;
|
|
rgExpected[1] = DispatchTypes::GraphicsOptions::Off;
|
|
VerifyDispatchTypes({ rgExpected, 2 }, *pDispatch);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Test 5.b: Test an empty param in the middle of a sequence");
|
|
|
|
sequence = L"\x1b[1;;1m";
|
|
mach.ProcessString(sequence);
|
|
VERIFY_IS_TRUE(pDispatch->_setGraphics);
|
|
|
|
rgExpected[0] = DispatchTypes::GraphicsOptions::BoldBright;
|
|
rgExpected[1] = DispatchTypes::GraphicsOptions::Off;
|
|
rgExpected[2] = DispatchTypes::GraphicsOptions::BoldBright;
|
|
VerifyDispatchTypes({ rgExpected, 3 }, *pDispatch);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Test 5.c: Test an empty param at the start of a sequence");
|
|
|
|
sequence = L"\x1b[;31;1m";
|
|
mach.ProcessString(sequence);
|
|
VERIFY_IS_TRUE(pDispatch->_setGraphics);
|
|
|
|
rgExpected[0] = DispatchTypes::GraphicsOptions::Off;
|
|
rgExpected[1] = DispatchTypes::GraphicsOptions::ForegroundRed;
|
|
rgExpected[2] = DispatchTypes::GraphicsOptions::BoldBright;
|
|
VerifyDispatchTypes({ rgExpected, 3 }, *pDispatch);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestDeviceStatusReport)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
Log::Comment(L"Test 1: Check OS (operating status) case 5. Should succeed.");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
mach.ProcessCharacter(L'5');
|
|
mach.ProcessCharacter(L'n');
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_deviceStatusReport);
|
|
VERIFY_ARE_EQUAL(DispatchTypes::AnsiStatusType::OS_OperatingStatus, pDispatch->_statusReportType);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Test 2: Check CPR (cursor position report) case 6. Should succeed.");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
mach.ProcessCharacter(L'6');
|
|
mach.ProcessCharacter(L'n');
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_deviceStatusReport);
|
|
VERIFY_ARE_EQUAL(DispatchTypes::AnsiStatusType::CPR_CursorPositionReport, pDispatch->_statusReportType);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestDeviceAttributes)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
Log::Comment(L"Test 1: Check default case, no params.");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
mach.ProcessCharacter(L'c');
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_deviceAttributes);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Test 2: Check default case, 0 param.");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
mach.ProcessCharacter(L'0');
|
|
mach.ProcessCharacter(L'c');
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_deviceAttributes);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Test 3: Check fail case, 1 (or any other) param.");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
mach.ProcessCharacter(L'1');
|
|
mach.ProcessCharacter(L'c');
|
|
|
|
VERIFY_IS_FALSE(pDispatch->_deviceAttributes);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestSecondaryDeviceAttributes)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
Log::Comment(L"Test 1: Check default case, no params.");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
mach.ProcessCharacter(L'>');
|
|
mach.ProcessCharacter(L'c');
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_secondaryDeviceAttributes);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Test 2: Check default case, 0 param.");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
mach.ProcessCharacter(L'>');
|
|
mach.ProcessCharacter(L'0');
|
|
mach.ProcessCharacter(L'c');
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_secondaryDeviceAttributes);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Test 3: Check fail case, 1 (or any other) param.");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
mach.ProcessCharacter(L'>');
|
|
mach.ProcessCharacter(L'1');
|
|
mach.ProcessCharacter(L'c');
|
|
|
|
VERIFY_IS_FALSE(pDispatch->_secondaryDeviceAttributes);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestTertiaryDeviceAttributes)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
Log::Comment(L"Test 1: Check default case, no params.");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
mach.ProcessCharacter(L'=');
|
|
mach.ProcessCharacter(L'c');
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_tertiaryDeviceAttributes);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Test 2: Check default case, 0 param.");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
mach.ProcessCharacter(L'=');
|
|
mach.ProcessCharacter(L'0');
|
|
mach.ProcessCharacter(L'c');
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_tertiaryDeviceAttributes);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Test 3: Check fail case, 1 (or any other) param.");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
mach.ProcessCharacter(L'=');
|
|
mach.ProcessCharacter(L'1');
|
|
mach.ProcessCharacter(L'c');
|
|
|
|
VERIFY_IS_FALSE(pDispatch->_tertiaryDeviceAttributes);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestRequestTerminalParameters)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
Log::Comment(L"Test 1: Check default case, no params.");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
mach.ProcessCharacter(L'x');
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_requestTerminalParameters);
|
|
VERIFY_ARE_EQUAL(DispatchTypes::ReportingPermission::Unsolicited, pDispatch->_reportingPermission);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Test 2: Check unsolicited permission, 0 param.");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
mach.ProcessCharacter(L'0');
|
|
mach.ProcessCharacter(L'x');
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_requestTerminalParameters);
|
|
VERIFY_ARE_EQUAL(DispatchTypes::ReportingPermission::Unsolicited, pDispatch->_reportingPermission);
|
|
|
|
Log::Comment(L"Test 3: Check solicited permission, 1 param.");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'[');
|
|
mach.ProcessCharacter(L'1');
|
|
mach.ProcessCharacter(L'x');
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_requestTerminalParameters);
|
|
VERIFY_ARE_EQUAL(DispatchTypes::ReportingPermission::Solicited, pDispatch->_reportingPermission);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestStrings)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
DispatchTypes::GraphicsOptions rgExpected[16];
|
|
DispatchTypes::EraseType expectedDispatchTypes;
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
Log::Comment(L"Test 1: Basic String processing. One sequence in a string.");
|
|
mach.ProcessString(L"\x1b[0m");
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_setGraphics);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
Log::Comment(L"Test 2: A couple of sequences all in one string");
|
|
|
|
mach.ProcessString(L"\x1b[1;4;7;30;45;53m\x1b[2J");
|
|
VERIFY_IS_TRUE(pDispatch->_setGraphics);
|
|
VERIFY_IS_TRUE(pDispatch->_eraseDisplay);
|
|
|
|
rgExpected[0] = DispatchTypes::GraphicsOptions::BoldBright;
|
|
rgExpected[1] = DispatchTypes::GraphicsOptions::Underline;
|
|
rgExpected[2] = DispatchTypes::GraphicsOptions::Negative;
|
|
rgExpected[3] = DispatchTypes::GraphicsOptions::ForegroundBlack;
|
|
rgExpected[4] = DispatchTypes::GraphicsOptions::BackgroundMagenta;
|
|
rgExpected[5] = DispatchTypes::GraphicsOptions::Overline;
|
|
expectedDispatchTypes = DispatchTypes::EraseType::All;
|
|
VerifyDispatchTypes({ rgExpected, 6 }, *pDispatch);
|
|
VERIFY_ARE_EQUAL(expectedDispatchTypes, pDispatch->_eraseType);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
Log::Comment(L"Test 3: Two sequences separated by a non-sequence of characters");
|
|
|
|
mach.ProcessString(L"\x1b[1;30mHello World\x1b[2J");
|
|
|
|
rgExpected[0] = DispatchTypes::GraphicsOptions::BoldBright;
|
|
rgExpected[1] = DispatchTypes::GraphicsOptions::ForegroundBlack;
|
|
expectedDispatchTypes = DispatchTypes::EraseType::All;
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_setGraphics);
|
|
VERIFY_IS_TRUE(pDispatch->_eraseDisplay);
|
|
|
|
VerifyDispatchTypes({ rgExpected, 2 }, *pDispatch);
|
|
VERIFY_ARE_EQUAL(expectedDispatchTypes, pDispatch->_eraseType);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
Log::Comment(L"Test 4: An entire sequence broke into multiple strings");
|
|
mach.ProcessString(L"\x1b[1;");
|
|
VERIFY_IS_FALSE(pDispatch->_setGraphics);
|
|
VERIFY_IS_FALSE(pDispatch->_eraseDisplay);
|
|
|
|
mach.ProcessString(L"30mHello World\x1b[2J");
|
|
|
|
rgExpected[0] = DispatchTypes::GraphicsOptions::BoldBright;
|
|
rgExpected[1] = DispatchTypes::GraphicsOptions::ForegroundBlack;
|
|
expectedDispatchTypes = DispatchTypes::EraseType::All;
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_setGraphics);
|
|
VERIFY_IS_TRUE(pDispatch->_eraseDisplay);
|
|
|
|
VerifyDispatchTypes({ rgExpected, 2 }, *pDispatch);
|
|
VERIFY_ARE_EQUAL(expectedDispatchTypes, pDispatch->_eraseType);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
Log::Comment(L"Test 5: A sequence with mixed ProcessCharacter and ProcessString calls");
|
|
|
|
rgExpected[0] = DispatchTypes::GraphicsOptions::BoldBright;
|
|
rgExpected[1] = DispatchTypes::GraphicsOptions::ForegroundBlack;
|
|
|
|
mach.ProcessString(L"\x1b[1;");
|
|
VERIFY_IS_FALSE(pDispatch->_setGraphics);
|
|
VERIFY_IS_FALSE(pDispatch->_eraseDisplay);
|
|
|
|
mach.ProcessCharacter(L'3');
|
|
VERIFY_IS_FALSE(pDispatch->_setGraphics);
|
|
VERIFY_IS_FALSE(pDispatch->_eraseDisplay);
|
|
|
|
mach.ProcessCharacter(L'0');
|
|
VERIFY_IS_FALSE(pDispatch->_setGraphics);
|
|
VERIFY_IS_FALSE(pDispatch->_eraseDisplay);
|
|
|
|
mach.ProcessCharacter(L'm');
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_setGraphics);
|
|
VERIFY_IS_FALSE(pDispatch->_eraseDisplay);
|
|
VerifyDispatchTypes({ rgExpected, 2 }, *pDispatch);
|
|
|
|
mach.ProcessString(L"Hello World\x1b[2J");
|
|
|
|
expectedDispatchTypes = DispatchTypes::EraseType::All;
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_eraseDisplay);
|
|
|
|
VERIFY_ARE_EQUAL(expectedDispatchTypes, pDispatch->_eraseType);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestLineFeed)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
Log::Comment(L"IND (Index) escape sequence");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'D');
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_lineFeed);
|
|
VERIFY_ARE_EQUAL(DispatchTypes::LineFeedType::WithoutReturn, pDispatch->_lineFeedType);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"NEL (Next Line) escape sequence");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'E');
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_lineFeed);
|
|
VERIFY_ARE_EQUAL(DispatchTypes::LineFeedType::WithReturn, pDispatch->_lineFeedType);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"LF (Line Feed) control code");
|
|
mach.ProcessCharacter(AsciiChars::LF);
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_lineFeed);
|
|
VERIFY_ARE_EQUAL(DispatchTypes::LineFeedType::DependsOnMode, pDispatch->_lineFeedType);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"FF (Form Feed) control code");
|
|
mach.ProcessCharacter(AsciiChars::FF);
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_lineFeed);
|
|
VERIFY_ARE_EQUAL(DispatchTypes::LineFeedType::DependsOnMode, pDispatch->_lineFeedType);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"VT (Vertical Tab) control code");
|
|
mach.ProcessCharacter(AsciiChars::VT);
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_lineFeed);
|
|
VERIFY_ARE_EQUAL(DispatchTypes::LineFeedType::DependsOnMode, pDispatch->_lineFeedType);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestControlCharacters)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
Log::Comment(L"BEL (Warning Bell) control character");
|
|
mach.ProcessCharacter(AsciiChars::BEL);
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_warningBell);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"BS (Back Space) control character");
|
|
mach.ProcessCharacter(AsciiChars::BS);
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_cursorBackward);
|
|
VERIFY_ARE_EQUAL(1u, pDispatch->_cursorDistance);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"CR (Carriage Return) control character");
|
|
mach.ProcessCharacter(AsciiChars::CR);
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_carriageReturn);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"HT (Horizontal Tab) control character");
|
|
mach.ProcessCharacter(AsciiChars::TAB);
|
|
|
|
VERIFY_IS_TRUE(pDispatch->_forwardTab);
|
|
VERIFY_ARE_EQUAL(1u, pDispatch->_numTabs);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestTabClear)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
mach.ProcessString(L"\x1b[g");
|
|
auto expectedClearTypes = std::vector{ DispatchTypes::TabClearType::ClearCurrentColumn };
|
|
VERIFY_IS_TRUE(pDispatch->_tabClear);
|
|
VERIFY_ARE_EQUAL(expectedClearTypes, pDispatch->_tabClearTypes);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\x1b[3g");
|
|
expectedClearTypes = std::vector{ DispatchTypes::TabClearType::ClearAllColumns };
|
|
VERIFY_IS_TRUE(pDispatch->_tabClear);
|
|
VERIFY_ARE_EQUAL(expectedClearTypes, pDispatch->_tabClearTypes);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\x1b[0;3g");
|
|
expectedClearTypes = std::vector{ DispatchTypes::TabClearType::ClearCurrentColumn, DispatchTypes::TabClearType::ClearAllColumns };
|
|
VERIFY_IS_TRUE(pDispatch->_tabClear);
|
|
VERIFY_ARE_EQUAL(expectedClearTypes, pDispatch->_tabClearTypes);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestVt52Sequences)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
// ANSI mode must be reset for VT52 sequences to be recognized.
|
|
mach.SetAnsiMode(false);
|
|
|
|
Log::Comment(L"Cursor Up");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'A');
|
|
VERIFY_IS_TRUE(pDispatch->_cursorUp);
|
|
VERIFY_ARE_EQUAL(1u, pDispatch->_cursorDistance);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Cursor Down");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'B');
|
|
VERIFY_IS_TRUE(pDispatch->_cursorDown);
|
|
VERIFY_ARE_EQUAL(1u, pDispatch->_cursorDistance);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Cursor Right");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'C');
|
|
VERIFY_IS_TRUE(pDispatch->_cursorForward);
|
|
VERIFY_ARE_EQUAL(1u, pDispatch->_cursorDistance);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Cursor Left");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'D');
|
|
VERIFY_IS_TRUE(pDispatch->_cursorBackward);
|
|
VERIFY_ARE_EQUAL(1u, pDispatch->_cursorDistance);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Cursor to Home");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'H');
|
|
VERIFY_IS_TRUE(pDispatch->_cursorPosition);
|
|
VERIFY_ARE_EQUAL(1u, pDispatch->_line);
|
|
VERIFY_ARE_EQUAL(1u, pDispatch->_column);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Reverse Line Feed");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'I');
|
|
VERIFY_IS_TRUE(pDispatch->_reverseLineFeed);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Erase to End of Screen");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'J');
|
|
VERIFY_IS_TRUE(pDispatch->_eraseDisplay);
|
|
VERIFY_ARE_EQUAL(DispatchTypes::EraseType::ToEnd, pDispatch->_eraseType);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Erase to End of Line");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'K');
|
|
VERIFY_IS_TRUE(pDispatch->_eraseLine);
|
|
VERIFY_ARE_EQUAL(DispatchTypes::EraseType::ToEnd, pDispatch->_eraseType);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Direct Cursor Address");
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'Y');
|
|
mach.ProcessCharacter(L' ' + 3); // Coordinates must be printable ASCII values,
|
|
mach.ProcessCharacter(L' ' + 5); // so are relative to 0x20 (the space character).
|
|
VERIFY_IS_TRUE(pDispatch->_cursorPosition);
|
|
VERIFY_ARE_EQUAL(3u, pDispatch->_line - 1); // CursorPosition coordinates are 1-based,
|
|
VERIFY_ARE_EQUAL(5u, pDispatch->_column - 1); // so are 1 more than the expected values.
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestIdentifyDeviceReport)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
Log::Comment(L"Identify Device in VT52 mode.");
|
|
mach.SetAnsiMode(false);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'Z');
|
|
VERIFY_IS_TRUE(pDispatch->_vt52DeviceAttributes);
|
|
VERIFY_IS_FALSE(pDispatch->_deviceAttributes);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
Log::Comment(L"Identify Device in ANSI mode.");
|
|
mach.SetAnsiMode(true);
|
|
mach.ProcessCharacter(AsciiChars::ESC);
|
|
mach.ProcessCharacter(L'Z');
|
|
VERIFY_IS_TRUE(pDispatch->_deviceAttributes);
|
|
VERIFY_IS_FALSE(pDispatch->_vt52DeviceAttributes);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestOscSetDefaultForeground)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
// Single param
|
|
mach.ProcessString(L"\033]10;rgb:1/1/1\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
|
|
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_defaultForegroundColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]10;rgb:12/34/56\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
|
|
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_defaultForegroundColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]10;#111\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]10;#123456\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
|
|
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_defaultForegroundColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]10;DarkOrange\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
|
|
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_defaultForegroundColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
// Multiple params
|
|
mach.ProcessString(L"\033]10;#111;rgb:2/2/2\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor);
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
|
|
VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_defaultBackgroundColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]10;#111;DarkOrange\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor);
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
|
|
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_defaultBackgroundColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]10;#111;DarkOrange;rgb:2/2/2\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor);
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
|
|
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_defaultBackgroundColor);
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultCursorColor);
|
|
VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_defaultCursorColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
// Partially valid multi-param sequences.
|
|
mach.ProcessString(L"\033]10;#111;\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]10;#111;rgb:\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor);
|
|
VERIFY_IS_FALSE(pDispatch->_setDefaultBackground);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]10;#111;#2\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultForeground);
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultForegroundColor);
|
|
VERIFY_IS_FALSE(pDispatch->_setDefaultBackground);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]10;;rgb:1/1/1\033\\");
|
|
VERIFY_IS_FALSE(pDispatch->_setDefaultForeground);
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
|
|
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_defaultBackgroundColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]10;#1;rgb:1/1/1\033\\");
|
|
VERIFY_IS_FALSE(pDispatch->_setDefaultForeground);
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
|
|
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_defaultBackgroundColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
// Invalid sequences.
|
|
mach.ProcessString(L"\033]10;rgb:1/1/\033\\");
|
|
VERIFY_IS_FALSE(pDispatch->_setDefaultForeground);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]10;#1\033\\");
|
|
VERIFY_IS_FALSE(pDispatch->_setDefaultForeground);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestOscSetDefaultBackground)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
mach.ProcessString(L"\033]11;rgb:1/1/1\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
|
|
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_defaultBackgroundColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
// Single param
|
|
mach.ProcessString(L"\033]11;rgb:12/34/56\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
|
|
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_defaultBackgroundColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]11;#111\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]11;#123456\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
|
|
VERIFY_ARE_EQUAL(RGB(0x12, 0x34, 0x56), pDispatch->_defaultBackgroundColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]11;DarkOrange\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
|
|
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_defaultBackgroundColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
// Multiple params
|
|
mach.ProcessString(L"\033]11;#111;rgb:2/2/2\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor);
|
|
VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_defaultCursorColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]11;#111;DarkOrange\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor);
|
|
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_defaultCursorColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]11;#111;DarkOrange;rgb:2/2/2\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor);
|
|
VERIFY_ARE_EQUAL(RGB(255, 140, 0), pDispatch->_defaultCursorColor);
|
|
// The third param is out of range.
|
|
|
|
pDispatch->ClearState();
|
|
|
|
// Partially valid multi-param sequences.
|
|
mach.ProcessString(L"\033]11;#111;\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]11;#111;rgb:\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]11;#111;#2\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultBackground);
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_defaultBackgroundColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]11;;rgb:1/1/1\033\\");
|
|
VERIFY_IS_FALSE(pDispatch->_setDefaultBackground);
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultCursorColor);
|
|
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_defaultCursorColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]11;#1;rgb:1/1/1\033\\");
|
|
VERIFY_IS_FALSE(pDispatch->_setDefaultBackground);
|
|
VERIFY_IS_TRUE(pDispatch->_setDefaultCursorColor);
|
|
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_defaultCursorColor);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
// Invalid sequences.
|
|
mach.ProcessString(L"\033]11;rgb:1/1/\033\\");
|
|
VERIFY_IS_FALSE(pDispatch->_setDefaultBackground);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]11;#1\033\\");
|
|
VERIFY_IS_FALSE(pDispatch->_setDefaultBackground);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestOscSetColorTableEntry)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
mach.ProcessString(L"\033]4;0;rgb:1/1/1\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setColorTableEntry);
|
|
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_colorTable.at(0));
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]4;16;rgb:11/11/11\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setColorTableEntry);
|
|
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_colorTable.at(16));
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]4;64;#111\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setColorTableEntry);
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_colorTable.at(64));
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]4;128;orange\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setColorTableEntry);
|
|
VERIFY_ARE_EQUAL(RGB(255, 165, 0), pDispatch->_colorTable.at(128));
|
|
|
|
pDispatch->ClearState();
|
|
|
|
// Invalid sequences.
|
|
mach.ProcessString(L"\033]4;\033\\");
|
|
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]4;;\033\\");
|
|
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]4;0\033\\");
|
|
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]4;111\033\\");
|
|
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]4;#111\033\\");
|
|
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]4;1;111\033\\");
|
|
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]4;1;rgb:\033\\");
|
|
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
// Multiple params.
|
|
mach.ProcessString(L"\033]4;0;rgb:1/1/1;16;rgb:2/2/2\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setColorTableEntry);
|
|
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_colorTable.at(0));
|
|
VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_colorTable.at(16));
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]4;0;rgb:1/1/1;16;rgb:2/2/2;64;#111\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setColorTableEntry);
|
|
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_colorTable.at(0));
|
|
VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_colorTable.at(16));
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_colorTable.at(64));
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]4;0;rgb:1/1/1;16;rgb:2/2/2;64;#111;128;orange\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setColorTableEntry);
|
|
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_colorTable.at(0));
|
|
VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_colorTable.at(16));
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_colorTable.at(64));
|
|
VERIFY_ARE_EQUAL(RGB(255, 165, 0), pDispatch->_colorTable.at(128));
|
|
|
|
pDispatch->ClearState();
|
|
|
|
// Partially valid sequences. Valid colors should not be affected by invalid colors.
|
|
mach.ProcessString(L"\033]4;0;rgb:11;1;rgb:2/2/2;2;#111;3;orange;4;#111\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setColorTableEntry);
|
|
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(0));
|
|
VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_colorTable.at(1));
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_colorTable.at(2));
|
|
VERIFY_ARE_EQUAL(RGB(255, 165, 0), pDispatch->_colorTable.at(3));
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_colorTable.at(4));
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]4;0;rgb:1/1/1;1;rgb:2/2/2;2;#111;3;orange;4;111\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setColorTableEntry);
|
|
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_colorTable.at(0));
|
|
VERIFY_ARE_EQUAL(RGB(0x22, 0x22, 0x22), pDispatch->_colorTable.at(1));
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_colorTable.at(2));
|
|
VERIFY_ARE_EQUAL(RGB(255, 165, 0), pDispatch->_colorTable.at(3));
|
|
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(4));
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]4;0;rgb:1/1/1;1;rgb:2;2;#111;3;orange;4;#222\033\\");
|
|
VERIFY_IS_TRUE(pDispatch->_setColorTableEntry);
|
|
VERIFY_ARE_EQUAL(RGB(0x11, 0x11, 0x11), pDispatch->_colorTable.at(0));
|
|
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(1));
|
|
VERIFY_ARE_EQUAL(RGB(0x10, 0x10, 0x10), pDispatch->_colorTable.at(2));
|
|
VERIFY_ARE_EQUAL(RGB(255, 165, 0), pDispatch->_colorTable.at(3));
|
|
VERIFY_ARE_EQUAL(RGB(0x20, 0x20, 0x20), pDispatch->_colorTable.at(4));
|
|
|
|
pDispatch->ClearState();
|
|
|
|
// Invalid multi-param sequences
|
|
mach.ProcessString(L"\033]4;0;;1;;\033\\");
|
|
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
|
|
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(0));
|
|
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(1));
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]4;0;;;;;1;;;;;\033\\");
|
|
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
|
|
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(0));
|
|
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(1));
|
|
|
|
pDispatch->ClearState();
|
|
|
|
mach.ProcessString(L"\033]4;0;rgb:1/1/;16;rgb:2/2/;64;#11\033\\");
|
|
VERIFY_IS_FALSE(pDispatch->_setColorTableEntry);
|
|
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(0));
|
|
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(16));
|
|
VERIFY_ARE_EQUAL(RGB(0, 0, 0), pDispatch->_colorTable.at(64));
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestSetClipboard)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
// Passing an empty `Pc` param and a base64-encoded simple text `Pd` param works.
|
|
mach.ProcessString(L"\x1b]52;;Zm9v\x07");
|
|
VERIFY_ARE_EQUAL(L"foo", pDispatch->_copyContent);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
// Passing an empty `Pc` param and a base64-encoded multi-lines text `Pd` works.
|
|
mach.ProcessString(L"\x1b]52;;Zm9vDQpiYXI=\x07");
|
|
VERIFY_ARE_EQUAL(L"foo\r\nbar", pDispatch->_copyContent);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
// Passing an empty `Pc` param and a base64-encoded multibyte text `Pd` works.
|
|
// U+306b U+307b U+3093 U+3054 U+6c49 U+8bed U+d55c U+ad6d
|
|
mach.ProcessString(L"\x1b]52;;44Gr44G744KT44GU5rGJ6K+t7ZWc6rWt\x07");
|
|
VERIFY_ARE_EQUAL(L"にほんご汉语한국", pDispatch->_copyContent);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
// Passing an empty `Pc` param and a base64-encoded multibyte text w/ emoji sequences `Pd` works.
|
|
// U+d83d U+dc4d U+d83d U+dc4d U+d83c U+dffb U+d83d U+dc4d U+d83c U+dffc U+d83d
|
|
// U+dc4d U+d83c U+dffd U+d83d U+dc4d U+d83c U+dffe U+d83d U+dc4d U+d83c U+dfff
|
|
mach.ProcessString(L"\x1b]52;;8J+RjfCfkY3wn4+78J+RjfCfj7zwn5GN8J+PvfCfkY3wn4++8J+RjfCfj78=\x07");
|
|
VERIFY_ARE_EQUAL(L"👍👍🏻👍🏼👍🏽👍🏾👍🏿", pDispatch->_copyContent);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
// Passing a non-empty `Pc` param (`s0` is ignored) and a valid `Pd` param works.
|
|
mach.ProcessString(L"\x1b]52;s0;Zm9v\x07");
|
|
VERIFY_ARE_EQUAL(L"foo", pDispatch->_copyContent);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
pDispatch->_copyContent = L"UNCHANGED";
|
|
// Passing only base64 `Pd` param is illegal, won't change the content.
|
|
mach.ProcessString(L"\x1b]52;Zm9v\x07");
|
|
VERIFY_ARE_EQUAL(L"UNCHANGED", pDispatch->_copyContent);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
pDispatch->_copyContent = L"UNCHANGED";
|
|
// Passing a non-base64 `Pd` param is illegal, won't change the content.
|
|
mach.ProcessString(L"\x1b]52;;???\x07");
|
|
VERIFY_ARE_EQUAL(L"UNCHANGED", pDispatch->_copyContent);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
pDispatch->_copyContent = L"UNCHANGED";
|
|
// Passing a valid `Pc;Pd` with one more extra param is illegal, won't change the content.
|
|
mach.ProcessString(L"\x1b]52;;;Zm9v\x07");
|
|
VERIFY_ARE_EQUAL(L"UNCHANGED", pDispatch->_copyContent);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
pDispatch->_copyContent = L"UNCHANGED";
|
|
// Passing a query character won't change the content.
|
|
mach.ProcessString(L"\x1b]52;;?\x07");
|
|
VERIFY_ARE_EQUAL(L"UNCHANGED", pDispatch->_copyContent);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
pDispatch->_copyContent = L"UNCHANGED";
|
|
// Passing a query character with missing `Pc` param is illegal, won't change the content.
|
|
mach.ProcessString(L"\x1b]52;?\x07");
|
|
VERIFY_ARE_EQUAL(L"UNCHANGED", pDispatch->_copyContent);
|
|
|
|
pDispatch->ClearState();
|
|
|
|
pDispatch->_copyContent = L"UNCHANGED";
|
|
// Passing a query character with one more extra param is illegal, won't change the content.
|
|
mach.ProcessString(L"\x1b]52;;;?\x07");
|
|
VERIFY_ARE_EQUAL(L"UNCHANGED", pDispatch->_copyContent);
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
|
|
TEST_METHOD(TestAddHyperlink)
|
|
{
|
|
auto dispatch = std::make_unique<StatefulDispatch>();
|
|
auto pDispatch = dispatch.get();
|
|
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
|
StateMachine mach(std::move(engine));
|
|
|
|
// First we test with no custom id
|
|
// Process the opening osc 8 sequence
|
|
mach.ProcessString(L"\x1b]8;;test.url\x9c");
|
|
VERIFY_IS_TRUE(pDispatch->_hyperlinkMode);
|
|
VERIFY_ARE_EQUAL(pDispatch->_uri, L"test.url");
|
|
VERIFY_IS_TRUE(pDispatch->_customId.empty());
|
|
|
|
// Process the closing osc 8 sequences
|
|
mach.ProcessString(L"\x1b]8;;\x9c");
|
|
VERIFY_IS_FALSE(pDispatch->_hyperlinkMode);
|
|
VERIFY_IS_TRUE(pDispatch->_uri.empty());
|
|
|
|
// Next we test with a custom id
|
|
// Process the opening osc 8 sequence
|
|
mach.ProcessString(L"\x1b]8;id=testId;test2.url\x9c");
|
|
VERIFY_IS_TRUE(pDispatch->_hyperlinkMode);
|
|
VERIFY_ARE_EQUAL(pDispatch->_uri, L"test2.url");
|
|
VERIFY_ARE_EQUAL(pDispatch->_customId, L"testId");
|
|
|
|
// Process the closing osc 8 sequence
|
|
mach.ProcessString(L"\x1b]8;;\x9c");
|
|
VERIFY_IS_FALSE(pDispatch->_hyperlinkMode);
|
|
VERIFY_IS_TRUE(pDispatch->_uri.empty());
|
|
|
|
// Let's try more complicated params and URLs
|
|
mach.ProcessString(L"\x1b]8;id=testId;https://example.com\x9c");
|
|
VERIFY_IS_TRUE(pDispatch->_hyperlinkMode);
|
|
VERIFY_ARE_EQUAL(pDispatch->_uri, L"https://example.com");
|
|
VERIFY_ARE_EQUAL(pDispatch->_customId, L"testId");
|
|
|
|
mach.ProcessString(L"\x1b]8;;\x9c");
|
|
VERIFY_IS_FALSE(pDispatch->_hyperlinkMode);
|
|
VERIFY_IS_TRUE(pDispatch->_uri.empty());
|
|
|
|
// Multiple params
|
|
mach.ProcessString(L"\x1b]8;id=testId:foo=bar;https://example.com\x9c");
|
|
VERIFY_IS_TRUE(pDispatch->_hyperlinkMode);
|
|
VERIFY_ARE_EQUAL(pDispatch->_uri, L"https://example.com");
|
|
VERIFY_ARE_EQUAL(pDispatch->_customId, L"testId");
|
|
|
|
mach.ProcessString(L"\x1b]8;;\x9c");
|
|
VERIFY_IS_FALSE(pDispatch->_hyperlinkMode);
|
|
VERIFY_IS_TRUE(pDispatch->_uri.empty());
|
|
|
|
mach.ProcessString(L"\x1b]8;foo=bar:id=testId;https://example.com\x9c");
|
|
VERIFY_IS_TRUE(pDispatch->_hyperlinkMode);
|
|
VERIFY_ARE_EQUAL(pDispatch->_uri, L"https://example.com");
|
|
VERIFY_ARE_EQUAL(pDispatch->_customId, L"testId");
|
|
|
|
mach.ProcessString(L"\x1b]8;;\x9c");
|
|
VERIFY_IS_FALSE(pDispatch->_hyperlinkMode);
|
|
VERIFY_IS_TRUE(pDispatch->_uri.empty());
|
|
|
|
// URIs with query strings
|
|
mach.ProcessString(L"\x1b]8;id=testId;https://example.com?query1=value1\x9c");
|
|
VERIFY_IS_TRUE(pDispatch->_hyperlinkMode);
|
|
VERIFY_ARE_EQUAL(pDispatch->_uri, L"https://example.com?query1=value1");
|
|
VERIFY_ARE_EQUAL(pDispatch->_customId, L"testId");
|
|
|
|
mach.ProcessString(L"\x1b]8;;\x9c");
|
|
VERIFY_IS_FALSE(pDispatch->_hyperlinkMode);
|
|
VERIFY_IS_TRUE(pDispatch->_uri.empty());
|
|
|
|
mach.ProcessString(L"\x1b]8;id=testId;https://example.com?query1=value1;value2;value3\x9c");
|
|
VERIFY_IS_TRUE(pDispatch->_hyperlinkMode);
|
|
VERIFY_ARE_EQUAL(pDispatch->_uri, L"https://example.com?query1=value1;value2;value3");
|
|
VERIFY_ARE_EQUAL(pDispatch->_customId, L"testId");
|
|
|
|
mach.ProcessString(L"\x1b]8;;\x9c");
|
|
VERIFY_IS_FALSE(pDispatch->_hyperlinkMode);
|
|
VERIFY_IS_TRUE(pDispatch->_uri.empty());
|
|
|
|
pDispatch->ClearState();
|
|
}
|
|
};
|