vim-mode: refine command line mode

1. Support `/` and `?` to search. `N` and `Shift+N` to find next/previous
occurence.
2. `Ctrl+N` and `Ctrl+Shift+N` to navigate through the command history.
3. `:nohlsearch` or `<leader><space>` to clear search highlight.
4. `#` and `*` to search current word under cursor.
This commit is contained in:
Le Tan 2017-07-12 19:32:16 +08:00
parent c53950fe77
commit cd2ac10509
16 changed files with 978 additions and 167 deletions

View File

@ -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

View File

@ -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 `<leader>y/d/p` equals to `"+y/d/p`, which will access the system's clipboard;
- `<leader><Space>` 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.

View File

@ -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`)
- 目前 `<leader>y/d/p` 等同于 `"+y/d/p`, 从而可以访问系统剪切板;
- `<leader><Space>` 清除查找高亮;
- `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的宏和重复(`.`)特性。

View File

@ -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

View File

@ -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

View File

@ -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();
}
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);
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));
if (!validCommand) {
message(tr("Not an editor command: %1").arg(p_cmd));
} else {
message(msg);
}
return validCommand;
}
bool VVim::hasNonDigitPendingKeys(const QList<Key> &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)) {
// <leader><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<Token> &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();
}

View File

@ -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<SearchItem> m_forwardItems;
int m_forwardIdx;
QList<SearchItem> 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, <nums>
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<Token> &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;

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -41,4 +41,13 @@ enum class TextDecoration { None,
Underline,
Strikethrough,
InlineCode };
enum FindOption
{
CaseSensitive = 0x1U,
WholeWordOnly = 0x2U,
RegularExpression = 0x4U,
IncrementalSearch = 0x8U
};
#endif

View File

@ -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<QTextCursor> 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());
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);

View File

@ -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,

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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<VEditTab *>(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<VVim *>(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<QChar, VVim::Mark> &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);
}

View File

@ -2,10 +2,63 @@
#define VVIMINDICATOR_H
#include <QWidget>
#include <QLineEdit>
#include <QPointer>
#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<VVim> m_vim;
QPointer<VEditTab> m_editTab;
};
#endif // VVIMINDICATOR_H