Browse Source

vim-mode: support f/F/t/T movement

Support `;` and `,` to repeat last find movement.
Le Tan 8 years ago
parent
commit
7871965bf8
4 changed files with 347 additions and 11 deletions
  1. 41 0
      src/utils/veditutils.cpp
  2. 8 0
      src/utils/veditutils.h
  3. 261 8
      src/utils/vvim.cpp
  4. 37 3
      src/utils/vvim.h

+ 41 - 0
src/utils/veditutils.cpp

@@ -210,3 +210,44 @@ void VEditUtils::unindentBlock(QTextCursor &p_cursor,
         }
     }
 }
+
+bool VEditUtils::findTargetWithinBlock(QTextCursor &p_cursor,
+                                       QTextCursor::MoveMode p_mode,
+                                       QChar p_target,
+                                       bool p_forward,
+                                       bool p_inclusive)
+{
+    qDebug() << "find target" << p_target << p_forward << p_inclusive;
+    QTextBlock block = p_cursor.block();
+    QString text = block.text();
+    int pib = p_cursor.positionInBlock();
+    int delta = p_forward ? 1 : -1;
+    int idx = pib + delta;
+
+repeat:
+    for (; idx < text.size() && idx >= 0; idx += delta) {
+        if (text[idx] == p_target) {
+            break;
+        }
+    }
+
+    if (idx < 0 || idx >= text.size()) {
+        return false;
+    }
+
+    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;
+    }
+
+    if (idx == pib) {
+        // We need to skip current match.
+        idx = pib + delta * 2;
+        goto repeat;
+    }
+
+    p_cursor.setPosition(block.position() + idx, p_mode);
+    return true;
+}

+ 8 - 0
src/utils/veditutils.h

@@ -63,6 +63,14 @@ public:
     static void unindentBlock(QTextCursor &p_cursor,
                               const QString &p_indentationText);
 
+    // Find a char within a block.
+    // Returns true if target is found.
+    static bool findTargetWithinBlock(QTextCursor &p_cursor,
+                                      QTextCursor::MoveMode p_mode,
+                                      QChar p_target,
+                                      bool p_forward,
+                                      bool p_inclusive);
+
 private:
     VEditUtils() {}
 };

+ 261 - 8
src/utils/vvim.cpp

@@ -308,6 +308,34 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
         goto clear_accept;
     }
 
+    if (expectingCharacterTarget()) {
+        // Expecting a target character for f/F/t/T.
+        Movement mm = Movement::Invalid;
+        const Key &aKey = m_keys.first();
+        if (aKey.m_key == Qt::Key_F) {
+            if (aKey.m_modifiers == Qt::NoModifier) {
+                mm = Movement::FindForward;
+            } else {
+                mm = Movement::FindBackward;
+            }
+        } else {
+            if (aKey.m_modifiers == Qt::NoModifier) {
+                mm = Movement::TillForward;
+            } else {
+                mm = Movement::TillBackward;
+            }
+        }
+
+        V_ASSERT(mm != Movement::Invalid);
+
+        tryAddMoveAction();
+        addMovementToken(mm, keyInfo);
+        m_lastFindToken = m_tokens.last();
+        processCommand(m_tokens);
+
+        goto clear_accept;
+    }
+
     // We will add key to m_keys. If all m_keys can combined to a token, add
     // a new token to m_tokens, clear m_keys and try to process m_tokens.
     switch (key) {
@@ -1164,6 +1192,82 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
         break;
     }
 
+    case Qt::Key_F:
+    {
+        if (m_mode == VimMode::VisualLine) {
+            break;
+        }
+
+        if (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier) {
+            // f/F, find forward/backward within a block.
+            tryGetRepeatToken(m_keys, m_tokens);
+
+            if (m_keys.isEmpty()) {
+                m_keys.append(keyInfo);
+                goto accept;
+            }
+
+            break;
+        }
+
+        break;
+    }
+
+    case Qt::Key_T:
+    {
+        if (m_mode == VimMode::VisualLine) {
+            break;
+        }
+
+        if (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier) {
+            // t/T, find till forward/backward within a block.
+            tryGetRepeatToken(m_keys, m_tokens);
+
+            if (m_keys.isEmpty()) {
+                m_keys.append(keyInfo);
+                goto accept;
+            }
+
+            break;
+        }
+
+        break;
+    }
+
+    case Qt::Key_Comma:
+    {
+        if (m_mode == VimMode::VisualLine) {
+            break;
+        }
+
+        // ,, repeat last find target movement, but reversely.
+        tryGetRepeatToken(m_keys, m_tokens);
+
+        if (m_keys.isEmpty()) {
+            repeatLastFindMovement(true);
+            break;
+        }
+
+        break;
+    }
+
+    case Qt::Key_Semicolon:
+    {
+        if (m_mode == VimMode::VisualLine) {
+            break;
+        }
+
+        // ;, repeat last find target movement.
+        tryGetRepeatToken(m_keys, m_tokens);
+
+        if (m_keys.isEmpty()) {
+            repeatLastFindMovement(false);
+            break;
+        }
+
+        break;
+    }
+
     default:
         break;
     }
