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,
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:
VEditUtils() {}
};

View File

@ -308,6 +308,34 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
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
// a new token to m_tokens, clear m_keys and try to process m_tokens.
switch (key) {
@ -1164,6 +1192,82 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
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:
break;
}
@ -1327,7 +1431,7 @@ void VVim::processMoveAction(QList<Token> &p_tokens)
? QTextCursor::KeepAnchor
: QTextCursor::MoveAnchor;
bool hasMoved = processMovement(cursor, m_editor->document(),
moveMode, mvToken.m_movement, repeat);
moveMode, mvToken, repeat);
if (hasMoved) {
// 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,
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:
{
if (p_repeat == -1) {
@ -1765,6 +1936,29 @@ bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc,
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:
break;
}
@ -1979,7 +2173,7 @@ void VVim::processDeleteAction(QList<Token> &p_tokens)
}
cursor.beginEditBlock();
hasMoved = processMovement(cursor, doc, moveMode, to.m_movement, repeat);
hasMoved = processMovement(cursor, doc, moveMode, to, repeat);
if (repeat == -1) {
repeat = 1;
}
@ -2235,7 +2429,7 @@ void VVim::processCopyAction(QList<Token> &p_tokens)
}
cursor.beginEditBlock();
changed = processMovement(cursor, doc, moveMode, to.m_movement, repeat);
changed = processMovement(cursor, doc, moveMode, to, repeat);
if (repeat == -1) {
repeat = 1;
}
@ -2545,7 +2739,7 @@ void VVim::processChangeAction(QList<Token> &p_tokens)
}
cursor.beginEditBlock();
hasMoved = processMovement(cursor, doc, moveMode, to.m_movement, repeat);
hasMoved = processMovement(cursor, doc, moveMode, to, repeat);
if (repeat == -1) {
repeat = 1;
}
@ -2793,7 +2987,7 @@ void VVim::processIndentAction(QList<Token> &p_tokens, bool p_isIndent)
processMovement(cursor,
doc,
QTextCursor::KeepAnchor,
to.m_movement,
to,
repeat);
VEditUtils::indentSelectedBlocks(doc,
cursor,
@ -2852,7 +3046,7 @@ void VVim::processToLowerAction(QList<Token> &p_tokens, bool p_toLower)
changed = processMovement(cursor,
doc,
moveMode,
to.m_movement,
to,
repeat);
if (repeat == -1) {
repeat = 1;
@ -3051,6 +3245,19 @@ bool VVim::expectingRegisterName() const
&& 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
{
if (p_key.isAlphabet()) {
@ -3164,6 +3371,11 @@ void VVim::addMovementToken(Movement 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)
{
if (p_cursor.hasSelection()) {
@ -3303,3 +3515,44 @@ const QString &VVim::Register::read()
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_modifiers;
@ -89,6 +91,11 @@ private:
}
}
bool isValid() const
{
return m_key > -1 && m_modifiers > -1;
}
bool operator==(const Key &p_key) const
{
return p_key.m_key == m_key && p_key.m_modifiers == m_modifiers;
@ -138,6 +145,10 @@ private:
WORDBackward,
BackwardEndOfWord,
BackwardEndOfWORD,
FindForward,
FindBackward,
TillForward,
TillBackward,
Invalid
};
@ -177,6 +188,9 @@ private:
Token(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)
: m_type(TokenType::Range), m_range(p_range) {}
@ -202,6 +216,11 @@ private:
return m_type == TokenType::Range;
}
bool isValid() const
{
return m_type != TokenType::Invalid;
}
QString toString() const
{
QString str;
@ -235,9 +254,12 @@ private:
{
Action m_action;
int m_repeat;
Movement m_movement;
Range m_range;
Movement m_movement;
};
// Used in some Movement.
Key m_key;
};
struct Register
@ -358,6 +380,9 @@ private:
// Check m_keys to see if we are expecting a register name.
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.
// If @p_key is not a valid register name, return a NULL QChar.
QChar keyToRegisterName(const Key &p_key) const;
@ -381,6 +406,9 @@ private:
// Add an Movement token at the end of m_tokens.
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.
// @p_clearEmptyBlock: whether to remove the empty block after deletion.
void deleteSelectedText(QTextCursor &p_cursor, bool p_clearEmptyBlock);
@ -401,11 +429,11 @@ private:
// Remove QChar::ObjectReplacementCharacter before saving.
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.
bool processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc,
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.
// Return true if it has moved @p_cursor.
@ -421,6 +449,9 @@ private:
// Check if m_tokens only contains action token @p_action.
bool checkActionToken(Action p_action) const;
// Repeat m_lastFindToken.
void repeatLastFindMovement(bool p_reverse);
VEdit *m_editor;
const VEditConfig *m_editConfig;
VimMode m_mode;
@ -440,6 +471,9 @@ private:
// Currently used register.
QChar m_regName;
// Last f/F/t/T Token.
Token m_lastFindToken;
static const QChar c_unnamedRegister;
static const QChar c_blackHoleRegister;
static const QChar c_selectionRegister;