vim-mode: support f/F/t/T movement

Support `;` and `,` to repeat last find movement.
This commit is contained in:
Le Tan 2017-06-17 13:57:03 +08:00
parent 5047e19b24
commit 7871965bf8
4 changed files with 348 additions and 12 deletions

View File

@ -210,3 +210,44 @@ void VEditUtils::unindentBlock(QTextCursor &p_cursor,
} }
} }
} }
bool VEditUtils::findTargetWithinBlock(QTextCursor &p_cursor,
QTextCursor::MoveMode p_mode,
QChar p_target,
bool p_forward,
bool p_inclusive)
{
qDebug() << "find target" << p_target << p_forward << p_inclusive;
QTextBlock block = p_cursor.block();
QString text = block.text();
int pib = p_cursor.positionInBlock();
int delta = p_forward ? 1 : -1;
int idx = pib + delta;
repeat:
for (; idx < text.size() && idx >= 0; idx += delta) {
if (text[idx] == p_target) {
break;
}
}
if (idx < 0 || idx >= text.size()) {
return false;
}
if ((p_forward && p_inclusive && p_mode == QTextCursor::KeepAnchor)
|| (!p_forward && !p_inclusive)) {
++idx;
} else if (p_forward && !p_inclusive && p_mode == QTextCursor::MoveAnchor) {
--idx;
}
if (idx == pib) {
// We need to skip current match.
idx = pib + delta * 2;
goto repeat;
}
p_cursor.setPosition(block.position() + idx, p_mode);
return true;
}

View File

@ -63,6 +63,14 @@ public:
static void unindentBlock(QTextCursor &p_cursor, static void unindentBlock(QTextCursor &p_cursor,
const QString &p_indentationText); const QString &p_indentationText);
// Find a char within a block.
// Returns true if target is found.
static bool findTargetWithinBlock(QTextCursor &p_cursor,
QTextCursor::MoveMode p_mode,
QChar p_target,
bool p_forward,
bool p_inclusive);
private: private:
VEditUtils() {} VEditUtils() {}
}; };

View File

