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

Support `Ctrl+R` to read a register both in Insert mode and command line.
This commit is contained in:
Le Tan 2017-07-13 22:40:46 +08:00
parent cd2ac10509
commit eb71c8eff1
7 changed files with 134 additions and 22 deletions

View File

@ -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.

View File

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

View File

@ -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 "";
}

View File

@ -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;

View File

@ -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()

View File

@ -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;
}

View File

@ -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