Browse Source

Editor: refine find logics

Le Tan 7 years ago
parent
commit
647807a918

+ 5 - 0
src/dialog/vfindreplacedialog.cpp

@@ -307,3 +307,8 @@ void VFindReplaceDialog::updateState(DocType p_docType, bool p_editMode)
 
     m_replaceAvailable = p_editMode;
 }
+
+QString VFindReplaceDialog::textToFind() const
+{
+    return m_findEdit->text();
+}

+ 10 - 0
src/dialog/vfindreplacedialog.h

@@ -14,11 +14,17 @@ class VFindReplaceDialog : public QWidget
     Q_OBJECT
 public:
     explicit VFindReplaceDialog(QWidget *p_parent = 0);
+
+    uint options() const;
+
     void setOption(FindOption p_opt, bool p_enabled);
+
     // Update the options enabled/disabled state according to current
     // edit tab.
     void updateState(DocType p_docType, bool p_editMode);
 
+    QString textToFind() const;
+
 signals:
     void dialogClosed();
     void findTextChanged(const QString &p_text, uint p_options);
@@ -68,4 +74,8 @@ private:
     QCheckBox *m_incrementalSearchCheck;
 };
 
+inline uint VFindReplaceDialog::options() const
+{
+    return m_options;
+}
 #endif // VFINDREPLACEDIALOG_H

+ 2 - 0
src/resources/docs/shortcuts_en.md

@@ -25,6 +25,8 @@ Open Flash Page.
 Edit current note or save changes and exit edit mode.
 - `Ctrl+G`  
 Activate Universal Entry.
+- `Ctrl+8`/`Ctrl+9`  
+Jump to the next/previous match in last find action.
 
 ### Read Mode
 - `H`/`J`/`K`/`L`  

+ 2 - 0
src/resources/docs/shortcuts_zh.md

@@ -25,6 +25,8 @@
 编辑当前笔记或保存更改并退出编辑模式。
 - `Ctrl+G`  
 激活通用入口。
+- `Ctrl+8`/`Ctrl+9`  
+跳转到最近一次查找的下一个/上一个匹配。
 
 ### 阅读模式
 - `H`/`J`/`K`/`L`  

+ 4 - 0
src/resources/vnote.ini

@@ -391,6 +391,10 @@ Find=Ctrl+F
 FindNext=F3
 ; Find previous occurence
 FindPrevious=Shift+F3
+; Jump to next match of last find
+NextMatch=Ctrl+8
+; Jump to previous match of last find
+PreviousMatch=Ctrl+9
 ; Advanced find
 AdvancedFind=Ctrl+Alt+F
 ; Recover last closed file

+ 58 - 23
src/veditarea.cpp

@@ -35,29 +35,7 @@ VEditArea::VEditArea(QWidget *parent)
 
     registerCaptainTargets();
 
-    QString keySeq = g_config->getShortcutKeySequence("ActivateNextTab");
-    qDebug() << "set ActivateNextTab shortcut to" << keySeq;
-    QShortcut *activateNextTab = new QShortcut(QKeySequence(keySeq), this);
-    activateNextTab->setContext(Qt::ApplicationShortcut);
-    connect(activateNextTab, &QShortcut::activated,
-            this, [this]() {
-                VEditWindow *win = getCurrentWindow();
-                if (win) {
-                    win->focusNextTab(true);
-                }
-            });
-
-    keySeq = g_config->getShortcutKeySequence("ActivatePreviousTab");
-    qDebug() << "set ActivatePreviousTab shortcut to" << keySeq;
-    QShortcut *activatePreviousTab = new QShortcut(QKeySequence(keySeq), this);
-    activatePreviousTab->setContext(Qt::ApplicationShortcut);
-    connect(activatePreviousTab, &QShortcut::activated,
-            this, [this]() {
-                VEditWindow *win = getCurrentWindow();
-                if (win) {
-                    win->focusNextTab(false);
-                }
-            });
+    initShortcuts();
 
     QTimer *timer = new QTimer(this);
     timer->setSingleShot(false);
@@ -130,6 +108,49 @@ void VEditArea::setupUI()
             });
 }
 
