mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
vim-mode: support command line mode and leader key
1. We now support limited commands: :w, :q, :wq, :x, :q! ; 2. We now support fixed leader sequence: y, d, p, P ; 3. Support <num>% to goto <num>/100 percent of the document.
This commit is contained in:
parent
5953954786
commit
10a9447b96
@ -18,10 +18,74 @@ const QChar VVim::c_unnamedRegister = QChar('"');
|
||||
const QChar VVim::c_blackHoleRegister = QChar('_');
|
||||
const QChar VVim::c_selectionRegister = QChar('+');
|
||||
|
||||
#define ADDKEY(x, y) case (x): {ch = (y); break;}
|
||||
|
||||
// Returns NULL QChar if invalid.
|
||||
static QChar keyToChar(int p_key, int p_modifiers)
|
||||
{
|
||||
if (p_modifiers == Qt::ControlModifier) {
|
||||
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) {
|
||||
return QChar('A' + (p_key - Qt::Key_A));
|
||||
} else {
|
||||
return QChar('a' + (p_key - Qt::Key_A));
|
||||
}
|
||||
}
|
||||
|
||||
QChar ch;
|
||||
switch (p_key) {
|
||||
ADDKEY(Qt::Key_Tab, '\t');
|
||||
ADDKEY(Qt::Key_Space, ' ');
|
||||
ADDKEY(Qt::Key_Exclam, '!');
|
||||
ADDKEY(Qt::Key_QuoteDbl, '"');
|
||||
ADDKEY(Qt::Key_NumberSign, '#');
|
||||
ADDKEY(Qt::Key_Dollar, '$');
|
||||
ADDKEY(Qt::Key_Percent, '%');
|
||||
ADDKEY(Qt::Key_Ampersand, '&');
|
||||
ADDKEY(Qt::Key_Apostrophe, '\'');
|
||||
ADDKEY(Qt::Key_ParenLeft, '(');
|
||||
ADDKEY(Qt::Key_ParenRight, ')');
|
||||
ADDKEY(Qt::Key_Asterisk, '*');
|
||||
ADDKEY(Qt::Key_Plus, '+');
|
||||
ADDKEY(Qt::Key_Comma, ',');
|
||||
ADDKEY(Qt::Key_Minus, '-');
|
||||
ADDKEY(Qt::Key_Period, '.');
|
||||
ADDKEY(Qt::Key_Slash, '/');
|
||||
ADDKEY(Qt::Key_Colon, ':');
|
||||
ADDKEY(Qt::Key_Semicolon, ';');
|
||||
ADDKEY(Qt::Key_Less, '<');
|
||||
ADDKEY(Qt::Key_Equal, '=');
|
||||
ADDKEY(Qt::Key_Greater, '>');
|
||||
ADDKEY(Qt::Key_Question, '?');
|
||||
ADDKEY(Qt::Key_At, '@');
|
||||
ADDKEY(Qt::Key_BracketLeft, '[');
|
||||
ADDKEY(Qt::Key_Backslash, '\\');
|
||||
ADDKEY(Qt::Key_BracketRight, ']');
|
||||
ADDKEY(Qt::Key_AsciiCircum, '^');
|
||||
ADDKEY(Qt::Key_Underscore, '_');
|
||||
ADDKEY(Qt::Key_QuoteLeft, '`');
|
||||
ADDKEY(Qt::Key_BraceLeft, '{');
|
||||
ADDKEY(Qt::Key_Bar, '|');
|
||||
ADDKEY(Qt::Key_BraceRight, '}');
|
||||
ADDKEY(Qt::Key_AsciiTilde, '~');
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ch;
|
||||
}
|
||||
|
||||
VVim::VVim(VEdit *p_editor)
|
||||
: QObject(p_editor), m_editor(p_editor),
|
||||
m_editConfig(&p_editor->getConfig()), m_mode(VimMode::Normal),
|
||||
m_resetPositionInBlock(true), m_regName(c_unnamedRegister)
|
||||
m_resetPositionInBlock(true), m_regName(c_unnamedRegister),
|
||||
m_cmdMode(false), m_leaderKey(Key(Qt::Key_Space)), m_replayLeaderSequence(false)
|
||||
{
|
||||
initRegisters();
|
||||
|
||||
@ -203,7 +267,7 @@ static void moveCursorAcrossSpaces(QTextCursor &p_cursor,
|
||||
|
||||
// Expand the selection of @p_cursor to contain additional spaces at the two ends
|
||||
// within a block.
|
||||
void expandSelectionAcrossSpacesWithinBlock(QTextCursor &p_cursor)
|
||||
static void expandSelectionAcrossSpacesWithinBlock(QTextCursor &p_cursor)
|
||||
{
|
||||
QTextBlock block = p_cursor.block();
|
||||
QString text = block.text();
|
||||
@ -261,11 +325,34 @@ static void insertChangeBlockAfterDeletion(QTextCursor &p_cursor, int p_deletion
|
||||
}
|
||||
}
|
||||
|
||||
// Given the percentage of the text, return the corresponding block number.
|
||||
// Notice that the block number is based on 0.
|
||||
// Returns -1 if it is not valid.
|
||||
static int percentageToBlockNumber(const QTextDocument *p_doc, int p_percent)
|
||||
{
|
||||
if (p_percent > 100 || p_percent <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int nrBlock = p_doc->blockCount();
|
||||
int num = nrBlock * (p_percent * 1.0 / 100) - 1;
|
||||
|
||||
return num >= 0 ? num : 0;
|
||||
}
|
||||
|
||||
bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
|
||||
{
|
||||
bool ret = handleKeyPressEvent(p_event->key(), p_event->modifiers());
|
||||
if (ret) {
|
||||
p_event->accept();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool VVim::handleKeyPressEvent(int key, int modifiers)
|
||||
{
|
||||
bool ret = false;
|
||||
int modifiers = p_event->modifiers();
|
||||
int key = p_event->key();
|
||||
bool resetPositionInBlock = true;
|
||||
Key keyInfo(key, modifiers);
|
||||
bool unindent = false;
|
||||
@ -290,15 +377,35 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
|
||||
goto accept;
|
||||
}
|
||||
|
||||
if (m_replayLeaderSequence) {
|
||||
qDebug() << "replaying sequence" << keyToChar(key, modifiers);
|
||||
}
|
||||
|
||||
if (expectingCommandLineInput()) {
|
||||
// All input will be treated as command line input.
|
||||
// [Enter] to execute the command and exit command line mode.
|
||||
if (processCommandLine(keyInfo)) {
|
||||
goto clear_accept;
|
||||
} else {
|
||||
goto accept;
|
||||
}
|
||||
}
|
||||
|
||||
m_pendingKeys.append(keyInfo);
|
||||
|
||||
if (expectingLeaderSequence()) {
|
||||
if (processLeaderSequence(keyInfo)) {
|
||||
goto accept;
|
||||
} else {
|
||||
goto clear_accept;
|
||||
}
|
||||
}
|
||||
|
||||
if (expectingRegisterName()) {
|
||||
// Expecting a register name.
|
||||
QChar reg = keyToRegisterName(keyInfo);
|
||||
if (!reg.isNull()) {
|
||||
// We should keep m_pendingKeys.
|
||||
m_keys.clear();
|
||||
m_tokens.clear();
|
||||
setRegister(reg);
|
||||
if (m_registers[reg].isNamedRegister()) {
|
||||
m_registers[reg].m_append = (modifiers == Qt::ShiftModifier);
|
||||
@ -340,12 +447,30 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
|
||||
goto clear_accept;
|
||||
}
|
||||
|
||||
// Check leader key here. If leader key conflicts with other keys, it will
|
||||
// overwrite it.
|
||||
// Leader sequence is just like an action.
|
||||
if (keyInfo == m_leaderKey
|
||||
&& !hasActionToken()
|
||||
&& !hasNonDigitPendingKeys()
|
||||
&& !m_replayLeaderSequence) {
|
||||
tryGetRepeatToken(m_keys, m_tokens);
|
||||
|
||||
Q_ASSERT(m_keys.isEmpty());
|
||||
|
||||
m_pendingKeys.pop_back();
|
||||
m_pendingKeys.append(Key(Qt::Key_Backslash));
|
||||
m_keys.append(Key(Qt::Key_Backslash));
|
||||
goto 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:
|
||||
{
|
||||
if (modifiers == Qt::NoModifier) {
|
||||
if (modifiers == Qt::NoModifier
|
||||
|| modifiers == Qt::KeypadModifier) {
|
||||
if (!m_keys.isEmpty()) {
|
||||
// Repeat.
|
||||
V_ASSERT(m_keys.last().isDigit());
|
||||
@ -376,7 +501,8 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
|
||||
case Qt::Key_8:
|
||||
case Qt::Key_9:
|
||||
{
|
||||
if (modifiers == Qt::NoModifier) {
|
||||
if (modifiers == Qt::NoModifier
|
||||
|| modifiers == Qt::KeypadModifier) {
|
||||
if (!m_keys.isEmpty() && numberFromKeySequence(m_keys) == -1) {
|
||||
// Invalid sequence.
|
||||
break;
|
||||
@ -475,8 +601,13 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
|
||||
}
|
||||
|
||||
// Enter Insert mode.
|
||||
if (m_mode == VimMode::Normal) {
|
||||
setMode(VimMode::Insert);
|
||||
// Different from Vim:
|
||||
// We enter Insert mode even in Visual and VisualLine mode. We
|
||||
// also keep the selection after the mode change.
|
||||
if (checkMode(VimMode::Normal)
|
||||
|| checkMode(VimMode::Visual)
|
||||
|| checkMode(VimMode::VisualLine)) {
|
||||
setMode(VimMode::Insert, false);
|
||||
}
|
||||
} else if (modifiers == Qt::ShiftModifier) {
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
@ -1045,7 +1176,8 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
|
||||
{
|
||||
if (modifiers == Qt::ShiftModifier) {
|
||||
// Specify a register.
|
||||
if (!m_keys.isEmpty() || !m_tokens.isEmpty()) {
|
||||
tryGetRepeatToken(m_keys, m_tokens);
|
||||
if (!m_keys.isEmpty() || hasActionToken()) {
|
||||
// Invalid sequence.
|
||||
break;
|
||||
}
|
||||
@ -1355,6 +1487,54 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
|
||||
break;
|
||||
}
|
||||
|
||||
case Qt::Key_Colon:
|
||||
{
|
||||
if (modifiers == Qt::ShiftModifier) {
|
||||
if (m_keys.isEmpty()
|
||||
&& m_tokens.isEmpty()
|
||||
&& checkMode(VimMode::Normal)) {
|
||||
// :, enter command line mode.
|
||||
// For simplicity, we do not use a standalone mode for this mode.
|
||||
// Just let it be in Normal mode and use another variable to
|
||||
// specify this.
|
||||
m_cmdMode = true;
|
||||
goto accept;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Qt::Key_Percent:
|
||||
{
|
||||
if (modifiers == Qt::ShiftModifier) {
|
||||
tryGetRepeatToken(m_keys, m_tokens);
|
||||
if (m_keys.isEmpty() && hasRepeatToken()) {
|
||||
// xx%, jump to a certain line (percentage of the documents).
|
||||
// Change the repeat from percentage to line number.
|
||||
Token *token = getRepeatToken();
|
||||
int bn = percentageToBlockNumber(m_editor->document(), token->m_repeat);
|
||||
if (bn == -1) {
|
||||
break;
|
||||
} else {
|
||||
// Repeat of LineJump is based on 1.
|
||||
token->m_repeat = bn + 1;
|
||||
}
|
||||
|
||||
tryAddMoveAction();
|
||||
addMovementToken(Movement::LineJump);
|
||||
processCommand(m_tokens);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -1363,7 +1543,6 @@ clear_accept:
|
||||
resetState();
|
||||
|
||||
accept:
|
||||
p_event->accept();
|
||||
ret = true;
|
||||
|
||||
exit:
|
||||
@ -1379,6 +1558,7 @@ void VVim::resetState()
|
||||
m_pendingKeys.clear();
|
||||
setRegister(c_unnamedRegister);
|
||||
m_resetPositionInBlock = true;
|
||||
m_cmdMode = false;
|
||||
}
|
||||
|
||||
VimMode VVim::getMode() const
|
||||
@ -1386,10 +1566,13 @@ VimMode VVim::getMode() const
|
||||
return m_mode;
|
||||
}
|
||||
|
||||
void VVim::setMode(VimMode p_mode)
|
||||
void VVim::setMode(VimMode p_mode, bool p_clearSelection)
|
||||
{
|
||||
if (m_mode != p_mode) {
|
||||
clearSelection();
|
||||
if (p_clearSelection) {
|
||||
clearSelection();
|
||||
}
|
||||
|
||||
m_mode = p_mode;
|
||||
resetState();
|
||||
|
||||
@ -1572,69 +1755,6 @@ void VVim::processMoveAction(QList<Token> &p_tokens)
|
||||
}
|
||||
}
|
||||
|
||||
#define ADDKEY(x, y) case (x): {ch = (y); break;}
|
||||
|
||||
// Returns NULL QChar if invalid.
|
||||
static QChar keyToChar(int p_key, int p_modifiers)
|
||||
{
|
||||
if (p_modifiers == Qt::ControlModifier) {
|
||||
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) {
|
||||
return QChar('A' + (p_key - Qt::Key_A));
|
||||
} else {
|
||||
return QChar('a' + (p_key - Qt::Key_A));
|
||||
}
|
||||
}
|
||||
|
||||
QChar ch;
|
||||
switch (p_key) {
|
||||
ADDKEY(Qt::Key_Tab, '\t');
|
||||
ADDKEY(Qt::Key_Space, ' ');
|
||||
ADDKEY(Qt::Key_Exclam, '!');
|
||||
ADDKEY(Qt::Key_QuoteDbl, '"');
|
||||
ADDKEY(Qt::Key_NumberSign, '#');
|
||||
ADDKEY(Qt::Key_Dollar, '$');
|
||||
ADDKEY(Qt::Key_Percent, '%');
|
||||
ADDKEY(Qt::Key_Ampersand, '&');
|
||||
ADDKEY(Qt::Key_Apostrophe, '\'');
|
||||
ADDKEY(Qt::Key_ParenLeft, '(');
|
||||
ADDKEY(Qt::Key_ParenRight, ')');
|
||||
ADDKEY(Qt::Key_Asterisk, '*');
|
||||
ADDKEY(Qt::Key_Plus, '+');
|
||||
ADDKEY(Qt::Key_Comma, ',');
|
||||
ADDKEY(Qt::Key_Minus, '-');
|
||||
ADDKEY(Qt::Key_Period, '.');
|
||||
ADDKEY(Qt::Key_Slash, '/');
|
||||
ADDKEY(Qt::Key_Colon, ':');
|
||||
ADDKEY(Qt::Key_Semicolon, ';');
|
||||
ADDKEY(Qt::Key_Less, '<');
|
||||
ADDKEY(Qt::Key_Equal, '=');
|
||||
ADDKEY(Qt::Key_Greater, '>');
|
||||
ADDKEY(Qt::Key_Question, '?');
|
||||
ADDKEY(Qt::Key_At, '@');
|
||||
ADDKEY(Qt::Key_BracketLeft, '[');
|
||||
ADDKEY(Qt::Key_Backslash, '\\');
|
||||
ADDKEY(Qt::Key_BracketRight, ']');
|
||||
ADDKEY(Qt::Key_AsciiCircum, '^');
|
||||
ADDKEY(Qt::Key_Underscore, '_');
|
||||
ADDKEY(Qt::Key_QuoteLeft, '`');
|
||||
ADDKEY(Qt::Key_BraceLeft, '{');
|
||||
ADDKEY(Qt::Key_Bar, '|');
|
||||
ADDKEY(Qt::Key_BraceRight, '}');
|
||||
ADDKEY(Qt::Key_AsciiTilde, '~');
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ch;
|
||||
}
|
||||
|
||||
bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc,
|
||||
QTextCursor::MoveMode p_moveMode,
|
||||
const Token &p_token, int p_repeat)
|
||||
@ -3247,6 +3367,20 @@ bool VVim::expectingCharacterTarget() const
|
||||
|| key == Key(Qt::Key_T, Qt::ShiftModifier));
|
||||
}
|
||||
|
||||
bool VVim::expectingCommandLineInput() const
|
||||
{
|
||||
return m_cmdMode;
|
||||
}
|
||||
|
||||
bool VVim::expectingLeaderSequence() const
|
||||
{
|
||||
if (m_replayLeaderSequence || m_keys.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_keys.first() == Key(Qt::Key_Backslash);
|
||||
}
|
||||
|
||||
QChar VVim::keyToRegisterName(const Key &p_key) const
|
||||
{
|
||||
if (p_key.isAlphabet()) {
|
||||
@ -3301,6 +3435,24 @@ bool VVim::hasActionToken() const
|
||||
return has;
|
||||
}
|
||||
|
||||
bool VVim::hasRepeatToken() const
|
||||
{
|
||||
// There will be only one repeat token.
|
||||
bool has = false;
|
||||
if (m_tokens.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_tokens.size(); ++i) {
|
||||
if (m_tokens.at(i).isRepeat()) {
|
||||
V_ASSERT(!has);
|
||||
has = true;
|
||||
}
|
||||
}
|
||||
|
||||
return has;
|
||||
}
|
||||
|
||||
bool VVim::hasActionTokenValidForTextObject() const
|
||||
{
|
||||
if (hasActionToken()) {
|
||||
@ -3350,6 +3502,19 @@ const VVim::Token *VVim::getActionToken() const
|
||||
return &m_tokens.first();
|
||||
}
|
||||
|
||||
VVim::Token *VVim::getRepeatToken()
|
||||
{
|
||||
V_ASSERT(hasRepeatToken());
|
||||
|
||||
for (auto & token : m_tokens) {
|
||||
if (token.isRepeat()) {
|
||||
return &token;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void VVim::addRangeToken(Range p_range)
|
||||
{
|
||||
m_tokens.append(Token(p_range));
|
||||
@ -3576,3 +3741,192 @@ void VVim::setRegister(QChar p_reg)
|
||||
{
|
||||
m_regName = p_reg;
|
||||
}
|
||||
|
||||
bool VVim::checkMode(VimMode p_mode)
|
||||
{
|
||||
return m_mode == p_mode;
|
||||
}
|
||||
|
||||
bool VVim::processCommandLine(const Key &p_key)
|
||||
{
|
||||
Q_ASSERT(m_cmdMode);
|
||||
|
||||
if (p_key == Key(Qt::Key_Return)
|
||||
|| p_key == Key(Qt::Key_Enter, Qt::KeypadModifier)) {
|
||||
// Enter, try to execute the command and exit cmd line mode.
|
||||
executeCommand(m_keys);
|
||||
m_cmdMode = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p_key.m_key == Qt::Key_Escape
|
||||
|| (p_key.m_key == Qt::Key_BracketLeft && p_key.m_modifiers == Qt::ControlModifier)) {
|
||||
// Go back to Normal mode.
|
||||
m_keys.clear();
|
||||
m_pendingKeys.clear();
|
||||
m_cmdMode = false;
|
||||
|
||||
setMode(VimMode::Normal);
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (p_key.m_key) {
|
||||
case Qt::Key_Backspace:
|
||||
// Delete one char backward.
|
||||
if (m_keys.isEmpty()) {
|
||||
// Exit command line mode.
|
||||
Q_ASSERT(m_pendingKeys.size() == 1);
|
||||
m_pendingKeys.pop_back();
|
||||
m_cmdMode = false;
|
||||
return true;
|
||||
} else {
|
||||
m_keys.pop_back();
|
||||
m_pendingKeys.pop_back();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case Qt::Key_U:
|
||||
{
|
||||
if (p_key.m_modifiers == Qt::ControlModifier) {
|
||||
// Ctrl+U, delete all input keys.
|
||||
while (!m_keys.isEmpty()) {
|
||||
m_keys.pop_back();
|
||||
m_pendingKeys.pop_back();
|
||||
}
|
||||
} else {
|
||||
// Just pend this key.
|
||||
m_pendingKeys.append(p_key);
|
||||
m_keys.append(p_key);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// Just pend this key.
|
||||
m_pendingKeys.append(p_key);
|
||||
m_keys.append(p_key);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void VVim::executeCommand(const QList<Key> &p_keys)
|
||||
{
|
||||
bool validCommand = true;
|
||||
QString msg;
|
||||
|
||||
if (p_keys.isEmpty()) {
|
||||
return;
|
||||
} if (p_keys.size() == 1) {
|
||||
const Key &key0 = p_keys.first();
|
||||
if (key0 == Key(Qt::Key_W)) {
|
||||
// :w, save current file.
|
||||
emit m_editor->saveNote();
|
||||
msg = tr("Note has been saved");
|
||||
} else if (key0 == Key(Qt::Key_Q)) {
|
||||
// :q, quit edit mode.
|
||||
emit m_editor->discardAndRead();
|
||||
msg = tr("Quit");
|
||||
} else if (key0 == Key(Qt::Key_X)) {
|
||||
// :x, save if there is any change and quit edit mode.
|
||||
emit m_editor->saveAndRead();
|
||||
msg = tr("Quit with note having been saved");
|
||||
} else {
|
||||
validCommand = false;
|
||||
}
|
||||
} else if (p_keys.size() == 2) {
|
||||
const Key &key0 = p_keys.first();
|
||||
const Key &key1 = p_keys.at(1);
|
||||
if (key0 == Key(Qt::Key_W) && key1 == Key(Qt::Key_Q)) {
|
||||
// :wq, save change and quit edit mode.
|
||||
// We treat it same as :x.
|
||||
emit m_editor->saveAndRead();
|
||||
msg = tr("Quit with note having been saved");
|
||||
} else if (key0 == Key(Qt::Key_Q) && key1 == Key(Qt::Key_Exclam, Qt::ShiftModifier)) {
|
||||
// :q!, discard change and quit edit mode.
|
||||
emit m_editor->discardAndRead();
|
||||
msg = tr("Quit");
|
||||
} else {
|
||||
validCommand = false;
|
||||
}
|
||||
} else {
|
||||
validCommand = false;
|
||||
}
|
||||
|
||||
if (!validCommand) {
|
||||
QString str;
|
||||
for (auto const & key : p_keys) {
|
||||
str.append(keyToChar(key.m_key, key.m_modifiers));
|
||||
}
|
||||
|
||||
message(tr("Not an editor command: %1").arg(str));
|
||||
} else {
|
||||
message(msg);
|
||||
}
|
||||
}
|
||||
|
||||
bool VVim::hasNonDigitPendingKeys()
|
||||
{
|
||||
for (auto const &key : m_keys) {
|
||||
if (!key.isDigit()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VVim::processLeaderSequence(const Key &p_key)
|
||||
{
|
||||
// Different from Vim:
|
||||
// If it is not a valid sequence, we just do nothing here.
|
||||
V_ASSERT(checkPendingKey(Key(Qt::Key_Backslash)));
|
||||
bool validSequence = true;
|
||||
QList<Key> replaySeq;
|
||||
|
||||
if (p_key == Key(Qt::Key_Y)) {
|
||||
// <leader>y, "+y
|
||||
replaySeq.append(Key(Qt::Key_QuoteDbl, Qt::ShiftModifier));
|
||||
replaySeq.append(Key(Qt::Key_Plus, Qt::ShiftModifier));
|
||||
replaySeq.append(Key(Qt::Key_Y));
|
||||
} else if (p_key == Key(Qt::Key_D)) {
|
||||
// <leader>d, "+d
|
||||
replaySeq.append(Key(Qt::Key_QuoteDbl, Qt::ShiftModifier));
|
||||
replaySeq.append(Key(Qt::Key_Plus, Qt::ShiftModifier));
|
||||
replaySeq.append(Key(Qt::Key_D));
|
||||
} else if (p_key == Key(Qt::Key_P)) {
|
||||
// <leader>p, "+p
|
||||
replaySeq.append(Key(Qt::Key_QuoteDbl, Qt::ShiftModifier));
|
||||
replaySeq.append(Key(Qt::Key_Plus, Qt::ShiftModifier));
|
||||
replaySeq.append(Key(Qt::Key_P));
|
||||
} else if (p_key == Key(Qt::Key_P, Qt::ShiftModifier)) {
|
||||
// <leader>P, "+P
|
||||
replaySeq.append(Key(Qt::Key_QuoteDbl, Qt::ShiftModifier));
|
||||
replaySeq.append(Key(Qt::Key_Plus, Qt::ShiftModifier));
|
||||
replaySeq.append(Key(Qt::Key_P, Qt::ShiftModifier));
|
||||
} else {
|
||||
validSequence = false;
|
||||
}
|
||||
|
||||
if (!replaySeq.isEmpty()) {
|
||||
// Replay the sequence.
|
||||
m_replayLeaderSequence = true;
|
||||
m_keys.clear();
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
m_pendingKeys.pop_back();
|
||||
}
|
||||
|
||||
for (auto const &key : replaySeq) {
|
||||
bool ret = handleKeyPressEvent(key.m_key, key.m_modifiers);
|
||||
if (!ret) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_replayLeaderSequence = false;
|
||||
}
|
||||
|
||||
return validSequence;
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ public:
|
||||
VimMode getMode() const;
|
||||
|
||||
// Set current mode.
|
||||
void setMode(VimMode p_mode);
|
||||
void setMode(VimMode p_mode, bool p_clearSelection = true);
|
||||
|
||||
// Set current register.
|
||||
void setRegister(QChar p_reg);
|
||||
@ -146,7 +146,7 @@ private:
|
||||
{
|
||||
return m_key >= Qt::Key_0
|
||||
&& m_key <= Qt::Key_9
|
||||
&& m_modifiers == Qt::NoModifier;
|
||||
&& (m_modifiers == Qt::NoModifier || m_modifiers == Qt::KeypadModifier);
|
||||
}
|
||||
|
||||
int toDigit() const
|
||||
@ -348,6 +348,9 @@ private:
|
||||
Key m_key;
|
||||
};
|
||||
|
||||
// Returns true if the event is consumed and need no more handling.
|
||||
bool handleKeyPressEvent(int key, int modifiers);
|
||||
|
||||
// Reset all key state info.
|
||||
void resetState();
|
||||
|
||||
@ -416,6 +419,12 @@ private:
|
||||
// Check m_keys to see if we are expecting a target for f/t/F/T command.
|
||||
bool expectingCharacterTarget() const;
|
||||
|
||||
// Check if we are in command line mode.
|
||||
bool expectingCommandLineInput() const;
|
||||
|
||||
// Check if we are in a leader sequence.
|
||||
bool expectingLeaderSequence() 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;
|
||||
@ -423,6 +432,9 @@ private:
|
||||
// Check if @m_tokens contains an action token.
|
||||
bool hasActionToken() const;
|
||||
|
||||
// Check if @m_tokens contains a repeat token.
|
||||
bool hasRepeatToken() const;
|
||||
|
||||
// Try to add an Action::Move action at the front if there is no any action
|
||||
// token.
|
||||
void tryAddMoveAction();
|
||||
@ -433,6 +445,9 @@ private:
|
||||
// Get the action token from m_tokens.
|
||||
const Token *getActionToken() const;
|
||||
|
||||
// Get the repeat token from m_tokens.
|
||||
Token *getRepeatToken();
|
||||
|
||||
// Add an Range token at the end of m_tokens.
|
||||
void addRangeToken(Range p_range);
|
||||
|
||||
@ -487,6 +502,32 @@ private:
|
||||
|
||||
void message(const QString &p_str);
|
||||
|
||||
// Check if m_mode equals to p_mode.
|
||||
bool checkMode(VimMode p_mode);
|
||||
|
||||
// In command line mode, read input @p_key and process it.
|
||||
// Returns true if a command has been completed, otherwise returns false.
|
||||
bool processCommandLine(const Key &p_key);
|
||||
|
||||
// Execute command specified by @p_keys.
|
||||
// @p_keys does not contain the leading colon.
|
||||
// Following commands are supported:
|
||||
// :w, :wq, :q, :q!, :x
|
||||
void executeCommand(const QList<Key> &p_keys);
|
||||
|
||||
// Check if m_keys has non-digit key.
|
||||
bool hasNonDigitPendingKeys();
|
||||
|
||||
// Reading a leader sequence, read input @p_key and process it.
|
||||
// Returns true if a sequence has been replayed or it is being read,
|
||||
// otherwise returns false.
|
||||
// Following sequences are supported:
|
||||
// y: "+y
|
||||
// d: "+d
|
||||
// p: "+p
|
||||
// P: "+P
|
||||
bool processLeaderSequence(const Key &p_key);
|
||||
|
||||
VEdit *m_editor;
|
||||
const VEditConfig *m_editConfig;
|
||||
VimMode m_mode;
|
||||
@ -512,6 +553,17 @@ private:
|
||||
// Last f/F/t/T Token.
|
||||
Token m_lastFindToken;
|
||||
|
||||
// Whether in command line mode.
|
||||
bool m_cmdMode;
|
||||
|
||||
// The leader key, which is Key_Space by default.
|
||||
Key m_leaderKey;
|
||||
|
||||
// Whether we are parsing a leader sequence.
|
||||
// We will map a leader sequence to another actual sequence. When replaying
|
||||
// this actual sequence, m_leaderSequence will be true.
|
||||
bool m_replayLeaderSequence;
|
||||
|
||||
static const QChar c_unnamedRegister;
|
||||
static const QChar c_blackHoleRegister;
|
||||
static const QChar c_selectionRegister;
|
||||
|
@ -86,10 +86,18 @@ public:
|
||||
void requestUpdateVimStatus();
|
||||
|
||||
signals:
|
||||
// Request VEditTab to save and exit edit mode.
|
||||
void saveAndRead();
|
||||
|
||||
// Request VEditTab to discard and exit edit mode.
|
||||
void discardAndRead();
|
||||
|
||||
// Request VEditTab to edit current note.
|
||||
void editNote();
|
||||
|
||||
// Request VEditTab to save this file.
|
||||
void saveNote();
|
||||
|
||||
// Emit when m_config has been updated.
|
||||
void configUpdated();
|
||||
|
||||
|
@ -311,6 +311,8 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
|
||||
break;
|
||||
}
|
||||
|
||||
case Qt::Key_Enter:
|
||||
// Fall through.
|
||||
case Qt::Key_Return:
|
||||
{
|
||||
if (handleKeyReturn(p_event)) {
|
||||
@ -326,8 +328,12 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
|
||||
|
||||
exit:
|
||||
// Qt::Key_Return, Qt::Key_Tab and Qt::Key_Backtab will handle m_autoIndentPos.
|
||||
if (key != Qt::Key_Return && key != Qt::Key_Tab && key != Qt::Key_Backtab &&
|
||||
key != Qt::Key_Shift) {
|
||||
if (key != Qt::Key_Return
|
||||
&& key != Qt::Key_Enter
|
||||
&& key != Qt::Key_Tab
|
||||
&& key != Qt::Key_Backtab
|
||||
&& key != Qt::Key_Shift
|
||||
&& key != Qt::Key_Control) {
|
||||
m_autoIndentPos = -1;
|
||||
}
|
||||
|
||||
|
@ -58,6 +58,8 @@ void VMdTab::setupUI()
|
||||
this, &VMdTab::saveAndRead);
|
||||
connect(m_editor, &VEdit::discardAndRead,
|
||||
this, &VMdTab::discardAndRead);
|
||||
connect(m_editor, &VEdit::saveNote,
|
||||
this, &VMdTab::saveFile);
|
||||
connect(m_editor, &VEdit::statusMessage,
|
||||
this, &VEditTab::statusMessage);
|
||||
connect(m_editor, &VEdit::vimStatusUpdated,
|
||||
|
Loading…
x
Reference in New Issue
Block a user