vim-mode: support S, { and }

Thanks to xianzhon@github
This commit is contained in:
Le Tan 2017-09-02 20:39:26 +08:00
parent bb7e6e196c
commit 9523168fc3
6 changed files with 107 additions and 14 deletions

View File

@ -161,9 +161,9 @@ VNote supports a simple but useful Vim mode, including **Normal**, **Insert**, *
VNote supports following features of Vim: VNote supports following features of Vim:
- `r`, `s`, `i`, `I`, `a`, `A`, `o`, and `O`; - `r`, `s`, `S`, `i`, `I`, `a`, `A`, `c`, `C`, `o`, and `O`;
- Actions `d`, `c`, `y`, `p`, `<`, `>`, `gu`, `gU`, and `~`; - Actions `d`, `c`, `y`, `p`, `<`, `>`, `gu`, `gU`, and `~`;
- Movements `h/j/k/l`, `gj/gk`, `Ctrl+U`, `Ctrl+D`, `gg`, `G`, `0`, `^`, and `$`; - Movements `h/j/k/l`, `gj/gk`, `Ctrl+U`, `Ctrl+D`, `gg`, `G`, `0`, `^`, `{`, `}`, and `$`;
- Marks `a-z`; - Marks `a-z`;
- Registers `"`, `_`, `+`, `a-z`(`A-Z`); - Registers `"`, `_`, `+`, `a-z`(`A-Z`);
- Jump locations list (`Ctrl+O` and `Ctrl+I`); - Jump locations list (`Ctrl+O` and `Ctrl+I`);

View File

@ -162,9 +162,9 @@ VNote支持一个简单但有用的Vim模式包括 **正常** **插入**
VNote支持以下几个Vim的特性 VNote支持以下几个Vim的特性
- `r`, `s`, `i`, `I`, `a`, `A`, `o`, `O`; - `r`, `s`, `S`, `i`, `I`, `a`, `A`, `c`, `C`, `o`, `O`;
- 操作 `d`, `c`, `y`, `p`, `<`, `>`, `gu`, `gU`, `~` - 操作 `d`, `c`, `y`, `p`, `<`, `>`, `gu`, `gU`, `~`
- 移动 `h/j/k/l`, `gj/gk`, `Ctrl+U`, `Ctrl+D`, `gg`, `G`, `0`, `^`, `$` - 移动 `h/j/k/l`, `gj/gk`, `Ctrl+U`, `Ctrl+D`, `gg`, `G`, `0`, `^`, `{`, `}`, `$`
- 标记 `a-z` - 标记 `a-z`
- 寄存器 `"`, `_`, `+`, `a-z`(`A-Z`) - 寄存器 `"`, `_`, `+`, `a-z`(`A-Z`)
- 跳转位置列表 (`Ctrl+O` and `Ctrl+I`) - 跳转位置列表 (`Ctrl+O` and `Ctrl+I`)

View File

@ -639,3 +639,34 @@ round:
p_cursor.setPosition(closing, QTextCursor::KeepAnchor); p_cursor.setPosition(closing, QTextCursor::KeepAnchor);
return true; return true;
} }
int VEditUtils::findNextEmptyBlock(const QTextCursor &p_cursor,
bool p_forward,
int p_repeat)
{
Q_ASSERT(p_repeat > 0);
int res = -1;
QTextBlock block = p_cursor.block();
if (p_forward) {
block = block.next();
} else {
block = block.previous();
}
while (block.isValid()) {
if (block.length() == 1) {
res = block.position();
if (--p_repeat == 0) {
break;
}
}
if (p_forward) {
block = block.next();
} else {
block = block.previous();
}
}
return p_repeat > 0 ? -1 : res;
}

View File

