Clang: Provide tooltips

This includes also the query data for the help system (F1) for an
identifier under cursor.

Regressions (libclang changes necessary):
 - Function signatures do not contain default values.
 - Aliases are not resolved for/at:
   - template types
   - qualified name of a type

Fixes/Improvements:
 - Resolve "auto"
 - On a template type, show also the template parameter.
 - For a typedef like
     typedef long long superlong;
   the tooltip was "long long superlong", which was confusing.
   Now, "long long" is shown.

New:
 - Show first or \brief paragraph of a documentation comment.
 - Show size of a class at definition.
 - Show size of a field member in class definition.

Task-number: QTCREATORBUG-11259
Change-Id: Ie1a07930d0e882015d07dc43e35bb81a685cdeb8
Reviewed-by: Marco Bubke <marco.bubke@qt.io>
This commit is contained in:
Nikolai Kosjar
2018-01-12 12:29:43 +01:00
parent 7872ddde4c
commit 76c25bcd6a
71 changed files with 2824 additions and 112 deletions

View File

@@ -392,6 +392,15 @@ QFuture<CppTools::CursorInfo> BackendCommunicator::requestLocalReferences(
return m_receiver.addExpectedReferencesMessage(message.ticketNumber(), textDocument);
}
QFuture<CppTools::ToolTipInfo> BackendCommunicator::requestToolTip(
const FileContainer &fileContainer, quint32 line, quint32 column)
{
const RequestToolTipMessage message(fileContainer, line, column);
m_sender->requestToolTip(message);
return m_receiver.addExpectedToolTipMessage(message.ticketNumber());
}
QFuture<CppTools::SymbolInfo> BackendCommunicator::requestFollowSymbol(
const FileContainer &curFileContainer,
quint32 line,

View File

@@ -82,6 +82,9 @@ public:
quint32 line,
quint32 column,
QTextDocument *textDocument);
QFuture<CppTools::ToolTipInfo> requestToolTip(const FileContainer &fileContainer,
quint32 line,
quint32 column);
QFuture<CppTools::SymbolInfo> requestFollowSymbol(const FileContainer &curFileContainer,
quint32 line,
quint32 column);

View File

