vim-mode: support registers

This commit is contained in:
Le Tan 2017-06-12 20:22:37 +08:00
parent cbf207d9ed
commit 71ea514bfa
2 changed files with 204 additions and 71 deletions

View File

@ -7,10 +7,14 @@
#include <QDebug> #include <QDebug>
#include "vedit.h" #include "vedit.h"
const QChar VVim::c_unnamedRegister = QChar('"');
const QChar VVim::c_blackHoleRegister = QChar('_');
const QChar VVim::c_selectionRegister = QChar('+');
VVim::VVim(VEdit *p_editor) VVim::VVim(VEdit *p_editor)
: QObject(p_editor), m_editor(p_editor), : QObject(p_editor), m_editor(p_editor),
m_editConfig(&p_editor->getConfig()), m_mode(VimMode::Normal), m_editConfig(&p_editor->getConfig()), m_mode(VimMode::Normal),
m_resetPositionInBlock(true) m_resetPositionInBlock(true), m_register(c_unnamedRegister)
{ {
connect(m_editor, &VEdit::copyAvailable, connect(m_editor, &VEdit::copyAvailable,
this, &VVim::selectionToVisualMode); this, &VVim::selectionToVisualMode);
@ -179,17 +183,30 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
goto exit; 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. // Ctrl and Shift may be sent out first.
case Qt::Key_Control: if (key == Qt::Key_Control || key == Qt::Key_Shift) {
// Fall through.
case Qt::Key_Shift:
{
goto accept; goto accept;
} }
if (expectingRegisterName()) {
// Expecting a register name.
QChar reg = keyToRegisterName(keyInfo);
if (!reg.isNull()) {
resetState();
m_register = reg;
m_registers[reg].m_append = (modifiers == Qt::ShiftModifier);
qDebug() << "use register" << reg << m_registers[reg].m_append;
goto accept;
}
goto clear_accept;
}
// 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) {
case Qt::Key_0: case Qt::Key_0:
{ {
if (modifiers == Qt::NoModifier) { if (modifiers == Qt::NoModifier) {
@ -202,10 +219,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
goto accept; goto accept;
} else { } else {
// StartOfLine. // StartOfLine.
if (m_tokens.isEmpty()) { tryInsertMoveAction();
// Move.
m_tokens.append(Token(Action::Move));
}
m_tokens.append(Token(Movement::StartOfLine)); m_tokens.append(Token(Movement::StartOfLine));
@ -300,10 +314,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
} }
V_ASSERT(mm != Movement::Invalid); V_ASSERT(mm != Movement::Invalid);
if (m_tokens.isEmpty()) { tryInsertMoveAction();
// Move.
m_tokens.append(Token(Action::Move));
}
m_tokens.append(Token(mm)); m_tokens.append(Token(mm));
processCommand(m_tokens); processCommand(m_tokens);
@ -435,10 +446,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
tryGetRepeatToken(m_keys, m_tokens); tryGetRepeatToken(m_keys, m_tokens);
if (m_keys.isEmpty()) { if (m_keys.isEmpty()) {
if (m_tokens.isEmpty()) { tryInsertMoveAction();
// Move.
m_tokens.append(Token(Action::Move));
}
m_tokens.append(Token(Movement::EndOfLine)); m_tokens.append(Token(Movement::EndOfLine));
processCommand(m_tokens); processCommand(m_tokens);
@ -478,10 +486,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
} }
if (mm != Movement::Invalid) { if (mm != Movement::Invalid) {
if (m_tokens.isEmpty()) { tryInsertMoveAction();
// Move.
m_tokens.append(Token(Action::Move));
}
m_tokens.append(Token(mm)); m_tokens.append(Token(mm));
processCommand(m_tokens); processCommand(m_tokens);
@ -510,11 +515,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
mm = Movement::WORDBackward; mm = Movement::WORDBackward;
} }
if (m_tokens.isEmpty()) { tryInsertMoveAction();
// Move.
m_tokens.append(Token(Action::Move));
}
m_tokens.append(Token(mm)); m_tokens.append(Token(mm));
processCommand(m_tokens); processCommand(m_tokens);
break; break;
@ -533,11 +534,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
} }
Movement mm = Movement::PageUp; Movement mm = Movement::PageUp;
if (m_tokens.isEmpty()) { tryInsertMoveAction();
// Move.
m_tokens.append(Token(Action::Move));
}
m_tokens.append(Token(mm)); m_tokens.append(Token(mm));
processCommand(m_tokens); processCommand(m_tokens);
resetPositionInBlock = false; resetPositionInBlock = false;
@ -557,11 +554,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
} }
Movement mm = Movement::HalfPageUp; Movement mm = Movement::HalfPageUp;
if (m_tokens.isEmpty()) { tryInsertMoveAction();
// Move.
m_tokens.append(Token(Action::Move));
}
m_tokens.append(Token(mm)); m_tokens.append(Token(mm));
processCommand(m_tokens); processCommand(m_tokens);
resetPositionInBlock = false; resetPositionInBlock = false;
@ -581,11 +574,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
} }
Movement mm = Movement::PageDown; Movement mm = Movement::PageDown;
if (m_tokens.isEmpty()) { tryInsertMoveAction();
// Move.
m_tokens.append(Token(Action::Move));
}
m_tokens.append(Token(mm)); m_tokens.append(Token(mm));
processCommand(m_tokens); processCommand(m_tokens);
resetPositionInBlock = false; resetPositionInBlock = false;
@ -605,14 +594,12 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
} }
Movement mm = Movement::HalfPageDown; Movement mm = Movement::HalfPageDown;
if (m_tokens.isEmpty()) { tryInsertMoveAction();
// Move.
m_tokens.append(Token(Action::Move));
}
m_tokens.append(Token(mm)); m_tokens.append(Token(mm));
processCommand(m_tokens); processCommand(m_tokens);
resetPositionInBlock = false; resetPositionInBlock = false;
} else if (modifiers == Qt::NoModifier) {
// d, delete action.
} }
break; break;
@ -680,11 +667,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
} }
Movement mm = Movement::FirstCharacter; Movement mm = Movement::FirstCharacter;
if (m_tokens.isEmpty()) { tryInsertMoveAction();
// Move.
m_tokens.append(Token(Action::Move));
}
m_tokens.append(Token(mm)); m_tokens.append(Token(mm));
processCommand(m_tokens); processCommand(m_tokens);
} }
@ -708,11 +691,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
mm = Movement::WORDForward; mm = Movement::WORDForward;
} }
if (m_tokens.isEmpty()) { tryInsertMoveAction();
// Move.
m_tokens.append(Token(Action::Move));
}
m_tokens.append(Token(mm)); m_tokens.append(Token(mm));
processCommand(m_tokens); processCommand(m_tokens);
} }
@ -747,11 +726,7 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
} }
} }
if (m_tokens.isEmpty()) { tryInsertMoveAction();
// Move.
m_tokens.append(Token(Action::Move));
}
m_tokens.append(Token(mm)); m_tokens.append(Token(mm));
processCommand(m_tokens); processCommand(m_tokens);
} }
@ -759,13 +734,28 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
break; break;
} }
case Qt::Key_QuoteDbl:
{
if (modifiers == Qt::ShiftModifier) {
// Specify a register.
if (!m_keys.isEmpty() || !m_tokens.isEmpty()) {
// Invalid sequence.
break;
}
m_keys.append(keyInfo);
goto accept;
}
break;
}
default: default:
break; break;
} }
clear_accept: clear_accept:
m_keys.clear(); resetState();
m_tokens.clear();
accept: accept:
p_event->accept(); p_event->accept();
@ -780,6 +770,7 @@ void VVim::resetState()
{ {
m_keys.clear(); m_keys.clear();
m_tokens.clear(); m_tokens.clear();
m_register = c_unnamedRegister;
m_resetPositionInBlock = true; m_resetPositionInBlock = true;
} }
@ -846,11 +837,6 @@ bool VVim::tryGetRepeatToken(QList<Key> &p_keys, QList<Token> &p_tokens)
if (!p_keys.isEmpty()) { if (!p_keys.isEmpty()) {
int repeat = numberFromKeySequence(p_keys); int repeat = numberFromKeySequence(p_keys);
if (repeat != -1) { if (repeat != -1) {
if (p_tokens.isEmpty()) {
// Move.
p_tokens.append(Token(Action::Move));
}
p_tokens.append(Token(repeat)); p_tokens.append(Token(repeat));
p_keys.clear(); p_keys.clear();
@ -1371,3 +1357,74 @@ void VVim::expandSelectionInVisualLineMode(QTextCursor &p_cursor)
QTextCursor::KeepAnchor); QTextCursor::KeepAnchor);
} }
} }
void VVim::initRegisters()
{
m_registers.clear();
for (char ch = 'a'; ch > 'z'; ++ch) {
m_registers[QChar(ch)] = Register(QChar(ch));
}
m_registers[c_unnamedRegister] = Register(c_unnamedRegister);
m_registers[c_blackHoleRegister] = Register(c_blackHoleRegister);
m_registers[c_selectionRegister] = Register(c_selectionRegister);
}
bool VVim::expectingRegisterName() const
{
return m_keys.size() == 1
&& m_keys.at(0) == Key(Qt::Key_QuoteDbl, Qt::ShiftModifier);
}
QChar VVim::keyToRegisterName(const Key &p_key) const
{
if (p_key.isAlphabet()) {
return p_key.toAlphabet().toLower();
}
switch (p_key.m_key) {
case Qt::Key_QuoteDbl:
if (p_key.m_modifiers == Qt::ShiftModifier) {
return c_unnamedRegister;
}
break;
case Qt::Key_Plus:
if (p_key.m_modifiers == Qt::ShiftModifier) {
return c_selectionRegister;
}
break;
case Qt::Key_Underscore:
if (p_key.m_modifiers == Qt::ShiftModifier) {
return c_blackHoleRegister;
}
break;
default:
break;
}
return QChar();
}
bool VVim::hasActionToken() const
{
for (auto const &token : m_tokens) {
if (token.isAction()) {
return true;
}
}
return false;
}
void VVim::tryInsertMoveAction()
{
if (!hasActionToken()) {
m_tokens.prepend(Token(Action::Move));
}
}

