From 8309606a52c4fbb6e0e6caadc52b5f36c25fb25a Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Fri, 1 Feb 2019 15:52:43 +0100 Subject: [PATCH] Help: Lookup in index if ID is not found And if multiple topics are found in the index, show the topic chooser dialog that we already have for the help index. Fixes: QTCREATORBUG-12704 Task-number: QTCREATORBUG-15959 Change-Id: I7afa6f44bbecc12f602aaaa4a11209ec72399689 Reviewed-by: David Schulz --- src/plugins/coreplugin/helpitem.cpp | 133 ++++++++++++++---- src/plugins/coreplugin/helpitem.h | 15 +- src/plugins/coreplugin/helpmanager.cpp | 5 + src/plugins/coreplugin/helpmanager.h | 1 + .../coreplugin/helpmanager_implementation.h | 1 + src/plugins/help/helpindexfilter.cpp | 2 +- src/plugins/help/helpmanager.h | 2 +- src/plugins/help/helpplugin.cpp | 18 ++- src/plugins/qmljseditor/qmljshoverhandler.cpp | 9 +- 9 files changed, 143 insertions(+), 43 deletions(-) diff --git a/src/plugins/coreplugin/helpitem.cpp b/src/plugins/coreplugin/helpitem.cpp index 268c00ce67b..b8794481cfe 100644 --- a/src/plugins/coreplugin/helpitem.cpp +++ b/src/plugins/coreplugin/helpitem.cpp @@ -104,7 +104,7 @@ bool HelpItem::isValid() const { if (m_helpUrl.isEmpty() && m_helpIds.isEmpty()) return false; - return !links().isEmpty(); + return !links().empty(); } QString HelpItem::extractContent(bool extended) const @@ -116,7 +116,8 @@ QString HelpItem::extractContent(bool extended) const htmlExtractor.setMode(Utils::HtmlDocExtractor::FirstParagraph); QString contents; - for (const QUrl &url : links()) { + for (const Link &item : links()) { + const QUrl url = item.second; const QString html = QString::fromUtf8(Core::HelpManager::fileData(url)); switch (m_category) { case Brief: @@ -157,50 +158,124 @@ QString HelpItem::extractContent(bool extended) const return contents; } -const QMap &HelpItem::links() const +static std::pair extractVersion(const QUrl &url) +{ + const QString host = url.host(); + const QStringList hostParts = host.split('.'); + if (hostParts.size() == 4 && (host.startsWith("com.trolltech.") + || host.startsWith("org.qt-project."))) { + bool ok = false; + // the following is only correct under the specific current conditions, and it will + // always be quite some guessing as long as the version information does not + // include separators for major vs minor vs patch version + const int version = hostParts.at(3).toInt(&ok); + if (ok) { + QUrl urlWithoutVersion(url); + urlWithoutVersion.setHost(hostParts.mid(0, 3).join('.')); + return {urlWithoutVersion, version}; + } + } + return {url, 0}; +} + +// sort primary by "url without version" and seconday by "version" +static bool helpUrlLessThan(const QUrl &a, const QUrl &b) +{ + const std::pair va = extractVersion(a); + const std::pair vb = extractVersion(b); + const QString sa = va.first.toString(); + const QString sb = vb.first.toString(); + if (sa == sb) + return va.second > vb.second; + return sa < sb; +} + +static bool linkLessThan(const HelpItem::Link &a, const HelpItem::Link &b) +{ + return helpUrlLessThan(a.second, b.second); +} + +// links are sorted with highest "version" first (for Qt help urls) +const HelpItem::Links &HelpItem::links() const { if (!m_helpLinks) { if (!m_helpUrl.isEmpty()) { - m_helpLinks.emplace(QMap({{m_helpUrl.toString(), m_helpUrl}})); + m_keyword = m_helpUrl.toString(); + m_helpLinks.emplace(Links{{m_keyword, m_helpUrl}}); } else { m_helpLinks.emplace(); // set a value even if there are no help IDs + QMap helpLinks; for (const QString &id : m_helpIds) { - m_helpLinks = Core::HelpManager::linksForIdentifier(id); - if (!m_helpLinks->isEmpty()) + helpLinks = Core::HelpManager::linksForIdentifier(id); + if (!helpLinks.isEmpty()) { + m_keyword = id; break; + } + } + if (helpLinks.isEmpty()) { // perform keyword lookup as well as a fallback + for (const QString &id : m_helpIds) { + helpLinks = Core::HelpManager::linksForKeyword(id); + if (!helpLinks.isEmpty()) { + m_keyword = id; + m_isFuzzyMatch = true; + break; + } + } + } + QMapIterator it(helpLinks); + while (it.hasNext()) { + it.next(); + m_helpLinks->emplace_back(it.key(), it.value()); } } + Utils::sort(*m_helpLinks, linkLessThan); } return *m_helpLinks; } -static QUrl findBestLink(const QMap &links) +static const HelpItem::Links getBestLinks(const HelpItem::Links &links) { - if (links.isEmpty()) - return QUrl(); - if (links.size() == 1) - return links.first(); - QUrl source = links.first(); - // workaround to show the latest Qt version - int version = 0; - QRegExp exp("(\\d+)"); - foreach (const QUrl &link, links) { - const QString &authority = link.authority(); - if (authority.startsWith("com.trolltech.") - || authority.startsWith("org.qt-project.")) { - if (exp.indexIn(authority) >= 0) { - const int tmpVersion = exp.cap(1).toInt(); - if (tmpVersion > version) { - source = link; - version = tmpVersion; - } - } + // extract the highest version (== first) link of each individual topic + HelpItem::Links bestLinks; + QUrl currentUnversionedUrl; + for (const HelpItem::Link &link : links) { + const QUrl unversionedUrl = extractVersion(link.second).first; + if (unversionedUrl != currentUnversionedUrl) { + currentUnversionedUrl = unversionedUrl; + bestLinks.push_back(link); } } - return source; + return bestLinks; } -const QUrl HelpItem::bestLink() const +static const HelpItem::Links getBestLink(const HelpItem::Links &links) { - return findBestLink(links()); + if (links.empty()) + return {}; + // Extract single link with highest version, from all topics. + // This is to ensure that if we succeeded with an ID lookup, and we have e.g. Qt5 and Qt4 + // documentation, that we only return the Qt5 link even though the Qt5 and Qt4 URLs look + // different. + int highestVersion = -1; + HelpItem::Link bestLink; + for (const HelpItem::Link &link : links) { + const int version = extractVersion(link.second).second; + if (version > highestVersion) { + highestVersion = version; + bestLink = link; + } + } + return {bestLink}; +} + +const HelpItem::Links HelpItem::bestLinks() const +{ + if (m_isFuzzyMatch) + return getBestLinks(links()); + return getBestLink(links()); +} + +const QString HelpItem::keyword() const +{ + return m_keyword; } diff --git a/src/plugins/coreplugin/helpitem.h b/src/plugins/coreplugin/helpitem.h index 5468659b2a4..4d9ab153fb8 100644 --- a/src/plugins/coreplugin/helpitem.h +++ b/src/plugins/coreplugin/helpitem.h @@ -29,16 +29,20 @@ #include -#include #include #include #include +#include + namespace Core { class CORE_EXPORT HelpItem { public: + using Link = std::pair; + using Links = std::vector; + enum Category { ClassOrNamespace, Enum, @@ -77,15 +81,18 @@ public: QString extractContent(bool extended) const; - const QMap &links() const; - const QUrl bestLink() const; + const Links &links() const; + const Links bestLinks() const; + const QString keyword() const; private: QUrl m_helpUrl; QStringList m_helpIds; QString m_docMark; Category m_category = Unknown; - mutable Utils::optional> m_helpLinks; // cached help links + mutable Utils::optional m_helpLinks; // cached help links + mutable QString m_keyword; + mutable bool m_isFuzzyMatch = false; }; } // namespace Core diff --git a/src/plugins/coreplugin/helpmanager.cpp b/src/plugins/coreplugin/helpmanager.cpp index 68c976fbfa0..20c6adf83b7 100644 --- a/src/plugins/coreplugin/helpmanager.cpp +++ b/src/plugins/coreplugin/helpmanager.cpp @@ -91,6 +91,11 @@ QMap linksForIdentifier(const QString &id) return checkInstance() ? m_instance->linksForIdentifier(id) : QMap(); } +QMap linksForKeyword(const QString &keyword) +{ + return checkInstance() ? m_instance->linksForKeyword(keyword) : QMap(); +} + QByteArray fileData(const QUrl &url) { return checkInstance() ? m_instance->fileData(url) : QByteArray(); diff --git a/src/plugins/coreplugin/helpmanager.h b/src/plugins/coreplugin/helpmanager.h index 00273365049..4f4484c65db 100644 --- a/src/plugins/coreplugin/helpmanager.h +++ b/src/plugins/coreplugin/helpmanager.h @@ -64,6 +64,7 @@ CORE_EXPORT void registerDocumentation(const QStringList &fileNames); CORE_EXPORT void unregisterDocumentation(const QStringList &nameSpaces); CORE_EXPORT QMap linksForIdentifier(const QString &id); +CORE_EXPORT QMap linksForKeyword(const QString &id); CORE_EXPORT QByteArray fileData(const QUrl &url); CORE_EXPORT void showHelpUrl(const QUrl &url, HelpViewerLocation location = HelpModeAlways); diff --git a/src/plugins/coreplugin/helpmanager_implementation.h b/src/plugins/coreplugin/helpmanager_implementation.h index 47afa96f348..6c160c7ce3f 100644 --- a/src/plugins/coreplugin/helpmanager_implementation.h +++ b/src/plugins/coreplugin/helpmanager_implementation.h @@ -41,6 +41,7 @@ public: virtual void registerDocumentation(const QStringList &fileNames) = 0; virtual void unregisterDocumentation(const QStringList &nameSpaces) = 0; virtual QMap linksForIdentifier(const QString &id) = 0; + virtual QMap linksForKeyword(const QString &keyword) = 0; virtual QByteArray fileData(const QUrl &url) = 0; virtual void showHelpUrl(const QUrl &url, HelpViewerLocation location = HelpModeAlways) = 0; }; diff --git a/src/plugins/help/helpindexfilter.cpp b/src/plugins/help/helpindexfilter.cpp index e205359cf4d..42467934b79 100644 --- a/src/plugins/help/helpindexfilter.cpp +++ b/src/plugins/help/helpindexfilter.cpp @@ -141,7 +141,7 @@ void HelpIndexFilter::accept(LocatorFilterEntry selection, Q_UNUSED(selectionStart) Q_UNUSED(selectionLength) const QString &key = selection.displayName; - const QMap &links = HelpManager::linksForKeyword(key); + const QMap &links = HelpManager::instance()->linksForKeyword(key); emit linksActivated(links, key); } diff --git a/src/plugins/help/helpmanager.h b/src/plugins/help/helpmanager.h index 825a27d0467..3a65b0f33dc 100644 --- a/src/plugins/help/helpmanager.h +++ b/src/plugins/help/helpmanager.h @@ -54,8 +54,8 @@ public: static void registerUserDocumentation(const QStringList &filePaths); static QSet userDocumentationPaths(); - static QMap linksForKeyword(const QString &key); QMap linksForIdentifier(const QString &id) override; + QMap linksForKeyword(const QString &key) override; static QUrl findFile(const QUrl &url); QByteArray fileData(const QUrl &url) override; diff --git a/src/plugins/help/helpplugin.cpp b/src/plugins/help/helpplugin.cpp index ada46f3b3ec..7703541670e 100644 --- a/src/plugins/help/helpplugin.cpp +++ b/src/plugins/help/helpplugin.cpp @@ -44,6 +44,7 @@ #include "searchwidget.h" #include "searchtaskhandler.h" #include "textbrowserhelpviewer.h" +#include "topicchooser.h" #ifdef QTC_MAC_NATIVE_HELPVIEWER #include "macwebkithelpviewer.h" @@ -636,8 +637,8 @@ void HelpPluginPrivate::requestContextHelp() void HelpPluginPrivate::showContextHelp(const HelpItem &contextHelp) { - const QUrl source = contextHelp.bestLink(); - if (!source.isValid()) { + const HelpItem::Links links = contextHelp.bestLinks(); + if (links.empty()) { // No link found or no context object HelpViewer *viewer = showHelpUrl(QUrl(Help::Constants::AboutBlank), LocalHelpManager::contextHelpOption()); @@ -653,8 +654,19 @@ void HelpPluginPrivate::showContextHelp(const HelpItem &contextHelp) .arg(contextHelp.helpIds().join(", ")) .arg(HelpPlugin::tr("No documentation available."))); } + } else if (links.size() == 1) { + showHelpUrl(links.front().second, LocalHelpManager::contextHelpOption()); } else { - showHelpUrl(source, LocalHelpManager::contextHelpOption()); + QMap map; + for (const HelpItem::Link &link : links) + map.insert(link.first, link.second); + auto tc = new TopicChooser(ICore::dialogParent(), contextHelp.keyword(), map); + tc->setModal(true); + connect(tc, &QDialog::accepted, this, [this, tc] { + showHelpUrl(tc->link(), LocalHelpManager::contextHelpOption()); + }); + connect(tc, &QDialog::finished, tc, [tc] { tc->deleteLater(); }); + tc->show(); } } diff --git a/src/plugins/qmljseditor/qmljshoverhandler.cpp b/src/plugins/qmljseditor/qmljshoverhandler.cpp index 53b3422c80e..6479ba13ad1 100644 --- a/src/plugins/qmljseditor/qmljshoverhandler.cpp +++ b/src/plugins/qmljseditor/qmljshoverhandler.cpp @@ -147,7 +147,6 @@ bool QmlJSHoverHandler::setQmlTypeHelp(const ScopeChain &scopeChain, const Docum { QString moduleName = getModuleName(scopeChain, qmlDocument, value); - QString helpId; QStringList helpIdCandidates; QStringList helpIdPieces(qName); @@ -168,7 +167,7 @@ bool QmlJSHoverHandler::setQmlTypeHelp(const ScopeChain &scopeChain, const Docum helpIdCandidates += helpIdPieces.join('.'); const HelpItem helpItem(helpIdCandidates, qName.join('.'), HelpItem::QmlComponent); - const QMap urlMap = helpItem.links(); + const HelpItem::Links links = helpItem.links(); // Check if the module name contains a major version. QRegularExpression version("^([^\\d]*)(\\d+)\\.*\\d*$"); @@ -176,10 +175,10 @@ bool QmlJSHoverHandler::setQmlTypeHelp(const ScopeChain &scopeChain, const Docum if (m.hasMatch()) { QMap filteredUrlMap; QStringRef maj = m.capturedRef(2); - for (auto x = urlMap.begin(); x != urlMap.end(); ++x) { - QString urlModuleName = x.value().path().split('/')[1]; + for (const HelpItem::Link &link : links) { + QString urlModuleName = link.second.path().split('/')[1]; if (urlModuleName.contains(maj)) - filteredUrlMap.insert(x.key(), x.value()); + filteredUrlMap.insert(link.first, link.second); } if (!filteredUrlMap.isEmpty()) { // Use the URL, to disambiguate different versions