diff --git a/src/libs/utils/htmldocextractor.cpp b/src/libs/utils/htmldocextractor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7690dae79d2e4db63666bd7872e8aac73ae4027b --- /dev/null +++ b/src/libs/utils/htmldocextractor.cpp @@ -0,0 +1,417 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "htmldocextractor.h" + +#include <QtCore/QLatin1String> +#include <QtCore/QLatin1Char> +#include <QtCore/QStringList> +#include <QtCore/QRegExp> + +using namespace Utils; + +namespace { + QRegExp createMinimalExp(const QString &pattern) { + QRegExp exp(pattern); + exp.setMinimal(true); + return exp; + } +} + +HtmlDocExtractor::HtmlDocExtractor() : + m_lengthReference(-1), + m_truncateAtParagraph(false), + m_formatContents(true) +{} + +void HtmlDocExtractor::setLengthReference(const int length, const bool truncateAtParagraph) +{ + m_lengthReference = length; + m_truncateAtParagraph = truncateAtParagraph; +} + +void HtmlDocExtractor::setFormatContents(const bool format) +{ m_formatContents = format; } + +QString HtmlDocExtractor::assemble(const QString &elementAttr, + const QString &elementTemplate) const +{ + const QString &cleanAttr = cleanReference(elementAttr); + return QString(elementTemplate).arg(cleanAttr); +} + +QString HtmlDocExtractor::getClassOrNamespaceBrief(const QString &html, const QString &name) const +{ + QString contents = getContentsByMarks(html, name + QLatin1String("-brief"), false); + if (contents.isEmpty()) { + QLatin1String pattern("<h1 class=\"title\">.*</p>"); + contents = findByPattern(html, pattern); + if (!contents.isEmpty()) + contents.remove(QRegExp(QLatin1String("<h1.*</h1>"))); + } + if (!contents.isEmpty()) { + contents.remove(QLatin1String("<a href=\"#details\">More...</a>")); + if (m_formatContents) { + contents.prepend(QLatin1String("<nobr>")); + contents.append(QLatin1String("</nobr>")); + } + } + + formatContents(&contents); + return contents; +} + +QString HtmlDocExtractor::getClassOrNamespaceDescription(const QString &html, + const QString &name) const +{ + QString contents = getContentsByMarks(html, name + QLatin1String("-description"), false); + if (contents.isEmpty()) { + QLatin1String pattern("<a name=\"details\"></a>.*<hr />.*<hr />"); + contents = findByPattern(html, pattern); + } + if (!contents.isEmpty()) + contents.replace(QLatin1String("<h2>Detailed Description</h2>"), name); + + formatContents(&contents); + return contents; +} + +QString HtmlDocExtractor::getEnumDescription(const QString &html, const QString &name) const +{ + const QString &enumm = name + QLatin1String("-enum"); + QString contents = getClassOrNamespaceMemberDescription(html, name, enumm, false); + formatContents(&contents); + return contents; +} + +QString HtmlDocExtractor::getTypedefDescription(const QString &html, const QString &name) const +{ + const QString &typedeff = name + QLatin1String("-typedef"); + QString contents = getClassOrNamespaceMemberDescription(html, name, typedeff, false); + formatContents(&contents); + return contents; +} + +QString HtmlDocExtractor::getVarDescription(const QString &html, const QString &name) const +{ + const QString &var = name + QLatin1String("-var"); + QString contents = getClassOrNamespaceMemberDescription(html, name, var, false); + formatContents(&contents); + return contents; +} + +QString HtmlDocExtractor::getMacroDescription(const QString &html, + const QString &mark, + const QString &anchorName) const +{ + QString contents = getClassOrNamespaceMemberDescription(html, mark, anchorName, false, true); + formatContents(&contents); + return contents; +} + +QString HtmlDocExtractor::getFunctionDescription(const QString &html, + const QString &mark, + const QString &anchorName, + const bool mainOverload) const +{ + QString contents = getClassOrNamespaceMemberDescription(html, mark, anchorName, mainOverload); + if (contents.isEmpty()) { + // Maybe marks are not present and/or this is a property. Besides setX/isX/hasX there are + // other (not so usual) names for property based functions. A few examples of those: + // - toPlainText / Prop. plainText from QPlainTextEdit. + // - resize / Prop. size from QWidget. + // - move / Prop. pos from QWidget (nothing similar in the names in this case). + // So I try to find the link to this property in the list of properties, extract its + // anchor and then follow by the name found. + QString pattern = assemble(anchorName, + QLatin1String("<a href=\"[a-z\\.]+#([A-Za-z]+-prop)\">%1</a>")); + QRegExp exp = createMinimalExp(pattern); + if (exp.indexIn(html) != -1) { + const QString &prop = exp.cap(1); + contents = getClassOrNamespaceMemberDescription(html, prop, prop, false); + } + } + formatContents(&contents); + return contents; +} + +QString HtmlDocExtractor::getClassOrNamespaceMemberDescription(const QString &html, + const QString &mark, + const QString &anchorName, + const bool mainOverload, + const bool relaxedMatch) const +{ + // Try with extraction marks (present in newer verions of the docs). If nothing is found try + // with the anchor. + QString contents; + if (!mark.isEmpty()) + contents = getContentsByMarks(html, mark, mainOverload); + if (contents.isEmpty()) + contents = getContentsByAnchor(html, anchorName, relaxedMatch); + + return contents; +} + +QString HtmlDocExtractor::getContentsByAnchor(const QString &html, + const QString &name, + const bool relaxedMatch) const +{ + // This approach is not very accurate. + QString pattern; + if (relaxedMatch) { + pattern = QLatin1String( + "(?:<h3 class=\"[a-z]+\">)?<a name=\"%1.*(?:<h3 class|<p />|<hr />|</div>)"); + } else { + // When there are duplicates the HTML generator incrementally appends 'x' to references. + pattern = QLatin1String( + "(?:<h3 class=\"[a-z]+\">)?<a name=\"%1x*\">.*(?:<h3 class|<p />|<hr />|</div>)"); + } + + return findByPattern(html, assemble(name, pattern)); +} + +QString HtmlDocExtractor::getContentsByMarks(const QString &html, + const QString &id, + const bool mainOverload) const +{ + QString endMark; + QString startMark; + if (id.contains(QLatin1Char('('))) { + const int index = id.indexOf(QLatin1Char('(')); + startMark = id.left(index); + endMark = startMark; + if (mainOverload) { + startMark.append(QLatin1String("[overload1]")); + } else { + QString complementaryId = id.right(id.length() - index); + complementaryId.remove(QRegExp(QLatin1String("[\\(\\), ]"))); + startMark.append(complementaryId); + } + } else { + startMark = id; + } + startMark.prepend(QLatin1String("$$$")); + + if (endMark.isEmpty()) { + if (id.contains(QLatin1Char('-'))) { + const int index = id.indexOf(QLatin1Char('-')); + endMark = id.left(index); + } else { + endMark = id; + } + } + endMark.prepend(QLatin1String("<!-- @@@")); + + return findByMarks(html, startMark, endMark); +} + +QString HtmlDocExtractor::findByMarks(const QString &html, + const QString &startMark, + const QString &endMark) const +{ + QString contents; + int start = html.indexOf(startMark); + if (start != -1) { + start = html.indexOf(QLatin1String("-->"), start); + if (start != -1) { + int end = html.indexOf(endMark, start); + if (end != -1) { + start += 3; + contents = html.mid(start, end - start); + } + } + } + return contents; +} + +QString HtmlDocExtractor::findByPattern(const QString &html, const QString &pattern) const +{ + QRegExp exp(pattern); + exp.setMinimal(true); + const int match = exp.indexIn(html); + if (match != -1) + return html.mid(match, exp.matchedLength()); + return QString(); +} + +void HtmlDocExtractor::formatContents(QString *html) const +{ + if (html->isEmpty()) + return; + + if (m_formatContents) { + replaceNonStyledHeadingsForBold(html); + replaceTablesForSimpleLines(html); + replaceListsForSimpleLines(html); + stripLinks(html); + stripHorizontalLines(html); + stripDivs(html); + stripTagsStyles(html); + stripHeadings(html); + } + + if (m_lengthReference > -1 && html->length() > m_lengthReference) { + if (m_truncateAtParagraph) { + const int nextBegin = html->indexOf(QLatin1String("<p>"), m_lengthReference); + QRegExp exp = createMinimalExp(QLatin1String("</p>|<br />")); + const int previousEnd = html->lastIndexOf(exp, m_lengthReference); + if (nextBegin != -1 && previousEnd != -1) + html->truncate(qMin(nextBegin, previousEnd + exp.matchedLength())); + else if (nextBegin != -1 || previousEnd != -1) + html->truncate((nextBegin != -1? nextBegin : previousEnd + exp.matchedLength())); + } else { + html->truncate(m_lengthReference); + } + html->append(QLatin1String("...")); + if (m_formatContents) + html->append(QLatin1String("<br />")); + } +} + +void HtmlDocExtractor::stripAllHtml(QString *html) +{ + html->remove(createMinimalExp(QLatin1String("<.*>"))); +} + +void HtmlDocExtractor::stripHeadings(QString *html) +{ + html->remove(createMinimalExp(QLatin1String("<h\\d{1}.*>|</h\\d{1}>"))); +} + +void HtmlDocExtractor::stripLinks(QString *html) +{ + html->remove(createMinimalExp(QLatin1String("<a\\s+.*>|</a>"))); +} + +void HtmlDocExtractor::stripHorizontalLines(QString *html) +{ + html->remove(createMinimalExp(QLatin1String("<hr\\s+/>"))); +} + +void HtmlDocExtractor::stripDivs(QString *html) +{ + html->remove(createMinimalExp(QLatin1String("<div\\s+.*>|</div>|<div\\s+.*/\\s*>"))); +} + +void HtmlDocExtractor::stripTagsStyles(QString *html) +{ + const QRegExp &exp = createMinimalExp(QLatin1String("<(.*\\s+)class=\".*\">")); + html->replace(exp, QLatin1String("<\\1>")); +} + +void HtmlDocExtractor::stripTeletypes(QString *html) +{ + html->remove(createMinimalExp(QLatin1String("<tt>|</tt>"))); +} + +void HtmlDocExtractor::replaceNonStyledHeadingsForBold(QString *html) +{ + const QRegExp &hStart = createMinimalExp(QLatin1String("<h\\d{1}>")); + const QRegExp &hEnd = createMinimalExp(QLatin1String("</h\\d{1}>")); + html->replace(hStart, QLatin1String("<p><b>")); + html->replace(hEnd, QLatin1String("</b></p>")); +} + +void HtmlDocExtractor::replaceTablesForSimpleLines(QString *html) +{ + html->remove(createMinimalExp(QLatin1String("<table.*>"))); + html->remove(QLatin1String("</table>")); + html->remove(createMinimalExp(QLatin1String("<tr.*><th.*>.*</th></tr>"))); + html->replace(QLatin1String("</td><td"), QLatin1String("</td> <td")); + html->remove(createMinimalExp(QLatin1String("<td.*>"))); + html->remove(QLatin1String("</td>")); + html->replace(QLatin1String("<tr>"), QLatin1String(" - ")); + html->replace(QLatin1String("</tr>"), QLatin1String("<br />")); +} + +void HtmlDocExtractor::replaceListsForSimpleLines(QString *html) +{ + html->remove(createMinimalExp(QLatin1String("<(?:ul|ol).*>"))); + html->remove(createMinimalExp(QLatin1String("</(?:ul|ol)>"))); + html->replace(QLatin1String("<li>"), QLatin1String(" - ")); + html->replace(QLatin1String("</li>"), QLatin1String("<br />")); +} + +/* + @todo: We need to clean the anchor in the same way qtdoc does. Currently, this method is a + duplicate of HtmlGenerator::cleanRef. It would be good to reuse the same code either by exposing + parts of qtdocs or by refactoring the behavior to use some Qt component for example. + */ +QString HtmlDocExtractor::cleanReference(const QString &reference) +{ + QString clean; + + if (reference.isEmpty()) + return clean; + + clean.reserve(reference.size() + 20); + const QChar c = reference[0]; + const uint u = c.unicode(); + + if ((u >= QLatin1Char('a') && u <= QLatin1Char('z')) || + (u >= QLatin1Char('A') && u <= QLatin1Char('Z')) || + (u >= QLatin1Char('0') && u <= QLatin1Char('9'))) { + clean += c; + } else if (u == QLatin1Char('~')) { + clean += QLatin1String("dtor."); + } else if (u == QLatin1Char('_')) { + clean += QLatin1String("underscore."); + } else { + clean += QLatin1String("A"); + } + + for (int i = 1; i < (int) reference.length(); i++) { + const QChar c = reference[i]; + const uint u = c.unicode(); + if ((u >= QLatin1Char('a') && u <= QLatin1Char('z')) || + (u >= QLatin1Char('A') && u <= QLatin1Char('Z')) || + (u >= QLatin1Char('0') && u <= QLatin1Char('9')) || u == QLatin1Char('-') || + u == QLatin1Char('_') || u == QLatin1Char(':') || u == QLatin1Char('.')) { + clean += c; + } else if (c.isSpace()) { + clean += QLatin1String("-"); + } else if (u == QLatin1Char('!')) { + clean += QLatin1String("-not"); + } else if (u == QLatin1Char('&')) { + clean += QLatin1String("-and"); + } else if (u == QLatin1Char('<')) { + clean += QLatin1String("-lt"); + } else if (u == QLatin1Char('=')) { + clean += QLatin1String("-eq"); + } else if (u == QLatin1Char('>')) { + clean += QLatin1String("-gt"); + } else if (u == QLatin1Char('#')) { + clean += QLatin1String("#"); + } else { + clean += QLatin1String("-"); + clean += QString::number((int)u, 16); + } + } + return clean; +} diff --git a/src/libs/utils/htmldocextractor.h b/src/libs/utils/htmldocextractor.h new file mode 100644 index 0000000000000000000000000000000000000000..86728d4788498c08fbb1d84acb3480e998df346c --- /dev/null +++ b/src/libs/utils/htmldocextractor.h @@ -0,0 +1,101 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef HTMLDOCEXTRACTOR_H +#define HTMLDOCEXTRACTOR_H + +#include "utils_global.h" + +#include <QtCore/QString> + +namespace Utils { + +class QTCREATOR_UTILS_EXPORT HtmlDocExtractor +{ +public: + HtmlDocExtractor(); + + void setLengthReference(const int reference, const bool truncateAtParagraph); + void setFormatContents(const bool format); + + QString getClassOrNamespaceBrief(const QString &html, const QString &name) const; + QString getClassOrNamespaceDescription(const QString &html, const QString &name) const; + QString getEnumDescription(const QString &html, const QString &name) const; + QString getTypedefDescription(const QString &html, const QString &name) const; + QString getVarDescription(const QString &html, const QString &name) const; + QString getMacroDescription(const QString &html, + const QString &mark, + const QString &anchorName) const; + QString getFunctionDescription(const QString &html, + const QString &mark, + const QString &anchorName, + const bool mainOverload = true) const; + +private: + QString assemble(const QString& elementAttr, const QString &elementTemplate) const; + QString getContentsByAnchor(const QString &html, + const QString &name, + const bool relaxedMatch) const; + QString getContentsByMarks(const QString &html, + const QString &id, + const bool mainOverload) const; + QString getClassOrNamespaceMemberDescription(const QString &html, + const QString &mark, + const QString &anchorName, + const bool mainOverload, + const bool relaxedMatch = false) const; + + QString findByMarks(const QString &html, + const QString &startMark, + const QString &endMark) const; + QString findByPattern(const QString &html, const QString &pattern) const; + + void formatContents(QString *html) const; + + static void stripAllHtml(QString *html); + static void stripHeadings(QString *html); + static void stripLinks(QString *html); + static void stripHorizontalLines(QString *html); + static void stripDivs(QString *html); + static void stripTagsStyles(QString *html); + static void stripTeletypes(QString *html); + static void replaceNonStyledHeadingsForBold(QString *html); + static void replaceTablesForSimpleLines(QString *html); + static void replaceListsForSimpleLines(QString *html); + + static QString cleanReference(const QString &reference); + + int m_lengthReference; + bool m_truncateAtParagraph; + bool m_formatContents; +}; + +} // namespace Utils + +#endif // HTMLDOCEXTRACTOR_H diff --git a/src/libs/utils/utils.pro b/src/libs/utils/utils.pro index bf2691171bae4a44f81d13308c4ec5088b9ec331..a189b6725083f21ff0b9234db75b8cfd268a8c64 100644 --- a/src/libs/utils/utils.pro +++ b/src/libs/utils/utils.pro @@ -40,7 +40,8 @@ SOURCES += reloadpromptutils.cpp \ detailswidget.cpp \ changeset.cpp \ filterlineedit.cpp \ - faketooltip.cpp + faketooltip.cpp \ + htmldocextractor.cpp win32 { SOURCES += abstractprocess_win.cpp \ consoleprocess_win.cpp \ @@ -93,7 +94,8 @@ HEADERS += utils_global.h \ detailswidget.h \ changeset.h \ filterlineedit.h \ - faketooltip.h + faketooltip.h \ + htmldocextractor.h FORMS += filewizardpage.ui \ projectintropage.ui \ newclasswidget.ui \ diff --git a/src/plugins/coreplugin/helpmanager.cpp b/src/plugins/coreplugin/helpmanager.cpp index 7d2ebc92c42ab461507742d3c6e3d9290f84ad8d..02bd88acffdce5a3766e67e8a77aac2dda90ab40 100644 --- a/src/plugins/coreplugin/helpmanager.cpp +++ b/src/plugins/coreplugin/helpmanager.cpp @@ -240,6 +240,13 @@ QUrl HelpManager::findFile(const QUrl &url) const return m_helpEngine->findFile(url); } +QByteArray HelpManager::fileData(const QUrl &url) const +{ + if (m_needsSetup) + return QByteArray(); + return m_helpEngine->fileData(url); +} + void HelpManager::handleHelpRequest(const QString &url) { emit helpRequested(QUrl(url)); diff --git a/src/plugins/coreplugin/helpmanager.h b/src/plugins/coreplugin/helpmanager.h index 7df6c55659caf6e7baf628d13eef500afe93dd5b..a3096430929486807afe3fc1920ca0dd3640d23e 100644 --- a/src/plugins/coreplugin/helpmanager.h +++ b/src/plugins/coreplugin/helpmanager.h @@ -38,6 +38,7 @@ #include <QtCore/QStringList> #include <QtCore/QUrl> #include <QtCore/QVariant> +#include <QtCore/QByteArray> QT_FORWARD_DECLARE_CLASS(QHelpEngineCore) QT_FORWARD_DECLARE_CLASS(QSqlQuery) @@ -64,6 +65,7 @@ public: QStringList findKeywords(const QString &key, int maxHits = INT_MAX) const; QUrl findFile(const QUrl &url) const; + QByteArray fileData(const QUrl &url) const; void handleHelpRequest(const QString &url); QStringList registeredNamespaces() const; diff --git a/src/plugins/cppeditor/cpphoverhandler.cpp b/src/plugins/cppeditor/cpphoverhandler.cpp index 8115032b1e13d4fedc0e89de9b664e5fabc6bac8..6d1eb8fe8148317f91dba1475acd398bde291a59 100644 --- a/src/plugins/cppeditor/cpphoverhandler.cpp +++ b/src/plugins/cppeditor/cpphoverhandler.cpp @@ -29,7 +29,6 @@ #include "cpphoverhandler.h" #include "cppeditor.h" -#include "cppplugin.h" #include <coreplugin/icore.h> #include <coreplugin/helpmanager.h> @@ -40,47 +39,67 @@ #include <texteditor/itexteditor.h> #include <texteditor/basetexteditor.h> #include <debugger/debuggerconstants.h> +#include <utils/htmldocextractor.h> -#include <CoreTypes.h> #include <FullySpecifiedType.h> -#include <Literals.h> -#include <Control.h> -#include <Names.h> #include <Scope.h> #include <Symbol.h> #include <Symbols.h> #include <cplusplus/ExpressionUnderCursor.h> #include <cplusplus/Overview.h> #include <cplusplus/TypeOfExpression.h> -#include <cplusplus/SimpleLexer.h> +#include <cplusplus/LookupContext.h> +#include <cplusplus/LookupItem.h> -#include <QtCore/QDebug> #include <QtCore/QDir> #include <QtCore/QFileInfo> -#include <QtCore/QSettings> #include <QtGui/QToolTip> #include <QtGui/QTextCursor> -#include <QtGui/QTextBlock> using namespace CppEditor::Internal; using namespace CPlusPlus; using namespace Core; +namespace { + QString removeQualificationIfAny(const QString &name) { + int index = name.lastIndexOf(QLatin1Char(':')); + if (index == -1) + return name; + else + return name.right(name.length() - index - 1); + } + + void moveCursorToEndOfQualifiedName(QTextCursor *tc) { + QTextDocument *doc = tc->document(); + if (!doc) + return; + + while (true) { + const QChar &ch = doc->characterAt(tc->position()); + if (ch.isLetterOrNumber() || ch == QLatin1Char('_')) + tc->movePosition(QTextCursor::NextCharacter); + else if (ch == QLatin1Char(':') && + doc->characterAt(tc->position() + 1) == QLatin1Char(':')) + tc->movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, 2); + else + break; + } + } +} + CppHoverHandler::CppHoverHandler(QObject *parent) - : QObject(parent) + : QObject(parent), m_modelManager(0), m_matchingHelpCandidate(-1) { - m_modelManager = ExtensionSystem::PluginManager::instance()->getObject<CppTools::CppModelManagerInterface>(); + m_modelManager = + ExtensionSystem::PluginManager::instance()->getObject<CppTools::CppModelManagerInterface>(); + + m_htmlDocExtractor.setLengthReference(1000, true); // Listen for editor opened events in order to connect to tooltip/helpid requests connect(ICore::instance()->editorManager(), SIGNAL(editorOpened(Core::IEditor *)), this, SLOT(editorOpened(Core::IEditor *))); } -void CppHoverHandler::updateContextHelpId(TextEditor::ITextEditor *editor, int pos) -{ - updateHelpIdAndTooltip(editor, pos); -} - void CppHoverHandler::editorOpened(IEditor *editor) { CPPEditorEditable *cppEditor = qobject_cast<CPPEditorEditable *>(editor); @@ -94,22 +113,56 @@ void CppHoverHandler::editorOpened(IEditor *editor) this, SLOT(updateContextHelpId(TextEditor::ITextEditor*, int))); } +void CppHoverHandler::updateContextHelpId(TextEditor::ITextEditor *editor, int pos) +{ + if (!editor) + return; + + // If the tooltip is visible and there is a help match, this match is used to update the help + // id. Otherwise, the identification process happens. + if (!QToolTip::isVisible() || m_matchingHelpCandidate == -1) + identifyMatch(editor, pos); + + if (m_matchingHelpCandidate != -1) + editor->setContextHelpId(m_helpCandidates.at(m_matchingHelpCandidate).m_helpId); + else + editor->setContextHelpId(QString()); +} + void CppHoverHandler::showToolTip(TextEditor::ITextEditor *editor, const QPoint &point, int pos) { if (!editor) return; - ICore *core = ICore::instance(); - const int dbgcontext = core->uniqueIDManager()->uniqueIdentifier(Debugger::Constants::C_DEBUGMODE); + editor->setContextHelpId(QString()); - if (core->hasContext(dbgcontext)) + ICore *core = ICore::instance(); + const int dbgContext = + core->uniqueIDManager()->uniqueIdentifier(Debugger::Constants::C_DEBUGMODE); + if (core->hasContext(dbgContext)) return; - updateHelpIdAndTooltip(editor, pos); + identifyMatch(editor, pos); - if (m_toolTip.isEmpty()) + if (m_toolTip.isEmpty()) { QToolTip::hideText(); - else { + } else { + if (m_matchingHelpCandidate != -1) { + const QString &contents = getDocContents(); + if (!contents.isEmpty()) { + m_toolTip = contents; + } else { + m_toolTip = Qt::escape(m_toolTip); + m_toolTip.prepend(QLatin1String("<nobr>")); + m_toolTip.append(QLatin1String("</nobr>")); + } + + m_toolTip = QString(QLatin1String("<table><tr>" + "<td valign=middle>%1</td>" + "<td><img src=\":/cppeditor/images/f1.png\"></td>" + "</tr></table>")).arg(m_toolTip); + } + const QPoint pnt = point - QPoint(0, #ifdef Q_WS_WIN 24 @@ -122,210 +175,239 @@ void CppHoverHandler::showToolTip(TextEditor::ITextEditor *editor, const QPoint } } -static QString buildHelpId(Symbol *symbol, const Name *declarationName) +void CppHoverHandler::identifyMatch(TextEditor::ITextEditor *editor, int pos) { - Scope *scope = 0; + resetMatchings(); - if (symbol) { - scope = symbol->scope(); - declarationName = symbol->name(); - } + if (!m_modelManager) + return; - if (! declarationName) - return QString(); + const Snapshot &snapshot = m_modelManager->snapshot(); + Document::Ptr doc = snapshot.document(editor->file()->fileName()); + if (!doc) + return; - Overview overview; - overview.setShowArgumentNames(false); - overview.setShowReturnTypes(false); + int line = 0; + int column = 0; + editor->convertPosition(pos, &line, &column); - QStringList qualifiedNames; - qualifiedNames.prepend(overview.prettyName(declarationName)); + if (!matchDiagnosticMessage(doc, line) && + !matchIncludeFile(doc, line) && + !matchMacroInUse(doc, pos)) { - for (; scope; scope = scope->enclosingScope()) { - Symbol *owner = scope->owner(); + TextEditor::BaseTextEditor *baseEditor = baseTextEditor(editor); + if (!baseEditor) + return; - if (owner && owner->name() && ! scope->isEnumScope()) { - const Name *name = owner->name(); - const Identifier *id = 0; + bool extraSelectionTooltip = false; + if (!baseEditor->extraSelectionTooltip(pos).isEmpty()) { + m_toolTip = baseEditor->extraSelectionTooltip(pos); + extraSelectionTooltip = true; + } - if (const NameId *nameId = name->asNameId()) - id = nameId->identifier(); + QTextCursor tc(baseEditor->document()); + tc.setPosition(pos); + moveCursorToEndOfQualifiedName(&tc); - else if (const TemplateNameId *nameId = name->asTemplateNameId()) - id = nameId->identifier(); + // Fetch the expression's code + ExpressionUnderCursor expressionUnderCursor; + const QString &expression = expressionUnderCursor(tc); + Scope *scope = doc->scopeAt(line, column); - if (id) - qualifiedNames.prepend(QString::fromLatin1(id->chars(), id->size())); - } + TypeOfExpression typeOfExpression; + typeOfExpression.init(doc, snapshot); + const QList<LookupItem> &lookupItems = typeOfExpression(expression, scope); + if (lookupItems.isEmpty()) + return; + + const LookupItem &lookupItem = lookupItems.first(); // ### TODO: select the best candidate. + handleLookupItemMatch(lookupItem, !extraSelectionTooltip); } - return qualifiedNames.join(QLatin1String("::")); + evaluateHelpCandidates(); } -void CppHoverHandler::updateHelpIdAndTooltip(TextEditor::ITextEditor *editor, int pos) +bool CppHoverHandler::matchDiagnosticMessage(const CPlusPlus::Document::Ptr &document, + const int line) { - m_helpId.clear(); - m_toolTip.clear(); - - if (!m_modelManager) - return; - - TextEditor::BaseTextEditor *edit = qobject_cast<TextEditor::BaseTextEditor *>(editor->widget()); - if (!edit) - return; - - const Snapshot snapshot = m_modelManager->snapshot(); - const QString fileName = editor->file()->fileName(); - Document::Ptr doc = snapshot.document(fileName); - if (!doc) - return; // nothing to do - - QString formatTooltip = edit->extraSelectionTooltip(pos); - QTextCursor tc(edit->document()); - tc.setPosition(pos); - - const unsigned lineNumber = tc.block().blockNumber() + 1; - - // Find the last symbol up to the cursor position - int line = 0, column = 0; - editor->convertPosition(tc.position(), &line, &column); - Scope *scope = doc->scopeAt(line, column); - - TypeOfExpression typeOfExpression; - typeOfExpression.init(doc, snapshot); - - // We only want to show F1 if the tooltip matches the help id - bool showF1 = true; - - foreach (const Document::DiagnosticMessage &m, doc->diagnosticMessages()) { - if (m.line() == lineNumber) { + foreach (const Document::DiagnosticMessage &m, document->diagnosticMessages()) { + if (m.line() == line) { m_toolTip = m.text(); - showF1 = false; - break; + return true; } } + return false; +} - QMap<QString, QUrl> helpLinks; - if (m_toolTip.isEmpty()) { - foreach (const Document::Include &incl, doc->includes()) { - if (incl.line() == lineNumber) { - m_toolTip = QDir::toNativeSeparators(incl.fileName()); - m_helpId = QFileInfo(incl.fileName()).fileName(); - helpLinks = Core::HelpManager::instance()->linksForIdentifier(m_helpId); - break; - } +bool CppHoverHandler::matchIncludeFile(const CPlusPlus::Document::Ptr &document, const int line) +{ + foreach (const Document::Include &includeFile, document->includes()) { + if (includeFile.line() == line) { + m_toolTip = QDir::toNativeSeparators(includeFile.fileName()); + const QString &fileName = QFileInfo(includeFile.fileName()).fileName(); + m_helpCandidates.append(HelpCandidate(fileName, fileName, HelpCandidate::Include)); + return true; } } + return false; +} - if (m_helpId.isEmpty()) { - // Move to the end of a qualified name - bool stop = false; - while (!stop) { - const QChar ch = editor->characterAt(tc.position()); - if (ch.isLetterOrNumber() || ch == QLatin1Char('_')) - tc.setPosition(tc.position() + 1); - else if (ch == QLatin1Char(':') && editor->characterAt(tc.position() + 1) == QLatin1Char(':')) { - tc.setPosition(tc.position() + 2); - } else { - stop = true; - } +bool CppHoverHandler::matchMacroInUse(const CPlusPlus::Document::Ptr &document, const int pos) +{ + foreach (const Document::MacroUse &use, document->macroUses()) { + if (use.contains(pos)) { + const Macro& macro = use.macro(); + m_toolTip = macro.toString(); + m_helpCandidates.append(HelpCandidate(macro.name(), + macro.name(), + HelpCandidate::Macro)); + return true; } + } + return false; +} - // Fetch the expression's code - ExpressionUnderCursor expressionUnderCursor; - const QString expression = expressionUnderCursor(tc); - - const QList<LookupItem> types = typeOfExpression(expression, scope); - - - if (!types.isEmpty()) { - Overview overview; - overview.setShowArgumentNames(true); - overview.setShowReturnTypes(true); - overview.setShowFullyQualifiedNamed(true); - - const LookupItem result = types.first(); // ### TODO: select the best candidate. - FullySpecifiedType symbolTy = result.type(); // result of `type of expression'. - Symbol *declaration = result.declaration(); // lookup symbol - const Name *declarationName = declaration ? declaration->name() : 0; +void CppHoverHandler::handleLookupItemMatch(const LookupItem &lookupItem, const bool assignTooltip) +{ + Symbol *matchingDeclaration = lookupItem.declaration(); + FullySpecifiedType matchingType = lookupItem.type(); - if (declaration && declaration->scope() - && declaration->scope()->isClassScope()) { - Class *enclosingClass = declaration->scope()->owner()->asClass(); - if (const Identifier *id = enclosingClass->identifier()) { - if (id->isEqualTo(declaration->identifier())) - declaration = enclosingClass; - } + Overview overview; + overview.setShowArgumentNames(true); + overview.setShowReturnTypes(true); + overview.setShowFullyQualifiedNamed(true); + + if (!matchingDeclaration && assignTooltip) { + m_toolTip = overview.prettyType(matchingType, QLatin1String("")); + } else { + QString qualifiedName; + HelpCandidate::Category helpCategory; + if (matchingDeclaration->enclosingSymbol()->isClass() || + matchingDeclaration->enclosingSymbol()->isNamespace() || + matchingDeclaration->enclosingSymbol()->isEnum()) { + // Fully qualify the name if enclosed by a class, namespace or enum. + QList<const Name *> names = LookupContext::fullyQualifiedName(matchingDeclaration); + if (matchingDeclaration->isNamespace() || + matchingDeclaration->isClass() || + matchingDeclaration->isForwardClassDeclaration()) { + // In this case the declaration name appears in the fully qualified name. Remove + // it since it is already considered below. + names.removeLast(); + helpCategory = HelpCandidate::ClassOrNamespace; + } else if (matchingDeclaration->isEnum()) { + helpCategory = HelpCandidate::Enum; + } else if (matchingDeclaration->isTypedef()) { + helpCategory = HelpCandidate::Typedef; + } else if (matchingDeclaration->isStatic() && !matchingDeclaration->isFunction()) { + helpCategory = HelpCandidate::Var; + } else { + helpCategory = HelpCandidate::Function; } - - m_helpId = buildHelpId(declaration, declarationName); - - if (m_toolTip.isEmpty()) { - Symbol *symbol = declaration; - - if (declaration) - symbol = declaration; - - if (symbol && symbol == declaration && symbol->isClass()) { - m_toolTip = m_helpId; - - } else if (declaration && (declaration->isDeclaration() || declaration->isArgument())) { - m_toolTip = overview.prettyType(symbolTy, buildHelpId(declaration, declaration->name())); - - } else if (symbolTy->isClassType() || symbolTy->isEnumType() || - symbolTy->isForwardClassDeclarationType()) { - m_toolTip = m_helpId; - - } else { - m_toolTip = overview.prettyType(symbolTy, m_helpId); - - } + foreach (const Name *name, names) { + qualifiedName.append(overview.prettyName(name)); + qualifiedName.append(QLatin1String("::")); } - - // Some docs don't contain the namespace in the documentation pages, for instance - // there is QtMobility::QContactManager but the help page is for QContactManager. - // To show their help anyway, try stripping scopes until we find something. - const QString startHelpId = m_helpId; - while (!m_helpId.isEmpty()) { - helpLinks = Core::HelpManager::instance()->linksForIdentifier(m_helpId); - if (!helpLinks.isEmpty()) - break; - - int coloncolonIndex = m_helpId.indexOf(QLatin1String("::")); - if (coloncolonIndex == -1) { - m_helpId = startHelpId; - break; - } - - m_helpId.remove(0, coloncolonIndex + 2); + } + qualifiedName.append(overview.prettyName(matchingDeclaration->name())); + + if (assignTooltip) { + if (matchingDeclaration->isClass() || + matchingDeclaration->isNamespace() || + matchingDeclaration->isForwardClassDeclaration() || + matchingDeclaration->isEnum()) { + m_toolTip = qualifiedName; + } else { + m_toolTip = overview.prettyType(matchingType, qualifiedName); } } + + // Help identifiers are simply the name with no signature, arguments or return type. + // They might or might not include a qualification. This is why two candidates are + // created. + overview.setShowArgumentNames(false); + overview.setShowReturnTypes(false); + overview.setShowFunctionSignatures(false); + overview.setShowFullyQualifiedNamed(false); + const QString &simpleName = overview.prettyName(matchingDeclaration->name()); + overview.setShowFunctionSignatures(true); + const QString &specifierId = overview.prettyType(matchingType, simpleName); + + m_helpCandidates.append(HelpCandidate(simpleName, specifierId, helpCategory)); + m_helpCandidates.append(HelpCandidate(qualifiedName, specifierId, helpCategory)); } +} - if (m_toolTip.isEmpty()) { - foreach (const Document::MacroUse &use, doc->macroUses()) { - if (use.contains(pos)) { - const Macro m = use.macro(); - m_toolTip = m.toString(); - m_helpId = m.name(); - break; - } +void CppHoverHandler::evaluateHelpCandidates() +{ + for (int i = 0; i < m_helpCandidates.size(); ++i) { + if (helpIdExists(m_helpCandidates.at(i).m_helpId)) { + m_matchingHelpCandidate = i; + return; } } +} - if (!formatTooltip.isEmpty()) - m_toolTip = formatTooltip; +bool CppHoverHandler::helpIdExists(const QString &helpId) const +{ + QMap<QString, QUrl> helpLinks = Core::HelpManager::instance()->linksForIdentifier(helpId); + if (!helpLinks.isEmpty()) + return true; + return false; +} - if (!m_helpId.isEmpty() && !helpLinks.isEmpty()) { - if (showF1) { - // we need the original width without escape sequences - const int width = QFontMetrics(QToolTip::font()).width(m_toolTip); - m_toolTip = QString(QLatin1String("<table><tr><td valign=middle width=%2>%1</td>" - "<td><img src=\":/cppeditor/images/f1.png\"></td></tr></table>")) - .arg(Qt::escape(m_toolTip)).arg(width); +QString CppHoverHandler::getDocContents() +{ + Q_ASSERT(m_matchingHelpCandidate >= 0); + + QString contents; + const HelpCandidate &help = m_helpCandidates.at(m_matchingHelpCandidate); + QMap<QString, QUrl> helpLinks = + Core::HelpManager::instance()->linksForIdentifier(help.m_helpId); + foreach (const QUrl &url, helpLinks) { + // The help id might or might not be qualified. But anchors and marks are not qualified. + const QString &name = removeQualificationIfAny(help.m_helpId); + const QByteArray &html = Core::HelpManager::instance()->fileData(url); + switch (help.m_category) { + case HelpCandidate::Include: + contents = m_htmlDocExtractor.getClassOrNamespaceBrief(html, name); + break; + case HelpCandidate::ClassOrNamespace: + contents = m_htmlDocExtractor.getClassOrNamespaceDescription(html, name); + break; + case HelpCandidate::Function: + contents = + m_htmlDocExtractor.getFunctionDescription(html, help.m_markId, name); + break; + case HelpCandidate::Enum: + contents = m_htmlDocExtractor.getEnumDescription(html, name); + break; + case HelpCandidate::Typedef: + contents = m_htmlDocExtractor.getTypedefDescription(html, name); + break; + case HelpCandidate::Var: + contents = m_htmlDocExtractor.getVarDescription(html, name); + break; + case HelpCandidate::Macro: + contents = m_htmlDocExtractor.getMacroDescription(html, help.m_markId, name); + break; + default: + break; } - editor->setContextHelpId(m_helpId); - } else if (!m_toolTip.isEmpty() && Qt::mightBeRichText(m_toolTip)) { - m_toolTip = QString(QLatin1String("<nobr>%1</nobr>")).arg(Qt::escape(m_toolTip)); + + if (!contents.isEmpty()) + break; } + return contents; +} + +void CppHoverHandler::resetMatchings() +{ + m_matchingHelpCandidate = -1; + m_helpCandidates.clear(); + m_toolTip.clear(); +} + +TextEditor::BaseTextEditor *CppHoverHandler::baseTextEditor(TextEditor::ITextEditor *editor) +{ + return qobject_cast<TextEditor::BaseTextEditor *>(editor->widget()); } diff --git a/src/plugins/cppeditor/cpphoverhandler.h b/src/plugins/cppeditor/cpphoverhandler.h index 311b82922361c0c7a6c4956d900ec40467f505c1..3ba7177c15aa647e62588dcb999bd9d62a08e2f9 100644 --- a/src/plugins/cppeditor/cpphoverhandler.h +++ b/src/plugins/cppeditor/cpphoverhandler.h @@ -30,12 +30,21 @@ #ifndef CPPHOVERHANDLER_H #define CPPHOVERHANDLER_H +#include <utils/htmldocextractor.h> + #include <QtCore/QObject> +#include <QtCore/QList> + +#include <cplusplus/CppDocument.h> QT_BEGIN_NAMESPACE class QPoint; QT_END_NAMESPACE +namespace CPlusPlus { +class LookupItem; +} + namespace Core { class IEditor; } @@ -46,6 +55,7 @@ class CppModelManagerInterface; namespace TextEditor { class ITextEditor; +class BaseTextEditor; } namespace CppEditor { @@ -54,7 +64,6 @@ namespace Internal { class CppHoverHandler : public QObject { Q_OBJECT - public: CppHoverHandler(QObject *parent = 0); @@ -66,11 +75,45 @@ private slots: void editorOpened(Core::IEditor *editor); private: - void updateHelpIdAndTooltip(TextEditor::ITextEditor *editor, int pos); + struct HelpCandidate + { + enum Category { + ClassOrNamespace, + Enum, + Typedef, + Var, + Macro, + Include, + Function + }; + + HelpCandidate(const QString &helpId, const QString &markId, Category category) : + m_helpId(helpId), m_markId(markId), m_category(category) + {} + QString m_helpId; + QString m_markId; + Category m_category; + }; + + void resetMatchings(); + void identifyMatch(TextEditor::ITextEditor *editor, int pos); + bool matchDiagnosticMessage(const CPlusPlus::Document::Ptr &document, const int line); + bool matchIncludeFile(const CPlusPlus::Document::Ptr &document, const int line); + bool matchMacroInUse(const CPlusPlus::Document::Ptr &document, const int pos); + void handleLookupItemMatch(const CPlusPlus::LookupItem &lookupItem, + const bool assignTooltip); + + void evaluateHelpCandidates(); + bool helpIdExists(const QString &helpId) const; + QString getDocContents(); + + static TextEditor::BaseTextEditor *baseTextEditor(TextEditor::ITextEditor *editor); CppTools::CppModelManagerInterface *m_modelManager; - QString m_helpId; + int m_matchingHelpCandidate; + QList<HelpCandidate> m_helpCandidates; QString m_toolTip; + Utils::HtmlDocExtractor m_htmlDocExtractor; }; } // namespace Internal