+void VEditArea::initShortcuts()
+{
+    QString keySeq = g_config->getShortcutKeySequence("ActivateNextTab");
+    qDebug() << "set ActivateNextTab shortcut to" << keySeq;
+    QShortcut *activateNextTab = new QShortcut(QKeySequence(keySeq), this);
+    activateNextTab->setContext(Qt::ApplicationShortcut);
+    connect(activateNextTab, &QShortcut::activated,
+            this, [this]() {
+                VEditWindow *win = getCurrentWindow();
+                if (win) {
+                    win->focusNextTab(true);
+                }
+            });
+
+    keySeq = g_config->getShortcutKeySequence("ActivatePreviousTab");
+    qDebug() << "set ActivatePreviousTab shortcut to" << keySeq;
+    QShortcut *activatePreviousTab = new QShortcut(QKeySequence(keySeq), this);
+    activatePreviousTab->setContext(Qt::ApplicationShortcut);
+    connect(activatePreviousTab, &QShortcut::activated,
+            this, [this]() {
+                VEditWindow *win = getCurrentWindow();
+                if (win) {
+                    win->focusNextTab(false);
+                }
+            });
+
+    keySeq = g_config->getShortcutKeySequence("NextMatch");
+    qDebug() << "set NextMatch shortcut to" << keySeq;
+    QShortcut *nextMatchSC = new QShortcut(QKeySequence(keySeq), this);
+    connect(nextMatchSC, &QShortcut::activated,
+            this, [this]() {
+                nextMatch(true);
+            });
+
+    keySeq = g_config->getShortcutKeySequence("PreviousMatch");
+    qDebug() << "set PreviousMatch shortcut to" << keySeq;
+    QShortcut *previousMatchSC = new QShortcut(QKeySequence(keySeq), this);
+    connect(previousMatchSC, &QShortcut::activated,
+            this, [this]() {
+                nextMatch(false);
+            });
+}
+
 void VEditArea::insertSplitWindow(int idx)
 {
     VEditWindow *win = new VEditWindow(this);
@@ -1277,3 +1298,17 @@ void VEditArea::distributeSplits()
 
     splitter->setSizes(sizes);
 }
+
+void VEditArea::nextMatch(bool p_forward)
+{
+    VEditTab *tab = getCurrentTab();
+    if (!tab) {
+        return;
+    }
+
+    Q_ASSERT(m_findReplace);
+
+    tab->nextMatch(m_findReplace->textToFind(),
+                   m_findReplace->options(),
+                   p_forward);
+}

+ 7 - 0
src/veditarea.h

@@ -179,9 +179,16 @@ private slots:
     // Handle the timeout signal of file timer.
     void handleFileTimerTimeout();
 
+    // Jump to next match of last find.
+    void nextMatch(bool p_forward);
+
 private:
     void setupUI();
+
+    void initShortcuts();
+
     QVector<QPair<int, int> > findTabsByFile(const VFile *p_file);
+
     int openFileInWindow(int windowIndex, VFile *p_file, OpenFileMode p_mode);
     void setCurrentTab(int windowIndex, int tabIndex, bool setFocus);
     void setCurrentWindow(int windowIndex, bool setFocus);

+ 247 - 72
src/veditor.cpp

@@ -56,6 +56,8 @@ void VEditor::init()
     const int labelSize = 64;
 
     m_document = documentW();
+    QObject::connect(m_document, &QTextDocument::contentsChanged,
+                     m_object, &VEditorObject::clearFindCache);
 
     m_selectedWordFg = QColor(g_config->getEditorSelectedWordFg());
     m_selectedWordBg = QColor(g_config->getEditorSelectedWordBg());
@@ -344,7 +346,10 @@ static QTextDocument::FindFlags findOptionsToFlags(uint p_options, bool p_forwar
     return findFlags;
 }
 
