From 71bcfe30191ba9824621211335f44386a2fa2fc7 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Wed, 5 Jul 2017 19:42:40 +0800 Subject: [PATCH] vim-mode - `Ctrl+Tab` and `Ctrl+Shift+Tab` in Normal mode to alternate tabs; - Support `:` command for line jump; - Support `%`; --- src/utils/veditutils.cpp | 44 +++++++++ src/utils/veditutils.h | 15 ++- src/utils/vvim.cpp | 197 ++++++++++++++++++++++++++++++--------- src/utils/vvim.h | 12 ++- 4 files changed, 217 insertions(+), 51 deletions(-) diff --git a/src/utils/veditutils.cpp b/src/utils/veditutils.cpp index dc0b3016..bb9eb3a9 100644 --- a/src/utils/veditutils.cpp +++ b/src/utils/veditutils.cpp @@ -254,6 +254,50 @@ bool VEditUtils::findTargetWithinBlock(QTextCursor &p_cursor, return true; } +int VEditUtils::findTargetsWithinBlock(QTextCursor &p_cursor, + QTextCursor::MoveMode p_mode, + const QList &p_targets, + bool p_forward, + bool p_inclusive) +{ + if (p_targets.isEmpty()) { + return -1; + } + + int targetIdx = -1; + QTextBlock block = p_cursor.block(); + QString text = block.text(); + int pib = p_cursor.positionInBlock(); + int delta = p_forward ? 1 : -1; + + // The index to start searching. + int idx = pib + (p_inclusive ? delta : 2 * delta); + + for (; idx < text.size() && idx >= 0; idx += delta) { + int index = p_targets.indexOf(text[idx]); + if (index != -1) { + targetIdx = index; + break; + } + } + + if (idx < 0 || idx >= text.size()) { + return -1; + } + + // text[idx] is the target character. + if ((p_forward && p_inclusive && p_mode == QTextCursor::KeepAnchor) + || (!p_forward && !p_inclusive)) { + ++idx; + } else if (p_forward && !p_inclusive && p_mode == QTextCursor::MoveAnchor) { + --idx; + } + + p_cursor.setPosition(block.position() + idx, p_mode); + return targetIdx; +} + + int VEditUtils::selectedBlockCount(const QTextCursor &p_cursor) { if (!p_cursor.hasSelection()) { diff --git a/src/utils/veditutils.h b/src/utils/veditutils.h index c5fef6ba..8dafd711 100644 --- a/src/utils/veditutils.h +++ b/src/utils/veditutils.h @@ -65,8 +65,10 @@ public: static void unindentBlock(QTextCursor &p_cursor, const QString &p_indentationText); - // Find @p_repeat th occurence a char within a block. + // Find @p_repeat th occurence of a char within a block. // Returns true if target is found. + // Please pay attention to the one-step-forward/backward in KeepAnchor mode + // and exclusive case. static bool findTargetWithinBlock(QTextCursor &p_cursor, QTextCursor::MoveMode p_mode, QChar p_target, @@ -74,6 +76,17 @@ public: bool p_inclusive, int p_repeat); + // Find th first occurence of a char in @p_targets within a block. + // Returns the index of the found char in @p_targets if found. + // Returns -1 if none of the @p_targets is found. + // Please pay attention to the one-step-forward/backward in KeepAnchor mode + // and exclusive case. + static int findTargetsWithinBlock(QTextCursor &p_cursor, + QTextCursor::MoveMode p_mode, + const QList &p_targets, + bool p_forward, + bool p_inclusive); + // Find a pair target (@p_opening, @p_closing) containing current cursor and // select the range between them. // Need to call setTextCursor() to make it take effect. diff --git a/src/utils/vvim.cpp b/src/utils/vvim.cpp index 80d090c8..1fdfa297 100644 --- a/src/utils/vvim.cpp +++ b/src/utils/vvim.cpp @@ -485,6 +485,13 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos) goto accept; } + // Ctrl+Tab and Ctrl+Shift+BackTab to alternate tabs. + if ((key == Qt::Key_Tab && modifiers == Qt::ControlModifier) + || (key == Qt::Key_Backtab && modifiers == (Qt::ShiftModifier | Qt::ControlModifier))) { + // Let it be handled outside VVim. + goto exit; + } + if (m_replayLeaderSequence) { qDebug() << "replaying sequence" << keyToChar(key, modifiers); } @@ -1794,23 +1801,31 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos) case Qt::Key_Percent: { if (modifiers == Qt::ShiftModifier) { - tryGetRepeatToken(m_keys, m_tokens); - if (m_keys.isEmpty() && hasRepeatToken()) { - // xx%, jump to a certain line (percentage of the documents). - // Change the repeat from percentage to line number. - Token *token = getRepeatToken(); - int bn = percentageToBlockNumber(m_editor->document(), token->m_repeat); - if (bn == -1) { - break; - } else { - // Repeat of LineJump is based on 1. - token->m_repeat = bn + 1; - } - + if (m_keys.isEmpty()) { + // %, FindPair movement. tryAddMoveAction(); - addMovementToken(Movement::LineJump); + addMovementToken(Movement::FindPair); processCommand(m_tokens); break; + } else { + tryGetRepeatToken(m_keys, m_tokens); + if (m_keys.isEmpty() && hasRepeatToken()) { + // xx%, jump to a certain line (percentage of the documents). + // Change the repeat from percentage to line number. + Token *token = getRepeatToken(); + int bn = percentageToBlockNumber(m_editor->document(), token->m_repeat); + if (bn == -1) { + break; + } else { + // Repeat of LineJump is based on 1. + token->m_repeat = bn + 1; + } + + tryAddMoveAction(); + addMovementToken(Movement::LineJump); + processCommand(m_tokens); + break; + } } break; @@ -2199,8 +2214,7 @@ void VVim::processMoveAction(QList &p_tokens) || m_mode == VimMode::VisualLine) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor; - bool hasMoved = processMovement(cursor, m_editor->document(), - moveMode, mvToken, repeat); + bool hasMoved = processMovement(cursor, moveMode, mvToken, repeat); if (hasMoved) { // Maintain positionInBlock. @@ -2231,15 +2245,17 @@ void VVim::processMoveAction(QList &p_tokens) } } -bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc, +bool VVim::processMovement(QTextCursor &p_cursor, QTextCursor::MoveMode p_moveMode, - const Token &p_token, int p_repeat) + const Token &p_token, + int p_repeat) { V_ASSERT(p_token.isMovement()); bool hasMoved = false; bool inclusive = true; bool forward = true; + QTextDocument *doc = p_cursor.document(); switch (p_token.m_movement) { case Movement::Left: @@ -2301,7 +2317,7 @@ bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc, p_repeat = 1; } - int blockCount = p_doc->blockCount(); + int blockCount = doc->blockCount(); p_repeat = qMin(blockCount - 1 - p_cursor.block().blockNumber(), p_repeat); if (p_repeat > 0) { @@ -2343,7 +2359,7 @@ bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc, int blockStep = blockCountOfPageStep() * p_repeat; int block = p_cursor.block().blockNumber(); block = qMax(0, block - blockStep); - p_cursor.setPosition(p_doc->findBlockByNumber(block).position(), p_moveMode); + p_cursor.setPosition(doc->findBlockByNumber(block).position(), p_moveMode); hasMoved = true; break; @@ -2357,8 +2373,8 @@ bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc, int blockStep = blockCountOfPageStep() * p_repeat; int block = p_cursor.block().blockNumber(); - block = qMin(block + blockStep, p_doc->blockCount() - 1); - p_cursor.setPosition(p_doc->findBlockByNumber(block).position(), p_moveMode); + block = qMin(block + blockStep, doc->blockCount() - 1); + p_cursor.setPosition(doc->findBlockByNumber(block).position(), p_moveMode); hasMoved = true; break; @@ -2375,7 +2391,7 @@ bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc, blockStep = p_repeat * halfBlockStep; int block = p_cursor.block().blockNumber(); block = qMax(0, block - blockStep); - p_cursor.setPosition(p_doc->findBlockByNumber(block).position(), p_moveMode); + p_cursor.setPosition(doc->findBlockByNumber(block).position(), p_moveMode); hasMoved = true; break; @@ -2391,8 +2407,8 @@ bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc, int halfBlockStep = qMax(blockStep / 2, 1); blockStep = p_repeat * halfBlockStep; int block = p_cursor.block().blockNumber(); - block = qMin(block + blockStep, p_doc->blockCount() - 1); - p_cursor.setPosition(p_doc->findBlockByNumber(block).position(), p_moveMode); + block = qMin(block + blockStep, doc->blockCount() - 1); + p_cursor.setPosition(doc->findBlockByNumber(block).position(), p_moveMode); hasMoved = true; break; @@ -2446,7 +2462,7 @@ bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc, m_locations.addLocation(p_cursor); // @p_repeat starts from 1 while block number starts from 0. - QTextBlock block = p_doc->findBlockByNumber(p_repeat - 1); + QTextBlock block = doc->findBlockByNumber(p_repeat - 1); if (block.isValid()) { p_cursor.setPosition(block.position(), p_moveMode); } else { @@ -2693,7 +2709,7 @@ handle_target: QChar target = keyToChar(key.m_key, key.m_modifiers); Location loc = m_marks.getMarkLocation(target); if (loc.isValid()) { - if (loc.m_blockNumber >= p_doc->blockCount()) { + if (loc.m_blockNumber >= doc->blockCount()) { // Invalid block number. message(tr("Mark not set")); m_marks.clearMark(target); @@ -2703,7 +2719,7 @@ handle_target: // Different from Vim: // We just use the block number for mark, so if we delete the line // where the mark locates, we could not detect if it is set or not. - QTextBlock block = p_doc->findBlockByNumber(loc.m_blockNumber); + QTextBlock block = doc->findBlockByNumber(loc.m_blockNumber); p_cursor.setPosition(block.position(), p_moveMode); if (p_token.m_movement == Movement::MarkJump) { setCursorPositionInBlock(p_cursor, loc.m_positionInBlock, p_moveMode); @@ -2718,6 +2734,80 @@ handle_target: break; } + case Movement::FindPair: + { + Q_ASSERT(p_repeat == -1); + int anchor = p_cursor.anchor(); + int position = p_cursor.position(); + QList> pairs; + pairs.append(QPair('(', ')')); + pairs.append(QPair('[', ']')); + pairs.append(QPair('{', '}')); + + // Find forward for a pair (), [], and {}. + QList targets; + for (auto const & pair : pairs) { + targets.append(pair.first); + targets.append(pair.second); + } + + // First check if current char hits the targets. + QChar ch = doc->characterAt(position); + int idx = targets.indexOf(ch); + if (idx == -1) { + // Use MoveAnchor to avoid the one-step-forward. + idx = VEditUtils::findTargetsWithinBlock(p_cursor, + QTextCursor::MoveAnchor, + targets, + true, + true); + } + + if (idx == -1) { + break; + } + + idx /= 2; + int pairPosition = p_cursor.position(); + bool ret = VEditUtils::selectPairTargetAround(p_cursor, + pairs.at(idx).first, + pairs.at(idx).second, + true, + true, + 1); + + if (ret) { + // Found matched pair. + int first = p_cursor.position(); + int second = p_cursor.anchor(); + if (first > second) { + int tmp = first; + first = second; + second = tmp; + } + + --second; + + int target = first; + if (first == pairPosition) { + target = second; + } + + p_cursor.setPosition(anchor); + p_cursor.setPosition(target, p_moveMode); + hasMoved = true; + break; + } else { + // Restore the cursor position. + p_cursor.setPosition(anchor); + if (anchor != position) { + p_cursor.setPosition(position, QTextCursor::KeepAnchor); + } + } + + break; + } + default: break; } @@ -3133,7 +3223,7 @@ void VVim::processDeleteAction(QList &p_tokens) } cursor.beginEditBlock(); - hasMoved = processMovement(cursor, doc, moveMode, to, repeat); + hasMoved = processMovement(cursor, moveMode, to, repeat); if (repeat == -1) { repeat = 1; } @@ -3345,7 +3435,7 @@ void VVim::processCopyAction(QList &p_tokens) } cursor.beginEditBlock(); - changed = processMovement(cursor, doc, moveMode, to, repeat); + changed = processMovement(cursor, moveMode, to, repeat); if (repeat == -1) { repeat = 1; } @@ -3591,7 +3681,7 @@ void VVim::processChangeAction(QList &p_tokens) } cursor.beginEditBlock(); - hasMoved = processMovement(cursor, doc, moveMode, to, repeat); + hasMoved = processMovement(cursor, moveMode, to, repeat); if (repeat == -1) { repeat = 1; } @@ -3885,7 +3975,6 @@ void VVim::processIndentAction(QList &p_tokens, bool p_isIndent) } processMovement(cursor, - doc, QTextCursor::KeepAnchor, to, repeat); @@ -3959,7 +4048,6 @@ void VVim::processToLowerAction(QList &p_tokens, bool p_toLower) cursor.beginEditBlock(); changed = processMovement(cursor, - doc, moveMode, to, repeat); @@ -4667,8 +4755,10 @@ void VVim::repeatLastFindMovement(bool p_reverse) void VVim::message(const QString &p_msg) { - qDebug() << "vim msg:" << p_msg; - emit vimMessage(p_msg); + if (!p_msg.isEmpty()) { + qDebug() << "vim msg:" << p_msg; + emit vimMessage(p_msg); + } } const QMap &VVim::getRegisters() const @@ -4713,7 +4803,7 @@ bool VVim::processCommandLine(const Key &p_key) if (p_key == Key(Qt::Key_Return) || p_key == Key(Qt::Key_Enter, Qt::KeypadModifier)) { // Enter, try to execute the command and exit cmd line mode. - executeCommand(m_keys); + executeCommand(); m_cmdMode = false; return true; } @@ -4771,15 +4861,15 @@ bool VVim::processCommandLine(const Key &p_key) return false; } -void VVim::executeCommand(const QList &p_keys) +void VVim::executeCommand() { bool validCommand = true; QString msg; - if (p_keys.isEmpty()) { + if (m_keys.isEmpty()) { return; - } if (p_keys.size() == 1) { - const Key &key0 = p_keys.first(); + } if (m_keys.size() == 1) { + const Key &key0 = m_keys.first(); if (key0 == Key(Qt::Key_W)) { // :w, save current file. emit m_editor->saveNote(); @@ -4795,9 +4885,9 @@ void VVim::executeCommand(const QList &p_keys) } else { validCommand = false; } - } else if (p_keys.size() == 2) { - const Key &key0 = p_keys.first(); - const Key &key1 = p_keys.at(1); + } else if (m_keys.size() == 2) { + const Key &key0 = m_keys.first(); + const Key &key1 = m_keys.at(1); if (key0 == Key(Qt::Key_W) && key1 == Key(Qt::Key_Q)) { // :wq, save change and quit edit mode. // We treat it same as :x. @@ -4814,9 +4904,19 @@ void VVim::executeCommand(const QList &p_keys) validCommand = false; } + if (!validCommand && !hasNonDigitPendingKeys() && m_tokens.isEmpty()) { + // All digits. + // Jump to a specific line. + tryGetRepeatToken(m_keys, m_tokens); + tryAddMoveAction(); + addMovementToken(Movement::LineJump); + processCommand(m_tokens); + validCommand = true; + } + if (!validCommand) { QString str; - for (auto const & key : p_keys) { + for (auto const & key : m_keys) { str.append(keyToChar(key.m_key, key.m_modifiers)); } @@ -4826,9 +4926,9 @@ void VVim::executeCommand(const QList &p_keys) } } -bool VVim::hasNonDigitPendingKeys() +bool VVim::hasNonDigitPendingKeys(const QList &p_keys) { - for (auto const &key : m_keys) { + for (auto const &key : p_keys) { if (!key.isDigit()) { return true; } @@ -4837,6 +4937,11 @@ bool VVim::hasNonDigitPendingKeys() return false; } +bool VVim::hasNonDigitPendingKeys() +{ + return hasNonDigitPendingKeys(m_keys); +} + bool VVim::processLeaderSequence(const Key &p_key) { // Different from Vim: diff --git a/src/utils/vvim.h b/src/utils/vvim.h index 0846da98..b0902fe8 100644 --- a/src/utils/vvim.h +++ b/src/utils/vvim.h @@ -300,6 +300,7 @@ private: TillBackward, MarkJump, MarkJumpLine, + FindPair, Invalid }; @@ -617,9 +618,10 @@ private: // Move @p_cursor according to @p_moveMode and @p_token. // Return true if it has moved @p_cursor. - bool processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc, + bool processMovement(QTextCursor &p_cursor, QTextCursor::MoveMode p_moveMode, - const Token &p_token, int p_repeat); + const Token &p_token, + int p_repeat); // Move @p_cursor according to @p_moveMode and @p_range. // Return true if it has moved @p_cursor. @@ -647,15 +649,17 @@ private: // Returns true if a command has been completed, otherwise returns false. bool processCommandLine(const Key &p_key); - // Execute command specified by @p_keys. + // Execute command specified by m_keys. // @p_keys does not contain the leading colon. // Following commands are supported: // :w, :wq, :q, :q!, :x - void executeCommand(const QList &p_keys); + void executeCommand(); // Check if m_keys has non-digit key. bool hasNonDigitPendingKeys(); + bool hasNonDigitPendingKeys(const QList &p_keys); + // Reading a leader sequence, read input @p_key and process it. // Returns true if a sequence has been replayed or it is being read, // otherwise returns false.