forked from qt-creator/qt-creator
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 <david.schulz@qt.io>
This commit is contained in:
@@ -104,7 +104,7 @@ bool HelpItem::isValid() const
|
|||||||
{
|
{
|
||||||
if (m_helpUrl.isEmpty() && m_helpIds.isEmpty())
|
if (m_helpUrl.isEmpty() && m_helpIds.isEmpty())
|
||||||
return false;
|
return false;
|
||||||
return !links().isEmpty();
|
return !links().empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString HelpItem::extractContent(bool extended) const
|
QString HelpItem::extractContent(bool extended) const
|
||||||
@@ -116,7 +116,8 @@ QString HelpItem::extractContent(bool extended) const
|
|||||||
htmlExtractor.setMode(Utils::HtmlDocExtractor::FirstParagraph);
|
htmlExtractor.setMode(Utils::HtmlDocExtractor::FirstParagraph);
|
||||||
|
|
||||||
QString contents;
|
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));
|
const QString html = QString::fromUtf8(Core::HelpManager::fileData(url));
|
||||||
switch (m_category) {
|
switch (m_category) {
|
||||||
case Brief:
|
case Brief:
|
||||||
@@ -157,50 +158,124 @@ QString HelpItem::extractContent(bool extended) const
|
|||||||
return contents;
|
return contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QMap<QString, QUrl> &HelpItem::links() const
|
static std::pair<QUrl, int> 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<QUrl, int> va = extractVersion(a);
|
||||||
|
const std::pair<QUrl, int> 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_helpLinks) {
|
||||||
if (!m_helpUrl.isEmpty()) {
|
if (!m_helpUrl.isEmpty()) {
|
||||||
m_helpLinks.emplace(QMap<QString, QUrl>({{m_helpUrl.toString(), m_helpUrl}}));
|
m_keyword = m_helpUrl.toString();
|
||||||
|
m_helpLinks.emplace(Links{{m_keyword, m_helpUrl}});
|
||||||
} else {
|
} else {
|
||||||
m_helpLinks.emplace(); // set a value even if there are no help IDs
|
m_helpLinks.emplace(); // set a value even if there are no help IDs
|
||||||
|
QMap<QString, QUrl> helpLinks;
|
||||||
for (const QString &id : m_helpIds) {
|
for (const QString &id : m_helpIds) {
|
||||||
m_helpLinks = Core::HelpManager::linksForIdentifier(id);
|
helpLinks = Core::HelpManager::linksForIdentifier(id);
|
||||||
if (!m_helpLinks->isEmpty())
|
if (!helpLinks.isEmpty()) {
|
||||||
|
m_keyword = id;
|
||||||
break;
|
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<QString, QUrl> it(helpLinks);
|
||||||
|
while (it.hasNext()) {
|
||||||
|
it.next();
|
||||||
|
m_helpLinks->emplace_back(it.key(), it.value());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Utils::sort(*m_helpLinks, linkLessThan);
|
||||||
}
|
}
|
||||||
return *m_helpLinks;
|
return *m_helpLinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
static QUrl findBestLink(const QMap<QString, QUrl> &links)
|
static const HelpItem::Links getBestLinks(const HelpItem::Links &links)
|
||||||
{
|
{
|
||||||
if (links.isEmpty())
|
// extract the highest version (== first) link of each individual topic
|
||||||
return QUrl();
|
HelpItem::Links bestLinks;
|
||||||
if (links.size() == 1)
|
QUrl currentUnversionedUrl;
|
||||||
return links.first();
|
for (const HelpItem::Link &link : links) {
|
||||||
QUrl source = links.first();
|
const QUrl unversionedUrl = extractVersion(link.second).first;
|
||||||
// workaround to show the latest Qt version
|
if (unversionedUrl != currentUnversionedUrl) {
|
||||||
int version = 0;
|
currentUnversionedUrl = unversionedUrl;
|
||||||
QRegExp exp("(\\d+)");
|
bestLinks.push_back(link);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
@@ -29,16 +29,20 @@
|
|||||||
|
|
||||||
#include <utils/optional.h>
|
#include <utils/optional.h>
|
||||||
|
|
||||||
#include <QMap>
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
class CORE_EXPORT HelpItem
|
class CORE_EXPORT HelpItem
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using Link = std::pair<QString, QUrl>;
|
||||||
|
using Links = std::vector<Link>;
|
||||||
|
|
||||||
enum Category {
|
enum Category {
|
||||||
ClassOrNamespace,
|
ClassOrNamespace,
|
||||||
Enum,
|
Enum,
|
||||||
@@ -77,15 +81,18 @@ public:
|
|||||||
|
|
||||||
QString extractContent(bool extended) const;
|
QString extractContent(bool extended) const;
|
||||||
|
|
||||||
const QMap<QString, QUrl> &links() const;
|
const Links &links() const;
|
||||||
const QUrl bestLink() const;
|
const Links bestLinks() const;
|
||||||
|
const QString keyword() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QUrl m_helpUrl;
|
QUrl m_helpUrl;
|
||||||
QStringList m_helpIds;
|
QStringList m_helpIds;
|
||||||
QString m_docMark;
|
QString m_docMark;
|
||||||
Category m_category = Unknown;
|
Category m_category = Unknown;
|
||||||
mutable Utils::optional<QMap<QString, QUrl>> m_helpLinks; // cached help links
|
mutable Utils::optional<Links> m_helpLinks; // cached help links
|
||||||
|
mutable QString m_keyword;
|
||||||
|
mutable bool m_isFuzzyMatch = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
@@ -91,6 +91,11 @@ QMap<QString, QUrl> linksForIdentifier(const QString &id)
|
|||||||
return checkInstance() ? m_instance->linksForIdentifier(id) : QMap<QString, QUrl>();
|
return checkInstance() ? m_instance->linksForIdentifier(id) : QMap<QString, QUrl>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QMap<QString, QUrl> linksForKeyword(const QString &keyword)
|
||||||
|
{
|
||||||
|
return checkInstance() ? m_instance->linksForKeyword(keyword) : QMap<QString, QUrl>();
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray fileData(const QUrl &url)
|
QByteArray fileData(const QUrl &url)
|
||||||
{
|
{
|
||||||
return checkInstance() ? m_instance->fileData(url) : QByteArray();
|
return checkInstance() ? m_instance->fileData(url) : QByteArray();
|
||||||
|
@@ -64,6 +64,7 @@ CORE_EXPORT void registerDocumentation(const QStringList &fileNames);
|
|||||||
CORE_EXPORT void unregisterDocumentation(const QStringList &nameSpaces);
|
CORE_EXPORT void unregisterDocumentation(const QStringList &nameSpaces);
|
||||||
|
|
||||||
CORE_EXPORT QMap<QString, QUrl> linksForIdentifier(const QString &id);
|
CORE_EXPORT QMap<QString, QUrl> linksForIdentifier(const QString &id);
|
||||||
|
CORE_EXPORT QMap<QString, QUrl> linksForKeyword(const QString &id);
|
||||||
CORE_EXPORT QByteArray fileData(const QUrl &url);
|
CORE_EXPORT QByteArray fileData(const QUrl &url);
|
||||||
|
|
||||||
CORE_EXPORT void showHelpUrl(const QUrl &url, HelpViewerLocation location = HelpModeAlways);
|
CORE_EXPORT void showHelpUrl(const QUrl &url, HelpViewerLocation location = HelpModeAlways);
|
||||||
|
@@ -41,6 +41,7 @@ public:
|
|||||||
virtual void registerDocumentation(const QStringList &fileNames) = 0;
|
virtual void registerDocumentation(const QStringList &fileNames) = 0;
|
||||||
virtual void unregisterDocumentation(const QStringList &nameSpaces) = 0;
|
virtual void unregisterDocumentation(const QStringList &nameSpaces) = 0;
|
||||||
virtual QMap<QString, QUrl> linksForIdentifier(const QString &id) = 0;
|
virtual QMap<QString, QUrl> linksForIdentifier(const QString &id) = 0;
|
||||||
|
virtual QMap<QString, QUrl> linksForKeyword(const QString &keyword) = 0;
|
||||||
virtual QByteArray fileData(const QUrl &url) = 0;
|
virtual QByteArray fileData(const QUrl &url) = 0;
|
||||||
virtual void showHelpUrl(const QUrl &url, HelpViewerLocation location = HelpModeAlways) = 0;
|
virtual void showHelpUrl(const QUrl &url, HelpViewerLocation location = HelpModeAlways) = 0;
|
||||||
};
|
};
|
||||||
|
@@ -141,7 +141,7 @@ void HelpIndexFilter::accept(LocatorFilterEntry selection,
|
|||||||
Q_UNUSED(selectionStart)
|
Q_UNUSED(selectionStart)
|
||||||
Q_UNUSED(selectionLength)
|
Q_UNUSED(selectionLength)
|
||||||
const QString &key = selection.displayName;
|
const QString &key = selection.displayName;
|
||||||
const QMap<QString, QUrl> &links = HelpManager::linksForKeyword(key);
|
const QMap<QString, QUrl> &links = HelpManager::instance()->linksForKeyword(key);
|
||||||
emit linksActivated(links, key);
|
emit linksActivated(links, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -54,8 +54,8 @@ public:
|
|||||||
static void registerUserDocumentation(const QStringList &filePaths);
|
static void registerUserDocumentation(const QStringList &filePaths);
|
||||||
static QSet<QString> userDocumentationPaths();
|
static QSet<QString> userDocumentationPaths();
|
||||||
|
|
||||||
static QMap<QString, QUrl> linksForKeyword(const QString &key);
|
|
||||||
QMap<QString, QUrl> linksForIdentifier(const QString &id) override;
|
QMap<QString, QUrl> linksForIdentifier(const QString &id) override;
|
||||||
|
QMap<QString, QUrl> linksForKeyword(const QString &key) override;
|
||||||
|
|
||||||
static QUrl findFile(const QUrl &url);
|
static QUrl findFile(const QUrl &url);
|
||||||
QByteArray fileData(const QUrl &url) override;
|
QByteArray fileData(const QUrl &url) override;
|
||||||
|
@@ -44,6 +44,7 @@
|
|||||||
#include "searchwidget.h"
|
#include "searchwidget.h"
|
||||||
#include "searchtaskhandler.h"
|
#include "searchtaskhandler.h"
|
||||||
#include "textbrowserhelpviewer.h"
|
#include "textbrowserhelpviewer.h"
|
||||||
|
#include "topicchooser.h"
|
||||||
|
|
||||||
#ifdef QTC_MAC_NATIVE_HELPVIEWER
|
#ifdef QTC_MAC_NATIVE_HELPVIEWER
|
||||||
#include "macwebkithelpviewer.h"
|
#include "macwebkithelpviewer.h"
|
||||||
@@ -636,8 +637,8 @@ void HelpPluginPrivate::requestContextHelp()
|
|||||||
|
|
||||||
void HelpPluginPrivate::showContextHelp(const HelpItem &contextHelp)
|
void HelpPluginPrivate::showContextHelp(const HelpItem &contextHelp)
|
||||||
{
|
{
|
||||||
const QUrl source = contextHelp.bestLink();
|
const HelpItem::Links links = contextHelp.bestLinks();
|
||||||
if (!source.isValid()) {
|
if (links.empty()) {
|
||||||
// No link found or no context object
|
// No link found or no context object
|
||||||
HelpViewer *viewer = showHelpUrl(QUrl(Help::Constants::AboutBlank),
|
HelpViewer *viewer = showHelpUrl(QUrl(Help::Constants::AboutBlank),
|
||||||
LocalHelpManager::contextHelpOption());
|
LocalHelpManager::contextHelpOption());
|
||||||
@@ -653,8 +654,19 @@ void HelpPluginPrivate::showContextHelp(const HelpItem &contextHelp)
|
|||||||
.arg(contextHelp.helpIds().join(", "))
|
.arg(contextHelp.helpIds().join(", "))
|
||||||
.arg(HelpPlugin::tr("No documentation available.")));
|
.arg(HelpPlugin::tr("No documentation available.")));
|
||||||
}
|
}
|
||||||
|
} else if (links.size() == 1) {
|
||||||
|
showHelpUrl(links.front().second, LocalHelpManager::contextHelpOption());
|
||||||
} else {
|
} else {
|
||||||
showHelpUrl(source, LocalHelpManager::contextHelpOption());
|
QMap<QString, QUrl> 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -147,7 +147,6 @@ bool QmlJSHoverHandler::setQmlTypeHelp(const ScopeChain &scopeChain, const Docum
|
|||||||
{
|
{
|
||||||
QString moduleName = getModuleName(scopeChain, qmlDocument, value);
|
QString moduleName = getModuleName(scopeChain, qmlDocument, value);
|
||||||
|
|
||||||
QString helpId;
|
|
||||||
QStringList helpIdCandidates;
|
QStringList helpIdCandidates;
|
||||||
|
|
||||||
QStringList helpIdPieces(qName);
|
QStringList helpIdPieces(qName);
|
||||||
@@ -168,7 +167,7 @@ bool QmlJSHoverHandler::setQmlTypeHelp(const ScopeChain &scopeChain, const Docum
|
|||||||
helpIdCandidates += helpIdPieces.join('.');
|
helpIdCandidates += helpIdPieces.join('.');
|
||||||
|
|
||||||
const HelpItem helpItem(helpIdCandidates, qName.join('.'), HelpItem::QmlComponent);
|
const HelpItem helpItem(helpIdCandidates, qName.join('.'), HelpItem::QmlComponent);
|
||||||
const QMap<QString, QUrl> urlMap = helpItem.links();
|
const HelpItem::Links links = helpItem.links();
|
||||||
|
|
||||||
// Check if the module name contains a major version.
|
// Check if the module name contains a major version.
|
||||||
QRegularExpression version("^([^\\d]*)(\\d+)\\.*\\d*$");
|
QRegularExpression version("^([^\\d]*)(\\d+)\\.*\\d*$");
|
||||||
@@ -176,10 +175,10 @@ bool QmlJSHoverHandler::setQmlTypeHelp(const ScopeChain &scopeChain, const Docum
|
|||||||
if (m.hasMatch()) {
|
if (m.hasMatch()) {
|
||||||
QMap<QString, QUrl> filteredUrlMap;
|
QMap<QString, QUrl> filteredUrlMap;
|
||||||
QStringRef maj = m.capturedRef(2);
|
QStringRef maj = m.capturedRef(2);
|
||||||
for (auto x = urlMap.begin(); x != urlMap.end(); ++x) {
|
for (const HelpItem::Link &link : links) {
|
||||||
QString urlModuleName = x.value().path().split('/')[1];
|
QString urlModuleName = link.second.path().split('/')[1];
|
||||||
if (urlModuleName.contains(maj))
|
if (urlModuleName.contains(maj))
|
||||||
filteredUrlMap.insert(x.key(), x.value());
|
filteredUrlMap.insert(link.first, link.second);
|
||||||
}
|
}
|
||||||
if (!filteredUrlMap.isEmpty()) {
|
if (!filteredUrlMap.isEmpty()) {
|
||||||
// Use the URL, to disambiguate different versions
|
// Use the URL, to disambiguate different versions
|
||||||
|
Reference in New Issue
Block a user