Skip to content

Commit

Permalink
Add support for colon separated sub-parameters (#15648)
Browse files Browse the repository at this point in the history
Adds support for colon `:` separated sub parameters in parser.
Technically, after this PR, nothing should change except, now sub
parameters are parsed, stored safely and we don't invalidate the whole
sequence when a `:` is received within a parameter substring.

In this PR:
- If sub parameters are detected with a parameter, but the usage is
unrecognised, we simply *skip* the parameter in `adaptDispatch`.
- A separate store for sub parameters is used to avoid too many changes
to the codebase.
- We currently allow up to `6` sub parameters for each parameter, extra
sub parameters are *ignored*.
- Introduced `VTSubParameters` for easy access to underlying sub
parameters.

> **Info**: We don't use sub parameters for any feature yet, this is
just the core implementation to support newer usecases.

## Validation Steps Performed
- [x] Use of sub parameters must not have any effect on the output.
- [x] Skip parameters with unexpected set of sub parameters.
- [x] Skip sequences with unexpected set of sub parameters.

References #4321
References #7228
References #15599
References xtermjs/xterm.js#2751
Closes #4321
  • Loading branch information
tusharsnx authored Jul 18, 2023
1 parent 11a9808 commit 6873c85
Show file tree
Hide file tree
Showing 8 changed files with 483 additions and 51 deletions.
116 changes: 103 additions & 13 deletions src/terminal/adapter/DispatchTypes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,67 +152,157 @@ namespace Microsoft::Console::VirtualTerminal
VTInt _value;
};

class VTSubParameters
{
public:
constexpr VTSubParameters() noexcept
{
}

constexpr VTSubParameters(const std::span<const VTParameter> subParams) noexcept :
_subParams{ subParams }
{
}

constexpr VTParameter at(const size_t index) const noexcept
{
return til::at(_subParams, index);
}

VTSubParameters subspan(const size_t offset, const size_t count) const noexcept
{
const auto subParamsSpan = _subParams.subspan(offset, count);
return { subParamsSpan };
}

bool empty() const noexcept
{
return _subParams.empty();
}

size_t size() const noexcept
{
return _subParams.size();
}

constexpr operator std::span<const VTParameter>() const noexcept
{
return _subParams;
}

private:
std::span<const VTParameter> _subParams;
};

class VTParameters
{
public:
constexpr VTParameters() noexcept
{
}

constexpr VTParameters(const VTParameter* ptr, const size_t count) noexcept :
_values{ ptr, count }
constexpr VTParameters(const VTParameter* paramsPtr, const size_t paramsCount) noexcept :
_params{ paramsPtr, paramsCount },
_subParams{},
_subParamRanges{}
{
}

constexpr VTParameters(const std::span<const VTParameter> params,
const std::span<const VTParameter> subParams,
const std::span<const std::pair<BYTE, BYTE>> subParamRanges) noexcept :
_params{ params },
_subParams{ subParams },
_subParamRanges{ subParamRanges }
{
}

constexpr VTParameter at(const size_t index) const noexcept
{
// If the index is out of range, we return a parameter with no value.
return index < _values.size() ? til::at(_values, index) : defaultParameter;
return index < _params.size() ? til::at(_params, index) : defaultParameter;
}

constexpr bool empty() const noexcept
{
return _values.empty();
return _params.empty();
}

constexpr size_t size() const noexcept
{
// We always return a size of at least 1, since an empty parameter
// list is the equivalent of a single "default" parameter.
return std::max<size_t>(_values.size(), 1);
return std::max<size_t>(_params.size(), 1);
}

VTParameters subspan(const size_t offset) const noexcept
{
const auto subValues = _values.subspan(std::min(offset, _values.size()));
return { subValues.data(), subValues.size() };
// We need sub parameters to always be in their original index
// because we store their indexes in subParamRanges. So we pass
// _subParams as is and create new span for others.
const auto newParamsSpan = _params.subspan(std::min(offset, _params.size()));
const auto newSubParamRangesSpan = _subParamRanges.subspan(std::min(offset, _subParamRanges.size()));
return { newParamsSpan, _subParams, newSubParamRangesSpan };
}

VTSubParameters subParamsFor(const size_t index) const noexcept
{
if (index < _subParamRanges.size())
{
const auto& range = til::at(_subParamRanges, index);
return _subParams.subspan(range.first, range.second - range.first);
}
else
{
return VTSubParameters{};
}
}

bool hasSubParams() const noexcept
{
return !_subParams.empty();
}

bool hasSubParamsFor(const size_t index) const noexcept
{
if (index < _subParamRanges.size())
{
const auto& range = til::at(_subParamRanges, index);
return range.second > range.first;
}
else
{
return false;
}
}

template<typename T>
bool for_each(const T&& predicate) const
{
auto values = _values;
auto params = _params;

// We always return at least 1 value here, since an empty parameter
// list is the equivalent of a single "default" parameter.
if (values.empty())
if (params.empty())
{
values = defaultParameters;
params = defaultParameters;
}

auto success = true;
for (const auto& v : values)
for (const auto& v : params)
{
success = predicate(v) && success;
}
return success;
}

private:
static constexpr VTParameter defaultParameter;
static constexpr VTParameter defaultParameter{};
static constexpr std::span defaultParameters{ &defaultParameter, 1 };

std::span<const VTParameter> _values;
std::span<const VTParameter> _params;
VTSubParameters _subParams;
std::span<const std::pair<BYTE, BYTE>> _subParamRanges;
};

