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:
|
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`;
|
- 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`);
|
||||||
- Leader key (`Space`)
|
- 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`;
|
- `zz`, `zb`, `zt`;
|
||||||
- `u` and `Ctrl+R` for undo and redo;
|
- `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.
|
For now, VNote does **NOT** support the macro and repeat(`.`) features of Vim.
|
||||||
|
|
||||||
|
@ -120,14 +120,17 @@ VNote支持一个简单但有用的Vim模式,包括 **正常**, **插入**
|
|||||||
|
|
||||||
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`(`A-Z`);
|
- 寄存器 `"`, `_`, `+`, `a-z`(`A-Z`);
|
||||||
- 跳转位置列表 (`Ctrl+O` and `Ctrl+I`);
|
- 跳转位置列表 (`Ctrl+O` and `Ctrl+I`);
|
||||||
- 前导键 (`Space`)
|
- 前导键 (`Space`)
|
||||||
- 目前 `<leader>y/d/p` 等同于 `"+y/d/p`, 从而可以访问系统剪切板。
|
- 目前 `<leader>y/d/p` 等同于 `"+y/d/p`, 从而可以访问系统剪切板;
|
||||||
- `zz`, `zb`, `zt`;
|
- `zz`, `zb`, `zt`;
|
||||||
- `u` 和 `Ctrl+R` 撤销和重做;
|
- `u` 和 `Ctrl+R` 撤销和重做;
|
||||||
|
- 文本对象 `i/a`:word, WORD, `''`, `""`, `` ` ` ``, `()`, `[]`, `<>`, and `{}`;
|
||||||
|
- 命令行 `:w`, `:wq`, `:x`, `:q`, and `:q!`;
|
||||||
|
|
||||||
VNote目前暂时不支持Vim的宏和重复(`.`)特性。
|
VNote目前暂时不支持Vim的宏和重复(`.`)特性。
|
||||||
|
|
||||||
|
@ -351,6 +351,31 @@ static bool isControlModifier(int p_modifiers)
|
|||||||
#endif
|
#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 VVim::handleKeyPressEvent(QKeyEvent *p_event, int *p_autoIndentPos)
|
||||||
{
|
{
|
||||||
bool ret = handleKeyPressEvent(p_event->key(), p_event->modifiers(), 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;
|
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
|
// Check leader key here. If leader key conflicts with other keys, it will
|
||||||
// overwrite it.
|
// overwrite it.
|
||||||
// Leader sequence is just like an action.
|
// Leader sequence is just like an action.
|
||||||
@ -1632,6 +1666,13 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
|
|||||||
addActionToken(Action::Redo);
|
addActionToken(Action::Redo);
|
||||||
processCommand(m_tokens);
|
processCommand(m_tokens);
|
||||||
break;
|
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;
|
break;
|
||||||
@ -1974,6 +2015,10 @@ void VVim::processCommand(QList<Token> &p_tokens)
|
|||||||
processJumpLocationAction(p_tokens, true);
|
processJumpLocationAction(p_tokens, true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Action::Replace:
|
||||||
|
processReplaceAction(p_tokens);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
p_tokens.clear();
|
p_tokens.clear();
|
||||||
break;
|
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()
|
bool VVim::clearSelection()
|
||||||
{
|
{
|
||||||
QTextCursor cursor = m_editor->textCursor();
|
QTextCursor cursor = m_editor->textCursor();
|
||||||
@ -4051,6 +4145,12 @@ bool VVim::expectingCharacterTarget() const
|
|||||||
|| key == Key(Qt::Key_T, Qt::ShiftModifier));
|
|| 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
|
bool VVim::expectingCommandLineInput() const
|
||||||
{
|
{
|
||||||
return m_cmdMode;
|
return m_cmdMode;
|
||||||
@ -4227,6 +4327,11 @@ void VVim::addMovementToken(Movement p_movement, Key p_key)
|
|||||||
m_tokens.append(Token(p_movement, 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)
|
void VVim::deleteSelectedText(QTextCursor &p_cursor, bool p_clearEmptyBlock)
|
||||||
{
|
{
|
||||||
if (p_cursor.hasSelection()) {
|
if (p_cursor.hasSelection()) {
|
||||||
|
@ -262,6 +262,7 @@ private:
|
|||||||
RedrawAtBottom,
|
RedrawAtBottom,
|
||||||
JumpPreviousLocation,
|
JumpPreviousLocation,
|
||||||
JumpNextLocation,
|
JumpNextLocation,
|
||||||
|
Replace,
|
||||||
Invalid
|
Invalid
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -326,7 +327,7 @@ private:
|
|||||||
Invalid
|
Invalid
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class TokenType { Action = 0, Repeat, Movement, Range, Invalid };
|
enum class TokenType { Action = 0, Repeat, Movement, Range, Key, Invalid };
|
||||||
|
|
||||||
struct Token
|
struct Token
|
||||||
{
|
{
|
||||||
@ -345,6 +346,9 @@ private:
|
|||||||
Token(Range p_range)
|
Token(Range p_range)
|
||||||
: m_type(TokenType::Range), m_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) {}
|
Token() : m_type(TokenType::Invalid) {}
|
||||||
|
|
||||||
bool isRepeat() const
|
bool isRepeat() const
|
||||||
@ -367,6 +371,11 @@ private:
|
|||||||
return m_type == TokenType::Range;
|
return m_type == TokenType::Range;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isKey() const
|
||||||
|
{
|
||||||
|
return m_type == TokenType::Key;
|
||||||
|
}
|
||||||
|
|
||||||
bool isValid() const
|
bool isValid() const
|
||||||
{
|
{
|
||||||
return m_type != TokenType::Invalid;
|
return m_type != TokenType::Invalid;
|
||||||
@ -392,6 +401,10 @@ private:
|
|||||||
str = QString("range %1").arg((int)m_range);
|
str = QString("range %1").arg((int)m_range);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case TokenType::Key:
|
||||||
|
str = QString("key %1 %2").arg(m_key.m_key).arg(m_key.m_modifiers);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
str = "invalid";
|
str = "invalid";
|
||||||
}
|
}
|
||||||
@ -409,7 +422,7 @@ private:
|
|||||||
Movement m_movement;
|
Movement m_movement;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Used in some Movement.
|
// Used in some Movement and Key Token.
|
||||||
Key m_key;
|
Key m_key;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -503,6 +516,9 @@ private:
|
|||||||
// Action::JumpPreviousLocation and Action::JumpNextLocation action.
|
// Action::JumpPreviousLocation and Action::JumpNextLocation action.
|
||||||
void processJumpLocationAction(QList<Token> &p_tokens, bool p_next);
|
void processJumpLocationAction(QList<Token> &p_tokens, bool p_next);
|
||||||
|
|
||||||
|
// Action::Replace.
|
||||||
|
void processReplaceAction(QList<Token> &p_tokens);
|
||||||
|
|
||||||
// Clear selection if there is any.
|
// Clear selection if there is any.
|
||||||
// Returns true if there is selection.
|
// Returns true if there is selection.
|
||||||
bool clearSelection();
|
bool clearSelection();
|
||||||
@ -525,6 +541,9 @@ private:
|
|||||||
// Check m_keys to see if we are expecting a target for f/t/F/T command.
|
// Check m_keys to see if we are expecting a target for f/t/F/T command.
|
||||||
bool expectingCharacterTarget() const;
|
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.
|
// Check if we are in command line mode.
|
||||||
bool expectingCommandLineInput() const;
|
bool expectingCommandLineInput() const;
|
||||||
|
|
||||||
@ -560,15 +579,18 @@ private:
|
|||||||
// Get the repeat token from m_tokens.
|
// Get the repeat token from m_tokens.
|
||||||
Token *getRepeatToken();
|
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);
|
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);
|
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);
|
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.
|
// Delete selected text if there is any.
|
||||||
// @p_clearEmptyBlock: whether to remove the empty block after deletion.
|
// @p_clearEmptyBlock: whether to remove the empty block after deletion.
|
||||||
void deleteSelectedText(QTextCursor &p_cursor, bool p_clearEmptyBlock);
|
void deleteSelectedText(QTextCursor &p_cursor, bool p_clearEmptyBlock);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user