1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "helpindexfilter.h"
27
28 #include "helpicons.h"
29 #include "helpmanager.h"
30 #include "topicchooser.h"
31
32 #include <coreplugin/icore.h>
33 #include <coreplugin/helpmanager.h>
34 #include <extensionsystem/pluginmanager.h>
35 #include <utils/utilsicons.h>
36
37 #include <QIcon>
38
39 #ifndef HELP_NEW_FILTER_ENGINE
40
41 #include <utils/algorithm.h>
42
43 #include <QSqlDatabase>
44 #include <QSqlDriver>
45 #include <QSqlError>
46 #include <QSqlQuery>
47
48 #else
49
50 #include "localhelpmanager.h"
51 #include <QtHelp/QHelpEngine>
52 #include <QtHelp/QHelpFilterEngine>
53 #include <QtHelp/QHelpLink>
54
55 #endif
56
57 using namespace Core;
58 using namespace Help;
59 using namespace Help::Internal;
60
HelpIndexFilter()61 HelpIndexFilter::HelpIndexFilter()
62 {
63 setId("HelpIndexFilter");
64 setDisplayName(tr("Help Index"));
65 setDefaultIncludedByDefault(false);
66 setDefaultShortcutString("?");
67
68 m_icon = Utils::Icons::BOOKMARK.icon();
69 connect(Core::HelpManager::Signals::instance(), &Core::HelpManager::Signals::setupFinished,
70 this, &HelpIndexFilter::invalidateCache);
71 connect(Core::HelpManager::Signals::instance(),
72 &Core::HelpManager::Signals::documentationChanged,
73 this,
74 &HelpIndexFilter::invalidateCache);
75 connect(HelpManager::instance(), &HelpManager::collectionFileChanged,
76 this, &HelpIndexFilter::invalidateCache);
77 }
78
79 HelpIndexFilter::~HelpIndexFilter() = default;
80
81 #ifndef HELP_NEW_FILTER_ENGINE
82
prepareSearch(const QString & entry)83 void HelpIndexFilter::prepareSearch(const QString &entry)
84 {
85 Q_UNUSED(entry)
86 QStringList namespaces = HelpManager::registeredNamespaces();
87 m_helpDatabases = Utils::transform(namespaces, &HelpManager::fileFromNamespace);
88 }
89
matchesFor(QFutureInterface<LocatorFilterEntry> & future,const QString & entry)90 QList<LocatorFilterEntry> HelpIndexFilter::matchesFor(QFutureInterface<LocatorFilterEntry> &future, const QString &entry)
91 {
92 if (m_needsUpdate.exchange(false) || m_searchTermCache.size() < 2
93 || m_searchTermCache.isEmpty() || !entry.contains(m_searchTermCache)) {
94 int limit = entry.size() < 2 ? 200 : INT_MAX;
95 QSet<QString> results;
96 for (const QString &filePath : qAsConst(m_helpDatabases)) {
97 if (future.isCanceled())
98 return QList<LocatorFilterEntry>();
99 QSet<QString> result;
100 QMetaObject::invokeMethod(this, "searchMatches", Qt::BlockingQueuedConnection,
101 Q_RETURN_ARG(QSet<QString>, result),
102 Q_ARG(QString, filePath),
103 Q_ARG(QString, entry),
104 Q_ARG(int, limit));
105 results.unite(result);
106 }
107 m_keywordCache = results;
108 m_searchTermCache = entry;
109 }
110
111 Qt::CaseSensitivity cs = caseSensitivity(entry);
112 QList<LocatorFilterEntry> entries;
113 QStringList keywords;
114 QStringList unsortedKeywords;
115 keywords.reserve(m_keywordCache.size());
116 unsortedKeywords.reserve(m_keywordCache.size());
117 QSet<QString> allresults;
118 for (const QString &keyword : qAsConst(m_keywordCache)) {
119 if (future.isCanceled())
120 return QList<LocatorFilterEntry>();
121 if (keyword.startsWith(entry, cs)) {
122 keywords.append(keyword);
123 allresults.insert(keyword);
124 } else if (keyword.contains(entry, cs)) {
125 unsortedKeywords.append(keyword);
126 allresults.insert(keyword);
127 }
128 }
129 Utils::sort(keywords);
130 keywords << unsortedKeywords;
131 m_keywordCache = allresults;
132 m_searchTermCache = entry;
133 for (const QString &keyword : qAsConst(keywords)) {
134 const int index = keyword.indexOf(entry, 0, cs);
135 LocatorFilterEntry filterEntry(this, keyword, QVariant(), m_icon);
136 filterEntry.highlightInfo = {index, entry.length()};
137 entries.append(filterEntry);
138 }
139
140 return entries;
141 }
142
143 #else
144
updateCache(QFutureInterface<LocatorFilterEntry> & future,const QStringList & cache,const QString & entry)145 bool HelpIndexFilter::updateCache(QFutureInterface<LocatorFilterEntry> &future,
146 const QStringList &cache, const QString &entry)
147 {
148 const Qt::CaseSensitivity cs = caseSensitivity(entry);
149 QStringList bestKeywords;
150 QStringList worseKeywords;
151 bestKeywords.reserve(cache.size());
152 worseKeywords.reserve(cache.size());
153 for (const QString &keyword : cache) {
154 if (future.isCanceled())
155 return false;
156 if (keyword.startsWith(entry, cs))
157 bestKeywords.append(keyword);
158 else if (keyword.contains(entry, cs))
159 worseKeywords.append(keyword);
160 }
161 bestKeywords << worseKeywords;
162 m_lastIndicesCache = bestKeywords;
163 m_lastEntry = entry;
164
165 return true;
166 }
167
matchesFor(QFutureInterface<LocatorFilterEntry> & future,const QString & entry)168 QList<LocatorFilterEntry> HelpIndexFilter::matchesFor(QFutureInterface<LocatorFilterEntry> &future, const QString &entry)
169 {
170 if (m_needsUpdate.exchange(false)) {
171 QStringList indices;
172 QMetaObject::invokeMethod(this, [this] { return allIndices(); },
173 Qt::BlockingQueuedConnection, &indices);
174 m_allIndicesCache = indices;
175 // force updating the cache taking the m_allIndicesCache
176 m_lastIndicesCache = QStringList();
177 m_lastEntry = QString();
178 }
179
180 const QStringList cacheBase = m_lastEntry.isEmpty() || !entry.contains(m_lastEntry)
181 ? m_allIndicesCache : m_lastIndicesCache;
182
183 if (!updateCache(future, cacheBase, entry))
184 return QList<LocatorFilterEntry>();
185
186 const Qt::CaseSensitivity cs = caseSensitivity(entry);
187 QList<LocatorFilterEntry> entries;
188 for (const QString &keyword : qAsConst(m_lastIndicesCache)) {
189 const int index = keyword.indexOf(entry, 0, cs);
190 LocatorFilterEntry filterEntry(this, keyword, QVariant(), m_icon);
191 filterEntry.highlightInfo = {index, int(entry.length())};
192 entries.append(filterEntry);
193 }
194
195 return entries;
196 }
197
198 #endif
199
accept(LocatorFilterEntry selection,QString * newText,int * selectionStart,int * selectionLength) const200 void HelpIndexFilter::accept(LocatorFilterEntry selection,
201 QString *newText, int *selectionStart, int *selectionLength) const
202 {
203 Q_UNUSED(newText)
204 Q_UNUSED(selectionStart)
205 Q_UNUSED(selectionLength)
206 const QString &key = selection.displayName;
207 #ifndef HELP_NEW_FILTER_ENGINE
208 const QMultiMap<QString, QUrl> &links = HelpManager::instance()->linksForKeyword(key);
209 #else
210 QMultiMap<QString, QUrl> links;
211 const QList<QHelpLink> docs = LocalHelpManager::helpEngine().documentsForKeyword(key, QString());
212 for (const auto &doc : docs)
213 links.insert(doc.title, doc.url);
214 #endif
215 emit linksActivated(links, key);
216 }
217
refresh(QFutureInterface<void> & future)218 void HelpIndexFilter::refresh(QFutureInterface<void> &future)
219 {
220 Q_UNUSED(future)
221 invalidateCache();
222 }
223
224 #ifndef HELP_NEW_FILTER_ENGINE
225
searchMatches(const QString & databaseFilePath,const QString & term,int limit)226 QSet<QString> HelpIndexFilter::searchMatches(const QString &databaseFilePath,
227 const QString &term, int limit)
228 {
229 static const QLatin1String sqlite("QSQLITE");
230 static const QLatin1String name("HelpManager::findKeywords");
231
232 QSet<QString> keywords;
233
234 { // make sure db is destroyed before removeDatabase call
235 QSqlDatabase db = QSqlDatabase::addDatabase(sqlite, name);
236 if (db.driver() && db.driver()->lastError().type() == QSqlError::NoError) {
237 db.setDatabaseName(databaseFilePath);
238 if (db.open()) {
239 QSqlQuery query = QSqlQuery(db);
240 query.setForwardOnly(true);
241 query.exec(QString::fromLatin1("SELECT DISTINCT Name FROM IndexTable WHERE Name LIKE "
242 "'%%1%' LIMIT %2").arg(term, QString::number(limit)));
243 while (query.next()) {
244 const QString &keyValue = query.value(0).toString();
245 if (!keyValue.isEmpty())
246 keywords.insert(keyValue);
247 }
248 db.close();
249 }
250 }
251 }
252 QSqlDatabase::removeDatabase(name);
253 return keywords;
254 }
255
256 #else
257
allIndices() const258 QStringList HelpIndexFilter::allIndices() const
259 {
260 LocalHelpManager::setupGuiHelpEngine();
261 return LocalHelpManager::filterEngine()->indices(QString());
262 }
263
264 #endif
265
invalidateCache()266 void HelpIndexFilter::invalidateCache()
267 {
268 m_needsUpdate = true;
269 }
270