From c53950fe7738948778242a5da207803869123a5f Mon Sep 17 00:00:00 2001 From: Le Tan Date: Tue, 11 Jul 2017 22:36:45 +0800 Subject: [PATCH] refine find/replace logics Do not select the found target. Use highlight instead. --- src/utils/veditutils.cpp | 2 +- src/utils/veditutils.h | 1 + src/utils/vvim.cpp | 12 +- src/vedit.cpp | 295 ++++++++++++++++++++++++++++----------- src/vedit.h | 41 +++++- 5 files changed, 266 insertions(+), 85 deletions(-) diff --git a/src/utils/veditutils.cpp b/src/utils/veditutils.cpp index bb9eb3a9..5df5a30a 100644 --- a/src/utils/veditutils.cpp +++ b/src/utils/veditutils.cpp @@ -339,7 +339,7 @@ void VEditUtils::scrollBlockInPage(QTextEdit *p_edit, QScrollBar *vsbar = p_edit->verticalScrollBar(); if (!vsbar || !vsbar->isVisible()) { - // No vertical scrollbar. No need to scrool. + // No vertical scrollbar. No need to scroll. return; } diff --git a/src/utils/veditutils.h b/src/utils/veditutils.h index 8dafd711..025a45c8 100644 --- a/src/utils/veditutils.h +++ b/src/utils/veditutils.h @@ -104,6 +104,7 @@ public: // 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. + // Will set the cursor to the block. static void scrollBlockInPage(QTextEdit *p_edit, int p_blockNum, int p_dest); diff --git a/src/utils/vvim.cpp b/src/utils/vvim.cpp index 1fdfa297..f1e2db45 100644 --- a/src/utils/vvim.cpp +++ b/src/utils/vvim.cpp @@ -94,7 +94,7 @@ VVim::VVim(VEdit *p_editor) initRegisters(); - connect(m_editor, &VEdit::copyAvailable, + connect(m_editor, &VEdit::selectionChangedByMouse, this, &VVim::selectionToVisualMode); } @@ -4328,9 +4328,13 @@ int VVim::blockCountOfPageStep() const void VVim::selectionToVisualMode(bool p_hasText) { - if (p_hasText && m_mode == VimMode::Normal) { - // Enter visual mode without clearing the selection. - setMode(VimMode::Visual, false); + if (p_hasText) { + if (m_mode == VimMode::Normal) { + // Enter visual mode without clearing the selection. + setMode(VimMode::Visual, false); + } + } else if (m_mode == VimMode::Visual || m_mode == VimMode::VisualLine) { + setMode(VimMode::Normal); } } diff --git a/src/vedit.cpp b/src/vedit.cpp index 3269e89d..4c66214d 100644 --- a/src/vedit.cpp +++ b/src/vedit.cpp @@ -51,6 +51,8 @@ VEdit::VEdit(VFile *p_file, QWidget *p_parent) 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()); QPixmap wrapPixmap(":/resources/icons/search_wrap.svg"); @@ -178,41 +180,29 @@ void VEdit::insertImage() bool VEdit::peekText(const QString &p_text, uint p_options) { - static int startPos = textCursor().selectionStart(); - static int lastPos = startPos; - bool found = false; - if (p_text.isEmpty()) { - // Clear previous selection - QTextCursor cursor = textCursor(); - cursor.clearSelection(); - cursor.setPosition(startPos); - setTextCursor(cursor); - } else { - QTextCursor cursor = textCursor(); - int curPos = cursor.selectionStart(); - if (curPos != lastPos) { - // Cursor has been moved. Just start at current potition. - startPos = curPos; - lastPos = curPos; - } else { - cursor.setPosition(startPos); - setTextCursor(cursor); - } + makeBlockVisible(document()->findBlock(textCursor().selectionStart())); + highlightIncrementalSearchedWord(QTextCursor()); + return false; } + bool wrapped = false; - found = findTextHelper(p_text, p_options, true, wrapped); + QTextCursor retCursor; + bool found = findTextHelper(p_text, p_options, true, + textCursor().position() + 1, wrapped, retCursor); if (found) { - lastPos = textCursor().selectionStart(); - found = true; + makeBlockVisible(document()->findBlock(retCursor.selectionStart())); + highlightIncrementalSearchedWord(retCursor); } + return found; } // Use QTextEdit::find() instead of QTextDocument::find() because the later has // bugs in searching backward. bool VEdit::findTextHelper(const QString &p_text, uint p_options, - bool p_forward, bool &p_wrapped) + bool p_forward, int p_start, + bool &p_wrapped, QTextCursor &p_cursor) { p_wrapped = false; bool found = false; @@ -224,12 +214,15 @@ bool VEdit::findTextHelper(const QString &p_text, uint p_options, findFlags |= QTextDocument::FindCaseSensitively; caseSensitive = true; } + if (p_options & FindOption::WholeWordOnly) { findFlags |= QTextDocument::FindWholeWords; } + if (!p_forward) { findFlags |= QTextDocument::FindBackward; } + // Use regular expression bool useRegExp = false; QRegExp exp; @@ -238,32 +231,53 @@ bool VEdit::findTextHelper(const QString &p_text, uint p_options, exp = QRegExp(p_text, caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); } + + // Store current state of the cursor. QTextCursor cursor = textCursor(); + if (cursor.position() != p_start) { + if (p_start < 0) { + p_start = 0; + } else if (p_start > document()->characterCount()) { + p_start = document()->characterCount(); + } + + QTextCursor startCursor = cursor; + startCursor.setPosition(p_start); + setTextCursor(startCursor); + } + while (!found) { if (useRegExp) { found = find(exp, findFlags); } else { found = find(p_text, findFlags); } + if (p_wrapped) { - if (!found) { - setTextCursor(cursor); - } break; } + if (!found) { // Wrap to the other end of the document to search again. p_wrapped = true; QTextCursor wrapCursor = textCursor(); - wrapCursor.clearSelection(); if (p_forward) { wrapCursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor); } else { wrapCursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor); } + setTextCursor(wrapCursor); } } + + if (found) { + p_cursor = textCursor(); + } + + // Restore the original cursor. + setTextCursor(cursor); + return found; } @@ -312,26 +326,44 @@ QList VEdit::findTextAll(const QString &p_text, uint p_options) bool VEdit::findText(const QString &p_text, uint p_options, bool p_forward) { - bool found = false; + clearIncrementalSearchedWordHighlight(); + if (p_text.isEmpty()) { - QTextCursor cursor = textCursor(); - cursor.clearSelection(); - setTextCursor(cursor); - } else { - bool wrapped = false; - found = findTextHelper(p_text, p_options, p_forward, wrapped); - if (found) { - if (wrapped) { - showWrapLabel(); - } - highlightSearchedWord(p_text, p_options); - } else { - // Simply clear previous highlight. - highlightSearchedWord("", p_options); - } + clearSearchedWordHighlight(); + return false; } - qDebug() << "findText" << p_text << p_options << p_forward - << (found ? "Found" : "NotFound"); + + bool wrapped = false; + QTextCursor retCursor; + int matches = 0; + bool found = findTextHelper(p_text, p_options, p_forward, + p_forward ? textCursor().position() + 1 + : textCursor().position(), + wrapped, retCursor); + if (found) { + Q_ASSERT(!retCursor.isNull()); + if (wrapped) { + showWrapLabel(); + } + + QTextCursor cursor = textCursor(); + cursor.setPosition(retCursor.selectionStart()); + setTextCursor(cursor); + + highlightSearchedWord(p_text, p_options); + highlightSearchedWordUnderCursor(retCursor); + matches = m_extraSelections[(int)SelectionId::SearchedKeyword].size(); + } else { + clearSearchedWordHighlight(); + } + + if (matches == 0) { + statusMessage(tr("Found no match")); + } else { + statusMessage(tr("Found %1 %2").arg(matches) + .arg(matches > 1 ? tr("matches") : tr("match"))); + } + return found; } @@ -339,47 +371,40 @@ void VEdit::replaceText(const QString &p_text, uint p_options, const QString &p_replaceText, bool p_findNext) { QTextCursor cursor = textCursor(); - if (cursor.hasSelection()) { - // Replace occurs only if the selected text matches @p_text with @p_options. - QTextCursor tmpCursor = cursor; - tmpCursor.setPosition(tmpCursor.selectionStart()); - tmpCursor.clearSelection(); - setTextCursor(tmpCursor); - bool wrapped = false; - bool found = findTextHelper(p_text, p_options, true, wrapped); - bool matched = false; - if (found) { - tmpCursor = textCursor(); - matched = (cursor.selectionStart() == tmpCursor.selectionStart()) - && (cursor.selectionEnd() == tmpCursor.selectionEnd()); + bool wrapped = false; + QTextCursor retCursor; + bool found = findTextHelper(p_text, p_options, true, + cursor.position(), wrapped, retCursor); + if (found) { + if (retCursor.selectionStart() == cursor.position()) { + // Matched. + retCursor.beginEditBlock(); + retCursor.insertText(p_replaceText); + retCursor.endEditBlock(); + setTextCursor(retCursor); } - if (matched) { - cursor.beginEditBlock(); - cursor.removeSelectedText(); - cursor.insertText(p_replaceText); - cursor.endEditBlock(); - setTextCursor(cursor); - } else { - setTextCursor(cursor); + + if (p_findNext) { + findText(p_text, p_options, true); } } - if (p_findNext) { - findText(p_text, p_options, true); - } } void VEdit::replaceTextAll(const QString &p_text, uint p_options, const QString &p_replaceText) { - // Replace from the start to the end and resotre the cursor. + // Replace from the start to the end and restore the cursor. QTextCursor cursor = textCursor(); int nrReplaces = 0; QTextCursor tmpCursor = cursor; tmpCursor.setPosition(0); setTextCursor(tmpCursor); + int start = tmpCursor.position(); while (true) { bool wrapped = false; - bool found = findTextHelper(p_text, p_options, true, wrapped); + QTextCursor retCursor; + bool found = findTextHelper(p_text, p_options, true, + start, wrapped, retCursor); if (!found) { break; } else { @@ -387,19 +412,24 @@ void VEdit::replaceTextAll(const QString &p_text, uint p_options, // Wrap back. break; } + nrReplaces++; - tmpCursor = textCursor(); - tmpCursor.beginEditBlock(); - tmpCursor.removeSelectedText(); - tmpCursor.insertText(p_replaceText); - tmpCursor.endEditBlock(); - setTextCursor(tmpCursor); + retCursor.beginEditBlock(); + retCursor.insertText(p_replaceText); + retCursor.endEditBlock(); + setTextCursor(retCursor); + start = retCursor.position(); } } + // Restore cursor position. cursor.clearSelection(); setTextCursor(cursor); qDebug() << "replace all" << nrReplaces << "occurences"; + + emit statusMessage(tr("Replace %1 %2").arg(nrReplaces) + .arg(nrReplaces > 1 ? tr("occurences") + : tr("occurence"))); } void VEdit::showWrapLabel() @@ -624,13 +654,84 @@ void VEdit::highlightSearchedWord(const QString &p_text, uint p_options) highlightTextAll(p_text, p_options, SelectionId::SearchedKeyword, format); } +void VEdit::highlightSearchedWordUnderCursor(const QTextCursor &p_cursor) +{ + QList &selects = m_extraSelections[(int)SelectionId::SearchedKeywordUnderCursor]; + if (!p_cursor.hasSelection()) { + if (!selects.isEmpty()) { + selects.clear(); + highlightExtraSelections(true); + } + + return; + } + + selects.clear(); + QTextEdit::ExtraSelection select; + select.format.setBackground(m_searchedWordCursorColor); + select.cursor = p_cursor; + selects.append(select); + + highlightExtraSelections(true); +} + +void VEdit::highlightIncrementalSearchedWord(const QTextCursor &p_cursor) +{ + QList &selects = m_extraSelections[(int)SelectionId::IncrementalSearchedKeyword]; + if (!vconfig.getHighlightSearchedWord() || !p_cursor.hasSelection()) { + if (!selects.isEmpty()) { + selects.clear(); + highlightExtraSelections(true); + } + + return; + } + + selects.clear(); + QTextEdit::ExtraSelection select; + select.format.setBackground(m_incrementalSearchedWordColor); + select.cursor = p_cursor; + selects.append(select); + + highlightExtraSelections(true); +} + void VEdit::clearSearchedWordHighlight() { + clearIncrementalSearchedWordHighlight(false); + clearSearchedWordUnderCursorHighlight(false); + QList &selects = m_extraSelections[(int)SelectionId::SearchedKeyword]; + if (selects.isEmpty()) { + return; + } + selects.clear(); highlightExtraSelections(true); } +void VEdit::clearSearchedWordUnderCursorHighlight(bool p_now) +{ + QList &selects = m_extraSelections[(int)SelectionId::SearchedKeywordUnderCursor]; + if (selects.isEmpty()) { + return; + } + + selects.clear(); + highlightExtraSelections(p_now); +} + +void VEdit::clearIncrementalSearchedWordHighlight(bool p_now) +{ + QList &selects = m_extraSelections[(int)SelectionId::IncrementalSearchedKeyword]; + if (selects.isEmpty()) { + return; + } + + selects.clear(); + highlightExtraSelections(p_now); +} + void VEdit::contextMenuEvent(QContextMenuEvent *p_event) { QMenu *menu = createStandardContextMenu(); @@ -746,6 +847,8 @@ void VEdit::mousePressEvent(QMouseEvent *p_event) m_mouseMoveScrolled = false; QTextEdit::mousePressEvent(p_event); + + emit selectionChangedByMouse(textCursor().hasSelection()); } void VEdit::mouseReleaseEvent(QMouseEvent *p_event) @@ -797,6 +900,8 @@ void VEdit::mouseMoveEvent(QMouseEvent *p_event) } QTextEdit::mouseMoveEvent(p_event); + + emit selectionChangedByMouse(textCursor().hasSelection()); } void VEdit::requestUpdateVimStatus() @@ -1033,3 +1138,37 @@ int LineNumberArea::calculateWidth() const return m_width; } + +void VEdit::makeBlockVisible(const QTextBlock &p_block) +{ + if (!p_block.isValid() || !p_block.isVisible()) { + return; + } + + QScrollBar *vbar = verticalScrollBar(); + if (!vbar || !vbar->isVisible()) { + // No vertical scrollbar. No need to scroll. + return; + } + + QAbstractTextDocumentLayout *layout = document()->documentLayout(); + int height = rect().height(); + QScrollBar *hbar = horizontalScrollBar(); + if (hbar && hbar->isVisible()) { + height -= hbar->height(); + } + + QRectF rect = layout->blockBoundingRect(p_block); + int y = contentOffsetY() + (int)rect.y(); + while (y < 0 && vbar->value() > vbar->minimum()) { + vbar->setValue(vbar->value() - vbar->singleStep()); + rect = layout->blockBoundingRect(p_block); + y = contentOffsetY() + (int)rect.y(); + } + + while (y + (int)rect.height() > height && vbar->value() < vbar->maximum()) { + vbar->setValue(vbar->value() + vbar->singleStep()); + rect = layout->blockBoundingRect(p_block); + y = contentOffsetY() + (int)rect.y(); + } +} diff --git a/src/vedit.h b/src/vedit.h index b74365ab..f10fc546 100644 --- a/src/vedit.h +++ b/src/vedit.h @@ -26,6 +26,8 @@ enum class SelectionId { CurrentLine = 0, SelectedWord, SearchedKeyword, + SearchedKeywordUnderCursor, + IncrementalSearchedKeyword, TrailingSapce, MaxSelection }; @@ -78,16 +80,27 @@ public: virtual void scrollToLine(int p_lineNumber); // User requests to insert an image. virtual void insertImage(); - bool findTextHelper(const QString &p_text, uint p_options, - bool p_forward, bool &p_wrapped); + + // 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 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, const QString &p_replaceText); void setReadOnly(bool p_ro); + + // Clear SearchedKeyword highlight. void clearSearchedWordHighlight(); + + // Clear SearchedKeywordUnderCursor Highlight. + void clearSearchedWordUnderCursorHighlight(bool p_now = true); + + // Clear IncrementalSearchedKeyword highlight. + void clearIncrementalSearchedWordHighlight(bool p_now = true); + VFile *getFile() const; VEditConfig &getConfig(); @@ -127,6 +140,9 @@ signals: // Emit when Vim status updated. void vimStatusUpdated(const VVim *p_vim); + // Selection changed by mouse. + void selectionChangedByMouse(bool p_hasSelection); + public slots: virtual void highlightCurrentLine(); @@ -181,6 +197,8 @@ private: QColor m_selectedWordColor; QColor m_searchedWordColor; + QColor m_searchedWordCursorColor; + QColor m_incrementalSearchedWordColor; QColor m_trailingSpaceColor; // Timer for extra selections highlight. @@ -214,6 +232,13 @@ private: void (*p_filter)(VEdit *, QList &) = NULL); void highlightSearchedWord(const QString &p_text, uint p_options); + + // Highlight @p_cursor as the searched keyword under cursor. + void highlightSearchedWordUnderCursor(const QTextCursor &p_cursor); + + // Highlight @p_cursor as the incremental searched keyword. + void highlightIncrementalSearchedWord(const QTextCursor &p_cursor); + bool wordInSearchedSelection(const QString &p_text); // Return the first visible block. @@ -221,6 +246,18 @@ private: // Return the y offset of the content. int contentOffsetY(); + + // Find @p_text in the document starting from @p_start. + // Returns true if @p_text is found and set @p_cursor to indicate + // the position. + // Will NOT change current cursor. + bool findTextHelper(const QString &p_text, uint p_options, + bool p_forward, int p_start, + bool &p_wrapped, QTextCursor &p_cursor); + + // Scroll the content to make @p_block visible. + // Will not change current cursor. + void makeBlockVisible(const QTextBlock &p_block); }; class LineNumberArea : public QWidget