// FlaggedEnumValue is a convenience class that produces enum values (of a specified size)
Expand Down
3 changes: 3 additions & 0 deletions src/terminal/adapter/adaptDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,9 @@ namespace Microsoft::Console::VirtualTerminal
size_t _ApplyGraphicsOption(const VTParameters options,
const size_t optionIndex,
TextAttribute& attr) noexcept;
void _ApplyGraphicsOptionSubParam(const VTParameter option,
const VTSubParameters subParams,
TextAttribute& attr) noexcept;
void _ApplyGraphicsOptions(const VTParameters options,
TextAttribute& attr) noexcept;

Expand Down
26 changes: 26 additions & 0 deletions src/terminal/adapter/adaptDispatchGraphics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ size_t AdaptDispatch::_SetRgbColorsHelper(const VTParameters options,

// Routine Description:
// - Helper to apply a single graphic rendition option to an attribute.
// - Calls appropriate helper to apply the option with sub parameters when necessary.
// Arguments:
// - options - An array of options.
// - optionIndex - The start index of the option that will be applied.
Expand All @@ -75,6 +76,14 @@ size_t AdaptDispatch::_ApplyGraphicsOption(const VTParameters options,
TextAttribute& attr) noexcept
{
const GraphicsOptions opt = options.at(optionIndex);

if (options.hasSubParamsFor(optionIndex))
{
const auto subParams = options.subParamsFor(optionIndex);
_ApplyGraphicsOptionSubParam(opt, subParams, attr);
return 1;
}

switch (opt)
{
case Off:
Expand Down Expand Up @@ -250,6 +259,23 @@ size_t AdaptDispatch::_ApplyGraphicsOption(const VTParameters options,
}
}

// Routine Description:
// - This is a no-op until we have something meaningful to do with sub parameters.
// - Helper to apply a single graphic rendition option with sub parameters to an attribute.
// Arguments:
// - option - An option to apply.
// - attr - The attribute that will be updated with the applied option.
// Return Value:
// - <None>
void AdaptDispatch::_ApplyGraphicsOptionSubParam(const VTParameter /* option */,
const VTSubParameters /* subParam */,
TextAttribute& /* attr */) noexcept
{
// here, we apply our "best effort" rule, while handling sub params if we don't
// recognise the parameter substring (parameter and it's sub parameters) then
// we should just skip over them.
}

// Routine Description:
// - Helper to apply a number of graphic rendition options to an attribute.
// Arguments:
Expand Down
26 changes: 26 additions & 0 deletions src/terminal/parser/OutputStateMachineEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,12 @@ bool OutputStateMachineEngine::ActionVt52EscDispatch(const VTID id, const VTPara
// - true iff we successfully dispatched the sequence.
bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParameters parameters)
{
// Bail out if we receive subparameters, but we don't accept them in the sequence.
if (parameters.hasSubParams() && !_CanSeqAcceptSubParam(id, parameters)) [[unlikely]]
{
return false;
}

auto success = false;

switch (id)
Expand Down Expand Up @@ -1098,6 +1104,26 @@ bool OutputStateMachineEngine::_GetOscSetClipboard(const std::wstring_view strin
return SUCCEEDED_LOG(Base64::Decode(substr, content));
}

// Routine Description:
// - Takes a sequence id ("final byte") and determines if it accepts sub parameters.
// Arguments:
// - id - The sequence id to check for.
// Return Value:
// - True, if it accepts sub parameters or else False.
bool OutputStateMachineEngine::_CanSeqAcceptSubParam(const VTID id, const VTParameters& parameters) noexcept
{
switch (id)
{
case SGR_SetGraphicsRendition:
return true;
case DECCARA_ChangeAttributesRectangularArea:
case DECRARA_ReverseAttributesRectangularArea:
return !parameters.hasSubParamsFor(0) && !parameters.hasSubParamsFor(1) && !parameters.hasSubParamsFor(2) && !parameters.hasSubParamsFor(3);
default:
return false;
}
}

// Method Description:
// - Clears our last stored character. The last stored character is the last
// graphical character we printed, which is reset if any other action is
Expand Down
2 changes: 2 additions & 0 deletions src/terminal/parser/OutputStateMachineEngine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ namespace Microsoft::Console::VirtualTerminal
std::wstring& params,
std::wstring& uri) const;

bool _CanSeqAcceptSubParam(const VTID id, const VTParameters& parameters) noexcept;

void _ClearLastChar() noexcept;
};
}
Loading

0 comments on commit 6873c85

Please sign in to comment.