diff --git a/src/utils/veditutils.cpp b/src/utils/veditutils.cpp index 51aa2ee5..26410aa2 100644 --- a/src/utils/veditutils.cpp +++ b/src/utils/veditutils.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include "vutils.h" @@ -264,3 +266,89 @@ int VEditUtils::selectedBlockCount(const QTextCursor &p_cursor) 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(); +} diff --git a/src/utils/veditutils.h b/src/utils/veditutils.h index 2cb609c1..f963c217 100644 --- a/src/utils/veditutils.h +++ b/src/utils/veditutils.h @@ -5,6 +5,7 @@ #include class QTextDocument; +class QTextEdit; // Utils for text edit. class VEditUtils @@ -74,6 +75,13 @@ public: // Get the count of blocks selected. 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: VEditUtils() {} }; diff --git a/src/utils/vvim.cpp b/src/utils/vvim.cpp index 3a7197ca..07281224 100644 --- a/src/utils/vvim.cpp +++ b/src/utils/vvim.cpp @@ -670,7 +670,13 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) } else if (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier) { tryGetRepeatToken(m_keys, m_tokens); 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; } @@ -711,9 +717,11 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) case Qt::Key_U: { + tryGetRepeatToken(m_keys, m_tokens); + bool toLower = modifiers == Qt::NoModifier; + if (modifiers == Qt::ControlModifier) { // Ctrl+U, HalfPageUp. - tryGetRepeatToken(m_keys, m_tokens); if (!m_keys.isEmpty()) { // Not a valid sequence. break; @@ -724,12 +732,34 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) m_tokens.append(Token(mm)); processCommand(m_tokens); 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. + if (modifiers == Qt::NoModifier) { + addActionToken(Action::Undo); + processCommand(m_tokens); + } break; } else { - bool toLower = modifiers == Qt::NoModifier; - tryGetRepeatToken(m_keys, m_tokens); if (hasActionToken()) { // guu/gUU. if ((toLower && checkActionToken(Action::ToLower)) @@ -748,6 +778,12 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) 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); @@ -1230,6 +1266,11 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) if (m_keys.isEmpty()) { m_keys.append(keyInfo); 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; @@ -1272,6 +1313,48 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) 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: break; } @@ -1369,6 +1452,26 @@ void VVim::processCommand(QList &p_tokens) processToLowerAction(p_tokens, false); 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: p_tokens.clear(); break; @@ -3003,6 +3106,67 @@ exit: } } +void VVim::processUndoAction(QList &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 &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 &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() { QTextCursor cursor = m_editor->textCursor(); diff --git a/src/utils/vvim.h b/src/utils/vvim.h index 0ea24415..7ac0ffec 100644 --- a/src/utils/vvim.h +++ b/src/utils/vvim.h @@ -196,6 +196,11 @@ private: UnIndent, ToUpper, ToLower, + Undo, + Redo, + RedrawAtTop, + RedrawAtCenter, + RedrawAtBottom, Invalid }; @@ -379,6 +384,16 @@ private: // @p_tokens is the arguments of the Action::ToLower and Action::ToUpper action. void processToLowerAction(QList &p_tokens, bool p_toLower); + // @p_tokens is the arguments of the Action::Undo action. + void processUndoAction(QList &p_tokens); + + // @p_tokens is the arguments of the Action::Redo action. + void processRedoAction(QList &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 &p_tokens, int p_dest); + // Clear selection if there is any. // Returns true if there is selection. bool clearSelection(); diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index 052b9739..b8f36eef 100644 --- a/src/vmainwindow.cpp +++ b/src/vmainwindow.cpp @@ -196,7 +196,6 @@ void VMainWindow::initViewToolBar() tr("Expand"), this); expandViewAct->setStatusTip(tr("Expand the edit area")); expandViewAct->setCheckable(true); - expandViewAct->setShortcut(QKeySequence("Ctrl+T")); expandViewAct->setMenu(panelMenu); connect(expandViewAct, &QAction::triggered, this, &VMainWindow::expandPanelView); @@ -255,7 +254,7 @@ void VMainWindow::initFileToolBar() tr("Save Changes And Read"), this); saveExitAct->setStatusTip(tr("Save changes and exit edit mode")); saveExitAct->setMenu(exitEditMenu); - saveExitAct->setShortcut(QKeySequence("Ctrl+R")); + saveExitAct->setShortcut(QKeySequence("Ctrl+T")); connect(saveExitAct, &QAction::triggered, editArea, &VEditArea::saveAndReadFile);