vim-mode: support more text objects for i and a

Now Vim supports word, WORD, '', "", ``, (), [], <>, {} as text object.
This commit is contained in:
Le Tan 2017-06-28 22:57:06 +08:00
parent 4b1e256308
commit 6df4dbe12a
4 changed files with 719 additions and 120 deletions

View File

@ -400,3 +400,172 @@ void VEditUtils::deleteIndentAndListMark(QTextCursor &p_cursor)
p_cursor.removeSelectedText(); p_cursor.removeSelectedText();
} }
bool VEditUtils::selectPairTargetAround(QTextCursor &p_cursor,
QChar p_opening,
QChar p_closing,
bool p_inclusive,
bool p_crossBlock,
int p_repeat)
{
Q_ASSERT(p_repeat >= 1);
QTextDocument *doc = p_cursor.document();
int pos = p_cursor.position();
// Search range [start, end].
int start = 0;
int end = doc->characterCount() - 1;
if (!p_crossBlock) {
QTextBlock block = p_cursor.block();
start = block.position();
end = block.position() + block.length() - 1;
}
if (start == end || pos > end) {
return false;
}
Q_ASSERT(!doc->characterAt(pos).isNull());
bool found = false;
// The number of un-paired symbols before we meet a target.
// For example, when we are searching the `(`, nrPair is the number of
// the un-paired `)` currently.
int nrPair = 0;
// The absolute position of the found target.
// vnote|(vnote|)vnote
int opening = pos;
int closing = pos;
round:
// "abc|"def", after `di"`, becomes "|"def"
// So we need to try closing first.
QChar ch = doc->characterAt(closing);
Q_ASSERT(!ch.isNull());
if (ch == p_closing) {
// Try to find the opening.
nrPair = 1;
int i = opening;
if (opening == closing) {
--i;
}
for (; i >= start; --i) {
ch = doc->characterAt(i);
Q_ASSERT(!ch.isNull());
if (ch == p_opening) {
if (--nrPair == 0) {
break;
}
} else if (ch == p_closing) {
++nrPair;
}
}
if (i >= start) {
// Found the opening. Done.
opening = i;
found = true;
}
}
ch = doc->characterAt(opening);
Q_ASSERT(!ch.isNull());
if (!found && ch == p_opening) {
// Try to find the closing.
nrPair = 1;
int j = closing;
if (opening == closing) {
++j;
}
for (; j <= end; ++j) {
ch = doc->characterAt(j);
Q_ASSERT(!ch.isNull());
if (ch == p_closing) {
if (--nrPair == 0) {
break;
}
} else if (ch == p_opening) {
++nrPair;
}
}
if (j <= end) {
// Foudnd the closing. Done.
closing = j;
found = true;
}
}
if (!found
&& doc->characterAt(opening) != p_opening
&& doc->characterAt(closing) != p_closing) {
// Need to find both the opening and closing.
int i = opening - 1;
int j = closing + 1;
// Pretend that we have found one.
nrPair = 1;
for (; i >= start; --i) {
ch = doc->characterAt(i);
Q_ASSERT(!ch.isNull());
if (ch == p_opening) {
if (--nrPair == 0) {
break;
}
} else if (ch == p_closing) {
++nrPair;
}
}
if (i >= start) {
opening = i;
// Continue to find the closing.
nrPair = 1;
for (; j <= end; ++j) {
ch = doc->characterAt(j);
Q_ASSERT(!ch.isNull());
if (ch == p_closing) {
if (--nrPair == 0) {
break;
}
} else if (ch == p_opening) {
++nrPair;
}
}
if (j <= end) {
closing = j;
found = true;
}
}
}
if (!found) {
return false;
} else if (--p_repeat) {
// Need to find more.
found = false;
--opening;
++closing;
if (opening < start && closing > end) {
return false;
}
goto round;
}
if (p_inclusive) {
++closing;
} else {
++opening;
}
p_cursor.setPosition(opening, QTextCursor::MoveAnchor);
p_cursor.setPosition(closing, QTextCursor::KeepAnchor);
return true;
}

View File

