From a8c76d6742cd17a97747ed99c08cdefdfcf84a74 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Thu, 8 Jun 2017 08:15:30 +0800 Subject: [PATCH] support normal Vim mode 1. Support Insert/Normal/Visual/VisualLine modes: - `V`, `v`, `I`, `i`, `A`, `a`, `s`, `Esc`, `Ctrl+[`, `o`, `O`; 2. Support movement commands (with Repeat support): - `h`, `j`, `k`, `l`, `gj`, `gk`; - `gg`, `G`; - `^`, `0`, `$`; - `Ctrl+U`, `Ctrl+D`, `PageUp`, `PageDown`, `Ctrl+B`; --- src/resources/styles/default.mdhl | 5 +- src/resources/vnote.ini | 3 + src/src.pro | 6 +- src/utils/vvim.cpp | 1010 +++++++++++++++++++++++++++++ src/utils/vvim.h | 243 +++++++ src/vconfigmanager.cpp | 41 +- src/vconfigmanager.h | 78 ++- src/vedit.cpp | 44 +- src/vedit.h | 39 +- src/veditoperations.cpp | 60 +- src/veditoperations.h | 26 +- src/vmainwindow.cpp | 15 + src/vmainwindow.h | 1 + src/vmdedit.cpp | 20 +- src/vmdedit.h | 1 - src/vmdeditoperations.cpp | 707 ++++---------------- src/vmdeditoperations.h | 11 - 17 files changed, 1643 insertions(+), 667 deletions(-) create mode 100644 src/utils/vvim.cpp create mode 100644 src/utils/vvim.h diff --git a/src/resources/styles/default.mdhl b/src/resources/styles/default.mdhl index 0ee7db3d..7d2ae18b 100644 --- a/src/resources/styles/default.mdhl +++ b/src/resources/styles/default.mdhl @@ -14,7 +14,10 @@ background: 005fff editor-current-line background: c5cae9 -vim-background: a5d6a7 +vim-insert-background: c5cae9 +vim-normal-background: a5d6a7 +vim-visual-background: a5d6a7 +vim-replace-background: a5d6a7 H1 foreground: 111111 diff --git a/src/resources/vnote.ini b/src/resources/vnote.ini index 8fed963a..db0705f5 100644 --- a/src/resources/vnote.ini +++ b/src/resources/vnote.ini @@ -45,6 +45,9 @@ image_folder=_v_images ; Enable trailing space highlight enable_trailing_space_highlight=true +; Enable Vim mode in edit mode +enable_vim_mode=false + [session] tools_dock_checked=true diff --git a/src/src.pro b/src/src.pro index c98d65a5..502647c7 100644 --- a/src/src.pro +++ b/src/src.pro @@ -64,7 +64,8 @@ SOURCES += main.cpp\ vimagepreviewer.cpp \ vexporter.cpp \ vmdtab.cpp \ - vhtmltab.cpp + vhtmltab.cpp \ + utils/vvim.cpp HEADERS += vmainwindow.h \ vdirectorytree.h \ @@ -115,7 +116,8 @@ HEADERS += vmainwindow.h \ vimagepreviewer.h \ vexporter.h \ vmdtab.h \ - vhtmltab.h + vhtmltab.h \ + utils/vvim.h RESOURCES += \ vnote.qrc \ diff --git a/src/utils/vvim.cpp b/src/utils/vvim.cpp new file mode 100644 index 00000000..af6e506f --- /dev/null +++ b/src/utils/vvim.cpp @@ -0,0 +1,1010 @@ +#include "vvim.h" +#include +#include +#include +#include +#include +#include +#include "vedit.h" + +VVim::VVim(VEdit *p_editor) + : QObject(p_editor), m_editor(p_editor), + m_editConfig(&p_editor->getConfig()), m_mode(VimMode::Normal), + m_resetPositionInBlock(true) +{ + connect(m_editor, &VEdit::copyAvailable, + this, &VVim::selectionToVisualMode); +} + +// Set @p_cursor's position specified by @p_positionInBlock. +// If @p_positionInBlock is bigger than the block's length, move to the end of block. +// Need to setTextCursor() after calling this. +static void setCursorPositionInBlock(QTextCursor &p_cursor, int p_positionInBlock, + QTextCursor::MoveMode p_mode) +{ + QTextBlock block = p_cursor.block(); + if (block.length() > p_positionInBlock) { + p_cursor.setPosition(block.position() + p_positionInBlock, p_mode); + } else { + p_cursor.movePosition(QTextCursor::EndOfBlock, p_mode, 1); + } +} + +// Move @p_cursor to the first non-space character of current block. +// Need to setTextCursor() after calling this. +static void moveCursorFirstNonSpaceCharacter(QTextCursor &p_cursor, + QTextCursor::MoveMode p_mode) +{ + QTextBlock block = p_cursor.block(); + QString text = block.text(); + int idx = 0; + for (; idx < text.size(); ++idx) { + if (text[idx].isSpace()) { + continue; + } else { + break; + } + } + + p_cursor.setPosition(block.position() + idx, p_mode); +} + +bool VVim::handleKeyPressEvent(QKeyEvent *p_event) +{ + bool ret = false; + int modifiers = p_event->modifiers(); + int key = p_event->key(); + bool resetPositionInBlock = true; + Key keyInfo(key, modifiers); + + // Handle Insert mode key press. + if (VimMode::Insert == m_mode) { + if (key == Qt::Key_Escape + || (key == Qt::Key_BracketLeft && modifiers == Qt::ControlModifier)) { + // Clear selection and enter Normal mode. + clearSelection(); + + setMode(VimMode::Normal); + goto clear_accept; + } + + // Let it be handled outside VVim. + goto exit; + } + + // 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) { + // Ctrl and Shift may be sent out first. + case Qt::Key_Control: + // Fall through. + case Qt::Key_Shift: + { + goto accept; + } + + case Qt::Key_0: + { + if (modifiers == Qt::NoModifier) { + if (!m_keys.isEmpty()) { + // Repeat. + V_ASSERT(m_keys.last().isDigit()); + + m_keys.append(keyInfo); + resetPositionInBlock = false; + goto accept; + } else { + // StartOfLine. + if (m_tokens.isEmpty()) { + // Move. + m_tokens.append(Token(Action::Move)); + } + + m_tokens.append(Token(Movement::StartOfLine)); + + processCommand(m_tokens); + } + } + + break; + } + + case Qt::Key_1: + case Qt::Key_2: + case Qt::Key_3: + case Qt::Key_4: + case Qt::Key_5: + case Qt::Key_6: + case Qt::Key_7: + case Qt::Key_8: + case Qt::Key_9: + { + if (modifiers == Qt::NoModifier) { + if (!m_keys.isEmpty() && numberFromKeySequence(m_keys) == -1) { + // Invalid sequence. + break; + } + + m_keys.append(keyInfo); + resetPositionInBlock = false; + goto accept; + } + + break; + } + + case Qt::Key_Left: + case Qt::Key_Down: + case Qt::Key_Up: + case Qt::Key_Right: + case Qt::Key_H: + case Qt::Key_J: + case Qt::Key_K: + case Qt::Key_L: + { + if (modifiers == Qt::NoModifier) { + // Check if we could generate a Repeat token. + tryGetRepeatToken(m_keys, m_tokens); + + // Generate a Movement token. + Movement mm = Movement::Invalid; + + if (!m_keys.isEmpty()) { + // gj, gk. + Key gKey(Qt::Key_G); + if (m_keys.size() == 1 && m_keys.at(0) == gKey) { + if (key == Qt::Key_J) { + mm = Movement::VisualDown; + } else if (key == Qt::Key_K) { + mm = Movement::VisualUp; + } else { + break; + } + } else { + // Not a valid sequence. + break; + } + } else { + // h, j, k, l. + switch (key) { + case Qt::Key_H: + case Qt::Key_Left: + mm = Movement::Left; + break; + + case Qt::Key_L: + case Qt::Key_Right: + mm = Movement::Right; + break; + + case Qt::Key_J: + case Qt::Key_Down: + mm = Movement::Down; + break; + + case Qt::Key_K: + case Qt::Key_Up: + mm = Movement::Up; + break; + + default: + V_ASSERT(false); + } + } + + V_ASSERT(mm != Movement::Invalid); + if (m_tokens.isEmpty()) { + // Move. + m_tokens.append(Token(Action::Move)); + } + + m_tokens.append(Token(mm)); + processCommand(m_tokens); + resetPositionInBlock = false; + } + + break; + } + + case Qt::Key_I: + { + if (modifiers == Qt::NoModifier) { + // Enter Insert mode. + if (m_mode == VimMode::Normal) { + setMode(VimMode::Insert); + } + } else if (modifiers == Qt::ShiftModifier) { + QTextCursor cursor = m_editor->textCursor(); + if (m_mode == VimMode::Normal) { + // Insert at the first non-space character. + moveCursorFirstNonSpaceCharacter(cursor, QTextCursor::MoveAnchor); + } else if (m_mode == VimMode::Visual || m_mode == VimMode::VisualLine) { + // Insert at the start of line. + cursor.movePosition(QTextCursor::StartOfBlock, + QTextCursor::MoveAnchor, + 1); + } + + m_editor->setTextCursor(cursor); + setMode(VimMode::Insert); + } + + break; + } + + case Qt::Key_A: + { + if (modifiers == Qt::NoModifier) { + // Enter Insert mode. + // Move cursor back one character. + if (m_mode == VimMode::Normal) { + QTextCursor cursor = m_editor->textCursor(); + V_ASSERT(!cursor.hasSelection()); + + if (!cursor.atBlockEnd()) { + cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 1); + m_editor->setTextCursor(cursor); + } + + setMode(VimMode::Insert); + } + } else if (modifiers == Qt::ShiftModifier) { + // Insert at the end of line. + QTextCursor cursor = m_editor->textCursor(); + if (m_mode == VimMode::Normal) { + cursor.movePosition(QTextCursor::EndOfBlock, + QTextCursor::MoveAnchor, + 1); + m_editor->setTextCursor(cursor); + } else if (m_mode == VimMode::Visual || m_mode == VimMode::VisualLine) { + if (!cursor.atBlockEnd()) { + cursor.clearSelection(); + cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 1); + m_editor->setTextCursor(cursor); + } + } + + setMode(VimMode::Insert); + } + + break; + } + + case Qt::Key_O: + { + if (modifiers == Qt::NoModifier) { + // Insert a new block under current block and enter insert mode. + if (m_mode == VimMode::Normal) { + QTextCursor cursor = m_editor->textCursor(); + cursor.movePosition(QTextCursor::EndOfBlock, + QTextCursor::MoveAnchor, + 1); + cursor.insertBlock(); + m_editor->setTextCursor(cursor); + setMode(VimMode::Insert); + } + } else if (modifiers == Qt::ShiftModifier) { + // Insert a new block above current block and enter insert mode. + if (m_mode == VimMode::Normal) { + QTextCursor cursor = m_editor->textCursor(); + cursor.movePosition(QTextCursor::StartOfBlock, + QTextCursor::MoveAnchor, + 1); + cursor.insertBlock(); + cursor.movePosition(QTextCursor::PreviousBlock, + QTextCursor::MoveAnchor, + 1); + m_editor->setTextCursor(cursor); + setMode(VimMode::Insert); + } + } + + break; + } + + case Qt::Key_S: + { + if (modifiers == Qt::NoModifier) { + // 1. If there is selection, delete the selected text. + // 2. Otherwise, if cursor is not at the end of block, delete the + // character after current cursor. + // 3. Enter Insert mode. + QTextCursor cursor = m_editor->textCursor(); + if (cursor.hasSelection() || !cursor.atBlockEnd()) { + cursor.deleteChar(); + m_editor->setTextCursor(cursor); + } + + setMode(VimMode::Insert); + } + + break; + } + + case Qt::Key_Dollar: + { + if (modifiers == Qt::ShiftModifier) { + // $, move to end of line. + tryGetRepeatToken(m_keys, m_tokens); + + if (m_keys.isEmpty()) { + if (m_tokens.isEmpty()) { + // Move. + m_tokens.append(Token(Action::Move)); + } + + m_tokens.append(Token(Movement::EndOfLine)); + processCommand(m_tokens); + } + } + + break; + } + + case Qt::Key_G: + { + Movement mm = Movement::Invalid; + if (modifiers == Qt::NoModifier) { + tryGetRepeatToken(m_keys, m_tokens); + if (m_keys.isEmpty()) { + // First g, pend it. + m_keys.append(keyInfo); + goto accept; + } else if (m_keys.size() == 1 && m_keys.at(0) == keyInfo) { + // gg, go to a certain line or first line. + if (!m_tokens.isEmpty() && m_tokens.last().isRepeat()) { + mm = Movement::LineJump; + } else { + mm = Movement::StartOfDocument; + } + } + } else if (modifiers == Qt::ShiftModifier) { + tryGetRepeatToken(m_keys, m_tokens); + if (m_keys.isEmpty()) { + // G, go to a certain line or the last line. + if (!m_tokens.isEmpty() && m_tokens.last().isRepeat()) { + mm = Movement::LineJump; + } else { + mm = Movement::EndOfDocument; + } + } + } + + if (mm != Movement::Invalid) { + if (m_tokens.isEmpty()) { + // Move. + m_tokens.append(Token(Action::Move)); + } + + m_tokens.append(Token(mm)); + processCommand(m_tokens); + } + + break; + } + + // Should be kept together with Qt::Key_PageUp. + case Qt::Key_B: + { + if (modifiers == Qt::ControlModifier) { + // Ctrl+B, page up, fall through. + modifiers = Qt::NoModifier; + } else { + break; + } + } + + case Qt::Key_PageUp: + { + if (modifiers == Qt::NoModifier) { + tryGetRepeatToken(m_keys, m_tokens); + if (!m_keys.isEmpty()) { + // Not a valid sequence. + break; + } + + Movement mm = Movement::PageUp; + if (m_tokens.isEmpty()) { + // Move. + m_tokens.append(Token(Action::Move)); + } + + m_tokens.append(Token(mm)); + processCommand(m_tokens); + resetPositionInBlock = false; + } + + break; + } + + case Qt::Key_U: + { + if (modifiers == Qt::ControlModifier) { + // Ctrl+U, HalfPageUp. + tryGetRepeatToken(m_keys, m_tokens); + if (!m_keys.isEmpty()) { + // Not a valid sequence. + break; + } + + Movement mm = Movement::HalfPageUp; + if (m_tokens.isEmpty()) { + // Move. + m_tokens.append(Token(Action::Move)); + } + + m_tokens.append(Token(mm)); + processCommand(m_tokens); + resetPositionInBlock = false; + } + + break; + } + + // Ctrl+F is used for Find dialog, not used here. + case Qt::Key_PageDown: + { + if (modifiers == Qt::NoModifier) { + tryGetRepeatToken(m_keys, m_tokens); + if (!m_keys.isEmpty()) { + // Not a valid sequence. + break; + } + + Movement mm = Movement::PageDown; + if (m_tokens.isEmpty()) { + // Move. + m_tokens.append(Token(Action::Move)); + } + + m_tokens.append(Token(mm)); + processCommand(m_tokens); + resetPositionInBlock = false; + } + + break; + } + + case Qt::Key_D: + { + if (modifiers == Qt::ControlModifier) { + // Ctrl+D, HalfPageDown. + tryGetRepeatToken(m_keys, m_tokens); + if (!m_keys.isEmpty()) { + // Not a valid sequence. + break; + } + + Movement mm = Movement::HalfPageDown; + if (m_tokens.isEmpty()) { + // Move. + m_tokens.append(Token(Action::Move)); + } + + m_tokens.append(Token(mm)); + processCommand(m_tokens); + resetPositionInBlock = false; + } + + break; + } + + // Should be kept together with Qt::Key_Escape. + case Qt::Key_BracketLeft: + { + if (modifiers == Qt::ControlModifier) { + // fallthrough. + } else { + break; + } + + } + + case Qt::Key_Escape: + { + // Clear selection and enter normal mode. + clearSelection(); + + setMode(VimMode::Normal); + break; + } + + case Qt::Key_V: + { + if (modifiers == Qt::NoModifier) { + // Toggle Visual Mode. + clearSelection(); + VimMode mode = VimMode::Visual; + if (m_mode == VimMode::Visual) { + mode = VimMode::Normal; + } + + setMode(mode); + } else if (modifiers == Qt::ShiftModifier) { + // Visual Line Mode. + clearSelection(); + VimMode mode = VimMode::VisualLine; + if (m_mode == VimMode::VisualLine) { + mode = VimMode::Normal; + } + + setMode(mode); + + if (m_mode == VimMode::VisualLine) { + QTextCursor cursor = m_editor->textCursor(); + expandSelectionInVisualLineMode(cursor); + m_editor->setTextCursor(cursor); + } + } + + break; + } + + case Qt::Key_AsciiCircum: + { + if (modifiers == Qt::ShiftModifier) { + // ~, go to first non-space character of current line (block). + tryGetRepeatToken(m_keys, m_tokens); + if (!m_keys.isEmpty()) { + // Not a valid sequence. + break; + } + + Movement mm = Movement::FirstCharacter; + if (m_tokens.isEmpty()) { + // Move. + m_tokens.append(Token(Action::Move)); + } + + m_tokens.append(Token(mm)); + processCommand(m_tokens); + } + + break; + } + + default: + break; + } + +clear_accept: + m_keys.clear(); + m_tokens.clear(); + +accept: + p_event->accept(); + ret = true; + +exit: + m_resetPositionInBlock = resetPositionInBlock; + return ret; +} + +void VVim::resetState() +{ + m_keys.clear(); + m_tokens.clear(); + m_resetPositionInBlock = true; +} + +VimMode VVim::getMode() const +{ + return m_mode; +} + +void VVim::setMode(VimMode p_mode) +{ + if (m_mode != p_mode) { + m_mode = p_mode; + resetState(); + + emit modeChanged(m_mode); + } +} + +void VVim::processCommand(QList &p_tokens) +{ + if (p_tokens.isEmpty()) { + return; + } + + 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(); + } + + Token act = p_tokens.takeFirst(); + switch (act.m_action) { + case Action::Move: + processMoveAction(p_tokens); + break; + + default: + p_tokens.clear(); + break; + } + + Q_ASSERT(p_tokens.isEmpty()); +} + +int VVim::numberFromKeySequence(const QList &p_keys) +{ + int num = 0; + + for (auto const & key : p_keys) { + if (key.isDigit()) { + num = num * 10 + key.toDigit(); + } else { + return -1; + } + } + + return num == 0 ? -1 : num; +} + +bool VVim::tryGetRepeatToken(QList &p_keys, QList &p_tokens) +{ + if (!p_keys.isEmpty()) { + int repeat = numberFromKeySequence(p_keys); + if (repeat != -1) { + if (p_tokens.isEmpty()) { + // Move. + p_tokens.append(Token(Action::Move)); + } + + p_tokens.append(Token(repeat)); + p_keys.clear(); + + return true; + } + } + + return false; +} + +void VVim::processMoveAction(QList &p_tokens) +{ + // Only moving left/right could change this. + static int positionInBlock = 0; + + Token to = p_tokens.takeFirst(); + V_ASSERT(to.isRepeat() || to.isMovement()); + Token mvToken; + int repeat = -1; + if (to.isRepeat()) { + repeat = to.m_repeat; + mvToken = p_tokens.takeFirst(); + } else { + mvToken = to; + } + + 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; + } + + int pib = cursor.positionInBlock(); + repeat = qMin(pib, repeat); + + if (repeat > 0) { + cursor.movePosition(QTextCursor::Left, moveMode, repeat); + positionInBlock = cursor.positionInBlock(); + hasMoved = true; + } + + break; + } + + case Movement::Right: + { + if (repeat == -1) { + repeat = 1; + } + + int pib = cursor.positionInBlock(); + int length = cursor.block().length(); + if (length - pib <= repeat) { + repeat = length - pib - 1; + } + + if (repeat > 0) { + cursor.movePosition(QTextCursor::Right, moveMode, repeat); + positionInBlock = cursor.positionInBlock(); + hasMoved = true; + } + + break; + } + + case Movement::Up: + { + if (repeat == -1) { + repeat = 1; + } + + repeat = qMin(cursor.block().blockNumber(), repeat); + + if (repeat > 0) { + cursor.movePosition(QTextCursor::PreviousBlock, moveMode, repeat); + if (positionInBlock > 0) { + setCursorPositionInBlock(cursor, positionInBlock, moveMode); + } + + hasMoved = true; + } + + break; + } + + case Movement::Down: + { + if (repeat == -1) { + 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); + } + + hasMoved = true; + } + + break; + } + + case Movement::VisualUp: + { + if (repeat == -1) { + repeat = 1; + } + + cursor.movePosition(QTextCursor::Up, moveMode, repeat); + hasMoved = true; + break; + } + + case Movement::VisualDown: + { + if (repeat == -1) { + repeat = 1; + } + + cursor.movePosition(QTextCursor::Down, moveMode, repeat); + hasMoved = true; + break; + } + + case Movement::PageUp: + { + if (repeat == -1) { + repeat = 1; + } + + int blockStep = blockCountOfPageStep() * repeat; + int block = cursor.block().blockNumber(); + block = qMax(0, block - blockStep); + cursor.setPosition(doc->findBlockByNumber(block).position(), moveMode); + + setCursorPositionInBlock(cursor, positionInBlock, moveMode); + hasMoved = true; + + break; + } + + case Movement::PageDown: + { + if (repeat == -1) { + 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); + hasMoved = true; + + break; + } + + case Movement::HalfPageUp: + { + if (repeat == -1) { + repeat = 1; + } + + int blockStep = blockCountOfPageStep(); + int halfBlockStep = qMax(blockStep / 2, 1); + blockStep = repeat * halfBlockStep; + int block = cursor.block().blockNumber(); + block = qMax(0, block - blockStep); + cursor.setPosition(doc->findBlockByNumber(block).position(), moveMode); + + setCursorPositionInBlock(cursor, positionInBlock, moveMode); + hasMoved = true; + + break; + } + + case Movement::HalfPageDown: + { + if (repeat == -1) { + 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); + hasMoved = true; + + break; + } + + case Movement::StartOfLine: + { + Q_ASSERT(repeat == -1); + + // Start of the Line (block). + cursor.movePosition(QTextCursor::StartOfBlock, 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); + } + + // Move to the end of block. + cursor.movePosition(QTextCursor::EndOfBlock, moveMode, 1); + hasMoved = true; + break; + } + + case Movement::FirstCharacter: + { + // 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); + hasMoved = true; + break; + } + + case Movement::LineJump: + { + // Jump to the first non-space character of @repeat line (block). + V_ASSERT(repeat > 0); + + // @repeat starts from 1 while block number starts from 0. + QTextBlock block = doc->findBlockByNumber(repeat - 1); + if (block.isValid()) { + cursor.setPosition(block.position(), moveMode); + } else { + // Go beyond the document. + cursor.movePosition(QTextCursor::End, moveMode, 1); + } + + moveCursorFirstNonSpaceCharacter(cursor, moveMode); + hasMoved = true; + break; + } + + 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); + hasMoved = true; + break; + } + + 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); + hasMoved = true; + break; + } + + default: + break; + } + + if (hasMoved) { + expandSelectionInVisualLineMode(cursor); + m_editor->setTextCursor(cursor); + } +} + +bool VVim::clearSelection() +{ + QTextCursor cursor = m_editor->textCursor(); + if (cursor.hasSelection()) { + cursor.clearSelection(); + m_editor->setTextCursor(cursor); + return true; + } + + return false; +} + +int VVim::blockCountOfPageStep() const +{ + int lineCount = m_editor->document()->blockCount(); + QScrollBar *bar = m_editor->verticalScrollBar(); + int steps = (bar->maximum() - bar->minimum() + bar->pageStep()); + int pageLineCount = lineCount * (bar->pageStep() * 1.0 / steps); + return pageLineCount; +} + +void VVim::selectionToVisualMode(bool p_hasText) +{ + if (p_hasText && m_mode == VimMode::Normal) { + // Enter visual mode. + setMode(VimMode::Visual); + } +} + +void VVim::expandSelectionInVisualLineMode(QTextCursor &p_cursor) +{ + if (m_mode != VimMode::VisualLine) { + return; + } + + QTextDocument *doc = m_editor->document(); + int curPos = p_cursor.position(); + int anchorPos = p_cursor.anchor(); + QTextBlock curBlock = doc->findBlock(curPos); + QTextBlock anchorBlock = doc->findBlock(anchorPos); + + if (curPos >= anchorPos) { + p_cursor.setPosition(anchorBlock.position(), QTextCursor::MoveAnchor); + p_cursor.setPosition(curBlock.position() + curBlock.length() - 1, + QTextCursor::KeepAnchor); + } else { + p_cursor.setPosition(anchorBlock.position() + anchorBlock.length() - 1, + QTextCursor::MoveAnchor); + p_cursor.setPosition(curBlock.position(), + QTextCursor::KeepAnchor); + } +} diff --git a/src/utils/vvim.h b/src/utils/vvim.h new file mode 100644 index 00000000..0fc746ae --- /dev/null +++ b/src/utils/vvim.h @@ -0,0 +1,243 @@ +#ifndef VVIM_H +#define VVIM_H + +#include +#include +#include +#include "vutils.h" + +class VEdit; +class QKeyEvent; +class VEditConfig; +class QKeyEvent; + +enum class VimMode { + Normal = 0, + Insert, + Visual, + VisualLine, + Replace, + Invalid +}; + +class VVim : public QObject +{ + Q_OBJECT +public: + explicit VVim(VEdit *p_editor); + + // Handle key press event. + // Returns true if the event is consumed and need no more handling. + bool handleKeyPressEvent(QKeyEvent *p_event); + + // Return current mode. + VimMode getMode() const; + + // Set current mode. + void setMode(VimMode p_mode); + +signals: + // Emit when current mode has been changed. + void modeChanged(VimMode p_mode); + +private slots: + // When user use mouse to select texts in Normal mode, we should change to + // Visual mode. + void selectionToVisualMode(bool p_hasText); + +private: + // Struct for a key press. + struct Key + { + Key(int p_key, int p_modifiers = Qt::NoModifier) + : m_key(p_key), m_modifiers(p_modifiers) + { + } + + int m_key; + int m_modifiers; + + bool isDigit() const + { + return m_key >= Qt::Key_0 + && m_key <= Qt::Key_9 + && m_modifiers == Qt::NoModifier; + } + + int toDigit() const + { + V_ASSERT(isDigit()); + return m_key - Qt::Key_0; + } + + bool operator==(const Key &p_key) const + { + return p_key.m_key == m_key && p_key.m_modifiers == m_modifiers; + } + }; + + // Supported actions. + enum class Action + { + Move = 0, + Delete, + Copy, + Paste, + Change, + Indent, + UnIndent, + ToUpper, + ToLower, + DeleteToClipboard, + CopyToClipboard, + PasteFromClipboard, + ChangeToClipboard, + Invalid + }; + + // Supported movements. + enum class Movement + { + Left = 0, + Right, + Up, + Down, + VisualUp, + VisualDown, + PageUp, + PageDown, + HalfPageUp, + HalfPageDown, + StartOfLine, + EndOfLine, + FirstCharacter, + LineJump, + StartOfDocument, + EndOfDocument, + Invalid + }; + + // Supported ranges. + enum class Range + { + Line = 0, + Word, + Invalid + }; + + enum class TokenType { Action = 0, Repeat, Movement, Range, Invalid }; + + struct Token + { + Token(Action p_action) + : m_type(TokenType::Action), m_action(p_action) {} + + Token(int p_repeat) + : m_type(TokenType::Repeat), m_repeat(p_repeat) {} + + Token(Movement p_movement) + : m_type(TokenType::Movement), m_movement(p_movement) {} + + Token(Range p_range) + : m_type(TokenType::Range), m_range(p_range) {} + + Token() : m_type(TokenType::Invalid) {} + + bool isRepeat() const + { + return m_type == TokenType::Repeat; + } + + bool isAction() const + { + return m_type == TokenType::Action; + } + + bool isMovement() const + { + return m_type == TokenType::Movement; + } + + bool isRange() const + { + return m_type == TokenType::Range; + } + + QString toString() const + { + QString str; + switch (m_type) { + case TokenType::Action: + str = QString("action %1").arg((int)m_action); + break; + + case TokenType::Repeat: + str = QString("repeat %1").arg(m_repeat); + break; + + case TokenType::Movement: + str = QString("movement %1").arg((int)m_movement); + break; + + case TokenType::Range: + str = QString("range %1").arg((int)m_range); + break; + + default: + str = "invalid"; + } + + return str; + } + + TokenType m_type; + + union + { + Action m_action; + int m_repeat; + Movement m_movement; + Range m_range; + }; + }; + + // Reset all key state info. + void resetState(); + + // Now m_tokens constitute a command. Execute it. + // Will clear @p_tokens. + void processCommand(QList &p_tokens); + + // Return the number represented by @p_keys. + // Return -1 if @p_keys is not a valid digit sequence. + int numberFromKeySequence(const QList &p_keys); + + // Try to generate a Repeat token from @p_keys and insert it to @p_tokens. + // If succeed, clear @p_keys and return true. + bool tryGetRepeatToken(QList &p_keys, QList &p_tokens); + + // @p_tokens is the arguments of the Action::Move action. + void processMoveAction(QList &p_tokens); + + // Clear selection if there is any. + // Returns true if there is selection. + bool clearSelection(); + + // 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 + // of @p_cursor. + void expandSelectionInVisualLineMode(QTextCursor &p_cursor); + + VEdit *m_editor; + const VEditConfig *m_editConfig; + VimMode m_mode; + QList m_keys; + QList m_tokens; + + // Whether reset the position in block when moving cursor. + bool m_resetPositionInBlock; +}; + +#endif // VVIM_H diff --git a/src/vconfigmanager.cpp b/src/vconfigmanager.cpp index aa040caa..0f91dac0 100644 --- a/src/vconfigmanager.cpp +++ b/src/vconfigmanager.cpp @@ -146,6 +146,9 @@ void VConfigManager::initialize() m_enableTrailingSpaceHighlight = getConfigFromSettings("global", "enable_trailing_space_highlight").toBool(); + + m_enableVimMode = getConfigFromSettings("global", + "enable_vim_mode").toBool(); } void VConfigManager::readPredefinedColorsFromSettings() @@ -347,34 +350,48 @@ void VConfigManager::updateMarkdownEditStyle() QMap> styles; parser.fetchMarkdownEditorStyles(mdEditPalette, mdEditFont, styles); - m_editorCurrentLineBackground = defaultCurrentLineBackground; - m_editorCurrentLineVimBackground = defaultCurrentLineVimBackground; + m_editorCurrentLineBg = defaultCurrentLineBackground; + m_editorVimInsertBg = defaultCurrentLineBackground; + m_editorVimNormalBg = defaultCurrentLineVimBackground; + m_editorVimVisualBg = m_editorVimNormalBg; + m_editorVimReplaceBg = m_editorVimNormalBg; auto editorCurrentLineIt = styles.find("editor-current-line"); if (editorCurrentLineIt != styles.end()) { auto backgroundIt = editorCurrentLineIt->find("background"); if (backgroundIt != editorCurrentLineIt->end()) { // Do not need to add "#" here, since this is a built-in attribute. - m_editorCurrentLineBackground = *backgroundIt; + m_editorCurrentLineBg = *backgroundIt; } - auto vimBackgroundIt = editorCurrentLineIt->find("vim-background"); - if (vimBackgroundIt != editorCurrentLineIt->end()) { - m_editorCurrentLineVimBackground = "#" + *vimBackgroundIt; + auto vimBgIt = editorCurrentLineIt->find("vim-insert-background"); + if (vimBgIt != editorCurrentLineIt->end()) { + m_editorVimInsertBg = "#" + *vimBgIt; + } + + vimBgIt = editorCurrentLineIt->find("vim-normal-background"); + if (vimBgIt != editorCurrentLineIt->end()) { + m_editorVimNormalBg = "#" + *vimBgIt; + } + + vimBgIt = editorCurrentLineIt->find("vim-visual-background"); + if (vimBgIt != editorCurrentLineIt->end()) { + m_editorVimVisualBg = "#" + *vimBgIt; + } + + vimBgIt = editorCurrentLineIt->find("vim-replace-background"); + if (vimBgIt != editorCurrentLineIt->end()) { + m_editorVimReplaceBg = "#" + *vimBgIt; } } - m_editorTrailingSpaceBackground = defaultTrailingSpaceBackground; + m_editorTrailingSpaceBg = defaultTrailingSpaceBackground; auto editorIt = styles.find("editor"); if (editorIt != styles.end()) { auto trailingIt = editorIt->find("trailing-space"); if (trailingIt != editorIt->end()) { - m_editorTrailingSpaceBackground = "#" + *trailingIt; + m_editorTrailingSpaceBg = "#" + *trailingIt; } } - - qDebug() << "editor-current-line" << m_editorCurrentLineBackground; - qDebug() << "editor-current-line-vim" << m_editorCurrentLineVimBackground; - qDebug() << "editor-trailing-space" << m_editorTrailingSpaceBackground; } void VConfigManager::updateEditStyle() diff --git a/src/vconfigmanager.h b/src/vconfigmanager.h index cffceb07..aeec09f7 100644 --- a/src/vconfigmanager.h +++ b/src/vconfigmanager.h @@ -156,10 +156,14 @@ public: void setWebZoomFactor(qreal p_factor); inline bool isCustomWebZoomFactor(); - inline QString getEditorCurrentLineBackground() const; - inline QString getEditorCurrentLineVimBackground() const; + inline const QString &getEditorCurrentLineBg() const; inline QString getEditorTrailingSpaceBackground() const; + inline const QString &getEditorVimNormalBg() const; + inline const QString &getEditorVimInsertBg() const; + inline const QString &getEditorVimVisualBg() const; + inline const QString &getEditorVimReplaceBg() const; + inline bool getEnableCodeBlockHighlight() const; inline void setEnableCodeBlockHighlight(bool p_enabled); @@ -183,6 +187,9 @@ public: inline bool getEnableTrailingSpaceHighlight() const; inline void setEnableTrailingSapceHighlight(bool p_enabled); + inline bool getEnableVimMode() const; + inline void setEnableVimMode(bool p_enabled); + // Get the folder the ini file exists. QString getConfigFolder() const; @@ -292,13 +299,22 @@ private: qreal m_webZoomFactor; // Current line background color in editor. - QString m_editorCurrentLineBackground; + QString m_editorCurrentLineBg; - // Current line background color in editor in Vim mode. - QString m_editorCurrentLineVimBackground; + // Current line background color in editor in Vim normal mode. + QString m_editorVimNormalBg; + + // Current line background color in editor in Vim insert mode. + QString m_editorVimInsertBg; + + // Current line background color in editor in Vim visual mode. + QString m_editorVimVisualBg; + + // Current line background color in editor in Vim replace mode. + QString m_editorVimReplaceBg; // Trailing space background color in editor. - QString m_editorTrailingSpaceBackground; + QString m_editorTrailingSpaceBg; // Enable colde block syntax highlight. bool m_enableCodeBlockHighlight; @@ -322,6 +338,9 @@ private: // Enable trailing-space highlight. bool m_enableTrailingSpaceHighlight; + // Enable Vim mode. + bool m_enableVimMode; + // The name of the config file in each directory, obsolete. // Use c_dirConfigFile instead. static const QString c_obsoleteDirConfigFile; @@ -722,19 +741,34 @@ inline bool VConfigManager::isCustomWebZoomFactor() return factorFromIni > 0; } -inline QString VConfigManager::getEditorCurrentLineBackground() const +inline const QString &VConfigManager::getEditorCurrentLineBg() const { - return m_editorCurrentLineBackground; -} - -inline QString VConfigManager::getEditorCurrentLineVimBackground() const -{ - return m_editorCurrentLineVimBackground; + return m_editorCurrentLineBg; } inline QString VConfigManager::getEditorTrailingSpaceBackground() const { - return m_editorTrailingSpaceBackground; + return m_editorTrailingSpaceBg; +} + +inline const QString &VConfigManager::getEditorVimNormalBg() const +{ + return m_editorVimNormalBg; +} + +inline const QString &VConfigManager::getEditorVimInsertBg() const +{ + return m_editorVimInsertBg; +} + +inline const QString &VConfigManager::getEditorVimVisualBg() const +{ + return m_editorVimVisualBg; +} + +inline const QString &VConfigManager::getEditorVimReplaceBg() const +{ + return m_editorVimReplaceBg; } inline bool VConfigManager::getEnableCodeBlockHighlight() const @@ -857,4 +891,20 @@ inline void VConfigManager::setEnableTrailingSapceHighlight(bool p_enabled) m_enableTrailingSpaceHighlight); } +inline bool VConfigManager::getEnableVimMode() const +{ + return m_enableVimMode; +} + +inline void VConfigManager::setEnableVimMode(bool p_enabled) +{ + if (m_enableVimMode == p_enabled) { + return; + } + + m_enableVimMode = p_enabled; + setConfigToSettings("global", "enable_vim_mode", + m_enableVimMode); +} + #endif // VCONFIGMANAGER_H diff --git a/src/vedit.cpp b/src/vedit.cpp index eca3d2b3..729ce12c 100644 --- a/src/vedit.cpp +++ b/src/vedit.cpp @@ -13,6 +13,27 @@ extern VConfigManager vconfig; extern VNote *g_vnote; +void VEditConfig::init(const QFontMetrics &p_metric) +{ + if (vconfig.getTabStopWidth() > 0) { + m_tabStopWidth = vconfig.getTabStopWidth() * p_metric.width(' '); + } else { + m_tabStopWidth = 0; + } + + m_expandTab = vconfig.getIsExpandTab(); + + if (m_expandTab && (vconfig.getTabStopWidth() > 0)) { + m_tabSpaces = QString(vconfig.getTabStopWidth(), ' '); + } else { + m_tabSpaces = "\t"; + } + + m_enableVimMode = vconfig.getEnableVimMode(); + + m_cursorLineBg = QColor(vconfig.getEditorCurrentLineBg()); +} + VEdit::VEdit(VFile *p_file, QWidget *p_parent) : QTextEdit(p_parent), m_file(p_file), m_editOps(NULL) { @@ -20,7 +41,6 @@ VEdit::VEdit(VFile *p_file, QWidget *p_parent) const int extraSelectionHighlightTimer = 500; const int labelSize = 64; - m_cursorLineColor = QColor(g_vnote->getColorFromPalette("Indigo1")); m_selectedWordColor = QColor("Yellow"); m_searchedWordColor = QColor(g_vnote->getColorFromPalette("Green4")); m_trailingSpaceColor = QColor(vconfig.getEditorTrailingSpaceBackground()); @@ -45,8 +65,11 @@ VEdit::VEdit(VFile *p_file, QWidget *p_parent) (VFile *)m_file, &VFile::setModified); m_extraSelections.resize((int)SelectionId::MaxSelection); + updateFontAndPalette(); + updateConfig(); + connect(this, &VEdit::cursorPositionChanged, this, &VEdit::handleCursorPositionChanged); @@ -62,10 +85,23 @@ VEdit::~VEdit() } } +void VEdit::updateConfig() +{ + m_config.init(QFontMetrics(font())); + + if (m_config.m_tabStopWidth > 0) { + setTabStopWidth(m_config.m_tabStopWidth); + } + + emit configUpdated(); +} + void VEdit::beginEdit() { updateFontAndPalette(); + updateConfig(); + setReadOnly(false); setModified(false); } @@ -404,7 +440,7 @@ void VEdit::highlightCurrentLine() if (vconfig.getHighlightCursorLine() && !isReadOnly()) { // Need to highlight current line. QTextEdit::ExtraSelection select; - select.format.setBackground(m_cursorLineColor); + select.format.setBackground(m_config.m_cursorLineBg); select.format.setProperty(QTextFormat::FullWidthSelection, true); select.cursor = textCursor(); select.cursor.clearSelection(); @@ -651,3 +687,7 @@ void VEdit::handleCursorPositionChanged() lastCursor = cursor; } +VEditConfig &VEdit::getConfig() +{ + return m_config; +} diff --git a/src/vedit.h b/src/vedit.h index 4642b98c..8e1009cc 100644 --- a/src/vedit.h +++ b/src/vedit.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "vconstants.h" #include "vtoc.h" #include "vfile.h" @@ -23,6 +24,27 @@ enum class SelectionId { MaxSelection }; +class VEditConfig { +public: + VEditConfig() : m_tabStopWidth(0), m_tabSpaces("\t"), + m_enableVimMode(false) {} + + void init(const QFontMetrics &p_metric); + + // Width in pixels. + int m_tabStopWidth; + + bool m_expandTab; + + // The literal string for Tab. It is spaces if Tab is expanded. + QString m_tabSpaces; + + bool m_enableVimMode; + + // The background color of cursor line. + QColor m_cursorLineBg; +}; + class VEdit : public QTextEdit { Q_OBJECT @@ -51,11 +73,19 @@ public: void clearSearchedWordHighlight(); VFile *getFile() const; + VEditConfig &getConfig(); + signals: void saveAndRead(); void discardAndRead(); void editNote(); + // Emit when m_config has been updated. + void configUpdated(); + +public slots: + virtual void highlightCurrentLine(); + private slots: void labelTimerTimeout(); void highlightSelectedWord(); @@ -65,17 +95,18 @@ private slots: void highlightTrailingSpace(); void handleCursorPositionChanged(); -protected slots: - virtual void highlightCurrentLine(); - protected: QPointer m_file; VEditOperations *m_editOps; - QColor m_cursorLineColor; + + VEditConfig m_config; virtual void updateFontAndPalette(); virtual void contextMenuEvent(QContextMenuEvent *p_event) Q_DECL_OVERRIDE; + // Update m_config according to VConfigManager. + void updateConfig(); + private: QLabel *m_wrapLabel; QTimer *m_labelTimer; diff --git a/src/veditoperations.cpp b/src/veditoperations.cpp index 9de3376f..3898bcc7 100644 --- a/src/veditoperations.cpp +++ b/src/veditoperations.cpp @@ -4,14 +4,20 @@ #include "vedit.h" #include "veditoperations.h" #include "vconfigmanager.h" +#include "utils/vutils.h" extern VConfigManager vconfig; VEditOperations::VEditOperations(VEdit *p_editor, VFile *p_file) - : QObject(p_editor), m_editor(p_editor), m_file(p_file), m_expandTab(false), - m_keyState(KeyState::Normal), m_pendingTime(2) + : QObject(p_editor), m_editor(p_editor), m_file(p_file), + m_editConfig(&p_editor->getConfig()) { - updateTabSettings(); + m_vim = new VVim(m_editor); + + connect(m_editor, &VEdit::configUpdated, + this, &VEditOperations::handleEditConfigUpdated); + connect(m_vim, &VVim::modeChanged, + this, &VEditOperations::handleVimModeChanged); } void VEditOperations::insertTextAtCurPos(const QString &p_text) @@ -25,14 +31,46 @@ VEditOperations::~VEditOperations() { } -void VEditOperations::updateTabSettings() +void VEditOperations::updateCursorLineBg() { - if (vconfig.getTabStopWidth() > 0) { - QFontMetrics metrics(vconfig.getMdEditFont()); - m_editor->setTabStopWidth(vconfig.getTabStopWidth() * metrics.width(' ')); - } - m_expandTab = vconfig.getIsExpandTab(); - if (m_expandTab && (vconfig.getTabStopWidth() > 0)) { - m_tabSpaces = QString(vconfig.getTabStopWidth(), ' '); + if (m_editConfig->m_enableVimMode) { + switch (m_vim->getMode()) { + case VimMode::Normal: + m_editConfig->m_cursorLineBg = QColor(vconfig.getEditorVimNormalBg()); + break; + + case VimMode::Insert: + m_editConfig->m_cursorLineBg = QColor(vconfig.getEditorVimInsertBg()); + break; + + case VimMode::Visual: + case VimMode::VisualLine: + m_editConfig->m_cursorLineBg = QColor(vconfig.getEditorVimVisualBg()); + break; + + case VimMode::Replace: + m_editConfig->m_cursorLineBg = QColor(vconfig.getEditorVimReplaceBg()); + break; + + default: + V_ASSERT(false); + break; + } + } else { + m_editConfig->m_cursorLineBg = QColor(vconfig.getEditorCurrentLineBg()); } + + m_editor->highlightCurrentLine(); +} + +void VEditOperations::handleEditConfigUpdated() +{ + updateCursorLineBg(); +} + +void VEditOperations::handleVimModeChanged(VimMode p_mode) +{ + Q_UNUSED(p_mode); + + updateCursorLineBg(); } diff --git a/src/veditoperations.h b/src/veditoperations.h index da7bf9e7..ca9a6a60 100644 --- a/src/veditoperations.h +++ b/src/veditoperations.h @@ -6,13 +6,13 @@ #include #include #include "vfile.h" +#include "utils/vvim.h" class VEdit; +class VEditConfig; class QMimeData; class QKeyEvent; -enum class KeyState { Normal = 0, Vim, VimVisual}; - class VEditOperations: public QObject { Q_OBJECT @@ -22,25 +22,29 @@ public: virtual bool insertImageFromMimeData(const QMimeData *source) = 0; virtual bool insertImage() = 0; virtual bool insertImageFromURL(const QUrl &p_imageUrl) = 0; + // Return true if @p_event has been handled and no need to be further // processed. virtual bool handleKeyPressEvent(QKeyEvent *p_event) = 0; - void updateTabSettings(); -signals: - void keyStateChanged(KeyState p_state); +protected slots: + // Handle the update of VEditConfig of the editor. + virtual void handleEditConfigUpdated(); + + // Vim mode changed. + void handleVimModeChanged(VimMode p_mode); + +private: + // Update m_editConfig->m_cursorLineBg. + void updateCursorLineBg(); protected: void insertTextAtCurPos(const QString &p_text); VEdit *m_editor; QPointer m_file; - bool m_expandTab; - QString m_tabSpaces; - KeyState m_keyState; - // Seconds for pending mode. - int m_pendingTime; - QList m_pendingKey; + VEditConfig *m_editConfig; + VVim *m_vim; }; #endif // VEDITOPERATIONS_H diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index a02e70a1..02e9cc5a 100644 --- a/src/vmainwindow.cpp +++ b/src/vmainwindow.cpp @@ -605,6 +605,13 @@ void VMainWindow::initEditMenu() connect(autoListAct, &QAction::triggered, this, &VMainWindow::changeAutoList); + // Vim Mode. + QAction *vimAct = new QAction(tr("Vim Mode"), this); + vimAct->setToolTip(tr("Enable Vim mode for editing (re-enter edit mode to make it work)")); + vimAct->setCheckable(true); + connect(vimAct, &QAction::triggered, + this, &VMainWindow::changeVimMode); + // Highlight current cursor line. QAction *cursorLineAct = new QAction(tr("Highlight Cursor Line"), this); cursorLineAct->setToolTip(tr("Highlight current cursor line")); @@ -687,6 +694,9 @@ void VMainWindow::initEditMenu() } Q_ASSERT(!(autoListAct->isChecked() && !m_autoIndentAct->isChecked())); + editMenu->addAction(vimAct); + vimAct->setChecked(vconfig.getEnableVimMode()); + editMenu->addSeparator(); initEditorStyleMenu(editMenu); @@ -1381,6 +1391,11 @@ void VMainWindow::changeAutoList(bool p_checked) } } +void VMainWindow::changeVimMode(bool p_checked) +{ + vconfig.setEnableVimMode(p_checked); +} + void VMainWindow::enableCodeBlockHighlight(bool p_checked) { vconfig.setEnableCodeBlockHighlight(p_checked); diff --git a/src/vmainwindow.h b/src/vmainwindow.h index fa53c570..ad1b12f0 100644 --- a/src/vmainwindow.h +++ b/src/vmainwindow.h @@ -76,6 +76,7 @@ private slots: void handleCaptainModeChanged(bool p_enabled); void changeAutoIndent(bool p_checked); void changeAutoList(bool p_checked); + void changeVimMode(bool p_checked); void enableCodeBlockHighlight(bool p_checked); void enableImagePreview(bool p_checked); void enableImagePreviewConstraint(bool p_checked); diff --git a/src/vmdedit.cpp b/src/vmdedit.cpp index 0b41574e..ab174064 100644 --- a/src/vmdedit.cpp +++ b/src/vmdedit.cpp @@ -45,8 +45,6 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type, m_imagePreviewer = new VImagePreviewer(this, 500); m_editOps = new VMdEditOperations(this, m_file); - connect(m_editOps, &VEditOperations::keyStateChanged, - this, &VMdEdit::handleEditStateChanged); connect(this, &VMdEdit::cursorPositionChanged, this, &VMdEdit::updateCurHeader); @@ -56,22 +54,23 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type, connect(QApplication::clipboard(), &QClipboard::changed, this, &VMdEdit::handleClipboardChanged); - m_editOps->updateTabSettings(); updateFontAndPalette(); + + updateConfig(); } void VMdEdit::updateFontAndPalette() { setFont(vconfig.getMdEditFont()); setPalette(vconfig.getMdEditPalette()); - m_cursorLineColor = vconfig.getEditorCurrentLineBackground(); } void VMdEdit::beginEdit() { - m_editOps->updateTabSettings(); updateFontAndPalette(); + updateConfig(); + Q_ASSERT(m_file->getContent() == toPlainTextWithoutImg()); initInitImages(); @@ -366,17 +365,6 @@ int VMdEdit::removeObjectReplacementLine(QString &p_text, int p_index) const return prevLineIdx - 1; } -void VMdEdit::handleEditStateChanged(KeyState p_state) -{ - qDebug() << "edit state" << (int)p_state; - if (p_state == KeyState::Normal) { - m_cursorLineColor = vconfig.getEditorCurrentLineBackground(); - } else { - m_cursorLineColor = vconfig.getEditorCurrentLineVimBackground(); - } - highlightCurrentLine(); -} - void VMdEdit::handleSelectionChanged() { if (!vconfig.getEnablePreviewImages()) { diff --git a/src/vmdedit.h b/src/vmdedit.h index 03a2dc16..22a02dad 100644 --- a/src/vmdedit.h +++ b/src/vmdedit.h @@ -53,7 +53,6 @@ private slots: // When there is no header in current cursor, will signal an invalid header. void updateCurHeader(); - void handleEditStateChanged(KeyState p_state); void handleSelectionChanged(); void handleClipboardChanged(QClipboard::Mode p_mode); diff --git a/src/vmdeditoperations.cpp b/src/vmdeditoperations.cpp index 698522f8..9f7dc04f 100644 --- a/src/vmdeditoperations.cpp +++ b/src/vmdeditoperations.cpp @@ -21,6 +21,7 @@ #include "vfile.h" #include "vmdedit.h" #include "vconfigmanager.h" +#include "utils/vvim.h" extern VConfigManager vconfig; @@ -29,10 +30,6 @@ const QString VMdEditOperations::c_defaultImageTitle = "image"; VMdEditOperations::VMdEditOperations(VEdit *p_editor, VFile *p_file) : VEditOperations(p_editor, p_file), m_autoIndentPos(-1) { - m_pendingTimer = new QTimer(this); - m_pendingTimer->setSingleShot(true); - m_pendingTimer->setInterval(m_pendingTime * 1000); // milliseconds - connect(m_pendingTimer, &QTimer::timeout, this, &VMdEditOperations::pendingTimerTimeout); } bool VMdEditOperations::insertImageFromMimeData(const QMimeData *source) @@ -193,162 +190,137 @@ bool VMdEditOperations::insertImage() return true; } -// Will modify m_pendingKey. -bool VMdEditOperations::shouldTriggerVimMode(QKeyEvent *p_event) -{ - int modifiers = p_event->modifiers(); - int key = p_event->key(); - if (key == Qt::Key_Escape || - (key == Qt::Key_BracketLeft && modifiers == Qt::ControlModifier)) { - return false; - } else if (m_keyState == KeyState::Vim || m_keyState == KeyState::VimVisual) { - return true; - } - return false; -} - bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event) { + if (m_editConfig->m_enableVimMode && m_vim->handleKeyPressEvent(p_event)) { + m_autoIndentPos = -1; + return true; + } + bool ret = false; int key = p_event->key(); int modifiers = p_event->modifiers(); - if (shouldTriggerVimMode(p_event)) { - if (handleKeyPressVim(p_event)) { + switch (key) { + case Qt::Key_1: + case Qt::Key_2: + case Qt::Key_3: + case Qt::Key_4: + case Qt::Key_5: + case Qt::Key_6: + { + if (modifiers == Qt::ControlModifier) { + // Ctrl + : insert title at level . + if (insertTitle(key - Qt::Key_0)) { + p_event->accept(); + ret = true; + goto exit; + } + } + break; + } + + case Qt::Key_Tab: + { + if (handleKeyTab(p_event)) { ret = true; goto exit; } - } else { - switch (key) { - case Qt::Key_1: - case Qt::Key_2: - case Qt::Key_3: - case Qt::Key_4: - case Qt::Key_5: - case Qt::Key_6: - { - if (modifiers == Qt::ControlModifier) { - // Ctrl + : insert title at level . - if (insertTitle(key - Qt::Key_0)) { - p_event->accept(); - ret = true; - goto exit; - } - } - break; - } + break; + } - case Qt::Key_Tab: - { - if (handleKeyTab(p_event)) { - ret = true; - goto exit; - } - break; + case Qt::Key_Backtab: + { + if (handleKeyBackTab(p_event)) { + ret = true; + goto exit; } + break; + } - case Qt::Key_Backtab: - { - if (handleKeyBackTab(p_event)) { - ret = true; - goto exit; - } - break; + case Qt::Key_B: + { + if (handleKeyB(p_event)) { + ret = true; + goto exit; } + break; + } - case Qt::Key_B: - { - if (handleKeyB(p_event)) { - ret = true; - goto exit; - } - break; + case Qt::Key_H: + { + if (handleKeyH(p_event)) { + ret = true; + goto exit; } + break; + } - case Qt::Key_D: - { - if (handleKeyD(p_event)) { - ret = true; - goto exit; - } - break; + case Qt::Key_I: + { + if (handleKeyI(p_event)) { + ret = true; + goto exit; } + break; + } - case Qt::Key_H: - { - if (handleKeyH(p_event)) { - ret = true; - goto exit; - } - break; + case Qt::Key_O: + { + if (handleKeyO(p_event)) { + ret = true; + goto exit; } + break; + } - case Qt::Key_I: - { - if (handleKeyI(p_event)) { - ret = true; - goto exit; - } - break; + case Qt::Key_U: + { + if (handleKeyU(p_event)) { + ret = true; + goto exit; } + break; + } - case Qt::Key_O: - { - if (handleKeyO(p_event)) { - ret = true; - goto exit; - } - break; + case Qt::Key_W: + { + if (handleKeyW(p_event)) { + ret = true; + goto exit; } + break; + } - case Qt::Key_U: - { - if (handleKeyU(p_event)) { - ret = true; - goto exit; - } - break; + case Qt::Key_BracketLeft: + { + if (handleKeyBracketLeft(p_event)) { + ret = true; + goto exit; } + break; + } - case Qt::Key_W: - { - if (handleKeyW(p_event)) { - ret = true; - goto exit; - } - break; + case Qt::Key_Escape: + { + if (handleKeyEsc(p_event)) { + ret = true; + goto exit; } + break; + } - case Qt::Key_BracketLeft: - { - if (handleKeyBracketLeft(p_event)) { - ret = true; - goto exit; - } - break; + case Qt::Key_Return: + { + if (handleKeyReturn(p_event)) { + ret = true; + goto exit; } + break; + } - case Qt::Key_Escape: - { - if (handleKeyEsc(p_event)) { - ret = true; - goto exit; - } - break; - } - - case Qt::Key_Return: - { - if (handleKeyReturn(p_event)) { - ret = true; - goto exit; - } - break; - } - - default: - break; - } + default: + break; } exit: @@ -357,44 +329,32 @@ exit: key != Qt::Key_Shift) { m_autoIndentPos = -1; } + return ret; } // Let Ctrl+[ behave exactly like ESC. bool VMdEditOperations::handleKeyBracketLeft(QKeyEvent *p_event) { - // 1. If it is not in Normal state, just go back to Normal state; - // 2. If it is already Normal state, try to clear selection; - // 3. Otherwise, ignore this event and let parent handles it. - bool accept = false; + // 1. If there is any selection, clear it. + // 2. Otherwise, ignore this event and let parent handles it. if (p_event->modifiers() == Qt::ControlModifier) { - if (m_keyState != KeyState::Normal) { - m_pendingTimer->stop(); - setKeyState(KeyState::Normal); - m_pendingKey.clear(); - accept = true; - } else { - QTextCursor cursor = m_editor->textCursor(); - if (cursor.hasSelection()) { - cursor.clearSelection(); - m_editor->setTextCursor(cursor); - accept = true; - } + QTextCursor cursor = m_editor->textCursor(); + if (cursor.hasSelection()) { + cursor.clearSelection(); + m_editor->setTextCursor(cursor); + p_event->accept(); + return true; } } - if (accept) { - p_event->accept(); - } - return accept; + + return false; } bool VMdEditOperations::handleKeyTab(QKeyEvent *p_event) { QTextDocument *doc = m_editor->document(); - QString text("\t"); - if (m_expandTab) { - text = m_tabSpaces; - } + QString text(m_editConfig->m_tabSpaces); if (p_event->modifiers() == Qt::NoModifier) { QTextCursor cursor = m_editor->textCursor(); @@ -483,8 +443,8 @@ bool VMdEditOperations::handleKeyBackTab(QKeyEvent *p_event) continue; } else { // Spaces. - if (m_expandTab) { - int width = m_tabSpaces.size(); + if (m_editConfig->m_expandTab) { + int width = m_editConfig->m_tabSpaces.size(); for (int i = 0; i < width; ++i) { if (text[i] == ' ') { blockCursor.deleteChar(); @@ -555,20 +515,6 @@ bool VMdEditOperations::handleKeyB(QKeyEvent *p_event) return false; } -bool VMdEditOperations::handleKeyD(QKeyEvent *p_event) -{ - if (p_event->modifiers() == Qt::ControlModifier) { - // Ctrl+D, enter Vim-pending mode. - // Will accept the key stroke in m_pendingTime as Vim normal command. - setKeyState(KeyState::Vim); - m_pendingTimer->stop(); - m_pendingTimer->start(); - p_event->accept(); - return true; - } - return false; -} - bool VMdEditOperations::handleKeyH(QKeyEvent *p_event) { if (p_event->modifiers() == Qt::ControlModifier) { @@ -713,27 +659,17 @@ bool VMdEditOperations::handleKeyW(QKeyEvent *p_event) bool VMdEditOperations::handleKeyEsc(QKeyEvent *p_event) { - // 1. If it is not in Normal state, just go back to Normal state; - // 2. If it is already Normal state, try to clear selection; - // 3. Otherwise, ignore this event and let parent handles it. - bool accept = false; - if (m_keyState != KeyState::Normal) { - m_pendingTimer->stop(); - setKeyState(KeyState::Normal); - m_pendingKey.clear(); - accept = true; - } else { - QTextCursor cursor = m_editor->textCursor(); - if (cursor.hasSelection()) { - cursor.clearSelection(); - m_editor->setTextCursor(cursor); - accept = true; - } - } - if (accept) { + // 1. If there is any selection, clear it. + // 2. Otherwise, ignore this event and let parent handles it. + QTextCursor cursor = m_editor->textCursor(); + if (cursor.hasSelection()) { + cursor.clearSelection(); + m_editor->setTextCursor(cursor); p_event->accept(); + return true; } - return accept; + + return false; } bool VMdEditOperations::handleKeyReturn(QKeyEvent *p_event) @@ -935,399 +871,6 @@ void VMdEditOperations::deleteIndentAndListMark() m_editor->setTextCursor(cursor); } -bool VMdEditOperations::handleKeyPressVim(QKeyEvent *p_event) -{ - int modifiers = p_event->modifiers(); - bool visualMode = m_keyState == KeyState::VimVisual; - QTextCursor::MoveMode mode = visualMode ? QTextCursor::KeepAnchor - : QTextCursor::MoveAnchor; - - switch (p_event->key()) { - // Ctrl and Shift may be sent out first. - case Qt::Key_Control: - // Fall through. - case Qt::Key_Shift: - { - goto pending; - break; - } - - case Qt::Key_H: - case Qt::Key_J: - case Qt::Key_K: - case Qt::Key_L: - { - if (modifiers == Qt::NoModifier) { - QTextCursor::MoveOperation op = QTextCursor::Left; - switch (p_event->key()) { - case Qt::Key_H: - op = QTextCursor::Left; - break; - case Qt::Key_J: - op = QTextCursor::Down; - break; - case Qt::Key_K: - op = QTextCursor::Up; - break; - case Qt::Key_L: - op = QTextCursor::Right; - } - // Move cursor characters left/Down/Up/Right. - int repeat = keySeqToNumber(m_pendingKey); - m_pendingKey.clear(); - QTextCursor cursor = m_editor->textCursor(); - cursor.movePosition(op, mode, repeat == 0 ? 1 : repeat); - m_editor->setTextCursor(cursor); - goto pending; - } - break; - } - - case Qt::Key_1: - case Qt::Key_2: - case Qt::Key_3: - case Qt::Key_4: - case Qt::Key_5: - case Qt::Key_6: - case Qt::Key_7: - case Qt::Key_8: - case Qt::Key_9: - { - if (modifiers == Qt::NoModifier) { - if (!suffixNumAllowed(m_pendingKey)) { - break; - } - int num = p_event->key() - Qt::Key_0; - m_pendingKey.append(QString::number(num)); - goto pending; - } - break; - } - - case Qt::Key_X: - { - // Delete characters. - if (modifiers == Qt::NoModifier) { - int repeat = keySeqToNumber(m_pendingKey); - m_pendingKey.clear(); - QTextCursor cursor = m_editor->textCursor(); - if (repeat == 0) { - repeat = 1; - } - cursor.beginEditBlock(); - if (cursor.hasSelection()) { - QClipboard *clipboard = QApplication::clipboard(); - clipboard->setText(cursor.selectedText()); - cursor.removeSelectedText(); - } else { - for (int i = 0; i < repeat; ++i) { - cursor.deleteChar(); - } - } - cursor.endEditBlock(); - m_editor->setTextCursor(cursor); - goto pending; - } - break; - } - - case Qt::Key_W: - { - if (modifiers == Qt::NoModifier) { - // Move to the start of the next word. - // Slightly different from the Vim behavior. - int repeat = keySeqToNumber(m_pendingKey); - m_pendingKey.clear(); - QTextCursor cursor = m_editor->textCursor(); - if (repeat == 0) { - repeat = 1; - } - cursor.movePosition(QTextCursor::NextWord, mode, repeat); - m_editor->setTextCursor(cursor); - goto pending; - } - break; - } - - case Qt::Key_E: - { - if (modifiers == Qt::NoModifier) { - // Move to the end of the next word. - // Slightly different from the Vim behavior. - int repeat = keySeqToNumber(m_pendingKey); - m_pendingKey.clear(); - QTextCursor cursor = m_editor->textCursor(); - if (repeat == 0) { - repeat = 1; - } - cursor.beginEditBlock(); - int pos = cursor.position(); - // First move to the end of current word. - cursor.movePosition(QTextCursor::EndOfWord, mode); - if (cursor.position() != pos) { - // We did move. - repeat--; - } - if (repeat) { - cursor.movePosition(QTextCursor::NextWord, mode, repeat); - cursor.movePosition(QTextCursor::EndOfWord, mode); - } - cursor.endEditBlock(); - m_editor->setTextCursor(cursor); - goto pending; - } - break; - } - - case Qt::Key_B: - { - if (modifiers == Qt::NoModifier) { - // Move to the start of the previous word. - // Slightly different from the Vim behavior. - int repeat = keySeqToNumber(m_pendingKey); - m_pendingKey.clear(); - QTextCursor cursor = m_editor->textCursor(); - if (repeat == 0) { - repeat = 1; - } - cursor.beginEditBlock(); - int pos = cursor.position(); - // First move to the start of current word. - cursor.movePosition(QTextCursor::StartOfWord, mode); - if (cursor.position() != pos) { - // We did move. - repeat--; - } - if (repeat) { - cursor.movePosition(QTextCursor::PreviousWord, mode, repeat); - } - cursor.endEditBlock(); - m_editor->setTextCursor(cursor); - goto pending; - } - break; - } - - case Qt::Key_0: - { - if (modifiers == Qt::NoModifier) { - if (keySeqToNumber(m_pendingKey) == 0) { - QTextCursor cursor = m_editor->textCursor(); - cursor.movePosition(QTextCursor::StartOfLine, mode); - m_editor->setTextCursor(cursor); - } else { - m_pendingKey.append("0"); - } - goto pending; - } - break; - } - - case Qt::Key_Dollar: - { - if (modifiers == Qt::ShiftModifier) { - if (m_pendingKey.isEmpty()) { - // Go to end of line. - QTextCursor cursor = m_editor->textCursor(); - cursor.movePosition(QTextCursor::EndOfLine, mode); - m_editor->setTextCursor(cursor); - goto pending; - } - } - break; - } - - case Qt::Key_AsciiCircum: - { - if (modifiers == Qt::ShiftModifier) { - if (m_pendingKey.isEmpty()) { - // Go to first non-space character of current line. - QTextCursor cursor = m_editor->textCursor(); - QTextBlock block = cursor.block(); - QString text = block.text(); - cursor.beginEditBlock(); - if (text.trimmed().isEmpty()) { - cursor.movePosition(QTextCursor::StartOfLine, mode); - } else { - cursor.movePosition(QTextCursor::StartOfLine, mode); - int pos = cursor.positionInBlock(); - while (pos < text.size() && text[pos].isSpace()) { - cursor.movePosition(QTextCursor::NextWord, mode); - pos = cursor.positionInBlock(); - } - } - cursor.endEditBlock(); - m_editor->setTextCursor(cursor); - goto pending; - } - } - break; - } - - case Qt::Key_G: - { - if (modifiers == Qt::NoModifier) { - // g, pending or go to first line. - if (m_pendingKey.isEmpty()) { - m_pendingKey.append("g"); - goto pending; - } else if (m_pendingKey.size() == 1 && m_pendingKey.at(0) == "g") { - m_pendingKey.clear(); - QTextCursor cursor = m_editor->textCursor(); - cursor.movePosition(QTextCursor::Start, mode); - m_editor->setTextCursor(cursor); - goto pending; - } - } else if (modifiers == Qt::ShiftModifier) { - // G, go to a certain line or the end of document. - int lineNum = keySeqToNumber(m_pendingKey); - m_pendingKey.clear(); - QTextCursor cursor = m_editor->textCursor(); - if (lineNum == 0) { - cursor.movePosition(QTextCursor::End, mode); - } else { - QTextDocument *doc = m_editor->document(); - QTextBlock block = doc->findBlockByNumber(lineNum - 1); - if (block.isValid()) { - cursor.setPosition(block.position(), mode); - } else { - // Go beyond the document. - cursor.movePosition(QTextCursor::End, mode); - } - } - m_editor->setTextCursor(cursor); - goto pending; - } - break; - } - - case Qt::Key_V: - { - if (modifiers == Qt::NoModifier) { - // V to enter visual mode. - if (m_pendingKey.isEmpty() && m_keyState != KeyState::VimVisual) { - setKeyState(KeyState::VimVisual); - goto pending; - } - } - break; - } - - case Qt::Key_Y: - { - if (modifiers == Qt::NoModifier) { - if (m_pendingKey.isEmpty()) { - QTextCursor cursor = m_editor->textCursor(); - if (cursor.hasSelection()) { - QString text = cursor.selectedText(); - QClipboard *clipboard = QApplication::clipboard(); - clipboard->setText(text); - } - goto pending; - } - } - break; - } - - case Qt::Key_D: - { - if (modifiers == Qt::NoModifier) { - // d, pending or delete current line. - QTextCursor cursor = m_editor->textCursor(); - if (m_pendingKey.isEmpty()) { - if (cursor.hasSelection()) { - cursor.deleteChar(); - m_editor->setTextCursor(cursor); - } else { - m_pendingKey.append("d"); - } - goto pending; - } else if (m_pendingKey.size() == 1 && m_pendingKey.at(0) == "d") { - m_pendingKey.clear(); - cursor.select(QTextCursor::BlockUnderCursor); - cursor.removeSelectedText(); - m_editor->setTextCursor(cursor); - goto pending; - } - } - break; - } - - default: - // Unknown key. End Vim mode. - break; - } - - m_pendingTimer->stop(); - if (m_keyState == KeyState::VimVisual) { - // Clear the visual selection. - QTextCursor cursor = m_editor->textCursor(); - cursor.clearSelection(); - m_editor->setTextCursor(cursor); - } - setKeyState(KeyState::Normal); - m_pendingKey.clear(); - p_event->accept(); - return true; - -pending: - // When pending in Ctrl+Alt, we just want to clear m_pendingKey. - if (m_pendingTimer->isActive()) { - m_pendingTimer->stop(); - m_pendingTimer->start(); - } - p_event->accept(); - return true; -} - -int VMdEditOperations::keySeqToNumber(const QList &p_seq) -{ - int num = 0; - for (int i = 0; i < p_seq.size(); ++i) { - QString tmp = p_seq.at(i); - bool ok; - int tmpInt = tmp.toInt(&ok); - if (!ok) { - return 0; - } - num = num * 10 + tmpInt; - } - return num; -} - -void VMdEditOperations::pendingTimerTimeout() -{ - qDebug() << "key pending timer timeout"; - if (m_keyState == KeyState::VimVisual) { - m_pendingTimer->start(); - return; - } - setKeyState(KeyState::Normal); - m_pendingKey.clear(); -} - -bool VMdEditOperations::suffixNumAllowed(const QList &p_seq) -{ - if (!p_seq.isEmpty()) { - QString firstEle = p_seq.at(0); - if (firstEle[0].isDigit()) { - return true; - } else { - return false; - } - } - return true; -} - -void VMdEditOperations::setKeyState(KeyState p_state) -{ - if (m_keyState == p_state) { - return; - } - m_keyState = p_state; - emit keyStateChanged(m_keyState); -} - bool VMdEditOperations::insertTitle(int p_level) { Q_ASSERT(p_level > 0 && p_level < 7); diff --git a/src/vmdeditoperations.h b/src/vmdeditoperations.h index 68530550..32947dca 100644 --- a/src/vmdeditoperations.h +++ b/src/vmdeditoperations.h @@ -21,9 +21,6 @@ public: bool handleKeyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE; bool insertImageFromURL(const QUrl &p_imageUrl) Q_DECL_OVERRIDE; -private slots: - void pendingTimerTimeout(); - private: void insertImageFromPath(const QString &title, const QString &path, const QString &oriImagePath); @@ -32,13 +29,10 @@ private: // @image: the image to be inserted; void insertImageFromQImage(const QString &title, const QString &path, const QImage &image); - void setKeyState(KeyState p_state); - // Key press handlers. bool handleKeyTab(QKeyEvent *p_event); bool handleKeyBackTab(QKeyEvent *p_event); bool handleKeyB(QKeyEvent *p_event); - bool handleKeyD(QKeyEvent *p_event); bool handleKeyH(QKeyEvent *p_event); bool handleKeyI(QKeyEvent *p_event); bool handleKeyO(QKeyEvent *p_event); @@ -46,11 +40,7 @@ private: bool handleKeyW(QKeyEvent *p_event); bool handleKeyEsc(QKeyEvent *p_event); bool handleKeyReturn(QKeyEvent *p_event); - bool handleKeyPressVim(QKeyEvent *p_event); bool handleKeyBracketLeft(QKeyEvent *p_event); - bool shouldTriggerVimMode(QKeyEvent *p_event); - int keySeqToNumber(const QList &p_seq); - bool suffixNumAllowed(const QList &p_seq); bool insertTitle(int p_level); bool insertNewBlockWithIndent(); bool insertListMarkAsPreviousLine(); @@ -67,7 +57,6 @@ private: // Change the sequence number of a list block. void changeListBlockSeqNumber(QTextBlock &p_block, int p_seq); - QTimer *m_pendingTimer; // The cursor position after auto indent or auto list. // It will be -1 if last key press do not trigger the auto indent or auto list. int m_autoIndentPos;