View File

@ -4,6 +4,7 @@
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include <QTextCursor> #include <QTextCursor>
#include <QHash>
#include "vutils.h" #include "vutils.h"
class VEdit; class VEdit;
@ -70,6 +71,23 @@ private:
return m_key - Qt::Key_0; return m_key - Qt::Key_0;
} }
bool isAlphabet() const
{
return m_key >= Qt::Key_A
&& m_key <= Qt::Key_Z
&& (m_modifiers == Qt::NoModifier || m_modifiers == Qt::ShiftModifier);
}
QChar toAlphabet() const
{
V_ASSERT(isAlphabet());
if (m_modifiers == Qt::NoModifier) {
return QChar('a' + (m_key - Qt::Key_A));
} else {
return QChar('A' + (m_key - Qt::Key_A));
}
}
bool operator==(const Key &p_key) const bool operator==(const Key &p_key) const
{ {
return p_key.m_key == m_key && p_key.m_modifiers == m_modifiers; return p_key.m_key == m_key && p_key.m_modifiers == m_modifiers;
@ -205,6 +223,32 @@ private:
}; };
}; };
struct Register
{
Register(QChar p_name, const QString &p_value)
: m_name(p_name), m_value(p_value), m_append(false)
{
}
Register(QChar p_name)
: m_name(p_name), m_append(false)
{
}
Register()
: m_append(false)
{
}
QChar m_name;
QString m_value;
// This is not info of Register itself, but a hint to the handling logics
// whether we need to append the content to this register.
// Only valid for a-z registers.
bool m_append;
};
// Reset all key state info. // Reset all key state info.
void resetState(); void resetState();
@ -234,14 +278,46 @@ private:
// of @p_cursor. // of @p_cursor.
void expandSelectionInVisualLineMode(QTextCursor &p_cursor); void expandSelectionInVisualLineMode(QTextCursor &p_cursor);
// Init m_registers.
// Currently supported registers:
// a-z, A-Z (append to a-z), ", +, _
void initRegisters();
// Check m_keys to see if we are expecting a register name.
bool expectingRegisterName() const;
// Return the corresponding register name of @p_key.
// If @p_key is not a valid register name, return a NULL QChar.
QChar keyToRegisterName(const Key &p_key) const;
// Check if @m_tokens contains an action token.
bool hasActionToken() const;
// Try to insert a Action::Move action at the front if there is no any action
// token.
void tryInsertMoveAction();
VEdit *m_editor; VEdit *m_editor;
const VEditConfig *m_editConfig; const VEditConfig *m_editConfig;
VimMode m_mode; VimMode m_mode;
// A valid command token should follow the rule:
// Action, Repeat, Movement.
// Action, Repeat, Range.
QList<Key> m_keys; QList<Key> m_keys;
QList<Token> m_tokens; QList<Token> m_tokens;
// Whether reset the position in block when moving cursor. // Whether reset the position in block when moving cursor.
bool m_resetPositionInBlock; bool m_resetPositionInBlock;
QHash<QChar, Register> m_registers;
// Currently used register.
QChar m_register;
static const QChar c_unnamedRegister;
static const QChar c_blackHoleRegister;
static const QChar c_selectionRegister;
}; };
#endif // VVIM_H #endif // VVIM_H