Browse Source

Editor: highlight matches of full-text search result in page

Le Tan 7 years ago
parent
commit
a055d6e935

+ 61 - 0
src/dialog/vsettingsdialog.cpp

@@ -62,6 +62,7 @@ VSettingsDialog::VSettingsDialog(QWidget *p_parent)
     addTab(new VReadEditTab(), tr("Read/Edit"));
     addTab(new VNoteManagementTab(), tr("Note Management"));
     addTab(new VMarkdownTab(), tr("Markdown"));
+    addTab(new VMiscTab(), tr("Misc"));
 
     m_tabList->setMaximumWidth(m_tabList->sizeHintForColumn(0) + 5);
 
@@ -193,6 +194,15 @@ void VSettingsDialog::loadConfiguration()
         }
     }
 
+    // Misc Tab.
+    {
+        VMiscTab *miscTab = dynamic_cast<VMiscTab *>(m_tabs->widget(idx++));
+        Q_ASSERT(miscTab);
+        if (!miscTab->loadConfiguration()) {
+            goto err;
+        }
+    }
+
     return;
 err:
     VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
@@ -249,6 +259,15 @@ void VSettingsDialog::saveConfiguration()
         }
     }
 
+    // Misc Tab.
+    {
+        VMiscTab *miscTab = dynamic_cast<VMiscTab *>(m_tabs->widget(idx++));
+        Q_ASSERT(miscTab);
+        if (!miscTab->saveConfiguration()) {
+            goto err;
+        }
+    }
+
     accept();
     return;
 err:
@@ -1354,3 +1373,45 @@ bool VMarkdownTab::saveGraphviz()
     g_config->setGraphvizDot(m_graphvizDotEdit->text());
     return true;
 }
+
+VMiscTab::VMiscTab(QWidget *p_parent)
+    : QWidget(p_parent)
+{
+    m_matchesInPageCB = new QCheckBox(tr("Highlight matches of a full-text search in page"),
+                                      this);
+
+    QFormLayout *mainLayout = new QFormLayout();
+    mainLayout->addRow(m_matchesInPageCB);
+
+    setLayout(mainLayout);
+}
+
+bool VMiscTab::loadConfiguration()
+{
+    if (!loadMatchesInPage()) {
+        return false;
+    }
+
+    return true;
+}
+
+bool VMiscTab::saveConfiguration()
+{
+    if (!saveMatchesInPage()) {
+        return false;
+    }
+
+    return true;
+}
+
+bool VMiscTab::loadMatchesInPage()
+{
+    m_matchesInPageCB->setChecked(g_config->getHighlightMatchesInPage());
+    return true;
+}
+
+bool VMiscTab::saveMatchesInPage()
+{
+    g_config->setHighlightMatchesInPage(m_matchesInPageCB->isChecked());
+    return true;
+}

+ 16 - 0
src/dialog/vsettingsdialog.h

@@ -227,6 +227,22 @@ private:
     VLineEdit *m_graphvizDotEdit;
 };
 
+class VMiscTab : public QWidget
+{
+    Q_OBJECT
+public:
+    explicit VMiscTab(QWidget *p_parent = 0);
+    bool loadConfiguration();
+    bool saveConfiguration();
+
+private:
+    bool loadMatchesInPage();
+    bool saveMatchesInPage();
+
+    // Highlight matches in page.
+    QCheckBox *m_matchesInPageCB;
+};
+
 class VSettingsDialog : public QDialog
 {
     Q_OBJECT

+ 3 - 0
src/resources/vnote.ini

@@ -262,6 +262,9 @@ multiple_keyboard_layout=true
 ; Whether insert new note in front
 insert_new_note_in_front=false
 
+; Whether highlight matches in page when activating a search result item
+highlight_matches_in_page=true
+
 [editor]
 ; Auto indent as previous line
 auto_indent=true

+ 10 - 1
src/utils/vutils.cpp

@@ -1526,12 +1526,21 @@ const QTreeWidgetItem *VUtils::topLevelTreeItem(const QTreeWidgetItem *p_item)
     }
 
     if (p_item->parent()) {
-        return p_item->parent();
+        return topLevelTreeItem(p_item->parent());
     } else {
         return p_item;
     }
 }
 
