Browse Source

vim-mode: support ~ to reverse case

Le Tan 8 years ago
parent
commit
c4683dd232
4 changed files with 109 additions and 6 deletions
  1. 1 1
      src/resources/docs/shortcuts_en.md
  2. 2 1
      src/resources/docs/shortcuts_zh.md
  3. 102 4
      src/utils/vvim.cpp
  4. 4 0
      src/utils/vvim.h

+ 1 - 1
src/resources/docs/shortcuts_en.md

@@ -123,7 +123,7 @@ VNote supports a simple but useful Vim mode, including **Normal**, **Insert**, *
 VNote supports following features of Vim:
 
 - `r`, `s`, `i`, `I`, `a`, `A`, `o`, and `O`;
-- Actions `d`, `c`, `y`, `p`, `<`, `>`, `gu`, and `gU`;
+- Actions `d`, `c`, `y`, `p`, `<`, `>`, `gu`, `gU`, and `~`;
 - Movements `h/j/k/l`, `gj/gk`, `Ctrl+U`, `Ctrl+D`, `gg`, `G`, `0`, `^`, and `$`;
 - Marks `a-z`;
 - Registers `"`, `_`, `+`, `a-z`(`A-Z`);

+ 2 - 1
src/resources/docs/shortcuts_zh.md

@@ -123,7 +123,8 @@ VNote支持一个简单但有用的Vim模式,包括 **正常**, **插入**
 VNote支持以下几个Vim的特性:
 
 - `r`, `s`, `i`, `I`, `a`, `A`, `o`, `O`;
-- 操作 `d`, `c`, `y`, `p`, `<`, `>`, `gu`, `gU`;
+- 操作 `d`, `c`, `y`, `p`, `<`, `>`, `gu`, `gU`, `~`;
+- 移动 `h/j/k/l`, `gj/gk`, `Ctrl+U`, `Ctrl+D`, `gg`, `G`, `0`, `^`, `$`;
 - 标记 `a-z`;
 - 寄存器 `"`, `_`, `+`, `a-z`(`A-Z`);
 - 跳转位置列表 (`Ctrl+O` and `Ctrl+I`);

+ 102 - 4
src/utils/vvim.cpp

@@ -380,6 +380,46 @@ static bool replaceSelectedTextWithCharacter(QTextCursor &p_cursor, QChar p_char
     return true;
 }
 
+// Reverse the case of selected text.
+// Returns true if the reverse has taken place.
+// Need to setTextCursor() after calling this.
+static bool reverseSelectedTextCase(QTextCursor &p_cursor)
+{
+    if (!p_cursor.hasSelection()) {
+        return false;
+    }
+
+    QTextDocument *doc = p_cursor.document();
+    int start = p_cursor.selectionStart();
+    int end = p_cursor.selectionEnd();
+    p_cursor.setPosition(start, QTextCursor::MoveAnchor);
+    while (p_cursor.position() < end) {
+        if (p_cursor.atBlockEnd()) {
+            p_cursor.movePosition(QTextCursor::NextCharacter);
+        } else {
+            QChar ch = doc->characterAt(p_cursor.position());
+            bool changed = false;
+            if (ch.isLower()) {
+                ch = ch.toUpper();
+                changed = true;
+            } else if (ch.isUpper()) {
+                ch = ch.toLower();
+                changed = true;
+            }
+
+            if (changed) {
+                p_cursor.deleteChar();
+                // insertText() will move the cursor right after the inserted text.
+                p_cursor.insertText(ch);
+            } else {
+                p_cursor.movePosition(QTextCursor::NextCharacter);
+            }
+        }
+    }
+
+    return true;
+}
+
 bool VVim::handleKeyPressEvent(QKeyEvent *p_event, int *p_autoIndentPos)
 {
     bool ret = handleKeyPressEvent(p_event->key(), p_event->modifiers(), p_autoIndentPos);
@@ -1930,6 +1970,24 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
         break;
     }
 
+    case Qt::Key_AsciiTilde:
+    {
+        if (modifiers == Qt::ShiftModifier) {
+            tryGetRepeatToken(m_keys, m_tokens);
+            if (hasActionToken() || !m_keys.isEmpty()) {
+                break;
+            }
+
+            // Reverse the case.
+            addActionToken(Action::ReverseCase);
+            processCommand(m_tokens);
+
+            break;
+        }
+
+        break;
+    }
+
     default:
         break;
     }
@@ -1995,10 +2053,6 @@ void VVim::processCommand(QList<Token> &p_tokens)
 
     V_ASSERT(p_tokens.at(0).isAction());
 
-    for (int i = 0; i < p_tokens.size(); ++i) {
-        qDebug() << "token" << i << p_tokens[i].toString();
-    }
-
     Token act = p_tokens.takeFirst();
     switch (act.m_action) {
     case Action::Move:
@@ -2073,6 +2127,10 @@ void VVim::processCommand(QList<Token> &p_tokens)
         processReplaceAction(p_tokens);
         break;
 
+    case Action::ReverseCase:
+        processReverseCaseAction(p_tokens);
+        break;
+
     default:
         p_tokens.clear();
         break;
@@ -4119,6 +4177,46 @@ void VVim::processReplaceAction(QList<Token> &p_tokens)
     }
 }
 
+void VVim::processReverseCaseAction(QList<Token> &p_tokens)
+{
+    int repeat = 1;
+    if (!p_tokens.isEmpty()) {
+        Token to = p_tokens.takeFirst();
+        Q_ASSERT(to.isRepeat() && p_tokens.isEmpty());
+        repeat = to.m_repeat;
+    }
+
+    if (!(checkMode(VimMode::Normal)
+          || checkMode(VimMode::Visual)
+          || checkMode(VimMode::VisualLine))) {
+        return;
+    }
+
+    // Reverse the next repeat characters' case until the end of line.
+    // If repeat is greater than the number of left characters in current line,
+    // just change the actual number of left characters.
+    // In visual mode, repeat is ignored and reverse the selected text.
+    QTextCursor cursor = m_editor->textCursor();
+    cursor.beginEditBlock();
+    if (checkMode(VimMode::Normal)) {
+        // Select the characters to be replaced.
+        cursor.clearSelection();
+        int pib = cursor.positionInBlock();
+        int nrChar = cursor.block().length() - 1 - pib;
+        cursor.movePosition(QTextCursor::Right,
+                            QTextCursor::KeepAnchor,
+                            repeat > nrChar ? nrChar : repeat);
+    }
+
+    bool changed = reverseSelectedTextCase(cursor);
+    cursor.endEditBlock();
+
+    if (changed) {
+        m_editor->setTextCursor(cursor);
+        setMode(VimMode::Normal);
+    }
+}
+
 bool VVim::clearSelection()
 {
     QTextCursor cursor = m_editor->textCursor();

+ 4 - 0
src/utils/vvim.h

@@ -255,6 +255,7 @@ private:
         UnIndent,
         ToUpper,
         ToLower,
+        ReverseCase,
         Undo,
         Redo,
         RedrawAtTop,
@@ -519,6 +520,9 @@ private:
     // Action::Replace.
     void processReplaceAction(QList<Token> &p_tokens);
 
+    // Action::ReverseCase.
+    void processReverseCaseAction(QList<Token> &p_tokens);
+
     // Clear selection if there is any.
     // Returns true if there is selection.
     bool clearSelection();