diff --git a/src/utils/vvim.cpp b/src/utils/vvim.cpp index 03c10d9a..554c7928 100644 --- a/src/utils/vvim.cpp +++ b/src/utils/vvim.cpp @@ -106,7 +106,8 @@ VVim::VVim(VEditor *p_editor) m_leaderKey(Key(Qt::Key_Space)), m_replayLeaderSequence(false), m_registerPending(false), - m_insertModeAfterCommand(false) + m_insertModeAfterCommand(false), + m_positionBeforeVisualMode(0) { Q_ASSERT(m_editConfig->m_enableVimMode); @@ -114,8 +115,10 @@ VVim::VVim(VEditor *p_editor) initRegisters(); - connect(m_editor->object(), &VEditorObject::selectionChangedByMouse, - this, &VVim::selectionToVisualMode); + connect(m_editor->object(), &VEditorObject::mousePressed, + this, &VVim::handleMousePressed); + connect(m_editor->object(), &VEditorObject::mouseMoved, + this, &VVim::handleMouseMoved); } // Set @p_cursor's position specified by @p_positionInBlock. @@ -1332,26 +1335,33 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos) case Qt::Key_Escape: { // Clear selection and enter normal mode. + int position = -1; + if (checkMode(VimMode::Visual)) { + QTextCursor cursor = m_editor->textCursorW(); + if (cursor.position() > cursor.anchor()) { + position = cursor.position() - 1; + } + } + bool ret = clearSelection(); if (!ret && checkMode(VimMode::Normal)) { emit m_editor->object()->requestCloseFindReplaceDialog(); } - setMode(VimMode::Normal); + setMode(VimMode::Normal, true, position); break; } case Qt::Key_V: { if (modifiers == Qt::NoModifier) { - // Toggle Visual Mode. - clearSelection(); - VimMode mode = VimMode::Visual; - if (m_mode == VimMode::Visual) { - mode = VimMode::Normal; + if (checkMode(VimMode::Visual)) { + setMode(VimMode::Normal, true); + } else { + // Toggle Visual Mode. + setMode(VimMode::Visual); + maintainSelectionInVisualMode(); } - - setMode(mode); } else if (modifiers == Qt::ShiftModifier) { // Visual Line Mode. clearSelection(); @@ -2224,9 +2234,18 @@ VimMode VVim::getMode() const return m_mode; } -void VVim::setMode(VimMode p_mode, bool p_clearSelection) +void VVim::setMode(VimMode p_mode, bool p_clearSelection, int p_position) { if (m_mode != p_mode) { + QTextCursor cursor = m_editor->textCursorW(); + int position = p_position; + if (position == -1 + && m_mode == VimMode::Visual + && p_mode == VimMode::Normal + && cursor.position() > cursor.anchor()) { + position = cursor.position() - 1; + } + if (p_clearSelection) { clearSelection(); } @@ -2240,7 +2259,26 @@ void VVim::setMode(VimMode p_mode, bool p_clearSelection) m_mode = p_mode; resetState(); - m_editor->setCursorBlockEnabled(checkMode(VimMode::Normal)); + switch (m_mode) { + case VimMode::Insert: + m_editor->setCursorBlockModeW(CursorBlock::None); + break; + + case VimMode::Visual: + m_positionBeforeVisualMode = cursor.anchor(); + V_FALLTHROUGH; + + default: + m_editor->setCursorBlockModeW(CursorBlock::RightSide); + break; + } + + if (position != -1) { + cursor.setPosition(position); + m_editor->setTextCursorW(cursor); + } + + amendCursorPosition(); emit modeChanged(m_mode); emit vimStatusUpdated(this); @@ -2436,8 +2474,10 @@ void VVim::processMoveAction(QList &p_tokens) break; } - if (m_mode == VimMode::VisualLine) { + if (checkMode(VimMode::VisualLine)) { expandSelectionToWholeLines(cursor); + } else if (checkMode(VimMode::Visual)) { + maintainSelectionInVisualMode(&cursor); } m_editor->setTextCursorW(cursor); @@ -4848,15 +4888,58 @@ int VVim::blockCountOfPageStep() const return pageLineCount; } -void VVim::selectionToVisualMode(bool p_hasText) +void VVim::maintainSelectionInVisualMode(QTextCursor *p_cursor) { - if (p_hasText) { - if (m_mode == VimMode::Normal) { - // Enter visual mode without clearing the selection. - setMode(VimMode::Visual, false); + // We need to always select the character on current position. + QTextCursor *cursor = p_cursor; + QTextCursor tmpCursor = m_editor->textCursorW(); + if (!cursor) { + cursor = &tmpCursor; + } + + bool hasChanged = false; + int pos = cursor->position(); + int anchor = cursor->anchor(); + + if (pos > anchor) { + Q_ASSERT(pos > m_positionBeforeVisualMode); + if (anchor > m_positionBeforeVisualMode) { + // Re-select. + cursor->setPosition(m_positionBeforeVisualMode); + cursor->setPosition(pos, QTextCursor::KeepAnchor); + hasChanged = true; } - } else if (m_mode == VimMode::Visual || m_mode == VimMode::VisualLine) { - setMode(VimMode::Normal); + + m_editor->setCursorBlockModeW(CursorBlock::LeftSide); + } else if (pos == anchor) { + Q_ASSERT(anchor >= m_positionBeforeVisualMode); + // Re-select. + if (anchor == m_positionBeforeVisualMode) { + cursor->setPosition(m_positionBeforeVisualMode + 1); + cursor->setPosition(pos, QTextCursor::KeepAnchor); + hasChanged = true; + + m_editor->setCursorBlockModeW(CursorBlock::RightSide); + } else { + cursor->setPosition(m_positionBeforeVisualMode); + cursor->setPosition(pos, QTextCursor::KeepAnchor); + hasChanged = true; + + m_editor->setCursorBlockModeW(CursorBlock::LeftSide); + } + } else { + // Re-select. + if (anchor <= m_positionBeforeVisualMode) { + cursor->setPosition(m_positionBeforeVisualMode + 1); + cursor->setPosition(pos, QTextCursor::KeepAnchor); + hasChanged = true; + } + + m_editor->setCursorBlockModeW(CursorBlock::RightSide); + } + + if (hasChanged && !p_cursor) { + m_editor->setTextCursorW(*cursor); } } @@ -5828,3 +5911,62 @@ QString VVim::readRegister(int p_key, int p_modifiers) return ""; } + +void VVim::amendCursorPosition() +{ + if (checkMode(VimMode::Normal)) { + QTextCursor cursor = m_editor->textCursorW(); + if (cursor.atBlockEnd() && !cursor.atBlockStart()) { + // Normal mode and cursor at the end of a non-empty block. + cursor.movePosition(QTextCursor::PreviousCharacter); + m_editor->setTextCursorW(cursor); + qDebug() << "vvim alter the cursor position one character left"; + } + } +} + +void VVim::handleMousePressed(QMouseEvent *p_event) +{ + Q_UNUSED(p_event); + QTextCursor cursor = m_editor->textCursorW(); + if (checkMode(VimMode::Visual) || checkMode(VimMode::VisualLine)) { + setMode(VimMode::Normal); + } else if (checkMode(VimMode::Normal)) { + if (cursor.hasSelection()) { + setMode(VimMode::Visual, false); + maintainSelectionInVisualMode(); + } + } +} + +void VVim::handleMouseMoved(QMouseEvent *p_event) +{ + if (p_event->buttons() != Qt::LeftButton) { + return; + } + + QTextCursor cursor = m_editor->textCursorW(); + if (cursor.hasSelection()) { + if (checkMode(VimMode::Normal)) { + int pos = cursor.position(); + int anchor = cursor.anchor(); + QTextBlock block = cursor.document()->findBlock(anchor); + if (anchor > 0 && anchor == block.position() + block.length() - 1) { + // Move anchor left. + cursor.setPosition(anchor - 1); + cursor.setPosition(pos, QTextCursor::KeepAnchor); + m_editor->setTextCursorW(cursor); + } + + setMode(VimMode::Visual, false); + maintainSelectionInVisualMode(); + } else if (checkMode(VimMode::Visual)) { + // We need to assure we always select the character on m_positionBeforeVisualMode. + maintainSelectionInVisualMode(); + } + } else if (checkMode(VimMode::Visual)) { + // User move cursor in Visual mode. Now the cursor and anchor + // are at the same position. + maintainSelectionInVisualMode(); + } +} diff --git a/src/utils/vvim.h b/src/utils/vvim.h index 5da8b5a0..7e813f75 100644 --- a/src/utils/vvim.h +++ b/src/utils/vvim.h @@ -12,6 +12,7 @@ class VEditor; class QKeyEvent; class VEditConfig; class QKeyEvent; +class QMouseEvent; enum class VimMode { Normal = 0, @@ -163,7 +164,7 @@ public: VimMode getMode() const; // Set current mode. - void setMode(VimMode p_mode, bool p_clearSelection = true); + void setMode(VimMode p_mode, bool p_clearSelection = true, int p_position = -1); // Set current register. void setCurrentRegisterName(QChar p_reg); @@ -218,9 +219,13 @@ signals: void commandLineTriggered(VVim::CommandLineType p_type); private slots: - // When user use mouse to select texts in Normal mode, we should change to - // Visual mode. - void selectionToVisualMode(bool p_hasText); + void handleMousePressed(QMouseEvent *p_event); + + void handleMouseMoved(QMouseEvent *p_event); + + // When we display cursor as block, it makes no sense to put cursor at the + // end of line. + void amendCursorPosition(); private: // Struct for a key press. @@ -805,6 +810,12 @@ private: Register &getRegister(QChar p_regName) const; void setRegister(QChar p_regName, const QString &p_val); + // May need to do these things: + // 1. Change the CursorBlock mode; + // 2. Alter the selection to assure the character in m_positionBeforeVisualMode + // is always selected. + void maintainSelectionInVisualMode(QTextCursor *p_cursor = NULL); + VEditor *m_editor; const VEditConfig *m_editConfig; VimMode m_mode; @@ -849,6 +860,11 @@ private: // Whether enter insert mode after a command. bool m_insertModeAfterCommand; + // Cursor position when entering Visual mode. + // After displaying cursor as block, we need to always select current character + // when entering Visual mode. + int m_positionBeforeVisualMode; + static const QChar c_unnamedRegister; static const QChar c_blackHoleRegister; static const QChar c_selectionRegister; diff --git a/src/vconstants.h b/src/vconstants.h index ad37049c..ddbc30f5 100644 --- a/src/vconstants.h +++ b/src/vconstants.h @@ -118,4 +118,14 @@ enum class StartupPageType Invalid }; +// Cursor block mode. +enum class CursorBlock +{ + None = 0, + // Display a cursor block on the character on the right side of the cursor. + RightSide, + // Display a cursor block on the character on the left side of the cursor. + LeftSide +}; + #endif diff --git a/src/veditor.h b/src/veditor.h index 94830bfb..e10120dc 100644 --- a/src/veditor.h +++ b/src/veditor.h @@ -8,6 +8,7 @@ #include #include "veditconfig.h" +#include "vconstants.h" #include "vfile.h" class QWidget; @@ -17,6 +18,7 @@ class QTimer; class QLabel; class VVim; enum class VimMode; +class QMouseEvent; enum class SelectionId { @@ -144,9 +146,6 @@ public: // @p_modified: if true, delete the whole content and insert the new content. virtual void setContent(const QString &p_content, bool p_modified = false) = 0; - // Whether display cursor as block. - virtual void setCursorBlockEnabled(bool p_enabled) = 0; - // Set the cursor block's background and foreground. virtual void setCursorBlockColor(const QColor &p_bg, const QColor &p_fg) = 0; @@ -186,6 +185,9 @@ public: virtual void redoW() = 0; + // Whether display cursor as block. + virtual void setCursorBlockModeW(CursorBlock p_mode) = 0; + protected: void init(); @@ -332,9 +334,6 @@ signals: // Request VEditTab to save this file. void saveNote(); - // Selection changed by mouse. - void selectionChangedByMouse(bool p_hasSelection); - // Emit when Vim status updated. void vimStatusUpdated(const VVim *p_vim); @@ -344,6 +343,10 @@ signals: // Request the edit tab to close find and replace dialog. void requestCloseFindReplaceDialog(); + void mouseMoved(QMouseEvent *p_event); + + void mousePressed(QMouseEvent *p_event); + private slots: // Timer for find-wrap label. void labelTimerTimeout() diff --git a/src/vmdeditor.cpp b/src/vmdeditor.cpp index a2b61db5..e89aa05f 100644 --- a/src/vmdeditor.cpp +++ b/src/vmdeditor.cpp @@ -302,7 +302,7 @@ void VMdEditor::mousePressEvent(QMouseEvent *p_event) VTextEdit::mousePressEvent(p_event); - emit m_object->selectionChangedByMouse(textCursor().hasSelection()); + emit m_object->mousePressed(p_event); } void VMdEditor::mouseReleaseEvent(QMouseEvent *p_event) @@ -322,7 +322,7 @@ void VMdEditor::mouseMoveEvent(QMouseEvent *p_event) VTextEdit::mouseMoveEvent(p_event); - emit m_object->selectionChangedByMouse(textCursor().hasSelection()); + emit m_object->mouseMoved(p_event); } QVariant VMdEditor::inputMethodQuery(Qt::InputMethodQuery p_query) const diff --git a/src/vmdeditor.h b/src/vmdeditor.h index faf0b2dd..3cea8b97 100644 --- a/src/vmdeditor.h +++ b/src/vmdeditor.h @@ -59,9 +59,6 @@ public: void setContent(const QString &p_content, bool p_modified = false) Q_DECL_OVERRIDE; - // Whether display cursor as block. - void setCursorBlockEnabled(bool p_enabled) Q_DECL_OVERRIDE; - // Set the cursor block's background and foreground. void setCursorBlockColor(const QColor &p_bg, const QColor &p_fg) Q_DECL_OVERRIDE; @@ -148,6 +145,12 @@ public: redo(); } + // Whether display cursor as block. + void setCursorBlockModeW(CursorBlock p_mode) Q_DECL_OVERRIDE + { + setCursorBlockMode(p_mode); + } + signals: // Signal when headers change. void headersChanged(const QVector &p_headers); @@ -218,11 +221,6 @@ private: bool m_freshEdit; }; -inline void VMdEditor::setCursorBlockEnabled(bool p_enabled) -{ - setCursorBlockMode(p_enabled); -} - inline void VMdEditor::setCursorBlockColor(const QColor &p_bg, const QColor &p_fg) { setCursorBlockBg(p_bg); diff --git a/src/vtextdocumentlayout.cpp b/src/vtextdocumentlayout.cpp index 4c4d5fde..436c8fea 100644 --- a/src/vtextdocumentlayout.cpp +++ b/src/vtextdocumentlayout.cpp @@ -32,7 +32,7 @@ VTextDocumentLayout::VTextDocumentLayout(QTextDocument *p_doc, m_blockImageEnabled(false), m_imageWidthConstrainted(false), m_imageLineColor("#9575CD"), - m_cursorBlockMode(false), + m_cursorBlockMode(CursorBlock::None), m_virtualCursorBlockWidth(8), m_cursorBlockFg("#EEEEEE"), m_cursorBlockBg("#222222"), @@ -232,14 +232,17 @@ void VTextDocumentLayout::draw(QPainter *p_painter, const PaintContext &p_contex bool drawCursor = p_context.cursorPosition >= blpos && p_context.cursorPosition < blpos + bllen; int cursorWidth = m_cursorWidth; - if (drawCursor && m_cursorBlockMode) { - if (p_context.cursorPosition == blpos + bllen - 1) { + int cursorPosition = p_context.cursorPosition - blpos; + if (drawCursor && m_cursorBlockMode != CursorBlock::None) { + if (cursorPosition > 0 && m_cursorBlockMode == CursorBlock::LeftSide) { + --cursorPosition; + } + + if (cursorPosition == bllen - 1) { cursorWidth = m_virtualCursorBlockWidth; } else { // Get the width of the selection to update cursor width. - cursorWidth = getTextWidthWithinTextLine(layout, - p_context.cursorPosition - blpos, - 1); + cursorWidth = getTextWidthWithinTextLine(layout, cursorPosition, 1); if (cursorWidth < m_cursorWidth) { cursorWidth = m_cursorWidth; } @@ -263,16 +266,14 @@ void VTextDocumentLayout::draw(QPainter *p_painter, const PaintContext &p_contex if (drawCursor || (p_context.cursorPosition < -1 && !layout->preeditAreaText().isEmpty())) { - int cpos = p_context.cursorPosition; - if (cpos < -1) { - cpos = layout->preeditAreaPosition() - (cpos + 2); - } else { - cpos -= blpos; + if (p_context.cursorPosition < -1) { + cursorPosition = layout->preeditAreaPosition() + - (p_context.cursorPosition + 2); } layout->drawCursor(p_painter, offset, - cpos, + cursorPosition, cursorWidth); } @@ -353,12 +354,6 @@ int VTextDocumentLayout::hitTest(const QPointF &p_point, Qt::HitTestAccuracy p_a } } - if (m_cursorBlockMode - && off == block.length() - 1 - && off != 0) { - --off; - } - return block.position() + off; } diff --git a/src/vtextdocumentlayout.h b/src/vtextdocumentlayout.h index 785a64eb..0ca2ab1f 100644 --- a/src/vtextdocumentlayout.h +++ b/src/vtextdocumentlayout.h @@ -5,6 +5,7 @@ #include #include #include +#include "vconstants.h" class VImageResourceManager2; struct VPreviewedImageInfo; @@ -54,7 +55,7 @@ public: void setImageLineColor(const QColor &p_color); - void setCursorBlockMode(bool p_enabled); + void setCursorBlockMode(CursorBlock p_mode); void setCursorBlockFg(const QColor &p_color); @@ -62,6 +63,8 @@ public: void setVirtualCursorBlockWidth(int p_width); + void clearLastCursorBlockWidth(); + signals: // Emit to update current cursor block width if m_cursorBlockMode is enabled. void cursorBlockWidthUpdated(int p_width); @@ -282,7 +285,7 @@ private: QColor m_imageLineColor; // Draw cursor as block. - bool m_cursorBlockMode; + CursorBlock m_cursorBlockMode; // Virtual cursor block: cursor block on no character. int m_virtualCursorBlockWidth; @@ -313,9 +316,9 @@ inline void VTextDocumentLayout::scaleSize(QSize &p_size, int p_width, int p_hei } } -inline void VTextDocumentLayout::setCursorBlockMode(bool p_enabled) +inline void VTextDocumentLayout::setCursorBlockMode(CursorBlock p_mode) { - m_cursorBlockMode = p_enabled; + m_cursorBlockMode = p_mode; } inline void VTextDocumentLayout::setCursorBlockFg(const QColor &p_color) @@ -332,4 +335,9 @@ inline void VTextDocumentLayout::setVirtualCursorBlockWidth(int p_width) { m_virtualCursorBlockWidth = p_width; } + +inline void VTextDocumentLayout::clearLastCursorBlockWidth() +{ + m_lastCursorBlockWidth = -1; +} #endif // VTEXTDOCUMENTLAYOUT_H diff --git a/src/vtextedit.cpp b/src/vtextedit.cpp index e18684ea..d7bb8974 100644 --- a/src/vtextedit.cpp +++ b/src/vtextedit.cpp @@ -49,7 +49,7 @@ void VTextEdit::init() m_blockImageEnabled = false; - m_cursorBlockMode = false; + m_cursorBlockMode = CursorBlock::None; m_imageMgr = new VImageResourceManager2(); @@ -346,13 +346,14 @@ void VTextEdit::setImageLineColor(const QColor &p_color) getLayout()->setImageLineColor(p_color); } -void VTextEdit::setCursorBlockMode(bool p_enabled) +void VTextEdit::setCursorBlockMode(CursorBlock p_mode) { - if (p_enabled != m_cursorBlockMode) { - m_cursorBlockMode = p_enabled; + if (p_mode != m_cursorBlockMode) { + m_cursorBlockMode = p_mode; getLayout()->setCursorBlockMode(m_cursorBlockMode); - - setCursorWidth(m_cursorBlockMode ? VIRTUAL_CURSOR_BLOCK_WIDTH : 1); + getLayout()->clearLastCursorBlockWidth(); + setCursorWidth(m_cursorBlockMode != CursorBlock::None ? VIRTUAL_CURSOR_BLOCK_WIDTH + : 1); } } diff --git a/src/vtextedit.h b/src/vtextedit.h index 73d35084..6c8402a1 100644 --- a/src/vtextedit.h +++ b/src/vtextedit.h @@ -5,6 +5,7 @@ #include #include "vlinenumberarea.h" +#include "vconstants.h" class VTextDocumentLayout; class QPainter; @@ -56,7 +57,7 @@ public: void relayout(const QSet &p_blocks); - void setCursorBlockMode(bool p_enabled); + void setCursorBlockMode(CursorBlock p_mode); void setCursorBlockFg(const QColor &p_color); @@ -85,7 +86,7 @@ private: bool m_blockImageEnabled; - bool m_cursorBlockMode; + CursorBlock m_cursorBlockMode; }; inline void VTextEdit::setLineNumberType(LineNumberType p_type)