diff --git a/src/dialog/vfindreplacedialog.h b/src/dialog/vfindreplacedialog.h index d4ca12ac..59cd0397 100644 --- a/src/dialog/vfindreplacedialog.h +++ b/src/dialog/vfindreplacedialog.h @@ -9,14 +9,6 @@ class QLineEdit; class QPushButton; class QCheckBox; -enum FindOption -{ - CaseSensitive = 0x1U, - WholeWordOnly = 0x2U, - RegularExpression = 0x4U, - IncrementalSearch = 0x8U -}; - class VFindReplaceDialog : public QWidget { Q_OBJECT diff --git a/src/resources/docs/shortcuts_en.md b/src/resources/docs/shortcuts_en.md index 883d7c3f..a20574fb 100644 --- a/src/resources/docs/shortcuts_en.md +++ b/src/resources/docs/shortcuts_en.md @@ -5,7 +5,7 @@ ## Normal Shortcuts - `Ctrl+E E` Toggle expanding the edit area. -- `Ctrl+N` +- `Ctrl+Alt+N` Create a note in current directory. - `Ctrl+F` Find/Replace in current note. @@ -162,10 +162,11 @@ VNote supports following features of Vim: - Jump locations list (`Ctrl+O` and `Ctrl+I`); - Leader key (`Space`) - Currently `y/d/p` equals to `"+y/d/p`, which will access the system's clipboard; + - `` to clear search highlight; - `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!`; +- Command line `:w`, `:wq`, `:x`, `:q`, `:q!`, and `:nohlsearch`; - Jump between titles - `[[`: jump to previous title; - `]]`: jump to next title; @@ -173,6 +174,9 @@ VNote supports following features of Vim: - `][`: jump to next title at the same level; - `[{`: jump to previous title at a higher level; - `]}`: jump to next title at a higher level; +- `/` and `?` to search + - `n` and `N` to find next or previous occurence; + - `Ctrl+N` and `Ctrl+P` to navigate through the search history; For now, VNote does **NOT** support the macro and repeat(`.`) features of Vim. diff --git a/src/resources/docs/shortcuts_zh.md b/src/resources/docs/shortcuts_zh.md index 47bd8a8e..ce1ba88e 100644 --- a/src/resources/docs/shortcuts_zh.md +++ b/src/resources/docs/shortcuts_zh.md @@ -5,7 +5,7 @@ ## 常规快捷键 - `Ctrl+E E` 是否扩展编辑区域。 -- `Ctrl+N` +- `Ctrl+Alt+N` 在当前文件夹下新建笔记。 - `Ctrl+F` 页内查找和替换。 @@ -163,10 +163,11 @@ VNote支持以下几个Vim的特性: - 跳转位置列表 (`Ctrl+O` and `Ctrl+I`); - 前导键 (`Space`) - 目前 `y/d/p` 等同于 `"+y/d/p`, 从而可以访问系统剪切板; + - `` 清除查找高亮; - `zz`, `zb`, `zt`; - `u` 和 `Ctrl+R` 撤销和重做; -- 文本对象 `i/a`:word, WORD, `''`, `""`, `` ` ` ``, `()`, `[]`, `<>`, and `{}`; -- 命令行 `:w`, `:wq`, `:x`, `:q`, and `:q!`; +- 文本对象 `i/a`:word, WORD, `''`, `""`, `` ` ` ``, `()`, `[]`, `<>`, `{}`; +- 命令行 `:w`, `:wq`, `:x`, `:q`, `:q!`, `:nohlsearch`; - 标题跳转 - `[[`:跳转到上一个标题; - `]]`: 跳转到下一个标题; @@ -174,6 +175,9 @@ VNote支持以下几个Vim的特性: - `][`:跳转到下一个同层级的标题; - `[{`:跳转到上一个高一层级的标题; - `]}`:跳转到下一个高一层级的标题; +- `/` 和 `?` 开始查找 + - `n` 和 `N` 查找下一处或上一处; + - `Ctrl+N` 和 `Ctrl+P` 浏览查找历史; VNote目前暂时不支持Vim的宏和重复(`.`)特性。 diff --git a/src/resources/styles/default.mdhl b/src/resources/styles/default.mdhl index 0668915d..2b95e9a8 100644 --- a/src/resources/styles/default.mdhl +++ b/src/resources/styles/default.mdhl @@ -12,11 +12,20 @@ editor # QTextEdit just choose the first available font, so specify the Chinese fonts first # Do not use "" to quote the name font-family: Hiragino Sans GB, 冬青黑体, Microsoft YaHei, 微软雅黑, Microsoft YaHei UI, WenQuanYi Micro Hei, 文泉驿雅黑, Dengxian, 等线体, STXihei, 华文细黑, Liberation Sans, Droid Sans, NSimSun, 新宋体, SimSun, 宋体, Helvetica, sans-serif, Tahoma, Arial, Verdana, Geneva, Georgia, Times New Roman +font-size: 12 # [VNote] Style for trailing space trailing-space: a8a8a8 -font-size: 12 +# [VNote] Style for line number line-number-background: bdbdbd line-number-foreground: 424242 +# [VNote] style for selected word highlight +selected-word-background: dfdf00 +# [VNote] style for searched word highlight +searched-word-background: 4db6ac +# [VNote] style for searched word under cursor highlight +searched-word-cursor-background: 66bb6a +# [VNote] style for incremental searched word highlight +incremental-searched-word-background: ce93d8 editor-selection foreground: eeeeee diff --git a/src/resources/vnote.ini b/src/resources/vnote.ini index 556e1f4c..22583600 100644 --- a/src/resources/vnote.ini +++ b/src/resources/vnote.ini @@ -78,7 +78,7 @@ size=4 ; Ctrl+E is reserved for Captain Mode. ; Ctrl+Q is reserved for quitting VNote. 1\operation=NewNote -1\keysequence=Ctrl+N +1\keysequence=Ctrl+Alt+N 2\operation=SaveNote 2\keysequence=Ctrl+S 3\operation=SaveAndRead diff --git a/src/utils/vvim.cpp b/src/utils/vvim.cpp index f1e2db45..871b62b3 100644 --- a/src/utils/vvim.cpp +++ b/src/utils/vvim.cpp @@ -11,6 +11,7 @@ #include "vconfigmanager.h" #include "vedit.h" #include "utils/veditutils.h" +#include "vconstants.h" extern VConfigManager vconfig; @@ -18,6 +19,8 @@ const QChar VVim::c_unnamedRegister = QChar('"'); const QChar VVim::c_blackHoleRegister = QChar('_'); const QChar VVim::c_selectionRegister = QChar('+'); +const int VVim::SearchHistory::c_capacity = 50; + #define ADDKEY(x, y) case (x): {ch = (y); break;} // Returns NULL QChar if invalid. @@ -86,7 +89,7 @@ VVim::VVim(VEdit *p_editor) : QObject(p_editor), m_editor(p_editor), m_editConfig(&p_editor->getConfig()), m_mode(VimMode::Invalid), m_resetPositionInBlock(true), m_regName(c_unnamedRegister), - m_cmdMode(false), m_leaderKey(Key(Qt::Key_Space)), m_replayLeaderSequence(false) + m_leaderKey(Key(Qt::Key_Space)), m_replayLeaderSequence(false) { Q_ASSERT(m_editConfig->m_enableVimMode); @@ -496,16 +499,6 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos) qDebug() << "replaying sequence" << keyToChar(key, modifiers); } - if (expectingCommandLineInput()) { - // All input will be treated as command line input. - // [Enter] to execute the command and exit command line mode. - if (processCommandLine(keyInfo)) { - goto clear_accept; - } else { - goto accept; - } - } - m_pendingKeys.append(keyInfo); if (expectingLeaderSequence()) { @@ -726,8 +719,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos) V_ASSERT(mm != Movement::Invalid); tryAddMoveAction(); - - m_tokens.append(Token(mm)); + addMovementToken(mm); processCommand(m_tokens); resetPositionInBlock = false; } @@ -1784,15 +1776,8 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos) if (m_keys.isEmpty() && m_tokens.isEmpty() && checkMode(VimMode::Normal)) { - // :, enter command line mode. - // For simplicity, we do not use a standalone mode for this mode. - // Just let it be in Normal mode and use another variable to - // specify this. - m_cmdMode = true; - goto accept; + emit commandLineTriggered(CommandLineType::Command); } - - break; } break; @@ -2003,6 +1988,91 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos) break; } + case Qt::Key_Slash: + { + if (modifiers == Qt::NoModifier) { + if (m_tokens.isEmpty() + && m_keys.isEmpty() + && checkMode(VimMode::Normal)) { + emit commandLineTriggered(CommandLineType::SearchForward); + } + } + + break; + } + + case Qt::Key_Question: + { + if (modifiers == Qt::ShiftModifier) { + if (m_tokens.isEmpty() + && m_keys.isEmpty() + && checkMode(VimMode::Normal)) { + emit commandLineTriggered(CommandLineType::SearchBackward); + } + } + + break; + } + + case Qt::Key_N: + { + if (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier) { + // n, FindNext/FindPrevious movement. + tryGetRepeatToken(m_keys, m_tokens); + + if (!m_keys.isEmpty()) { + break; + } + + Movement mm = Movement::FindNext; + if (modifiers == Qt::ShiftModifier) { + mm = Movement::FindPrevious; + } + + tryAddMoveAction(); + addMovementToken(mm); + processCommand(m_tokens); + } + + break; + } + + case Qt::Key_Asterisk: + { + if (modifiers == Qt::ShiftModifier) { + // *, FindNextWordUnderCursor movement. + tryGetRepeatToken(m_keys, m_tokens); + + if (!m_keys.isEmpty()) { + break; + } + + tryAddMoveAction(); + addMovementToken(Movement::FindNextWordUnderCursor); + processCommand(m_tokens); + } + + break; + } + + case Qt::Key_NumberSign: + { + if (modifiers == Qt::ShiftModifier) { + // #, FindPreviousWordUnderCursor movement. + tryGetRepeatToken(m_keys, m_tokens); + + if (!m_keys.isEmpty()) { + break; + } + + tryAddMoveAction(); + addMovementToken(Movement::FindPreviousWordUnderCursor); + processCommand(m_tokens); + } + + break; + } + default: break; } @@ -2031,7 +2101,6 @@ void VVim::resetState() m_pendingKeys.clear(); setRegister(c_unnamedRegister); m_resetPositionInBlock = true; - m_cmdMode = false; } VimMode VVim::getMode() const @@ -2808,6 +2877,99 @@ handle_target: break; } + case Movement::FindPrevious: + forward = false; + // Fall through. + case Movement::FindNext: + { + if (p_repeat == -1) { + p_repeat = 1; + } + + if (m_searchHistory.isEmpty()) { + break; + } + + // Record current location. + m_locations.addLocation(p_cursor); + + const SearchItem &item = m_searchHistory.lastItem(); + while (--p_repeat >= 0) { + hasMoved = m_editor->findText(item.m_text, item.m_options, + forward ? item.m_forward : !item.m_forward, + &p_cursor, p_moveMode); + } + + break; + } + + case Movement::FindPreviousWordUnderCursor: + forward = false; + // Fall through. + case Movement::FindNextWordUnderCursor: + { + if (p_repeat == -1) { + p_repeat = 1; + } + + // Get current word under cursor. + // Different from Vim: + // We do not recognize a word as strict as Vim. + int start, end; + findCurrentWord(p_cursor, start, end); + if (start == end) { + // Spaces, find next word. + QTextCursor cursor = p_cursor; + while (true) { + moveCursorAcrossSpaces(cursor, p_moveMode, true); + if (cursor.atEnd()) { + break; + } + + if (!doc->characterAt(cursor.position()).isSpace()) { + findCurrentWord(cursor, start, end); + Q_ASSERT(start != end); + break; + } + } + + if (start == end) { + break; + } + } + + QTextCursor cursor = p_cursor; + cursor.setPosition(start); + cursor.setPosition(end, QTextCursor::KeepAnchor); + QString text = cursor.selectedText(); + if (text.isEmpty()) { + break; + } + + // Record current location. + m_locations.addLocation(p_cursor); + + p_cursor.setPosition(start, p_moveMode); + + // Case-insensitive, non-regularexpression. + SearchItem item; + item.m_rawStr = text; + item.m_text = text; + item.m_forward = forward; + + m_searchHistory.addItem(item); + m_searchHistory.resetIndex(); + while (--p_repeat >= 0) { + hasMoved = m_editor->findText(item.m_text, item.m_options, + item.m_forward, + &p_cursor, p_moveMode); + } + + Q_ASSERT(hasMoved); + + break; + } + default: break; } @@ -4395,11 +4557,6 @@ bool VVim::expectingReplaceCharacter() const && m_keys.first() == Key(Qt::Key_R, Qt::NoModifier); } -bool VVim::expectingCommandLineInput() const -{ - return m_cmdMode; -} - bool VVim::expectingLeaderSequence() const { if (m_replayLeaderSequence || m_keys.isEmpty()) { @@ -4800,134 +4957,153 @@ bool VVim::checkMode(VimMode p_mode) return m_mode == p_mode; } -bool VVim::processCommandLine(const Key &p_key) +bool VVim::processCommandLine(VVim::CommandLineType p_type, const QString &p_cmd) { - Q_ASSERT(m_cmdMode); - - if (p_key == Key(Qt::Key_Return) - || p_key == Key(Qt::Key_Enter, Qt::KeypadModifier)) { - // Enter, try to execute the command and exit cmd line mode. - executeCommand(); - m_cmdMode = false; - return true; - } - - if (p_key.m_key == Qt::Key_Escape - || (p_key.m_key == Qt::Key_BracketLeft && isControlModifier(p_key.m_modifiers))) { - // Go back to Normal mode. - m_keys.clear(); - m_pendingKeys.clear(); - m_cmdMode = false; - - setMode(VimMode::Normal); - return true; - } - - switch (p_key.m_key) { - case Qt::Key_Backspace: - // Delete one char backward. - if (m_keys.isEmpty()) { - // Exit command line mode. - Q_ASSERT(m_pendingKeys.size() == 1); - m_pendingKeys.pop_back(); - m_cmdMode = false; - return true; - } else { - m_keys.pop_back(); - m_pendingKeys.pop_back(); - } + setMode(VimMode::Normal); + bool ret = false; + switch (p_type) { + case CommandLineType::Command: + ret = executeCommand(p_cmd); break; - case Qt::Key_U: + case CommandLineType::SearchForward: + // Fall through. + case CommandLineType::SearchBackward: { - if (isControlModifier(p_key.m_modifiers)) { - // Ctrl+U, delete all input keys. - while (!m_keys.isEmpty()) { - m_keys.pop_back(); - m_pendingKeys.pop_back(); - } - } else { - // Just pend this key. - m_pendingKeys.append(p_key); - m_keys.append(p_key); - } - + SearchItem item = fetchSearchItem(p_type, p_cmd); + m_editor->findText(item.m_text, item.m_options, item.m_forward); + m_searchHistory.addItem(item); + m_searchHistory.resetIndex(); break; } default: - // Just pend this key. - m_pendingKeys.append(p_key); - m_keys.append(p_key); + break; } - return false; + return ret; } -void VVim::executeCommand() +void VVim::processCommandLineChanged(VVim::CommandLineType p_type, + const QString &p_cmd) +{ + setMode(VimMode::Normal); + + if (p_type == CommandLineType::SearchForward + || p_type == CommandLineType::SearchBackward) { + // Peek text. + SearchItem item = fetchSearchItem(p_type, p_cmd); + m_editor->peekText(item.m_text, item.m_options, item.m_forward); + } +} + +void VVim::processCommandLineCancelled() +{ + m_searchHistory.resetIndex(); + m_editor->clearIncrementalSearchedWordHighlight(); +} + +VVim::SearchItem VVim::fetchSearchItem(VVim::CommandLineType p_type, + const QString &p_cmd) +{ + Q_ASSERT(p_type == CommandLineType::SearchForward + || p_type == CommandLineType::SearchBackward); + + SearchItem item; + item.m_rawStr = p_cmd; + item.m_text = p_cmd; + item.m_forward = p_type == CommandLineType::SearchForward; + + if (p_cmd.indexOf("\\C") > -1) { + item.m_options |= FindOption::CaseSensitive; + item.m_text.remove("\\C"); + } + + item.m_options |= FindOption::RegularExpression; + + return item; +} + +bool VVim::executeCommand(const QString &p_cmd) { bool validCommand = true; QString msg; - if (m_keys.isEmpty()) { - return; - } if (m_keys.size() == 1) { - const Key &key0 = m_keys.first(); - if (key0 == Key(Qt::Key_W)) { + Q_ASSERT(m_tokens.isEmpty() && m_keys.isEmpty()); + if (p_cmd.isEmpty()) { + return true; + }else if (p_cmd.size() == 1) { + if (p_cmd == "w") { // :w, save current file. emit m_editor->saveNote(); msg = tr("Note has been saved"); - } else if (key0 == Key(Qt::Key_Q)) { + } else if (p_cmd == "q") { // :q, quit edit mode. emit m_editor->discardAndRead(); msg = tr("Quit"); - } else if (key0 == Key(Qt::Key_X)) { + } else if (p_cmd == "x") { // :x, save if there is any change and quit edit mode. emit m_editor->saveAndRead(); msg = tr("Quit with note having been saved"); } else { validCommand = false; } - } else if (m_keys.size() == 2) { - const Key &key0 = m_keys.first(); - const Key &key1 = m_keys.at(1); - if (key0 == Key(Qt::Key_W) && key1 == Key(Qt::Key_Q)) { + } else if (p_cmd.size() == 2) { + if (p_cmd == "wq") { // :wq, save change and quit edit mode. // We treat it same as :x. emit m_editor->saveAndRead(); msg = tr("Quit with note having been saved"); - } else if (key0 == Key(Qt::Key_Q) && key1 == Key(Qt::Key_Exclam, Qt::ShiftModifier)) { + } else if (p_cmd == "q!") { // :q!, discard change and quit edit mode. emit m_editor->discardAndRead(); msg = tr("Quit"); } else { validCommand = false; } + } else if (p_cmd == "nohlsearch") { + // :nohlsearch, clear highlight search. + clearSearchHighlight(); } else { validCommand = false; } - if (!validCommand && !hasNonDigitPendingKeys() && m_tokens.isEmpty()) { + if (!validCommand) { + bool allDigits = true; + for (int i = 0; i < p_cmd.size(); ++i) { + if (!p_cmd[i].isDigit()) { + allDigits = false; + break; + } + } + // All digits. // Jump to a specific line. - tryGetRepeatToken(m_keys, m_tokens); - tryAddMoveAction(); - addMovementToken(Movement::LineJump); - processCommand(m_tokens); - validCommand = true; + if (allDigits) { + bool ok; + int num = p_cmd.toInt(&ok, 10); + if (num == 0) { + num = 1; + } + + if (ok && num > 0) { + m_tokens.append(Token(num)); + tryAddMoveAction(); + addMovementToken(Movement::LineJump); + processCommand(m_tokens); + validCommand = true; + } + } } if (!validCommand) { - QString str; - for (auto const & key : m_keys) { - str.append(keyToChar(key.m_key, key.m_modifiers)); - } - - message(tr("Not an editor command: %1").arg(str)); + message(tr("Not an editor command: %1").arg(p_cmd)); } else { message(msg); } + + return validCommand; } bool VVim::hasNonDigitPendingKeys(const QList &p_keys) @@ -4974,6 +5150,9 @@ bool VVim::processLeaderSequence(const Key &p_key) replaySeq.append(Key(Qt::Key_QuoteDbl, Qt::ShiftModifier)); replaySeq.append(Key(Qt::Key_Plus, Qt::ShiftModifier)); replaySeq.append(Key(Qt::Key_P, Qt::ShiftModifier)); + } else if (p_key == Key(Qt::Key_Space)) { + // , clear search highlight + clearSearchHighlight(); } else { validSequence = false; } @@ -4994,6 +5173,8 @@ bool VVim::processLeaderSequence(const Key &p_key) } m_replayLeaderSequence = false; + } else { + resetState(); } return validSequence; @@ -5163,3 +5344,109 @@ void VVim::processTitleJump(const QList &p_tokens, bool p_forward, int p_ m_locations.addLocation(cursor); } } + +void VVim::SearchHistory::addItem(const SearchItem &p_item) +{ + m_isLastItemForward = p_item.m_forward; + if (m_isLastItemForward) { + m_forwardItems.push_back(p_item); + m_forwardIdx = m_forwardItems.size(); + } else { + m_backwardItems.push_back(p_item); + m_backwardIdx = m_forwardItems.size(); + } + + qDebug() << "search history add item" << m_isLastItemForward + << m_forwardIdx << m_forwardItems.size() + << m_backwardIdx << m_backwardItems.size(); +} + +const VVim::SearchItem &VVim::SearchHistory::lastItem() const +{ + if (m_isLastItemForward) { + Q_ASSERT(!m_forwardItems.isEmpty()); + return m_forwardItems.back(); + } else { + Q_ASSERT(!m_backwardItems.isEmpty()); + return m_backwardItems.back(); + } +} + +const VVim::SearchItem &VVim::SearchHistory::nextItem(bool p_forward) +{ + Q_ASSERT(hasNext(p_forward)); + return p_forward ? m_forwardItems.at(++m_forwardIdx) + : m_backwardItems.at(++m_backwardIdx); +} + +// Return previous item in the @p_forward stack. +const VVim::SearchItem &VVim::SearchHistory::previousItem(bool p_forward) +{ + Q_ASSERT(hasPrevious(p_forward)); + qDebug() << "previousItem" << p_forward << m_forwardItems.size() << m_backwardItems.size() + << m_forwardIdx << m_backwardIdx; + return p_forward ? m_forwardItems.at(--m_forwardIdx) + : m_backwardItems.at(--m_backwardIdx); +} + +void VVim::SearchHistory::resetIndex() +{ + m_forwardIdx = m_forwardItems.size(); + m_backwardIdx = m_backwardItems.size(); +} + +QString VVim::getNextCommandHistory(VVim::CommandLineType p_type, + const QString &p_cmd) +{ + Q_UNUSED(p_cmd); + bool forward = false; + QString cmd; + switch (p_type) { + case CommandLineType::SearchForward: + forward = true; + // Fall through. + case CommandLineType::SearchBackward: + if (m_searchHistory.hasNext(forward)) { + return m_searchHistory.nextItem(forward).m_rawStr; + } else { + m_searchHistory.resetIndex(); + } + + break; + + default: + break; + } + + return cmd; +} + +// Get the previous command in history of @p_type. @p_cmd is the current input. +QString VVim::getPreviousCommandHistory(VVim::CommandLineType p_type, + const QString &p_cmd) +{ + Q_UNUSED(p_cmd); + bool forward = false; + QString cmd; + switch (p_type) { + case CommandLineType::SearchForward: + forward = true; + // Fall through. + case CommandLineType::SearchBackward: + if (m_searchHistory.hasPrevious(forward)) { + return m_searchHistory.previousItem(forward).m_rawStr; + } + + break; + + default: + break; + } + + return cmd; +} + +void VVim::clearSearchHighlight() +{ + m_editor->clearSearchedWordHighlight(); +} diff --git a/src/utils/vvim.h b/src/utils/vvim.h index b0902fe8..bd65fc38 100644 --- a/src/utils/vvim.h +++ b/src/utils/vvim.h @@ -146,6 +146,14 @@ public: QChar m_lastUsedMark; }; + enum class CommandLineType + { + Command, + SearchForward, + SearchBackward, + Invalid + }; + // Handle key press event. // @p_autoIndentPos: the cursor position of last auto indent. // Returns true if the event is consumed and need no more handling. @@ -172,6 +180,26 @@ public: // Get m_marks. const VVim::Marks &getMarks() const; + // Process command line of type @p_type and command @p_cmd. + // Returns true if it is a valid command. + bool processCommandLine(VVim::CommandLineType p_type, const QString &p_cmd); + + // Process the command line text change. + void processCommandLineChanged(VVim::CommandLineType p_type, + const QString &p_cmd); + + void processCommandLineCancelled(); + + // Get the next command in history of @p_type. @p_cmd is the current input. + // Return NULL QString if history is not applicable. + QString getNextCommandHistory(VVim::CommandLineType p_type, + const QString &p_cmd); + + // Get the previous command in history of @p_type. @p_cmd is the current input. + // Return NULL QString if history is not applicable. + QString getPreviousCommandHistory(VVim::CommandLineType p_type, + const QString &p_cmd); + signals: // Emit when current mode has been changed. void modeChanged(VimMode p_mode); @@ -182,6 +210,9 @@ signals: // Emit when current status updated. void vimStatusUpdated(const VVim *p_vim); + // Emit when user pressed : to trigger command line. + void commandLineTriggered(VVim::CommandLineType p_type); + private slots: // When user use mouse to select texts in Normal mode, we should change to // Visual mode. @@ -242,6 +273,79 @@ private: } }; + // Search item including the searched text and options. + struct SearchItem + { + SearchItem() : m_options(0), m_forward(true) {} + + // The user raw input. + QString m_rawStr; + + // The string used to search. + QString m_text; + + uint m_options; + bool m_forward; + }; + + class SearchHistory + { + public: + SearchHistory() + : m_forwardIdx(0), m_backwardIdx(0), m_isLastItemForward(true) {} + + // Add @p_item to history. + void addItem(const SearchItem &p_item); + + // Whether the history is empty. + bool isEmpty() const + { + return m_forwardItems.isEmpty() && m_backwardItems.isEmpty(); + } + + bool hasNext(bool p_forward) const + { + return p_forward ? m_forwardIdx < m_forwardItems.size() - 1 + : m_backwardIdx < m_backwardItems.size() - 1; + } + + bool hasPrevious(bool p_forward) const + { + return p_forward ? m_forwardIdx > 0 + : m_backwardIdx > 0; + } + + // Return the last search item according to m_isLastItemForward. + // Make sure the history is not empty before calling this. + const SearchItem &lastItem() const; + + // Return next item in the @p_forward stack. + // Make sure before by calling hasNext(). + const SearchItem &nextItem(bool p_forward); + + // Return previous item in the @p_forward stack. + // Make sure before by calling hasPrevious(). + const SearchItem &previousItem(bool p_forward); + + void resetIndex(); + + private: + // Maintain two stacks for the search history. Use the back as the top + // of the stack. + // The idx points to the next item to push. + // Just simply add new search item to the stack, without duplication. + QList m_forwardItems; + int m_forwardIdx; + + QList m_backwardItems; + int m_backwardIdx; + + // Whether last search item is forward or not. + bool m_isLastItemForward; + + static const int c_capacity; + }; + // Supported actions. enum class Action { @@ -301,6 +405,10 @@ private: MarkJump, MarkJumpLine, FindPair, + FindNext, + FindPrevious, + FindNextWordUnderCursor, + FindPreviousWordUnderCursor, Invalid }; @@ -549,9 +657,6 @@ private: // 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; - // Check if we are in a leader sequence. bool expectingLeaderSequence() const; @@ -645,15 +750,12 @@ private: // Check if m_mode equals to p_mode. bool checkMode(VimMode p_mode); - // In command line mode, read input @p_key and process it. - // Returns true if a command has been completed, otherwise returns false. - bool processCommandLine(const Key &p_key); - - // Execute command specified by m_keys. - // @p_keys does not contain the leading colon. + // Execute command specified by @p_cmd. + // @p_cmd does not contain the leading colon. + // Returns true if it is a valid command. // Following commands are supported: - // :w, :wq, :q, :q!, :x - void executeCommand(); + // w, wq, q, q!, x, + bool executeCommand(const QString &p_cmd); // Check if m_keys has non-digit key. bool hasNonDigitPendingKeys(); @@ -674,6 +776,15 @@ private: // [[, ]], [], ][, [{, ]}. void processTitleJump(const QList &p_tokens, bool p_forward, int p_relativeLevel); + // Fetch the searched string and options from @p_type and @p_cmd. + // \C for case-sensitive; + // Case-insensitive by default. + // Regular-expression by default. + VVim::SearchItem fetchSearchItem(VVim::CommandLineType p_type, const QString &p_cmd); + + // Clear search highlight. + void clearSearchHighlight(); + VEdit *m_editor; const VEditConfig *m_editConfig; VimMode m_mode; @@ -699,9 +810,6 @@ private: // Last f/F/t/T Token. Token m_lastFindToken; - // Whether in command line mode. - bool m_cmdMode; - // The leader key, which is Key_Space by default. Key m_leaderKey; @@ -714,6 +822,9 @@ private: Marks m_marks; + // Search history. + SearchHistory m_searchHistory; + static const QChar c_unnamedRegister; static const QChar c_blackHoleRegister; static const QChar c_selectionRegister; diff --git a/src/vconfigmanager.cpp b/src/vconfigmanager.cpp index 9ca31811..9aa135f8 100644 --- a/src/vconfigmanager.cpp +++ b/src/vconfigmanager.cpp @@ -340,7 +340,11 @@ void VConfigManager::updateMarkdownEditStyle() static const QString defaultVimInsertBg = "#CDC0B0"; static const QString defaultVimVisualBg = "#90CAF9"; static const QString defaultVimReplaceBg = "#F8BBD0"; - static const QString defaultTrailingSpaceBackground = "#A8A8A8"; + static const QString defaultTrailingSpaceBg = "#A8A8A8"; + static const QString defaultSelectedWordBg = "#DFDF00"; + static const QString defaultSearchedWordBg = "#81C784"; + static const QString defaultSearchedWordCursorBg = "#4DB6AC"; + static const QString defaultIncrementalSearchedWordBg = "#CE93D8"; static const QString defaultLineNumberBg = "#BDBDBD"; static const QString defaultLineNumberFg = "#424242"; @@ -398,7 +402,11 @@ void VConfigManager::updateMarkdownEditStyle() } } - m_editorTrailingSpaceBg = defaultTrailingSpaceBackground; + m_editorTrailingSpaceBg = defaultTrailingSpaceBg; + m_editorSelectedWordBg = defaultSelectedWordBg; + m_editorSearchedWordBg = defaultSearchedWordBg; + m_editorSearchedWordCursorBg = defaultSearchedWordCursorBg; + m_editorIncrementalSearchedWordBg = defaultIncrementalSearchedWordBg; m_editorLineNumberBg = defaultLineNumberBg; m_editorLineNumberFg = defaultLineNumberFg; auto editorIt = styles.find("editor"); @@ -417,6 +425,26 @@ void VConfigManager::updateMarkdownEditStyle() if (it != editorIt->end()) { m_editorLineNumberFg = "#" + *it; } + + it = editorIt->find("selected-word-background"); + if (it != editorIt->end()) { + m_editorSelectedWordBg = "#" + *it; + } + + it = editorIt->find("searched-word-background"); + if (it != editorIt->end()) { + m_editorSearchedWordBg = "#" + *it; + } + + it = editorIt->find("searched-word-cursor-background"); + if (it != editorIt->end()) { + m_editorSearchedWordCursorBg = "#" + *it; + } + + it = editorIt->find("incremental-searched-word-background"); + if (it != editorIt->end()) { + m_editorIncrementalSearchedWordBg = "#" + *it; + } } } diff --git a/src/vconfigmanager.h b/src/vconfigmanager.h index 4bc3c1e8..9bc825b7 100644 --- a/src/vconfigmanager.h +++ b/src/vconfigmanager.h @@ -163,7 +163,11 @@ public: bool isCustomWebZoomFactor(); const QString &getEditorCurrentLineBg() const; - QString getEditorTrailingSpaceBackground() const; + const QString &getEditorTrailingSpaceBg() const; + const QString &getEditorSelectedWordBg() const; + const QString &getEditorSearchedWordBg() const; + const QString &getEditorSearchedWordCursorBg() const; + const QString &getEditorIncrementalSearchedWordBg() const; const QString &getEditorVimNormalBg() const; const QString &getEditorVimInsertBg() const; @@ -363,6 +367,18 @@ private: // Trailing space background color in editor. QString m_editorTrailingSpaceBg; + // Background color of selected word in editor. + QString m_editorSelectedWordBg; + + // Background color of searched word in editor. + QString m_editorSearchedWordBg; + + // Background color of searched word under cursor in editor. + QString m_editorSearchedWordCursorBg; + + // Background color of incremental searched word in editor. + QString m_editorIncrementalSearchedWordBg; + // Enable colde block syntax highlight. bool m_enableCodeBlockHighlight; @@ -827,11 +843,31 @@ inline const QString &VConfigManager::getEditorCurrentLineBg() const return m_editorCurrentLineBg; } -inline QString VConfigManager::getEditorTrailingSpaceBackground() const +inline const QString &VConfigManager::getEditorTrailingSpaceBg() const { return m_editorTrailingSpaceBg; } +inline const QString &VConfigManager::getEditorSelectedWordBg() const +{ + return m_editorSelectedWordBg; +} + +inline const QString &VConfigManager::getEditorSearchedWordBg() const +{ + return m_editorSearchedWordBg; +} + +inline const QString &VConfigManager::getEditorSearchedWordCursorBg() const +{ + return m_editorSearchedWordCursorBg; +} + +inline const QString &VConfigManager::getEditorIncrementalSearchedWordBg() const +{ + return m_editorIncrementalSearchedWordBg; +} + inline const QString &VConfigManager::getEditorVimNormalBg() const { return m_editorVimNormalBg; diff --git a/src/vconstants.h b/src/vconstants.h index f3f58624..e759139b 100644 --- a/src/vconstants.h +++ b/src/vconstants.h @@ -41,4 +41,13 @@ enum class TextDecoration { None, Underline, Strikethrough, InlineCode }; + +enum FindOption +{ + CaseSensitive = 0x1U, + WholeWordOnly = 0x2U, + RegularExpression = 0x4U, + IncrementalSearch = 0x8U +}; + #endif diff --git a/src/vedit.cpp b/src/vedit.cpp index 4c66214d..13c014b9 100644 --- a/src/vedit.cpp +++ b/src/vedit.cpp @@ -6,8 +6,8 @@ #include "vconfigmanager.h" #include "vtoc.h" #include "utils/vutils.h" +#include "utils/veditutils.h" #include "veditoperations.h" -#include "dialog/vfindreplacedialog.h" #include "vedittab.h" extern VConfigManager vconfig; @@ -49,11 +49,11 @@ VEdit::VEdit(VFile *p_file, QWidget *p_parent) const int extraSelectionHighlightTimer = 500; const int labelSize = 64; - m_selectedWordColor = QColor("Yellow"); - m_searchedWordColor = QColor(g_vnote->getColorFromPalette("Green4")); - m_searchedWordCursorColor = QColor("#64B5F6"); - m_incrementalSearchedWordColor = QColor(g_vnote->getColorFromPalette("Purple2")); - m_trailingSpaceColor = QColor(vconfig.getEditorTrailingSpaceBackground()); + m_selectedWordColor = QColor(vconfig.getEditorSelectedWordBg()); + m_searchedWordColor = QColor(vconfig.getEditorSearchedWordBg()); + m_searchedWordCursorColor = QColor(vconfig.getEditorSearchedWordCursorBg()); + m_incrementalSearchedWordColor = QColor(vconfig.getEditorIncrementalSearchedWordBg()); + m_trailingSpaceColor = QColor(vconfig.getEditorTrailingSpaceBg()); QPixmap wrapPixmap(":/resources/icons/search_wrap.svg"); m_wrapLabel = new QLabel(this); @@ -151,11 +151,11 @@ void VEdit::scrollToLine(int p_lineNumber) { Q_ASSERT(p_lineNumber >= 0); - // Move the cursor to the end first - moveCursor(QTextCursor::End); - QTextCursor cursor(document()->findBlockByLineNumber(p_lineNumber)); - cursor.movePosition(QTextCursor::EndOfBlock); - setTextCursor(cursor); + QTextBlock block = document()->findBlockByLineNumber(p_lineNumber); + if (block.isValid()) { + VEditUtils::scrollBlockInPage(this, block.blockNumber(), 0); + moveCursor(QTextCursor::EndOfBlock); + } } bool VEdit::isModified() const @@ -178,7 +178,7 @@ void VEdit::insertImage() } } -bool VEdit::peekText(const QString &p_text, uint p_options) +bool VEdit::peekText(const QString &p_text, uint p_options, bool p_forward) { if (p_text.isEmpty()) { makeBlockVisible(document()->findBlock(textCursor().selectionStart())); @@ -188,8 +188,10 @@ bool VEdit::peekText(const QString &p_text, uint p_options) bool wrapped = false; QTextCursor retCursor; - bool found = findTextHelper(p_text, p_options, true, - textCursor().position() + 1, wrapped, retCursor); + bool found = findTextHelper(p_text, p_options, p_forward, + p_forward ? textCursor().position() + 1 + : textCursor().position(), + wrapped, retCursor); if (found) { makeBlockVisible(document()->findBlock(retCursor.selectionStart())); highlightIncrementalSearchedWord(retCursor); @@ -324,7 +326,8 @@ QList VEdit::findTextAll(const QString &p_text, uint p_options) return results; } -bool VEdit::findText(const QString &p_text, uint p_options, bool p_forward) +bool VEdit::findText(const QString &p_text, uint p_options, bool p_forward, + QTextCursor *p_cursor, QTextCursor::MoveMode p_moveMode) { clearIncrementalSearchedWordHighlight(); @@ -333,12 +336,16 @@ bool VEdit::findText(const QString &p_text, uint p_options, bool p_forward) return false; } + QTextCursor cursor = textCursor(); bool wrapped = false; QTextCursor retCursor; int matches = 0; - bool found = findTextHelper(p_text, p_options, p_forward, - p_forward ? textCursor().position() + 1 - : textCursor().position(), + int start = p_forward ? cursor.position() + 1 : cursor.position(); + if (p_cursor) { + start = p_forward ? p_cursor->position() + 1 : p_cursor->position(); + } + + bool found = findTextHelper(p_text, p_options, p_forward, start, wrapped, retCursor); if (found) { Q_ASSERT(!retCursor.isNull()); @@ -346,9 +353,12 @@ bool VEdit::findText(const QString &p_text, uint p_options, bool p_forward) showWrapLabel(); } - QTextCursor cursor = textCursor(); - cursor.setPosition(retCursor.selectionStart()); - setTextCursor(cursor); + if (p_cursor) { + p_cursor->setPosition(retCursor.selectionStart(), p_moveMode); + } else { + cursor.setPosition(retCursor.selectionStart(), p_moveMode); + setTextCursor(cursor); + } highlightSearchedWord(p_text, p_options); highlightSearchedWordUnderCursor(retCursor); diff --git a/src/vedit.h b/src/vedit.h index f10fc546..625d080a 100644 --- a/src/vedit.h +++ b/src/vedit.h @@ -83,9 +83,14 @@ public: // Used for incremental search. // User has enter the content to search, but does not enter the "find" button yet. - bool peekText(const QString &p_text, uint p_options); + bool peekText(const QString &p_text, uint p_options, bool p_forward = true); + + // If @p_cursor is not now, set the position of @p_cursor instead of current + // cursor. + bool findText(const QString &p_text, uint p_options, bool p_forward, + QTextCursor *p_cursor = NULL, + QTextCursor::MoveMode p_moveMode = QTextCursor::MoveAnchor); - bool findText(const QString &p_text, uint p_options, bool p_forward); void replaceText(const QString &p_text, uint p_options, const QString &p_replaceText, bool p_findNext); void replaceTextAll(const QString &p_text, uint p_options, diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index 077c0390..54bbc696 100644 --- a/src/vmainwindow.cpp +++ b/src/vmainwindow.cpp @@ -1773,10 +1773,10 @@ void VMainWindow::updateStatusInfo(const VEditTabInfo &p_info) void VMainWindow::handleVimStatusUpdated(const VVim *p_vim) { + m_vimIndicator->update(p_vim, m_curTab); if (!p_vim || !m_curTab || !m_curTab->isEditMode()) { m_vimIndicator->hide(); } else { - m_vimIndicator->update(p_vim); m_vimIndicator->show(); } } diff --git a/src/vmdedit.cpp b/src/vmdedit.cpp index 12be0969..cdadac35 100644 --- a/src/vmdedit.cpp +++ b/src/vmdedit.cpp @@ -354,8 +354,15 @@ void VMdEdit::generateEditOutline() void VMdEdit::scrollToHeader(const VAnchor &p_anchor) { if (p_anchor.lineNumber == -1 - || p_anchor.m_outlineIndex < 0 - || p_anchor.m_outlineIndex >= m_headers.size()) { + || p_anchor.m_outlineIndex < 0) { + // Move to the start of document if m_headers is not empty. + // Otherwise, there is no outline, so just let it be. + if (!m_headers.isEmpty()) { + moveCursor(QTextCursor::Start); + } + + return; + } else if (p_anchor.m_outlineIndex >= m_headers.size()) { return; } diff --git a/src/vvimindicator.cpp b/src/vvimindicator.cpp index 62620fa7..94ee7a6b 100644 --- a/src/vvimindicator.cpp +++ b/src/vvimindicator.cpp @@ -10,6 +10,7 @@ #include "vconfigmanager.h" #include "vbuttonwithwidget.h" +#include "vedittab.h" extern VConfigManager vconfig; @@ -21,6 +22,66 @@ VVimIndicator::VVimIndicator(QWidget *p_parent) void VVimIndicator::setupUI() { + m_cmdLineEdit = new VVimCmdLineEdit(this); + connect(m_cmdLineEdit, &VVimCmdLineEdit::commandCancelled, + this, [this](){ + if (m_vim) { + m_vim->processCommandLineCancelled(); + } + + if (m_editTab) { + m_editTab->focusTab(); + } + + // NOTICE: m_cmdLineEdit should not hide itself before setting + // focus to edit tab. + m_cmdLineEdit->hide(); + }); + + connect(m_cmdLineEdit, &VVimCmdLineEdit::commandFinished, + this, [this](VVim::CommandLineType p_type, const QString &p_cmd){ + if (m_vim) { + m_vim->processCommandLine(p_type, p_cmd); + } + + if (m_editTab) { + m_editTab->focusTab(); + } + + m_cmdLineEdit->hide(); + }); + + connect(m_cmdLineEdit, &VVimCmdLineEdit::commandChanged, + this, [this](VVim::CommandLineType p_type, const QString &p_cmd){ + if (m_vim) { + m_vim->processCommandLineChanged(p_type, p_cmd); + } + }); + + connect(m_cmdLineEdit, &VVimCmdLineEdit::requestNextCommand, + this, [this](VVim::CommandLineType p_type, const QString &p_cmd){ + if (m_vim) { + QString cmd = m_vim->getNextCommandHistory(p_type, p_cmd); + if (!cmd.isNull()) { + m_cmdLineEdit->setCommand(cmd); + } else { + m_cmdLineEdit->restoreUserLastInput(); + } + } + }); + + connect(m_cmdLineEdit, &VVimCmdLineEdit::requestPreviousCommand, + this, [this](VVim::CommandLineType p_type, const QString &p_cmd){ + if (m_vim) { + QString cmd = m_vim->getPreviousCommandHistory(p_type, p_cmd); + if (!cmd.isNull()) { + m_cmdLineEdit->setCommand(cmd); + } + } + }); + + m_cmdLineEdit->hide(); + m_modeLabel = new QLabel(this); m_regBtn = new VButtonWithWidget(QIcon(":/resources/icons/arrow_dropup.svg"), @@ -60,6 +121,8 @@ void VVimIndicator::setupUI() m_keyLabel->setMinimumWidth(metric.width('A') * 5); QHBoxLayout *mainLayout = new QHBoxLayout(this); + mainLayout->addStretch(); + mainLayout->addWidget(m_cmdLineEdit); mainLayout->addWidget(m_modeLabel); mainLayout->addWidget(m_regBtn); mainLayout->addWidget(m_markBtn); @@ -154,9 +217,29 @@ static void fillTreeItemsWithRegisters(QTreeWidget *p_tree, p_tree->resizeColumnToContents(1); } -void VVimIndicator::update(const VVim *p_vim) +void VVimIndicator::update(const VVim *p_vim, const VEditTab *p_editTab) { - m_vim = p_vim; + m_editTab = const_cast(p_editTab); + if (m_vim != p_vim) { + // Disconnect from previous Vim. + if (m_vim) { + disconnect(m_vim.data(), 0, this, 0); + } + + m_vim = const_cast(p_vim); + if (m_vim) { + // Connect signal. + connect(m_vim.data(), &VVim::commandLineTriggered, + this, &VVimIndicator::triggerCommandLine); + + m_cmdLineEdit->hide(); + } + } + + if (!m_vim) { + m_cmdLineEdit->hide(); + return; + } VimMode mode = VimMode::Normal; QChar curRegName(' '); @@ -230,3 +313,169 @@ void VVimIndicator::updateMarksTree(QWidget *p_widget) const QMap &marks = m_vim->getMarks().getMarks(); fillTreeItemsWithMarks(markTree, marks); } + +void VVimIndicator::triggerCommandLine(VVim::CommandLineType p_type) +{ + m_cmdLineEdit->reset(p_type); +} + +VVimCmdLineEdit::VVimCmdLineEdit(QWidget *p_parent) + : QLineEdit(p_parent), m_type(VVim::CommandLineType::Invalid) +{ + // When user delete all the text, cancel command input. + connect(this, &VVimCmdLineEdit::textChanged, + this, [this](const QString &p_text){ + if (p_text.isEmpty()) { + emit commandCancelled(); + } else { + emit commandChanged(m_type, p_text.right(p_text.size() - 1)); + } + }); + + connect(this, &VVimCmdLineEdit::textEdited, + this, [this](const QString &p_text){ + if (p_text.size() < 2) { + m_userLastInput.clear(); + } else { + m_userLastInput = p_text.right(p_text.size() - 1); + } + }); +} + +QString VVimCmdLineEdit::getCommand() const +{ + QString tx = text(); + if (tx.size() < 2) { + return ""; + } else { + return tx.right(tx.size() - 1); + } +} + +QString VVimCmdLineEdit::commandLineTypeLeader(VVim::CommandLineType p_type) +{ + QString leader; + switch (p_type) { + case VVim::CommandLineType::Command: + leader = ":"; + break; + + case VVim::CommandLineType::SearchForward: + leader = "/"; + break; + + case VVim::CommandLineType::SearchBackward: + leader = "?"; + break; + + case VVim::CommandLineType::Invalid: + leader.clear(); + break; + + default: + Q_ASSERT(false); + break; + } + + return leader; +} + +void VVimCmdLineEdit::reset(VVim::CommandLineType p_type) +{ + m_type = p_type; + m_userLastInput.clear(); + setCommand(""); + show(); + setFocus(); +} + +// See if @p_modifiers is Control which is different on macOs and Windows. +static bool isControlModifier(int p_modifiers) +{ +#if defined(Q_OS_MACOS) || defined(Q_OS_MAC) + return p_modifiers == Qt::MetaModifier; +#else + return p_modifiers == Qt::ControlModifier; +#endif +} + +void VVimCmdLineEdit::keyPressEvent(QKeyEvent *p_event) +{ + int key = p_event->key(); + int modifiers = p_event->modifiers(); + + if ((key == Qt::Key_Return && modifiers == Qt::NoModifier) + || (key == Qt::Key_Enter && modifiers == Qt::KeypadModifier)) { + // Enter, complete the command line input. + p_event->accept(); + emit commandFinished(m_type, getCommand()); + return; + } else if (key == Qt::Key_Escape + || (key == Qt::Key_BracketLeft && isControlModifier(modifiers))) { + // Exit command line input. + setText(commandLineTypeLeader(m_type)); + p_event->accept(); + emit commandCancelled(); + return; + } + + switch (key) { + case Qt::Key_U: + if (isControlModifier(modifiers)) { + // Ctrl+U, delete all user input. + setText(commandLineTypeLeader(m_type)); + p_event->accept(); + return; + } + + break; + + case Qt::Key_N: + if (!isControlModifier(modifiers)) { + break; + } + // Ctrl+N, request next command. + // Fall through. + case Qt::Key_Down: + { + emit requestNextCommand(m_type, getCommand()); + p_event->accept(); + return; + } + + case Qt::Key_P: + if (!isControlModifier(modifiers)) { + break; + } + // Ctrl+P, request previous command. + // Fall through. + case Qt::Key_Up: + { + emit requestPreviousCommand(m_type, getCommand()); + p_event->accept(); + return; + } + + default: + break; + } + + QLineEdit::keyPressEvent(p_event); +} + +void VVimCmdLineEdit::focusOutEvent(QFocusEvent *p_event) +{ + if (p_event->reason() != Qt::ActiveWindowFocusReason) { + emit commandCancelled(); + } +} + +void VVimCmdLineEdit::setCommand(const QString &p_cmd) +{ + setText(commandLineTypeLeader(m_type) + p_cmd); +} + +void VVimCmdLineEdit::restoreUserLastInput() +{ + setCommand(m_userLastInput); +} diff --git a/src/vvimindicator.h b/src/vvimindicator.h index 2d17316d..526132fc 100644 --- a/src/vvimindicator.h +++ b/src/vvimindicator.h @@ -2,10 +2,63 @@ #define VVIMINDICATOR_H #include +#include +#include #include "utils/vvim.h" class QLabel; class VButtonWithWidget; +class QKeyEvent; +class QFocusEvent; +class VEditTab; + +class VVimCmdLineEdit : public QLineEdit +{ + Q_OBJECT + +public: + explicit VVimCmdLineEdit(QWidget *p_parent = 0); + + void reset(VVim::CommandLineType p_type); + + // Set the command to @p_cmd with leader unchanged. + void setCommand(const QString &p_cmd); + + // Get the command. + QString getCommand() const; + + void restoreUserLastInput(); + +signals: + // User has finished the input and the command is ready to execute. + void commandFinished(VVim::CommandLineType p_type, const QString &p_cmd); + + // User cancelled the input. + void commandCancelled(); + + // User request the next command in the history. + void requestNextCommand(VVim::CommandLineType p_type, const QString &p_cmd); + + // User request the previous command in the history. + void requestPreviousCommand(VVim::CommandLineType p_type, const QString &p_cmd); + + // Emit when the input text changed. + void commandChanged(VVim::CommandLineType p_type, const QString &p_cmd); + +protected: + void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE; + + void focusOutEvent(QFocusEvent *p_event) Q_DECL_OVERRIDE; + +private: + // Return the leader of @p_type. + QString commandLineTypeLeader(VVim::CommandLineType p_type); + + VVim::CommandLineType m_type; + + // The latest command user input. + QString m_userLastInput; +}; class VVimIndicator : public QWidget { @@ -15,18 +68,24 @@ public: explicit VVimIndicator(QWidget *p_parent = 0); // Update indicator according to @p_vim. - void update(const VVim *p_vim); + void update(const VVim *p_vim, const VEditTab *p_editTab); private slots: void updateRegistersTree(QWidget *p_widget); void updateMarksTree(QWidget *p_widget); + // Vim request to trigger command line. + void triggerCommandLine(VVim::CommandLineType p_type); + private: void setupUI(); QString modeToString(VimMode p_mode) const; + // Command line input. + VVimCmdLineEdit *m_cmdLineEdit; + // Indicate the mode. QLabel *m_modeLabel; @@ -39,7 +98,8 @@ private: // Indicate the pending keys. QLabel *m_keyLabel; - const VVim *m_vim; + QPointer m_vim; + QPointer m_editTab; }; #endif // VVIMINDICATOR_H