mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
vim-mode: support undo/redo and zt/zz/zb
Change SaveAndRead from Ctrl+R to Ctrl+T.
This commit is contained in:
parent
d909091f46
commit
5953954786
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QTextEdit>
|
||||||
|
#include <QScrollBar>
|
||||||
|
|
||||||
#include "vutils.h"
|
#include "vutils.h"
|
||||||
|
|
||||||
@ -264,3 +266,89 @@ int VEditUtils::selectedBlockCount(const QTextCursor &p_cursor)
|
|||||||
|
|
||||||
return ebNum - sbNum + 1;
|
return ebNum - sbNum + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VEditUtils::scrollBlockInPage(QTextEdit *p_edit,
|
||||||
|
int p_blockNum,
|
||||||
|
int p_dest)
|
||||||
|
{
|
||||||
|
QTextDocument *doc = p_edit->document();
|
||||||
|
QTextCursor cursor = p_edit->textCursor();
|
||||||
|
if (p_blockNum >= doc->blockCount()) {
|
||||||
|
p_blockNum = doc->blockCount() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextBlock block = doc->findBlockByNumber(p_blockNum);
|
||||||
|
|
||||||
|
int pib = cursor.positionInBlock();
|
||||||
|
if (cursor.block().blockNumber() != p_blockNum) {
|
||||||
|
// Move the cursor to the block.
|
||||||
|
if (pib >= block.length()) {
|
||||||
|
pib = block.length() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.setPosition(block.position() + pib);
|
||||||
|
p_edit->setTextCursor(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll to let current cursor locate in proper position.
|
||||||
|
p_edit->ensureCursorVisible();
|
||||||
|
QScrollBar *vsbar = p_edit->verticalScrollBar();
|
||||||
|
|
||||||
|
if (!vsbar || !vsbar->isVisible()) {
|
||||||
|
// No vertical scrollbar. No need to scrool.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect rect = p_edit->cursorRect();
|
||||||
|
int height = p_edit->rect().height();
|
||||||
|
QScrollBar *sbar = p_edit->horizontalScrollBar();
|
||||||
|
if (sbar && sbar->isVisible()) {
|
||||||
|
height -= sbar->height();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (p_dest) {
|
||||||
|
case 0:
|
||||||
|
{
|
||||||
|
// Top.
|
||||||
|
while (rect.y() > 0 && vsbar->value() < vsbar->maximum()) {
|
||||||
|
vsbar->setValue(vsbar->value() + vsbar->singleStep());
|
||||||
|
rect = p_edit->cursorRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
{
|
||||||
|
// Center.
|
||||||
|
height = qMax(height / 2, 1);
|
||||||
|
if (rect.y() > height) {
|
||||||
|
while (rect.y() > height && vsbar->value() < vsbar->maximum()) {
|
||||||
|
vsbar->setValue(vsbar->value() + vsbar->singleStep());
|
||||||
|
rect = p_edit->cursorRect();
|
||||||
|
}
|
||||||
|
} else if (rect.y() < height) {
|
||||||
|
while (rect.y() < height && vsbar->value() > vsbar->minimum()) {
|
||||||
|
vsbar->setValue(vsbar->value() - vsbar->singleStep());
|
||||||
|
rect = p_edit->cursorRect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
// Bottom.
|
||||||
|
while (rect.y() < height && vsbar->value() > vsbar->minimum()) {
|
||||||
|
vsbar->setValue(vsbar->value() - vsbar->singleStep());
|
||||||
|
rect = p_edit->cursorRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
p_edit->ensureCursorVisible();
|
||||||
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include <QTextCursor>
|
#include <QTextCursor>
|
||||||
|
|
||||||
class QTextDocument;
|
class QTextDocument;
|
||||||
|
class QTextEdit;
|
||||||
|
|
||||||
// Utils for text edit.
|
// Utils for text edit.
|
||||||
class VEditUtils
|
class VEditUtils
|
||||||
@ -74,6 +75,13 @@ public:
|
|||||||
// Get the count of blocks selected.
|
// Get the count of blocks selected.
|
||||||
static int selectedBlockCount(const QTextCursor &p_cursor);
|
static int selectedBlockCount(const QTextCursor &p_cursor);
|
||||||
|
|
||||||
|
// Scroll block @p_blockNum into the visual window.
|
||||||
|
// @p_dest is the position of the window: 0 for top, 1 for center, 2 for bottom.
|
||||||
|
// @p_blockNum is based on 0.
|
||||||
|
static void scrollBlockInPage(QTextEdit *p_edit,
|
||||||
|
int p_blockNum,
|
||||||
|
int p_dest);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VEditUtils() {}
|
VEditUtils() {}
|
||||||
};
|
};
|
||||||
|
@ -670,7 +670,13 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
|
|||||||
} else if (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier) {
|
} else if (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier) {
|
||||||
tryGetRepeatToken(m_keys, m_tokens);
|
tryGetRepeatToken(m_keys, m_tokens);
|
||||||
if (!m_keys.isEmpty()) {
|
if (!m_keys.isEmpty()) {
|
||||||
// Not a valid sequence.
|
if (modifiers == Qt::NoModifier && checkPendingKey(Key(Qt::Key_Z))) {
|
||||||
|
// zb, redraw to make a certain line the bottom of window.
|
||||||
|
addActionToken(Action::RedrawAtBottom);
|
||||||
|
processCommand(m_tokens);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -711,9 +717,11 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
|
|||||||
|
|
||||||
case Qt::Key_U:
|
case Qt::Key_U:
|
||||||
{
|
{
|
||||||
|
tryGetRepeatToken(m_keys, m_tokens);
|
||||||
|
bool toLower = modifiers == Qt::NoModifier;
|
||||||
|
|
||||||
if (modifiers == Qt::ControlModifier) {
|
if (modifiers == Qt::ControlModifier) {
|
||||||
// Ctrl+U, HalfPageUp.
|
// Ctrl+U, HalfPageUp.
|
||||||
tryGetRepeatToken(m_keys, m_tokens);
|
|
||||||
if (!m_keys.isEmpty()) {
|
if (!m_keys.isEmpty()) {
|
||||||
// Not a valid sequence.
|
// Not a valid sequence.
|
||||||
break;
|
break;
|
||||||
@ -724,12 +732,34 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
|
|||||||
m_tokens.append(Token(mm));
|
m_tokens.append(Token(mm));
|
||||||
processCommand(m_tokens);
|
processCommand(m_tokens);
|
||||||
resetPositionInBlock = false;
|
resetPositionInBlock = false;
|
||||||
} else if (m_keys.isEmpty() && m_tokens.isEmpty() && modifiers == Qt::NoModifier) {
|
} else if (m_keys.isEmpty() && !hasActionToken()) {
|
||||||
|
if (m_mode == VimMode::Visual || m_mode == VimMode::VisualLine) {
|
||||||
|
// u/U for tolower and toupper selected text.
|
||||||
|
QTextCursor cursor = m_editor->textCursor();
|
||||||
|
cursor.beginEditBlock();
|
||||||
|
// Different from Vim:
|
||||||
|
// If there is no selection in Visual mode, we do nothing.
|
||||||
|
if (m_mode == VimMode::VisualLine) {
|
||||||
|
int nrBlock = VEditUtils::selectedBlockCount(cursor);
|
||||||
|
message(tr("%1 %2 changed").arg(nrBlock).arg(nrBlock > 1 ? tr("lines")
|
||||||
|
: tr("line")));
|
||||||
|
}
|
||||||
|
|
||||||
|
convertCaseOfSelectedText(cursor, toLower);
|
||||||
|
cursor.endEditBlock();
|
||||||
|
m_editor->setTextCursor(cursor);
|
||||||
|
|
||||||
|
setMode(VimMode::Normal);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// u, Undo.
|
// u, Undo.
|
||||||
|
if (modifiers == Qt::NoModifier) {
|
||||||
|
addActionToken(Action::Undo);
|
||||||
|
processCommand(m_tokens);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
bool toLower = modifiers == Qt::NoModifier;
|
|
||||||
tryGetRepeatToken(m_keys, m_tokens);
|
|
||||||
if (hasActionToken()) {
|
if (hasActionToken()) {
|
||||||
// guu/gUU.
|
// guu/gUU.
|
||||||
if ((toLower && checkActionToken(Action::ToLower))
|
if ((toLower && checkActionToken(Action::ToLower))
|
||||||
@ -748,6 +778,12 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
|
|||||||
cursor.beginEditBlock();
|
cursor.beginEditBlock();
|
||||||
// Different from Vim:
|
// Different from Vim:
|
||||||
// If there is no selection in Visual mode, we do nothing.
|
// If there is no selection in Visual mode, we do nothing.
|
||||||
|
if (m_mode == VimMode::VisualLine) {
|
||||||
|
int nrBlock = VEditUtils::selectedBlockCount(cursor);
|
||||||
|
message(tr("%1 %2 changed").arg(nrBlock).arg(nrBlock > 1 ? tr("lines")
|
||||||
|
: tr("line")));
|
||||||
|
}
|
||||||
|
|
||||||
convertCaseOfSelectedText(cursor, toLower);
|
convertCaseOfSelectedText(cursor, toLower);
|
||||||
cursor.endEditBlock();
|
cursor.endEditBlock();
|
||||||
m_editor->setTextCursor(cursor);
|
m_editor->setTextCursor(cursor);
|
||||||
@ -1230,6 +1266,11 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
|
|||||||
if (m_keys.isEmpty()) {
|
if (m_keys.isEmpty()) {
|
||||||
m_keys.append(keyInfo);
|
m_keys.append(keyInfo);
|
||||||
goto accept;
|
goto accept;
|
||||||
|
} else if (modifiers == Qt::NoModifier && checkPendingKey(Key(Qt::Key_Z))) {
|
||||||
|
// zt, redraw to make a certain line the top of window.
|
||||||
|
addActionToken(Action::RedrawAtTop);
|
||||||
|
processCommand(m_tokens);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -1272,6 +1313,48 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case Qt::Key_R:
|
||||||
|
{
|
||||||
|
if (m_mode == VimMode::VisualLine) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modifiers == Qt::ControlModifier) {
|
||||||
|
// Redo.
|
||||||
|
tryGetRepeatToken(m_keys, m_tokens);
|
||||||
|
if (!m_keys.isEmpty() || hasActionToken()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
addActionToken(Action::Redo);
|
||||||
|
processCommand(m_tokens);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Qt::Key_Z:
|
||||||
|
{
|
||||||
|
if (modifiers == Qt::NoModifier) {
|
||||||
|
tryGetRepeatToken(m_keys, m_tokens);
|
||||||
|
if (m_keys.isEmpty() && !hasActionToken()) {
|
||||||
|
// First z, pend it.
|
||||||
|
m_keys.append(keyInfo);
|
||||||
|
goto accept;
|
||||||
|
} else if (checkPendingKey(keyInfo)) {
|
||||||
|
// zz, redraw to make a certain line the center of the window.
|
||||||
|
addActionToken(Action::RedrawAtCenter);
|
||||||
|
processCommand(m_tokens);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1369,6 +1452,26 @@ void VVim::processCommand(QList<Token> &p_tokens)
|
|||||||
processToLowerAction(p_tokens, false);
|
processToLowerAction(p_tokens, false);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Action::Undo:
|
||||||
|
processUndoAction(p_tokens);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Action::Redo:
|
||||||
|
processRedoAction(p_tokens);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Action::RedrawAtTop:
|
||||||
|
processRedrawLineAction(p_tokens, 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Action::RedrawAtCenter:
|
||||||
|
processRedrawLineAction(p_tokens, 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Action::RedrawAtBottom:
|
||||||
|
processRedrawLineAction(p_tokens, 2);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
p_tokens.clear();
|
p_tokens.clear();
|
||||||
break;
|
break;
|
||||||
@ -3003,6 +3106,67 @@ exit:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VVim::processUndoAction(QList<Token> &p_tokens)
|
||||||
|
{
|
||||||
|
int repeat = 1;
|
||||||
|
if (!p_tokens.isEmpty()) {
|
||||||
|
Token to = p_tokens.takeFirst();
|
||||||
|
if (!p_tokens.isEmpty() || !to.isRepeat()) {
|
||||||
|
p_tokens.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
repeat = to.m_repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextDocument *doc = m_editor->document();
|
||||||
|
int i = 0;
|
||||||
|
for (i = 0; i < repeat && doc->isUndoAvailable(); ++i) {
|
||||||
|
doc->undo();
|
||||||
|
}
|
||||||
|
|
||||||
|
message(tr("Undo %1 %2").arg(i).arg(i > 1 ? tr("changes") : tr("change")));
|
||||||
|
}
|
||||||
|
|
||||||
|
void VVim::processRedoAction(QList<Token> &p_tokens)
|
||||||
|
{
|
||||||
|
int repeat = 1;
|
||||||
|
if (!p_tokens.isEmpty()) {
|
||||||
|
Token to = p_tokens.takeFirst();
|
||||||
|
if (!p_tokens.isEmpty() || !to.isRepeat()) {
|
||||||
|
p_tokens.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
repeat = to.m_repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextDocument *doc = m_editor->document();
|
||||||
|
int i = 0;
|
||||||
|
for (i = 0; i < repeat && doc->isRedoAvailable(); ++i) {
|
||||||
|
doc->redo();
|
||||||
|
}
|
||||||
|
|
||||||
|
message(tr("Redo %1 %2").arg(i).arg(i > 1 ? tr("changes") : tr("change")));
|
||||||
|
}
|
||||||
|
|
||||||
|
void VVim::processRedrawLineAction(QList<Token> &p_tokens, int p_dest)
|
||||||
|
{
|
||||||
|
QTextCursor cursor = m_editor->textCursor();
|
||||||
|
int repeat = cursor.block().blockNumber();
|
||||||
|
if (!p_tokens.isEmpty()) {
|
||||||
|
Token to = p_tokens.takeFirst();
|
||||||
|
if (!p_tokens.isEmpty() || !to.isRepeat()) {
|
||||||
|
p_tokens.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
repeat = to.m_repeat - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
VEditUtils::scrollBlockInPage(m_editor, repeat, p_dest);
|
||||||
|
}
|
||||||
|
|
||||||
bool VVim::clearSelection()
|
bool VVim::clearSelection()
|
||||||
{
|
{
|
||||||
QTextCursor cursor = m_editor->textCursor();
|
QTextCursor cursor = m_editor->textCursor();
|
||||||
|
@ -196,6 +196,11 @@ private:
|
|||||||
UnIndent,
|
UnIndent,
|
||||||
ToUpper,
|
ToUpper,
|
||||||
ToLower,
|
ToLower,
|
||||||
|
Undo,
|
||||||
|
Redo,
|
||||||
|
RedrawAtTop,
|
||||||
|
RedrawAtCenter,
|
||||||
|
RedrawAtBottom,
|
||||||
Invalid
|
Invalid
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -379,6 +384,16 @@ private:
|
|||||||
// @p_tokens is the arguments of the Action::ToLower and Action::ToUpper action.
|
// @p_tokens is the arguments of the Action::ToLower and Action::ToUpper action.
|
||||||
void processToLowerAction(QList<Token> &p_tokens, bool p_toLower);
|
void processToLowerAction(QList<Token> &p_tokens, bool p_toLower);
|
||||||
|
|
||||||
|
// @p_tokens is the arguments of the Action::Undo action.
|
||||||
|
void processUndoAction(QList<Token> &p_tokens);
|
||||||
|
|
||||||
|
// @p_tokens is the arguments of the Action::Redo action.
|
||||||
|
void processRedoAction(QList<Token> &p_tokens);
|
||||||
|
|
||||||
|
// @p_tokens is the arguments of the Action::RedrawAtBottom/RedrawAtCenter/RedrawAtTop action.
|
||||||
|
// @p_dest: 0 for top, 1 for center, 2 for bottom.
|
||||||
|
void processRedrawLineAction(QList<Token> &p_tokens, int p_dest);
|
||||||
|
|
||||||
// Clear selection if there is any.
|
// Clear selection if there is any.
|
||||||
// Returns true if there is selection.
|
// Returns true if there is selection.
|
||||||
bool clearSelection();
|
bool clearSelection();
|
||||||
|
@ -196,7 +196,6 @@ void VMainWindow::initViewToolBar()
|
|||||||
tr("Expand"), this);
|
tr("Expand"), this);
|
||||||
expandViewAct->setStatusTip(tr("Expand the edit area"));
|
expandViewAct->setStatusTip(tr("Expand the edit area"));
|
||||||
expandViewAct->setCheckable(true);
|
expandViewAct->setCheckable(true);
|
||||||
expandViewAct->setShortcut(QKeySequence("Ctrl+T"));
|
|
||||||
expandViewAct->setMenu(panelMenu);
|
expandViewAct->setMenu(panelMenu);
|
||||||
connect(expandViewAct, &QAction::triggered,
|
connect(expandViewAct, &QAction::triggered,
|
||||||
this, &VMainWindow::expandPanelView);
|
this, &VMainWindow::expandPanelView);
|
||||||
@ -255,7 +254,7 @@ void VMainWindow::initFileToolBar()
|
|||||||
tr("Save Changes And Read"), this);
|
tr("Save Changes And Read"), this);
|
||||||
saveExitAct->setStatusTip(tr("Save changes and exit edit mode"));
|
saveExitAct->setStatusTip(tr("Save changes and exit edit mode"));
|
||||||
saveExitAct->setMenu(exitEditMenu);
|
saveExitAct->setMenu(exitEditMenu);
|
||||||
saveExitAct->setShortcut(QKeySequence("Ctrl+R"));
|
saveExitAct->setShortcut(QKeySequence("Ctrl+T"));
|
||||||
connect(saveExitAct, &QAction::triggered,
|
connect(saveExitAct, &QAction::triggered,
|
||||||
editArea, &VEditArea::saveAndReadFile);
|
editArea, &VEditArea::saveAndReadFile);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user