Ver código fonte

vim-mode: support J and gJ to join lines

Le Tan 8 anos atrás
pai
commit
94b671f505
2 arquivos alterados com 141 adições e 0 exclusões
  1. 135 0
      src/utils/vvim.cpp
  2. 6 0
      src/utils/vvim.h

+ 135 - 0
src/utils/vvim.cpp

@@ -434,6 +434,73 @@ static bool reverseSelectedTextCase(QTextCursor &p_cursor)
     return true;
 }
 
+// Join current cursor line and the next line.
+static void joinTwoLines(QTextCursor &p_cursor, bool p_modifySpaces)
+{
+    QTextDocument *doc = p_cursor.document();
+    QTextBlock firstBlock = p_cursor.block();
+    QString textToAppend = firstBlock.next().text();
+    p_cursor.movePosition(QTextCursor::EndOfBlock);
+
+    if (p_modifySpaces) {
+        bool insertSpaces = false;
+        if (firstBlock.length() > 1) {
+            QChar lastChar = doc->characterAt(p_cursor.position() - 1);
+            if (!lastChar.isSpace()) {
+                insertSpaces = true;
+            }
+        }
+
+        if (insertSpaces) {
+            p_cursor.insertText(" ");
+        }
+
+        // Remove indentation.
+        int idx = 0;
+        for (idx = 0; idx < textToAppend.size(); ++idx) {
+            if (!textToAppend[idx].isSpace()) {
+                break;
+            }
+        }
+
+        textToAppend = textToAppend.right(textToAppend.size() - idx);
+    }
+
+    // Now p_cursor is at the end of the first block.
+    int position = p_cursor.block().position() + p_cursor.positionInBlock();
+    p_cursor.insertText(textToAppend);
+
+    // Delete the second block.
+    p_cursor.movePosition(QTextCursor::NextBlock);
+    VEditUtils::removeBlock(p_cursor);
+
+    // Position p_cursor right at the front of appended text.
+    p_cursor.setPosition(position);
+}
+
+// Join lines specified by [@p_firstBlock, @p_firstBlock + p_blockCount).
+// Need to check the block range (based on 0).
+static bool joinLines(QTextCursor &p_cursor,
+                      int p_firstBlock,
+                      int p_blockCount,
+                      bool p_modifySpaces)
+{
+    QTextDocument *doc = p_cursor.document();
+    int totalBlockCount = doc->blockCount();
+    if (p_blockCount <= 0
+        || p_firstBlock >= totalBlockCount - 1) {
+        return false;
+    }
+
+    p_blockCount = qMin(p_blockCount, totalBlockCount - p_firstBlock);
+    p_cursor.setPosition(doc->findBlockByNumber(p_firstBlock).position());
+    for (int i = 1; i < p_blockCount; ++i) {
+        joinTwoLines(p_cursor, p_modifySpaces);
+    }
+
+    return true;
+}
+
 bool VVim::handleKeyPressEvent(QKeyEvent *p_event, int *p_autoIndentPos)
 {
     bool ret = handleKeyPressEvent(p_event->key(), p_event->modifiers(), p_autoIndentPos);
@@ -754,6 +821,24 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
             addMovementToken(mm);
             processCommand(m_tokens);
             resetPositionInBlock = false;
+        } else if (modifiers == Qt::ShiftModifier) {
+            if (key == Qt::Key_J) {
+                tryGetRepeatToken(m_keys, m_tokens);
+
+                if (hasActionToken()) {
+                    break;
+                }
+
+                if (checkPendingKey(Key(Qt::Key_G))) {
+                    // gJ, JoinNoModification.
+                    addActionToken(Action::JoinNoModification);
+                } else if (m_keys.isEmpty()) {
+                    // J, Join.
+                    addActionToken(Action::Join);
+                }
+
+                processCommand(m_tokens);
+            }
         }
 
         break;
@@ -2252,6 +2337,14 @@ void VVim::processCommand(QList<Token> &p_tokens)
         processReverseCaseAction(p_tokens);
         break;
 
+    case Action::Join:
+        processJoinAction(p_tokens, true);
+        break;
+
+    case Action::JoinNoModification:
+        processJoinAction(p_tokens, false);
+        break;
+
     default:
         p_tokens.clear();
         break;
@@ -4509,6 +4602,48 @@ void VVim::processReverseCaseAction(QList<Token> &p_tokens)
     }
 }
 
+void VVim::processJoinAction(QList<Token> &p_tokens, bool p_modifySpaces)
+{
+    int repeat = 2;
+    if (!p_tokens.isEmpty()) {
+        Token to = p_tokens.takeFirst();
+        Q_ASSERT(to.isRepeat() && p_tokens.isEmpty());
+        repeat = qMax(to.m_repeat, repeat);
+    }
+
+    if (!(checkMode(VimMode::Normal)
+          || checkMode(VimMode::Visual)
+          || checkMode(VimMode::VisualLine))) {
+        return;
+    }
+
+    // Join repeat lines, with the minimum of two lines. Do nothing when on the
+    // last line.
+    // If @p_modifySpaces is true, remove the indent and insert up to two spaces.
+    // If repeat is too big, it is reduced to the number of lines available.
+    // In visual mode, repeat is ignored and join the highlighted lines.
+    int firstBlock = -1;
+    int blockCount = repeat;
+    QTextCursor cursor = m_editor->textCursor();
+    cursor.beginEditBlock();
+    if (checkMode(VimMode::Normal)) {
+        firstBlock = cursor.block().blockNumber();
+    } else {
+        QTextDocument *doc = m_editor->document();
+        firstBlock = doc->findBlock(cursor.selectionStart()).blockNumber();
+        int lastBlock = doc->findBlock(cursor.selectionEnd()).blockNumber();
+        blockCount = lastBlock - firstBlock + 1;
+    }
+
+    bool changed = joinLines(cursor, firstBlock, blockCount, p_modifySpaces);
+    cursor.endEditBlock();
+
+    if (changed) {
+        m_editor->setTextCursor(cursor);
+        setMode(VimMode::Normal);
+    }
+}
+
 bool VVim::clearSelection()
 {
     QTextCursor cursor = m_editor->textCursor();

+ 6 - 0
src/utils/vvim.h

@@ -372,6 +372,8 @@ private:
         JumpPreviousLocation,
         JumpNextLocation,
         Replace,
+        Join,
+        JoinNoModification,
         Invalid
     };
 
@@ -636,6 +638,10 @@ private:
     // Action::ReverseCase.
     void processReverseCaseAction(QList<Token> &p_tokens);
 
+    // Action::Join and Action::JoinNoModification action.
+    // @p_modifySpaces: whether remove the indent and insert up to two spaces.
+    void processJoinAction(QList<Token> &p_tokens, bool p_modifySpaces);
+
     // Clear selection if there is any.
     // Returns true if there is selection.
     bool clearSelection();