+int VUtils::childIndexOfTreeItem(const QTreeWidgetItem *p_item)
+{
+    if (p_item->parent()) {
+        return p_item->parent()->indexOfChild(const_cast<QTreeWidgetItem *>(p_item));
+    } else {
+        return 0;
+    }
+}
+
 QImage VUtils::imageFromFile(const QString &p_filePath)
 {
     QImage img;

+ 2 - 0
src/utils/vutils.h

@@ -333,6 +333,8 @@ public:
 
     static const QTreeWidgetItem *topLevelTreeItem(const QTreeWidgetItem *p_item);
 
+    static int childIndexOfTreeItem(const QTreeWidgetItem *p_item);
+
     // Read QImage from local file @p_filePath.
     // Directly calling QImage(p_filePath) will judge the image format from the suffix,
     // resulting a null image in wrong suffix case.

+ 3 - 0
src/vconfigmanager.cpp

@@ -320,6 +320,9 @@ void VConfigManager::initialize()
     m_insertNewNoteInFront = getConfigFromSettings("global",
                                                    "insert_new_note_in_front").toBool();
 
+    m_highlightMatchesInPage = getConfigFromSettings("global",
+                                                     "highlight_matches_in_page").toBool();
+
     initEditorConfigs();
 }
 

+ 21 - 0
src/vconfigmanager.h

@@ -484,6 +484,9 @@ public:
     const QString &getQuickAccess() const;
     void setQuickAccess(const QString &p_path);
 
+    bool getHighlightMatchesInPage() const;
+    void setHighlightMatchesInPage(bool p_enabled);
+
     // All the themes.
     QList<QString> getThemes() const;
 
@@ -923,6 +926,9 @@ private:
     // Absolute path of quick access note.
     QString m_quickAccess;
 
+    // Whether highlight matches in page when activating a search item.
+    bool m_highlightMatchesInPage;
+
     // The theme name.
     QString m_theme;
 
@@ -2613,4 +2619,19 @@ inline void VConfigManager::setQuickAccess(const QString &p_path)
     m_quickAccess = p_path;
     setConfigToSettings("global", "quick_access", m_quickAccess);
 }
+
+inline bool VConfigManager::getHighlightMatchesInPage() const
+{
+    return m_highlightMatchesInPage;
+}
+
+inline void VConfigManager::setHighlightMatchesInPage(bool p_enabled)
+{
+    if (m_highlightMatchesInPage == p_enabled) {
+        return;
+    }
+
+    m_highlightMatchesInPage = p_enabled;
+    setConfigToSettings("global", "highlight_matches_in_page", m_highlightMatchesInPage);
+}
 #endif // VCONFIGMANAGER_H

+ 125 - 1
src/veditor.cpp

@@ -371,6 +371,69 @@ QList<QTextCursor> VEditor::findTextAll(const QString &p_text,
     return results;
 }
 