@@ -1327,7 +1431,7 @@ void VVim::processMoveAction(QList<Token> &p_tokens)
                                      ? QTextCursor::KeepAnchor
                                      : QTextCursor::MoveAnchor;
     bool hasMoved = processMovement(cursor, m_editor->document(),
-                                    moveMode, mvToken.m_movement, repeat);
+                                    moveMode, mvToken, repeat);
 
     if (hasMoved) {
         // Maintain positionInBlock.
@@ -1358,13 +1462,80 @@ void VVim::processMoveAction(QList<Token> &p_tokens)
     }
 }
 
+#define ADDKEY(x, y) case (x): {ch = (y); break;}
+
+// Returns NULL QChar if invalid.
+static QChar keyToChar(int p_key, int p_modifiers)
+{
+    if (p_modifiers == Qt::ControlModifier) {
+        return QChar();
+    }
+
+    if (p_key >= Qt::Key_0 && p_key <= Qt::Key_9) {
+        return QChar('0' + (p_key - Qt::Key_0));
+    } else if (p_key >= Qt::Key_A && p_key <= Qt::Key_Z) {
+        if (p_modifiers == Qt::ShiftModifier) {
+            return QChar('A' + (p_key - Qt::Key_A));
+        } else {
+            return QChar('a' + (p_key - Qt::Key_A));
+        }
+    }
+
+    QChar ch;
+    switch (p_key) {
+    ADDKEY(Qt::Key_Tab, '\t');
+    ADDKEY(Qt::Key_Space, ' ');
+    ADDKEY(Qt::Key_Exclam, '!');
+    ADDKEY(Qt::Key_QuoteDbl, '"');
+    ADDKEY(Qt::Key_NumberSign, '#');
+    ADDKEY(Qt::Key_Dollar, '$');
+    ADDKEY(Qt::Key_Percent, '%');
+    ADDKEY(Qt::Key_Ampersand, '&');
+    ADDKEY(Qt::Key_Apostrophe, '\'');
+    ADDKEY(Qt::Key_ParenLeft, '(');
+    ADDKEY(Qt::Key_ParenRight, ')');
+    ADDKEY(Qt::Key_Asterisk, '*');
+    ADDKEY(Qt::Key_Plus, '+');
+    ADDKEY(Qt::Key_Comma, ',');
+    ADDKEY(Qt::Key_Minus, '-');
+    ADDKEY(Qt::Key_Period, '.');
+    ADDKEY(Qt::Key_Slash, '/');
+    ADDKEY(Qt::Key_Colon, ':');
+    ADDKEY(Qt::Key_Semicolon, ';');
+    ADDKEY(Qt::Key_Less, '<');
+    ADDKEY(Qt::Key_Equal, '=');
+    ADDKEY(Qt::Key_Greater, '>');
+    ADDKEY(Qt::Key_Question, '?');
+    ADDKEY(Qt::Key_At, '@');
+    ADDKEY(Qt::Key_BracketLeft, '[');
+    ADDKEY(Qt::Key_Backslash, '\\');
+    ADDKEY(Qt::Key_BracketRight, ']');
+    ADDKEY(Qt::Key_AsciiCircum, '^');
+    ADDKEY(Qt::Key_Underscore, '_');
+    ADDKEY(Qt::Key_QuoteLeft, '`');
+    ADDKEY(Qt::Key_BraceLeft, '{');
+    ADDKEY(Qt::Key_Bar, '|');
+    ADDKEY(Qt::Key_BraceRight, '}');
+    ADDKEY(Qt::Key_AsciiTilde, '~');
+
+    default:
+        break;
+    }
+
+    return ch;
+}
+
 bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc,
                            QTextCursor::MoveMode p_moveMode,