@@ -128,6 +128,18 @@ QFuture<CppTools::SymbolInfo> BackendReceiver::addExpectedRequestFollowSymbolMes
return futureInterface.future();
}
QFuture<CppTools::ToolTipInfo> BackendReceiver::addExpectedToolTipMessage(quint64 ticket)
{
QTC_CHECK(!m_toolTipsTable.contains(ticket));
QFutureInterface<CppTools::ToolTipInfo> futureInterface;
futureInterface.reportStarted();
m_toolTipsTable.insert(ticket, futureInterface);
return futureInterface.future();
}
bool BackendReceiver::isExpectingCodeCompletedMessage() const
{
return !m_assistProcessorsTable.isEmpty();
@@ -272,6 +284,72 @@ void BackendReceiver::references(const ReferencesMessage &message)
futureInterface.reportFinished();
}
static TextEditor::HelpItem::Category toHelpItemCategory(ToolTipInfo::QdocCategory category)
{
switch (category) {
case ToolTipInfo::Unknown:
return TextEditor::HelpItem::Unknown;
case ToolTipInfo::ClassOrNamespace:
return TextEditor::HelpItem::ClassOrNamespace;
case ToolTipInfo::Enum:
return TextEditor::HelpItem::Enum;
case ToolTipInfo::Typedef:
return TextEditor::HelpItem::Typedef;
case ToolTipInfo::Macro:
return TextEditor::HelpItem::Macro;
case ToolTipInfo::Brief:
return TextEditor::HelpItem::Brief;
case ToolTipInfo::Function:
return TextEditor::HelpItem::Function;
}
return TextEditor::HelpItem::Unknown;
}
static QStringList toStringList(const Utf8StringVector &utf8StringVector)
{
QStringList list;
list.reserve(utf8StringVector.size());
for (const Utf8String &utf8String : utf8StringVector)
list << utf8String.toString();
return list;
}
static CppTools::ToolTipInfo toToolTipInfo(const ToolTipMessage &message)
{
CppTools::ToolTipInfo info;
const ToolTipInfo backendInfo = message.toolTipInfo();
info.text = backendInfo.text();
info.briefComment = backendInfo.briefComment();
info.qDocIdCandidates = toStringList(backendInfo.qdocIdCandidates());
info.qDocMark = backendInfo.qdocMark();
info.qDocCategory = toHelpItemCategory(backendInfo.qdocCategory());
info.sizeInBytes = backendInfo.sizeInBytes();
return info;
}
void BackendReceiver::tooltip(const ToolTipMessage &message)
{
qCDebugIpc() << "ToolTipMessage" << message.toolTipInfo().text();
const quint64 ticket = message.ticketNumber();
QFutureInterface<CppTools::ToolTipInfo> futureInterface = m_toolTipsTable.take(ticket);
QTC_CHECK(futureInterface != QFutureInterface<CppTools::ToolTipInfo>());
if (futureInterface.isCanceled())
return; // A new request was issued making this one outdated.
futureInterface.reportResult(toToolTipInfo(message));
futureInterface.reportFinished();
}
void BackendReceiver::followSymbol(const ClangBackEnd::FollowSymbolMessage &message)
{
qCDebugIpc() << "FollowSymbolMessage with"

View File

@@ -27,6 +27,7 @@
#include <cpptools/cppcursorinfo.h>
#include <cpptools/cppsymbolinfo.h>
#include <cpptools/baseeditordocumentprocessor.h>
#include <clangsupport/clangcodemodelclientinterface.h>
@@ -59,6 +60,7 @@ public:
const CppTools::SemanticInfo::LocalUseMap &localUses
= CppTools::SemanticInfo::LocalUseMap());
QFuture<CppTools::SymbolInfo> addExpectedRequestFollowSymbolMessage(quint64 ticket);
QFuture<CppTools::ToolTipInfo> addExpectedToolTipMessage(quint64 ticket);
bool isExpectingCodeCompletedMessage() const;
void reset();
@@ -70,6 +72,7 @@ private:
void documentAnnotationsChanged(const ClangBackEnd::DocumentAnnotationsChangedMessage &message) override;
void references(const ClangBackEnd::ReferencesMessage &message) override;
void tooltip(const ClangBackEnd::ToolTipMessage &message) override;
void followSymbol(const ClangBackEnd::FollowSymbolMessage &message) override;
private:
@@ -89,7 +92,7 @@ private:
CppTools::SemanticInfo::LocalUseMap localUses;
};
QHash<quint64, ReferencesEntry> m_referencesTable;
QHash<quint64, QFutureInterface<CppTools::ToolTipInfo>> m_toolTipsTable;
QHash<quint64, QFutureInterface<CppTools::SymbolInfo>> m_followTable;
};

View File