+static void mergeResult(QList<QTextCursor> &p_result, const QList<QTextCursor> &p_part)
+{
+    if (p_result.isEmpty()) {
+        p_result = p_part;
+        return;
+    }
+
+    int idx = 0;
+    for (auto const & cur : p_part) {
+        // Find position to insert into @p_result.
+        while (idx < p_result.size()) {
+            const QTextCursor &toCur = p_result[idx];
+            if (cur.selectionEnd() <= toCur.selectionStart()) {
+                // Insert it before toCur.
+                p_result.insert(idx, cur);
+                ++idx;
+                break;
+            } else if (cur.selectionStart() < toCur.selectionEnd()) {
+                // Interleave. Abandon it.
+                break;
+            } else {
+                ++idx;
+            }
+        }
+
+        // Insert to the end.
+        if (idx >= p_result.size()) {
+            p_result.append(cur);
+            idx = p_result.size();
+        }
+    }
+}
+
+QList<QTextCursor> VEditor::findTextAll(const VSearchToken &p_token,
+                                        int p_start,
+                                        int p_end)
+{
+    QList<QTextCursor> results;
+    if (p_token.isEmpty()) {
+        return results;
+    }
+
+    QTextDocument::FindFlags flags;
+    if (p_token.m_caseSensitivity == Qt::CaseSensitive) {
+        flags |= QTextDocument::FindCaseSensitively;
+    }
+
+    if (p_token.m_type == VSearchToken::RawString) {
+        for (auto const & wd : p_token.m_keywords) {
+            QList<QTextCursor> part = findTextAllInRange(m_document, wd, flags, p_start, p_end);
+            mergeResult(results, part);
+        }
+    } else {
+        // Regular expression.
+        for (auto const & reg : p_token.m_regs) {
+            QList<QTextCursor> part = findTextAllInRange(m_document, reg, flags, p_start, p_end);
+            mergeResult(results, part);
+        }
+    }
+
+    return results;
+}
+
 const QList<QTextCursor> &VEditor::findTextAllCached(const QString &p_text,
                                                      uint p_options,
                                                      int p_start,
@@ -391,6 +454,25 @@ const QList<QTextCursor> &VEditor::findTextAllCached(const QString &p_text,
     return m_findInfo.m_result;
 }
 
+const QList<QTextCursor> &VEditor::findTextAllCached(const VSearchToken &p_token,
+                                                     int p_start,
+                                                     int p_end)
+{
+    if (p_token.isEmpty()) {
+        m_findInfo.clear();
+        return m_findInfo.m_result;
+    }
+
+    if (m_findInfo.isCached(p_token, p_start, p_end)) {
+        return m_findInfo.m_result;
+    }
+
+    QList<QTextCursor> result = findTextAll(p_token, p_start, p_end);
+    m_findInfo.update(p_token, p_start, p_end, result);
+
+    return m_findInfo.m_result;
+}
+
 void VEditor::highlightSelectedWord()
 {
     QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SelectedWord];
@@ -613,6 +695,48 @@ bool VEditor::findText(const QString &p_text,
                            p_useLeftSideOfCursor);
 }
 
