mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-21 06:18:34 +00:00
Compare commits
8 Commits
dev/duhowe
...
dev/lhecke
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d165136852 | ||
|
|
c77b79fb8d | ||
|
|
28fe5d81d7 | ||
|
|
318328d475 | ||
|
|
605ba75cea | ||
|
|
7ad3277136 | ||
|
|
e51769b072 | ||
|
|
c83fbbb89e |
@@ -23,7 +23,7 @@ namespace Microsoft::Console::VirtualTerminal
|
|||||||
class Microsoft::Console::VirtualTerminal::ITermDispatch
|
class Microsoft::Console::VirtualTerminal::ITermDispatch
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using StringHandler = std::function<bool(const wchar_t)>;
|
using StringHandler = std::function<bool(std::wstring_view)>;
|
||||||
|
|
||||||
enum class OptionalFeature
|
enum class OptionalFeature
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -113,92 +113,96 @@ bool MacroBuffer::InitParser(const size_t macroId, const DispatchTypes::MacroDel
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacroBuffer::ParseDefinition(const wchar_t ch)
|
bool MacroBuffer::ParseDefinition(const std::wstring_view str)
|
||||||
{
|
{
|
||||||
// Once we receive an ESC, that marks the end of the definition, but if
|
for (const auto ch : str)
|
||||||
// an unterminated repeat is still pending, we should apply that now.
|
|
||||||
if (ch == AsciiChars::ESC)
|
|
||||||
{
|
{
|
||||||
if (_repeatPending && !_applyPendingRepeat())
|
// Once we receive an ESC, that marks the end of the definition, but if
|
||||||
|
// an unterminated repeat is still pending, we should apply that now.
|
||||||
|
if (ch == AsciiChars::ESC)
|
||||||
|
{
|
||||||
|
if (_repeatPending && !_applyPendingRepeat())
|
||||||
|
{
|
||||||
|
_deleteMacro(_activeMacro());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any other control characters are just ignored.
|
||||||
|
if (ch < L' ')
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For "text encoded" macros, we'll always be in the ExpectingText state.
|
||||||
|
// For "hex encoded" macros, we'll typically be alternating between the
|
||||||
|
// ExpectingHexDigit and ExpectingSecondHexDigit states as we parse the two
|
||||||
|
// digits of each hex pair. But we also need to deal with repeat sequences,
|
||||||
|
// which start with `!`, followed by a numeric repeat count, and then a
|
||||||
|
// range of hex pairs between two `;` characters. When parsing the repeat
|
||||||
|
// count, we use the ExpectingRepeatCount state, but when parsing the hex
|
||||||
|
// pairs of the repeat, we just use the regular ExpectingHexDigit states.
|
||||||
|
|
||||||
|
auto success = true;
|
||||||
|
switch (_parseState)
|
||||||
|
{
|
||||||
|
case State::ExpectingText:
|
||||||
|
success = _appendToActiveMacro(ch);
|
||||||
|
break;
|
||||||
|
case State::ExpectingHexDigit:
|
||||||
|
if (_decodeHexDigit(ch))
|
||||||
|
{
|
||||||
|
_parseState = State::ExpectingSecondHexDigit;
|
||||||
|
}
|
||||||
|
else if (ch == L'!' && !_repeatPending)
|
||||||
|
{
|
||||||
|
_parseState = State::ExpectingRepeatCount;
|
||||||
|
_repeatCount = 0;
|
||||||
|
}
|
||||||
|
else if (ch == L';' && _repeatPending)
|
||||||
|
{
|
||||||
|
success = _applyPendingRepeat();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case State::ExpectingSecondHexDigit:
|
||||||
|
success = _decodeHexDigit(ch) && _appendToActiveMacro(_decodedChar);
|
||||||
|
_decodedChar = 0;
|
||||||
|
_parseState = State::ExpectingHexDigit;
|
||||||
|
break;
|
||||||
|
case State::ExpectingRepeatCount:
|
||||||
|
if (ch >= L'0' && ch <= L'9')
|
||||||
|
{
|
||||||
|
_repeatCount = _repeatCount * 10 + (ch - L'0');
|
||||||
|
_repeatCount = std::min<size_t>(_repeatCount, MAX_PARAMETER_VALUE);
|
||||||
|
}
|
||||||
|
else if (ch == L';')
|
||||||
|
{
|
||||||
|
_repeatPending = true;
|
||||||
|
_repeatStart = _activeMacro().length();
|
||||||
|
_parseState = State::ExpectingHexDigit;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
success = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is an error in the definition, clear everything received so far.
|
||||||
|
if (!success)
|
||||||
{
|
{
|
||||||
_deleteMacro(_activeMacro());
|
_deleteMacro(_activeMacro());
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
// Any other control characters are just ignored.
|
|
||||||
if (ch < L' ')
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For "text encoded" macros, we'll always be in the ExpectingText state.
|
|
||||||
// For "hex encoded" macros, we'll typically be alternating between the
|
|
||||||
// ExpectingHexDigit and ExpectingSecondHexDigit states as we parse the two
|
|
||||||
// digits of each hex pair. But we also need to deal with repeat sequences,
|
|
||||||
// which start with `!`, followed by a numeric repeat count, and then a
|
|
||||||
// range of hex pairs between two `;` characters. When parsing the repeat
|
|
||||||
// count, we use the ExpectingRepeatCount state, but when parsing the hex
|
|
||||||
// pairs of the repeat, we just use the regular ExpectingHexDigit states.
|
|
||||||
|
|
||||||
auto success = true;
|
|
||||||
switch (_parseState)
|
|
||||||
{
|
|
||||||
case State::ExpectingText:
|
|
||||||
success = _appendToActiveMacro(ch);
|
|
||||||
break;
|
|
||||||
case State::ExpectingHexDigit:
|
|
||||||
if (_decodeHexDigit(ch))
|
|
||||||
{
|
|
||||||
_parseState = State::ExpectingSecondHexDigit;
|
|
||||||
}
|
|
||||||
else if (ch == L'!' && !_repeatPending)
|
|
||||||
{
|
|
||||||
_parseState = State::ExpectingRepeatCount;
|
|
||||||
_repeatCount = 0;
|
|
||||||
}
|
|
||||||
else if (ch == L';' && _repeatPending)
|
|
||||||
{
|
|
||||||
success = _applyPendingRepeat();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case State::ExpectingSecondHexDigit:
|
|
||||||
success = _decodeHexDigit(ch) && _appendToActiveMacro(_decodedChar);
|
|
||||||
_decodedChar = 0;
|
|
||||||
_parseState = State::ExpectingHexDigit;
|
|
||||||
break;
|
|
||||||
case State::ExpectingRepeatCount:
|
|
||||||
if (ch >= L'0' && ch <= L'9')
|
|
||||||
{
|
|
||||||
_repeatCount = _repeatCount * 10 + (ch - L'0');
|
|
||||||
_repeatCount = std::min<size_t>(_repeatCount, MAX_PARAMETER_VALUE);
|
|
||||||
}
|
|
||||||
else if (ch == L';')
|
|
||||||
{
|
|
||||||
_repeatPending = true;
|
|
||||||
_repeatStart = _activeMacro().length();
|
|
||||||
_parseState = State::ExpectingHexDigit;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
success = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is an error in the definition, clear everything received so far.
|
|
||||||
if (!success)
|
|
||||||
{
|
|
||||||
_deleteMacro(_activeMacro());
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MacroBuffer::_decodeHexDigit(const wchar_t ch) noexcept
|
bool MacroBuffer::_decodeHexDigit(const wchar_t ch) noexcept
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace Microsoft::Console::VirtualTerminal
|
|||||||
void InvokeMacro(const size_t macroId, StateMachine& stateMachine);
|
void InvokeMacro(const size_t macroId, StateMachine& stateMachine);
|
||||||
void ClearMacrosIfInUse();
|
void ClearMacrosIfInUse();
|
||||||
bool InitParser(const size_t macroId, const DispatchTypes::MacroDeleteControl deleteControl, const DispatchTypes::MacroEncoding encoding);
|
bool InitParser(const size_t macroId, const DispatchTypes::MacroDeleteControl deleteControl, const DispatchTypes::MacroEncoding encoding);
|
||||||
bool ParseDefinition(const wchar_t ch);
|
bool ParseDefinition(std::wstring_view str);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool _decodeHexDigit(const wchar_t ch) noexcept;
|
bool _decodeHexDigit(const wchar_t ch) noexcept;
|
||||||
|
|||||||
@@ -42,9 +42,8 @@ size_t SixelParser::MaxColorsForLevel(const VTInt conformanceLevel) noexcept
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SixelParser::SixelParser(AdaptDispatch& dispatcher, const StateMachine& stateMachine, const VTInt conformanceLevel) noexcept :
|
SixelParser::SixelParser(AdaptDispatch& dispatcher, const VTInt conformanceLevel) noexcept :
|
||||||
_dispatcher{ dispatcher },
|
_dispatcher{ dispatcher },
|
||||||
_stateMachine{ stateMachine },
|
|
||||||
_conformanceLevel{ conformanceLevel },
|
_conformanceLevel{ conformanceLevel },
|
||||||
_cellSize{ CellSizeForLevel(conformanceLevel) },
|
_cellSize{ CellSizeForLevel(conformanceLevel) },
|
||||||
_maxColors{ MaxColorsForLevel(conformanceLevel) }
|
_maxColors{ MaxColorsForLevel(conformanceLevel) }
|
||||||
@@ -80,7 +79,7 @@ void SixelParser::SetDisplayMode(const bool enabled) noexcept
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::function<bool(wchar_t)> SixelParser::DefineImage(const VTInt macroParameter, const DispatchTypes::SixelBackground backgroundSelect, const VTParameter backgroundColor)
|
std::function<bool(std::wstring_view)> SixelParser::DefineImage(const VTInt macroParameter, const DispatchTypes::SixelBackground backgroundSelect, const VTParameter backgroundColor)
|
||||||
{
|
{
|
||||||
if (_initTextBufferBoundaries())
|
if (_initTextBufferBoundaries())
|
||||||
{
|
{
|
||||||
@@ -89,8 +88,17 @@ std::function<bool(wchar_t)> SixelParser::DefineImage(const VTInt macroParameter
|
|||||||
_initImageBuffer();
|
_initImageBuffer();
|
||||||
_state = States::Normal;
|
_state = States::Normal;
|
||||||
_parameters.clear();
|
_parameters.clear();
|
||||||
return [&](const auto ch) {
|
return [&](const std::wstring_view str) {
|
||||||
_parseCommandChar(ch);
|
auto it = str.begin();
|
||||||
|
const auto end = str.end();
|
||||||
|
|
||||||
|
while (it != end)
|
||||||
|
{
|
||||||
|
const auto ch = *it++;
|
||||||
|
_isProcessingLastCharacter = it == end;
|
||||||
|
_parseCommandChar(ch);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -554,7 +562,7 @@ void SixelParser::_defineColor(const size_t colorNumber, const COLORREF color)
|
|||||||
// If some image content has already been defined at this point, and
|
// If some image content has already been defined at this point, and
|
||||||
// we're processing the last character in the packet, this is likely an
|
// we're processing the last character in the packet, this is likely an
|
||||||
// attempt to animate the palette, so we should flush the image.
|
// attempt to animate the palette, so we should flush the image.
|
||||||
if (_imageWidth > 0 && _stateMachine.IsProcessingLastCharacter())
|
if (_imageWidth > 0 && _isProcessingLastCharacter)
|
||||||
{
|
{
|
||||||
_maybeFlushImageBuffer();
|
_maybeFlushImageBuffer();
|
||||||
}
|
}
|
||||||
@@ -876,7 +884,7 @@ void SixelParser::_maybeFlushImageBuffer(const bool endOfSequence)
|
|||||||
const auto currentTime = steady_clock::now();
|
const auto currentTime = steady_clock::now();
|
||||||
const auto timeSinceLastFlush = duration_cast<milliseconds>(currentTime - _lastFlushTime);
|
const auto timeSinceLastFlush = duration_cast<milliseconds>(currentTime - _lastFlushTime);
|
||||||
const auto linesSinceLastFlush = _imageLineCount - _lastFlushLine;
|
const auto linesSinceLastFlush = _imageLineCount - _lastFlushLine;
|
||||||
if (endOfSequence || timeSinceLastFlush > 500ms || (linesSinceLastFlush <= 1 && _stateMachine.IsProcessingLastCharacter()))
|
if (endOfSequence || timeSinceLastFlush > 500ms || (linesSinceLastFlush <= 1 && _isProcessingLastCharacter))
|
||||||
{
|
{
|
||||||
_lastFlushTime = currentTime;
|
_lastFlushTime = currentTime;
|
||||||
_lastFlushLine = _imageLineCount;
|
_lastFlushLine = _imageLineCount;
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ namespace Microsoft::Console::VirtualTerminal
|
|||||||
{
|
{
|
||||||
class AdaptDispatch;
|
class AdaptDispatch;
|
||||||
class Page;
|
class Page;
|
||||||
class StateMachine;
|
|
||||||
|
|
||||||
class SixelParser
|
class SixelParser
|
||||||
{
|
{
|
||||||
@@ -31,10 +30,10 @@ namespace Microsoft::Console::VirtualTerminal
|
|||||||
static til::size CellSizeForLevel(const VTInt conformanceLevel = DefaultConformance) noexcept;
|
static til::size CellSizeForLevel(const VTInt conformanceLevel = DefaultConformance) noexcept;
|
||||||
static size_t MaxColorsForLevel(const VTInt conformanceLevel = DefaultConformance) noexcept;
|
static size_t MaxColorsForLevel(const VTInt conformanceLevel = DefaultConformance) noexcept;
|
||||||
|
|
||||||
SixelParser(AdaptDispatch& dispatcher, const StateMachine& stateMachine, const VTInt conformanceLevel = DefaultConformance) noexcept;
|
SixelParser(AdaptDispatch& dispatcher, const VTInt conformanceLevel = DefaultConformance) noexcept;
|
||||||
void SoftReset();
|
void SoftReset();
|
||||||
void SetDisplayMode(const bool enabled) noexcept;
|
void SetDisplayMode(const bool enabled) noexcept;
|
||||||
std::function<bool(wchar_t)> DefineImage(const VTInt macroParameter, const DispatchTypes::SixelBackground backgroundSelect, const VTParameter backgroundColor);
|
std::function<bool(std::wstring_view)> DefineImage(const VTInt macroParameter, const DispatchTypes::SixelBackground backgroundSelect, const VTParameter backgroundColor);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// NB: If we want to support more than 256 colors, we'll also need to
|
// NB: If we want to support more than 256 colors, we'll also need to
|
||||||
@@ -49,7 +48,6 @@ namespace Microsoft::Console::VirtualTerminal
|
|||||||
};
|
};
|
||||||
|
|
||||||
AdaptDispatch& _dispatcher;
|
AdaptDispatch& _dispatcher;
|
||||||
const StateMachine& _stateMachine;
|
|
||||||
const VTInt _conformanceLevel;
|
const VTInt _conformanceLevel;
|
||||||
|
|
||||||
void _parseCommandChar(const wchar_t ch);
|
void _parseCommandChar(const wchar_t ch);
|
||||||
@@ -106,6 +104,7 @@ namespace Microsoft::Console::VirtualTerminal
|
|||||||
size_t _colorsAvailable = 0;
|
size_t _colorsAvailable = 0;
|
||||||
IndexedPixel _foregroundPixel = {};
|
IndexedPixel _foregroundPixel = {};
|
||||||
bool _colorTableChanged = false;
|
bool _colorTableChanged = false;
|
||||||
|
bool _isProcessingLastCharacter = false;
|
||||||
|
|
||||||
void _initImageBuffer();
|
void _initImageBuffer();
|
||||||
void _resizeImageBuffer(const til::CoordType requiredHeight);
|
void _resizeImageBuffer(const til::CoordType requiredHeight);
|
||||||
|
|||||||
@@ -3787,7 +3787,7 @@ ITermDispatch::StringHandler AdaptDispatch::DefineSixelImage(const VTInt macroPa
|
|||||||
// The sixel parser is created on demand.
|
// The sixel parser is created on demand.
|
||||||
if (!_sixelParser)
|
if (!_sixelParser)
|
||||||
{
|
{
|
||||||
_sixelParser = std::make_unique<SixelParser>(*this, _api.GetStateMachine());
|
_sixelParser = std::make_unique<SixelParser>(*this);
|
||||||
_sixelParser->SetDisplayMode(_modes.test(Mode::SixelDisplay));
|
_sixelParser->SetDisplayMode(_modes.test(Mode::SixelDisplay));
|
||||||
}
|
}
|
||||||
return _sixelParser->DefineImage(macroParameter, backgroundSelect, backgroundColor);
|
return _sixelParser->DefineImage(macroParameter, backgroundSelect, backgroundColor);
|
||||||
@@ -3836,34 +3836,37 @@ ITermDispatch::StringHandler AdaptDispatch::DownloadDRCS(const VTInt fontNumber,
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [=](const auto ch) {
|
return [=](const std::wstring_view str) {
|
||||||
// We pass the data string straight through to the font buffer class
|
for (const auto ch : str)
|
||||||
// until we receive an ESC, indicating the end of the string. At that
|
|
||||||
// point we can finalize the buffer, and if valid, update the renderer
|
|
||||||
// with the constructed bit pattern.
|
|
||||||
if (ch != AsciiChars::ESC)
|
|
||||||
{
|
{
|
||||||
_fontBuffer->AddSixelData(ch);
|
// We pass the data string straight through to the font buffer class
|
||||||
}
|
// until we receive an ESC, indicating the end of the string. At that
|
||||||
else if (_fontBuffer->FinalizeSixelData())
|
// point we can finalize the buffer, and if valid, update the renderer
|
||||||
{
|
// with the constructed bit pattern.
|
||||||
// We also need to inform the character set mapper of the ID that
|
if (ch != AsciiChars::ESC)
|
||||||
// will map to this font (we only support one font buffer so there
|
|
||||||
// will only ever be one active dynamic character set).
|
|
||||||
if (charsetSize == DispatchTypes::CharsetSize::Size96)
|
|
||||||
{
|
{
|
||||||
_termOutput.SetDrcs96Designation(_fontBuffer->GetDesignation());
|
_fontBuffer->AddSixelData(ch);
|
||||||
}
|
}
|
||||||
else
|
else if (_fontBuffer->FinalizeSixelData())
|
||||||
{
|
{
|
||||||
_termOutput.SetDrcs94Designation(_fontBuffer->GetDesignation());
|
// We also need to inform the character set mapper of the ID that
|
||||||
}
|
// will map to this font (we only support one font buffer so there
|
||||||
if (_renderer)
|
// will only ever be one active dynamic character set).
|
||||||
{
|
if (charsetSize == DispatchTypes::CharsetSize::Size96)
|
||||||
const auto bitPattern = _fontBuffer->GetBitPattern();
|
{
|
||||||
const auto cellSize = _fontBuffer->GetCellSize();
|
_termOutput.SetDrcs96Designation(_fontBuffer->GetDesignation());
|
||||||
const auto centeringHint = _fontBuffer->GetTextCenteringHint();
|
}
|
||||||
_renderer->UpdateSoftFont(bitPattern, cellSize, centeringHint);
|
else
|
||||||
|
{
|
||||||
|
_termOutput.SetDrcs94Designation(_fontBuffer->GetDesignation());
|
||||||
|
}
|
||||||
|
if (_renderer)
|
||||||
|
{
|
||||||
|
const auto bitPattern = _fontBuffer->GetBitPattern();
|
||||||
|
const auto cellSize = _fontBuffer->GetCellSize();
|
||||||
|
const auto centeringHint = _fontBuffer->GetTextCenteringHint();
|
||||||
|
_renderer->UpdateSoftFont(bitPattern, cellSize, centeringHint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -3889,24 +3892,27 @@ void AdaptDispatch::RequestUserPreferenceCharset()
|
|||||||
// - a function to parse the character set ID
|
// - a function to parse the character set ID
|
||||||
ITermDispatch::StringHandler AdaptDispatch::AssignUserPreferenceCharset(const DispatchTypes::CharsetSize charsetSize)
|
ITermDispatch::StringHandler AdaptDispatch::AssignUserPreferenceCharset(const DispatchTypes::CharsetSize charsetSize)
|
||||||
{
|
{
|
||||||
return [this, charsetSize, idBuilder = VTIDBuilder{}](const auto ch) mutable {
|
return [this, charsetSize, idBuilder = VTIDBuilder{}](const std::wstring_view str) mutable {
|
||||||
if (ch >= L'\x20' && ch <= L'\x2f')
|
for (const auto ch : str)
|
||||||
{
|
{
|
||||||
idBuilder.AddIntermediate(ch);
|
if (ch >= L'\x20' && ch <= L'\x2f')
|
||||||
}
|
|
||||||
else if (ch >= L'\x30' && ch <= L'\x7e')
|
|
||||||
{
|
|
||||||
const auto id = idBuilder.Finalize(ch);
|
|
||||||
switch (charsetSize)
|
|
||||||
{
|
{
|
||||||
case DispatchTypes::CharsetSize::Size94:
|
idBuilder.AddIntermediate(ch);
|
||||||
_termOutput.AssignUserPreferenceCharset(id, false);
|
}
|
||||||
break;
|
else if (ch >= L'\x30' && ch <= L'\x7e')
|
||||||
case DispatchTypes::CharsetSize::Size96:
|
{
|
||||||
_termOutput.AssignUserPreferenceCharset(id, true);
|
const auto id = idBuilder.Finalize(ch);
|
||||||
break;
|
switch (charsetSize)
|
||||||
|
{
|
||||||
|
case DispatchTypes::CharsetSize::Size94:
|
||||||
|
_termOutput.AssignUserPreferenceCharset(id, false);
|
||||||
|
break;
|
||||||
|
case DispatchTypes::CharsetSize::Size96:
|
||||||
|
_termOutput.AssignUserPreferenceCharset(id, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@@ -3932,8 +3938,8 @@ ITermDispatch::StringHandler AdaptDispatch::DefineMacro(const VTInt macroId,
|
|||||||
|
|
||||||
if (_macroBuffer->InitParser(macroId, deleteControl, encoding))
|
if (_macroBuffer->InitParser(macroId, deleteControl, encoding))
|
||||||
{
|
{
|
||||||
return [&](const auto ch) {
|
return [&](const std::wstring_view str) {
|
||||||
return _macroBuffer->ParseDefinition(ch);
|
return _macroBuffer->ParseDefinition(str);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4053,45 +4059,48 @@ void AdaptDispatch::_ReportColorTable(const DispatchTypes::ColorModel colorModel
|
|||||||
// - a function to parse the report data.
|
// - a function to parse the report data.
|
||||||
ITermDispatch::StringHandler AdaptDispatch::_RestoreColorTable()
|
ITermDispatch::StringHandler AdaptDispatch::_RestoreColorTable()
|
||||||
{
|
{
|
||||||
return [this, parameter = VTInt{}, parameters = std::vector<VTParameter>{}](const auto ch) mutable {
|
return [this, parameter = VTInt{}, parameters = std::vector<VTParameter>{}](const std::wstring_view str) mutable {
|
||||||
if (ch >= L'0' && ch <= L'9')
|
for (const auto ch : str)
|
||||||
{
|
{
|
||||||
parameter *= 10;
|
if (ch >= L'0' && ch <= L'9')
|
||||||
parameter += (ch - L'0');
|
{
|
||||||
parameter = std::min(parameter, MAX_PARAMETER_VALUE);
|
parameter *= 10;
|
||||||
}
|
parameter += (ch - L'0');
|
||||||
else if (ch == L';')
|
parameter = std::min(parameter, MAX_PARAMETER_VALUE);
|
||||||
{
|
}
|
||||||
if (parameters.size() < 5)
|
else if (ch == L';')
|
||||||
|
{
|
||||||
|
if (parameters.size() < 5)
|
||||||
|
{
|
||||||
|
parameters.push_back(parameter);
|
||||||
|
}
|
||||||
|
parameter = 0;
|
||||||
|
}
|
||||||
|
else if (ch == L'/' || ch == AsciiChars::ESC)
|
||||||
{
|
{
|
||||||
parameters.push_back(parameter);
|
parameters.push_back(parameter);
|
||||||
}
|
const auto colorParameters = VTParameters{ parameters.data(), parameters.size() };
|
||||||
parameter = 0;
|
const auto colorNumber = colorParameters.at(0).value_or(0);
|
||||||
}
|
if (colorNumber < TextColor::TABLE_SIZE)
|
||||||
else if (ch == L'/' || ch == AsciiChars::ESC)
|
|
||||||
{
|
|
||||||
parameters.push_back(parameter);
|
|
||||||
const auto colorParameters = VTParameters{ parameters.data(), parameters.size() };
|
|
||||||
const auto colorNumber = colorParameters.at(0).value_or(0);
|
|
||||||
if (colorNumber < TextColor::TABLE_SIZE)
|
|
||||||
{
|
|
||||||
const auto colorModel = DispatchTypes::ColorModel{ colorParameters.at(1) };
|
|
||||||
const auto x = colorParameters.at(2).value_or(0);
|
|
||||||
const auto y = colorParameters.at(3).value_or(0);
|
|
||||||
const auto z = colorParameters.at(4).value_or(0);
|
|
||||||
if (colorModel == DispatchTypes::ColorModel::HLS)
|
|
||||||
{
|
{
|
||||||
SetColorTableEntry(colorNumber, Utils::ColorFromHLS(x, y, z));
|
const auto colorModel = DispatchTypes::ColorModel{ colorParameters.at(1) };
|
||||||
}
|
const auto x = colorParameters.at(2).value_or(0);
|
||||||
else if (colorModel == DispatchTypes::ColorModel::RGB)
|
const auto y = colorParameters.at(3).value_or(0);
|
||||||
{
|
const auto z = colorParameters.at(4).value_or(0);
|
||||||
SetColorTableEntry(colorNumber, Utils::ColorFromRGB100(x, y, z));
|
if (colorModel == DispatchTypes::ColorModel::HLS)
|
||||||
|
{
|
||||||
|
SetColorTableEntry(colorNumber, Utils::ColorFromHLS(x, y, z));
|
||||||
|
}
|
||||||
|
else if (colorModel == DispatchTypes::ColorModel::RGB)
|
||||||
|
{
|
||||||
|
SetColorTableEntry(colorNumber, Utils::ColorFromRGB100(x, y, z));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
parameters.clear();
|
||||||
|
parameter = 0;
|
||||||
}
|
}
|
||||||
parameters.clear();
|
|
||||||
parameter = 0;
|
|
||||||
}
|
}
|
||||||
return (ch != AsciiChars::ESC);
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4111,42 +4120,43 @@ ITermDispatch::StringHandler AdaptDispatch::RequestSetting()
|
|||||||
// this is the opposite of what is documented in most DEC manuals, which
|
// this is the opposite of what is documented in most DEC manuals, which
|
||||||
// say that 0 is for a valid response, and 1 is for an error. The correct
|
// say that 0 is for a valid response, and 1 is for an error. The correct
|
||||||
// interpretation is documented in the DEC STD 070 reference.
|
// interpretation is documented in the DEC STD 070 reference.
|
||||||
return [this, parameter = VTInt{}, idBuilder = VTIDBuilder{}](const auto ch) mutable {
|
return [this, parameter = VTInt{}, idBuilder = VTIDBuilder{}](const std::wstring_view str) mutable {
|
||||||
const auto isFinal = ch >= L'\x40' && ch <= L'\x7e';
|
for (const auto ch : str)
|
||||||
if (isFinal)
|
|
||||||
{
|
{
|
||||||
const auto id = idBuilder.Finalize(ch);
|
const auto isFinal = ch >= L'\x40' && ch <= L'\x7e';
|
||||||
switch (id)
|
if (isFinal)
|
||||||
{
|
{
|
||||||
case VTID("m"):
|
const auto id = idBuilder.Finalize(ch);
|
||||||
_ReportSGRSetting();
|
switch (id)
|
||||||
break;
|
{
|
||||||
case VTID("r"):
|
case VTID("m"):
|
||||||
_ReportDECSTBMSetting();
|
_ReportSGRSetting();
|
||||||
break;
|
break;
|
||||||
case VTID("s"):
|
case VTID("r"):
|
||||||
_ReportDECSLRMSetting();
|
_ReportDECSTBMSetting();
|
||||||
break;
|
break;
|
||||||
case VTID(" q"):
|
case VTID("s"):
|
||||||
_ReportDECSCUSRSetting();
|
_ReportDECSLRMSetting();
|
||||||
break;
|
break;
|
||||||
case VTID("\"q"):
|
case VTID(" q"):
|
||||||
_ReportDECSCASetting();
|
_ReportDECSCUSRSetting();
|
||||||
break;
|
break;
|
||||||
case VTID("*x"):
|
case VTID("\"q"):
|
||||||
_ReportDECSACESetting();
|
_ReportDECSCASetting();
|
||||||
break;
|
break;
|
||||||
case VTID(",|"):
|
case VTID("*x"):
|
||||||
_ReportDECACSetting(VTParameter{ parameter });
|
_ReportDECSACESetting();
|
||||||
break;
|
break;
|
||||||
default:
|
case VTID(",|"):
|
||||||
_ReturnDcsResponse(L"0$r");
|
_ReportDECACSetting(VTParameter{ parameter });
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
_ReturnDcsResponse(L"0$r");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Although we don't yet support any operations with parameter
|
// Although we don't yet support any operations with parameter
|
||||||
// prefixes, it's important that we still parse the prefix and
|
// prefixes, it's important that we still parse the prefix and
|
||||||
// include it in the ID. Otherwise, we'll mistakenly respond to
|
// include it in the ID. Otherwise, we'll mistakenly respond to
|
||||||
@@ -4164,8 +4174,8 @@ ITermDispatch::StringHandler AdaptDispatch::RequestSetting()
|
|||||||
parameter += (ch - L'0');
|
parameter += (ch - L'0');
|
||||||
parameter = std::min(parameter, MAX_PARAMETER_VALUE);
|
parameter = std::min(parameter, MAX_PARAMETER_VALUE);
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4513,126 +4523,129 @@ ITermDispatch::StringHandler AdaptDispatch::_RestoreCursorInformation()
|
|||||||
VTParameter row{};
|
VTParameter row{};
|
||||||
VTParameter column{};
|
VTParameter column{};
|
||||||
};
|
};
|
||||||
return [&, state = State{}](const auto ch) mutable {
|
return [&, state = State{}](const std::wstring_view str) mutable {
|
||||||
if (numeric.test(state.field))
|
for (const auto ch : str)
|
||||||
{
|
{
|
||||||
if (ch >= '0' && ch <= '9')
|
if (numeric.test(state.field))
|
||||||
{
|
{
|
||||||
state.value *= 10;
|
if (ch >= '0' && ch <= '9')
|
||||||
state.value += (ch - L'0');
|
{
|
||||||
state.value = std::min(state.value, MAX_PARAMETER_VALUE);
|
state.value *= 10;
|
||||||
|
state.value += (ch - L'0');
|
||||||
|
state.value = std::min(state.value, MAX_PARAMETER_VALUE);
|
||||||
|
}
|
||||||
|
else if (ch == L';' || ch == AsciiChars::ESC)
|
||||||
|
{
|
||||||
|
if (state.field == Field::Row)
|
||||||
|
{
|
||||||
|
state.row = state.value;
|
||||||
|
}
|
||||||
|
else if (state.field == Field::Column)
|
||||||
|
{
|
||||||
|
state.column = state.value;
|
||||||
|
}
|
||||||
|
else if (state.field == Field::Page)
|
||||||
|
{
|
||||||
|
PagePositionAbsolute(state.value);
|
||||||
|
}
|
||||||
|
else if (state.field == Field::GL && state.value <= 3)
|
||||||
|
{
|
||||||
|
LockingShift(state.value);
|
||||||
|
}
|
||||||
|
else if (state.field == Field::GR && state.value <= 3)
|
||||||
|
{
|
||||||
|
LockingShiftRight(state.value);
|
||||||
|
}
|
||||||
|
state.value = {};
|
||||||
|
state.field = static_cast<Field>(state.field + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (ch == L';' || ch == AsciiChars::ESC)
|
else if (flags.test(state.field))
|
||||||
{
|
{
|
||||||
if (state.field == Field::Row)
|
// Note that there could potentially be multiple characters in a
|
||||||
|
// flag field, so we process the flags as soon as they're received.
|
||||||
|
// But for now we're only interested in the first one, so once the
|
||||||
|
// state.value is set, we ignore everything else until the `;`.
|
||||||
|
if (ch >= L'@' && ch <= '~' && !state.value)
|
||||||
{
|
{
|
||||||
state.row = state.value;
|
state.value = ch;
|
||||||
}
|
if (state.field == Field::SGR)
|
||||||
else if (state.field == Field::Column)
|
|
||||||
{
|
|
||||||
state.column = state.value;
|
|
||||||
}
|
|
||||||
else if (state.field == Field::Page)
|
|
||||||
{
|
|
||||||
PagePositionAbsolute(state.value);
|
|
||||||
}
|
|
||||||
else if (state.field == Field::GL && state.value <= 3)
|
|
||||||
{
|
|
||||||
LockingShift(state.value);
|
|
||||||
}
|
|
||||||
else if (state.field == Field::GR && state.value <= 3)
|
|
||||||
{
|
|
||||||
LockingShiftRight(state.value);
|
|
||||||
}
|
|
||||||
state.value = {};
|
|
||||||
state.field = static_cast<Field>(state.field + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (flags.test(state.field))
|
|
||||||
{
|
|
||||||
// Note that there could potentially be multiple characters in a
|
|
||||||
// flag field, so we process the flags as soon as they're received.
|
|
||||||
// But for now we're only interested in the first one, so once the
|
|
||||||
// state.value is set, we ignore everything else until the `;`.
|
|
||||||
if (ch >= L'@' && ch <= '~' && !state.value)
|
|
||||||
{
|
|
||||||
state.value = ch;
|
|
||||||
if (state.field == Field::SGR)
|
|
||||||
{
|
|
||||||
const auto page = _pages.ActivePage();
|
|
||||||
auto attr = page.Attributes();
|
|
||||||
attr.SetIntense(state.value & 1);
|
|
||||||
attr.SetUnderlineStyle(state.value & 2 ? UnderlineStyle::SinglyUnderlined : UnderlineStyle::NoUnderline);
|
|
||||||
attr.SetBlinking(state.value & 4);
|
|
||||||
attr.SetReverseVideo(state.value & 8);
|
|
||||||
attr.SetInvisible(state.value & 16);
|
|
||||||
page.SetAttributes(attr);
|
|
||||||
}
|
|
||||||
else if (state.field == Field::Attr)
|
|
||||||
{
|
|
||||||
const auto page = _pages.ActivePage();
|
|
||||||
auto attr = page.Attributes();
|
|
||||||
attr.SetProtected(state.value & 1);
|
|
||||||
page.SetAttributes(attr);
|
|
||||||
}
|
|
||||||
else if (state.field == Field::Sizes)
|
|
||||||
{
|
|
||||||
state.charset96.at(0) = state.value & 1;
|
|
||||||
state.charset96.at(1) = state.value & 2;
|
|
||||||
state.charset96.at(2) = state.value & 4;
|
|
||||||
state.charset96.at(3) = state.value & 8;
|
|
||||||
}
|
|
||||||
else if (state.field == Field::Flags)
|
|
||||||
{
|
|
||||||
const bool originMode = state.value & 1;
|
|
||||||
const bool ss2 = state.value & 2;
|
|
||||||
const bool ss3 = state.value & 4;
|
|
||||||
const bool delayedEOLWrap = state.value & 8;
|
|
||||||
// The cursor position is parsed at the start of the sequence,
|
|
||||||
// but we only set the position once we know the origin mode.
|
|
||||||
_modes.set(Mode::Origin, originMode);
|
|
||||||
CursorPosition(state.row, state.column);
|
|
||||||
// There can only be one single shift applied at a time, so
|
|
||||||
// we'll just apply the last one that is enabled.
|
|
||||||
_termOutput.SingleShift(ss3 ? 3 : (ss2 ? 2 : 0));
|
|
||||||
// The EOL flag will always be reset by the cursor movement
|
|
||||||
// above, so we only need to worry about setting it.
|
|
||||||
if (delayedEOLWrap)
|
|
||||||
{
|
{
|
||||||
const auto page = _pages.ActivePage();
|
const auto page = _pages.ActivePage();
|
||||||
page.Cursor().DelayEOLWrap();
|
auto attr = page.Attributes();
|
||||||
|
attr.SetIntense(state.value & 1);
|
||||||
|
attr.SetUnderlineStyle(state.value & 2 ? UnderlineStyle::SinglyUnderlined : UnderlineStyle::NoUnderline);
|
||||||
|
attr.SetBlinking(state.value & 4);
|
||||||
|
attr.SetReverseVideo(state.value & 8);
|
||||||
|
attr.SetInvisible(state.value & 16);
|
||||||
|
page.SetAttributes(attr);
|
||||||
|
}
|
||||||
|
else if (state.field == Field::Attr)
|
||||||
|
{
|
||||||
|
const auto page = _pages.ActivePage();
|
||||||
|
auto attr = page.Attributes();
|
||||||
|
attr.SetProtected(state.value & 1);
|
||||||
|
page.SetAttributes(attr);
|
||||||
|
}
|
||||||
|
else if (state.field == Field::Sizes)
|
||||||
|
{
|
||||||
|
state.charset96.at(0) = state.value & 1;
|
||||||
|
state.charset96.at(1) = state.value & 2;
|
||||||
|
state.charset96.at(2) = state.value & 4;
|
||||||
|
state.charset96.at(3) = state.value & 8;
|
||||||
|
}
|
||||||
|
else if (state.field == Field::Flags)
|
||||||
|
{
|
||||||
|
const bool originMode = state.value & 1;
|
||||||
|
const bool ss2 = state.value & 2;
|
||||||
|
const bool ss3 = state.value & 4;
|
||||||
|
const bool delayedEOLWrap = state.value & 8;
|
||||||
|
// The cursor position is parsed at the start of the sequence,
|
||||||
|
// but we only set the position once we know the origin mode.
|
||||||
|
_modes.set(Mode::Origin, originMode);
|
||||||
|
CursorPosition(state.row, state.column);
|
||||||
|
// There can only be one single shift applied at a time, so
|
||||||
|
// we'll just apply the last one that is enabled.
|
||||||
|
_termOutput.SingleShift(ss3 ? 3 : (ss2 ? 2 : 0));
|
||||||
|
// The EOL flag will always be reset by the cursor movement
|
||||||
|
// above, so we only need to worry about setting it.
|
||||||
|
if (delayedEOLWrap)
|
||||||
|
{
|
||||||
|
const auto page = _pages.ActivePage();
|
||||||
|
page.Cursor().DelayEOLWrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (ch == L';')
|
||||||
|
{
|
||||||
|
state.value = 0;
|
||||||
|
state.field = static_cast<Field>(state.field + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (ch == L';')
|
else if (charset.test(state.field))
|
||||||
{
|
{
|
||||||
state.value = 0;
|
if (ch >= L' ' && ch <= L'/')
|
||||||
state.field = static_cast<Field>(state.field + 1);
|
{
|
||||||
|
state.charsetId.AddIntermediate(ch);
|
||||||
|
}
|
||||||
|
else if (ch >= L'0' && ch <= L'~')
|
||||||
|
{
|
||||||
|
const auto id = state.charsetId.Finalize(ch);
|
||||||
|
const auto gset = state.field - Field::G0;
|
||||||
|
if (state.charset96.at(gset))
|
||||||
|
{
|
||||||
|
Designate96Charset(gset, id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Designate94Charset(gset, id);
|
||||||
|
}
|
||||||
|
state.charsetId.Clear();
|
||||||
|
state.field = static_cast<Field>(state.field + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (charset.test(state.field))
|
return true;
|
||||||
{
|
|
||||||
if (ch >= L' ' && ch <= L'/')
|
|
||||||
{
|
|
||||||
state.charsetId.AddIntermediate(ch);
|
|
||||||
}
|
|
||||||
else if (ch >= L'0' && ch <= L'~')
|
|
||||||
{
|
|
||||||
const auto id = state.charsetId.Finalize(ch);
|
|
||||||
const auto gset = state.field - Field::G0;
|
|
||||||
if (state.charset96.at(gset))
|
|
||||||
{
|
|
||||||
Designate96Charset(gset, id);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Designate94Charset(gset, id);
|
|
||||||
}
|
|
||||||
state.charsetId.Clear();
|
|
||||||
state.field = static_cast<Field>(state.field + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (ch != AsciiChars::ESC);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4685,30 +4698,33 @@ ITermDispatch::StringHandler AdaptDispatch::_RestoreTabStops()
|
|||||||
_ClearAllTabStops();
|
_ClearAllTabStops();
|
||||||
_InitTabStopsForWidth(width);
|
_InitTabStopsForWidth(width);
|
||||||
|
|
||||||
return [this, width, column = size_t{}](const auto ch) mutable {
|
return [this, width, column = size_t{}](const std::wstring_view str) mutable {
|
||||||
if (ch >= L'0' && ch <= L'9')
|
for (const auto ch : str)
|
||||||
{
|
{
|
||||||
column *= 10;
|
if (ch >= L'0' && ch <= L'9')
|
||||||
column += (ch - L'0');
|
|
||||||
column = std::min<size_t>(column, MAX_PARAMETER_VALUE);
|
|
||||||
}
|
|
||||||
else if (ch == L'/' || ch == AsciiChars::ESC)
|
|
||||||
{
|
|
||||||
// Note that column 1 is always a tab stop, so there is no
|
|
||||||
// need to record an entry at that offset.
|
|
||||||
if (column > 1u && column <= static_cast<size_t>(width))
|
|
||||||
{
|
{
|
||||||
_tabStopColumns.at(column - 1) = true;
|
column *= 10;
|
||||||
|
column += (ch - L'0');
|
||||||
|
column = std::min<size_t>(column, MAX_PARAMETER_VALUE);
|
||||||
|
}
|
||||||
|
else if (ch == L'/' || ch == AsciiChars::ESC)
|
||||||
|
{
|
||||||
|
// Note that column 1 is always a tab stop, so there is no
|
||||||
|
// need to record an entry at that offset.
|
||||||
|
if (column > 1u && column <= static_cast<size_t>(width))
|
||||||
|
{
|
||||||
|
_tabStopColumns.at(column - 1) = true;
|
||||||
|
}
|
||||||
|
column = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If we receive an unexpected character, we don't try and
|
||||||
|
// process any more of the input - we just abort.
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
column = 0;
|
|
||||||
}
|
}
|
||||||
else
|
return true;
|
||||||
{
|
|
||||||
// If we receive an unexpected character, we don't try and
|
|
||||||
// process any more of the input - we just abort.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return (ch != AsciiChars::ESC);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1847,11 +1847,8 @@ public:
|
|||||||
{
|
{
|
||||||
const auto requestSetting = [=](const std::wstring_view settingId = {}) {
|
const auto requestSetting = [=](const std::wstring_view settingId = {}) {
|
||||||
const auto stringHandler = _pDispatch->RequestSetting();
|
const auto stringHandler = _pDispatch->RequestSetting();
|
||||||
for (auto ch : settingId)
|
stringHandler(settingId);
|
||||||
{
|
stringHandler(L"\033"); // String terminator
|
||||||
stringHandler(ch);
|
|
||||||
}
|
|
||||||
stringHandler(L'\033'); // String terminator
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Log::Comment(L"Requesting DECSTBM margins (5 to 10).");
|
Log::Comment(L"Requesting DECSTBM margins (5 to 10).");
|
||||||
@@ -3568,11 +3565,8 @@ public:
|
|||||||
{
|
{
|
||||||
const auto assignCharset = [=](const auto charsetSize, const std::wstring_view charsetId = {}) {
|
const auto assignCharset = [=](const auto charsetSize, const std::wstring_view charsetId = {}) {
|
||||||
const auto stringHandler = _pDispatch->AssignUserPreferenceCharset(charsetSize);
|
const auto stringHandler = _pDispatch->AssignUserPreferenceCharset(charsetSize);
|
||||||
for (auto ch : charsetId)
|
stringHandler(charsetId);
|
||||||
{
|
stringHandler(L"\033"); // String terminator
|
||||||
stringHandler(ch);
|
|
||||||
}
|
|
||||||
stringHandler(L'\033'); // String terminator
|
|
||||||
};
|
};
|
||||||
auto& termOutput = _pDispatch->_termOutput;
|
auto& termOutput = _pDispatch->_termOutput;
|
||||||
termOutput.SoftReset();
|
termOutput.SoftReset();
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace Microsoft::Console::VirtualTerminal
|
|||||||
class IStateMachineEngine
|
class IStateMachineEngine
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using StringHandler = std::function<bool(const wchar_t)>;
|
using StringHandler = std::function<bool(std::wstring_view)>;
|
||||||
|
|
||||||
virtual ~IStateMachineEngine() = 0;
|
virtual ~IStateMachineEngine() = 0;
|
||||||
IStateMachineEngine(const IStateMachineEngine&) = default;
|
IStateMachineEngine(const IStateMachineEngine&) = default;
|
||||||
|
|||||||
@@ -13,17 +13,7 @@ using namespace Microsoft::Console::VirtualTerminal;
|
|||||||
//Takes ownership of the pEngine.
|
//Takes ownership of the pEngine.
|
||||||
StateMachine::StateMachine(std::unique_ptr<IStateMachineEngine> engine, const bool isEngineForInput) noexcept :
|
StateMachine::StateMachine(std::unique_ptr<IStateMachineEngine> engine, const bool isEngineForInput) noexcept :
|
||||||
_engine(std::move(engine)),
|
_engine(std::move(engine)),
|
||||||
_isEngineForInput(isEngineForInput),
|
_isEngineForInput(isEngineForInput)
|
||||||
_state(VTStates::Ground),
|
|
||||||
_trace(Microsoft::Console::VirtualTerminal::ParserTracing()),
|
|
||||||
_parameters{},
|
|
||||||
_subParameters{},
|
|
||||||
_subParameterRanges{},
|
|
||||||
_parameterLimitOverflowed(false),
|
|
||||||
_subParameterLimitOverflowed(false),
|
|
||||||
_subParameterCounter(0),
|
|
||||||
_oscString{},
|
|
||||||
_cachedSequence{ std::nullopt }
|
|
||||||
{
|
{
|
||||||
// The state machine must always accept C1 controls for the input engine,
|
// The state machine must always accept C1 controls for the input engine,
|
||||||
// otherwise it won't work when the ConPTY terminal has S8C1T enabled.
|
// otherwise it won't work when the ConPTY terminal has S8C1T enabled.
|
||||||
@@ -324,18 +314,6 @@ static constexpr bool _isDcsIndicator(const wchar_t wch) noexcept
|
|||||||
return wch == L'P'; // 0x50
|
return wch == L'P'; // 0x50
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
|
||||||
// - Determines if a character is valid for a DCS pass through sequence.
|
|
||||||
// Arguments:
|
|
||||||
// - wch - Character to check.
|
|
||||||
// Return Value:
|
|
||||||
// - True if it is. False if it isn't.
|
|
||||||
static constexpr bool _isDcsPassThroughValid(const wchar_t wch) noexcept
|
|
||||||
{
|
|
||||||
// 0x20 - 0x7E
|
|
||||||
return wch >= AsciiChars::SPC && wch < AsciiChars::DEL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
// - Determines if a character is "start of string" beginning
|
// - Determines if a character is "start of string" beginning
|
||||||
// indicator.
|
// indicator.
|
||||||
@@ -655,7 +633,7 @@ void StateMachine::_ActionInterrupt()
|
|||||||
if (_state == VTStates::DcsPassThrough)
|
if (_state == VTStates::DcsPassThrough)
|
||||||
{
|
{
|
||||||
// The ESC signals the end of the data string.
|
// The ESC signals the end of the data string.
|
||||||
_dcsStringHandler(AsciiChars::ESC);
|
_dcsStringHandler(L"\x1b");
|
||||||
_dcsStringHandler = nullptr;
|
_dcsStringHandler = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1799,20 +1777,70 @@ void StateMachine::_EventDcsParam(const wchar_t wch)
|
|||||||
// - wch - Character that triggered the event
|
// - wch - Character that triggered the event
|
||||||
// Return Value:
|
// Return Value:
|
||||||
// - <none>
|
// - <none>
|
||||||
void StateMachine::_EventDcsPassThrough(const wchar_t wch)
|
void StateMachine::_EventDcsPassThrough(const wchar_t)
|
||||||
{
|
{
|
||||||
|
static constexpr auto isDcsIgnore = [](const wchar_t wch) {
|
||||||
|
return wch == AsciiChars::DEL;
|
||||||
|
};
|
||||||
|
static constexpr auto isDcsTerminator = [](const wchar_t wch) {
|
||||||
|
return wch == AsciiChars::CAN ||
|
||||||
|
wch == AsciiChars::SUB ||
|
||||||
|
wch == AsciiChars::ESC ||
|
||||||
|
(wch >= 0x80 && wch <= 0x9F);
|
||||||
|
};
|
||||||
|
static constexpr auto isDcsActionable = [](const wchar_t wch) {
|
||||||
|
return wch == AsciiChars::CAN ||
|
||||||
|
wch == AsciiChars::SUB ||
|
||||||
|
wch == AsciiChars::ESC ||
|
||||||
|
wch == AsciiChars::DEL ||
|
||||||
|
(wch >= 0x80 && wch <= 0x9F);
|
||||||
|
};
|
||||||
|
|
||||||
_trace.TraceOnEvent(L"DcsPassThrough");
|
_trace.TraceOnEvent(L"DcsPassThrough");
|
||||||
if (_isC0Code(wch) || _isDcsPassThroughValid(wch))
|
|
||||||
|
// Assuming other functions are correctly implemented,
|
||||||
|
// we're only called when we have data to process.
|
||||||
|
assert(_runEnd != 0 && _runEnd <= _currentString.size());
|
||||||
|
|
||||||
|
const auto end = _currentString.end();
|
||||||
|
auto runEnd = _currentString.begin() + (_runEnd - 1);
|
||||||
|
|
||||||
|
for (;;)
|
||||||
{
|
{
|
||||||
if (!_dcsStringHandler(wch))
|
const auto runBeg = runEnd;
|
||||||
|
|
||||||
|
// Process a chunk of non-actionable, passthrough characters
|
||||||
|
// and exit if we're out of input.
|
||||||
|
for (; runEnd != end && !isDcsActionable(*runEnd); ++runEnd)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
if (runBeg != runEnd && !_dcsStringHandler({ runBeg, runEnd }))
|
||||||
{
|
{
|
||||||
_EnterDcsIgnore();
|
_EnterDcsIgnore();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (runEnd == end)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore DEL characters.
|
||||||
|
for (; runEnd != end && isDcsIgnore(*runEnd); ++runEnd)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
if (runEnd == end)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Are we looking at a terminator?
|
||||||
|
if (isDcsTerminator(*runEnd))
|
||||||
|
{
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
_runEnd = runEnd - _currentString.begin();
|
||||||
_ActionIgnore();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
@@ -1973,144 +2001,169 @@ bool StateMachine::FlushToTerminal()
|
|||||||
// - string - Characters to operate upon
|
// - string - Characters to operate upon
|
||||||
// Return Value:
|
// Return Value:
|
||||||
// - <none>
|
// - <none>
|
||||||
|
#pragma warning(suppress : 26438) // Avoid 'goto'(es .76).
|
||||||
|
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).)
|
||||||
void StateMachine::ProcessString(const std::wstring_view string)
|
void StateMachine::ProcessString(const std::wstring_view string)
|
||||||
{
|
{
|
||||||
size_t i = 0;
|
|
||||||
_currentString = string;
|
_currentString = string;
|
||||||
_runOffset = 0;
|
_runBeg = 0;
|
||||||
_runSize = 0;
|
_runEnd = 0;
|
||||||
_injections.clear();
|
_injections.clear();
|
||||||
|
|
||||||
if (_state != VTStates::Ground)
|
if (_state != VTStates::Ground)
|
||||||
{
|
{
|
||||||
// Jump straight to where we need to.
|
// Jump straight to where we need to.
|
||||||
#pragma warning(suppress : 26438) // Avoid 'goto'(es .76).
|
#pragma warning(suppress : 26438) // Avoid 'goto' (es.76).
|
||||||
goto processStringLoopVtStart;
|
goto processStringLoopVtStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (i < string.size())
|
for (;;)
|
||||||
{
|
{
|
||||||
|
// Process as much plain text as possible.
|
||||||
{
|
{
|
||||||
// Pointer arithmetic is perfectly fine for our hot path.
|
// We're starting a new "chunk".
|
||||||
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).)
|
// --> Reset beg(in).
|
||||||
const auto beg = string.data() + i;
|
_runBeg = _runEnd;
|
||||||
const auto len = string.size() - i;
|
|
||||||
|
// Ran out of text. Return.
|
||||||
|
if (_runBeg >= _currentString.size())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
|
||||||
|
const auto beg = _currentString.data() + _runBeg;
|
||||||
|
const auto len = _currentString.size() - _runBeg;
|
||||||
const auto it = Microsoft::Console::Utils::FindActionableControlCharacter(beg, len);
|
const auto it = Microsoft::Console::Utils::FindActionableControlCharacter(beg, len);
|
||||||
|
|
||||||
_runOffset = i;
|
if (it != beg)
|
||||||
_runSize = it - beg;
|
|
||||||
|
|
||||||
if (_runSize)
|
|
||||||
{
|
{
|
||||||
|
_runEnd = it - _currentString.data();
|
||||||
_ActionPrintString(_CurrentRun());
|
_ActionPrintString(_CurrentRun());
|
||||||
|
_runBeg = _runEnd;
|
||||||
i += _runSize;
|
|
||||||
_runOffset = i;
|
|
||||||
_runSize = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Next, process VT sequences (plural!).
|
||||||
processStringLoopVtStart:
|
processStringLoopVtStart:
|
||||||
if (i >= string.size())
|
// Ran out of text. Return.
|
||||||
|
if (_runBeg >= _currentString.size())
|
||||||
{
|
{
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
|
for (;;)
|
||||||
do
|
|
||||||
{
|
{
|
||||||
_runSize++;
|
// The inner loop deals with 1 VT sequence at a time.
|
||||||
_processingLastCharacter = i + 1 >= string.size();
|
for (;;)
|
||||||
// If we're processing characters individually, send it to the state machine.
|
|
||||||
ProcessCharacter(til::at(string, i));
|
|
||||||
++i;
|
|
||||||
} while (i < string.size() && _state != VTStates::Ground);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're at the end of the string and have remaining un-printed characters,
|
|
||||||
if (_state != VTStates::Ground)
|
|
||||||
{
|
|
||||||
const auto run = _CurrentRun();
|
|
||||||
auto cacheUnusedRun = true;
|
|
||||||
|
|
||||||
// One of the "weird things" in VT input is the case of something like
|
|
||||||
// <kbd>alt+[</kbd>. In VT, that's encoded as `\x1b[`. However, that's
|
|
||||||
// also the start of a CSI, and could be the start of a longer sequence,
|
|
||||||
// there's no way to know for sure. For an <kbd>alt+[</kbd> keypress,
|
|
||||||
// the parser originally would just sit in the `CsiEntry` state after
|
|
||||||
// processing it, which would pollute the following keypress (e.g.
|
|
||||||
// <kbd>alt+[</kbd>, <kbd>A</kbd> would be processed like `\x1b[A`,
|
|
||||||
// which is _wrong_).
|
|
||||||
//
|
|
||||||
// At the same time, input may be broken up arbitrarily, depending on the pipe's
|
|
||||||
// buffer size, our read-buffer size, the sender's write-buffer size, and more.
|
|
||||||
// In fact, with the current WSL, input is broken up in 16 byte chunks (Why? :(),
|
|
||||||
// which breaks up many of our longer sequences, like our Win32InputMode ones.
|
|
||||||
//
|
|
||||||
// As a heuristic, this code specifically checks for a trailing Esc or Alt+key.
|
|
||||||
// If we encountered a win32-input-mode sequence before, we know that our \x1b[?9001h
|
|
||||||
// request to enable them was successful. While a client may still send \x1b{some char}
|
|
||||||
// intentionally, it's far more likely now that we're looking at a broken up sequence.
|
|
||||||
// The most common win32-input-mode is ConPTY itself after all, and we never emit
|
|
||||||
// \x1b{some char} once it's enabled.
|
|
||||||
if (_isEngineForInput)
|
|
||||||
{
|
|
||||||
const auto win32 = _engine->EncounteredWin32InputModeSequence();
|
|
||||||
if (!win32 && run.size() <= 2 && run.front() == L'\x1b')
|
|
||||||
{
|
{
|
||||||
_EnterGround();
|
const auto ch = til::at(_currentString, _runEnd);
|
||||||
if (run.size() == 1)
|
++_runEnd;
|
||||||
{
|
ProcessCharacter(ch);
|
||||||
_ActionExecute(L'\x1b');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_EnterEscape();
|
|
||||||
_ActionEscDispatch(run.back());
|
|
||||||
}
|
|
||||||
_EnterGround();
|
|
||||||
// No need to cache the run, since we've dealt with it now.
|
|
||||||
cacheUnusedRun = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (_state == VTStates::SosPmApcString || _state == VTStates::DcsPassThrough || _state == VTStates::DcsIgnore)
|
|
||||||
{
|
|
||||||
// There is no need to cache the run if we've reached one of the
|
|
||||||
// string processing states in the output engine, since that data
|
|
||||||
// will be dealt with as soon as it is received.
|
|
||||||
cacheUnusedRun = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the run hasn't been dealt with in one of the cases above, we cache
|
if (_state == VTStates::Ground)
|
||||||
// the partial sequence in case we have to flush the whole thing later.
|
{
|
||||||
if (cacheUnusedRun)
|
break;
|
||||||
{
|
}
|
||||||
if (!_cachedSequence)
|
|
||||||
{
|
// We're not back in ground state, but ran out of text.
|
||||||
_cachedSequence.emplace(std::wstring{});
|
// --> Incomplete sequence. Clean up at the end.
|
||||||
|
if (_runEnd >= _currentString.size())
|
||||||
|
{
|
||||||
|
_processStringIncompleteSequence();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& cachedSequence = *_cachedSequence;
|
// The only way we get here is if the above loop ran into the ground state.
|
||||||
cachedSequence.append(run);
|
// This means we're starting a new "chunk".
|
||||||
|
// --> Reset beg(in).
|
||||||
|
_runBeg = _runEnd;
|
||||||
|
|
||||||
|
// Ran out of text. Return.
|
||||||
|
if (_runBeg >= _currentString.size())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plain text? Go back to the start, where we process plain text.
|
||||||
|
if (!Utils::IsActionableFromGround(til::at(_currentString, _runEnd)))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
void Microsoft::Console::VirtualTerminal::StateMachine::_processStringIncompleteSequence()
|
||||||
// - Determines whether the character being processed is the last in the
|
|
||||||
// current output fragment, or there are more still to come. Other parts
|
|
||||||
// of the framework can use this information to work more efficiently.
|
|
||||||
// Arguments:
|
|
||||||
// - <none>
|
|
||||||
// Return Value:
|
|
||||||
// - True if we're processing the last character. False if not.
|
|
||||||
bool StateMachine::IsProcessingLastCharacter() const noexcept
|
|
||||||
{
|
{
|
||||||
return _processingLastCharacter;
|
const auto run = _CurrentRun();
|
||||||
|
auto cacheUnusedRun = true;
|
||||||
|
|
||||||
|
// One of the "weird things" in VT input is the case of something like
|
||||||
|
// <kbd>alt+[</kbd>. In VT, that's encoded as `\x1b[`. However, that's
|
||||||
|
// also the start of a CSI, and could be the start of a longer sequence,
|
||||||
|
// there's no way to know for sure. For an <kbd>alt+[</kbd> keypress,
|
||||||
|
// the parser originally would just sit in the `CsiEntry` state after
|
||||||
|
// processing it, which would pollute the following keypress (e.g.
|
||||||
|
// <kbd>alt+[</kbd>, <kbd>A</kbd> would be processed like `\x1b[A`,
|
||||||
|
// which is _wrong_).
|
||||||
|
//
|
||||||
|
// At the same time, input may be broken up arbitrarily, depending on the pipe's
|
||||||
|
// buffer size, our read-buffer size, the sender's write-buffer size, and more.
|
||||||
|
// In fact, with the current WSL, input is broken up in 16 byte chunks (Why? :(),
|
||||||
|
// which breaks up many of our longer sequences, like our Win32InputMode ones.
|
||||||
|
//
|
||||||
|
// As a heuristic, this code specifically checks for a trailing Esc or Alt+key.
|
||||||
|
// If we encountered a win32-input-mode sequence before, we know that our \x1b[?9001h
|
||||||
|
// request to enable them was successful. While a client may still send \x1b{some char}
|
||||||
|
// intentionally, it's far more likely now that we're looking at a broken up sequence.
|
||||||
|
// The most common win32-input-mode is ConPTY itself after all, and we never emit
|
||||||
|
// \x1b{some char} once it's enabled.
|
||||||
|
if (_isEngineForInput)
|
||||||
|
{
|
||||||
|
const auto win32 = _engine->EncounteredWin32InputModeSequence();
|
||||||
|
if (!win32 && run.size() <= 2 && run.front() == L'\x1b')
|
||||||
|
{
|
||||||
|
_EnterGround();
|
||||||
|
if (run.size() == 1)
|
||||||
|
{
|
||||||
|
_ActionExecute(L'\x1b');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_EnterEscape();
|
||||||
|
_ActionEscDispatch(run.back());
|
||||||
|
}
|
||||||
|
_EnterGround();
|
||||||
|
// No need to cache the run, since we've dealt with it now.
|
||||||
|
cacheUnusedRun = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_state == VTStates::SosPmApcString || _state == VTStates::DcsPassThrough || _state == VTStates::DcsIgnore)
|
||||||
|
{
|
||||||
|
// There is no need to cache the run if we've reached one of the
|
||||||
|
// string processing states in the output engine, since that data
|
||||||
|
// will be dealt with as soon as it is received.
|
||||||
|
cacheUnusedRun = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the run hasn't been dealt with in one of the cases above, we cache
|
||||||
|
// the partial sequence in case we have to flush the whole thing later.
|
||||||
|
if (cacheUnusedRun)
|
||||||
|
{
|
||||||
|
if (!_cachedSequence)
|
||||||
|
{
|
||||||
|
_cachedSequence.emplace(std::wstring{});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& cachedSequence = *_cachedSequence;
|
||||||
|
cachedSequence.append(run);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StateMachine::InjectSequence(const InjectionType type)
|
void StateMachine::InjectSequence(const InjectionType type)
|
||||||
{
|
{
|
||||||
_injections.emplace_back(type, _runOffset + _runSize);
|
_injections.emplace_back(type, _runEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
const til::small_vector<Injection, 8>& StateMachine::GetInjections() const noexcept
|
const til::small_vector<Injection, 8>& StateMachine::GetInjections() const noexcept
|
||||||
@@ -2191,8 +2244,8 @@ void StateMachine::_ExecuteCsiCompleteCallback()
|
|||||||
// We need to save the state of the string that we're currently
|
// We need to save the state of the string that we're currently
|
||||||
// processing in case the callback injects another string.
|
// processing in case the callback injects another string.
|
||||||
const auto savedCurrentString = _currentString;
|
const auto savedCurrentString = _currentString;
|
||||||
const auto savedRunOffset = _runOffset;
|
const auto savedRunBeg = _runBeg;
|
||||||
const auto savedRunSize = _runSize;
|
const auto savedRunEnd = _runEnd;
|
||||||
// We also need to take ownership of the callback function before
|
// We also need to take ownership of the callback function before
|
||||||
// executing it so there's no risk of it being run more than once.
|
// executing it so there's no risk of it being run more than once.
|
||||||
const auto callback = std::move(_onCsiCompleteCallback);
|
const auto callback = std::move(_onCsiCompleteCallback);
|
||||||
@@ -2200,7 +2253,16 @@ void StateMachine::_ExecuteCsiCompleteCallback()
|
|||||||
// Once the callback has returned, we can restore the original state
|
// Once the callback has returned, we can restore the original state
|
||||||
// and continue where we left off.
|
// and continue where we left off.
|
||||||
_currentString = savedCurrentString;
|
_currentString = savedCurrentString;
|
||||||
_runOffset = savedRunOffset;
|
_runBeg = savedRunBeg;
|
||||||
_runSize = savedRunSize;
|
_runEnd = savedRunEnd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Construct current run.
|
||||||
|
//
|
||||||
|
// Note: We intentionally use this method to create the run lazily for better performance.
|
||||||
|
// You may find the usage of offset & size unsafe, but under heavy load it shows noticeable performance benefit.
|
||||||
|
std::wstring_view Microsoft::Console::VirtualTerminal::StateMachine::_CurrentRun() const
|
||||||
|
{
|
||||||
|
return _currentString.substr(_runBeg, _runEnd - _runBeg);
|
||||||
|
}
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ namespace Microsoft::Console::VirtualTerminal
|
|||||||
|
|
||||||
void ProcessCharacter(const wchar_t wch);
|
void ProcessCharacter(const wchar_t wch);
|
||||||
void ProcessString(const std::wstring_view string);
|
void ProcessString(const std::wstring_view string);
|
||||||
bool IsProcessingLastCharacter() const noexcept;
|
|
||||||
|
|
||||||
void InjectSequence(InjectionType type);
|
void InjectSequence(InjectionType type);
|
||||||
const til::small_vector<Injection, 8>& GetInjections() const noexcept;
|
const til::small_vector<Injection, 8>& GetInjections() const noexcept;
|
||||||
@@ -94,6 +93,8 @@ namespace Microsoft::Console::VirtualTerminal
|
|||||||
IStateMachineEngine& Engine() noexcept;
|
IStateMachineEngine& Engine() noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void _processStringIncompleteSequence();
|
||||||
|
|
||||||
void _ActionExecute(const wchar_t wch);
|
void _ActionExecute(const wchar_t wch);
|
||||||
void _ActionExecuteFromEscape(const wchar_t wch);
|
void _ActionExecuteFromEscape(const wchar_t wch);
|
||||||
void _ActionPrint(const wchar_t wch);
|
void _ActionPrint(const wchar_t wch);
|
||||||
@@ -162,6 +163,7 @@ namespace Microsoft::Console::VirtualTerminal
|
|||||||
bool _SafeExecute(TLambda&& lambda);
|
bool _SafeExecute(TLambda&& lambda);
|
||||||
|
|
||||||
void _ExecuteCsiCompleteCallback();
|
void _ExecuteCsiCompleteCallback();
|
||||||
|
std::wstring_view _CurrentRun() const;
|
||||||
|
|
||||||
enum class VTStates
|
enum class VTStates
|
||||||
{
|
{
|
||||||
@@ -192,43 +194,30 @@ namespace Microsoft::Console::VirtualTerminal
|
|||||||
std::unique_ptr<IStateMachineEngine> _engine;
|
std::unique_ptr<IStateMachineEngine> _engine;
|
||||||
const bool _isEngineForInput;
|
const bool _isEngineForInput;
|
||||||
|
|
||||||
VTStates _state;
|
VTStates _state = VTStates::Ground;
|
||||||
|
|
||||||
til::enumset<Mode> _parserMode{ Mode::Ansi };
|
til::enumset<Mode> _parserMode{ Mode::Ansi };
|
||||||
|
|
||||||
std::wstring_view _currentString;
|
std::wstring_view _currentString;
|
||||||
size_t _runOffset;
|
size_t _runBeg = 0;
|
||||||
size_t _runSize;
|
size_t _runEnd = 0;
|
||||||
|
|
||||||
// Construct current run.
|
|
||||||
//
|
|
||||||
// Note: We intentionally use this method to create the run lazily for better performance.
|
|
||||||
// You may find the usage of offset & size unsafe, but under heavy load it shows noticeable performance benefit.
|
|
||||||
std::wstring_view _CurrentRun() const
|
|
||||||
{
|
|
||||||
return _currentString.substr(_runOffset, _runSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
VTIDBuilder _identifier;
|
VTIDBuilder _identifier;
|
||||||
std::vector<VTParameter> _parameters;
|
std::vector<VTParameter> _parameters;
|
||||||
bool _parameterLimitOverflowed;
|
|
||||||
std::vector<VTParameter> _subParameters;
|
std::vector<VTParameter> _subParameters;
|
||||||
std::vector<std::pair<BYTE /*range start*/, BYTE /*range end*/>> _subParameterRanges;
|
std::vector<std::pair<BYTE /*range start*/, BYTE /*range end*/>> _subParameterRanges;
|
||||||
bool _subParameterLimitOverflowed;
|
bool _parameterLimitOverflowed = false;
|
||||||
BYTE _subParameterCounter;
|
bool _subParameterLimitOverflowed = false;
|
||||||
|
BYTE _subParameterCounter = 0;
|
||||||
|
|
||||||
std::wstring _oscString;
|
std::wstring _oscString;
|
||||||
VTInt _oscParameter;
|
VTInt _oscParameter = 0;
|
||||||
|
|
||||||
IStateMachineEngine::StringHandler _dcsStringHandler;
|
IStateMachineEngine::StringHandler _dcsStringHandler;
|
||||||
|
|
||||||
std::optional<std::wstring> _cachedSequence;
|
std::optional<std::wstring> _cachedSequence;
|
||||||
til::small_vector<Injection, 8> _injections;
|
til::small_vector<Injection, 8> _injections;
|
||||||
|
|
||||||
// This is tracked per state machine instance so that separate calls to Process*
|
|
||||||
// can start and finish a sequence.
|
|
||||||
bool _processingLastCharacter;
|
|
||||||
|
|
||||||
std::function<void()> _onCsiCompleteCallback;
|
std::function<void()> _onCsiCompleteCallback;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ namespace Microsoft::Console::Utils
|
|||||||
// testing easier.
|
// testing easier.
|
||||||
std::wstring_view TrimPaste(std::wstring_view textView) noexcept;
|
std::wstring_view TrimPaste(std::wstring_view textView) noexcept;
|
||||||
|
|
||||||
|
bool IsActionableFromGround(const wchar_t wch) noexcept;
|
||||||
const wchar_t* FindActionableControlCharacter(const wchar_t* beg, const size_t len) noexcept;
|
const wchar_t* FindActionableControlCharacter(const wchar_t* beg, const size_t len) noexcept;
|
||||||
|
|
||||||
// Same deal, but in TerminalPage::_evaluatePathForCwd
|
// Same deal, but in TerminalPage::_evaluatePathForCwd
|
||||||
|
|||||||
@@ -1139,7 +1139,8 @@ std::wstring_view Utils::TrimPaste(std::wstring_view textView) noexcept
|
|||||||
#pragma warning(disable : 26490) // Don't use reinterpret_cast (type.1).
|
#pragma warning(disable : 26490) // Don't use reinterpret_cast (type.1).
|
||||||
|
|
||||||
// Returns true for C0 characters and C1 [single-character] CSI.
|
// Returns true for C0 characters and C1 [single-character] CSI.
|
||||||
constexpr bool isActionableFromGround(const wchar_t wch) noexcept
|
#pragma warning(suppress : 26497) // You can attempt to make 'Microsoft::Console::Utils::IsActionableFromGround' constexpr unless it contains any undefined behavior(f.4).
|
||||||
|
bool Utils::IsActionableFromGround(const wchar_t wch) noexcept
|
||||||
{
|
{
|
||||||
// This is equivalent to:
|
// This is equivalent to:
|
||||||
// return (wch <= 0x1f) || (wch >= 0x7f && wch <= 0x9f);
|
// return (wch <= 0x1f) || (wch >= 0x7f && wch <= 0x9f);
|
||||||
@@ -1230,7 +1231,7 @@ plainSearch:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#pragma loop(no_vector)
|
#pragma loop(no_vector)
|
||||||
for (const auto end = beg + len; it < end && !isActionableFromGround(*it); ++it)
|
for (const auto end = beg + len; it < end && !IsActionableFromGround(*it); ++it)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user