From f72b9253ab03a3672b3ff096ebda11246c4f99c9 Mon Sep 17 00:00:00 2001 From: Yaraslau Tamashevich Date: Sat, 26 Oct 2024 21:40:54 +0300 Subject: [PATCH] Implement vi mode jumps --- metainfo.xml | 1 + src/vtbackend/CMakeLists.txt | 2 ++ src/vtbackend/JumpHistory.cpp | 56 ++++++++++++++++++++++++++++++ src/vtbackend/JumpHistory.h | 42 +++++++++++++++++++++++ src/vtbackend/Screen.cpp | 3 +- src/vtbackend/Terminal.h | 4 +++ src/vtbackend/ViCommands.cpp | 58 +++++++++++++++++++++----------- src/vtbackend/ViCommands.h | 10 ++++-- src/vtbackend/ViInputHandler.cpp | 6 +++- src/vtbackend/ViInputHandler.h | 6 ++++ 10 files changed, 163 insertions(+), 25 deletions(-) create mode 100644 src/vtbackend/JumpHistory.cpp create mode 100644 src/vtbackend/JumpHistory.h diff --git a/metainfo.xml b/metainfo.xml index 8d111be6e7..aa452ac2e3 100644 --- a/metainfo.xml +++ b/metainfo.xml @@ -113,6 +113,7 @@
  • Protect user from accidentally pasting too large input (#1198)
  • Add binding to exit normal mode with `Esc` (#1604)
  • Add config option to switch into insert mode after yank (#1604)
  • +
  • Add vi-like normal mode Jumps. `''`, `C-O` and `C-I` motions (#1101)
  • Improves window size/resize handling on HiDPI monitor settings (#1628)
  • Improves macOS key handling for Option+Left|Right keys to jump between words in the shell
  • Fixes cropping of underscore character for some fonts (#1603)
  • diff --git a/src/vtbackend/CMakeLists.txt b/src/vtbackend/CMakeLists.txt index 8899089fdb..d010cf0234 100644 --- a/src/vtbackend/CMakeLists.txt +++ b/src/vtbackend/CMakeLists.txt @@ -45,6 +45,7 @@ set(vtbackend_HEADERS Viewport.h ViInputHandler.h ViCommands.h + JumpHistory.h primitives.h ) @@ -75,6 +76,7 @@ set(vtbackend_SOURCES Viewport.cpp ViInputHandler.cpp ViCommands.cpp + JumpHistory.cpp primitives.cpp ) diff --git a/src/vtbackend/JumpHistory.cpp b/src/vtbackend/JumpHistory.cpp new file mode 100644 index 0000000000..b6fbdc3a25 --- /dev/null +++ b/src/vtbackend/JumpHistory.cpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +namespace vtbackend +{ + +CellLocation JumpHistory::jumpToLast(CellLocation current) +{ + applyOffset(); + CellLocation last = _history.back(); + if (last == current) + { + _history.pop_back(); + if (_history.empty()) + { + return current; + } + last = _history.back(); + } + _history.pop_back(); + _history.push_back(current); + _current = _history.size(); + return last; +} + +CellLocation JumpHistory::jumpToMarkBackward([[maybe_unused]] CellLocation current) +{ + applyOffset(); + if (_current == 0) + { + // loop + _current = _history.size() - 1; + } + else + { + _current--; + } + return _history[_current]; +} + +CellLocation JumpHistory::jumpToMarkForward([[maybe_unused]] CellLocation current) +{ + applyOffset(); + if (_current == _history.size()) + { + // loop + _current = 0; + } + else + { + _current++; + } + return _history[_current]; +} + +} // namespace vtbackend diff --git a/src/vtbackend/JumpHistory.h b/src/vtbackend/JumpHistory.h new file mode 100644 index 0000000000..69a765234a --- /dev/null +++ b/src/vtbackend/JumpHistory.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#include + +namespace vtbackend +{ + +class JumpHistory +{ + + public: + template + void add(T&& cell) + { + applyOffset(); + _history.push_back(std::forward(cell)); + } + CellLocation jumpToLast(CellLocation current); + CellLocation jumpToMarkBackward(CellLocation current); + CellLocation jumpToMarkForward(CellLocation current); + void addOffset(LineOffset offset) { _offsetSinceLastJump += offset; } + + private: + std::vector _history; + size_t _current = 0; + LineOffset _offsetSinceLastJump { 0 }; + void applyOffset() + { + if (unbox(_offsetSinceLastJump) == 0) + return; + for (auto& cell: _history) + { + // minus since we are going in the history + cell.line -= _offsetSinceLastJump; + } + _offsetSinceLastJump = LineOffset { 0 }; + } +}; +} // namespace vtbackend diff --git a/src/vtbackend/Screen.cpp b/src/vtbackend/Screen.cpp index d085dbaa47..c5686417c1 100644 --- a/src/vtbackend/Screen.cpp +++ b/src/vtbackend/Screen.cpp @@ -727,7 +727,8 @@ void Screen::linefeed(ColumnOffset newColumn) { _cursor.wrapPending = false; _cursor.position.column = newColumn; - + if (unbox(historyLineCount()) > 0) + _terminal->addLineOffsetToJumpHistory(LineOffset { 1 }); if (*realCursorPosition().line == *margin().vertical.to) { // TODO(perf) if we know that we text is following this LF diff --git a/src/vtbackend/Terminal.h b/src/vtbackend/Terminal.h index dd2125a520..c2676b7b56 100644 --- a/src/vtbackend/Terminal.h +++ b/src/vtbackend/Terminal.h @@ -626,6 +626,10 @@ class Terminal return _viCommands.cursorPosition; } void moveNormalModeCursorTo(CellLocation pos) noexcept { _viCommands.moveCursorTo(pos); } + void addLineOffsetToJumpHistory(LineOffset offset) noexcept + { + _viCommands.addLineOffsetToJumpHistory(offset); + } // {{{ cursor management CursorDisplay cursorDisplay() const noexcept { return _settings.cursorDisplay; } diff --git a/src/vtbackend/ViCommands.cpp b/src/vtbackend/ViCommands.cpp index 25a169d517..b0c034a624 100644 --- a/src/vtbackend/ViCommands.cpp +++ b/src/vtbackend/ViCommands.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 #include #include +#include #include #include @@ -195,7 +196,11 @@ bool ViCommands::jumpToNextMatch(unsigned count) { for (unsigned i = 0; i < count; ++i) if (auto const nextPosition = _terminal->searchNextMatch(cursorPosition)) + { + inputLog()("jumpToNextMatch"); + _jumpHistory.add(nextPosition.value()); moveCursorTo(nextPosition.value()); + } else return false; @@ -206,7 +211,11 @@ bool ViCommands::jumpToPreviousMatch(unsigned count) { for (unsigned i = 0; i < count; ++i) if (auto const nextPosition = _terminal->searchPrevMatch(cursorPosition)) + { + inputLog()("jumpToPreviousMatch"); + _jumpHistory.add(nextPosition.value()); moveCursorTo(nextPosition.value()); + } else return false; @@ -659,7 +668,7 @@ CellLocationRange ViCommands::translateToCellRange(TextObjectScope scope, return { a, b }; } -CellLocationRange ViCommands::translateToCellRange(ViMotion motion, unsigned count) const noexcept +CellLocationRange ViCommands::translateToCellRange(ViMotion motion, unsigned count) noexcept { switch (motion) { @@ -668,7 +677,7 @@ CellLocationRange ViCommands::translateToCellRange(ViMotion motion, unsigned cou { cursorPosition.line, _terminal->pageSize().columns.as() - 1 } }; default: //. - return { cursorPosition, translateToCellLocation(motion, count) }; + return { cursorPosition, translateToCellLocationAndRecord(motion, count) }; } } @@ -773,8 +782,13 @@ CellLocation ViCommands::globalCharDown(CellLocation location, char ch, unsigned return result; } -CellLocation ViCommands::translateToCellLocation(ViMotion motion, unsigned count) const noexcept +CellLocation ViCommands::translateToCellLocationAndRecord(ViMotion motion, unsigned count) noexcept { + auto addJumpHistory = [this](CellLocation const& location) { + inputLog()("addJumpHistory: {}:{}", location.line, location.column); + _jumpHistory.add(location); + return location; + }; switch (motion) { case ViMotion::CharLeft: // h @@ -806,7 +820,8 @@ CellLocation ViCommands::translateToCellLocation(ViMotion motion, unsigned count { LineOffset::cast_from(-_terminal->currentScreen().historyLineCount().as()), ColumnOffset(0) }); case ViMotion::FileEnd: // G - return snapToCell({ _terminal->pageSize().lines.as() - 1, ColumnOffset(0) }); + return addJumpHistory( + snapToCell({ _terminal->pageSize().lines.as() - 1, ColumnOffset(0) })); case ViMotion::PageTop: // return snapToCell({ boxed_cast(-_terminal->viewport().scrollOffset()) + *_terminal->viewport().scrollOff(), @@ -837,9 +852,9 @@ CellLocation ViCommands::translateToCellLocation(ViMotion motion, unsigned count -_terminal->currentScreen().historyLineCount().as()), cursorPosition.column }; case ViMotion::LinesCenter: // M - return { LineOffset::cast_from(_terminal->pageSize().lines / 2 - 1) - - boxed_cast(_terminal->viewport().scrollOffset()), - cursorPosition.column }; + return addJumpHistory({ LineOffset::cast_from(_terminal->pageSize().lines / 2 - 1) + - boxed_cast(_terminal->viewport().scrollOffset()), + cursorPosition.column }); case ViMotion::PageDown: return { min(cursorPosition.line + LineOffset::cast_from(_terminal->pageSize().lines / 2), _terminal->pageSize().lines.as() - 1), @@ -864,16 +879,16 @@ CellLocation ViCommands::translateToCellLocation(ViMotion motion, unsigned count prev.line = current.line; current.line--; } - return snapToCell(current); + return addJumpHistory(snapToCell(current)); } case ViMotion::GlobalCurlyOpenUp: // [[ - return globalCharUp(cursorPosition, '{', count); + return addJumpHistory(globalCharUp(cursorPosition, '{', count)); case ViMotion::GlobalCurlyOpenDown: // ]] - return globalCharDown(cursorPosition, '{', count); + return addJumpHistory(globalCharDown(cursorPosition, '{', count)); case ViMotion::GlobalCurlyCloseUp: // [] - return globalCharUp(cursorPosition, '}', count); + return addJumpHistory(globalCharUp(cursorPosition, '}', count)); case ViMotion::GlobalCurlyCloseDown: // ][ - return globalCharDown(cursorPosition, '}', count); + return addJumpHistory(globalCharDown(cursorPosition, '}', count)); case ViMotion::LineMarkUp: // [m { auto const gridTop = -_terminal->currentScreen().historyLineCount().as(); @@ -888,7 +903,7 @@ CellLocation ViCommands::translateToCellLocation(ViMotion motion, unsigned count --result.line; --count; } - return result; + return addJumpHistory(result); } case ViMotion::LineMarkDown: // ]m { @@ -906,7 +921,7 @@ CellLocation ViCommands::translateToCellLocation(ViMotion motion, unsigned count } --count; } - return result; + return addJumpHistory(result); } case ViMotion::ParagraphForward: // } { @@ -922,7 +937,7 @@ CellLocation ViCommands::translateToCellLocation(ViMotion motion, unsigned count prev.line = current.line; current.line++; } - return snapToCell(current); + return addJumpHistory(snapToCell(current)); } case ViMotion::ParenthesisMatching: // % TODO return findMatchingPairFrom(cursorPosition); @@ -938,7 +953,7 @@ CellLocation ViCommands::translateToCellLocation(ViMotion motion, unsigned count startPosition = *nextPosition; } - return startPosition; + return addJumpHistory(startPosition); } case ViMotion::SearchResultForward: // n { @@ -951,7 +966,7 @@ CellLocation ViCommands::translateToCellLocation(ViMotion motion, unsigned count return cursorPosition; startPosition = *nextPosition; } - return startPosition; + return addJumpHistory(startPosition); } case ViMotion::WordBackward: // b { @@ -1059,12 +1074,15 @@ CellLocation ViCommands::translateToCellLocation(ViMotion motion, unsigned count return toCharLeft(count).value_or(cursorPosition); case ViMotion::RepeatCharMove: if (isValidCharMove(_lastCharMotion)) - return translateToCellLocation(*_lastCharMotion, count); + return translateToCellLocationAndRecord(*_lastCharMotion, count); return cursorPosition; case ViMotion::RepeatCharMoveReverse: if (isValidCharMove(_lastCharMotion)) - return translateToCellLocation(invertCharMove(*_lastCharMotion), count); + return translateToCellLocationAndRecord(invertCharMove(*_lastCharMotion), count); return cursorPosition; + case ViMotion::JumpToLastJumpPoint: return _jumpHistory.jumpToLast(cursorPosition); + case ViMotion::JumpToMarkBackward: return _jumpHistory.jumpToMarkBackward(cursorPosition); + case ViMotion::JumpToMarkForward: return _jumpHistory.jumpToMarkForward(cursorPosition); } crispy::unreachable(); } @@ -1133,7 +1151,7 @@ void ViCommands::moveCursor(ViMotion motion, unsigned count, char32_t lastChar) _lastChar = lastChar; } - auto const nextPosition = translateToCellLocation(motion, count); + auto const nextPosition = translateToCellLocationAndRecord(motion, count); inputLog()("Move cursor: {} to {}\n", motion, nextPosition); moveCursorTo(nextPosition); } diff --git a/src/vtbackend/ViCommands.h b/src/vtbackend/ViCommands.h index 28d5f74d93..92ff55d31c 100644 --- a/src/vtbackend/ViCommands.h +++ b/src/vtbackend/ViCommands.h @@ -1,10 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 #pragma once +#include #include +#include #include +#include #include namespace vtbackend @@ -48,8 +51,8 @@ class ViCommands: public ViInputHandler::Executor void moveCursorTo(CellLocation position); - [[nodiscard]] CellLocation translateToCellLocation(ViMotion motion, unsigned count) const noexcept; - [[nodiscard]] CellLocationRange translateToCellRange(ViMotion motion, unsigned count) const noexcept; + [[nodiscard]] CellLocation translateToCellLocationAndRecord(ViMotion motion, unsigned count) noexcept; + [[nodiscard]] CellLocationRange translateToCellRange(ViMotion motion, unsigned count) noexcept; [[nodiscard]] CellLocationRange translateToCellRange(TextObjectScope scope, TextObject textObject) const noexcept; [[nodiscard]] CellLocation prev(CellLocation location) const noexcept; @@ -88,7 +91,7 @@ class ViCommands: public ViInputHandler::Executor [[nodiscard]] CellLocation snapToCellRight(CellLocation location) const noexcept; [[nodiscard]] bool compareCellTextAt(CellLocation position, char32_t codepoint) const noexcept; - + void addLineOffsetToJumpHistory(LineOffset offset) { _jumpHistory.addOffset(offset); } // Cursor offset into the grid. CellLocation cursorPosition {}; @@ -99,6 +102,7 @@ class ViCommands: public ViInputHandler::Executor mutable char32_t _lastChar = U'\0'; std::optional _lastCharMotion = std::nullopt; bool _lastCursorVisible = true; + JumpHistory _jumpHistory; }; } // namespace vtbackend diff --git a/src/vtbackend/ViInputHandler.cpp b/src/vtbackend/ViInputHandler.cpp index 30b017a5ba..d06322f686 100644 --- a/src/vtbackend/ViInputHandler.cpp +++ b/src/vtbackend/ViInputHandler.cpp @@ -69,7 +69,7 @@ void ViInputHandler::registerAllCommands() std::array, 2> { { std::pair { 'i', TextObjectScope::Inner }, std::pair { 'a', TextObjectScope::A } } }; - auto constexpr MotionMappings = std::array, 43> { { + auto constexpr MotionMappings = std::array, 47> { { // clang-format off { "$", ViMotion::LineEnd }, { "%", ViMotion::ParenthesisMatching }, @@ -114,6 +114,10 @@ void ViInputHandler::registerAllCommands() { "{", ViMotion::ParagraphBackward }, { "|", ViMotion::ScreenColumn }, { "}", ViMotion::ParagraphForward }, + { "''",ViMotion::JumpToLastJumpPoint }, + { "``",ViMotion::JumpToLastJumpPoint }, + { "C-O",ViMotion::JumpToMarkBackward }, + { "C-I",ViMotion::JumpToMarkForward }, // clang-format on } }; diff --git a/src/vtbackend/ViInputHandler.h b/src/vtbackend/ViInputHandler.h index 4968889117..096f40436d 100644 --- a/src/vtbackend/ViInputHandler.h +++ b/src/vtbackend/ViInputHandler.h @@ -100,6 +100,9 @@ enum class ViMotion : uint8_t ToCharLeft, // F {char} RepeatCharMove, // ; RepeatCharMoveReverse, // , + JumpToLastJumpPoint, // '' or `` (jump to last jump) + JumpToMarkBackward, // + JumpToMarkForward, // }; enum class ViOperator : uint8_t @@ -370,6 +373,9 @@ struct std::formatter: formatter case ViMotion::ToCharLeft: name = "ToCharLeft"; break; case ViMotion::RepeatCharMove: name = "RepeatCharMove"; break; case ViMotion::RepeatCharMoveReverse: name = "RepeatCharMoveReverse"; break; + case ViMotion::JumpToLastJumpPoint: name = "JumpToLastJumpPoint"; break; + case ViMotion::JumpToMarkBackward: name = "JumpToMarkUp"; break; + case ViMotion::JumpToMarkForward: name = "JumpToMarkDown"; break; case ViMotion::GlobalCurlyCloseUp: name = "GlobalCurlyCloseUp"; break; case ViMotion::GlobalCurlyCloseDown: name = "GlobalCurlyCloseDown"; break; case ViMotion::GlobalCurlyOpenUp: name = "GlobalCurlyOpenUp"; break;