+bool VEditor::findText(const VSearchToken &p_token, bool p_forward, bool p_fromStart)
+{
+    clearIncrementalSearchedWordHighlight();
+
+    if (p_token.isEmpty()) {
+        m_findInfo.clear();
+        clearSearchedWordHighlight();
+        return false;
+    }
+
+    const QList<QTextCursor> &result = findTextAllCached(p_token);
+
+    if (result.isEmpty()) {
+        clearSearchedWordHighlight();
+
+        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_fromStart ? (m_document->characterCount() + 1) : cursor.position();
+        bool wrapped = false;
+        int idx = selectCursor(result, pos, p_forward, wrapped);
+        const QTextCursor &tcursor = result.at(idx);
+        if (wrapped && !p_fromStart) {
+            showWrapLabel();
+        }
+
+        cursor.setPosition(tcursor.selectionStart(), QTextCursor::MoveAnchor);
+        setTextCursorW(cursor);
+
+        highlightSearchedWord(result);
+
+        highlightSearchedWordUnderCursor(tcursor);
+
+        emit m_object->statusMessage(QObject::tr("Match found: %2 of %3")
+                                                .arg(idx + 1)
+                                                .arg(result.size()));
+    }
+
+    return !result.isEmpty();
+}
+
 bool VEditor::findTextInRange(const QString &p_text,
                               uint p_options,
                               bool p_forward,
@@ -1463,7 +1587,7 @@ void VEditor::nextMatch(bool p_forward)
     }
 
     if (m_findInfo.m_useToken) {
-        // TODO
+        findText(m_findInfo.m_token, p_forward, false);
     } else {
         findTextInRange(m_findInfo.m_text,
                         m_findInfo.m_options,

+ 41 - 1
src/veditor.h

@@ -84,6 +84,8 @@ public:
                   QTextCursor::MoveMode p_moveMode = QTextCursor::MoveAnchor,
                   bool p_useLeftSideOfCursor = false);
 
+    bool findText(const VSearchToken &p_token, bool p_forward, bool p_fromStart);
+
     // Constrain the scope.
     bool findTextInRange(const QString &p_text,
                          uint p_options,
@@ -327,6 +329,17 @@ private:
                    && m_end == p_end;
         }
 
+        bool isCached(const VSearchToken &p_token,
+                      int p_start = 0,
+                      int p_end = -1) const
+        {
+            return m_cacheValid
+                   && m_useToken
+                   && m_token == p_token
+                   && m_start == p_start
+                   && m_end == p_end;
+        }
+
         void update(const QString &p_text,
                     uint p_options,
                     int p_start,
@@ -347,10 +360,29 @@ private:
             m_token.clear();
         }
 
+        void update(const VSearchToken &p_token,
+                    int p_start,
+                    int p_end,
+                    const QList<QTextCursor> &p_result)
+        {
+            m_start = p_start;
+            m_end = p_end;
+
+            m_useToken = true;
+
+            m_token = p_token;
+
+            m_cacheValid = true;
+            m_result = p_result;
+
+            m_text.clear();
+            m_options = 0;
+        }
+
         bool isNull() const
         {
             if (m_useToken) {
-                return m_token.tokenSize() == 0;
+                return m_token.isEmpty();
             } else {
                 return m_text.isEmpty();
             }
@@ -396,11 +428,19 @@ private:
                                    int p_start = 0,
                                    int p_end = -1);
 
+    QList<QTextCursor> findTextAll(const VSearchToken &p_token,
+                                   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);
 
+    const QList<QTextCursor> &findTextAllCached(const VSearchToken &p_token,
+                                                int p_start = 0,
+                                                int p_end = -1);
+
     // Highlight @p_cursor as the incremental searched keyword.
     void highlightIncrementalSearchedWord(const QTextCursor &p_cursor);
 

+ 5 - 0
src/vedittab.h

@@ -9,6 +9,7 @@
 #include "utils/vvim.h"
 #include "vedittabinfo.h"
 #include "vwordcountinfo.h"
+#include "vsearchconfig.h"
 
 class VEditArea;
 class VSnippet;
@@ -59,6 +60,10 @@ public:
     virtual void findText(const QString &p_text, uint p_options, bool p_peek,
                           bool p_forward = true) = 0;
 
+    virtual void findText(const VSearchToken &p_token,
+                          bool p_forward = true,
+                          bool p_fromStart = false) = 0;
+
     // Replace @p_text with @p_replaceText in current note.
     virtual void replaceText(const QString &p_text, uint p_options,
                              const QString &p_replaceText, bool p_findNext) = 0;

+ 11 - 0
src/vhtmltab.cpp

@@ -224,6 +224,17 @@ void VHtmlTab::findText(const QString &p_text, uint p_options, bool p_peek,
     }
 }
 
+void VHtmlTab::findText(const VSearchToken &p_token,
+                        bool p_forward,
+                        bool p_fromStart)
+{
+    // TODO
+    Q_UNUSED(p_token);
+    Q_UNUSED(p_forward);
+    Q_UNUSED(p_fromStart);
+    return;
+}
+
 void VHtmlTab::replaceText(const QString &p_text, uint p_options,
                            const QString &p_replaceText, bool p_findNext)
 {

+ 4 - 0
src/vhtmltab.h

@@ -34,6 +34,10 @@ public:
     void findText(const QString &p_text, uint p_options, bool p_peek,
                   bool p_forward = true) Q_DECL_OVERRIDE;
 
+    void findText(const VSearchToken &p_token,
+                  bool p_forward = true,
+                  bool p_fromStart = false) Q_DECL_OVERRIDE;
+
     // Replace @p_text with @p_replaceText in current note.
     void replaceText(const QString &p_text, uint p_options,
                      const QString &p_replaceText, bool p_findNext) Q_DECL_OVERRIDE;

+ 12 - 0
src/vmdtab.cpp

@@ -701,6 +701,18 @@ void VMdTab::findText(const QString &p_text, uint p_options, bool p_peek,
     }
 }
 
