From eba2556a3ac1d4bb1fa28327f5f738005d3ea580 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Tue, 13 Jun 2017 21:22:38 +0800 Subject: [PATCH] vim-mode: support DELETE action Actions related to `d` and `x`. --- src/src.pro | 6 +- src/utils/veditutils.cpp | 41 +++ src/utils/veditutils.h | 24 ++ src/utils/vutils.h | 4 +- src/utils/vvim.cpp | 724 +++++++++++++++++++++++++++++---------- src/utils/vvim.h | 55 ++- src/vimagepreviewer.cpp | 5 +- src/vmdedit.cpp | 11 +- 8 files changed, 682 insertions(+), 188 deletions(-) create mode 100644 src/utils/veditutils.cpp create mode 100644 src/utils/veditutils.h diff --git a/src/src.pro b/src/src.pro index 502647c7..f1bae6f3 100644 --- a/src/src.pro +++ b/src/src.pro @@ -65,7 +65,8 @@ SOURCES += main.cpp\ vexporter.cpp \ vmdtab.cpp \ vhtmltab.cpp \ - utils/vvim.cpp + utils/vvim.cpp \ + utils/veditutils.cpp HEADERS += vmainwindow.h \ vdirectorytree.h \ @@ -117,7 +118,8 @@ HEADERS += vmainwindow.h \ vexporter.h \ vmdtab.h \ vhtmltab.h \ - utils/vvim.h + utils/vvim.h \ + utils/veditutils.h RESOURCES += \ vnote.qrc \ diff --git a/src/utils/veditutils.cpp b/src/utils/veditutils.cpp new file mode 100644 index 00000000..b6d3e343 --- /dev/null +++ b/src/utils/veditutils.cpp @@ -0,0 +1,41 @@ +#include "veditutils.h" + +#include +#include + +void VEditUtils::removeBlock(QTextBlock &p_block, QString *p_text) +{ + QTextCursor cursor(p_block); + removeBlock(cursor, p_text); +} + +void VEditUtils::removeBlock(QTextCursor &p_cursor, QString *p_text) +{ + const QTextDocument *doc = p_cursor.document(); + int blockCount = doc->blockCount(); + int blockNum = p_cursor.block().blockNumber(); + + p_cursor.select(QTextCursor::BlockUnderCursor); + if (p_text) { + *p_text = p_cursor.selectedText() + "\n"; + } + + p_cursor.deleteChar(); + + // Deleting the first block will leave an empty block. + // Deleting the last empty block will not work with deleteChar(). + if (blockCount == doc->blockCount()) { + if (blockNum == blockCount - 1) { + // The last block. + p_cursor.deletePreviousChar(); + } else { + p_cursor.deleteChar(); + } + } + + if (p_cursor.block().blockNumber() < blockNum) { + p_cursor.movePosition(QTextCursor::NextBlock); + } + + p_cursor.movePosition(QTextCursor::StartOfBlock); +} diff --git a/src/utils/veditutils.h b/src/utils/veditutils.h new file mode 100644 index 00000000..fc4bc413 --- /dev/null +++ b/src/utils/veditutils.h @@ -0,0 +1,24 @@ +#ifndef VEDITUTILS_H +#define VEDITUTILS_H + +#include +#include + +// Utils for text edit. +class VEditUtils +{ +public: + // Remove the whole block @p_block. + // If @p_text is not NULL, return the deleted text here. + static void removeBlock(QTextBlock &p_block, QString *p_text = NULL); + + // Remove the whole block under @p_cursor. + // If @p_text is not NULL, return the deleted text here. + // Need to call setTextCursor() to make it take effect. + static void removeBlock(QTextCursor &p_cursor, QString *p_text = NULL); + +private: + VEditUtils() {} +}; + +#endif // VEDITUTILS_H diff --git a/src/utils/vutils.h b/src/utils/vutils.h index 96071f72..8bf897b3 100644 --- a/src/utils/vutils.h +++ b/src/utils/vutils.h @@ -42,8 +42,6 @@ struct ImageLink class VUtils { public: - VUtils(); - static QString readFileFromDisk(const QString &filePath); static bool writeFileToDisk(const QString &filePath, const QString &text); // Transform FFFFFF string to QRgb @@ -117,6 +115,8 @@ public: static const QString c_fencedCodeBlockEndRegExp; private: + VUtils(); + static void initAvailableLanguage(); // diff --git a/src/utils/vvim.cpp b/src/utils/vvim.cpp index f7eff2c1..25bf0101 100644 --- a/src/utils/vvim.cpp +++ b/src/utils/vvim.cpp @@ -6,6 +6,7 @@ #include #include #include "vedit.h" +#include "utils/veditutils.h" const QChar VVim::c_unnamedRegister = QChar('"'); const QChar VVim::c_blackHoleRegister = QChar('_'); @@ -219,7 +220,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) goto accept; } else { // StartOfLine. - tryInsertMoveAction(); + tryAddMoveAction(); m_tokens.append(Token(Movement::StartOfLine)); @@ -314,7 +315,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) } V_ASSERT(mm != Movement::Invalid); - tryInsertMoveAction(); + tryAddMoveAction(); m_tokens.append(Token(mm)); processCommand(m_tokens); @@ -446,7 +447,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) tryGetRepeatToken(m_keys, m_tokens); if (m_keys.isEmpty()) { - tryInsertMoveAction(); + tryAddMoveAction(); m_tokens.append(Token(Movement::EndOfLine)); processCommand(m_tokens); @@ -486,7 +487,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) } if (mm != Movement::Invalid) { - tryInsertMoveAction(); + tryAddMoveAction(); m_tokens.append(Token(mm)); processCommand(m_tokens); @@ -515,7 +516,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) mm = Movement::WORDBackward; } - tryInsertMoveAction(); + tryAddMoveAction(); m_tokens.append(Token(mm)); processCommand(m_tokens); break; @@ -534,7 +535,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) } Movement mm = Movement::PageUp; - tryInsertMoveAction(); + tryAddMoveAction(); m_tokens.append(Token(mm)); processCommand(m_tokens); resetPositionInBlock = false; @@ -554,7 +555,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) } Movement mm = Movement::HalfPageUp; - tryInsertMoveAction(); + tryAddMoveAction(); m_tokens.append(Token(mm)); processCommand(m_tokens); resetPositionInBlock = false; @@ -574,7 +575,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) } Movement mm = Movement::PageDown; - tryInsertMoveAction(); + tryAddMoveAction(); m_tokens.append(Token(mm)); processCommand(m_tokens); resetPositionInBlock = false; @@ -594,12 +595,44 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) } Movement mm = Movement::HalfPageDown; - tryInsertMoveAction(); + tryAddMoveAction(); m_tokens.append(Token(mm)); processCommand(m_tokens); resetPositionInBlock = false; } else if (modifiers == Qt::NoModifier) { // d, delete action. + tryGetRepeatToken(m_keys, m_tokens); + if (hasActionToken()) { + // This is another d, something like dd. + if (getActionToken()->m_action == Action::Delete) { + addRangeToken(Range::Line); + processCommand(m_tokens); + break; + } else { + // An invalid sequence. + break; + } + } else { + // The first d, an Action. + if (m_mode == VimMode::Visual || m_mode == VimMode::VisualLine) { + deleteSelectedText(m_mode == VimMode::VisualLine); + setMode(VimMode::Normal); + break; + } + + addActionToken(Action::Delete); + goto accept; + } + } else if (modifiers == Qt::ShiftModifier) { + tryGetRepeatToken(m_keys, m_tokens); + if (!hasActionToken() && m_mode == VimMode::Normal) { + // D, same as d$. + addActionToken(Action::Delete); + addMovementToken(Movement::EndOfLine); + processCommand(m_tokens); + } + + break; } break; @@ -648,7 +681,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) if (m_mode == VimMode::VisualLine) { QTextCursor cursor = m_editor->textCursor(); - expandSelectionInVisualLineMode(cursor); + expandSelectionToWholeLines(cursor); m_editor->setTextCursor(cursor); } } @@ -667,7 +700,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) } Movement mm = Movement::FirstCharacter; - tryInsertMoveAction(); + tryAddMoveAction(); m_tokens.append(Token(mm)); processCommand(m_tokens); } @@ -691,7 +724,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) mm = Movement::WORDForward; } - tryInsertMoveAction(); + tryAddMoveAction(); m_tokens.append(Token(mm)); processCommand(m_tokens); } @@ -726,7 +759,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) } } - tryInsertMoveAction(); + tryAddMoveAction(); m_tokens.append(Token(mm)); processCommand(m_tokens); } @@ -750,6 +783,28 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) break; } + case Qt::Key_X: + { + if (modifiers == Qt::ShiftModifier || modifiers == Qt::NoModifier) { + // x, or X to delete one char. + tryGetRepeatToken(m_keys, m_tokens); + if (!m_keys.isEmpty()) { + break; + } + + if (!hasActionToken()) { + addActionToken(Action::Delete); + addMovementToken(modifiers == Qt::ShiftModifier ? Movement::Left + : Movement::Right); + processCommand(m_tokens); + } + + break; + } + + break; + } + default: break; } @@ -798,7 +853,6 @@ void VVim::processCommand(QList &p_tokens) V_ASSERT(p_tokens.at(0).isAction()); - qDebug() << "process tokens of size" << p_tokens.size(); for (int i = 0; i < p_tokens.size(); ++i) { qDebug() << "token" << i << p_tokens[i].toString(); } @@ -809,6 +863,10 @@ void VVim::processCommand(QList &p_tokens) processMoveAction(p_tokens); break; + case Action::Delete: + processDeleteAction(p_tokens); + break; + default: p_tokens.clear(); break; @@ -864,34 +922,68 @@ void VVim::processMoveAction(QList &p_tokens) } if (!mvToken.isMovement() || !p_tokens.isEmpty()) { - p_tokens.clear(); return; } QTextCursor cursor = m_editor->textCursor(); - QTextDocument *doc = m_editor->document(); if (m_resetPositionInBlock) { positionInBlock = cursor.positionInBlock(); } - bool hasMoved = false; QTextCursor::MoveMode moveMode = (m_mode == VimMode::Visual || m_mode == VimMode::VisualLine) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor; - switch (mvToken.m_movement) { - case Movement::Left: - { - if (repeat == -1) { - repeat = 1; + bool hasMoved = processMovement(cursor, m_editor->document(), + moveMode, mvToken.m_movement, repeat); + + if (hasMoved) { + // Maintain positionInBlock. + switch (mvToken.m_movement) { + case Movement::Left: + case Movement::Right: + positionInBlock = cursor.positionInBlock(); + break; + + case Movement::Up: + case Movement::Down: + case Movement::PageUp: + case Movement::PageDown: + case Movement::HalfPageUp: + case Movement::HalfPageDown: + setCursorPositionInBlock(cursor, positionInBlock, moveMode); + break; + + default: + break; } - int pib = cursor.positionInBlock(); - repeat = qMin(pib, repeat); + if (m_mode == VimMode::VisualLine) { + expandSelectionToWholeLines(cursor); + } - if (repeat > 0) { - cursor.movePosition(QTextCursor::Left, moveMode, repeat); - positionInBlock = cursor.positionInBlock(); + m_editor->setTextCursor(cursor); + } +} + +bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc, + QTextCursor::MoveMode &p_moveMode, + Movement p_movement, int p_repeat) +{ + bool hasMoved = false; + + switch (p_movement) { + case Movement::Left: + { + if (p_repeat == -1) { + p_repeat = 1; + } + + int pib = p_cursor.positionInBlock(); + p_repeat = qMin(pib, p_repeat); + + if (p_repeat > 0) { + p_cursor.movePosition(QTextCursor::Left, p_moveMode, p_repeat); hasMoved = true; } @@ -900,19 +992,18 @@ void VVim::processMoveAction(QList &p_tokens) case Movement::Right: { - if (repeat == -1) { - repeat = 1; + if (p_repeat == -1) { + p_repeat = 1; } - int pib = cursor.positionInBlock(); - int length = cursor.block().length(); - if (length - pib <= repeat) { - repeat = length - pib - 1; + int pib = p_cursor.positionInBlock(); + int length = p_cursor.block().length(); + if (length - pib <= p_repeat) { + p_repeat = length - pib - 1; } - if (repeat > 0) { - cursor.movePosition(QTextCursor::Right, moveMode, repeat); - positionInBlock = cursor.positionInBlock(); + if (p_repeat > 0) { + p_cursor.movePosition(QTextCursor::Right, p_moveMode, p_repeat); hasMoved = true; } @@ -921,18 +1012,14 @@ void VVim::processMoveAction(QList &p_tokens) case Movement::Up: { - if (repeat == -1) { - repeat = 1; + if (p_repeat == -1) { + p_repeat = 1; } - repeat = qMin(cursor.block().blockNumber(), repeat); - - if (repeat > 0) { - cursor.movePosition(QTextCursor::PreviousBlock, moveMode, repeat); - if (positionInBlock > 0) { - setCursorPositionInBlock(cursor, positionInBlock, moveMode); - } + p_repeat = qMin(p_cursor.block().blockNumber(), p_repeat); + if (p_repeat > 0) { + p_cursor.movePosition(QTextCursor::PreviousBlock, p_moveMode, p_repeat); hasMoved = true; } @@ -941,19 +1028,15 @@ void VVim::processMoveAction(QList &p_tokens) case Movement::Down: { - if (repeat == -1) { - repeat = 1; + if (p_repeat == -1) { + p_repeat = 1; } - int blockCount = m_editor->document()->blockCount(); - repeat = qMin(blockCount - 1 - cursor.block().blockNumber(), repeat); - - if (repeat > 0) { - cursor.movePosition(QTextCursor::NextBlock, moveMode, repeat); - if (positionInBlock > 0) { - setCursorPositionInBlock(cursor, positionInBlock, moveMode); - } + int blockCount = p_doc->blockCount(); + p_repeat = qMin(blockCount - 1 - p_cursor.block().blockNumber(), p_repeat); + if (p_repeat > 0) { + p_cursor.movePosition(QTextCursor::NextBlock, p_moveMode, p_repeat); hasMoved = true; } @@ -962,38 +1045,36 @@ void VVim::processMoveAction(QList &p_tokens) case Movement::VisualUp: { - if (repeat == -1) { - repeat = 1; + if (p_repeat == -1) { + p_repeat = 1; } - cursor.movePosition(QTextCursor::Up, moveMode, repeat); + p_cursor.movePosition(QTextCursor::Up, p_moveMode, p_repeat); hasMoved = true; break; } case Movement::VisualDown: { - if (repeat == -1) { - repeat = 1; + if (p_repeat == -1) { + p_repeat = 1; } - cursor.movePosition(QTextCursor::Down, moveMode, repeat); + p_cursor.movePosition(QTextCursor::Down, p_moveMode, p_repeat); hasMoved = true; break; } case Movement::PageUp: { - if (repeat == -1) { - repeat = 1; + if (p_repeat == -1) { + p_repeat = 1; } - int blockStep = blockCountOfPageStep() * repeat; - int block = cursor.block().blockNumber(); + int blockStep = blockCountOfPageStep() * p_repeat; + int block = p_cursor.block().blockNumber(); block = qMax(0, block - blockStep); - cursor.setPosition(doc->findBlockByNumber(block).position(), moveMode); - - setCursorPositionInBlock(cursor, positionInBlock, moveMode); + p_cursor.setPosition(p_doc->findBlockByNumber(block).position(), p_moveMode); hasMoved = true; break; @@ -1001,16 +1082,14 @@ void VVim::processMoveAction(QList &p_tokens) case Movement::PageDown: { - if (repeat == -1) { - repeat = 1; + if (p_repeat == -1) { + p_repeat = 1; } - int blockStep = blockCountOfPageStep() * repeat; - int block = cursor.block().blockNumber(); - block = qMin(block + blockStep, doc->blockCount() - 1); - cursor.setPosition(doc->findBlockByNumber(block).position(), moveMode); - - setCursorPositionInBlock(cursor, positionInBlock, moveMode); + 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); hasMoved = true; break; @@ -1018,18 +1097,16 @@ void VVim::processMoveAction(QList &p_tokens) case Movement::HalfPageUp: { - if (repeat == -1) { - repeat = 1; + if (p_repeat == -1) { + p_repeat = 1; } int blockStep = blockCountOfPageStep(); int halfBlockStep = qMax(blockStep / 2, 1); - blockStep = repeat * halfBlockStep; - int block = cursor.block().blockNumber(); + blockStep = p_repeat * halfBlockStep; + int block = p_cursor.block().blockNumber(); block = qMax(0, block - blockStep); - cursor.setPosition(doc->findBlockByNumber(block).position(), moveMode); - - setCursorPositionInBlock(cursor, positionInBlock, moveMode); + p_cursor.setPosition(p_doc->findBlockByNumber(block).position(), p_moveMode); hasMoved = true; break; @@ -1037,18 +1114,16 @@ void VVim::processMoveAction(QList &p_tokens) case Movement::HalfPageDown: { - if (repeat == -1) { - repeat = 1; + if (p_repeat == -1) { + p_repeat = 1; } int blockStep = blockCountOfPageStep(); int halfBlockStep = qMax(blockStep / 2, 1); - blockStep = repeat * halfBlockStep; - int block = cursor.block().blockNumber(); - block = qMin(block + blockStep, doc->blockCount() - 1); - cursor.setPosition(doc->findBlockByNumber(block).position(), moveMode); - - setCursorPositionInBlock(cursor, positionInBlock, moveMode); + 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); hasMoved = true; break; @@ -1056,55 +1131,58 @@ void VVim::processMoveAction(QList &p_tokens) case Movement::StartOfLine: { - Q_ASSERT(repeat == -1); + Q_ASSERT(p_repeat == -1); // Start of the Line (block). - cursor.movePosition(QTextCursor::StartOfBlock, moveMode, 1); - hasMoved = true; + if (!p_cursor.atBlockStart()) { + p_cursor.movePosition(QTextCursor::StartOfBlock, p_moveMode, 1); + hasMoved = true; + } + break; } case Movement::EndOfLine: { // End of line (block). - if (repeat == -1) { - repeat = 1; - } else if (repeat > 1) { - // Move down (repeat-1) blocks. - cursor.movePosition(QTextCursor::NextBlock, moveMode, repeat - 1); + if (p_repeat == -1) { + p_repeat = 1; + } else if (p_repeat > 1) { + // Move down (p_repeat-1) blocks. + p_cursor.movePosition(QTextCursor::NextBlock, p_moveMode, p_repeat - 1); } // Move to the end of block. - cursor.movePosition(QTextCursor::EndOfBlock, moveMode, 1); + p_cursor.movePosition(QTextCursor::EndOfBlock, p_moveMode, 1); hasMoved = true; break; } case Movement::FirstCharacter: { - // repeat is not considered in this command. + // p_repeat is not considered in this command. // If all the block is space, just move to the end of block; otherwise, // move to the first non-space character. - moveCursorFirstNonSpaceCharacter(cursor, moveMode); + moveCursorFirstNonSpaceCharacter(p_cursor, p_moveMode); hasMoved = true; break; } case Movement::LineJump: { - // Jump to the first non-space character of @repeat line (block). - V_ASSERT(repeat > 0); + // Jump to the first non-space character of @p_repeat line (block). + V_ASSERT(p_repeat > 0); - // @repeat starts from 1 while block number starts from 0. - QTextBlock block = doc->findBlockByNumber(repeat - 1); + // @p_repeat starts from 1 while block number starts from 0. + QTextBlock block = p_doc->findBlockByNumber(p_repeat - 1); if (block.isValid()) { - cursor.setPosition(block.position(), moveMode); + p_cursor.setPosition(block.position(), p_moveMode); } else { // Go beyond the document. - cursor.movePosition(QTextCursor::End, moveMode, 1); + p_cursor.movePosition(QTextCursor::End, p_moveMode, 1); } - moveCursorFirstNonSpaceCharacter(cursor, moveMode); + moveCursorFirstNonSpaceCharacter(p_cursor, p_moveMode); hasMoved = true; break; } @@ -1112,9 +1190,9 @@ void VVim::processMoveAction(QList &p_tokens) case Movement::StartOfDocument: { // Jump to the first non-space character of the start of the document. - V_ASSERT(repeat == -1); - cursor.movePosition(QTextCursor::Start, moveMode, 1); - moveCursorFirstNonSpaceCharacter(cursor, moveMode); + V_ASSERT(p_repeat == -1); + p_cursor.movePosition(QTextCursor::Start, p_moveMode, 1); + moveCursorFirstNonSpaceCharacter(p_cursor, p_moveMode); hasMoved = true; break; } @@ -1122,9 +1200,9 @@ void VVim::processMoveAction(QList &p_tokens) case Movement::EndOfDocument: { // Jump to the first non-space character of the end of the document. - V_ASSERT(repeat == -1); - cursor.movePosition(QTextCursor::End, moveMode, 1); - moveCursorFirstNonSpaceCharacter(cursor, moveMode); + V_ASSERT(p_repeat == -1); + p_cursor.movePosition(QTextCursor::End, p_moveMode, 1); + moveCursorFirstNonSpaceCharacter(p_cursor, p_moveMode); hasMoved = true; break; } @@ -1132,11 +1210,11 @@ void VVim::processMoveAction(QList &p_tokens) case Movement::WordForward: { // Go to the start of next word. - if (repeat == -1) { - repeat = 1; + if (p_repeat == -1) { + p_repeat = 1; } - cursor.movePosition(QTextCursor::NextWord, moveMode, repeat); + p_cursor.movePosition(QTextCursor::NextWord, p_moveMode, p_repeat); hasMoved = true; break; } @@ -1144,20 +1222,20 @@ void VVim::processMoveAction(QList &p_tokens) case Movement::WORDForward: { // Go to the start of next WORD. - if (repeat == -1) { - repeat = 1; + if (p_repeat == -1) { + p_repeat = 1; } - for (int i = 0; i < repeat; ++i) { + for (int i = 0; i < p_repeat; ++i) { int start, end; // [start, end] is current WORD. - findCurrentWORD(cursor, start, end); + findCurrentWORD(p_cursor, start, end); // Move cursor to end of current WORD. - cursor.setPosition(end, moveMode); + p_cursor.setPosition(end, p_moveMode); // Skip spaces. - moveCursorAcrossSpaces(cursor, moveMode, true); + moveCursorAcrossSpaces(p_cursor, p_moveMode, true); } hasMoved = true; @@ -1167,21 +1245,21 @@ void VVim::processMoveAction(QList &p_tokens) case Movement::ForwardEndOfWord: { // Go to the end of current word or next word. - if (repeat == -1) { - repeat = 1; + if (p_repeat == -1) { + p_repeat = 1; } - int pos = cursor.position(); + int pos = p_cursor.position(); // First move to the end of current word. - cursor.movePosition(QTextCursor::EndOfWord, moveMode, 1); - if (pos != cursor.position()) { + p_cursor.movePosition(QTextCursor::EndOfWord, p_moveMode, 1); + if (pos != p_cursor.position()) { // We did move. - repeat -= 1; + p_repeat -= 1; } - if (repeat) { - cursor.movePosition(QTextCursor::NextWord, moveMode, repeat); - cursor.movePosition(QTextCursor::EndOfWord, moveMode); + if (p_repeat) { + p_cursor.movePosition(QTextCursor::NextWord, p_moveMode, p_repeat); + p_cursor.movePosition(QTextCursor::EndOfWord, p_moveMode); } hasMoved = true; @@ -1191,20 +1269,20 @@ void VVim::processMoveAction(QList &p_tokens) case Movement::ForwardEndOfWORD: { // Go to the end of current WORD or next WORD. - if (repeat == -1) { - repeat = 1; + if (p_repeat == -1) { + p_repeat = 1; } - for (int i = 0; i < repeat; ++i) { + for (int i = 0; i < p_repeat; ++i) { // Skip spaces. - moveCursorAcrossSpaces(cursor, moveMode, true); + moveCursorAcrossSpaces(p_cursor, p_moveMode, true); int start, end; // [start, end] is current WORD. - findCurrentWORD(cursor, start, end); + findCurrentWORD(p_cursor, start, end); // Move cursor to the end of current WORD. - cursor.setPosition(end, moveMode); + p_cursor.setPosition(end, p_moveMode); } hasMoved = true; @@ -1214,20 +1292,20 @@ void VVim::processMoveAction(QList &p_tokens) case Movement::WordBackward: { // Go to the start of previous word or current word. - if (repeat == -1) { - repeat = 1; + if (p_repeat == -1) { + p_repeat = 1; } - int pos = cursor.position(); + int pos = p_cursor.position(); // first move to the start of current word. - cursor.movePosition(QTextCursor::StartOfWord, moveMode, 1); - if (pos != cursor.position()) { + p_cursor.movePosition(QTextCursor::StartOfWord, p_moveMode, 1); + if (pos != p_cursor.position()) { // We did move. - repeat -= 1; + p_repeat -= 1; } - if (repeat) { - cursor.movePosition(QTextCursor::PreviousWord, moveMode, repeat); + if (p_repeat) { + p_cursor.movePosition(QTextCursor::PreviousWord, p_moveMode, p_repeat); } hasMoved = true; @@ -1237,20 +1315,20 @@ void VVim::processMoveAction(QList &p_tokens) case Movement::WORDBackward: { // Go to the start of previous WORD or current WORD. - if (repeat == -1) { - repeat = 1; + if (p_repeat == -1) { + p_repeat = 1; } - for (int i = 0; i < repeat; ++i) { + for (int i = 0; i < p_repeat; ++i) { // Skip Spaces. - moveCursorAcrossSpaces(cursor, moveMode, false); + moveCursorAcrossSpaces(p_cursor, p_moveMode, false); int start, end; // [start, end] is current WORD. - findCurrentWORD(cursor, start, end); + findCurrentWORD(p_cursor, start, end); // Move cursor to the start of current WORD. - cursor.setPosition(start, moveMode); + p_cursor.setPosition(start, p_moveMode); } hasMoved = true; @@ -1260,17 +1338,17 @@ void VVim::processMoveAction(QList &p_tokens) case Movement::BackwardEndOfWord: { // Go to the end of previous word. - if (repeat == -1) { - repeat = 1; + if (p_repeat == -1) { + p_repeat = 1; } - int pib = cursor.positionInBlock(); - if (!(pib > 0 && cursor.block().text()[pib -1].isSpace())) { - ++repeat; + int pib = p_cursor.positionInBlock(); + if (!(pib > 0 && p_cursor.block().text()[pib -1].isSpace())) { + ++p_repeat; } - cursor.movePosition(QTextCursor::PreviousWord, moveMode, repeat); - cursor.movePosition(QTextCursor::EndOfWord, moveMode, 1); + p_cursor.movePosition(QTextCursor::PreviousWord, p_moveMode, p_repeat); + p_cursor.movePosition(QTextCursor::EndOfWord, p_moveMode, 1); hasMoved = true; break; } @@ -1278,17 +1356,17 @@ void VVim::processMoveAction(QList &p_tokens) case Movement::BackwardEndOfWORD: { // Go to the end of previous WORD. - if (repeat == -1) { - repeat = 1; + if (p_repeat == -1) { + p_repeat = 1; } - for (int i = 0; i < repeat; ++i) { + for (int i = 0; i < p_repeat; ++i) { int start, end; - findCurrentWORD(cursor, start, end); + findCurrentWORD(p_cursor, start, end); - cursor.setPosition(start, moveMode); + p_cursor.setPosition(start, p_moveMode); - moveCursorAcrossSpaces(cursor, moveMode, false); + moveCursorAcrossSpaces(p_cursor, p_moveMode, false); } hasMoved = true; @@ -1299,8 +1377,238 @@ void VVim::processMoveAction(QList &p_tokens) break; } + return hasMoved; +} + +void VVim::processDeleteAction(QList &p_tokens) +{ + Token to = p_tokens.takeFirst(); + int repeat = -1; + if (to.isRepeat()) { + repeat = to.m_repeat; + to = p_tokens.takeFirst(); + } + + if ((!to.isMovement() && !to.isRange()) || !p_tokens.isEmpty()) { + return; + } + + QTextCursor cursor = m_editor->textCursor(); + QTextDocument *doc = m_editor->document(); + bool hasMoved = false; + QTextCursor::MoveMode moveMode = QTextCursor::KeepAnchor; + + if (to.isRange()) { + switch (to.m_range) { + case Range::Line: + { + // dd, Delete current line. + if (repeat == -1) { + repeat = 1; + } + + QString deletedText; + + cursor.beginEditBlock(); + for (int i = 0; i < repeat; ++i) { + QString tmp; + int blockNum = cursor.block().blockNumber(); + VEditUtils::removeBlock(cursor, &tmp); + deletedText += tmp; + if (blockNum > cursor.block().blockNumber()) { + // The last block. + break; + } + } + + cursor.endEditBlock(); + + saveToRegister(deletedText); + + hasMoved = true; + + break; + } + + case Range::Word: + break; + + default: + return; + } + + goto exit; + } + + V_ASSERT(to.isMovement()); + + // Filter out not supported movement for DELETE action. + switch (to.m_movement) { + case Movement::PageUp: + case Movement::PageDown: + case Movement::HalfPageUp: + case Movement::HalfPageDown: + return; + + default: + break; + } + + cursor.beginEditBlock(); + hasMoved = processMovement(cursor, doc, moveMode, to.m_movement, repeat); + if (repeat == -1) { + repeat = 1; + } + + if (hasMoved) { + bool clearEmptyBlock = false; + switch (to.m_movement) { + case Movement::Left: + { + qDebug() << "delete backward" << repeat << "chars"; + break; + } + + case Movement::Right: + { + qDebug() << "delete forward" << repeat << "chars"; + break; + } + + case Movement::Up: + { + expandSelectionToWholeLines(cursor); + clearEmptyBlock = true; + qDebug() << "delete up" << repeat << "lines"; + break; + } + + case Movement::Down: + { + expandSelectionToWholeLines(cursor); + clearEmptyBlock = true; + qDebug() << "delete down" << repeat << "lines"; + break; + } + + case Movement::VisualUp: + { + qDebug() << "delete visual up" << repeat << "lines"; + break; + } + + case Movement::VisualDown: + { + qDebug() << "delete visual down" << repeat << "lines"; + break; + } + + case Movement::StartOfLine: + { + qDebug() << "delete till start of line"; + break; + } + + case Movement::EndOfLine: + { + // End of line (block). + if (repeat > 1) { + clearEmptyBlock = true; + } + + qDebug() << "delete till end of" << repeat << "line"; + break; + } + + case Movement::FirstCharacter: + { + qDebug() << "delete till first non-space character"; + break; + } + + case Movement::LineJump: + { + expandSelectionToWholeLines(cursor); + clearEmptyBlock = true; + qDebug() << "delete till line" << repeat; + break; + } + + case Movement::StartOfDocument: + { + expandSelectionToWholeLines(cursor); + clearEmptyBlock = true; + qDebug() << "delete till start of document"; + break; + } + + case Movement::EndOfDocument: + { + expandSelectionToWholeLines(cursor); + clearEmptyBlock = true; + qDebug() << "delete till end of document"; + break; + } + + case Movement::WordForward: + { + qDebug() << "delete" << repeat << "words forward"; + break; + } + + case Movement::WORDForward: + { + qDebug() << "delete" << repeat << "WORDs forward"; + break; + } + + case Movement::ForwardEndOfWord: + { + qDebug() << "delete" << repeat << "end of words forward"; + break; + } + + case Movement::ForwardEndOfWORD: + { + qDebug() << "delete" << repeat << "end of WORDs forward"; + break; + } + + case Movement::WordBackward: + { + qDebug() << "delete" << repeat << "words backward"; + break; + } + + case Movement::WORDBackward: + { + qDebug() << "delete" << repeat << "WORDs backward"; + break; + } + + case Movement::BackwardEndOfWord: + { + qDebug() << "delete" << repeat << "end of words backward"; + break; + } + + case Movement::BackwardEndOfWORD: + { + qDebug() << "delete" << repeat << "end of WORDs backward"; + break; + } + + default: + break; + } + + deleteSelectedText(cursor, clearEmptyBlock); + } + + cursor.endEditBlock(); + +exit: if (hasMoved) { - expandSelectionInVisualLineMode(cursor); m_editor->setTextCursor(cursor); } } @@ -1334,12 +1642,8 @@ void VVim::selectionToVisualMode(bool p_hasText) } } -void VVim::expandSelectionInVisualLineMode(QTextCursor &p_cursor) +void VVim::expandSelectionToWholeLines(QTextCursor &p_cursor) { - if (m_mode != VimMode::VisualLine) { - return; - } - QTextDocument *doc = m_editor->document(); int curPos = p_cursor.position(); int anchorPos = p_cursor.anchor(); @@ -1422,9 +1726,81 @@ bool VVim::hasActionToken() const return false; } -void VVim::tryInsertMoveAction() +void VVim::tryAddMoveAction() { if (!hasActionToken()) { - m_tokens.prepend(Token(Action::Move)); + addActionToken(Action::Move); + } +} + +void VVim::addActionToken(Action p_action) +{ + V_ASSERT(!hasActionToken()); + m_tokens.prepend(Token(p_action)); +} + +const VVim::Token *VVim::getActionToken() const +{ + V_ASSERT(hasActionToken()); + + for (auto const &token : m_tokens) { + if (token.isAction()) { + return &token; + } + } + + return NULL; +} + +void VVim::addRangeToken(Range p_range) +{ + m_tokens.append(Token(p_range)); +} + +void VVim::addMovementToken(Movement p_movement) +{ + m_tokens.append(Token(p_movement)); +} + +void VVim::deleteSelectedText(bool p_clearEmptyBlock) +{ + QTextCursor cursor = m_editor->textCursor(); + if (cursor.hasSelection()) { + cursor.beginEditBlock(); + deleteSelectedText(cursor, p_clearEmptyBlock); + cursor.endEditBlock(); + m_editor->setTextCursor(cursor); + } +} + +void VVim::deleteSelectedText(QTextCursor &p_cursor, bool p_clearEmptyBlock) +{ + if (p_cursor.hasSelection()) { + QString deletedText = p_cursor.selectedText(); + p_cursor.removeSelectedText(); + if (p_clearEmptyBlock && p_cursor.block().length() == 1) { + deletedText += "\n"; + VEditUtils::removeBlock(p_cursor); + } + + saveToRegister(deletedText); + } +} + +void VVim::saveToRegister(const QString &p_text) +{ + qDebug() << QString("save text(%1) to register(%2)").arg(p_text).arg(m_register); + + Register ® = m_registers[m_register]; + if (reg.isNamedRegister() && reg.m_append) { + // Append to current register. + reg.m_value += p_text; + } else { + reg.m_value = p_text; + } + + if (!reg.isBlackHoleRegister() && !reg.isUnnamedRegister()) { + // Save it to unnamed register. + m_registers[c_unnamedRegister].m_value = reg.m_value; } } diff --git a/src/utils/vvim.h b/src/utils/vvim.h index d71f6516..da9b5a47 100644 --- a/src/utils/vvim.h +++ b/src/utils/vvim.h @@ -240,6 +240,23 @@ private: { } + // Register a-z. + bool isNamedRegister() const + { + char ch = m_name.toLatin1(); + return ch >= 'a' && ch <= 'z'; + } + + bool isUnnamedRegister() const + { + return m_name == c_unnamedRegister; + } + + bool isBlackHoleRegister() const + { + return m_name == c_blackHoleRegister; + } + QChar m_name; QString m_value; @@ -267,6 +284,9 @@ private: // @p_tokens is the arguments of the Action::Move action. void processMoveAction(QList &p_tokens); + // @p_tokens is the arguments of the Action::Delete action. + void processDeleteAction(QList &p_tokens); + // Clear selection if there is any. // Returns true if there is selection. bool clearSelection(); @@ -274,9 +294,9 @@ private: // Get the block count of one page step in vertical scroll bar. int blockCountOfPageStep() const; - // Expand selection in the VisualLiine mode which will change the position + // Expand selection to whole lines which will change the position // of @p_cursor. - void expandSelectionInVisualLineMode(QTextCursor &p_cursor); + void expandSelectionToWholeLines(QTextCursor &p_cursor); // Init m_registers. // Currently supported registers: @@ -293,9 +313,36 @@ private: // Check if @m_tokens contains an action token. bool hasActionToken() const; - // Try to insert a Action::Move action at the front if there is no any action + // Try to add an Action::Move action at the front if there is no any action // token. - void tryInsertMoveAction(); + void tryAddMoveAction(); + + // Add an Action token in front of m_tokens. + void addActionToken(Action p_action); + + // Get the action token from m_tokens. + const Token *getActionToken() const; + + // Add an Range token at the end of m_tokens. + void addRangeToken(Range p_range); + + // Add an Movement token at the end of m_tokens. + void addMovementToken(Movement p_movement); + + // Delete selected text if there is any. + // @p_clearEmptyBlock: whether to remove the empty block after deletion. + void deleteSelectedText(bool p_clearEmptyBlock); + + void deleteSelectedText(QTextCursor &p_cursor, bool p_clearEmptyBlock); + + // Save @p_text to the Register pointed by m_register. + void saveToRegister(const QString &p_text); + + // Move @p_cursor according to @p_moveMode and @p_movement. + // 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); VEdit *m_editor; const VEditConfig *m_editConfig; diff --git a/src/vimagepreviewer.cpp b/src/vimagepreviewer.cpp index 6aa1b618..31666acf 100644 --- a/src/vimagepreviewer.cpp +++ b/src/vimagepreviewer.cpp @@ -8,6 +8,7 @@ #include "vmdedit.h" #include "vconfigmanager.h" #include "utils/vutils.h" +#include "utils/veditutils.h" #include "vfile.h" #include "vdownloader.h" #include "hgmarkdownhighlighter.h" @@ -302,9 +303,7 @@ void VImagePreviewer::removeBlock(QTextBlock &p_block) { bool modified = m_edit->isModified(); - QTextCursor cursor(p_block); - cursor.select(QTextCursor::BlockUnderCursor); - cursor.removeSelectedText(); + VEditUtils::removeBlock(p_block); m_edit->setModified(modified); } diff --git a/src/vmdedit.cpp b/src/vmdedit.cpp index ab174064..80295259 100644 --- a/src/vmdedit.cpp +++ b/src/vmdedit.cpp @@ -31,9 +31,14 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type, connect(m_mdHighlighter, &HGMarkdownHighlighter::highlightCompleted, this, [this]() { QRect rect = this->cursorRect(); - QRect viewRect = this->rect(); - if ((rect.y() < viewRect.height() - && rect.y() + rect.height() > viewRect.height()) + int height = this->rect().height(); + QScrollBar *sbar = this->horizontalScrollBar(); + if (sbar && sbar->isVisible()) { + height -= sbar->height(); + } + + if ((rect.y() < height + && rect.y() + rect.height() > height) || (rect.y() < 0 && rect.y() + rect.height() > 0)) { this->ensureCursorVisible(); }