-                           Movement p_movement, int p_repeat)
+                           const Token &p_token, int p_repeat)
 {
+    V_ASSERT(p_token.isMovement());
+
     bool hasMoved = false;
+    bool inclusive = true;
+    bool forward = true;
 
-    switch (p_movement) {
+    switch (p_token.m_movement) {
     case Movement::Left:
     {
         if (p_repeat == -1) {
@@ -1765,6 +1936,29 @@ bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc,
         break;
     }
 
+    case Movement::TillBackward:
+        forward = false;
+        // Fall through.
+    case Movement::TillForward:
+        inclusive = false;
+        goto handle_target;
+
+    case Movement::FindBackward:
+        forward = false;
+        // Fall through.
+    case Movement::FindForward:
+    {
+handle_target:
+        const Key &key = p_token.m_key;
+        QChar target = keyToChar(key.m_key, key.m_modifiers);
+        if (!target.isNull()) {
+            hasMoved = VEditUtils::findTargetWithinBlock(p_cursor, p_moveMode,
+                                                         target, forward, inclusive);
+        }
+
+        break;
+    }
+
     default:
         break;
     }
@@ -1979,7 +2173,7 @@ void VVim::processDeleteAction(QList<Token> &p_tokens)
     }
 
     cursor.beginEditBlock();
-    hasMoved = processMovement(cursor, doc, moveMode, to.m_movement, repeat);
+    hasMoved = processMovement(cursor, doc, moveMode, to, repeat);
     if (repeat == -1) {
         repeat = 1;
     }
@@ -2235,7 +2429,7 @@ void VVim::processCopyAction(QList<Token> &p_tokens)
     }
 
     cursor.beginEditBlock();
-    changed = processMovement(cursor, doc, moveMode, to.m_movement, repeat);
+    changed = processMovement(cursor, doc, moveMode, to, repeat);
     if (repeat == -1) {
         repeat = 1;
     }
@@ -2545,7 +2739,7 @@ void VVim::processChangeAction(QList<Token> &p_tokens)
     }
 
     cursor.beginEditBlock();
-    hasMoved = processMovement(cursor, doc, moveMode, to.m_movement, repeat);
+    hasMoved = processMovement(cursor, doc, moveMode, to, repeat);
     if (repeat == -1) {
         repeat = 1;
     }
@@ -2793,7 +2987,7 @@ void VVim::processIndentAction(QList<Token> &p_tokens, bool p_isIndent)
     processMovement(cursor,
                     doc,
                     QTextCursor::KeepAnchor,
-                    to.m_movement,
+                    to,
                     repeat);
     VEditUtils::indentSelectedBlocks(doc,
                                      cursor,
@@ -2852,7 +3046,7 @@ void VVim::processToLowerAction(QList<Token> &p_tokens, bool p_toLower)
     changed = processMovement(cursor,
                               doc,
                               moveMode,
-                              to.m_movement,
+                              to,
                               repeat);
     if (repeat == -1) {
         repeat = 1;
@@ -3051,6 +3245,19 @@ bool VVim::expectingRegisterName() const
            && m_keys.at(0) == Key(Qt::Key_QuoteDbl, Qt::ShiftModifier);
 }
 
+bool VVim::expectingCharacterTarget() const
+{
+    if (m_keys.size() != 1) {
+        return false;
+    }
+
+    const Key &key = m_keys.first();
+    return (key == Key(Qt::Key_F, Qt::NoModifier)
+            || key == Key(Qt::Key_F, Qt::ShiftModifier)
+            || key == Key(Qt::Key_T, Qt::NoModifier)
+            || key == Key(Qt::Key_T, Qt::ShiftModifier));
+}
+
 QChar VVim::keyToRegisterName(const Key &p_key) const
 {
     if (p_key.isAlphabet()) {
@@ -3164,6 +3371,11 @@ void VVim::addMovementToken(Movement p_movement)
     m_tokens.append(Token(p_movement));
 }
 
+void VVim::addMovementToken(Movement p_movement, Key p_key)
+{
+    m_tokens.append(Token(p_movement, p_key));
+}
+
 void VVim::deleteSelectedText(QTextCursor &p_cursor, bool p_clearEmptyBlock)
 {
     if (p_cursor.hasSelection()) {
@@ -3303,3 +3515,44 @@ const QString &VVim::Register::read()
 
     return m_value;
 }
+
+void VVim::repeatLastFindMovement(bool p_reverse)
+{
+    if (!m_lastFindToken.isValid()) {
+        return;
+    }
+
+    V_ASSERT(m_lastFindToken.isMovement());
+
+    Movement mm = m_lastFindToken.m_movement;
+    Key key = m_lastFindToken.m_key;
+
+    V_ASSERT(key.isValid());
+
+    if (p_reverse) {
+        switch (mm) {
+        case Movement::FindForward:
+            mm = Movement::FindBackward;
+            break;
+
+        case Movement::FindBackward:
+            mm = Movement::FindForward;
+            break;
+
+        case Movement::TillForward:
+            mm = Movement::TillBackward;
+            break;
+
+        case Movement::TillBackward:
+            mm = Movement::TillForward;
+            break;
+
+        default:
+            break;
+        }
+    }
+
+    tryAddMoveAction();
+    addMovementToken(mm, key);
+    processCommand(m_tokens);
+}

+ 37 - 3
src/utils/vvim.h

@@ -56,6 +56,8 @@ private:
         {
         }
 
+        Key() : m_key(-1), m_modifiers(Qt::NoModifier) {}
+
         int m_key;
         int m_modifiers;
 
@@ -89,6 +91,11 @@ private:
             }
         }
 
+        bool isValid() const
+        {
+            return m_key > -1 && m_modifiers > -1;
+        }
+
         bool operator==(const Key &p_key) const
         {
             return p_key.m_key == m_key && p_key.m_modifiers == m_modifiers;
@@ -138,6 +145,10 @@ private:
         WORDBackward,
         BackwardEndOfWord,
         BackwardEndOfWORD,
+        FindForward,
+        FindBackward,
+        TillForward,
+        TillBackward,
         Invalid
     };
 
@@ -177,6 +188,9 @@ private:
         Token(Movement p_movement)
             : m_type(TokenType::Movement), m_movement(p_movement) {}
 
+        Token(Movement p_movement, Key p_key)
+            : m_type(TokenType::Movement), m_movement(p_movement), m_key(p_key) {}
+
         Token(Range p_range)
             : m_type(TokenType::Range), m_range(p_range) {}
 
@@ -202,6 +216,11 @@ private:
             return m_type == TokenType::Range;
         }
 