-QList<QTextCursor> VEditor::findTextAll(const QString &p_text, uint p_options)
+QList<QTextCursor> VEditor::findTextAll(const QString &p_text,
+                                        uint p_options,
+                                        int p_start,
+                                        int p_end)
 {
     QList<QTextCursor> results;
     if (p_text.isEmpty()) {
@@ -355,33 +360,35 @@ QList<QTextCursor> VEditor::findTextAll(const QString &p_text, uint p_options)
     bool caseSensitive = p_options & FindOption::CaseSensitive;
     QTextDocument::FindFlags findFlags = findOptionsToFlags(p_options, true);
 
-    // Use regular expression
-    bool useRegExp = p_options & FindOption::RegularExpression;
-    QRegExp exp;
-    if (useRegExp) {
-        useRegExp = true;
-        exp = QRegExp(p_text,
-                      caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
+    if (p_options & FindOption::RegularExpression) {
+        QRegExp exp(p_text,
+                    caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
+        results = findTextAllInRange(m_document, exp, findFlags, p_start, p_end);
+    } else {
+        results = findTextAllInRange(m_document, p_text, findFlags, p_start, p_end);
     }
 
-    int startPos = 0;
-    QTextCursor cursor;
-    while (true) {
-        if (useRegExp) {
-            cursor = m_document->find(exp, startPos, findFlags);
-        } else {
-            cursor = m_document->find(p_text, startPos, findFlags);
-        }
+    return results;
+}
 
-        if (cursor.isNull()) {
-            break;
-        } else {
-            results.append(cursor);
-            startPos = cursor.selectionEnd();
-        }
+const QList<QTextCursor> &VEditor::findTextAllCached(const QString &p_text,
+                                                     uint p_options,
+                                                     int p_start,
+                                                     int p_end)
+{
+    if (p_text.isEmpty()) {
+        m_findInfo.clear();
+        return m_findInfo.m_result;
     }
 
-    return results;
+    if (m_findInfo.isCached(p_text, p_options, p_start, p_end)) {
+        return m_findInfo.m_result;
+    }
+
+    QList<QTextCursor> result = findTextAll(p_text, p_options, p_start, p_end);
+    m_findInfo.update(p_text, p_options, p_start, p_end, result);
+
+    return m_findInfo.m_result;
 }
 
 void VEditor::highlightSelectedWord()
@@ -521,72 +528,146 @@ bool VEditor::peekText(const QString &p_text, uint p_options, bool p_forward)
     return found;
 }
 
+// @p_cursors is in ascending order.
+// If @p_forward is true, find the smallest cursor whose selection start is greater
+// than @p_pos or the first cursor if wrapped.
+// Otherwise, find the largest cursor whose selection start is smaller than @p_pos
+// or the last cursor if wrapped.
+static int selectCursor(const QList<QTextCursor> &p_cursors,
+                        int p_pos,
+                        bool p_forward,
+                        bool &p_wrapped)
+{
+    Q_ASSERT(!p_cursors.isEmpty());
+
+    p_wrapped = false;
+
+    int first = 0, last = p_cursors.size() - 1;
+    int lastMatch = -1;
+    while (first <= last) {
+        int mid = (first + last) / 2;
+        const QTextCursor &cur = p_cursors.at(mid);
+        if (p_forward) {
+            if (cur.selectionStart() < p_pos) {
+                first = mid + 1;
+            } else if (cur.selectionStart() == p_pos) {
+                // Next one is the right one.
+                if (mid < p_cursors.size() - 1) {
+                    lastMatch = mid + 1;
+                } else {
+                    lastMatch = 0;
+                    p_wrapped = true;
+                }
+                break;
+            } else {
+                // It is a match.
+                if (lastMatch == -1 || mid < lastMatch) {
+                    lastMatch = mid;
+                }
+
+                last = mid - 1;
+            }
+        } else {
+            if (cur.selectionStart() > p_pos) {
+                last = mid - 1;
+            } else if (cur.selectionStart() == p_pos) {
+                // Previous one is the right one.
+                if (mid > 0) {
+                    lastMatch = mid - 1;
+                } else {
+                    lastMatch = p_cursors.size() - 1;
+                    p_wrapped = true;
+                }
+                break;
+            } else {
+                // It is a match.
+                if (lastMatch == -1 || mid > lastMatch) {
+                    lastMatch = mid;
+                }
+
+                first = mid + 1;
+            }
+        }
+    }
+
+    if (lastMatch == -1) {
+        p_wrapped = true;
+        lastMatch = p_forward ? 0 : (p_cursors.size() - 1);
+    }
+
+    return lastMatch;
+}
+
 bool VEditor::findText(const QString &p_text,
                        uint p_options,
                        bool p_forward,
                        QTextCursor *p_cursor,
                        QTextCursor::MoveMode p_moveMode,
                        bool p_useLeftSideOfCursor)
+{
+    return findTextInRange(p_text,
+                           p_options,
+                           p_forward,
+                           p_cursor,
+                           p_moveMode,
+                           p_useLeftSideOfCursor);
+}
+
+bool VEditor::findTextInRange(const QString &p_text,
+                              uint p_options,
+                              bool p_forward,
+                              QTextCursor *p_cursor,
+                              QTextCursor::MoveMode p_moveMode,
+                              bool p_useLeftSideOfCursor,
+                              int p_start,
+                              int p_end)
 {
     clearIncrementalSearchedWordHighlight();
 
     if (p_text.isEmpty()) {
+        m_findInfo.clear();
         clearSearchedWordHighlight();
         return false;
     }
 
-    QTextCursor cursor = textCursorW();
-    bool wrapped = false;
-    QTextCursor retCursor;
-    int matches = 0;
-    int start = p_cursor ? p_cursor->position() : cursor.position();
-    if (p_useLeftSideOfCursor) {
-        --start;
-    }
-    int skipPosition = start;
+    const QList<QTextCursor> &result = findTextAllCached(p_text, p_options, p_start, p_end);
 
-    bool found = false;
-    while (true) {
-        found = findTextHelper(p_text, p_options, p_forward, start, wrapped, retCursor);
-        if (found) {
-            Q_ASSERT(!retCursor.isNull());
-            if (wrapped) {
-                showWrapLabel();
-            }
+    if (result.isEmpty()) {
+        clearSearchedWordHighlight();
 
-            if (p_forward && retCursor.selectionStart() == skipPosition) {
-                // Skip the first match.
-                skipPosition = -1;
-                start = retCursor.selectionEnd();
-                continue;
-            }
+        emit m_object->statusMessage(QObject::tr("No match found"));
+    } else {
+        // Locate to the right match and update current cursor.
+        QTextCursor cursor = textCursorW();
+        int pos = p_cursor ? p_cursor->position() : cursor.position();
+        if (p_useLeftSideOfCursor) {
+            --pos;
+        }
 
-            if (p_cursor) {
-                p_cursor->setPosition(retCursor.selectionStart(), p_moveMode);
-            } else {
-                cursor.setPosition(retCursor.selectionStart(), p_moveMode);
-                setTextCursorW(cursor);
-            }
+        bool wrapped = false;
+        int idx = selectCursor(result, pos, p_forward, wrapped);
+        const QTextCursor &tcursor = result.at(idx);
+        if (wrapped) {
+            showWrapLabel();
+        }
 
-            highlightSearchedWord(p_text, p_options);
-            highlightSearchedWordUnderCursor(retCursor);
-            matches = m_extraSelections[(int)SelectionId::SearchedKeyword].size();
+        if (p_cursor) {
+            p_cursor->setPosition(tcursor.selectionStart(), p_moveMode);
         } else {
-            clearSearchedWordHighlight();
+            cursor.setPosition(tcursor.selectionStart(), p_moveMode);
+            setTextCursorW(cursor);
         }
 
-        break;
-    }
+        highlightSearchedWord(result);
 
-    if (matches == 0) {
-        emit m_object->statusMessage(QObject::tr("Found no match"));
-    } else {
-        emit m_object->statusMessage(QObject::tr("Found %1 %2").arg(matches)
-                                                               .arg(matches > 1 ? QObject::tr("matches")
-                                                                                : QObject::tr("match")));
+        highlightSearchedWordUnderCursor(tcursor);
+
+        emit m_object->statusMessage(QObject::tr("Match found: %2 of %3")
+                                                .arg(idx + 1)
+                                                .arg(result.size()));
     }
 
-    return found;
+    return !result.isEmpty();
 }
 
 bool VEditor::findTextOne(const QString &p_text, uint p_options, bool p_forward)
@@ -626,10 +707,14 @@ bool VEditor::findTextInRange(const QString &p_text,
                               int p_start,
                               int p_end)
 {
-    Q_UNUSED(p_start);
-    Q_UNUSED(p_end);
-    // TODO
-    return findText(p_text, p_options, p_forward);
+    return findTextInRange(p_text,
+                           p_options,
+                           p_forward,
+                           nullptr,
+                           QTextCursor::MoveAnchor,
+                           false,
+                           p_start,
+                           p_end);
 }
 
 void VEditor::highlightIncrementalSearchedWord(const QTextCursor &p_cursor)
@@ -791,10 +876,10 @@ void VEditor::showWrapLabel()
     m_labelTimer->start();
 }
 
-void VEditor::highlightSearchedWord(const QString &p_text, uint p_options)
+void VEditor::highlightSearchedWord(const QList<QTextCursor> &p_matches)
 {
     QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SearchedKeyword];
-    if (!g_config->getHighlightSearchedWord() || p_text.isEmpty()) {
+    if (!g_config->getHighlightSearchedWord() || p_matches.isEmpty()) {
         if (!selects.isEmpty()) {
             selects.clear();
             highlightExtraSelections(true);
@@ -803,10 +888,20 @@ void VEditor::highlightSearchedWord(const QString &p_text, uint p_options)
         return;
     }
 
+    selects.clear();
+
     QTextCharFormat format;
     format.setForeground(m_searchedWordFg);
     format.setBackground(m_searchedWordBg);
-    highlightTextAll(p_text, p_options, SelectionId::SearchedKeyword, format);
+
+    for (int i = 0; i < p_matches.size(); ++i) {
+        QTextEdit::ExtraSelection select;
+        select.format = format;
+        select.cursor = p_matches[i];
+        selects.append(select);
+    }
+
+    highlightExtraSelections();
 }
 
 void VEditor::highlightSearchedWordUnderCursor(const QTextCursor &p_cursor)
@@ -1297,3 +1392,83 @@ void VEditor::insertCompletion(const QString &p_prefix, const QString &p_complet
 
     setTextCursorW(cursor);
 }
+
+QList<QTextCursor> VEditor::findTextAllInRange(const QTextDocument *p_doc,
+                                               const QString &p_text,
+                                               QTextDocument::FindFlags p_flags,
+                                               int p_start,
+                                               int p_end)
+{
+    QList<QTextCursor> results;
+    if (p_text.isEmpty()) {
+        return results;
+    }
+
+    int start = p_start;
+    int end = p_end == -1 ? p_doc->characterCount() + 1 : p_end;
+
+    while (start < end) {
+        QTextCursor cursor = p_doc->find(p_text, start, p_flags);
+        if (cursor.isNull()) {
+            break;
+        } else {
+            start = cursor.selectionEnd();
+            if (start <= end) {
+                results.append(cursor);
+            }
+        }
+    }
+
+    return results;
+}
+
+QList<QTextCursor> VEditor::findTextAllInRange(const QTextDocument *p_doc,
+                                               const QRegExp &p_reg,
+                                               QTextDocument::FindFlags p_flags,
+                                               int p_start,
+                                               int p_end)
+{
+    QList<QTextCursor> results;
+    if (!p_reg.isValid()) {
+        return results;
+    }
+
+    int start = p_start;
+    int end = p_end == -1 ? p_doc->characterCount() + 1 : p_end;
+
+    while (start < end) {
+        QTextCursor cursor = p_doc->find(p_reg, start, p_flags);
+        if (cursor.isNull()) {
+            break;
+        } else {
+            start = cursor.selectionEnd();
+            if (start <= end) {
+                results.append(cursor);
+            }
+        }
+    }
+
+    return results;
+}
+
+void VEditor::clearFindCache()
+{
+    m_findInfo.clearResult();
+}
+
+void VEditor::nextMatch(bool p_forward)
+{
+    if (m_findInfo.isNull()) {
+        return;
+    }
+
+    if (m_findInfo.m_useToken) {
+        // TODO
+    } else {
+        findTextInRange(m_findInfo.m_text,
+                        m_findInfo.m_options,
+                        p_forward,
+                        m_findInfo.m_start,
+                        m_findInfo.m_end);
+    }
+}

+ 139 - 2
src/veditor.h

@@ -13,6 +13,7 @@
 #include "vfile.h"
 #include "vwordcountinfo.h"
 #include "vtexteditcompleter.h"
+#include "vsearchconfig.h"
 
 class QWidget;
 class VEditorObject;
@@ -99,6 +100,9 @@ public:
                         uint p_options,
                         const QString &p_replaceText);
 
+    // Use m_findInfo to find next match.
+    void nextMatch(bool p_forward = false);
+
     // Scroll the content to make @p_block visible.
     // If the @p_block is too long to hold in one page, just let it occupy the
     // whole page.
@@ -276,6 +280,100 @@ protected:
 private:
     friend class VEditorObject;
 
+    // Info about one find-in-page.
+    struct FindInfo
+    {
+        FindInfo()
+            : m_start(0),
+              m_end(-1),
+              m_useToken(false),
+              m_options(0),
+              m_cacheValid(false)
+        {
+        }
+
+        void clear()
+        {
+            m_start = 0;
+            m_end = -1;
+
+            m_useToken = false;
+
+            m_text.clear();
+            m_options = 0;
+
+            m_token.clear();
+
+            m_cacheValid = false;
+            m_result.clear();
+        }
+
+        void clearResult()
+        {
+            m_cacheValid = false;
+            m_result.clear();
+        }
+
+        bool isCached(const QString &p_text,
+                      uint p_options,
+                      int p_start = 0,
+                      int p_end = -1) const
+        {
+            return m_cacheValid
+                   && !m_useToken
+                   && m_text == p_text
+                   && m_options == p_options
+                   && m_start == p_start
+                   && m_end == p_end;
+        }
+
+        void update(const QString &p_text,
+                    uint p_options,
+                    int p_start,
+                    int p_end,
+                    const QList<QTextCursor> &p_result)
+        {
+            m_start = p_start;
+            m_end = p_end;
+
+            m_useToken = false;
+
+            m_text = p_text;
+            m_options = p_options;
+
+            m_cacheValid = true;
+            m_result = p_result;
+
+            m_token.clear();
+        }
+
+        bool isNull() const
+        {
+            if (m_useToken) {
+                return m_token.tokenSize() == 0;
+            } else {
+                return m_text.isEmpty();
+            }
+        }
+
+        // Find in [m_start, m_end).
+        int m_start;
+        int m_end;
+
+        bool m_useToken;
+
+        // Use text and options to search.
+        QString m_text;
+        uint m_options;
+
+        // Use token to search.
+        VSearchToken m_token;
+
+        bool m_cacheValid;
+
+        QList<QTextCursor> m_result;
+    };
+
     // Filter out the trailing space right before cursor.
     void filterTrailingSpace(QList<QTextEdit::ExtraSelection> &p_selects,
                              const QList<QTextEdit::ExtraSelection> &p_src);
@@ -293,7 +391,15 @@ private:
                                            QList<QTextEdit::ExtraSelection> &) = NULL);
 
     // Find all the occurences of @p_text.
-    QList<QTextCursor> findTextAll(const QString &p_text, uint p_options);
+    QList<QTextCursor> findTextAll(const QString &p_text,
+                                   uint p_options,
+                                   int p_start = 0,
+                                   int p_end = -1);
+
+    const QList<QTextCursor> &findTextAllCached(const QString &p_text,
+                                                uint p_options,
+                                                int p_start = 0,
+                                                int p_end = -1);
 
     // Highlight @p_cursor as the incremental searched keyword.
     void highlightIncrementalSearchedWord(const QTextCursor &p_cursor);
@@ -311,7 +417,7 @@ private:
 
     void showWrapLabel();
 
-    void highlightSearchedWord(const QString &p_text, uint p_options);
+    void highlightSearchedWord(const QList<QTextCursor> &p_matches);
 
     // Highlight @p_cursor as the searched keyword under cursor.
     void highlightSearchedWordUnderCursor(const QTextCursor &p_cursor);
@@ -322,6 +428,28 @@ private:
 
     bool findTextOne(const QString &p_text, uint p_options, bool p_forward);
 
+    // @p_end, -1 indicates the end of doc.
+    static QList<QTextCursor> findTextAllInRange(const QTextDocument *p_doc,
+                                                 const QString &p_text,
+                                                 QTextDocument::FindFlags p_flags,
+                                                 int p_start = 0,
+                                                 int p_end = -1);
+
+    static QList<QTextCursor> findTextAllInRange(const QTextDocument *p_doc,
+                                                 const QRegExp &p_reg,
+                                                 QTextDocument::FindFlags p_flags,
+                                                 int p_start = 0,
+                                                 int p_end = -1);
+
+    bool findTextInRange(const QString &p_text,
+                         uint p_options,
+                         bool p_forward,
+                         QTextCursor *p_cursor = nullptr,
+                         QTextCursor::MoveMode p_moveMode = QTextCursor::MoveAnchor,
+                         bool p_useLeftSideOfCursor = false,
+                         int p_start = 0,
+                         int p_end = -1);
+
     QLabel *m_wrapLabel;
     QTimer *m_labelTimer;
 
@@ -368,6 +496,8 @@ private:
     // Temp files needed to be delete.
     QStringList m_tempFiles;
 
+    FindInfo m_findInfo;
+
 // Functions for private slots.
 private:
     void labelTimerTimeout();
@@ -378,6 +508,8 @@ private:
     void updateTrailingSpaceHighlights();
 
     void doUpdateTrailingSpaceHighlights();
+
+    void clearFindCache();
 };
 
 
@@ -446,6 +578,11 @@ private slots:
         m_editor->doUpdateTrailingSpaceHighlights();
     }
 
+    void clearFindCache()
+    {
+        m_editor->clearFindCache();
+    }
+
 private:
     friend class VEditor;
 

+ 2 - 0
src/vedittab.h

@@ -66,6 +66,8 @@ public:
     virtual void replaceTextAll(const QString &p_text, uint p_options,
                                 const QString &p_replaceText) = 0;
 
+    virtual void nextMatch(const QString &p_text, uint p_options, bool p_forward) = 0;
+
     // Return selected text.
     virtual QString getSelectedText() const = 0;
 

+ 5 - 0
src/vhtmltab.cpp

@@ -240,6 +240,11 @@ void VHtmlTab::replaceTextAll(const QString &p_text, uint p_options,
     }
 }
 
