From c4683dd2321e1593a739c37027cbc3cddea059bf Mon Sep 17 00:00:00 2001 From: Le Tan Date: Mon, 3 Jul 2017 21:19:19 +0800 Subject: [PATCH] vim-mode: support ~ to reverse case --- src/resources/docs/shortcuts_en.md | 2 +- src/resources/docs/shortcuts_zh.md | 3 +- src/utils/vvim.cpp | 106 +++++++++++++++++++++++++++-- src/utils/vvim.h | 4 ++ 4 files changed, 109 insertions(+), 6 deletions(-) diff --git a/src/resources/docs/shortcuts_en.md b/src/resources/docs/shortcuts_en.md index cf00c186..4261870f 100644 --- a/src/resources/docs/shortcuts_en.md +++ b/src/resources/docs/shortcuts_en.md @@ -123,7 +123,7 @@ VNote supports a simple but useful Vim mode, including **Normal**, **Insert**, * VNote supports following features of Vim: - `r`, `s`, `i`, `I`, `a`, `A`, `o`, and `O`; -- Actions `d`, `c`, `y`, `p`, `<`, `>`, `gu`, and `gU`; +- Actions `d`, `c`, `y`, `p`, `<`, `>`, `gu`, `gU`, and `~`; - Movements `h/j/k/l`, `gj/gk`, `Ctrl+U`, `Ctrl+D`, `gg`, `G`, `0`, `^`, and `$`; - Marks `a-z`; - Registers `"`, `_`, `+`, `a-z`(`A-Z`); diff --git a/src/resources/docs/shortcuts_zh.md b/src/resources/docs/shortcuts_zh.md index e401e186..6246db89 100644 --- a/src/resources/docs/shortcuts_zh.md +++ b/src/resources/docs/shortcuts_zh.md @@ -123,7 +123,8 @@ VNote支持一个简单但有用的Vim模式,包括 **正常**, **插入** VNote支持以下几个Vim的特性: - `r`, `s`, `i`, `I`, `a`, `A`, `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`, `^`, `$`; - 标记 `a-z`; - 寄存器 `"`, `_`, `+`, `a-z`(`A-Z`); - 跳转位置列表 (`Ctrl+O` and `Ctrl+I`); diff --git a/src/utils/vvim.cpp b/src/utils/vvim.cpp index b6465790..4aaa0d9b 100644 --- a/src/utils/vvim.cpp +++ b/src/utils/vvim.cpp @@ -380,6 +380,46 @@ static bool replaceSelectedTextWithCharacter(QTextCursor &p_cursor, QChar p_char return true; } +// Reverse the case of selected text. +// Returns true if the reverse has taken place. +// Need to setTextCursor() after calling this. +static bool reverseSelectedTextCase(QTextCursor &p_cursor) +{ + if (!p_cursor.hasSelection()) { + return false; + } + + QTextDocument *doc = p_cursor.document(); + 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 { + QChar ch = doc->characterAt(p_cursor.position()); + bool changed = false; + if (ch.isLower()) { + ch = ch.toUpper(); + changed = true; + } else if (ch.isUpper()) { + ch = ch.toLower(); + changed = true; + } + + if (changed) { + p_cursor.deleteChar(); + // insertText() will move the cursor right after the inserted text. + p_cursor.insertText(ch); + } else { + p_cursor.movePosition(QTextCursor::NextCharacter); + } + } + } + + return true; +} + bool VVim::handleKeyPressEvent(QKeyEvent *p_event, int *p_autoIndentPos) { bool ret = handleKeyPressEvent(p_event->key(), p_event->modifiers(), p_autoIndentPos); @@ -1930,6 +1970,24 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos) break; } + case Qt::Key_AsciiTilde: + { + if (modifiers == Qt::ShiftModifier) { + tryGetRepeatToken(m_keys, m_tokens); + if (hasActionToken() || !m_keys.isEmpty()) { + break; + } + + // Reverse the case. + addActionToken(Action::ReverseCase); + processCommand(m_tokens); + + break; + } + + break; + } + default: break; } @@ -1995,10 +2053,6 @@ void VVim::processCommand(QList &p_tokens) V_ASSERT(p_tokens.at(0).isAction()); - for (int i = 0; i < p_tokens.size(); ++i) { - qDebug() << "token" << i << p_tokens[i].toString(); - } - Token act = p_tokens.takeFirst(); switch (act.m_action) { case Action::Move: @@ -2073,6 +2127,10 @@ void VVim::processCommand(QList &p_tokens) processReplaceAction(p_tokens); break; + case Action::ReverseCase: + processReverseCaseAction(p_tokens); + break; + default: p_tokens.clear(); break; @@ -4119,6 +4177,46 @@ void VVim::processReplaceAction(QList &p_tokens) } } +void VVim::processReverseCaseAction(QList &p_tokens) +{ + int repeat = 1; + if (!p_tokens.isEmpty()) { + Token to = p_tokens.takeFirst(); + Q_ASSERT(to.isRepeat() && p_tokens.isEmpty()); + repeat = to.m_repeat; + } + + if (!(checkMode(VimMode::Normal) + || checkMode(VimMode::Visual) + || checkMode(VimMode::VisualLine))) { + return; + } + + // Reverse the next repeat characters' case until the end of line. + // If repeat is greater than the number of left characters in current line, + // just change the actual number of left characters. + // In visual mode, repeat is ignored and reverse the selected text. + 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; + cursor.movePosition(QTextCursor::Right, + QTextCursor::KeepAnchor, + repeat > nrChar ? nrChar : repeat); + } + + bool changed = reverseSelectedTextCase(cursor); + cursor.endEditBlock(); + + if (changed) { + m_editor->setTextCursor(cursor); + setMode(VimMode::Normal); + } +} + bool VVim::clearSelection() { QTextCursor cursor = m_editor->textCursor(); diff --git a/src/utils/vvim.h b/src/utils/vvim.h index 1cc35644..0846da98 100644 --- a/src/utils/vvim.h +++ b/src/utils/vvim.h @@ -255,6 +255,7 @@ private: UnIndent, ToUpper, ToLower, + ReverseCase, Undo, Redo, RedrawAtTop, @@ -519,6 +520,9 @@ private: // Action::Replace. void processReplaceAction(QList &p_tokens); + // Action::ReverseCase. + void processReverseCaseAction(QList &p_tokens); + // Clear selection if there is any. // Returns true if there is selection. bool clearSelection();