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 f836b25
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 6 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] = area;
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
15 changes: 15 additions & 0 deletions src/terminal/Screen.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,21 @@ class Screen final: public ScreenBase, public capabilities::StaticDatabase
void clearToEndOfScreen();
void clearScreen();

// DECSEL
void selectiveEraseToBeginOfLine();
void selectiveEraseToEndOfLine();
void selectiveEraseLine(LineOffset line);

// DECSED
void selectiveEraseToBeginOfScreen();
void selectiveEraseToEndOfScreen();
void selectiveEraseScreen();

void selectiveEraseArea(Rect area);

void selectiveErase(LineOffset line, ColumnOffset begin, ColumnOffset end);
[[nodiscard]] bool containsProtectedCharacters(LineOffset line, ColumnOffset begin, ColumnOffset end) const;

void eraseCharacters(ColumnCount _n); // ECH
void insertCharacters(ColumnCount _n); // ICH
void deleteCharacters(ColumnCount _n); // DCH
Expand Down

0 comments on commit f836b25

Please sign in to comment.