+        bool isValid() const
+        {
+            return m_type != TokenType::Invalid;
+        }
+
         QString toString() const
         {
             QString str;
@@ -235,9 +254,12 @@ private:
         {
             Action m_action;
             int m_repeat;
-            Movement m_movement;
             Range m_range;
+            Movement m_movement;
         };
+
+        // Used in some Movement.
+        Key m_key;
     };
 
     struct Register
@@ -358,6 +380,9 @@ private:
     // Check m_keys to see if we are expecting a register name.
     bool expectingRegisterName() const;
 
+    // Check m_keys to see if we are expecting a target for f/t/F/T command.
+    bool expectingCharacterTarget() const;
+
     // Return the corresponding register name of @p_key.
     // If @p_key is not a valid register name, return a NULL QChar.
     QChar keyToRegisterName(const Key &p_key) const;
@@ -381,6 +406,9 @@ private:
     // Add an Movement token at the end of m_tokens.
     void addMovementToken(Movement p_movement);
 
+    // Add an Movement token at the end of m_tokens.
+    void addMovementToken(Movement p_movement, Key p_key);
+
     // Delete selected text if there is any.
     // @p_clearEmptyBlock: whether to remove the empty block after deletion.
     void deleteSelectedText(QTextCursor &p_cursor, bool p_clearEmptyBlock);
@@ -401,11 +429,11 @@ private:
     // Remove QChar::ObjectReplacementCharacter before saving.
     void saveToRegister(const QString &p_text);
 
-    // Move @p_cursor according to @p_moveMode and @p_movement.
+    // 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,
                          QTextCursor::MoveMode p_moveMode,
-                         Movement p_movement, 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.
@@ -421,6 +449,9 @@ private:
     // Check if m_tokens only contains action token @p_action.
     bool checkActionToken(Action p_action) const;
 
+    // Repeat m_lastFindToken.
+    void repeatLastFindMovement(bool p_reverse);
+
     VEdit *m_editor;
     const VEditConfig *m_editConfig;
     VimMode m_mode;
@@ -440,6 +471,9 @@ private:
     // Currently used register.
     QChar m_regName;
 
+    // Last f/F/t/T Token.
+    Token m_lastFindToken;
+
     static const QChar c_unnamedRegister;
     static const QChar c_blackHoleRegister;
     static const QChar c_selectionRegister;