+void VMdTab::findText(const VSearchToken &p_token,
+                      bool p_forward,
+                      bool p_fromStart)
+{
+    if (m_isEditMode) {
+        m_editor->findText(p_token, p_forward, p_fromStart);
+    } else {
+        // TODO
+        Q_ASSERT(false);
+    }
+}
+
 void VMdTab::replaceText(const QString &p_text, uint p_options,
                          const QString &p_replaceText, bool p_findNext)
 {

+ 4 - 0
src/vmdtab.h

@@ -50,6 +50,10 @@ public:
     void findText(const QString &p_text, uint p_options, bool p_peek,
                   bool p_forward = true) Q_DECL_OVERRIDE;
 
+    void findText(const VSearchToken &p_token,
+                  bool p_forward = true,
+                  bool p_fromStart = false) Q_DECL_OVERRIDE;
+
     // Replace @p_text with @p_replaceText in current note.
     void replaceText(const QString &p_text, uint p_options,
                      const QString &p_replaceText, bool p_findNext) Q_DECL_OVERRIDE;

+ 4 - 2
src/vsearch.cpp

@@ -487,7 +487,8 @@ VSearchResultItem *VSearch::searchForOutline(const VFile *p_file) const
             item = new VSearchResultItem(VSearchResultItem::Note,
                                          VSearchResultItem::OutlineIndex,
                                          p_file->getName(),
-                                         p_file->fetchPath());
+                                         p_file->fetchPath(),
+                                         m_config);
         }
 
         VSearchResultSubItem sitem(it.m_index, it.m_name);
@@ -600,7 +601,8 @@ VSearchResultItem *VSearch::searchForContent(const VFile *p_file) const
                     item = new VSearchResultItem(VSearchResultItem::Note,
                                                  VSearchResultItem::LineNumber,
                                                  p_file->getName(),
-                                                 p_file->fetchPath());
+                                                 p_file->fetchPath(),
+                                                 m_config);
                 }
 
                 VSearchResultSubItem sitem(lineNum, lineText);

+ 27 - 2
src/vsearchconfig.h

@@ -165,6 +165,26 @@ struct VSearchToken
         return m_type == Type::RawString ? m_keywords.size() : m_regs.size();
     }
 
+    bool isEmpty() const
+    {
+        return tokenSize() == 0;
+    }
+
+    bool operator==(const VSearchToken &p_other) const
+    {
+        if (m_type != p_other.m_type
+            || m_op != p_other.m_op
+            || m_caseSensitivity != p_other.m_caseSensitivity
+            || m_keywords.size() != p_other.m_keywords.size()
+            || m_numOfMatches != p_other.m_numOfMatches
+            || m_regs.size() != p_other.m_regs.size()) {
+            return false;
+        }
+
+        return m_keywords == p_other.m_keywords
+               && m_regs == p_other.m_regs;
+    }
+
     VSearchToken::Type m_type;
 
     VSearchToken::Operator m_op;
@@ -484,11 +504,13 @@ struct VSearchResultItem
     VSearchResultItem(VSearchResultItem::ItemType p_type,
                       VSearchResultItem::MatchType p_matchType,
                       const QString &p_text,
-                      const QString &p_path)
+                      const QString &p_path,
+                      const QSharedPointer<VSearchConfig> &p_config = nullptr)
         : m_type(p_type),
           m_matchType(p_matchType),
           m_text(p_text),
-          m_path(p_path)
+          m_path(p_path),
+          m_config(p_config)
     {
     }
 
