mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
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:
parent
c53950fe77
commit
cd2ac10509
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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的宏和重复(`.`)特性。
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
139
src/utils/vvim.h
139
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<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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -41,4 +41,13 @@ enum class TextDecoration { None,
|
||||
Underline,
|
||||
Strikethrough,
|
||||
InlineCode };
|
||||
|
||||
enum FindOption
|
||||
{
|
||||
CaseSensitive = 0x1U,
|
||||
WholeWordOnly = 0x2U,
|
||||
RegularExpression = 0x4U,
|
||||
IncrementalSearch = 0x8U
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user