diff --git a/src/utils/vvim.cpp b/src/utils/vvim.cpp index 348ff54b..16a793eb 100644 --- a/src/utils/vvim.cpp +++ b/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 &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 &p_tokens) } } +void VVim::processJoinAction(QList &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(); diff --git a/src/utils/vvim.h b/src/utils/vvim.h index 53313352..9092f1d7 100644 --- a/src/utils/vvim.h +++ b/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 &p_tokens); + // Action::Join and Action::JoinNoModification action. + // @p_modifySpaces: whether remove the indent and insert up to two spaces. + void processJoinAction(QList &p_tokens, bool p_modifySpaces); + // Clear selection if there is any. // Returns true if there is selection. bool clearSelection();