@ -124,6 +124,12 @@ public:
// Need to call setTextCursor() to make it take effect. // Need to call setTextCursor() to make it take effect.
static void deleteIndentAndListMark(QTextCursor &p_cursor); static void deleteIndentAndListMark(QTextCursor &p_cursor);
// Find next @p_repeat empty block.
// Returns the position of that block if found. Otherwise, returns -1.
static int findNextEmptyBlock(const QTextCursor &p_cursor,
bool p_forward,
int p_repeat);
private: private:
VEditUtils() {} VEditUtils() {}
}; };

View File

@ -1030,6 +1030,17 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
} }
setMode(VimMode::Insert); setMode(VimMode::Insert);
} else if (modifiers == Qt::ShiftModifier) {
// S, change current line.
tryGetRepeatToken(m_keys, m_tokens);
if (hasActionToken() || !m_keys.isEmpty()) {
// Invalid sequence.
break;
}
addActionToken(Action::Change);
addRangeToken(Range::Line);
processCommand(m_tokens);
} }
break; break;
@ -2048,12 +2059,17 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
addRangeToken(range); addRangeToken(range);
processCommand(m_tokens); processCommand(m_tokens);
break; break;
} else if (hasActionToken() || !checkMode(VimMode::Normal)) { } else if (m_keys.isEmpty()) {
// Invalid sequence. // {, ParagraphUp movement.
break; tryAddMoveAction();
} else if (checkPendingKey(Key(Qt::Key_BracketLeft))) { addMovementToken(Movement::ParagraphUp);
processCommand(m_tokens);
} else if (!hasActionToken()
&& checkPendingKey(Key(Qt::Key_BracketLeft))) {
// [{, goto previous title at one higher level. // [{, goto previous title at one higher level.
processTitleJump(m_tokens, false, -1); if (checkMode(VimMode::Normal)) {
processTitleJump(m_tokens, false, -1);
}
} }
break; break;
@ -2077,12 +2093,17 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
addRangeToken(range); addRangeToken(range);
processCommand(m_tokens); processCommand(m_tokens);
break; break;
} else if (hasActionToken() || !checkMode(VimMode::Normal)) { } else if (m_keys.isEmpty()) {
// Invalid sequence. // }, ParagraphDown movement.
break; tryAddMoveAction();
} else if (checkPendingKey(Key(Qt::Key_BracketRight))) { addMovementToken(Movement::ParagraphDown);
processCommand(m_tokens);
} else if (!hasActionToken()
&& checkPendingKey(Key(Qt::Key_BracketRight))) {
// ]}, goto next title at one higher level. // ]}, goto next title at one higher level.
processTitleJump(m_tokens, true, -1); if (checkMode(VimMode::Normal)) {
processTitleJump(m_tokens, true, -1);
}
} }
break; break;
@ -3101,6 +3122,36 @@ handle_target:
break; break;
} }
case Movement::ParagraphUp:
forward = false;
// Fall through.
case Movement::ParagraphDown:
{
if (p_repeat == -1) {
p_repeat = 1;
}
// Record current location.
m_locations.addLocation(p_cursor);
int oriPos = p_cursor.position();
int position = VEditUtils::findNextEmptyBlock(p_cursor,
forward,
p_repeat);
if (position == -1) {
// No empty block. Move to the first/last character.
p_cursor.movePosition(forward ? QTextCursor::End : QTextCursor::Start,
p_moveMode);
hasMoved = p_cursor.position() != oriPos;
} else {
p_cursor.setPosition(position, p_moveMode);
hasMoved = true;
}
break;
}
default: default:
break; break;
} }
@ -3575,6 +3626,9 @@ void VVim::processDeleteAction(QList<Token> &p_tokens)
break; break;
} }
// ParagraphUp and ParagraphDown are a little different from Vim in
// block deletion.
default: default:
break; break;
} }

View File

@ -415,6 +415,8 @@ private:
FindPrevious, FindPrevious,
FindNextWordUnderCursor, FindNextWordUnderCursor,
FindPreviousWordUnderCursor, FindPreviousWordUnderCursor,
ParagraphUp,
ParagraphDown,
Invalid Invalid
}; };