@@ -120,6 +120,13 @@ void BackendSender::requestReferences(const RequestReferencesMessage &message)
m_connection->serverProxy().requestReferences(message);
}
void BackendSender::requestToolTip(const RequestToolTipMessage &message)
{
QTC_CHECK(m_connection->isConnected());
qCDebug(ipcLog) << ">>>" << message;
m_connection->serverProxy().requestToolTip(message);
}
void BackendSender::requestFollowSymbol(const RequestFollowSymbolMessage &message)
{
QTC_CHECK(m_connection->isConnected());

View File

@@ -48,6 +48,7 @@ public:
void completeCode(const ClangBackEnd::CompleteCodeMessage &message) override;
void requestDocumentAnnotations(const ClangBackEnd::RequestDocumentAnnotationsMessage &message) override;
void requestReferences(const ClangBackEnd::RequestReferencesMessage &message) override;
void requestToolTip(const ClangBackEnd::RequestToolTipMessage &message) override;
void requestFollowSymbol(const ClangBackEnd::RequestFollowSymbolMessage &message) override;
void updateVisibleTranslationUnits(const ClangBackEnd::UpdateVisibleTranslationUnitsMessage &message) override;

View File

@@ -30,6 +30,7 @@ SOURCES += \
clangfixitoperationsextractor.cpp \
clangfollowsymbol.cpp \
clangfunctionhintmodel.cpp \
clanghoverhandler.cpp \
clangtokeninfosreporter.cpp \
clangmodelmanagersupport.cpp \
clangpreprocessorassistproposalitem.cpp \
@@ -66,6 +67,7 @@ HEADERS += \
clangfixitoperationsextractor.h \
clangfollowsymbol.h \
clangfunctionhintmodel.h \
clanghoverhandler.h \
clangisdiagnosticrelatedtolocation.h \
clangmodelmanagersupport.h \
clangpreprocessorassistproposalitem.h \

View File

@@ -80,6 +80,8 @@ QtcPlugin {
"clangfollowsymbol.h",
"clangfunctionhintmodel.cpp",
"clangfunctionhintmodel.h",
"clanghoverhandler.cpp",
"clanghoverhandler.h",
"clangtokeninfosreporter.cpp",
"clangtokeninfosreporter.h",
"clangisdiagnosticrelatedtolocation.h",

View File

@@ -375,6 +375,15 @@ ClangEditorDocumentProcessor::requestFollowSymbol(int line, int column)
static_cast<quint32>(column));
}
QFuture<CppTools::ToolTipInfo> ClangEditorDocumentProcessor::toolTipInfo(const QByteArray &codecName,
int line,
int column)
{
return m_communicator.requestToolTip(simpleFileContainer(codecName),
static_cast<quint32>(line),
static_cast<quint32>(column));
}
ClangBackEnd::FileContainer ClangEditorDocumentProcessor::fileContainerWithArguments() const
{
return fileContainerWithArguments(m_projectPart.data());
@@ -480,13 +489,19 @@ ClangEditorDocumentProcessor::creatorForHeaderErrorDiagnosticWidget(
};
}
ClangBackEnd::FileContainer ClangEditorDocumentProcessor::simpleFileContainer() const
ClangBackEnd::FileContainer ClangEditorDocumentProcessor::simpleFileContainer(
const QByteArray &codecName) const
{
Utf8String projectPartId;
if (m_projectPart)
projectPartId = m_projectPart->id();
return ClangBackEnd::FileContainer(filePath(), projectPartId, Utf8String(), false, revision());
return ClangBackEnd::FileContainer(filePath(),
projectPartId,
Utf8String(),
false,
revision(),
Utf8String::fromByteArray(codecName));
}
static CppTools::ProjectPart projectPartForLanguageOption(CppTools::ProjectPart *projectPart)

View File

@@ -88,6 +88,9 @@ public:
QFuture<CppTools::CursorInfo> cursorInfo(const CppTools::CursorInfoParams &params) override;
QFuture<CppTools::CursorInfo> requestLocalReferences(const QTextCursor &cursor) override;
QFuture<CppTools::SymbolInfo> requestFollowSymbol(int line, int column) override;
QFuture<CppTools::ToolTipInfo> toolTipInfo(const QByteArray &codecName,
int line,
int column) override;
ClangBackEnd::FileContainer fileContainerWithArguments() const;
@@ -106,7 +109,7 @@ private:
void requestDocumentAnnotations(const QString &projectpartId);
HeaderErrorDiagnosticWidgetCreator creatorForHeaderErrorDiagnosticWidget(
const ClangBackEnd::DiagnosticContainer &firstHeaderErrorDiagnostic);
ClangBackEnd::FileContainer simpleFileContainer() const;
ClangBackEnd::FileContainer simpleFileContainer(const QByteArray &codecName = QByteArray()) const;
ClangBackEnd::FileContainer fileContainerWithArguments(CppTools::ProjectPart *projectPart) const;
ClangBackEnd::FileContainer fileContainerWithArgumentsAndDocumentContent(
CppTools::ProjectPart *projectPart) const;

View File

