Skip to content

Commit

Permalink
Adds VT sequence DECSCA, DECSEL, DECSED and DECSERA to support protec…
Browse files Browse the repository at this point in the history
…ted grid areas during erase operations (#29, #30, #31).
  • Loading branch information
christianparpart committed Sep 24, 2022
1 parent 94fb93f commit 1d1003a
Show file tree
Hide file tree
Showing 6 changed files with 304 additions and 125 deletions.
3 changes: 2 additions & 1 deletion metainfo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@
<li>Fixes Sixel mode, when updating the color palette with a new color, that color must also be used for subsequent paints.</li>
<li>Fixes vertical cursor movement for Sixel graphics with only newlines (#822).</li>
<li>Fixes Sixel rendering for images with aspect ratios other than 1:1.</li>
<li>Removes `images.sixel_cursor_conformance` config option.</li>
<li>Removes `images.sixel_cursor_conformance` config option.</li>
<li>Adds VT sequence DECSCA, DECSEL, DECSED and DECSERA to support protected grid areas during erase operations (#29, #30, #31).</li>
</ul>
</description>
</release>
Expand Down
6 changes: 4 additions & 2 deletions src/terminal/CellFlags.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
namespace terminal
{

enum class CellFlags : uint16_t
enum class CellFlags : uint32_t
{
None = 0,

Expand All @@ -43,6 +43,7 @@ enum class CellFlags : uint16_t
Encircled = (1 << 13),
Overline = (1 << 14),
RapidBlinking = (1 << 15),
CharacterProtected = (1 << 16), // Character is protected by selective erase operations.
};

constexpr CellFlags& operator|=(CellFlags& a, CellFlags b) noexcept
Expand Down Expand Up @@ -101,7 +102,7 @@ struct formatter<terminal::CellFlags>
template <typename FormatContext>
auto format(const terminal::CellFlags _flags, FormatContext& ctx)
{
static const std::array<std::pair<terminal::CellFlags, std::string_view>, 16> nameMap = {
static const std::array<std::pair<terminal::CellFlags, std::string_view>, 17> nameMap = {
std::pair { terminal::CellFlags::Bold, std::string_view("Bold") },
std::pair { terminal::CellFlags::Faint, std::string_view("Faint") },
std::pair { terminal::CellFlags::Italic, std::string_view("Italic") },
Expand All @@ -118,6 +119,7 @@ struct formatter<terminal::CellFlags>
std::pair { terminal::CellFlags::Framed, std::string_view("Framed") },
std::pair { terminal::CellFlags::Encircled, std::string_view("Encircled") },
std::pair { terminal::CellFlags::Overline, std::string_view("Overline") },
std::pair { terminal::CellFlags::CharacterProtected, std::string_view("CharacterProtected") },
};
std::string s;
for (auto const& mapping: nameMap)
Expand Down
8 changes: 8 additions & 0 deletions src/terminal/Functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,10 @@ constexpr inline auto DECERA = detail::CSI(std::nullopt, 0, 4, '$', 'z', VT
constexpr inline auto DECFRA = detail::CSI(std::nullopt, 0, 4, '$', 'x', VTType::VT420, "DECFRA", "Fill rectangular area");
constexpr inline auto DECDC = detail::CSI(std::nullopt, 0, 1, '\'', '~', VTType::VT420, "DECDC", "Delete column");
constexpr inline auto DECIC = detail::CSI(std::nullopt, 0, 1, '\'', '}', VTType::VT420, "DECIC", "Insert column");
constexpr inline auto DECSCA = detail::CSI(std::nullopt, 0, 1, '"', 'q', VTType::VT240, "DECSCA", "Select Character Protection Attribute");
constexpr inline auto DECSED = detail::CSI('?', 0, 1, std::nullopt, 'J', VTType::VT240, "DECSED", "Selective Erase in Display");
constexpr inline auto DECSERA = detail::CSI('?', 0, 4, '$', '{', VTType::VT240, "DECSERA", "Selective Erase in Rectangular Area");
constexpr inline auto DECSEL = detail::CSI('?', 0, 1, std::nullopt, 'K', VTType::VT240, "DECSEL", "Selective Erase in Line");
constexpr inline auto XTRESTORE = detail::CSI('?', 0, ArgsMax, std::nullopt, 'r', VTExtension::XTerm, "XTRESTORE", "Restore DEC private modes.");
constexpr inline auto XTSAVE = detail::CSI('?', 0, ArgsMax, std::nullopt, 's', VTExtension::XTerm, "XTSAVE", "Save DEC private modes.");
constexpr inline auto DECRM = detail::CSI('?', 1, ArgsMax, std::nullopt, 'l', VTType::VT100, "DECRM", "Reset DEC-mode");
Expand Down Expand Up @@ -527,6 +531,10 @@ inline auto const& functions() noexcept
DECERA,
DECFRA,
DECIC,
DECSCA,
DECSED,
DECSERA,
DECSEL,
XTRESTORE,
XTSAVE,
DECPS,
Expand Down
182 changes: 179 additions & 3 deletions src/terminal/Screen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,7 @@ void Screen<Cell>::sendTerminalId()
_terminal.reply("\033[>{};{};{}c", Pp, Pv, Pc);
}

// {{{ ED
template <typename Cell>
void Screen<Cell>::clearToEndOfScreen()
{
Expand Down Expand Up @@ -871,6 +872,7 @@ void Screen<Cell>::clearScreen()
// up in case the content is still needed.
scrollUp(_state.pageSize.lines);
}
// }}}

template <typename Cell>
void Screen<Cell>::eraseCharacters(ColumnCount _n)
Expand All @@ -890,6 +892,135 @@ void Screen<Cell>::eraseCharacters(ColumnCount _n)
line.useCellAt(_state.cursor.position.column + i).reset(_state.cursor.graphicsRendition);
}

// {{{ DECSEL
template <typename Cell>
void Screen<Cell>::selectiveEraseToEndOfLine()
{
if (_terminal.isFullHorizontalMargins() && _state.cursor.position.column.value == 0)
selectiveEraseLine(_state.cursor.position.line);
else
selectiveErase(_state.cursor.position.line,
_state.cursor.position.column,
ColumnOffset::cast_from(_state.pageSize.columns));
}

template <typename Cell>
void Screen<Cell>::selectiveEraseToBeginOfLine()
{
if (_terminal.isFullHorizontalMargins()
&& _state.cursor.position.column.value == _state.pageSize.columns.value)
selectiveEraseLine(_state.cursor.position.line);
else
selectiveErase(_state.cursor.position.line, ColumnOffset(0), _state.cursor.position.column + 1);
}

template <typename Cell>
void Screen<Cell>::selectiveEraseLine(LineOffset line)
{
if (containsProtectedCharacters(
_state.cursor.position.line, ColumnOffset(0), ColumnOffset::cast_from(_state.pageSize.columns)))
{
selectiveErase(
_state.cursor.position.line, ColumnOffset(0), ColumnOffset::cast_from(_state.pageSize.columns));
return;
}

currentLine().reset(grid().defaultLineFlags(), _state.cursor.graphicsRendition);

auto const left = ColumnOffset(0);
auto const right = boxed_cast<ColumnOffset>(_state.pageSize.columns - 1);
auto const area = Rect { Top(*line), Left(*left), Bottom(*line), Right(*right) };
_terminal.markRegionDirty(area);
}

template <typename Cell>
void Screen<Cell>::selectiveErase(LineOffset line, ColumnOffset begin, ColumnOffset end)
{
Cell* i = &at(line, begin);
Cell const* e = i + unbox<uintptr_t>(end);
while (i != e)
{
if (i->isFlagEnabled(CellFlags::CharacterProtected))
continue;
i->reset(_state.cursor.graphicsRendition);
++i;
}

auto const left = begin;
auto const right = end - 1;
auto const area = Rect { Top(*line), Left(*left), Bottom(*line), Right(*right) };
_terminal.markRegionDirty(area);
}

template <typename Cell>
bool Screen<Cell>::containsProtectedCharacters(LineOffset line, ColumnOffset begin, ColumnOffset end) const
{
Cell const* i = &at(line, begin);
Cell const* e = i + unbox<uintptr_t>(end);
while (i != e)
{
if (i->isFlagEnabled(CellFlags::CharacterProtected))
return true;
++i;
}
return false;
}
// }}}
// {{{ DECSED
template <typename Cell>
void Screen<Cell>::selectiveEraseToEndOfScreen()
{
selectiveEraseToEndOfLine();

auto const lineStart = unbox<int>(_state.cursor.position.line) + 1;
auto const lineEnd = unbox<int>(_state.pageSize.lines);

for (auto const lineOffset: ranges::views::iota(lineStart, lineEnd))
selectiveEraseLine(LineOffset::cast_from(lineOffset));
}

template <typename Cell>
void Screen<Cell>::selectiveEraseToBeginOfScreen()
{
selectiveEraseToBeginOfLine();

for (auto const lineOffset: ranges::views::iota(0, *_state.cursor.position.line))
selectiveEraseLine(LineOffset::cast_from(lineOffset));
}

template <typename Cell>
void Screen<Cell>::selectiveEraseScreen()
{
for (auto const lineOffset: ranges::views::iota(0, *_state.pageSize.lines))
selectiveEraseLine(LineOffset::cast_from(lineOffset));
}
// }}}
// {{{ DECSERA
template <typename Cell>
void Screen<Cell>::selectiveEraseArea(Rect area)
{
auto const [top, left, bottom, right] = applyOriginMode(area).clampTo(_state.pageSize);
assert(unbox<int>(right) <= unbox<int>(_state.pageSize.columns));
assert(unbox<int>(bottom) <= unbox<int>(_state.pageSize.lines));

if (top.value > bottom.value || left.value > right.value)
return;

for (int y = top.value; y <= bottom.value; ++y)
{
for (Cell& cell: grid()
.lineAt(LineOffset::cast_from(y))
.useRange(ColumnOffset::cast_from(left),
ColumnCount::cast_from(right.value - left.value + 1)))
{
if (!cell.isFlagEnabled(CellFlags::CharacterProtected))
cell.write(_state.cursor.graphicsRendition, L' ', 1);
}
}
}
// }}}

// {{{ EL
template <typename Cell>
void Screen<Cell>::clearToEndOfLine()
{
Expand Down Expand Up @@ -943,6 +1074,7 @@ void Screen<Cell>::clearLine()
auto const area = Rect { Top(*line), Left(*left), Bottom(*line), Right(*right) };
_terminal.markRegionDirty(area);
}
// }}}

template <typename Cell>
void Screen<Cell>::moveCursorToNextLine(LineCount _n)
Expand Down Expand Up @@ -1821,9 +1953,11 @@ void Screen<Cell>::requestStatusString(RequestStatusString _value)
case RequestStatusString::DECSNLS: return fmt::format("{}*|", _state.pageSize.lines);
case RequestStatusString::SGR:
return fmt::format("0;{}m", vtSequenceParameterString(_state.cursor.graphicsRendition));
case RequestStatusString::DECSCA: // TODO
errorlog()(fmt::format("Requesting device status for {} not implemented yet.", _value));
break;
case RequestStatusString::DECSCA: {
auto const isProtected =
_state.cursor.graphicsRendition.styles & CellFlags::CharacterProtected;
return fmt::format("{}\"q", isProtected ? 1 : 2);
}
case RequestStatusString::DECSASD:
switch (_state.activeStatusDisplay)
{
Expand Down Expand Up @@ -3184,6 +3318,48 @@ ApplyResult Screen<Cell>::apply(FunctionDefinition const& function, Sequence con
break;
case DECDC: deleteColumns(seq.param_or(0, ColumnCount(1))); break;
case DECIC: insertColumns(seq.param_or(0, ColumnCount(1))); break;
case DECSCA: {
auto const Pc = seq.param_or(0, 0);
switch (Pc)
{
case 1:
_state.cursor.graphicsRendition.styles |= CellFlags::CharacterProtected;
return ApplyResult::Ok;
case 0:
case 2:
_state.cursor.graphicsRendition.styles &= ~CellFlags::CharacterProtected;
return ApplyResult::Ok;
default: return ApplyResult::Invalid;
}
}
case DECSED: {
switch (seq.param_or(0, Sequence::Parameter { 0 }))
{
case 0: selectiveEraseToEndOfScreen(); break;
case 1: selectiveEraseToBeginOfScreen(); break;
case 2: selectiveEraseScreen(); break;
default: return ApplyResult::Unsupported;
}
return ApplyResult::Ok;
}
case DECSERA: {
auto const top = seq.param_or(0, Top(0));
auto const left = seq.param_or(1, Left(0));
auto const bottom = seq.param_or(2, Bottom::cast_from(_state.pageSize.lines - 1));
auto const right = seq.param_or(3, Right::cast_from(_state.pageSize.columns - 1));
selectiveEraseArea(Rect { top, left, bottom, right });
return ApplyResult::Ok;
}
case DECSEL: {
switch (seq.param_or(0, Sequence::Parameter { 0 }))
{
case 0: selectiveEraseToEndOfLine(); break;
case 1: selectiveEraseToBeginOfLine(); break;
case 2: selectiveEraseLine(_state.cursor.position.line); break;
default: return ApplyResult::Invalid;
}
return ApplyResult::Ok;
}
case DECRM: {
ApplyResult r = ApplyResult::Ok;
crispy::for_each(crispy::times(seq.parameterCount()), [&](size_t i) {
Expand Down
Loading

0 comments on commit 1d1003a

Please sign in to comment.