+void VHtmlTab::nextMatch(const QString &p_text, uint p_options, bool p_forward)
+{
+    findText(p_text, p_options, false, p_forward);
+}
+
 QString VHtmlTab::getSelectedText() const
 {
     QTextCursor cursor = m_editor->textCursor();

+ 2 - 0
src/vhtmltab.h

@@ -41,6 +41,8 @@ public:
     void replaceTextAll(const QString &p_text, uint p_options,
                         const QString &p_replaceText) Q_DECL_OVERRIDE;
 
+    void nextMatch(const QString &p_text, uint p_options, bool p_forward) Q_DECL_OVERRIDE;
+
     QString getSelectedText() const Q_DECL_OVERRIDE;
 
     void clearSearchedWordHighlight() Q_DECL_OVERRIDE;

+ 10 - 0
src/vmdtab.cpp

@@ -719,6 +719,16 @@ void VMdTab::replaceTextAll(const QString &p_text, uint p_options,
     }
 }
 
+void VMdTab::nextMatch(const QString &p_text, uint p_options, bool p_forward)
+{
+    if (m_isEditMode) {
+        Q_ASSERT(m_editor);
+        m_editor->nextMatch(p_forward);
+    } else {
+        findTextInWebView(p_text, p_options, false, p_forward);
+    }
+}
+
 void VMdTab::findTextInWebView(const QString &p_text, uint p_options,
                                bool /* p_peek */, bool p_forward)
 {

+ 2 - 0
src/vmdtab.h

@@ -57,6 +57,8 @@ public:
     void replaceTextAll(const QString &p_text, uint p_options,
                         const QString &p_replaceText) Q_DECL_OVERRIDE;
 
+    void nextMatch(const QString &p_text, uint p_options, bool p_forward) Q_DECL_OVERRIDE;
+
     QString getSelectedText() const Q_DECL_OVERRIDE;
 
     void clearSearchedWordHighlight() Q_DECL_OVERRIDE;