@ -308,6 +308,34 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
goto clear_accept; goto clear_accept;
} }
if (expectingCharacterTarget()) {
// Expecting a target character for f/F/t/T.
Movement mm = Movement::Invalid;
const Key &aKey = m_keys.first();
if (aKey.m_key == Qt::Key_F) {
if (aKey.m_modifiers == Qt::NoModifier) {
mm = Movement::FindForward;
} else {
mm = Movement::FindBackward;
}
} else {
if (aKey.m_modifiers == Qt::NoModifier) {
mm = Movement::TillForward;
} else {
mm = Movement::TillBackward;
}
}
V_ASSERT(mm != Movement::Invalid);
tryAddMoveAction();
addMovementToken(mm, keyInfo);
m_lastFindToken = m_tokens.last();
processCommand(m_tokens);
goto clear_accept;
}
// We will add key to m_keys. If all m_keys can combined to a token, add // 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. // a new token to m_tokens, clear m_keys and try to process m_tokens.
switch (key) { switch (key) {
@ -1164,6 +1192,82 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
break; break;
} }
case Qt::Key_F:
{
if (m_mode == VimMode::VisualLine) {
break;
}
if (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier) {
// f/F, find forward/backward within a block.
tryGetRepeatToken(m_keys, m_tokens);
if (m_keys.isEmpty()) {
m_keys.append(keyInfo);
goto accept;
}
break;
}
break;
}
case Qt::Key_T:
{
if (m_mode == VimMode::VisualLine) {
break;
}
if (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier) {
// t/T, find till forward/backward within a block.
tryGetRepeatToken(m_keys, m_tokens);
if (m_keys.isEmpty()) {
m_keys.append(keyInfo);
goto accept;
}
break;
}
break;
}
case Qt::Key_Comma:
{
if (m_mode == VimMode::VisualLine) {
break;
}
// ,, repeat last find target movement, but reversely.
tryGetRepeatToken(m_keys, m_tokens);
if (m_keys.isEmpty()) {
repeatLastFindMovement(true);
break;
}
break;
}
case Qt::Key_Semicolon:
{
if (m_mode == VimMode::VisualLine) {
break;
}
// ;, repeat last find target movement.
tryGetRepeatToken(m_keys, m_tokens);
if (m_keys.isEmpty()) {
repeatLastFindMovement(false);
break;
}
break;
}
default: default:
break; break;
} }
@ -1327,7 +1431,7 @@ void VVim::processMoveAction(QList<Token> &p_tokens)
? QTextCursor::KeepAnchor ? QTextCursor::KeepAnchor
: QTextCursor::MoveAnchor; : QTextCursor::MoveAnchor;
bool hasMoved = processMovement(cursor, m_editor->document(), bool hasMoved = processMovement(cursor, m_editor->document(),
moveMode, mvToken.m_movement, repeat); moveMode, mvToken, repeat);
if (hasMoved) { if (hasMoved) {
// Maintain positionInBlock. // Maintain positionInBlock.
@ -1358,13 +1462,80 @@ 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, bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc,
QTextCursor::MoveMode p_moveMode, QTextCursor::MoveMode p_moveMode,
Movement p_movement, int p_repeat) const Token &p_token, int p_repeat)
{ {
bool hasMoved = false; V_ASSERT(p_token.isMovement());
switch (p_movement) { bool hasMoved = false;
bool inclusive = true;
bool forward = true;
switch (p_token.m_movement) {
case Movement::Left: case Movement::Left:
{ {
if (p_repeat == -1) { if (p_repeat == -1) {
@ -1765,6 +1936,29 @@ bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc,
break; break;
} }
case Movement::TillBackward:
forward = false;
// Fall through.
case Movement::TillForward:
inclusive = false;
goto handle_target;
case Movement::FindBackward:
forward = false;
// Fall through.
case Movement::FindForward:
{
handle_target:
const Key &key = p_token.m_key;
QChar target = keyToChar(key.m_key, key.m_modifiers);
if (!target.isNull()) {
hasMoved = VEditUtils::findTargetWithinBlock(p_cursor, p_moveMode,
target, forward, inclusive);
}
break;
}
default: default:
break; break;
} }
@ -1979,7 +2173,7 @@ void VVim::processDeleteAction(QList<Token> &p_tokens)
} }
cursor.beginEditBlock(); cursor.beginEditBlock();
hasMoved = processMovement(cursor, doc, moveMode, to.m_movement, repeat); hasMoved = processMovement(cursor, doc, moveMode, to, repeat);
if (repeat == -1) { if (repeat == -1) {
repeat = 1; repeat = 1;
} }
@ -2235,7 +2429,7 @@ void VVim::processCopyAction(QList<Token> &p_tokens)
} }
cursor.beginEditBlock(); cursor.beginEditBlock();
changed = processMovement(cursor, doc, moveMode, to.m_movement, repeat); changed = processMovement(cursor, doc, moveMode, to, repeat);
if (repeat == -1) { if (repeat == -1) {
repeat = 1; repeat = 1;
} }
@ -2545,7 +2739,7 @@ void VVim::processChangeAction(QList<Token> &p_tokens)
} }
cursor.beginEditBlock(); cursor.beginEditBlock();
hasMoved = processMovement(cursor, doc, moveMode, to.m_movement, repeat); hasMoved = processMovement(cursor, doc, moveMode, to, repeat);
if (repeat == -1) { if (repeat == -1) {
repeat = 1; repeat = 1;
} }
@ -2793,7 +2987,7 @@ void VVim::processIndentAction(QList<Token> &p_tokens, bool p_isIndent)
processMovement(cursor, processMovement(cursor,
doc, doc,
QTextCursor::KeepAnchor, QTextCursor::KeepAnchor,
to.m_movement, to,
repeat); repeat);
VEditUtils::indentSelectedBlocks(doc, VEditUtils::indentSelectedBlocks(doc,
cursor, cursor,
@ -2852,7 +3046,7 @@ void VVim::processToLowerAction(QList<Token> &p_tokens, bool p_toLower)
changed = processMovement(cursor, changed = processMovement(cursor,
doc, doc,
moveMode, moveMode,
to.m_movement, to,
repeat); repeat);
if (repeat == -1) { if (repeat == -1) {
repeat = 1; repeat = 1;
@ -3051,6 +3245,19 @@ bool VVim::expectingRegisterName() const
&& m_keys.at(0) == Key(Qt::Key_QuoteDbl, Qt::ShiftModifier); && m_keys.at(0) == Key(Qt::Key_QuoteDbl, Qt::ShiftModifier);
} }
bool VVim::expectingCharacterTarget() const
{
if (m_keys.size() != 1) {
return false;
}
const Key &key = m_keys.first();
return (key == Key(Qt::Key_F, Qt::NoModifier)
|| key == Key(Qt::Key_F, Qt::ShiftModifier)
|| key == Key(Qt::Key_T, Qt::NoModifier)
|| key == Key(Qt::Key_T, Qt::ShiftModifier));
}
QChar VVim::keyToRegisterName(const Key &p_key) const QChar VVim::keyToRegisterName(const Key &p_key) const
{ {
if (p_key.isAlphabet()) { if (p_key.isAlphabet()) {
@ -3164,6 +3371,11 @@ void VVim::addMovementToken(Movement p_movement)
m_tokens.append(Token(p_movement)); m_tokens.append(Token(p_movement));
} }
void VVim::addMovementToken(Movement p_movement, Key p_key)
{
m_tokens.append(Token(p_movement, p_key));
}
void VVim::deleteSelectedText(QTextCursor &p_cursor, bool p_clearEmptyBlock) void VVim::deleteSelectedText(QTextCursor &p_cursor, bool p_clearEmptyBlock)
{ {
if (p_cursor.hasSelection()) { if (p_cursor.hasSelection()) {
@ -3303,3 +3515,44 @@ const QString &VVim::Register::read()
return m_value; return m_value;
} }
void VVim::repeatLastFindMovement(bool p_reverse)
{
if (!m_lastFindToken.isValid()) {
return;
}
V_ASSERT(m_lastFindToken.isMovement());
Movement mm = m_lastFindToken.m_movement;
Key key = m_lastFindToken.m_key;
V_ASSERT(key.isValid());
if (p_reverse) {
switch (mm) {
case Movement::FindForward:
mm = Movement::FindBackward;
break;
case Movement::FindBackward:
mm = Movement::FindForward;
break;
case Movement::TillForward:
mm = Movement::TillBackward;
break;
case Movement::TillBackward:
mm = Movement::TillForward;
break;
default:
break;
}
}
tryAddMoveAction();
addMovementToken(mm, key);
processCommand(m_tokens);
}

View File

@ -56,6 +56,8 @@ private:
{ {
} }
Key() : m_key(-1), m_modifiers(Qt::NoModifier) {}
int m_key; int m_key;
int m_modifiers; int m_modifiers;
@ -89,6 +91,11 @@ private:
} }
} }
bool isValid() const
{
return m_key > -1 && m_modifiers > -1;
}
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;
@ -138,6 +145,10 @@ private:
WORDBackward, WORDBackward,
BackwardEndOfWord, BackwardEndOfWord,
BackwardEndOfWORD, BackwardEndOfWORD,
FindForward,
FindBackward,
TillForward,
TillBackward,
Invalid Invalid
}; };
@ -177,6 +188,9 @@ private:
Token(Movement p_movement) Token(Movement p_movement)
: m_type(TokenType::Movement), m_movement(p_movement) {} : m_type(TokenType::Movement), m_movement(p_movement) {}
Token(Movement p_movement, Key p_key)
: m_type(TokenType::Movement), m_movement(p_movement), m_key(p_key) {}
Token(Range p_range) Token(Range p_range)
: m_type(TokenType::Range), m_range(p_range) {} : m_type(TokenType::Range), m_range(p_range) {}
@ -202,6 +216,11 @@ private:
return m_type == TokenType::Range; return m_type == TokenType::Range;
} }
bool isValid() const
{
return m_type != TokenType::Invalid;
}
QString toString() const QString toString() const
{ {
QString str; QString str;
@ -235,9 +254,12 @@ private:
{ {
Action m_action; Action m_action;
int m_repeat; int m_repeat;
Movement m_movement;
Range m_range; Range m_range;
Movement m_movement;
}; };
// Used in some Movement.
Key m_key;
}; };
struct Register struct Register
@ -358,6 +380,9 @@ private:
// Check m_keys to see if we are expecting a register name. // Check m_keys to see if we are expecting a register name.
bool expectingRegisterName() const; bool expectingRegisterName() const;
// Check m_keys to see if we are expecting a target for f/t/F/T command.
bool expectingCharacterTarget() const;
// Return the corresponding register name of @p_key. // Return the corresponding register name of @p_key.
// If @p_key is not a valid register name, return a NULL QChar. // If @p_key is not a valid register name, return a NULL QChar.
QChar keyToRegisterName(const Key &p_key) const; QChar keyToRegisterName(const Key &p_key) const;
@ -381,6 +406,9 @@ private:
// Add an Movement token at the end of m_tokens. // Add an Movement token at the end of m_tokens.
void addMovementToken(Movement p_movement); void addMovementToken(Movement p_movement);
// Add an Movement token at the end of m_tokens.
void addMovementToken(Movement p_movement, Key p_key);
// Delete selected text if there is any. // Delete selected text if there is any.
// @p_clearEmptyBlock: whether to remove the empty block after deletion. // @p_clearEmptyBlock: whether to remove the empty block after deletion.
void deleteSelectedText(QTextCursor &p_cursor, bool p_clearEmptyBlock); void deleteSelectedText(QTextCursor &p_cursor, bool p_clearEmptyBlock);
@ -401,11 +429,11 @@ private:
// Remove QChar::ObjectReplacementCharacter before saving. // Remove QChar::ObjectReplacementCharacter before saving.
void saveToRegister(const QString &p_text); void saveToRegister(const QString &p_text);
// Move @p_cursor according to @p_moveMode and @p_movement. // Move @p_cursor according to @p_moveMode and @p_token.
// Return true if it has moved @p_cursor. // Return true if it has moved @p_cursor.
bool processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc, bool processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc,
QTextCursor::MoveMode p_moveMode, QTextCursor::MoveMode p_moveMode,
Movement p_movement, int p_repeat); const Token &p_token, int p_repeat);
// Move @p_cursor according to @p_moveMode and @p_range. // Move @p_cursor according to @p_moveMode and @p_range.
// Return true if it has moved @p_cursor. // Return true if it has moved @p_cursor.
@ -421,6 +449,9 @@ private:
// Check if m_tokens only contains action token @p_action. // Check if m_tokens only contains action token @p_action.
bool checkActionToken(Action p_action) const; bool checkActionToken(Action p_action) const;
// Repeat m_lastFindToken.
void repeatLastFindMovement(bool p_reverse);
VEdit *m_editor; VEdit *m_editor;
const VEditConfig *m_editConfig; const VEditConfig *m_editConfig;
VimMode m_mode; VimMode m_mode;
@ -440,6 +471,9 @@ private:
// Currently used register. // Currently used register.
QChar m_regName; QChar m_regName;
// Last f/F/t/T Token.
Token m_lastFindToken;
static const QChar c_unnamedRegister; static const QChar c_unnamedRegister;
static const QChar c_blackHoleRegister; static const QChar c_blackHoleRegister;
static const QChar c_selectionRegister; static const QChar c_selectionRegister;