mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
vim-mode: support f/F/t/T movement
Support `;` and `,` to repeat last find movement.
This commit is contained in:
parent
5047e19b24
commit
7871965bf8
@ -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;
|
||||
}
|
||||
|
@ -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() {}
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user