Browse Source

vim-mode: support Ctrl+R to read a register

Support `Ctrl+R` to read a register both in Insert mode and command line.
Le Tan 8 years ago
parent
commit
eb71c8eff1

+ 1 - 0
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.
 

+ 1 - 0
src/resources/docs/shortcuts_zh.md

@@ -178,6 +178,7 @@ VNote支持以下几个Vim的特性:
 - `/` 和 `?` 开始查找
     - `n` 和 `N` 查找下一处或上一处;
     - `Ctrl+N` 和 `Ctrl+P` 浏览查找历史;
+- `Ctrl+R` 读取指定寄存器的值;
 
 VNote目前暂时不支持Vim的宏和重复(`.`)特性。
 

+ 62 - 18
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 "";
+}

+ 7 - 0
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;

+ 1 - 3
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()

+ 52 - 1
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;
+}

+ 10 - 0
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