@ -74,6 +74,17 @@ public:
bool p_inclusive, bool p_inclusive,
int p_repeat); int p_repeat);
// Find a pair target (@p_opening, @p_closing) containing current cursor and
// select the range between them.
// Need to call setTextCursor() to make it take effect.
// Returns true if target is found.
static bool selectPairTargetAround(QTextCursor &p_cursor,
QChar p_opening,
QChar p_closing,
bool p_inclusive,
bool p_crossBlock,
int p_repeat);
// Get the count of blocks selected. // Get the count of blocks selected.
static int selectedBlockCount(const QTextCursor &p_cursor); static int selectedBlockCount(const QTextCursor &p_cursor);

View File

@ -655,6 +655,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
if (modifiers == Qt::NoModifier) { if (modifiers == Qt::NoModifier) {
if (hasActionTokenValidForTextObject()) { if (hasActionTokenValidForTextObject()) {
// Inner text object. // Inner text object.
tryGetRepeatToken(m_keys, m_tokens);
if (!m_keys.isEmpty()) { if (!m_keys.isEmpty()) {
// Invalid sequence; // Invalid sequence;
break; break;
@ -713,6 +714,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
if (modifiers == Qt::NoModifier) { if (modifiers == Qt::NoModifier) {
if (hasActionTokenValidForTextObject()) { if (hasActionTokenValidForTextObject()) {
// Around text object. // Around text object.
tryGetRepeatToken(m_keys, m_tokens);
if (!m_keys.isEmpty()) { if (!m_keys.isEmpty()) {
// Invalid sequence; // Invalid sequence;
break; break;
@ -1122,15 +1124,51 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
break; break;
} }
case Qt::Key_BracketRight:
{
if (modifiers == Qt::NoModifier) {
tryGetRepeatToken(m_keys, m_tokens);
if (checkPendingKey(Key(Qt::Key_I))
|| checkPendingKey(Key(Qt::Key_A))) {
// BracketInner/BracketAround.
Range range = Range::BracketInner;
if (checkPendingKey(Key(Qt::Key_A))) {
range = Range::BracketAround;
}
addRangeToken(range);
processCommand(m_tokens);
break;
}
}
break;
}
// Should be kept together with Qt::Key_Escape. // Should be kept together with Qt::Key_Escape.
case Qt::Key_BracketLeft: case Qt::Key_BracketLeft:
{ {
if (isControlModifier(modifiers)) { if (isControlModifier(modifiers)) {
// fallthrough. // fallthrough.
} else if (modifiers == Qt::NoModifier) {
tryGetRepeatToken(m_keys, m_tokens);
if (checkPendingKey(Key(Qt::Key_I))
|| checkPendingKey(Key(Qt::Key_A))) {
// BracketInner/BracketAround.
Range range = Range::BracketInner;
if (checkPendingKey(Key(Qt::Key_A))) {
range = Range::BracketAround;
}
addRangeToken(range);
processCommand(m_tokens);
break;
}
break;
} else { } else {
break; break;
} }
} }
case Qt::Key_Escape: case Qt::Key_Escape:
@ -1197,7 +1235,8 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
if (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier) { if (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier) {
bool shift = modifiers == Qt::ShiftModifier; bool shift = modifiers == Qt::ShiftModifier;
tryGetRepeatToken(m_keys, m_tokens); tryGetRepeatToken(m_keys, m_tokens);
if (checkPendingKey(Key(Qt::Key_I)) || checkPendingKey(Key(Qt::Key_A))) { if (checkPendingKey(Key(Qt::Key_I))
|| checkPendingKey(Key(Qt::Key_A))) {
// WordInner/WORDInner/WordAournd/WORDAround. // WordInner/WORDInner/WordAournd/WORDAround.
bool around = checkPendingKey(Key(Qt::Key_A)); bool around = checkPendingKey(Key(Qt::Key_A));
Range range = Range::Invalid; Range range = Range::Invalid;
@ -1287,13 +1326,24 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
case Qt::Key_QuoteDbl: case Qt::Key_QuoteDbl:
{ {
if (modifiers == Qt::ShiftModifier) { if (modifiers == Qt::ShiftModifier) {
// Specify a register.
tryGetRepeatToken(m_keys, m_tokens); tryGetRepeatToken(m_keys, m_tokens);
if (!m_keys.isEmpty() || hasActionToken()) { if (checkPendingKey(Key(Qt::Key_I))
|| checkPendingKey(Key(Qt::Key_A))) {
// DoubleQuoteInner/DoubleQuoteAround.
Range range = Range::DoubleQuoteInner;
if (checkPendingKey(Key(Qt::Key_A))) {
range = Range::DoubleQuoteAround;
}
addRangeToken(range);
processCommand(m_tokens);
break;
} else if (!m_keys.isEmpty() || hasActionToken()) {
// Invalid sequence. // Invalid sequence.
break; break;
} }
// ", specify a register.
m_keys.append(keyInfo); m_keys.append(keyInfo);
goto accept; goto accept;
} }
@ -1452,8 +1502,16 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
addRangeToken(Range::Line); addRangeToken(Range::Line);
processCommand(m_tokens); processCommand(m_tokens);
break; break;
} else { } else if (checkPendingKey(Key(Qt::Key_I))
// An invalid sequence. || checkPendingKey(Key(Qt::Key_A))) {
// AngleBracketInner/AngleBracketAround.
Range range = Range::AngleBracketInner;
if (checkPendingKey(Key(Qt::Key_A))) {
range = Range::AngleBracketAround;
}
addRangeToken(range);
processCommand(m_tokens);
break; break;
} }
} else { } else {
@ -1464,7 +1522,8 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
cursor, cursor,
m_editConfig->m_tabSpaces, m_editConfig->m_tabSpaces,
!unindent); !unindent);
setMode(VimMode::Normal); // Different from Vim:
// Do not exit Visual mode after indentation/unindentation.
break; break;
} }
@ -1664,10 +1723,22 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
case Qt::Key_Apostrophe: case Qt::Key_Apostrophe:
{ {
if (modifiers == Qt::NoModifier) { if (modifiers == Qt::NoModifier) {
// ', jump to the start of line of a mark.
// Repeat is useless.
tryGetRepeatToken(m_keys, m_tokens); tryGetRepeatToken(m_keys, m_tokens);
if (checkPendingKey(Key(Qt::Key_I))
|| checkPendingKey(Key(Qt::Key_A))) {
// QuoteInner/QuoteAround.
Range range = Range::QuoteInner;
if (checkPendingKey(Key(Qt::Key_A))) {
range = Range::QuoteAround;
}
addRangeToken(range);
processCommand(m_tokens);
break;
}
// ', jump to the start of line of a mark.
// Repeat is useless in this case.
if (m_keys.isEmpty()) { if (m_keys.isEmpty()) {
m_keys.append(keyInfo); m_keys.append(keyInfo);
goto accept; goto accept;
@ -1680,10 +1751,22 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
case Qt::Key_QuoteLeft: case Qt::Key_QuoteLeft:
{ {
if (modifiers == Qt::NoModifier) { if (modifiers == Qt::NoModifier) {
// `, jump to a mark.
// Repeat is useless.
tryGetRepeatToken(m_keys, m_tokens); tryGetRepeatToken(m_keys, m_tokens);
if (checkPendingKey(Key(Qt::Key_I))
|| checkPendingKey(Key(Qt::Key_A))) {
// BackQuoteInner/BackQuoteAround.
Range range = Range::BackQuoteInner;
if (checkPendingKey(Key(Qt::Key_A))) {
range = Range::BackQuoteAround;
}
addRangeToken(range);
processCommand(m_tokens);
break;
}
// `, jump to a mark.
// Repeat is useless in this case.
if (m_keys.isEmpty()) { if (m_keys.isEmpty()) {
m_keys.append(keyInfo); m_keys.append(keyInfo);
goto accept; goto accept;
@ -1693,6 +1776,71 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
break; break;
} }
case Qt::Key_ParenLeft:
// Fall through.
case Qt::Key_ParenRight:
{
if (modifiers == Qt::ShiftModifier) {
tryGetRepeatToken(m_keys, m_tokens);
if (checkPendingKey(Key(Qt::Key_I))
|| checkPendingKey(Key(Qt::Key_A))) {
// ParenthesisInner/ParenthesisAround.
Range range = Range::ParenthesisInner;
if (checkPendingKey(Key(Qt::Key_A))) {
range = Range::ParenthesisAround;
}
addRangeToken(range);
processCommand(m_tokens);
break;
}
}
break;
}
case Qt::Key_BraceLeft:
{
if (modifiers == Qt::ShiftModifier) {
tryGetRepeatToken(m_keys, m_tokens);
if (checkPendingKey(Key(Qt::Key_I))
|| checkPendingKey(Key(Qt::Key_A))) {
// BraceInner/BraceAround.
Range range = Range::BraceInner;
if (checkPendingKey(Key(Qt::Key_A))) {
range = Range::BraceAround;
}
addRangeToken(range);
processCommand(m_tokens);
break;
}
}
break;
}
case Qt::Key_BraceRight:
{
if (modifiers == Qt::ShiftModifier) {
tryGetRepeatToken(m_keys, m_tokens);
if (checkPendingKey(Key(Qt::Key_I))
|| checkPendingKey(Key(Qt::Key_A))) {
// BraceInner/BraceAround.
Range range = Range::BraceInner;
if (checkPendingKey(Key(Qt::Key_A))) {
range = Range::BraceAround;
}
addRangeToken(range);
processCommand(m_tokens);
break;
}
}
break;
}
default: default:
break; break;
} }
@ -2426,6 +2574,10 @@ bool VVim::selectRange(QTextCursor &p_cursor, const QTextDocument *p_doc,
bool hasMoved = false; bool hasMoved = false;
QTextCursor::MoveMode moveMode = QTextCursor::KeepAnchor; QTextCursor::MoveMode moveMode = QTextCursor::KeepAnchor;
bool around = false; bool around = false;
QChar opening;
QChar closing;
bool crossBlock = false;
bool multipleTargets = false;
Q_UNUSED(p_doc); Q_UNUSED(p_doc);
@ -2528,6 +2680,163 @@ bool VVim::selectRange(QTextCursor &p_cursor, const QTextDocument *p_doc,
break; break;
} }
case Range::ParenthesisAround:
{
around = true;
opening = '(';
closing = ')';
crossBlock = true;
multipleTargets = true;
goto handlePairTarget;
}
case Range::ParenthesisInner:
{
around = false;
opening = '(';
closing = ')';
crossBlock = true;
multipleTargets = true;
goto handlePairTarget;
}
case Range::BracketAround:
{
around = true;
opening = '[';
closing = ']';
crossBlock = true;
multipleTargets = true;
goto handlePairTarget;
}
case Range::BracketInner:
{
around = false;
opening = '[';
closing = ']';
crossBlock = true;
multipleTargets = true;
goto handlePairTarget;
}
case Range::AngleBracketAround:
{
around = true;
opening = '<';
closing = '>';
crossBlock = true;
multipleTargets = true;
goto handlePairTarget;
}
case Range::AngleBracketInner:
{
around = false;
opening = '<';
closing = '>';
crossBlock = true;
multipleTargets = true;
goto handlePairTarget;
}
case Range::BraceAround:
{
around = true;
opening = '{';
closing = '}';
crossBlock = true;
multipleTargets = true;
goto handlePairTarget;
}
case Range::BraceInner:
{
around = false;
opening = '{';
closing = '}';
crossBlock = true;
multipleTargets = true;
goto handlePairTarget;
}
case Range::DoubleQuoteAround:
{
around = true;
opening = '"';
closing = '"';
crossBlock = false;
multipleTargets = false;
goto handlePairTarget;
}
case Range::DoubleQuoteInner:
{
around = false;
opening = '"';
closing = '"';
crossBlock = false;
multipleTargets = false;
goto handlePairTarget;
}
case Range::BackQuoteAround:
{
around = true;
opening = '`';
closing = '`';
crossBlock = false;
multipleTargets = false;
goto handlePairTarget;
}
case Range::BackQuoteInner:
{
around = false;
opening = '`';
closing = '`';
crossBlock = false;
multipleTargets = false;
goto handlePairTarget;
}
case Range::QuoteAround:
{
around = true;
opening = '\'';
closing = '\'';
crossBlock = false;
multipleTargets = false;
goto handlePairTarget;
}
case Range::QuoteInner:
{
around = false;
opening = '\'';
closing = '\'';
crossBlock = false;
multipleTargets = false;
handlePairTarget:
if (p_repeat == -1) {
p_repeat = 1;
} else if (p_repeat > 1 && !multipleTargets) {
// According to the behavior of Vim.
p_repeat = 1;
around = true;
}
hasMoved = VEditUtils::selectPairTargetAround(p_cursor,
opening,
closing,
around,
crossBlock,
p_repeat);
break;
}
default: default:
break; break;
} }
@ -2557,8 +2866,10 @@ void VVim::processDeleteAction(QList<Token> &p_tokens)
if (to.isRange()) { if (to.isRange()) {
cursor.beginEditBlock(); cursor.beginEditBlock();
hasMoved = selectRange(cursor, doc, to.m_range, repeat); hasMoved = selectRange(cursor, doc, to.m_range, repeat);
bool around = false;
if (hasMoved) { if (hasMoved) {
// Whether the range may cross blocks.
bool mayCrossBlock = false;
switch (to.m_range) { switch (to.m_range) {
case Range::Line: case Range::Line:
{ {
@ -2575,34 +2886,69 @@ void VVim::processDeleteAction(QList<Token> &p_tokens)
message(tr("%1 fewer %2").arg(repeat).arg(repeat > 1 ? tr("lines") message(tr("%1 fewer %2").arg(repeat).arg(repeat > 1 ? tr("lines")
: tr("line"))); : tr("line")));
qDebug() << "delete" << repeat << "lines";
break; break;
} }
case Range::ParenthesisInner:
// Fall through.
case Range::ParenthesisAround:
// Fall through.
case Range::BracketInner:
// Fall through.
case Range::BracketAround:
// Fall through.
case Range::AngleBracketInner:
// Fall through.
case Range::AngleBracketAround:
// Fall through.
case Range::BraceInner:
// Fall through.
case Range::BraceAround:
// Fall through.
mayCrossBlock = true;
case Range::WordAround: case Range::WordAround:
around = true;
// Fall through. // Fall through.
case Range::WordInner: case Range::WordInner:
{ // Fall through.
if (cursor.hasSelection()) {
deleteSelectedText(cursor, false);
}
qDebug() << "delete" << (around ? "around" : "inner") << "word";
break;
}
case Range::WORDAround: case Range::WORDAround:
around = true;
// Fall through. // Fall through.
case Range::WORDInner: case Range::WORDInner:
// Fall through.
case Range::QuoteInner:
// Fall through.
case Range::QuoteAround:
// Fall through.
case Range::DoubleQuoteInner:
// Fall through.
case Range::DoubleQuoteAround:
// Fall through.
case Range::BackQuoteInner:
// Fall through.
case Range::BackQuoteAround:
{ {
if (cursor.hasSelection()) { if (cursor.hasSelection()) {
deleteSelectedText(cursor, false); bool clearEmptyBlock = false;
if (mayCrossBlock
&& VEditUtils::selectedBlockCount(cursor) > 1) {
clearEmptyBlock = true;
}
int blockCount = 0;
if (clearEmptyBlock) {
blockCount = doc->blockCount();
}
deleteSelectedText(cursor, clearEmptyBlock);
if (clearEmptyBlock) {
int nrBlock = blockCount - doc->blockCount();
Q_ASSERT(nrBlock > 0);
message(tr("%1 fewer %2").arg(nrBlock).arg(nrBlock > 1 ? tr("lines")
: tr("line")));
}
} }
qDebug() << "delete" << (around ? "around" : "inner") << "WORD";
break; break;
} }
@ -2733,8 +3079,10 @@ void VVim::processCopyAction(QList<Token> &p_tokens)
if (to.isRange()) { if (to.isRange()) {
cursor.beginEditBlock(); cursor.beginEditBlock();
changed = selectRange(cursor, doc, to.m_range, repeat); changed = selectRange(cursor, doc, to.m_range, repeat);
bool around = false;
if (changed) { if (changed) {
// Whether the range may cross blocks.
bool mayCrossBlock = false;
switch (to.m_range) { switch (to.m_range) {
case Range::Line: case Range::Line:
{ {
@ -2751,34 +3099,63 @@ void VVim::processCopyAction(QList<Token> &p_tokens)
message(tr("%1 %2 yanked").arg(repeat).arg(repeat > 1 ? tr("lines") message(tr("%1 %2 yanked").arg(repeat).arg(repeat > 1 ? tr("lines")
: tr("line"))); : tr("line")));
qDebug() << "copy" << repeat << "lines";
break; break;
} }
case Range::ParenthesisInner:
// Fall through.
case Range::ParenthesisAround:
// Fall through.
case Range::BracketInner:
// Fall through.
case Range::BracketAround:
// Fall through.
case Range::AngleBracketInner:
// Fall through.
case Range::AngleBracketAround:
// Fall through.
case Range::BraceInner:
// Fall through.
case Range::BraceAround:
// Fall through.
mayCrossBlock = true;
case Range::WordAround: case Range::WordAround:
around = true;
// Fall through. // Fall through.
case Range::WordInner: case Range::WordInner:
{ // Fall through.
if (cursor.hasSelection()) {
copySelectedText(cursor, false);
}
qDebug() << "copy" << (around ? "around" : "inner") << "word";
break;
}
case Range::WORDAround: case Range::WORDAround:
around = true;
// Fall through. // Fall through.
case Range::WORDInner: case Range::WORDInner:
// Fall through.
case Range::QuoteInner:
// Fall through.
case Range::QuoteAround:
// Fall through.
case Range::DoubleQuoteInner:
// Fall through.
case Range::DoubleQuoteAround:
// Fall through.
case Range::BackQuoteInner:
// Fall through.
case Range::BackQuoteAround:
{ {
if (cursor.hasSelection()) { if (cursor.hasSelection()) {
bool multipleBlocks = false;
int nrBlock = VEditUtils::selectedBlockCount(cursor);
if (mayCrossBlock && nrBlock > 1) {
multipleBlocks = true;
}
// No need to add new line even crossing multiple blocks.
copySelectedText(cursor, false); copySelectedText(cursor, false);
if (multipleBlocks) {
message(tr("%1 %2 yanked").arg(nrBlock).arg(nrBlock > 1 ? tr("lines")
: tr("line")));
}
} }
qDebug() << "copy" << (around ? "around" : "inner") << "WORD";
break; break;
} }
@ -2798,7 +3175,7 @@ void VVim::processCopyAction(QList<Token> &p_tokens)
V_ASSERT(to.isMovement()); V_ASSERT(to.isMovement());
// Filter out not supported movement for DELETE action. // Filter out not supported movement for Copy action.
switch (to.m_movement) { switch (to.m_movement) {
case Movement::PageUp: case Movement::PageUp:
case Movement::PageDown: case Movement::PageDown:
@ -2973,17 +3350,13 @@ void VVim::processChangeAction(QList<Token> &p_tokens)
if (to.isRange()) { if (to.isRange()) {
cursor.beginEditBlock(); cursor.beginEditBlock();
hasMoved = selectRange(cursor, doc, to.m_range, repeat); hasMoved = selectRange(cursor, doc, to.m_range, repeat);
bool around = false;
if (hasMoved) { if (hasMoved) {
int pos = cursor.selectionStart(); int pos = cursor.selectionStart();
switch (to.m_range) { switch (to.m_range) {
case Range::Line: case Range::Line:
{ {
// cc, change current line. // cc, change current line.
if (repeat == -1) {
repeat = 1;
}
if (cursor.hasSelection()) { if (cursor.hasSelection()) {
deleteSelectedText(cursor, true); deleteSelectedText(cursor, true);
insertChangeBlockAfterDeletion(cursor, pos); insertChangeBlockAfterDeletion(cursor, pos);
@ -2991,37 +3364,49 @@ void VVim::processChangeAction(QList<Token> &p_tokens)
saveToRegister("\n"); saveToRegister("\n");
} }
qDebug() << "change" << repeat << "lines";
break; break;
} }
case Range::ParenthesisInner:
// Fall through.
case Range::ParenthesisAround:
// Fall through.
case Range::BracketInner:
// Fall through.
case Range::BracketAround:
// Fall through.
case Range::AngleBracketInner:
// Fall through.
case Range::AngleBracketAround:
// Fall through.
case Range::BraceInner:
// Fall through.
case Range::BraceAround:
// Fall through.
case Range::WordAround: case Range::WordAround:
around = true;
// Fall through. // Fall through.
case Range::WordInner: case Range::WordInner:
{ // Fall through.
if (cursor.hasSelection()) {
deleteSelectedText(cursor, false);
} else {
saveToRegister("\n");
}
qDebug() << "delete" << (around ? "around" : "inner") << "word";
break;
}
case Range::WORDAround: case Range::WORDAround:
around = true;
// Fall through. // Fall through.
case Range::WORDInner: case Range::WORDInner:
// Fall through.
case Range::QuoteInner:
// Fall through.
case Range::QuoteAround:
// Fall through.
case Range::DoubleQuoteInner:
// Fall through.
case Range::DoubleQuoteAround:
// Fall through.
case Range::BackQuoteInner:
// Fall through.
case Range::BackQuoteAround:
{ {
if (cursor.hasSelection()) { if (cursor.hasSelection()) {
deleteSelectedText(cursor, false); deleteSelectedText(cursor, false);
} else {
saveToRegister("\n");
} }
qDebug() << "delete" << (around ? "around" : "inner") << "WORD";
break; break;
} }
@ -3241,49 +3626,88 @@ void VVim::processIndentAction(QList<Token> &p_tokens, bool p_isIndent)
QTextDocument *doc = m_editor->document(); QTextDocument *doc = m_editor->document();
if (to.isRange()) { if (to.isRange()) {
selectRange(cursor, doc, to.m_range, repeat); bool changed = selectRange(cursor, doc, to.m_range, repeat);
switch (to.m_range) { if (changed) {
case Range::Line: switch (to.m_range) {
{ case Range::Line:
// >>/<<, indent/unindent current line. {
if (repeat == -1) { // >>/<<, indent/unindent current line.
repeat = 1; if (repeat == -1) {
repeat = 1;
}
VEditUtils::indentSelectedBlocks(doc,
cursor,
m_editConfig->m_tabSpaces,
p_isIndent);
if (p_isIndent) {
message(tr("%1 %2 >ed 1 time").arg(repeat).arg(repeat > 1 ? tr("lines")
: tr("line")));
} else {
message(tr("%1 %2 <ed 1 time").arg(repeat).arg(repeat > 1 ? tr("lines")
: tr("line")));
}
break;
} }
VEditUtils::indentSelectedBlocks(doc, case Range::ParenthesisInner:
cursor, // Fall through.
m_editConfig->m_tabSpaces, case Range::ParenthesisAround:
p_isIndent); // Fall through.
case Range::BracketInner:
// Fall through.
case Range::BracketAround:
// Fall through.
case Range::AngleBracketInner:
// Fall through.
case Range::AngleBracketAround:
// Fall through.
case Range::BraceInner:
// Fall through.
case Range::BraceAround:
// Fall through.
case Range::WordAround:
// Fall through.
case Range::WordInner:
// Fall through.
case Range::WORDAround:
// Fall through.
case Range::WORDInner:
// Fall through.
case Range::QuoteInner:
// Fall through.
case Range::QuoteAround:
// Fall through.
case Range::DoubleQuoteInner:
// Fall through.
case Range::DoubleQuoteAround:
// Fall through.
case Range::BackQuoteInner:
// Fall through.
case Range::BackQuoteAround:
{
int nrBlock = VEditUtils::selectedBlockCount(cursor);
VEditUtils::indentSelectedBlocks(doc,
cursor,
m_editConfig->m_tabSpaces,
p_isIndent);
if (p_isIndent) { if (p_isIndent) {
message(tr("%1 %2 >ed 1 time").arg(repeat).arg(repeat > 1 ? tr("lines") message(tr("%1 %2 >ed 1 time").arg(nrBlock).arg(nrBlock > 1 ? tr("lines")
: tr("line"))); : tr("line")));
} else { } else {
message(tr("%1 %2 <ed 1 time").arg(repeat).arg(repeat > 1 ? tr("lines") message(tr("%1 %2 <ed 1 time").arg(nrBlock).arg(nrBlock > 1 ? tr("lines")
: tr("line"))); : tr("line")));
}
break;
} }
break; default:
} return;
}
case Range::WordAround:
// Fall through.
case Range::WordInner:
// Fall through.
case Range::WORDAround:
// Fall through.
case Range::WORDInner:
{
cursor.clearSelection();
VEditUtils::indentSelectedBlocks(doc,
cursor,
m_editConfig->m_tabSpaces,
p_isIndent);
break;
}
default:
return;
} }
return; return;
@ -3349,12 +3773,11 @@ void VVim::processToLowerAction(QList<Token> &p_tokens, bool p_toLower)
changed = selectRange(cursor, doc, to.m_range, repeat); changed = selectRange(cursor, doc, to.m_range, repeat);
if (changed) { if (changed) {
oriPos = cursor.selectionStart(); oriPos = cursor.selectionStart();
convertCaseOfSelectedText(cursor, p_toLower); int nrBlock = VEditUtils::selectedBlockCount(cursor);
message(tr("%1 %2 changed").arg(nrBlock)
.arg(nrBlock > 1 ? tr("lines") : tr("line")));
if (to.m_range == Range::Line) { convertCaseOfSelectedText(cursor, p_toLower);
message(tr("%1 %2 changed").arg(repeat == -1 ? 1 : repeat)
.arg(repeat > 1 ? tr("lines") : tr("line")));
}
cursor.setPosition(oriPos); cursor.setPosition(oriPos);
} }
@ -3389,40 +3812,34 @@ void VVim::processToLowerAction(QList<Token> &p_tokens, bool p_toLower)
if (changed) { if (changed) {
oriPos = cursor.selectionStart(); oriPos = cursor.selectionStart();
bool isBlock = false;
switch (to.m_movement) { switch (to.m_movement) {
case Movement::Up: case Movement::Up:
{ {
isBlock = true;
expandSelectionToWholeLines(cursor); expandSelectionToWholeLines(cursor);
break; break;
} }
case Movement::Down: case Movement::Down:
{ {
isBlock = true;
expandSelectionToWholeLines(cursor); expandSelectionToWholeLines(cursor);
break; break;
} }
case Movement::LineJump: case Movement::LineJump:
{ {
isBlock = true;
expandSelectionToWholeLines(cursor); expandSelectionToWholeLines(cursor);
break; break;
} }
case Movement::StartOfDocument: case Movement::StartOfDocument:
{ {
isBlock = true;
expandSelectionToWholeLines(cursor); expandSelectionToWholeLines(cursor);
break; break;
} }
case Movement::EndOfDocument: case Movement::EndOfDocument:
{ {
isBlock = true;
expandSelectionToWholeLines(cursor); expandSelectionToWholeLines(cursor);
break; break;
} }
@ -3431,11 +3848,9 @@ void VVim::processToLowerAction(QList<Token> &p_tokens, bool p_toLower)
break; break;
} }
if (isBlock) { int nrBlock = VEditUtils::selectedBlockCount(cursor);
int nrBlock = VEditUtils::selectedBlockCount(cursor); message(tr("%1 %2 changed").arg(nrBlock).arg(nrBlock > 1 ? tr("lines")
message(tr("%1 %2 changed").arg(nrBlock).arg(nrBlock > 1 ? tr("lines") : tr("line")));
: tr("line")));
}
convertCaseOfSelectedText(cursor, p_toLower); convertCaseOfSelectedText(cursor, p_toLower);
@ -3741,7 +4156,9 @@ bool VVim::hasActionTokenValidForTextObject() const
|| act == Action::Copy || act == Action::Copy
|| act == Action::Change || act == Action::Change
|| act == Action::ToLower || act == Action::ToLower
|| act == Action::ToUpper) { || act == Action::ToUpper
|| act == Action::Indent
|| act == Action::UnIndent) {
return true; return true;
} }
} }

View File

@ -313,6 +313,8 @@ private:
QuoteAround, QuoteAround,
DoubleQuoteInner, DoubleQuoteInner,
DoubleQuoteAround, DoubleQuoteAround,
BackQuoteInner,
BackQuoteAround,
ParenthesisInner, ParenthesisInner,
ParenthesisAround, ParenthesisAround,
BracketInner, BracketInner,