mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
vim-mode: support r for replacement
This commit is contained in:
parent
6df4dbe12a
commit
e1acd6e9a2
@ -120,14 +120,18 @@ VNote supports a simple but useful Vim mode, including **Normal**, **Insert**, *
|
||||
|
||||
VNote supports following features of Vim:
|
||||
|
||||
- `d`, `c`, `y`, `p`, `<`, `>`, `gu`, `gU` actions;
|
||||
- `r`, `s`, `i`, `I`, `a`, `A`, `o`, and `O`;
|
||||
- Actions `d`, `c`, `y`, `p`, `<`, `>`, `gu`, and `gU`;
|
||||
- Movements `h/j/k/l`, `gj/gk`, `Ctrl+U`, `Ctrl+D`, `gg`, `G`, `0`, `^`, and `$`;
|
||||
- Marks `a-z`;
|
||||
- Registers `"`, `_`, `+`, `a-z`(`A-Z`);
|
||||
- Jump locations list (`Ctrl+O` and `Ctrl+I`);
|
||||
- Leader key (`Space`)
|
||||
- Currently `<leader>y/d/p` equals to `"+y/d/p`, which will access the system's clipboard.
|
||||
- Currently `<leader>y/d/p` equals to `"+y/d/p`, which will access the system's clipboard;
|
||||
- `zz`, `zb`, `zt`;
|
||||
- `u` and `Ctrl+R` for undo and redo;
|
||||
- Text objects `i/a`: word, WORD, `''`, `""`, `` ` ` ``, `()`, `[]`, `<>`, and `{}`;
|
||||
- Command line `:w`, `:wq`, `:x`, `:q`, and `:q!`;
|
||||
|
||||
For now, VNote does **NOT** support the macro and repeat(`.`) features of Vim.
|
||||
|
||||
|
@ -120,14 +120,17 @@ VNote支持一个简单但有用的Vim模式,包括 **正常**, **插入**
|
||||
|
||||
VNote支持以下几个Vim的特性:
|
||||
|
||||
- `d`, `c`, `y`, `p`, `<`, `>`, `gu`, `gU` 操作;
|
||||
- `r`, `s`, `i`, `I`, `a`, `A`, `o`, `O`;
|
||||
- 操作 `d`, `c`, `y`, `p`, `<`, `>`, `gu`, `gU`;
|
||||
- 标记 `a-z`;
|
||||
- 寄存器 `"`, `_`, `+`, `a-z`(`A-Z`);
|
||||
- 跳转位置列表 (`Ctrl+O` and `Ctrl+I`);
|
||||
- 前导键 (`Space`)
|
||||
- 目前 `<leader>y/d/p` 等同于 `"+y/d/p`, 从而可以访问系统剪切板。
|
||||
- 目前 `<leader>y/d/p` 等同于 `"+y/d/p`, 从而可以访问系统剪切板;
|
||||
- `zz`, `zb`, `zt`;
|
||||
- `u` 和 `Ctrl+R` 撤销和重做;
|
||||
- 文本对象 `i/a`:word, WORD, `''`, `""`, `` ` ` ``, `()`, `[]`, `<>`, and `{}`;
|
||||
- 命令行 `:w`, `:wq`, `:x`, `:q`, and `:q!`;
|
||||
|
||||
VNote目前暂时不支持Vim的宏和重复(`.`)特性。
|
||||
|
||||
|
@ -351,6 +351,31 @@ static bool isControlModifier(int p_modifiers)
|
||||
#endif
|
||||
}
|
||||
|
||||
// Replace each of the character of selected text with @p_char.
|
||||
// Returns true if replacement has taken place.
|
||||
// Need to setTextCursor() after calling this.
|
||||
static bool replaceSelectedTextWithCharacter(QTextCursor &p_cursor, QChar p_char)
|
||||
{
|
||||
if (!p_cursor.hasSelection()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int start = p_cursor.selectionStart();
|
||||
int end = p_cursor.selectionEnd();
|
||||
p_cursor.setPosition(start, QTextCursor::MoveAnchor);
|
||||
while (p_cursor.position() < end) {
|
||||
if (p_cursor.atBlockEnd()) {
|
||||
p_cursor.movePosition(QTextCursor::NextCharacter);
|
||||
} else {
|
||||
p_cursor.deleteChar();
|
||||
// insertText() will move the cursor right after the inserted text.
|
||||
p_cursor.insertText(p_char);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VVim::handleKeyPressEvent(QKeyEvent *p_event, int *p_autoIndentPos)
|
||||
{
|
||||
bool ret = handleKeyPressEvent(p_event->key(), p_event->modifiers(), p_autoIndentPos);
|
||||
@ -511,6 +536,15 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
goto clear_accept;
|
||||
}
|
||||
|
||||
if (expectingReplaceCharacter()) {
|
||||
// Expecting a character to replace with for r.
|
||||
addActionToken(Action::Replace);
|
||||
addKeyToken(keyInfo);
|
||||
processCommand(m_tokens);
|
||||
|
||||
goto clear_accept;
|
||||
}
|
||||
|
||||
// Check leader key here. If leader key conflicts with other keys, it will
|
||||
// overwrite it.
|
||||
// Leader sequence is just like an action.
|
||||
@ -1632,6 +1666,13 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
||||
addActionToken(Action::Redo);
|
||||
processCommand(m_tokens);
|
||||
break;
|
||||
} else if (modifiers == Qt::NoModifier) {
|
||||
// r, replace.
|
||||
tryGetRepeatToken(m_keys, m_tokens);
|
||||
if (m_keys.isEmpty() && !hasActionToken()) {
|
||||
m_keys.append(keyInfo);
|
||||
goto accept;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
@ -1974,6 +2015,10 @@ void VVim::processCommand(QList<Token> &p_tokens)
|
||||
processJumpLocationAction(p_tokens, true);
|
||||
break;
|
||||
|
||||
case Action::Replace:
|
||||
processReplaceAction(p_tokens);
|
||||
break;
|
||||
|
||||
default:
|
||||
p_tokens.clear();
|
||||
break;
|
||||
@ -3971,6 +4016,55 @@ void VVim::processJumpLocationAction(QList<Token> &p_tokens, bool p_next)
|
||||
}
|
||||
}
|
||||
|
||||
void VVim::processReplaceAction(QList<Token> &p_tokens)
|
||||
{
|
||||
int repeat = 1;
|
||||
QChar replaceChar;
|
||||
Q_ASSERT(!p_tokens.isEmpty());
|
||||
Token to = p_tokens.takeFirst();
|
||||
if (to.isRepeat()) {
|
||||
repeat = to.m_repeat;
|
||||
Q_ASSERT(!p_tokens.isEmpty());
|
||||
to = p_tokens.takeFirst();
|
||||
}
|
||||
|
||||
Q_ASSERT(to.isKey() && p_tokens.isEmpty());
|
||||
replaceChar = keyToChar(to.m_key.m_key, to.m_key.m_modifiers);
|
||||
if (replaceChar.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(checkMode(VimMode::Normal)
|
||||
|| checkMode(VimMode::Visual)
|
||||
|| checkMode(VimMode::VisualLine))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace the next repeat characters with replaceChar until the end of line.
|
||||
// If repeat is greater than the number of left characters in current line,
|
||||
// do nothing.
|
||||
// In visual mode, repeat is ignored.
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
cursor.beginEditBlock();
|
||||
if (checkMode(VimMode::Normal)) {
|
||||
// Select the characters to be replaced.
|
||||
cursor.clearSelection();
|
||||
int pib = cursor.positionInBlock();
|
||||
int nrChar = cursor.block().length() - 1 - pib;
|
||||
if (repeat <= nrChar) {
|
||||
cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, repeat);
|
||||
}
|
||||
}
|
||||
|
||||
bool changed = replaceSelectedTextWithCharacter(cursor, replaceChar);
|
||||
cursor.endEditBlock();
|
||||
|
||||
if (changed) {
|
||||
m_editor->setTextCursor(cursor);
|
||||
setMode(VimMode::Normal);
|
||||
}
|
||||
}
|
||||
|
||||
bool VVim::clearSelection()
|
||||
{
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
@ -4051,6 +4145,12 @@ bool VVim::expectingCharacterTarget() const
|
||||
|| key == Key(Qt::Key_T, Qt::ShiftModifier));
|
||||
}
|
||||
|
||||
bool VVim::expectingReplaceCharacter() const
|
||||
{
|
||||
return m_keys.size() == 1
|
||||
&& m_keys.first() == Key(Qt::Key_R, Qt::NoModifier);
|
||||
}
|
||||
|
||||
bool VVim::expectingCommandLineInput() const
|
||||
{
|
||||
return m_cmdMode;
|
||||
@ -4227,6 +4327,11 @@ void VVim::addMovementToken(Movement p_movement, Key p_key)
|
||||
m_tokens.append(Token(p_movement, p_key));
|
||||
}
|
||||
|
||||
void VVim::addKeyToken(Key p_key)
|
||||
{
|
||||
m_tokens.append(Token(p_key));
|
||||
}
|
||||
|
||||
void VVim::deleteSelectedText(QTextCursor &p_cursor, bool p_clearEmptyBlock)
|
||||
{
|
||||
if (p_cursor.hasSelection()) {
|
||||
|
@ -262,6 +262,7 @@ private:
|
||||
RedrawAtBottom,
|
||||
JumpPreviousLocation,
|
||||
JumpNextLocation,
|
||||
Replace,
|
||||
Invalid
|
||||
};
|
||||
|
||||
@ -326,7 +327,7 @@ private:
|
||||
Invalid
|
||||
};
|
||||
|
||||
enum class TokenType { Action = 0, Repeat, Movement, Range, Invalid };
|
||||
enum class TokenType { Action = 0, Repeat, Movement, Range, Key, Invalid };
|
||||
|
||||
struct Token
|
||||
{
|
||||
@ -345,6 +346,9 @@ private:
|
||||
Token(Range p_range)
|
||||
: m_type(TokenType::Range), m_range(p_range) {}
|
||||
|
||||
Token(Key p_key)
|
||||
: m_type(TokenType::Key), m_key(p_key) {}
|
||||
|
||||
Token() : m_type(TokenType::Invalid) {}
|
||||
|
||||
bool isRepeat() const
|
||||
@ -367,6 +371,11 @@ private:
|
||||
return m_type == TokenType::Range;
|
||||
}
|
||||
|
||||
bool isKey() const
|
||||
{
|
||||
return m_type == TokenType::Key;
|
||||
}
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return m_type != TokenType::Invalid;
|
||||
@ -392,6 +401,10 @@ private:
|
||||
str = QString("range %1").arg((int)m_range);
|
||||
break;
|
||||
|
||||
case TokenType::Key:
|
||||
str = QString("key %1 %2").arg(m_key.m_key).arg(m_key.m_modifiers);
|
||||
break;
|
||||
|
||||
default:
|
||||
str = "invalid";
|
||||
}
|
||||
@ -409,7 +422,7 @@ private:
|
||||
Movement m_movement;
|
||||
};
|
||||
|
||||
// Used in some Movement.
|
||||
// Used in some Movement and Key Token.
|
||||
Key m_key;
|
||||
};
|
||||
|
||||
@ -503,6 +516,9 @@ private:
|
||||
// Action::JumpPreviousLocation and Action::JumpNextLocation action.
|
||||
void processJumpLocationAction(QList<Token> &p_tokens, bool p_next);
|
||||
|
||||
// Action::Replace.
|
||||
void processReplaceAction(QList<Token> &p_tokens);
|
||||
|
||||
// Clear selection if there is any.
|
||||
// Returns true if there is selection.
|
||||
bool clearSelection();
|
||||
@ -525,6 +541,9 @@ private:
|
||||
// Check m_keys to see if we are expecting a target for f/t/F/T command.
|
||||
bool expectingCharacterTarget() const;
|
||||
|
||||
// Check m_keys to see if we are expecting a character to replace with.
|
||||
bool expectingReplaceCharacter() const;
|
||||
|
||||
// Check if we are in command line mode.
|
||||
bool expectingCommandLineInput() const;
|
||||
|
||||
@ -560,15 +579,18 @@ private:
|
||||
// Get the repeat token from m_tokens.
|
||||
Token *getRepeatToken();
|
||||
|
||||
// Add an Range token at the end of m_tokens.
|
||||
// Add a Range token at the end of m_tokens.
|
||||
void addRangeToken(Range p_range);
|
||||
|
||||
// Add an Movement token at the end of m_tokens.
|
||||
// Add a Movement token at the end of m_tokens.
|
||||
void addMovementToken(Movement p_movement);
|
||||
|
||||
// Add an Movement token at the end of m_tokens.
|
||||
// Add a Movement token at the end of m_tokens.
|
||||
void addMovementToken(Movement p_movement, Key p_key);
|
||||
|
||||
// Add a Key token at the end of m_tokens.
|
||||
void addKeyToken(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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user