diff --git a/src/contour/Config.cpp b/src/contour/Config.cpp index a1854765a7..0bc5bc7ae5 100644 --- a/src/contour/Config.cpp +++ b/src/contour/Config.cpp @@ -1969,6 +1969,7 @@ void loadConfigFromFile(Config& _config, FileSystem::path const& _fileName) } tryLoadValue(usedKeys, doc, "reflow_on_resize", _config.reflowOnResize); + tryLoadValue(usedKeys, doc, "expand_tabs", _config.expandTabs); if (auto profiles = doc["profiles"]; profiles) { diff --git a/src/contour/Config.h b/src/contour/Config.h index 27b585f837..61c0afe46d 100644 --- a/src/contour/Config.h +++ b/src/contour/Config.h @@ -254,6 +254,7 @@ struct Config size_t ptyBufferObjectSize = 1024lu * 1024lu; bool reflowOnResize = true; + bool expandTabs = false; std::unordered_map colorschemes; std::unordered_map profiles; diff --git a/src/contour/TerminalSession.cpp b/src/contour/TerminalSession.cpp index 6e78c9925d..b80c83bc07 100644 --- a/src/contour/TerminalSession.cpp +++ b/src/contour/TerminalSession.cpp @@ -131,6 +131,7 @@ namespace settings.primaryScreen.allowReflowOnResize = config.reflowOnResize; settings.highlightDoubleClickedWord = profile.highlightDoubleClickedWord; settings.highlightTimeout = profile.highlightTimeout; + settings.expandTabs = config.expandTabs; return settings; } diff --git a/src/vtbackend/CellFlags.h b/src/vtbackend/CellFlags.h index e482a2c962..e52df2aec5 100644 --- a/src/vtbackend/CellFlags.h +++ b/src/vtbackend/CellFlags.h @@ -44,6 +44,7 @@ enum class CellFlags : uint32_t Overline = (1 << 14), RapidBlinking = (1 << 15), CharacterProtected = (1 << 16), // Character is protected by selective erase operations. + Tab = (1 << 17), // Cell is part of tab character. }; constexpr CellFlags& operator|=(CellFlags& a, CellFlags b) noexcept diff --git a/src/vtbackend/Grid.cpp b/src/vtbackend/Grid.cpp index 50feb7bbbc..e4741336d5 100644 --- a/src/vtbackend/Grid.cpp +++ b/src/vtbackend/Grid.cpp @@ -801,8 +801,7 @@ CellLocation Grid::resize(PageSize newSize, CellLocation currentCursorPos, flushLogicalLine(); if (line.isTrivialBuffer()) { - auto& buffer = line.trivialBuffer(); - buffer.displayWidth = newColumnCount; + line.trivialBuffer().resize(newColumnCount); grownLines.emplace_back(line); } else diff --git a/src/vtbackend/Line.cpp b/src/vtbackend/Line.cpp index 3079b2dfa1..d30e03a3bf 100644 --- a/src/vtbackend/Line.cpp +++ b/src/vtbackend/Line.cpp @@ -34,7 +34,7 @@ typename Line::InflatedBuffer Line::reflow(ColumnCount newColumnCoun { switch (crispy::strongCompare(newColumnCount, ColumnCount::cast_from(trivialBuffer().text.size()))) { - case Comparison::Greater: trivialBuffer().displayWidth = newColumnCount; return {}; + case Comparison::Greater: trivialBuffer().resize(newColumnCount); return {}; case Comparison::Equal: return {}; case Comparison::Less:; } @@ -99,8 +99,7 @@ inline void Line::resize(ColumnCount count) { if (isTrivialBuffer()) { - TrivialBuffer& buffer = trivialBuffer(); - buffer.displayWidth = count; + trivialBuffer().resize(count); return; } } @@ -179,7 +178,7 @@ InflatedLineBuffer inflate(TrivialLineBuffer const& input) auto lastChar = char32_t { 0 }; auto utf8DecoderState = unicode::utf8_decoder_state {}; auto gapPending = 0; - + size_t cellNr = 0; for (char const ch: input.text.view()) { unicode::ConvertResult const r = unicode::from_utf8(utf8DecoderState, static_cast(ch)); @@ -200,6 +199,7 @@ InflatedLineBuffer inflate(TrivialLineBuffer const& input) columns.emplace_back(Cell {}); columns.back().setHyperlink(input.hyperlink); columns.back().write(input.textAttributes, nextChar, static_cast(charWidth)); + columns.back().setTab(input.tabstops[cellNr]); gapPending = charWidth - 1; } else @@ -218,6 +218,7 @@ InflatedLineBuffer inflate(TrivialLineBuffer const& input) } } lastChar = nextChar; + ++cellNr; } while (gapPending > 0) @@ -229,10 +230,31 @@ InflatedLineBuffer inflate(TrivialLineBuffer const& input) assert(columns.size() == unbox(input.usedColumns)); while (columns.size() < unbox(input.displayWidth)) + { columns.emplace_back(Cell { input.fillAttributes }); + columns.back().setTab(input.tabstops[cellNr]); + ++cellNr; + } return columns; } + +template +void Line::setTab(ColumnOffset start, ColumnCount n, bool tab) +{ + if (isInflatedBuffer()) + { + for (; n > ColumnCount(0); --n) + useCellAt(start++).setTab(tab); + } + else + { + auto& buffer = trivialBuffer(); + for (; n > ColumnCount(0); --n) + buffer.tabstops[(start++).as()] = true; + // assert(false); + } +} } // end namespace terminal #include diff --git a/src/vtbackend/Line.h b/src/vtbackend/Line.h index 098373e15c..43deec08dc 100644 --- a/src/vtbackend/Line.h +++ b/src/vtbackend/Line.h @@ -64,7 +64,7 @@ struct TrivialLineBuffer ColumnCount usedColumns {}; crispy::BufferFragment text {}; - + std::vector tabstops = std::vector(displayWidth.value, false); void reset(GraphicsAttributes attributes) noexcept { textAttributes = attributes; @@ -72,6 +72,12 @@ struct TrivialLineBuffer hyperlink = {}; usedColumns = {}; text.reset(); + tabstops.clear(); + } + void resize(ColumnCount count) + { + displayWidth = count; + tabstops.resize(count.as()); } }; @@ -234,6 +240,15 @@ class Line return inflatedBuffer().at(unbox(column)).empty(); } + [[nodiscard]] bool hasTabstop(ColumnOffset column) const noexcept + { + Require(ColumnOffset(0) <= column); + Require(column <= ColumnOffset::cast_from(size())); + if (isInflatedBuffer()) + return cells()[column.as()].isTab(); + return trivialBuffer().tabstops[column.as()]; + } + [[nodiscard]] uint8_t cellWidthAt(ColumnOffset column) const noexcept { #if 0 // TODO: This optimization - but only when we return actual widths and not always 1. @@ -258,6 +273,8 @@ class Line [[nodiscard]] bool wrappable() const noexcept { return isFlagEnabled(LineFlags::Wrappable); } void setWrappable(bool enable) { setFlag(LineFlags::Wrappable, enable); } + void setTab(ColumnOffset start, ColumnCount n, bool tab); + [[nodiscard]] LineFlags wrappableFlag() const noexcept { return wrappable() ? LineFlags::Wrappable : LineFlags::None; diff --git a/src/vtbackend/Screen.cpp b/src/vtbackend/Screen.cpp index b42ece5006..ba39b9fc99 100644 --- a/src/vtbackend/Screen.cpp +++ b/src/vtbackend/Screen.cpp @@ -86,8 +86,6 @@ using std::vector; namespace terminal { -auto constexpr inline TabWidth = ColumnCount(8); - auto const inline VTCaptureBufferLog = logstore::Category("vt.ext.capturebuffer", "Capture Buffer debug logging.", logstore::Category::State::Disabled, @@ -1468,9 +1466,12 @@ void Screen::moveCursorToNextTab() ++i; auto const currentCursorColumn = logicalCursorPosition().column; - if (i < _state.tabs.size()) - moveCursorForward(boxed_cast(_state.tabs[i] - currentCursorColumn)); + { + auto const cursorMoveAmount = boxed_cast(_state.tabs[i] - currentCursorColumn); + currentLine().setTab(currentCursorColumn, cursorMoveAmount, true); + moveCursorForward(cursorMoveAmount); + } else if (realCursorPosition().column < margin().horizontal.to) moveCursorForward(boxed_cast(margin().horizontal.to - currentCursorColumn)); } @@ -1482,6 +1483,7 @@ void Screen::moveCursorToNextTab() auto const n = min((TabWidth - boxed_cast(_cursor.position.column) % TabWidth), _settings.pageSize.columns - boxed_cast(logicalCursorPosition().column)); + currentLine().setTab(logicalCursorPosition().column, n, true); moveCursorForward(n); } } @@ -3912,6 +3914,49 @@ bool Screen::isCursorInsideMargins() const noexcept return insideVerticalMargin && insideHorizontalMargin; } +template +CRISPY_REQUIRES(CellConcept) +CellLocation Screen::getTabstopStart(CellLocation position) const noexcept +{ + if (!_state.tabs.empty()) + { + auto tab = std::lower_bound(_state.tabs.begin(), _state.tabs.end(), position.column); + position.column = + (tab == _state.tabs.end()) ? ColumnOffset::cast_from(_settings.pageSize.columns - 1) : *tab - 1; + } + else + { + auto const n = min(boxed_cast(position.column) % TabWidth, + _settings.pageSize.columns - boxed_cast(position.column)); + position.column -= n; + } + auto const& line = _grid.lineAt(position.line); + while (!line.hasTabstop(position.column)) + ++position.column; + return position; +} + +template +CRISPY_REQUIRES(CellConcept) +CellLocation Screen::getTabstopEnd(CellLocation position) const noexcept +{ + if (!_state.tabs.empty()) + { + auto tab = std::upper_bound(_state.tabs.begin(), _state.tabs.end(), position.column); + position.column = + (tab == _state.tabs.end()) ? ColumnOffset::cast_from(_settings.pageSize.columns - 1) : *tab - 1; + } + else + { + auto const n = min((TabWidth - boxed_cast(position.column) % TabWidth - 1), + _settings.pageSize.columns - boxed_cast(position.column)); + position.column += n; + } + auto const& line = _grid.lineAt(position.line); + while (!line.hasTabstop(position.column)) + --position.column; + return position; +} } // namespace terminal #include diff --git a/src/vtbackend/Screen.h b/src/vtbackend/Screen.h index fc04039030..d88cabd9d2 100644 --- a/src/vtbackend/Screen.h +++ b/src/vtbackend/Screen.h @@ -72,6 +72,9 @@ class ScreenBase: public SequenceHandler [[nodiscard]] virtual Margin& margin() noexcept = 0; [[nodiscard]] virtual bool contains(CellLocation coord) const noexcept = 0; [[nodiscard]] virtual bool isCellEmpty(CellLocation position) const noexcept = 0; + [[nodiscard]] virtual bool hasTabstop(CellLocation position) const noexcept = 0; + [[nodiscard]] virtual CellLocation getTabstopStart(CellLocation position) const noexcept = 0; + [[nodiscard]] virtual CellLocation getTabstopEnd(CellLocation position) const noexcept = 0; [[nodiscard]] virtual bool compareCellTextAt(CellLocation position, char codepoint) const noexcept = 0; [[nodiscard]] virtual std::string cellTextAt(CellLocation position) const noexcept = 0; [[nodiscard]] virtual LineFlags lineFlagsAt(LineOffset line) const noexcept = 0; @@ -518,6 +521,12 @@ class Screen final: public ScreenBase, public capabilities::StaticDatabase return _grid.lineAt(position.line).cellEmptyAt(position.column); } + [[nodiscard]] bool hasTabstop(CellLocation position) const noexcept override + { + return _grid.lineAt(position.line).hasTabstop(position.column); + } + [[nodiscard]] CellLocation getTabstopStart(CellLocation position) const noexcept override; + [[nodiscard]] CellLocation getTabstopEnd(CellLocation position) const noexcept override; [[nodiscard]] bool compareCellTextAt(CellLocation position, char codepoint) const noexcept override { auto const& cell = _grid.lineAt(position.line).inflatedBuffer().at(position.column.as()); diff --git a/src/vtbackend/Selector.cpp b/src/vtbackend/Selector.cpp index 0fc31b4062..69f84409f4 100644 --- a/src/vtbackend/Selector.cpp +++ b/src/vtbackend/Selector.cpp @@ -26,7 +26,6 @@ namespace terminal namespace // {{{ helper { - tuple, CellLocation const, CellLocation const> prepare( Selection const& selection) { @@ -58,6 +57,16 @@ bool Selection::extend(CellLocation to) return true; } +bool Selection::extendStart(CellLocation from) +{ + assert(_state != State::Complete + && "In order extend a selection, the selector must be active (started)."); + _state = State::InProgress; + _from = from; + _onSelectionUpdated(); + return true; +} + void Selection::complete() { if (_state == State::InProgress) diff --git a/src/vtbackend/Selector.h b/src/vtbackend/Selector.h index c547eb360b..3e62deb7b8 100644 --- a/src/vtbackend/Selector.h +++ b/src/vtbackend/Selector.h @@ -111,6 +111,9 @@ class Selection /// Extends the selection to the given coordinate. [[nodiscard]] virtual bool extend(CellLocation to); + /// Include the given pos + [[nodiscard]] virtual bool extendStart(CellLocation from); + /// Constructs a vector of ranges for this selection. [[nodiscard]] virtual std::vector ranges() const; diff --git a/src/vtbackend/Settings.h b/src/vtbackend/Settings.h index aaa76c771a..2a1ab081e0 100644 --- a/src/vtbackend/Settings.h +++ b/src/vtbackend/Settings.h @@ -64,6 +64,7 @@ struct Settings std::chrono::milliseconds highlightTimeout = std::chrono::milliseconds { 150 }; bool highlightDoubleClickedWord = true; // TODO: ^^^ make also use of it. probably rename to how VScode has named it. + bool expandTabs = false; struct PrimaryScreen { diff --git a/src/vtbackend/Terminal.cpp b/src/vtbackend/Terminal.cpp index 74459ba2f2..d6f1d03597 100644 --- a/src/vtbackend/Terminal.cpp +++ b/src/vtbackend/Terminal.cpp @@ -37,6 +37,7 @@ #include #include +#include "vtbackend/Functions.h" #include using crispy::Size; @@ -49,7 +50,7 @@ using std::move; namespace terminal { - +// inline constexpr auto TabWidth = ColumnCount(8); namespace // {{{ helpers { constexpr size_t MaxColorPaletteSaveStackSize = 10; @@ -744,6 +745,7 @@ void Terminal::setSelector(std::unique_ptr selector) Require(selector.get() != nullptr); InputLog()("Creating cell selector: {}", *selector); _selection = std::move(selector); + _selectionStartedFrom = _selection->from(); } void Terminal::clearSelection() @@ -848,12 +850,101 @@ void Terminal::sendMouseMoveEvent(Modifier modifier, else if (selector()->state() != Selection::State::Complete && shouldExtendSelection) { if (currentScreen().isCellEmpty(relativePos) && !currentScreen().compareCellTextAt(relativePos, 0x20)) - relativePos.column = ColumnOffset { 0 } + *(_settings.pageSize.columns - 1); + { + if (currentScreen().hasTabstop(relativePos)) + { + // TODO add the behaviours where if a charecter is inserted inbetween the tab tab converted + // into space and tab + // TODO handle case where if I go into opposite dir then selection should reduce. + + auto const tabstopStart = currentScreen().getTabstopStart(relativePos); + auto const tabstopEnd = currentScreen().getTabstopEnd(relativePos); + + auto const selectionDirection = [&, this] { + if (relativePos == selector()->from()) + return SelectionDirection::None; + if (selector()->to() == selector()->from()) + { + return relativePos.line > selector()->from().line + || relativePos.column > selector()->to().column + ? SelectionDirection::Right + : SelectionDirection::Left; + } + return selector()->to().line > selector()->from().line + || selector()->to().column > selector()->from().column + ? SelectionDirection::Right + : SelectionDirection::Left; + }(); + + bool updateNeeded = false; + + bool const startingFromMidTabstop = [=, this] { + return crispy::ascending(tabstopStart, selector()->from(), tabstopEnd) + || crispy::ascending(tabstopEnd, selector()->from(), tabstopStart); + }(); + + bool const directionChanged = [&, this] { + using enum SelectionDirection; + switch (selectionDirection) + { + case Right: + return relativePos.line == selector()->from().line + && relativePos.column <= selector()->from().column; + case Left: + return relativePos.line == selector()->from().line + && relativePos.column >= selector()->from().column; + }; + return true; + }(); + + if (directionChanged) + { + selector()->extendStart( + std::max(selector()->from() - ColumnOffset(1), + CellLocation { selector()->from().line, ColumnOffset(0) })); + selector()->extend(selector()->from()); + return; + } + + if (selectionDirection == SelectionDirection::Right) + { + if (startingFromMidTabstop) + { + selector()->extendStart(tabstopStart); + } + selector()->extend(tabstopEnd); + updateNeeded = true; + } + else if (selectionDirection == SelectionDirection::Left) + { + if (startingFromMidTabstop) + { + selector()->extendStart(tabstopEnd); + } + selector()->extend(tabstopStart); + updateNeeded = true; + } + else + return; + + if (updateNeeded) + { + breakLoopAndRefreshRenderBuffer(); + } + return; + } + else + { + relativePos.column = ColumnOffset::cast_from(_settings.pageSize.columns - 1); + } + } _state.viCommands.cursorPosition = relativePos; if (_state.inputHandler.mode() != ViMode::Insert) _state.inputHandler.setMode(selector()->viMode()); if (selector()->extend(relativePos)) + { breakLoopAndRefreshRenderBuffer(); + } } } @@ -1204,12 +1295,14 @@ namespace Terminal const& term; ColumnOffset rightPage; ColumnOffset lastColumn {}; + bool wasLastCellTab = false; string text {}; string currentLine {}; void operator()(CellLocation pos, Cell const& cell) { - auto const isNewLine = pos.column < lastColumn || (pos.column == lastColumn && !text.empty()); + auto const isNewLine = + (pos.column < lastColumn && !cell.isTab()) || (pos.column == lastColumn && !text.empty()); bool const touchesRightPage = term.isSelected({ pos.line, rightPage }); if (isNewLine && (!term.isLineWrapped(pos.line) || !touchesRightPage)) { @@ -1219,10 +1312,34 @@ namespace text += '\n'; currentLine.clear(); } - if (cell.empty()) - currentLine += ' '; - else - currentLine += cell.toUtf8(); + if (cell.isTab()) + { + if (term.settings().expandTabs) + currentLine += ' '; + else if (!wasLastCellTab) + { + auto const& tabs = term.state().tabs; + if (!tabs.empty()) + { + auto itr = std::lower_bound(tabs.begin(), tabs.end(), pos.column); + if (itr != tabs.end()) + { + pos.column = *itr; + } + else + { + assert(false); + } + } + else + { + pos.column += TabWidth - boxed_cast(pos.column) % TabWidth; + } + currentLine += '\t'; + } + } + wasLastCellTab = cell.isTab(); + currentLine += cell.toUtf8(); lastColumn = pos.column; } diff --git a/src/vtbackend/Terminal.h b/src/vtbackend/Terminal.h index b003a6302f..90a7dd273d 100644 --- a/src/vtbackend/Terminal.h +++ b/src/vtbackend/Terminal.h @@ -47,6 +47,8 @@ namespace terminal { +constexpr static inline auto TabWidth = ColumnCount(8); + template CRISPY_REQUIRES(CellConcept) class Screen; @@ -838,6 +840,7 @@ class Terminal std::atomic _hoveringHyperlinkId = HyperlinkId {}; std::atomic _renderBufferUpdateEnabled = true; // for "Synchronized Updates" feature std::optional _highlightRange = std::nullopt; + std::optional _selectionStartedFrom; }; } // namespace terminal diff --git a/src/vtbackend/ViCommands.cpp b/src/vtbackend/ViCommands.cpp index 9fc1c13285..e97ac46d46 100644 --- a/src/vtbackend/ViCommands.cpp +++ b/src/vtbackend/ViCommands.cpp @@ -459,7 +459,13 @@ void ViCommands::paste(unsigned count, bool stripped) CellLocation ViCommands::prev(CellLocation location) const noexcept { if (location.column.value > 0) + { + if (_terminal.currentScreen().hasTabstop({ location.line, location.column - 1 })) + { + return _terminal.currentScreen().getTabstopStart({ location.line, location.column - 1 }); + } return { location.line, location.column - 1 }; + } auto const topLineOffset = _terminal.isPrimaryScreen() ? -boxed_cast(_terminal.primaryScreen().historyLineCount()) @@ -479,6 +485,10 @@ CellLocation ViCommands::next(CellLocation location) const noexcept auto const rightMargin = _terminal.pageSize().columns.as() - 1; if (location.column < rightMargin) { + if (_terminal.currentScreen().hasTabstop({ location.line, location.column + 1 })) + { + return _terminal.currentScreen().getTabstopEnd({ location.line, location.column + 1 }); + } auto const width = max(uint8_t { 1 }, _terminal.currentScreen().cellWidthAt(location)); return { location.line, location.column + ColumnOffset::cast_from(width) }; } diff --git a/src/vtbackend/cell/CellConcept.h b/src/vtbackend/cell/CellConcept.h index cdd8efe1af..b06a4ce312 100644 --- a/src/vtbackend/cell/CellConcept.h +++ b/src/vtbackend/cell/CellConcept.h @@ -99,6 +99,8 @@ concept CellConcept = requires(T t, T const& u) { u.hyperlink() } -> std::same_as; t.setHyperlink(HyperlinkId{}); + t.setTab(bool{}); + { u.isTab() } noexcept -> std::same_as; }; diff --git a/src/vtbackend/cell/CompactCell.h b/src/vtbackend/cell/CompactCell.h index 9010bea40b..8b523ae45f 100644 --- a/src/vtbackend/cell/CompactCell.h +++ b/src/vtbackend/cell/CompactCell.h @@ -126,6 +126,7 @@ class CRISPY_PACKED CompactCell void setForegroundColor(Color color) noexcept; [[nodiscard]] Color backgroundColor() const noexcept; void setBackgroundColor(Color color) noexcept; + void setTab(bool tab) noexcept; [[nodiscard]] std::shared_ptr imageFragment() const noexcept; void setImageFragment(std::shared_ptr rasterizedImage, CellLocation offset); @@ -138,6 +139,7 @@ class CRISPY_PACKED CompactCell void setHyperlink(HyperlinkId hyperlink); [[nodiscard]] bool empty() const noexcept; + [[nodiscard]] bool isTab() const noexcept; void setGraphicsRendition(GraphicsRendition sgr) noexcept; @@ -407,6 +409,14 @@ inline void CompactCell::setBackgroundColor(Color color) noexcept _backgroundColor = color; } +inline void CompactCell::setTab(bool tab) noexcept +{ + if (tab) + extra().flags |= CellFlags::Tab; + else + extra().flags &= ~CellFlags::Tab; +} + inline Color CompactCell::underlineColor() const noexcept { if (!_extra) @@ -459,6 +469,10 @@ inline bool CompactCell::empty() const noexcept return CellUtil::empty(*this); } +inline bool CompactCell::isTab() const noexcept +{ + return _extra && _extra->flags & CellFlags::Tab; +} inline void CompactCell::setGraphicsRendition(GraphicsRendition sgr) noexcept { CellUtil::applyGraphicsRendition(sgr, *this); diff --git a/src/vtbackend/cell/SimpleCell.h b/src/vtbackend/cell/SimpleCell.h index 5ae2e0c2b5..bdbde0c7bc 100644 --- a/src/vtbackend/cell/SimpleCell.h +++ b/src/vtbackend/cell/SimpleCell.h @@ -73,6 +73,7 @@ class SimpleCell void setForegroundColor(Color color) noexcept; void setBackgroundColor(Color color) noexcept; void setUnderlineColor(Color color) noexcept; + void setTab(bool tab) noexcept; [[nodiscard]] Color foregroundColor() const noexcept; [[nodiscard]] Color backgroundColor() const noexcept; [[nodiscard]] Color underlineColor() const noexcept; @@ -84,6 +85,7 @@ class SimpleCell void setHyperlink(HyperlinkId hyperlink) noexcept; [[nodiscard]] bool empty() const noexcept { return CellUtil::empty(*this); } + [[nodiscard]] bool isTab() const noexcept { return _flags & CellFlags::Tab; } private: std::u32string _codepoints {}; @@ -123,7 +125,6 @@ inline void SimpleCell::write(GraphicsAttributes sgr, char32_t codepoint, uint8_ _graphicsAttributes = sgr; _codepoints.clear(); _codepoints.push_back(codepoint); - _width = width; } @@ -238,6 +239,14 @@ inline void SimpleCell::setUnderlineColor(Color color) noexcept _graphicsAttributes.underlineColor = color; } +inline void SimpleCell::setTab(bool tab) noexcept +{ + if (tab) + _flags |= CellFlags::Tab; + else + _flags &= ~CellFlags::Tab; +} + inline Color SimpleCell::foregroundColor() const noexcept { return _graphicsAttributes.foregroundColor; diff --git a/src/vtbackend/primitives.h b/src/vtbackend/primitives.h index cbc9e45876..4749314459 100644 --- a/src/vtbackend/primitives.h +++ b/src/vtbackend/primitives.h @@ -735,6 +735,13 @@ enum class ViMode VisualBlock, // }; +enum class SelectionDirection +{ + Left, + Right, + None +}; + std::string to_string(GraphicsRendition s); constexpr unsigned toAnsiModeNum(AnsiMode m) @@ -1039,5 +1046,26 @@ struct formatter } }; +template <> +struct formatter +{ + template + constexpr auto parse(ParseContext& ctx) + { + return ctx.begin(); + } + template + auto format(terminal::SelectionDirection value, FormatContext& ctx) + { + using enum terminal::SelectionDirection; + switch (value) + { + case None: return fmt::format_to(ctx.out(), "None"); + case Right: return fmt::format_to(ctx.out(), "Right"); + case Left: return fmt::format_to(ctx.out(), "Left"); + } + return fmt::format_to(ctx.out(), "{}", static_cast(value)); + } +}; } // namespace fmt // }}}