- `Ctrl+Tab` and `Ctrl+Shift+Tab` in Normal mode to alternate tabs;
- Support `:<num>` command for line jump;
- Support `%`;
This commit is contained in:
Le Tan 2017-07-05 19:42:40 +08:00
parent 33146efcd9
commit 71bcfe3019
4 changed files with 217 additions and 51 deletions

View File

@ -254,6 +254,50 @@ bool VEditUtils::findTargetWithinBlock(QTextCursor &p_cursor,
return true; return true;
} }
int VEditUtils::findTargetsWithinBlock(QTextCursor &p_cursor,
QTextCursor::MoveMode p_mode,
const QList<QChar> &p_targets,
bool p_forward,
bool p_inclusive)
{
if (p_targets.isEmpty()) {
return -1;
}
int targetIdx = -1;
QTextBlock block = p_cursor.block();
QString text = block.text();
int pib = p_cursor.positionInBlock();
int delta = p_forward ? 1 : -1;
// The index to start searching.
int idx = pib + (p_inclusive ? delta : 2 * delta);
for (; idx < text.size() && idx >= 0; idx += delta) {
int index = p_targets.indexOf(text[idx]);
if (index != -1) {
targetIdx = index;
break;
}
}
if (idx < 0 || idx >= text.size()) {
return -1;
}
// text[idx] is the target character.
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;
}
p_cursor.setPosition(block.position() + idx, p_mode);
return targetIdx;
}
int VEditUtils::selectedBlockCount(const QTextCursor &p_cursor) int VEditUtils::selectedBlockCount(const QTextCursor &p_cursor)
{ {
if (!p_cursor.hasSelection()) { if (!p_cursor.hasSelection()) {

View File

@ -65,8 +65,10 @@ public:
static void unindentBlock(QTextCursor &p_cursor, static void unindentBlock(QTextCursor &p_cursor,
const QString &p_indentationText); const QString &p_indentationText);
// Find @p_repeat th occurence a char within a block. // Find @p_repeat th occurence of a char within a block.
// Returns true if target is found. // Returns true if target is found.
// Please pay attention to the one-step-forward/backward in KeepAnchor mode
// and exclusive case.
static bool findTargetWithinBlock(QTextCursor &p_cursor, static bool findTargetWithinBlock(QTextCursor &p_cursor,
QTextCursor::MoveMode p_mode, QTextCursor::MoveMode p_mode,
QChar p_target, QChar p_target,
@ -74,6 +76,17 @@ public:
bool p_inclusive, bool p_inclusive,
int p_repeat); int p_repeat);
// Find th first occurence of a char in @p_targets within a block.
// Returns the index of the found char in @p_targets if found.
// Returns -1 if none of the @p_targets is found.
// Please pay attention to the one-step-forward/backward in KeepAnchor mode
// and exclusive case.
static int findTargetsWithinBlock(QTextCursor &p_cursor,
QTextCursor::MoveMode p_mode,
const QList<QChar> &p_targets,
bool p_forward,
bool p_inclusive);
// Find a pair target (@p_opening, @p_closing) containing current cursor and // Find a pair target (@p_opening, @p_closing) containing current cursor and
// select the range between them. // select the range between them.
// Need to call setTextCursor() to make it take effect. // Need to call setTextCursor() to make it take effect.

View File

@ -485,6 +485,13 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
goto accept; goto accept;
} }
// Ctrl+Tab and Ctrl+Shift+BackTab to alternate tabs.
if ((key == Qt::Key_Tab && modifiers == Qt::ControlModifier)
|| (key == Qt::Key_Backtab && modifiers == (Qt::ShiftModifier | Qt::ControlModifier))) {
// Let it be handled outside VVim.
goto exit;
}
if (m_replayLeaderSequence) { if (m_replayLeaderSequence) {
qDebug() << "replaying sequence" << keyToChar(key, modifiers); qDebug() << "replaying sequence" << keyToChar(key, modifiers);
} }
@ -1794,6 +1801,13 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
case Qt::Key_Percent: case Qt::Key_Percent:
{ {
if (modifiers == Qt::ShiftModifier) { if (modifiers == Qt::ShiftModifier) {
if (m_keys.isEmpty()) {
// %, FindPair movement.
tryAddMoveAction();
addMovementToken(Movement::FindPair);
processCommand(m_tokens);
break;
} else {
tryGetRepeatToken(m_keys, m_tokens); tryGetRepeatToken(m_keys, m_tokens);
if (m_keys.isEmpty() && hasRepeatToken()) { if (m_keys.isEmpty() && hasRepeatToken()) {
// xx%, jump to a certain line (percentage of the documents). // xx%, jump to a certain line (percentage of the documents).
@ -1812,6 +1826,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
processCommand(m_tokens); processCommand(m_tokens);
break; break;
} }
}
break; break;
} }
@ -2199,8 +2214,7 @@ void VVim::processMoveAction(QList<Token> &p_tokens)
|| m_mode == VimMode::VisualLine) || m_mode == VimMode::VisualLine)
? QTextCursor::KeepAnchor ? QTextCursor::KeepAnchor
: QTextCursor::MoveAnchor; : QTextCursor::MoveAnchor;
bool hasMoved = processMovement(cursor, m_editor->document(), bool hasMoved = processMovement(cursor, moveMode, mvToken, repeat);
moveMode, mvToken, repeat);
if (hasMoved) { if (hasMoved) {
// Maintain positionInBlock. // Maintain positionInBlock.
@ -2231,15 +2245,17 @@ void VVim::processMoveAction(QList<Token> &p_tokens)
} }
} }
bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc, bool VVim::processMovement(QTextCursor &p_cursor,
QTextCursor::MoveMode p_moveMode, QTextCursor::MoveMode p_moveMode,
const Token &p_token, int p_repeat) const Token &p_token,
int p_repeat)
{ {
V_ASSERT(p_token.isMovement()); V_ASSERT(p_token.isMovement());
bool hasMoved = false; bool hasMoved = false;
bool inclusive = true; bool inclusive = true;
bool forward = true; bool forward = true;
QTextDocument *doc = p_cursor.document();
switch (p_token.m_movement) { switch (p_token.m_movement) {
case Movement::Left: case Movement::Left:
@ -2301,7 +2317,7 @@ bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc,
p_repeat = 1; p_repeat = 1;
} }
int blockCount = p_doc->blockCount(); int blockCount = doc->blockCount();
p_repeat = qMin(blockCount - 1 - p_cursor.block().blockNumber(), p_repeat); p_repeat = qMin(blockCount - 1 - p_cursor.block().blockNumber(), p_repeat);
if (p_repeat > 0) { if (p_repeat > 0) {
@ -2343,7 +2359,7 @@ bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc,
int blockStep = blockCountOfPageStep() * p_repeat; int blockStep = blockCountOfPageStep() * p_repeat;
int block = p_cursor.block().blockNumber(); int block = p_cursor.block().blockNumber();
block = qMax(0, block - blockStep); block = qMax(0, block - blockStep);
p_cursor.setPosition(p_doc->findBlockByNumber(block).position(), p_moveMode); p_cursor.setPosition(doc->findBlockByNumber(block).position(), p_moveMode);
hasMoved = true; hasMoved = true;
break; break;
@ -2357,8 +2373,8 @@ bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc,
int blockStep = blockCountOfPageStep() * p_repeat; int blockStep = blockCountOfPageStep() * p_repeat;
int block = p_cursor.block().blockNumber(); int block = p_cursor.block().blockNumber();
block = qMin(block + blockStep, p_doc->blockCount() - 1); block = qMin(block + blockStep, doc->blockCount() - 1);
p_cursor.setPosition(p_doc->findBlockByNumber(block).position(), p_moveMode); p_cursor.setPosition(doc->findBlockByNumber(block).position(), p_moveMode);
hasMoved = true; hasMoved = true;
break; break;
@ -2375,7 +2391,7 @@ bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc,
blockStep = p_repeat * halfBlockStep; blockStep = p_repeat * halfBlockStep;
int block = p_cursor.block().blockNumber(); int block = p_cursor.block().blockNumber();
block = qMax(0, block - blockStep); block = qMax(0, block - blockStep);
p_cursor.setPosition(p_doc->findBlockByNumber(block).position(), p_moveMode); p_cursor.setPosition(doc->findBlockByNumber(block).position(), p_moveMode);
hasMoved = true; hasMoved = true;
break; break;
@ -2391,8 +2407,8 @@ bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc,
int halfBlockStep = qMax(blockStep / 2, 1); int halfBlockStep = qMax(blockStep / 2, 1);
blockStep = p_repeat * halfBlockStep; blockStep = p_repeat * halfBlockStep;
int block = p_cursor.block().blockNumber(); int block = p_cursor.block().blockNumber();
block = qMin(block + blockStep, p_doc->blockCount() - 1); block = qMin(block + blockStep, doc->blockCount() - 1);
p_cursor.setPosition(p_doc->findBlockByNumber(block).position(), p_moveMode); p_cursor.setPosition(doc->findBlockByNumber(block).position(), p_moveMode);
hasMoved = true; hasMoved = true;
break; break;
@ -2446,7 +2462,7 @@ bool VVim::processMovement(QTextCursor &p_cursor, const QTextDocument *p_doc,
m_locations.addLocation(p_cursor); m_locations.addLocation(p_cursor);
// @p_repeat starts from 1 while block number starts from 0. // @p_repeat starts from 1 while block number starts from 0.
QTextBlock block = p_doc->findBlockByNumber(p_repeat - 1); QTextBlock block = doc->findBlockByNumber(p_repeat - 1);
if (block.isValid()) { if (block.isValid()) {
p_cursor.setPosition(block.position(), p_moveMode); p_cursor.setPosition(block.position(), p_moveMode);
} else { } else {
@ -2693,7 +2709,7 @@ handle_target:
QChar target = keyToChar(key.m_key, key.m_modifiers); QChar target = keyToChar(key.m_key, key.m_modifiers);
Location loc = m_marks.getMarkLocation(target); Location loc = m_marks.getMarkLocation(target);
if (loc.isValid()) { if (loc.isValid()) {
if (loc.m_blockNumber >= p_doc->blockCount()) { if (loc.m_blockNumber >= doc->blockCount()) {
// Invalid block number. // Invalid block number.
message(tr("Mark not set")); message(tr("Mark not set"));
m_marks.clearMark(target); m_marks.clearMark(target);
@ -2703,7 +2719,7 @@ handle_target:
// Different from Vim: // Different from Vim:
// We just use the block number for mark, so if we delete the line // We just use the block number for mark, so if we delete the line
// where the mark locates, we could not detect if it is set or not. // where the mark locates, we could not detect if it is set or not.
QTextBlock block = p_doc->findBlockByNumber(loc.m_blockNumber); QTextBlock block = doc->findBlockByNumber(loc.m_blockNumber);
p_cursor.setPosition(block.position(), p_moveMode); p_cursor.setPosition(block.position(), p_moveMode);
if (p_token.m_movement == Movement::MarkJump) { if (p_token.m_movement == Movement::MarkJump) {
setCursorPositionInBlock(p_cursor, loc.m_positionInBlock, p_moveMode); setCursorPositionInBlock(p_cursor, loc.m_positionInBlock, p_moveMode);
@ -2718,6 +2734,80 @@ handle_target:
break; break;
} }
case Movement::FindPair:
{
Q_ASSERT(p_repeat == -1);
int anchor = p_cursor.anchor();
int position = p_cursor.position();
QList<QPair<QChar, QChar>> pairs;
pairs.append(QPair<QChar, QChar>('(', ')'));
pairs.append(QPair<QChar, QChar>('[', ']'));
pairs.append(QPair<QChar, QChar>('{', '}'));
// Find forward for a pair (), [], and {}.
QList<QChar> targets;
for (auto const & pair : pairs) {
targets.append(pair.first);
targets.append(pair.second);
}
// First check if current char hits the targets.
QChar ch = doc->characterAt(position);
int idx = targets.indexOf(ch);
if (idx == -1) {
// Use MoveAnchor to avoid the one-step-forward.
idx = VEditUtils::findTargetsWithinBlock(p_cursor,
QTextCursor::MoveAnchor,
targets,
true,
true);
}
if (idx == -1) {
break;
}
idx /= 2;
int pairPosition = p_cursor.position();
bool ret = VEditUtils::selectPairTargetAround(p_cursor,
pairs.at(idx).first,
pairs.at(idx).second,
true,
true,
1);
if (ret) {
// Found matched pair.
int first = p_cursor.position();
int second = p_cursor.anchor();
if (first > second) {
int tmp = first;
first = second;
second = tmp;
}
--second;
int target = first;
if (first == pairPosition) {
target = second;
}
p_cursor.setPosition(anchor);
p_cursor.setPosition(target, p_moveMode);
hasMoved = true;
break;
} else {
// Restore the cursor position.
p_cursor.setPosition(anchor);
if (anchor != position) {
p_cursor.setPosition(position, QTextCursor::KeepAnchor);
}
}
break;
}
default: default:
break; break;
} }
@ -3133,7 +3223,7 @@ void VVim::processDeleteAction(QList<Token> &p_tokens)
} }
cursor.beginEditBlock(); cursor.beginEditBlock();
hasMoved = processMovement(cursor, doc, moveMode, to, repeat); hasMoved = processMovement(cursor, moveMode, to, repeat);
if (repeat == -1) { if (repeat == -1) {
repeat = 1; repeat = 1;
} }
@ -3345,7 +3435,7 @@ void VVim::processCopyAction(QList<Token> &p_tokens)
} }
cursor.beginEditBlock(); cursor.beginEditBlock();
changed = processMovement(cursor, doc, moveMode, to, repeat); changed = processMovement(cursor, moveMode, to, repeat);
if (repeat == -1) { if (repeat == -1) {
repeat = 1; repeat = 1;
} }
@ -3591,7 +3681,7 @@ void VVim::processChangeAction(QList<Token> &p_tokens)
} }
cursor.beginEditBlock(); cursor.beginEditBlock();
hasMoved = processMovement(cursor, doc, moveMode, to, repeat); hasMoved = processMovement(cursor, moveMode, to, repeat);
if (repeat == -1) { if (repeat == -1) {
repeat = 1; repeat = 1;
} }
@ -3885,7 +3975,6 @@ void VVim::processIndentAction(QList<Token> &p_tokens, bool p_isIndent)
} }
processMovement(cursor, processMovement(cursor,
doc,
QTextCursor::KeepAnchor, QTextCursor::KeepAnchor,
to, to,
repeat); repeat);
@ -3959,7 +4048,6 @@ void VVim::processToLowerAction(QList<Token> &p_tokens, bool p_toLower)
cursor.beginEditBlock(); cursor.beginEditBlock();
changed = processMovement(cursor, changed = processMovement(cursor,
doc,
moveMode, moveMode,
to, to,
repeat); repeat);
@ -4667,9 +4755,11 @@ void VVim::repeatLastFindMovement(bool p_reverse)
void VVim::message(const QString &p_msg) void VVim::message(const QString &p_msg)
{ {
if (!p_msg.isEmpty()) {
qDebug() << "vim msg:" << p_msg; qDebug() << "vim msg:" << p_msg;
emit vimMessage(p_msg); emit vimMessage(p_msg);
} }
}
const QMap<QChar, VVim::Register> &VVim::getRegisters() const const QMap<QChar, VVim::Register> &VVim::getRegisters() const
{ {
@ -4713,7 +4803,7 @@ bool VVim::processCommandLine(const Key &p_key)
if (p_key == Key(Qt::Key_Return) if (p_key == Key(Qt::Key_Return)
|| p_key == Key(Qt::Key_Enter, Qt::KeypadModifier)) { || p_key == Key(Qt::Key_Enter, Qt::KeypadModifier)) {
// Enter, try to execute the command and exit cmd line mode. // Enter, try to execute the command and exit cmd line mode.
executeCommand(m_keys); executeCommand();
m_cmdMode = false; m_cmdMode = false;
return true; return true;
} }
@ -4771,15 +4861,15 @@ bool VVim::processCommandLine(const Key &p_key)
return false; return false;
} }
void VVim::executeCommand(const QList<Key> &p_keys) void VVim::executeCommand()
{ {
bool validCommand = true; bool validCommand = true;
QString msg; QString msg;
if (p_keys.isEmpty()) { if (m_keys.isEmpty()) {
return; return;
} if (p_keys.size() == 1) { } if (m_keys.size() == 1) {
const Key &key0 = p_keys.first(); const Key &key0 = m_keys.first();
if (key0 == Key(Qt::Key_W)) { if (key0 == Key(Qt::Key_W)) {
// :w, save current file. // :w, save current file.
emit m_editor->saveNote(); emit m_editor->saveNote();
@ -4795,9 +4885,9 @@ void VVim::executeCommand(const QList<Key> &p_keys)
} else { } else {
validCommand = false; validCommand = false;
} }
} else if (p_keys.size() == 2) { } else if (m_keys.size() == 2) {
const Key &key0 = p_keys.first(); const Key &key0 = m_keys.first();
const Key &key1 = p_keys.at(1); const Key &key1 = m_keys.at(1);
if (key0 == Key(Qt::Key_W) && key1 == Key(Qt::Key_Q)) { if (key0 == Key(Qt::Key_W) && key1 == Key(Qt::Key_Q)) {
// :wq, save change and quit edit mode. // :wq, save change and quit edit mode.
// We treat it same as :x. // We treat it same as :x.
@ -4814,9 +4904,19 @@ void VVim::executeCommand(const QList<Key> &p_keys)
validCommand = false; validCommand = false;
} }
if (!validCommand && !hasNonDigitPendingKeys() && m_tokens.isEmpty()) {
// All digits.
// Jump to a specific line.
tryGetRepeatToken(m_keys, m_tokens);
tryAddMoveAction();
addMovementToken(Movement::LineJump);
processCommand(m_tokens);
validCommand = true;
}
if (!validCommand) { if (!validCommand) {
QString str; QString str;
for (auto const & key : p_keys) { for (auto const & key : m_keys) {
str.append(keyToChar(key.m_key, key.m_modifiers)); str.append(keyToChar(key.m_key, key.m_modifiers));
} }
@ -4826,9 +4926,9 @@ void VVim::executeCommand(const QList<Key> &p_keys)
} }
} }
bool VVim::hasNonDigitPendingKeys() bool VVim::hasNonDigitPendingKeys(const QList<Key> &p_keys)
{ {
for (auto const &key : m_keys) { for (auto const &key : p_keys) {
if (!key.isDigit()) { if (!key.isDigit()) {
return true; return true;
} }
@ -4837,6 +4937,11 @@ bool VVim::hasNonDigitPendingKeys()
return false; return false;
} }
bool VVim::hasNonDigitPendingKeys()
{
return hasNonDigitPendingKeys(m_keys);
}
bool VVim::processLeaderSequence(const Key &p_key) bool VVim::processLeaderSequence(const Key &p_key)
{ {
// Different from Vim: // Different from Vim:

View File

@ -300,6 +300,7 @@ private:
TillBackward, TillBackward,
MarkJump, MarkJump,
MarkJumpLine, MarkJumpLine,
FindPair,
Invalid Invalid
}; };
@ -617,9 +618,10 @@ private:
// Move @p_cursor according to @p_moveMode and @p_token. // 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,
QTextCursor::MoveMode p_moveMode, QTextCursor::MoveMode p_moveMode,
const Token &p_token, 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.
@ -647,15 +649,17 @@ private:
// Returns true if a command has been completed, otherwise returns false. // Returns true if a command has been completed, otherwise returns false.
bool processCommandLine(const Key &p_key); bool processCommandLine(const Key &p_key);
// Execute command specified by @p_keys. // Execute command specified by m_keys.
// @p_keys does not contain the leading colon. // @p_keys does not contain the leading colon.
// Following commands are supported: // Following commands are supported:
// :w, :wq, :q, :q!, :x // :w, :wq, :q, :q!, :x
void executeCommand(const QList<Key> &p_keys); void executeCommand();
// Check if m_keys has non-digit key. // Check if m_keys has non-digit key.
bool hasNonDigitPendingKeys(); bool hasNonDigitPendingKeys();
bool hasNonDigitPendingKeys(const QList<Key> &p_keys);
// Reading a leader sequence, read input @p_key and process it. // Reading a leader sequence, read input @p_key and process it.
// Returns true if a sequence has been replayed or it is being read, // Returns true if a sequence has been replayed or it is being read,
// otherwise returns false. // otherwise returns false.