@@ -518,6 +540,9 @@ struct VSearchResultItem
 
     // Matched places within this item.
     QList<VSearchResultSubItem> m_matches;
+
+    // Search config to search for this item.
+    QSharedPointer<VSearchConfig> m_config;
 };
 
 

+ 7 - 3
src/vsearchengine.cpp

@@ -14,10 +14,12 @@ VSearchEngineWorker::VSearchEngineWorker(QObject *p_parent)
 }
 
 void VSearchEngineWorker::setData(const QStringList &p_files,
-                                  const VSearchToken &p_token)
+                                  const VSearchToken &p_token,
+                                  const QSharedPointer<VSearchConfig> &p_config)
 {
     m_files = p_files;
     m_token = p_token;
+    m_config = p_config;
 }
 
 void VSearchEngineWorker::stop()
@@ -104,7 +106,8 @@ VSearchResultItem *VSearchEngineWorker::searchFile(const QString &p_fileName)
                 item = new VSearchResultItem(VSearchResultItem::Note,
                                              VSearchResultItem::LineNumber,
                                              VUtils::fileNameFromPath(p_fileName),
-                                             p_fileName);
+                                             p_fileName,
+                                             m_config);
             }
 
             VSearchResultSubItem sitem(lineNum, line);
@@ -188,7 +191,8 @@ void VSearchEngine::search(const QSharedPointer<VSearchConfig> &p_config,
 
         VSearchEngineWorker *th = new VSearchEngineWorker(this);
         th->setData(m_result->m_secondPhaseItems.mid(start, len),
-                    p_config->m_contentToken);
+                    p_config->m_contentToken,
+                    p_config);
         connect(th, &VSearchEngineWorker::finished,
                 this, &VSearchEngine::handleWorkerFinished);
         connect(th, &VSearchEngineWorker::resultItemsReady,

+ 4 - 1
src/vsearchengine.h

@@ -22,7 +22,8 @@ public:
     explicit VSearchEngineWorker(QObject *p_parent = nullptr);
 
     void setData(const QStringList &p_files,
-                 const VSearchToken &p_token);
+                 const VSearchToken &p_token,
+                 const QSharedPointer<VSearchConfig> &p_config);
 
 public slots:
     void stop();
@@ -46,6 +47,8 @@ private:
 
     VSearchToken m_token;
 
+    QSharedPointer<VSearchConfig> m_config;
+
     VSearchState m_state;
 
     QString m_error;

+ 2 - 36
src/vsearchresulttree.cpp

@@ -12,6 +12,7 @@
 #include "vhistorylist.h"
 #include "vexplorer.h"
 #include "vuniversalentry.h"
+#include "vsearchue.h"
 
 extern VNote *g_vnote;
 
@@ -268,40 +269,5 @@ void VSearchResultTree::activateItem(const QTreeWidgetItem *p_item) const
         return;
     }
 
-    const QSharedPointer<VSearchResultItem> &resItem = itemResultData(p_item);
-    switch (resItem->m_type) {
-    case VSearchResultItem::Note:
-    {
-        QStringList files(resItem->m_path);
-        g_mainWin->openFiles(files);
-        break;
-    }
-
-    case VSearchResultItem::Folder:
-    {
-        VDirectory *dir = g_vnote->getInternalDirectory(resItem->m_path);
-        if (dir) {
-            g_mainWin->locateDirectory(dir);
-        } else {
-            // External directory.
-            g_mainWin->showExplorerPanel(true);
-            g_mainWin->getExplorer()->setRootDirectory(resItem->m_path);
-        }
-
-        break;
-    }
-
-    case VSearchResultItem::Notebook:
-    {
-        VNotebook *nb = g_vnote->getNotebook(resItem->m_path);
-        if (nb) {
-            g_mainWin->locateNotebook(nb);
-        }
-
-        break;
-    }
-
-    default:
-        break;
-    }
+    VSearchUE::activateItem(itemResultData(p_item), VUtils::childIndexOfTreeItem(p_item));
 }