@@ -0,0 +1,232 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "clanghoverhandler.h"
#include <coreplugin/helpmanager.h>
#include <cpptools/baseeditordocumentprocessor.h>
#include <cpptools/cppmodelmanager.h>
#include <cpptools/editordocumenthandle.h>
#include <texteditor/texteditor.h>
#include <utils/qtcassert.h>
#include <utils/textutils.h>
#include <utils/tooltip/tooltip.h>
#include <QFutureWatcher>
#include <QLoggingCategory>
#include <QTextCodec>
#include <QVBoxLayout>
Q_LOGGING_CATEGORY(hoverLog, "qtc.clangcodemodel.hover");
using namespace TextEditor;
namespace ClangCodeModel {
namespace Internal {
static CppTools::BaseEditorDocumentProcessor *editorDocumentProcessor(TextEditorWidget *editorWidget)
{
const QString filePath = editorWidget->textDocument()->filePath().toString();
auto cppModelManager = CppTools::CppModelManager::instance();
CppTools::CppEditorDocumentHandle *editorHandle = cppModelManager->cppEditorDocument(filePath);
if (editorHandle)
return editorHandle->processor();
return 0;
}
static bool editorDocumentProcessorHasDiagnosticAt(TextEditorWidget *editorWidget, int pos)
{
if (CppTools::BaseEditorDocumentProcessor *processor = editorDocumentProcessor(editorWidget)) {
int line, column;
if (Utils::Text::convertPosition(editorWidget->document(), pos, &line, &column))
return processor->hasDiagnosticsAt(line, column);
}
return false;
}
static void processWithEditorDocumentProcessor(TextEditorWidget *editorWidget,
const QPoint &point,
int position,
const QString &helpId)
{
if (CppTools::BaseEditorDocumentProcessor *processor = editorDocumentProcessor(editorWidget)) {
int line, column;
if (Utils::Text::convertPosition(editorWidget->document(), position, &line, &column)) {
auto layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(2);
processor->addDiagnosticToolTipToLayout(line, column, layout);
Utils::ToolTip::show(point, layout, editorWidget, helpId);
}
}
}
static QFuture<CppTools::ToolTipInfo> editorDocumentHandlesToolTipInfo(
TextEditorWidget *editorWidget, int pos)
{
const QByteArray textCodecName = editorWidget->textDocument()->codec()->name();
if (CppTools::BaseEditorDocumentProcessor *processor = editorDocumentProcessor(editorWidget)) {
int line, column;
if (Utils::Text::convertPosition(editorWidget->document(), pos, &line, &column))
return processor->toolTipInfo(textCodecName, line, column + 1);
}
return QFuture<CppTools::ToolTipInfo>();
}
ClangHoverHandler::ClangHoverHandler()
{
setIsAsyncHandler(true);
}
ClangHoverHandler::~ClangHoverHandler()
{
cancelAsyncCheck();
}
void ClangHoverHandler::identifyMatchAsync(TextEditorWidget *editorWidget,
int pos,
BaseHoverHandler::ReportPriority report)
{
// Reset
m_futureWatcher.reset();
m_cursorPosition = -1;
// Check for diagnostics (sync)
if (editorDocumentProcessorHasDiagnosticAt(editorWidget, pos)) {
qCDebug(hoverLog) << "Checking for diagnostic at" << pos;
setPriority(Priority_Diagnostic);
m_cursorPosition = pos;
report(priority());
return;
}
// Check for tooltips (async)
QFuture<CppTools::ToolTipInfo> future = editorDocumentHandlesToolTipInfo(editorWidget, pos);
if (QTC_GUARD(future.isRunning())) {
qCDebug(hoverLog) << "Requesting tooltip info at" << pos;
m_reportPriority = report;
m_futureWatcher.reset(new QFutureWatcher<CppTools::ToolTipInfo>());
QObject::connect(m_futureWatcher.data(), &QFutureWatcherBase::finished, [this]() {
processToolTipInfo(m_futureWatcher->result());
});
m_futureWatcher->setFuture(future);
return;
}
report(Priority_None); // Ops, something went wrong.
}
void ClangHoverHandler::cancelAsyncCheck()
{
if (m_futureWatcher)
m_futureWatcher->cancel();
}
#define RETURN_TEXT_FOR_CASE(enumValue) case TextEditor::HelpItem::enumValue: return #enumValue
static const char *helpItemCategoryAsString(TextEditor::HelpItem::Category category)
{
switch (category) {
RETURN_TEXT_FOR_CASE(Unknown);
RETURN_TEXT_FOR_CASE(ClassOrNamespace);
RETURN_TEXT_FOR_CASE(Enum);
RETURN_TEXT_FOR_CASE(Typedef);
RETURN_TEXT_FOR_CASE(Macro);
RETURN_TEXT_FOR_CASE(Brief);
RETURN_TEXT_FOR_CASE(Function);
RETURN_TEXT_FOR_CASE(QmlComponent);
RETURN_TEXT_FOR_CASE(QmlProperty);
RETURN_TEXT_FOR_CASE(QMakeVariableOfFunction);
}
return "UnhandledHelpItemCategory";
}
#undef RETURN_TEXT_FOR_CASE
void ClangHoverHandler::processToolTipInfo(const CppTools::ToolTipInfo &info)
{
qCDebug(hoverLog) << "Processing tooltip info" << info.text;
QString text = info.text;
if (!info.briefComment.isEmpty())
text.append("\n\n" + info.briefComment);
for (const QString &qdocIdCandidate : info.qDocIdCandidates) {
qCDebug(hoverLog) << "Querying help manager with"
<< qdocIdCandidate
<< info.qDocMark
<< helpItemCategoryAsString(info.qDocCategory);
const QMap<QString, QUrl> helpLinks = Core::HelpManager::linksForIdentifier(qdocIdCandidate);
if (!helpLinks.isEmpty()) {
qCDebug(hoverLog) << " Match!";
setLastHelpItemIdentified(
HelpItem(qdocIdCandidate, info.qDocMark, info.qDocCategory, helpLinks));
break;
}
}
if (!info.sizeInBytes.isEmpty())
text.append(tr("\n\n%1 bytes").arg(info.sizeInBytes));
setToolTip(text);
m_reportPriority(priority());
}
void ClangHoverHandler::decorateToolTip()
{
if (priority() == Priority_Diagnostic)
return;
if (Qt::mightBeRichText(toolTip()))
setToolTip(toolTip().toHtmlEscaped());
const HelpItem &help = lastHelpItemIdentified();
if (help.isValid()) {
const QString text = CppTools::CppHoverHandler::tooltipTextForHelpItem(help);
if (!text.isEmpty())
setToolTip(text);
}
}
void ClangHoverHandler::operateTooltip(TextEditor::TextEditorWidget *editorWidget,
const QPoint &point)
{
if (priority() == Priority_Diagnostic) {
const HelpItem helpItem = lastHelpItemIdentified();
const QString helpId = helpItem.isValid() ? helpItem.helpId() : QString();
processWithEditorDocumentProcessor(editorWidget, point, m_cursorPosition, helpId);
return;
}
// Priority_Tooltip / Priority_Help
BaseHoverHandler::operateTooltip(editorWidget, point);
}
} // namespace Internal
} // namespace ClangCodeModel

