diff --git a/src/plugins/coreplugin/helpmanager.cpp b/src/plugins/coreplugin/helpmanager.cpp index 997c1f971054dc76ba8ada6d3391b8ccb2b9aaae..019f8f79f11e92589c95cbf3470cc48de1eefece 100644 --- a/src/plugins/coreplugin/helpmanager.cpp +++ b/src/plugins/coreplugin/helpmanager.cpp @@ -111,7 +111,7 @@ HelpManager::~HelpManager() delete d; } -QObject *HelpManager::instance() +HelpManager *HelpManager::instance() { Q_ASSERT(m_instance); return m_instance; @@ -248,51 +248,6 @@ QMap<QString, QUrl> HelpManager::linksForIdentifier(const QString &id) return d->m_helpEngine->linksForIdentifier(id); } -// This should go into Qt 4.8 once we start using it for Qt Creator -QStringList HelpManager::findKeywords(const QString &key, Qt::CaseSensitivity caseSensitivity, - int maxHits) -{ - QTC_ASSERT(!d->m_needsSetup, return QStringList()); - - const QLatin1String sqlite("QSQLITE"); - const QLatin1String name("HelpManager::findKeywords"); - - QSet<QString> keywords; - QSet<QString> keywordsToSort; - - DbCleaner cleaner(name); - QSqlDatabase db = QSqlDatabase::addDatabase(sqlite, name); - if (db.driver() && db.driver()->lastError().type() == QSqlError::NoError) { - QHelpEngineCore core(collectionFilePath()); - core.setAutoSaveFilter(false); - core.setCurrentFilter(tr("Unfiltered")); - core.setupData(); - const QStringList ®isteredDocs = core.registeredDocumentations(); - foreach (const QString &nameSpace, registeredDocs) { - db.setDatabaseName(core.documentationFileName(nameSpace)); - if (db.open()) { - QSqlQuery query = QSqlQuery(db); - query.setForwardOnly(true); - query.exec(QString::fromLatin1("SELECT DISTINCT Name FROM IndexTable WHERE Name LIKE " - "'%%1%' LIMIT %2").arg(key, QString::number(maxHits))); - while (query.next()) { - const QString &keyValue = query.value(0).toString(); - if (!keyValue.isEmpty()) { - if (keyValue.startsWith(key, caseSensitivity)) - keywordsToSort.insert(keyValue); - else - keywords.insert(keyValue); - } - } - } - } - } - - QStringList keywordsSorted = keywordsToSort.toList(); - Utils::sort(keywordsSorted); - return keywordsSorted + keywords.toList(); -} - QUrl HelpManager::findFile(const QUrl &url) { QTC_ASSERT(!d->m_needsSetup, return QUrl()); diff --git a/src/plugins/coreplugin/helpmanager.h b/src/plugins/coreplugin/helpmanager.h index c63ceb0fb1b3883880c775a18a8aa9b287e0ec69..185276f674a0b4a66650791092833a96aeeae29c 100644 --- a/src/plugins/coreplugin/helpmanager.h +++ b/src/plugins/coreplugin/helpmanager.h @@ -63,7 +63,7 @@ public: typedef QHash<QString, QStringList> Filters; - static QObject *instance(); + static HelpManager *instance(); static QString collectionFilePath(); static void registerDocumentation(const QStringList &fileNames); @@ -74,9 +74,6 @@ public: static QMap<QString, QUrl> linksForKeyword(const QString &key); static QMap<QString, QUrl> linksForIdentifier(const QString &id); - static QStringList findKeywords(const QString &key, - Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive, - int maxHits = INT_MAX); static QUrl findFile(const QUrl &url); static QByteArray fileData(const QUrl &url); diff --git a/src/plugins/help/help.pro b/src/plugins/help/help.pro index 4c19710f5f794da379ecb98914bab2442cb962c8..ca6e1adb911cdce8716fb553c743257623761f79 100644 --- a/src/plugins/help/help.pro +++ b/src/plugins/help/help.pro @@ -1,4 +1,4 @@ -QT += help network printsupport +QT += help network printsupport sql !isEmpty(QT.webkitwidgets.name): QT += webkitwidgets webkit else: DEFINES += QT_NO_WEBKIT diff --git a/src/plugins/help/helpindexfilter.cpp b/src/plugins/help/helpindexfilter.cpp index e6a5c29d371d34121bd01e0990084977d4cc4dd8..2c6ea45e1e49814d7320b219496682ab182d2431 100644 --- a/src/plugins/help/helpindexfilter.cpp +++ b/src/plugins/help/helpindexfilter.cpp @@ -37,8 +37,14 @@ #include <extensionsystem/pluginmanager.h> #include <coreplugin/icore.h> #include <coreplugin/helpmanager.h> +#include <utils/algorithm.h> #include <QIcon> +#include <QSqlDatabase> +#include <QSqlDriver> +#include <QSqlError> +#include <QSqlQuery> + using namespace Core; using namespace Help; @@ -47,6 +53,7 @@ using namespace Help::Internal; Q_DECLARE_METATYPE(ILocatorFilter*) HelpIndexFilter::HelpIndexFilter() + : m_needsUpdate(true) { setId("HelpIndexFilter"); setDisplayName(tr("Help Index")); @@ -54,6 +61,12 @@ HelpIndexFilter::HelpIndexFilter() setShortcutString(QString(QLatin1Char('?'))); m_icon = QIcon(QLatin1String(":/help/images/bookmark.png")); + connect(HelpManager::instance(), &HelpManager::setupFinished, + this, &HelpIndexFilter::invalidateCache); + connect(HelpManager::instance(), &HelpManager::documentationChanged, + this, &HelpIndexFilter::invalidateCache); + connect(HelpManager::instance(), &HelpManager::collectionFileChanged, + this, &HelpIndexFilter::invalidateCache); } HelpIndexFilter::~HelpIndexFilter() @@ -62,21 +75,62 @@ HelpIndexFilter::~HelpIndexFilter() void HelpIndexFilter::prepareSearch(const QString &entry) { - if (entry.length() < 2) - m_keywords = Core::HelpManager::findKeywords(entry, caseSensitivity(entry), 200); - else - m_keywords = Core::HelpManager::findKeywords(entry, caseSensitivity(entry)); + Q_UNUSED(entry) + QStringList namespaces = HelpManager::registeredNamespaces(); + m_helpDatabases = Utils::transform(namespaces, [](const QString &ns) { + return HelpManager::fileFromNamespace(ns); + }); } QList<LocatorFilterEntry> HelpIndexFilter::matchesFor(QFutureInterface<LocatorFilterEntry> &future, const QString &entry) { - Q_UNUSED(entry) // search is already done in the GUI thread in prepareSearch + m_mutex.lock(); // guard m_needsUpdate + bool forceUpdate = m_needsUpdate; + m_mutex.unlock(); + + if (forceUpdate || m_searchTermCache.size() < 2 || m_searchTermCache.isEmpty() + || !entry.contains(m_searchTermCache)) { + int limit = entry.size() < 2 ? 200 : INT_MAX; + QSet<QString> results; + foreach (const QString &filePath, m_helpDatabases) { + QSet<QString> result; + QMetaObject::invokeMethod(this, "searchMatches", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QSet<QString>, result), + Q_ARG(QString, filePath), + Q_ARG(QString, entry), + Q_ARG(int, limit)); + results.unite(result); + } + m_mutex.lock(); // guard m_needsUpdate + m_needsUpdate = false; + m_mutex.unlock(); + m_keywordCache = results; + m_searchTermCache = entry; + } + + Qt::CaseSensitivity cs = caseSensitivity(entry); QList<LocatorFilterEntry> entries; - foreach (const QString &keyword, m_keywords) { + QStringList keywords; + QStringList unsortedKeywords; + keywords.reserve(m_keywordCache.size()); + unsortedKeywords.reserve(m_keywordCache.size()); + QSet<QString> allresults; + foreach (const QString &keyword, m_keywordCache) { if (future.isCanceled()) break; - entries.append(LocatorFilterEntry(this, keyword, QVariant(), m_icon)); + if (keyword.startsWith(entry, cs)) { + keywords.append(keyword); + allresults.insert(keyword); + } else if (keyword.contains(entry, cs)) { + unsortedKeywords.append(keyword); + allresults.insert(keyword); + } } + Utils::sort(keywords); + keywords << unsortedKeywords; + m_keywordCache = allresults; + foreach (const QString &keyword, keywords) + entries.append(LocatorFilterEntry(this, keyword, QVariant(), m_icon)); return entries; } @@ -95,5 +149,42 @@ void HelpIndexFilter::accept(LocatorFilterEntry selection) const void HelpIndexFilter::refresh(QFutureInterface<void> &future) { Q_UNUSED(future) - // Nothing to refresh + invalidateCache(); +} + +QSet<QString> HelpIndexFilter::searchMatches(const QString &databaseFilePath, + const QString &term, int limit) +{ + static const QLatin1String sqlite("QSQLITE"); + static const QLatin1String name("HelpManager::findKeywords"); + + QSet<QString> keywords; + + { // make sure db is destroyed before removeDatabase call + QSqlDatabase db = QSqlDatabase::addDatabase(sqlite, name); + if (db.driver() && db.driver()->lastError().type() == QSqlError::NoError) { + db.setDatabaseName(databaseFilePath); + if (db.open()) { + QSqlQuery query = QSqlQuery(db); + query.setForwardOnly(true); + query.exec(QString::fromLatin1("SELECT DISTINCT Name FROM IndexTable WHERE Name LIKE " + "'%%1%' LIMIT %2").arg(term, QString::number(limit))); + while (query.next()) { + const QString &keyValue = query.value(0).toString(); + if (!keyValue.isEmpty()) + keywords.insert(keyValue); + } + db.close(); + } + } + } + QSqlDatabase::removeDatabase(name); + return keywords; +} + +void HelpIndexFilter::invalidateCache() +{ + m_mutex.lock(); + m_needsUpdate = true; + m_mutex.unlock(); } diff --git a/src/plugins/help/helpindexfilter.h b/src/plugins/help/helpindexfilter.h index 80558d41517137ff2de9c5a2999710ca8a1872ec..efae73dde3f4e714cf6ceaa40cdd43bc1bf5069c 100644 --- a/src/plugins/help/helpindexfilter.h +++ b/src/plugins/help/helpindexfilter.h @@ -34,6 +34,7 @@ #include <coreplugin/locator/ilocatorfilter.h> #include <QIcon> +#include <QMutex> namespace Help { namespace Internal { @@ -48,17 +49,26 @@ public: // ILocatorFilter void prepareSearch(const QString &entry); - QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future, const QString &entry); + QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future, + const QString &entry); void accept(Core::LocatorFilterEntry selection) const; void refresh(QFutureInterface<void> &future); + Q_INVOKABLE QSet<QString> searchMatches(const QString &databaseFilePath, + const QString &term, int limit); signals: void linkActivated(const QUrl &link) const; void linksActivated(const QMap<QString, QUrl> &links, const QString &key) const; private: + void invalidateCache(); + + QStringList m_helpDatabases; + QSet<QString> m_keywordCache; + QString m_searchTermCache; + bool m_needsUpdate; + QMutex m_mutex; QIcon m_icon; - QStringList m_keywords; }; } // namespace Internal