+ 44 - 3
src/vsearchue.cpp

@@ -18,11 +18,14 @@
 #include "veditarea.h"
 #include "vexplorer.h"
 #include "vuniversalentry.h"
+#include "vconfigmanager.h"
 
 extern VNote *g_vnote;
 
 extern VMainWindow *g_mainWin;
 
+extern VConfigManager *g_config;
+
 #define ITEM_NUM_TO_UPDATE_WIDGET 20
 
 VSearchUE::VSearchUE(QObject *p_parent)
@@ -908,13 +911,47 @@ const QSharedPointer<VSearchResultItem> &VSearchUE::itemResultData(const QTreeWi
     return m_data[idx];
 }
 
-void VSearchUE::activateItem(const QSharedPointer<VSearchResultItem> &p_item)
+void VSearchUE::activateItem(const QSharedPointer<VSearchResultItem> &p_item, int p_matchIndex)
 {
     switch (p_item->m_type) {
     case VSearchResultItem::Note:
     {
+        bool highlightPage = false;
+        bool jumpTitle = false;
+        if (!p_item->m_matches.isEmpty() && p_item->m_config) {
+            if (p_item->m_config->m_object == VSearchConfig::Content) {
+                highlightPage = g_config->getHighlightMatchesInPage();
+            } else if (p_item->m_config->m_object == VSearchConfig::Outline) {
+                jumpTitle = true;
+            }
+        }
+
         QStringList files(p_item->m_path);
-        g_mainWin->openFiles(files);
+        OpenFileMode mode = highlightPage ? OpenFileMode::Edit : OpenFileMode::Read;
+        bool forceMode = highlightPage;
+        QVector<VFile *> openedFiles = g_mainWin->openFiles(files,
+                                                            false,
+                                                            mode,
+                                                            forceMode);
+        if (openedFiles.size() == 1) {
+            VEditTab *tab = g_mainWin->getCurrentTab();
+            if (tab->getFile() != openedFiles.first()) {
+                break;
+            }
+
+            if (highlightPage) {
+                tab->findText(p_item->m_config->m_contentToken, true, true);
+            } else if (jumpTitle) {
+                if (p_matchIndex >= p_item->m_matches.size()) {
+                    p_matchIndex = p_item->m_matches.size() - 1;
+                }
+
+                VHeaderPointer header(tab->getFile(),
+                                      p_item->m_matches[p_matchIndex].m_lineNumber);
+                tab->scrollToHeader(header);
+            }
+        }
+
         break;
     }
 
@@ -923,6 +960,10 @@ void VSearchUE::activateItem(const QSharedPointer<VSearchResultItem> &p_item)
         VDirectory *dir = g_vnote->getInternalDirectory(p_item->m_path);
         if (dir) {
             g_mainWin->locateDirectory(dir);
+        } else {
+            // External directory.
+            g_mainWin->showExplorerPanel(true);
+            g_mainWin->getExplorer()->setRootDirectory(p_item->m_path);
         }
 
         break;
@@ -961,7 +1002,7 @@ void VSearchUE::activateItem(QTreeWidgetItem *p_item, int p_col)
     }
 
     emit requestHideUniversalEntry();
-    activateItem(itemResultData(p_item));
+    activateItem(itemResultData(p_item), VUtils::childIndexOfTreeItem(p_item));
 }
 
 void VSearchUE::selectNextItem(int p_id, bool p_forward)

+ 1 - 1
src/vsearchue.h

@@ -93,7 +93,7 @@ public:
 
     QString currentItemFolder(int p_id) Q_DECL_OVERRIDE;
 
-    static void activateItem(const QSharedPointer<VSearchResultItem> &p_item);
+    static void activateItem(const QSharedPointer<VSearchResultItem> &p_item, int p_matchIndex = 0);
 
 protected:
     void init() Q_DECL_OVERRIDE;