From eb71c8eff1c7df72e5a92f6e73bc341cc72a0f18 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Thu, 13 Jul 2017 22:40:46 +0800 Subject: [PATCH] vim-mode: support Ctrl+R to read a register Support `Ctrl+R` to read a register both in Insert mode and command line. --- src/resources/docs/shortcuts_en.md | 1 + src/resources/docs/shortcuts_zh.md | 1 + src/utils/vvim.cpp | 80 +++++++++++++++++++++++------- src/utils/vvim.h | 7 +++ src/veditoperations.cpp | 4 +- src/vvimindicator.cpp | 53 +++++++++++++++++++- src/vvimindicator.h | 10 ++++ 7 files changed, 134 insertions(+), 22 deletions(-) diff --git a/src/resources/docs/shortcuts_en.md b/src/resources/docs/shortcuts_en.md index a20574fb..9299e442 100644 --- a/src/resources/docs/shortcuts_en.md +++ b/src/resources/docs/shortcuts_en.md @@ -177,6 +177,7 @@ VNote supports following features of Vim: - `/` and `?` to search - `n` and `N` to find next or previous occurence; - `Ctrl+N` and `Ctrl+P` to navigate through the search history; +- `Ctrl+R` to read the content of a register; For now, VNote does **NOT** support the macro and repeat(`.`) features of Vim. diff --git a/src/resources/docs/shortcuts_zh.md b/src/resources/docs/shortcuts_zh.md index ce1ba88e..2636cb2c 100644 --- a/src/resources/docs/shortcuts_zh.md +++ b/src/resources/docs/shortcuts_zh.md @@ -178,6 +178,7 @@ VNote支持以下几个Vim的特性: - `/` 和 `?` 开始查找 - `n` 和 `N` 查找下一处或上一处; - `Ctrl+N` 和 `Ctrl+P` 浏览查找历史; +- `Ctrl+R` 读取指定寄存器的值; VNote目前暂时不支持Vim的宏和重复(`.`)特性。 diff --git a/src/utils/vvim.cpp b/src/utils/vvim.cpp index 871b62b3..3f59449f 100644 --- a/src/utils/vvim.cpp +++ b/src/utils/vvim.cpp @@ -23,18 +23,24 @@ const int VVim::SearchHistory::c_capacity = 50; #define ADDKEY(x, y) case (x): {ch = (y); break;} +// See if @p_modifiers is Control which is different on macOs and Windows. +static bool isControlModifier(int p_modifiers) +{ +#if defined(Q_OS_MACOS) || defined(Q_OS_MAC) + return p_modifiers == Qt::MetaModifier; +#else + return p_modifiers == Qt::ControlModifier; +#endif +} + // Returns NULL QChar if invalid. static QChar keyToChar(int p_key, int p_modifiers) { - if (p_modifiers != Qt::NoModifier - && p_modifiers != Qt::ShiftModifier) { - return QChar(); - } - if (p_key >= Qt::Key_0 && p_key <= Qt::Key_9) { return QChar('0' + (p_key - Qt::Key_0)); } else if (p_key >= Qt::Key_A && p_key <= Qt::Key_Z) { - if (p_modifiers == Qt::ShiftModifier) { + if (p_modifiers == Qt::ShiftModifier + || isControlModifier(p_modifiers)) { return QChar('A' + (p_key - Qt::Key_A)); } else { return QChar('a' + (p_key - Qt::Key_A)); @@ -85,11 +91,26 @@ static QChar keyToChar(int p_key, int p_modifiers) return ch; } +static QString keyToString(int p_key, int p_modifiers) +{ + QChar ch = keyToChar(p_key, p_modifiers); + if (ch.isNull()) { + return QString(); + } + + if (isControlModifier(p_modifiers)) { + return QString("^") + ch; + } else { + return ch; + } +} + VVim::VVim(VEdit *p_editor) : QObject(p_editor), m_editor(p_editor), m_editConfig(&p_editor->getConfig()), m_mode(VimMode::Invalid), m_resetPositionInBlock(true), m_regName(c_unnamedRegister), - m_leaderKey(Key(Qt::Key_Space)), m_replayLeaderSequence(false) + m_leaderKey(Key(Qt::Key_Space)), m_replayLeaderSequence(false), + m_registerPending(false) { Q_ASSERT(m_editConfig->m_enableVimMode); @@ -348,16 +369,6 @@ static int percentageToBlockNumber(const QTextDocument *p_doc, int p_percent) return num >= 0 ? num : 0; } -// See if @p_modifiers is Control which is different on macOs and Windows. -static bool isControlModifier(int p_modifiers) -{ -#if defined(Q_OS_MACOS) || defined(Q_OS_MAC) - return p_modifiers == Qt::MetaModifier; -#else - return p_modifiers == Qt::ControlModifier; -#endif -} - // Replace each of the character of selected text with @p_char. // Returns true if replacement has taken place. // Need to setTextCursor() after calling this. @@ -479,6 +490,27 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos) goto clear_accept; } + if (m_registerPending) { + // Ctrl and Shift may be sent out first. + if (key == Qt::Key_Control || key == Qt::Key_Shift || key == Qt::Key_Meta) { + goto accept; + } + + // Expecting a register name. + QChar reg = keyToRegisterName(keyInfo); + if (!reg.isNull()) { + // Insert register content. + m_editor->insertPlainText(m_registers[reg].read()); + } + + goto clear_accept; + } else if (key == Qt::Key_R && isControlModifier(modifiers)) { + // Ctrl+R, insert the content of a register. + m_pendingKeys.append(keyInfo); + m_registerPending = true; + goto accept; + } + // Let it be handled outside VVim. goto exit; } @@ -2101,6 +2133,7 @@ void VVim::resetState() m_pendingKeys.clear(); setRegister(c_unnamedRegister); m_resetPositionInBlock = true; + m_registerPending = false; } VimMode VVim::getMode() const @@ -4941,7 +4974,7 @@ QString VVim::getPendingKeys() const { QString str; for (auto const & key : m_pendingKeys) { - str.append(keyToChar(key.m_key, key.m_modifiers)); + str.append(keyToString(key.m_key, key.m_modifiers)); } return str; @@ -5450,3 +5483,14 @@ void VVim::clearSearchHighlight() { m_editor->clearSearchedWordHighlight(); } + +QString VVim::readRegister(int p_key, int p_modifiers) +{ + Key keyInfo(p_key, p_modifiers); + QChar reg = keyToRegisterName(keyInfo); + if (!reg.isNull()) { + return m_registers[reg].read(); + } + + return ""; +} diff --git a/src/utils/vvim.h b/src/utils/vvim.h index bd65fc38..53313352 100644 --- a/src/utils/vvim.h +++ b/src/utils/vvim.h @@ -200,6 +200,10 @@ public: QString getPreviousCommandHistory(VVim::CommandLineType p_type, const QString &p_cmd); + // Read the register content. + // Returns empty string if it is not a valid register. + QString readRegister(int p_key, int p_modifiers); + signals: // Emit when current mode has been changed. void modeChanged(VimMode p_mode); @@ -825,6 +829,9 @@ private: // Search history. SearchHistory m_searchHistory; + // Whether we are expecting to read a register to insert. + bool m_registerPending; + static const QChar c_unnamedRegister; static const QChar c_blackHoleRegister; static const QChar c_selectionRegister; diff --git a/src/veditoperations.cpp b/src/veditoperations.cpp index 00a52ec0..e856c790 100644 --- a/src/veditoperations.cpp +++ b/src/veditoperations.cpp @@ -29,9 +29,7 @@ VEditOperations::VEditOperations(VEdit *p_editor, VFile *p_file) void VEditOperations::insertTextAtCurPos(const QString &p_text) { - QTextCursor cursor = m_editor->textCursor(); - cursor.insertText(p_text); - m_editor->setTextCursor(cursor); + m_editor->insertPlainText(p_text); } VEditOperations::~VEditOperations() diff --git a/src/vvimindicator.cpp b/src/vvimindicator.cpp index 94ee7a6b..bbc9cd6f 100644 --- a/src/vvimindicator.cpp +++ b/src/vvimindicator.cpp @@ -80,6 +80,16 @@ void VVimIndicator::setupUI() } }); + connect(m_cmdLineEdit, &VVimCmdLineEdit::requestRegister, + this, [this](int p_key, int p_modifiers){ + if (m_vim) { + QString val = m_vim->readRegister(p_key, p_modifiers); + if (!val.isEmpty()) { + m_cmdLineEdit->setText(m_cmdLineEdit->text() + val); + } + } + }); + m_cmdLineEdit->hide(); m_modeLabel = new QLabel(this); @@ -320,7 +330,8 @@ void VVimIndicator::triggerCommandLine(VVim::CommandLineType p_type) } VVimCmdLineEdit::VVimCmdLineEdit(QWidget *p_parent) - : QLineEdit(p_parent), m_type(VVim::CommandLineType::Invalid) + : QLineEdit(p_parent), m_type(VVim::CommandLineType::Invalid), + m_registerPending(false) { // When user delete all the text, cancel command input. connect(this, &VVimCmdLineEdit::textChanged, @@ -340,6 +351,8 @@ VVimCmdLineEdit::VVimCmdLineEdit(QWidget *p_parent) m_userLastInput = p_text.right(p_text.size() - 1); } }); + + m_originStyleSheet = styleSheet(); } QString VVimCmdLineEdit::getCommand() const @@ -404,6 +417,20 @@ void VVimCmdLineEdit::keyPressEvent(QKeyEvent *p_event) int key = p_event->key(); int modifiers = p_event->modifiers(); + if (m_registerPending) { + // Ctrl and Shift may be sent out first. + if (key == Qt::Key_Control || key == Qt::Key_Shift || key == Qt::Key_Meta) { + goto exit; + } + + // Expecting a register name. + emit requestRegister(key, modifiers); + + p_event->accept(); + setRegisterPending(false); + return; + } + if ((key == Qt::Key_Return && modifiers == Qt::NoModifier) || (key == Qt::Key_Enter && modifiers == Qt::KeypadModifier)) { // Enter, complete the command line input. @@ -456,10 +483,21 @@ void VVimCmdLineEdit::keyPressEvent(QKeyEvent *p_event) return; } + case Qt::Key_R: + { + if (isControlModifier(modifiers)) { + // Ctrl+R, insert the content of a register. + setRegisterPending(true); + p_event->accept(); + return; + } + } + default: break; } +exit: QLineEdit::keyPressEvent(p_event); } @@ -479,3 +517,16 @@ void VVimCmdLineEdit::restoreUserLastInput() { setCommand(m_userLastInput); } + +void VVimCmdLineEdit::setRegisterPending(bool p_pending) +{ + if (p_pending && !m_registerPending) { + // Going to pending. + setStyleSheet("QLineEdit { background: #D6EACE }"); + } else if (!p_pending && m_registerPending) { + // Leaving pending. + setStyleSheet(m_originStyleSheet); + } + + m_registerPending = p_pending; +} diff --git a/src/vvimindicator.h b/src/vvimindicator.h index 526132fc..42fa1e07 100644 --- a/src/vvimindicator.h +++ b/src/vvimindicator.h @@ -45,6 +45,9 @@ signals: // Emit when the input text changed. void commandChanged(VVim::CommandLineType p_type, const QString &p_cmd); + // Emit when expecting to read a register. + void requestRegister(int p_key, int p_modifiers); + protected: void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE; @@ -54,10 +57,17 @@ private: // Return the leader of @p_type. QString commandLineTypeLeader(VVim::CommandLineType p_type); + void setRegisterPending(bool p_pending); + VVim::CommandLineType m_type; // The latest command user input. QString m_userLastInput; + + // Whether we are expecting a register name to read. + bool m_registerPending; + + QString m_originStyleSheet; }; class VVimIndicator : public QWidget