View File

@@ -0,0 +1,60 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <cpptools/baseeditordocumentprocessor.h>
#include <cpptools/cpphoverhandler.h>
#include <texteditor/basehoverhandler.h>
namespace ClangCodeModel {
namespace Internal {
class ClangHoverHandler : public TextEditor::BaseHoverHandler
{
Q_DECLARE_TR_FUNCTIONS(ClangHoverHandler)
public:
ClangHoverHandler();
~ClangHoverHandler() override;
void identifyMatchAsync(TextEditor::TextEditorWidget *editorWidget,
int pos,
ReportPriority report) override;
void decorateToolTip() override;
void operateTooltip(TextEditor::TextEditorWidget *editorWidget, const QPoint &point) override;
private:
void cancelAsyncCheck() override;
void processToolTipInfo(const CppTools::ToolTipInfo &info);
private:
int m_cursorPosition = -1;
QScopedPointer<QFutureWatcher<CppTools::ToolTipInfo>> m_futureWatcher;
ReportPriority m_reportPriority;
};
} // namespace Internal
} // namespace ClangCodeModel

View File

@@ -29,11 +29,11 @@
#include "clangeditordocumentprocessor.h"
#include "clangutils.h"
#include "clangfollowsymbol.h"
#include "clanghoverhandler.h"
#include "clangrefactoringengine.h"
#include <coreplugin/editormanager/editormanager.h>
#include <cpptools/cppfollowsymbolundercursor.h>
#include <cpptools/cpphoverhandler.h>
#include <cpptools/cppmodelmanager.h>
#include <cpptools/editordocumenthandle.h>
#include <cpptools/projectinfo.h>
@@ -114,7 +114,7 @@ CppTools::CppCompletionAssistProvider *ModelManagerSupportClang::completionAssis
TextEditor::BaseHoverHandler *ModelManagerSupportClang::createHoverHandler()
{
return new CppTools::CppHoverHandler;
return new Internal::ClangHoverHandler;
}
CppTools::FollowSymbolInterface &ModelManagerSupportClang::followSymbolInterface()