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

@@ -36,4 +36,9 @@ enum class PreferredTranslationUnit
LastUninitialized,
};
// CLANG-UPGRADE-CHECK: Remove IS_SUSPEND_SUPPORTED once we require clang >= 7.0
#if defined(CINDEX_VERSION_HAS_PRETTYDECL_BACKPORTED) || CINDEX_VERSION_MINOR >= 47
# define IS_PRETTY_DECL_SUPPORTED
#endif
} // namespace ClangBackEnd

View File

@@ -29,10 +29,12 @@ HEADERS += \
$$PWD/clangreparsesupportivetranslationunitjob.h \
$$PWD/clangrequestdocumentannotationsjob.h \
$$PWD/clangrequestreferencesjob.h \
$$PWD/clangrequesttooltipjob.h \
$$PWD/clangresumedocumentjob.h \
$$PWD/clangstring.h \
$$PWD/clangsupportivetranslationunitinitializer.h \
$$PWD/clangsuspenddocumentjob.h \
$$PWD/clangtooltipinfocollector.h \
$$PWD/clangtranslationunit.h \
$$PWD/clangtranslationunits.h \
$$PWD/clangtranslationunitupdater.h \
@@ -86,8 +88,10 @@ SOURCES += \
$$PWD/clangreparsesupportivetranslationunitjob.cpp \
$$PWD/clangrequestdocumentannotationsjob.cpp \
$$PWD/clangrequestreferencesjob.cpp \
$$PWD/clangrequesttooltipjob.cpp \
$$PWD/clangsuspenddocumentjob.cpp \
$$PWD/clangsupportivetranslationunitinitializer.cpp \
$$PWD/clangtooltipinfocollector.cpp \
$$PWD/clangtranslationunit.cpp \
$$PWD/clangtranslationunits.cpp \
$$PWD/clangtranslationunitupdater.cpp \

View File

@@ -258,6 +258,7 @@ static void fillJobRequest(JobRequest &jobRequest, const MessageType &message)
jobRequest.line = message.line();
jobRequest.column = message.column();
jobRequest.ticketNumber = message.ticketNumber();
jobRequest.textCodecName = message.fileContainer().textCodecName();
// The unsaved files might get updater later, so take the current
// revision for the request.
jobRequest.documentRevision = message.fileContainer().documentRevision();
@@ -303,6 +304,25 @@ void ClangCodeModelServer::requestFollowSymbol(const RequestFollowSymbolMessage
}
}
void ClangCodeModelServer::requestToolTip(const RequestToolTipMessage &message)
{
TIME_SCOPE_DURATION("ClangCodeModelServer::requestToolTip");
try {
const Document document = documents.document(message.fileContainer().filePath(),
message.fileContainer().projectPartId());
DocumentProcessor processor = documentProcessors().processor(document);
JobRequest jobRequest = processor.createJobRequest(JobRequest::Type::RequestToolTip);
fillJobRequest(jobRequest, message);
processor.addJob(jobRequest);
processor.process();
} catch (const std::exception &exception) {
qWarning() << "Error in ClangCodeModelServer::requestToolTip:" << exception.what();
}
}
void ClangCodeModelServer::updateVisibleTranslationUnits(const UpdateVisibleTranslationUnitsMessage &message)
{
qCDebug(serverLog) << "########## updateVisibleTranslationUnits";

View File

@@ -61,6 +61,7 @@ public:
void requestDocumentAnnotations(const RequestDocumentAnnotationsMessage &message) override;
void requestReferences(const RequestReferencesMessage &message) override;
void requestFollowSymbol(const RequestFollowSymbolMessage &message) override;
void requestToolTip(const RequestToolTipMessage &message) override;
public: // for tests
const Documents &documentsForTestOnly() const;

View File

@@ -32,6 +32,7 @@
#include "clangreparsesupportivetranslationunitjob.h"
#include "clangrequestdocumentannotationsjob.h"
#include "clangrequestreferencesjob.h"
#include "clangrequesttooltipjob.h"
#include "clangresumedocumentjob.h"
#include "clangsuspenddocumentjob.h"
#include "clangupdatedocumentannotationsjob.h"
@@ -40,6 +41,7 @@
#include <clangsupport/cmbcodecompletedmessage.h>
#include <clangsupport/followsymbolmessage.h>
#include <clangsupport/referencesmessage.h>
#include <clangsupport/tooltipmessage.h>
#include <utils/qtcassert.h>
@@ -62,6 +64,7 @@ static const char *JobRequestTypeToText(JobRequest::Type type)
RETURN_TEXT_FOR_CASE(RequestDocumentAnnotations);
RETURN_TEXT_FOR_CASE(RequestReferences);
RETURN_TEXT_FOR_CASE(FollowSymbol);
RETURN_TEXT_FOR_CASE(RequestToolTip);
RETURN_TEXT_FOR_CASE(SuspendDocument);
RETURN_TEXT_FOR_CASE(ResumeDocument);
}
@@ -126,6 +129,7 @@ static JobRequest::ExpirationConditions expirationConditionsForType(JobRequest::
return Conditions(Condition::AnythingChanged);
case Type::RequestReferences:
case Type::RequestDocumentAnnotations:
case Type::RequestToolTip:
case Type::FollowSymbol:
return Conditions(Condition::DocumentClosed)
| Conditions(Condition::DocumentRevisionChanged);
@@ -153,8 +157,10 @@ static JobRequest::RunConditions conditionsForType(JobRequest::Type type)
Conditions conditions = Conditions(Condition::DocumentUnsuspended)
| Conditions(Condition::DocumentVisible);
if (type == Type::RequestReferences || type == Type::FollowSymbol)
if (type == Type::RequestReferences || type == Type::FollowSymbol
|| type == Type::RequestToolTip) {
conditions |= Condition::CurrentDocumentRevision;
}
if (type != Type::UpdateDocumentAnnotations && type != Type::ParseSupportiveTranslationUnit)
conditions |= Condition::DocumentParsed;
@@ -192,6 +198,8 @@ IAsyncJob *JobRequest::createJob() const
return new RequestDocumentAnnotationsJob();
case JobRequest::Type::RequestReferences:
return new RequestReferencesJob();
case JobRequest::Type::RequestToolTip:
return new RequestToolTipJob();
case JobRequest::Type::FollowSymbol:
return new FollowSymbolJob();
case JobRequest::Type::SuspendDocument:
@@ -224,6 +232,11 @@ void JobRequest::cancelJob(ClangCodeModelClientInterface &client) const
false,
ticketNumber));
break;
case JobRequest::Type::RequestToolTip:
client.tooltip(ToolTipMessage(FileContainer(),
ToolTipInfo(),
ticketNumber));
break;
case JobRequest::Type::CompleteCode:
client.codeCompleted(CodeCompletedMessage(CodeCompletions(),
CompletionCorrection::NoCorrection,

View File

@@ -59,6 +59,7 @@ public:
RequestDocumentAnnotations,
RequestReferences,
FollowSymbol,
RequestToolTip,
SuspendDocument,
ResumeDocument,
@@ -118,6 +119,7 @@ public:
qint32 funcNameStartLine = -1;
qint32 funcNameStartColumn = -1;
quint64 ticketNumber = 0;
Utf8String textCodecName;
bool localReferences = false;
};

View File

@@ -0,0 +1,67 @@
/****************************************************************************
**
** 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 "clangrequesttooltipjob.h"
#include <clangsupport/clangsupportdebugutils.h>
#include <clangsupport/clangcodemodelclientinterface.h>
#include <clangsupport/tooltipmessage.h>
#include <utils/qtcassert.h>
namespace ClangBackEnd {
IAsyncJob::AsyncPrepareResult RequestToolTipJob::prepareAsyncRun()
{
const JobRequest jobRequest = context().jobRequest;
QTC_ASSERT(jobRequest.type == JobRequest::Type::RequestToolTip, return AsyncPrepareResult());
QTC_ASSERT(acquireDocument(), return AsyncPrepareResult());
const TranslationUnit translationUnit = *m_translationUnit;
const UnsavedFiles unsavedFiles = *context().unsavedFiles;
const quint32 line = jobRequest.line;
const quint32 column = jobRequest.column;
const Utf8String textCodecName = jobRequest.textCodecName;
setRunner([translationUnit, unsavedFiles, line, column, textCodecName]() {
TIME_SCOPE_DURATION("RequestToolTipJobRunner");
UnsavedFiles theUnsavedFiles = unsavedFiles;
return translationUnit.tooltip(theUnsavedFiles, textCodecName, line, column);
});
return AsyncPrepareResult{translationUnit.id()};
}
void RequestToolTipJob::finalizeAsyncRun()
{
if (!context().isOutdated()) {
const AsyncResult result = asyncResult();
context().client->tooltip(ToolTipMessage(m_pinnedFileContainer,
result,
context().jobRequest.ticketNumber));
}
}
} // namespace ClangBackEnd

View File

@@ -0,0 +1,43 @@
/****************************************************************************
**
** 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 <clangsupport/tooltipinfo.h>
#include "clangdocumentjob.h"
namespace ClangBackEnd {
class RequestToolTipJob : public DocumentJob<ToolTipInfo>
{
public:
using AsyncResult = ToolTipInfo;
AsyncPrepareResult prepareAsyncRun() override;
void finalizeAsyncRun() override;
};
} // namespace ClangBackEnd

View File

@@ -0,0 +1,531 @@
/****************************************************************************
**
** 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 "clangtooltipinfocollector.h"
#include "clangbackend_global.h"
#include "clangstring.h"
#include "cursor.h"
#include "sourcerange.h"
#include "unsavedfiles.h"
#include "unsavedfile.h"
#include <clangsupport/sourcerangecontainer.h>
#include <utils/qtcassert.h>
#include <utils/textfileformat.h>
#include <utf8string.h>
#include <QDebug>
#include <QDir>
#include <QTextCodec>
namespace ClangBackEnd {
namespace {
Utf8StringVector qualificationPrefixAsVector(const Cursor &cursor)
{
Utf8StringVector result;
for (Cursor parent = cursor.semanticParent();
parent.isValid() && (parent.kind() == CXCursor_Namespace || parent.isCompoundType());
parent = parent.semanticParent()) {
result.prepend(parent.spelling());
}
return result;
}
Utf8String qualificationPrefix(const Cursor &cursor)
{
// TODO: Implement with qualificationPrefixAsVector()
Utf8String qualifiedName;
for (Cursor parent = cursor.semanticParent();
parent.isValid() && (parent.kind() == CXCursor_Namespace);
parent = parent.semanticParent()) {
qualifiedName = parent.spelling() + Utf8StringLiteral("::") + qualifiedName;
}
return qualifiedName;
}
Utf8String displayName(const Cursor &cursor)
{
if (cursor.kind() == CXCursor_ClassTemplate) {
// TODO: The qualification should be part of the display name. Fix this in libclang.
return qualificationPrefix(cursor) + cursor.displayName();
}
return cursor.displayName();
}
Utf8String textForFunctionLike(const Cursor &cursor)
{
#ifdef IS_PRETTY_DECL_SUPPORTED
CXPrintingPolicy policy = clang_getCursorPrintingPolicy(cursor.cx());
clang_PrintingPolicy_setProperty(policy, CXPrintingPolicy_FullyQualifiedName, 1);
clang_PrintingPolicy_setProperty(policy, CXPrintingPolicy_TerseOutput, 1);
// Avoid printing attributes/pragmas
clang_PrintingPolicy_setProperty(policy, CXPrintingPolicy_PolishForDeclaration, 1);
clang_PrintingPolicy_setProperty(policy, CXPrintingPolicy_SuppressInitializers, 1);
const Utf8String prettyPrinted = ClangString(
clang_getCursorPrettyPrinted(cursor.cx(), policy));
clang_PrintingPolicy_dispose(policy);
return prettyPrinted;
#else
// Printing function declarations with displayName() is quite limited:
// * result type is not included
// * parameter names are not included
// * templates in the result type are not included
// * no full qualification of the function name
return Utf8String(cursor.resultType().spelling())
+ Utf8StringLiteral(" ")
+ qualificationPrefix(cursor)
+ Utf8String(cursor.displayName());
#endif
}
Utf8String textForEnumConstantDecl(const Cursor &cursor)
{
const Cursor semanticParent = cursor.semanticParent();
QTC_ASSERT(semanticParent.kind() == CXCursor_EnumDecl, return Utf8String());
const Type enumType = semanticParent.enumType();
if (enumType.isUnsigned())
return Utf8String::number(cursor.enumConstantUnsignedValue());
return Utf8String::number(cursor.enumConstantValue());
}
Utf8String textForInclusionDirective(const Cursor &cursor)
{
const CXFile includedFile = cursor.includedFile();
const Utf8String fileName = ClangString(clang_getFileName(includedFile));
return QDir::toNativeSeparators(fileName.toString());
}
Utf8String textForAnyTypeAlias(const Cursor &cursor)
{
// For a CXCursor_TypeAliasTemplateDecl the type of cursor/referenced
// is invalid, so we do not get the underlying type. This here solely
// reports the unresolved name instead of the empty string.
if (cursor.kind() == CXCursor_TypeAliasTemplateDecl)
return cursor.displayName();
return cursor.type().alias().utf8Spelling();
}
bool includeSizeForCursor(const Cursor &cursor)
{
return cursor.isCompoundType()
|| cursor.kind() == CXCursor_EnumDecl
|| cursor.kind() == CXCursor_UnionDecl
|| cursor.kind() == CXCursor_FieldDecl;
}
Utf8String sizeInBytes(const Cursor &cursor)
{
if (includeSizeForCursor(cursor)) {
bool ok = false;
const long long size = cursor.type().sizeOf(&ok);
if (ok)
return Utf8String::number(size);
}
return Utf8String();
}
Cursor referencedCursor(const Cursor &cursor)
{
// Query the referenced cursor directly instead of first testing with cursor.isReference().
// cursor.isReference() reports false for e.g. CXCursor_DeclRefExpr or CXCursor_CallExpr
// although it returns a valid cursor.
const Cursor referenced = cursor.referenced();
if (referenced.isValid())
return referenced;
const Cursor definition = cursor.definition();
if (definition.isValid())
return definition;
return cursor;
}
class ToolTipInfoCollector
{
public:
ToolTipInfoCollector(UnsavedFiles &unsavedFiles,
const Utf8String &textCodecName,
const Utf8String &mainFilePath,
CXTranslationUnit cxTranslationUnit);
ToolTipInfo collect(uint line, uint column) const;
private:
Utf8String text(const Cursor &cursor, const Cursor &referenced) const;
Utf8String textForMacroExpansion(const Cursor &cursor) const;
Utf8String textForNamespaceAlias(const Cursor &cursor) const;
ToolTipInfo qDocInfo(const Cursor &cursor) const;
CXSourceLocation toCXSourceLocation(uint line, uint column) const;
UnsavedFile unsavedFile(const Utf8String &filePath) const;
Utf8String lineRange(const Utf8String &filePath, unsigned fromLine, unsigned toLine) const;
private:
UnsavedFiles &m_unsavedFiles;
const Utf8String m_textCodecName;
const Utf8String m_mainFilePath;
CXTranslationUnit m_cxTranslationUnit = nullptr;
};
ToolTipInfoCollector::ToolTipInfoCollector(UnsavedFiles &unsavedFiles,
const Utf8String &textCodecName,
const Utf8String &mainFilePath,
CXTranslationUnit cxTranslationUnit)
: m_unsavedFiles(unsavedFiles)
, m_textCodecName(textCodecName)
, m_mainFilePath(mainFilePath)
, m_cxTranslationUnit(cxTranslationUnit)
{
}
Utf8String ToolTipInfoCollector::text(const Cursor &cursor, const Cursor &referenced) const
{
if (cursor.kind() == CXCursor_MacroExpansion)
return textForMacroExpansion(referenced);
if (referenced.kind() == CXCursor_EnumConstantDecl)
return textForEnumConstantDecl(referenced);
if (referenced.kind() == CXCursor_InclusionDirective)
return textForInclusionDirective(referenced);
if (referenced.kind() == CXCursor_Namespace)
return qualificationPrefix(referenced) + referenced.spelling();
if (referenced.kind() == CXCursor_NamespaceAlias)
return textForNamespaceAlias(referenced);
if (referenced.isAnyTypeAlias())
return textForAnyTypeAlias(referenced);
if (referenced.isFunctionLike())
return textForFunctionLike(referenced);
if (referenced.type().canonical().isBuiltinType())
return referenced.type().canonical().builtinTypeToString();
if (referenced.kind() == CXCursor_VarDecl)
return referenced.type().spelling(); // e.g. "Zii<int>"
const Type referencedType = referenced.type();
if (referencedType.isValid()) {
// Generally, the type includes the qualification but has this limitations:
// * namespace aliases are not resolved
// * outer class of a inner template class is not included
// The type includes the qualification, but not resolved namespace aliases.
// For a CXType_Record, this also includes e.g. "const " as prefix.
return referencedType.canonical().utf8Spelling();
}
return displayName(referenced);
}
Utf8String ToolTipInfoCollector::textForMacroExpansion(const Cursor &cursor) const
{
QTC_ASSERT(cursor.kind() == CXCursor_MacroDefinition, return Utf8String());
const SourceRange sourceRange = cursor.sourceRange();
const SourceLocation start = sourceRange.start();
const SourceLocation end = sourceRange.end();
return lineRange(start.filePath(), start.line(), end.line());
}
Utf8String ToolTipInfoCollector::textForNamespaceAlias(const Cursor &cursor) const
{
// TODO: Add some libclang API to get the aliased name straight away.
CXToken *cxTokens = nullptr;
uint cxTokenCount = 0;
clang_tokenize(m_cxTranslationUnit, cursor.cxSourceRange(), &cxTokens, &cxTokenCount);
Utf8String aliasedName;
// Start at 3 in order to skip these tokens: namespace X =
for (uint i = 3; i < cxTokenCount; ++i)
aliasedName += ClangString(clang_getTokenSpelling(m_cxTranslationUnit, cxTokens[i]));
clang_disposeTokens(m_cxTranslationUnit, cxTokens, cxTokenCount);
return aliasedName;
}
static Utf8String typeName(const Type &type)
{
return type.declaration().spelling();
}
static Utf8String qdocMark(const Cursor &cursor)
{
if (cursor.kind() == CXCursor_ClassTemplate)
return cursor.spelling();
if (cursor.type().kind() == CXType_Enum
|| cursor.type().kind() == CXType_Typedef
|| cursor.type().kind() == CXType_Record)
return typeName(cursor.type());
Utf8String text = cursor.displayName();
if (cursor.kind() == CXCursor_FunctionDecl) {
// TODO: Remove this workaround by fixing this in
// libclang with the help of CXPrintingPolicy.
text.replace(Utf8StringLiteral("<>"), Utf8String());
}
return text;
}
static ToolTipInfo::QdocCategory qdocCategory(const Cursor &cursor)
{
if (cursor.isFunctionLike())
return ToolTipInfo::Function;
if (cursor.kind() == CXCursor_MacroDefinition)
return ToolTipInfo::Macro;
if (cursor.kind() == CXCursor_EnumConstantDecl)
return ToolTipInfo::Enum;
if (cursor.type().kind() == CXType_Enum)
return ToolTipInfo::Enum;
if (cursor.kind() == CXCursor_InclusionDirective)
return ToolTipInfo::Brief;
// TODO: Handle CXCursor_NamespaceAlias, too?!
if (cursor.kind() == CXCursor_Namespace)
return ToolTipInfo::ClassOrNamespace;
if (cursor.isCompoundType())
return ToolTipInfo::ClassOrNamespace;
if (cursor.kind() == CXCursor_NamespaceAlias)
return ToolTipInfo::ClassOrNamespace;
if (cursor.type().kind() == CXType_Typedef)
return ToolTipInfo::Typedef;
if (cursor.type().kind() == CXType_Record)
return ToolTipInfo::ClassOrNamespace;
if (cursor.kind() == CXCursor_TypeAliasTemplateDecl)
return ToolTipInfo::Typedef;
if (cursor.kind() == CXCursor_ClassTemplate)
return ToolTipInfo::ClassOrNamespace;
return ToolTipInfo::Unknown;
}
static Utf8String name(const Cursor &cursor)
{
if (cursor.type().kind() == CXType_Record || cursor.kind() == CXCursor_EnumDecl)
return typeName(cursor.type());
return cursor.spelling();
}
static Utf8StringVector qDocIdCandidates(const Cursor &cursor)
{
Utf8StringVector components = qualificationPrefixAsVector(cursor);
if (components.isEmpty())
return { name(cursor) };
components << name(cursor);
Utf8StringVector result;
Utf8String name;
for (auto it = components.rbegin(); it != components.rend(); ++it) {
if (name.isEmpty())
name = *it;
else
name = *it + (Utf8StringLiteral("::") + name);
result.prepend(name);
}
return result;
}
// TODO: Add libclang API for this?!
static bool isBuiltinOrPointerToBuiltin(const Type &type)
{
Type theType = type;
if (theType.isBuiltinType())
return true;
// TODO: Simplify
// TODO: Test with **
while (theType.pointeeType().isValid()) {
theType = theType.pointeeType();
if (theType.isBuiltinType())
return true;
}
return false;
}
ToolTipInfo ToolTipInfoCollector::qDocInfo(const Cursor &cursor) const
{
ToolTipInfo result;
if (isBuiltinOrPointerToBuiltin(cursor.type()))
return result;
result.setQdocIdCandidates(qDocIdCandidates(cursor));
result.setQdocMark(qdocMark(cursor));
result.setQdocCategory(qdocCategory(cursor));
if (cursor.type().kind() == CXType_Record) {
result.setQdocIdCandidates(qDocIdCandidates(cursor.type().declaration()));
return result;
}
if (cursor.kind() == CXCursor_VarDecl || cursor.kind() == CXCursor_FieldDecl) {
result.setQdocMark(typeName(cursor.type()));
// maybe template instantiation
if (cursor.type().kind() == CXType_Unexposed && cursor.type().canonical().kind() == CXType_Record) {
result.setQdocIdCandidates(qDocIdCandidates(cursor.type().canonical().declaration()));
result.setQdocCategory(ToolTipInfo::ClassOrNamespace);
return result;
}
}
// TODO: Handle also RValueReference()
if (cursor.type().isLValueReference()) {
const Cursor pointeeTypeDeclaration = cursor.type().pointeeType().declaration();
result.setQdocIdCandidates(qDocIdCandidates(pointeeTypeDeclaration));
result.setQdocMark(pointeeTypeDeclaration.spelling());
result.setQdocCategory(qdocCategory(pointeeTypeDeclaration));
return result;
}
return result;
}
CXSourceLocation ToolTipInfoCollector::toCXSourceLocation(uint line, uint column) const
{
return clang_getLocation(m_cxTranslationUnit,
clang_getFile(m_cxTranslationUnit,
m_mainFilePath.constData()),
line,
column);
}
UnsavedFile ToolTipInfoCollector::unsavedFile(const Utf8String &filePath) const
{
const UnsavedFile &unsavedFile = m_unsavedFiles.unsavedFile(filePath);
if (!unsavedFile.filePath().isEmpty())
return unsavedFile;
// Create an unsaved file with the file content from disk.
// TODO: Make use of clang_getFileContents() instead of reading from disk.
QTextCodec *codec = QTextCodec::codecForName(m_textCodecName);
QByteArray fileContent;
QString errorString;
using namespace Utils;
const TextFileFormat::ReadResult readResult
= TextFileFormat::readFileUTF8(filePath.toString(), codec, &fileContent, &errorString);
if (readResult != TextFileFormat::ReadSuccess) {
qWarning() << "Failed to read file" << filePath << ":" << errorString;
return UnsavedFile();
}
return UnsavedFile(filePath, Utf8String::fromByteArray(fileContent));
}
Utf8String ToolTipInfoCollector::lineRange(const Utf8String &filePath,
unsigned fromLine,
unsigned toLine) const
{
if (toLine < fromLine)
return Utf8String();
const UnsavedFile file = unsavedFile(filePath);
if (file.fileContent().isEmpty())
return Utf8String();
return file.lineRange(fromLine, toLine);
}
ToolTipInfo ToolTipInfoCollector::collect(uint line, uint column) const
{
const Cursor cursor = clang_getCursor(m_cxTranslationUnit, toCXSourceLocation(line, column));
if (!cursor.isValid())
return ToolTipInfo(); // E.g. cursor on ifdeffed out range
const Cursor referenced = referencedCursor(cursor);
QTC_CHECK(referenced.isValid());
ToolTipInfo info;
info.setText(text(cursor, referenced));
info.setBriefComment(referenced.briefComment());
{
ToolTipInfo qDocToolTipInfo = qDocInfo(referenced);
info.setQdocIdCandidates(qDocToolTipInfo.qdocIdCandidates());
info.setQdocMark(qDocToolTipInfo.qdocMark());
info.setQdocCategory(qDocToolTipInfo.qdocCategory());
}
info.setSizeInBytes(sizeInBytes(cursor));
return info;
}
} // anonymous namespace
ToolTipInfo collectToolTipInfo(UnsavedFiles &unsavedFiles,
const Utf8String &textCodecName,
const Utf8String &mainFilePath,
CXTranslationUnit cxTranslationUnit,
uint line,
uint column)
{
ToolTipInfoCollector collector(unsavedFiles, textCodecName, mainFilePath, cxTranslationUnit);
const ToolTipInfo info = collector.collect(line, column);
return info;
}
} // namespace ClangBackEnd

View File

@@ -0,0 +1,45 @@
/****************************************************************************
**
** 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 <utf8string.h>
#include <clangsupport/tooltipinfo.h>
#include <clang-c/Index.h>
namespace ClangBackEnd {
class UnsavedFiles;
ToolTipInfo collectToolTipInfo(UnsavedFiles &unsavedFiles,
const Utf8String &textCodecName,
const Utf8String &mainFilePath,
CXTranslationUnit cxTranslationUnit,
uint line,
uint column);
} // namespace ClangBackEnd

View File

@@ -27,6 +27,7 @@
#include "clangbackend_global.h"
#include "clangreferencescollector.h"
#include "clangtooltipinfocollector.h"
#include "clangtranslationunitupdater.h"
#include "clangfollowsymbol.h"
#include "clangfollowsymboljob.h"
@@ -139,6 +140,20 @@ void TranslationUnit::extractDocumentAnnotations(
skippedSourceRanges = this->skippedSourceRanges().toSourceRangeContainers();
}
ToolTipInfo TranslationUnit::tooltip(UnsavedFiles &unsavedFiles,
const Utf8String &textCodecName,
uint line,
uint column) const
{
return collectToolTipInfo(unsavedFiles,
textCodecName,
filePath(),
m_cxTranslationUnit,
line,
column);
}
ReferencesResult TranslationUnit::references(uint line, uint column, bool localReferences) const
{
return collectReferences(m_cxTranslationUnit, line, column, localReferences);

View File

@@ -41,6 +41,7 @@ class SkippedSourceRanges;
class SourceLocation;
class SourceRange;
class SourceRangeContainer;
class ToolTipInfo;
class TranslationUnitUpdateInput;
class TranslationUnitUpdateResult;
class UnsavedFiles;
@@ -86,6 +87,10 @@ public:
ReferencesResult references(uint line, uint column, bool localReferences = false) const;
ToolTipInfo tooltip(UnsavedFiles &unsavedFiles,
const Utf8String &textCodecName,
uint line,
uint column) const;
DiagnosticSet diagnostics() const;
SourceLocation sourceLocationAt(uint line, uint column) const;

View File

@@ -84,6 +84,16 @@ bool Type::isBuiltinType() const
return cxType.kind >= CXType_FirstBuiltin && cxType.kind <= CXType_LastBuiltin;
}
bool Type::isUnsigned() const
{
return cxType.kind == CXType_UChar
|| cxType.kind == CXType_UShort
|| cxType.kind == CXType_UInt
|| cxType.kind == CXType_ULong
|| cxType.kind == CXType_ULongLong
|| cxType.kind == CXType_UInt128;
}
Utf8String Type::utf8Spelling() const
{
return ClangString(clang_getTypeSpelling(cxType));
@@ -94,6 +104,83 @@ ClangString Type::spelling() const
return ClangString(clang_getTypeSpelling(cxType));
}
static const char *builtinTypeToText(CXTypeKind kind)
{
// CLANG-UPGRADE-CHECK: Check for added built-in types.
switch (kind) {
case CXType_Void:
return "void";
case CXType_Bool:
return "bool";
// See also ${CLANG_REPOSITORY}/lib/Sema/SemaChecking.cpp - IsSameCharType().
case CXType_Char_U:
case CXType_UChar:
return "unsigned char";
case CXType_Char_S:
case CXType_SChar:
return "signed char";
case CXType_Char16:
return "char16_t";
case CXType_Char32:
return "char32_t";
case CXType_WChar:
return "wchar_t";
case CXType_UShort:
return "unsigned short";
case CXType_UInt:
return "unsigned int";
case CXType_ULong:
return "unsigned long";
case CXType_ULongLong:
return "unsigned long long";
case CXType_Short:
return "short";
case CXType_Int:
return "int";
case CXType_Long:
return "long";
case CXType_LongLong:
return "long long";
case CXType_Float:
return "float";
case CXType_Double:
return "double";
case CXType_LongDouble:
return "long double";
case CXType_NullPtr:
return "nullptr_t";
// https://gcc.gnu.org/onlinedocs/gcc/_005f_005fint128.html
case CXType_Int128: return "__int128";
case CXType_UInt128: return "unsigned __int128";
// https://gcc.gnu.org/onlinedocs/gcc/Floating-Types.html
case CXType_Float128: return "__float128";
// CLANG-UPGRADE-CHECK: CXType_Float16 available with >= clang-6.0:
// case CXType_Float16: return "_Float16";
// https://www.khronos.org/registry/OpenCL/sdk/2.1/docs/man/xhtml/scalarDataTypes.html
case CXType_Half:
return "half";
default:
return "";
}
}
Utf8String Type::builtinTypeToString() const
{
const char *text = builtinTypeToText(cxType.kind);
return Utf8String::fromByteArray(QByteArray::fromRawData(text, strlen(text)));
}
int Type::argumentCount() const
{
return clang_getNumArgTypes(cxType);
@@ -129,6 +216,16 @@ Cursor Type::declaration() const
return clang_getTypeDeclaration(cxType);
}
long long Type::sizeOf(bool *isValid) const
{
const long long size = clang_Type_getSizeOf(cxType);
*isValid = size != CXTypeLayoutError_Invalid
&& size != CXTypeLayoutError_Incomplete
&& size != CXTypeLayoutError_Dependent;
return size;
}
CXTypeKind Type::kind() const
{
return cxType.kind;

View File

@@ -53,9 +53,11 @@ public:
bool isReferencingConstant() const;
bool isOutputArgument() const;
bool isBuiltinType() const;
bool isUnsigned() const;
Utf8String utf8Spelling() const;
ClangString spelling() const;
Utf8String builtinTypeToString() const;
int argumentCount() const;
Type alias() const;
@@ -66,6 +68,8 @@ public:
Cursor declaration() const;
long long sizeOf(bool *isValid) const;
CXTypeKind kind() const;
private:

View File

@@ -158,6 +158,14 @@ bool Cursor::isTemplateLike() const
Q_UNREACHABLE();
}
bool Cursor::isAnyTypeAlias() const
{
const CXCursorKind k = kind();
return k == CXCursor_TypeAliasDecl
|| k == CXCursor_TypedefDecl
|| k == CXCursor_TypeAliasTemplateDecl;
}
bool Cursor::hasFinalFunctionAttribute() const
{
bool hasFinal = false;
@@ -248,11 +256,31 @@ Type Cursor::nonPointerTupe() const
return typeResult;
}
Type Cursor::enumType() const
{
return clang_getEnumDeclIntegerType(cxCursor);
}
long long Cursor::enumConstantValue() const
{
return clang_getEnumConstantDeclValue(cxCursor);
}
unsigned long long Cursor::enumConstantUnsignedValue() const
{
return clang_getEnumConstantDeclUnsignedValue(cxCursor);
}
Cursor Cursor::specializedCursorTemplate() const
{
return clang_getSpecializedCursorTemplate(cxCursor);
}
CXFile Cursor::includedFile() const
{
return clang_getIncludedFile(cxCursor);
}
SourceLocation Cursor::sourceLocation() const
{
return clang_getCursorLocation(cxCursor);
@@ -341,6 +369,11 @@ Cursor Cursor::functionBase() const
return functionBaseCursor;
}
Type Cursor::resultType() const
{
return clang_getResultType(type().cxType);
}
Cursor Cursor::argument(int index) const
{
return clang_Cursor_getArgument(cxCursor, index);
@@ -398,6 +431,11 @@ CXCursorKind Cursor::kind() const
return clang_getCursorKind(cxCursor);
}
CXCursor Cursor::cx() const
{
return cxCursor;
}
bool operator==(const Cursor &first, const Cursor &second)
{
return clang_equalCursors(first.cxCursor, second.cxCursor);

View File

@@ -67,6 +67,7 @@ public:
bool isFunctionLike() const;
bool isConstructorOrDestructor() const;
bool isTemplateLike() const;
bool isAnyTypeAlias() const;
bool hasFinalFunctionAttribute() const;
bool hasFinalClassAttribute() const;
bool isUnexposed() const;
@@ -81,6 +82,10 @@ public:
Type type() const;
Type nonPointerTupe() const;
Type enumType() const;
long long enumConstantValue() const;
unsigned long long enumConstantUnsignedValue() const;
SourceLocation sourceLocation() const;
CXSourceLocation cxSourceLocation() const;
@@ -92,17 +97,19 @@ public:
Cursor definition() const;
Cursor canonical() const;
Cursor alias() const;
Cursor referenced() const;
Cursor semanticParent() const;
Cursor lexicalParent() const;
Cursor functionBaseDeclaration() const;
Cursor functionBase() const;
Type resultType() const;
Cursor argument(int index) const;
unsigned overloadedDeclarationsCount() const;
Cursor overloadedDeclaration(unsigned index) const;
Cursor specializedCursorTemplate() const;
CXFile includedFile() const;
void collectOutputArgumentRangesTo(
std::vector<CXSourceRange> &outputArgumentRanges) const;
std::vector<CXSourceRange> outputArgumentRanges() const;
@@ -112,6 +119,8 @@ public:
template <class VisitorCallback>
void visit(VisitorCallback visitorCallback) const;
CXCursor cx() const;
private:
CXCursor cxCursor;
};

View File

@@ -30,6 +30,8 @@
#include <ostream>
#include <utils/qtcassert.h>
namespace ClangBackEnd {
UnsavedFile::UnsavedFile()
@@ -79,6 +81,25 @@ bool UnsavedFile::hasCharacterAt(uint line, uint column, char character) const
return positionIsOk && hasCharacterAt(utf8Position, character);
}
Utf8String UnsavedFile::lineRange(uint fromLine, uint toLine) const
{
QTC_ASSERT(fromLine <= toLine, return Utf8String());
// Find start of first line
bool ok = false;
const uint fromPosition = toUtf8Position(fromLine, 1, &ok);
QTC_ASSERT(ok, return Utf8String());
// Find end of last line
uint toPosition = toUtf8Position(toLine, 1, &ok);
QTC_ASSERT(ok, return Utf8String());
const uint endPosition = uint(m_fileContent.byteSize());
while (toPosition < endPosition && m_fileContent.constData()[toPosition] != '\n')
++toPosition;
return m_fileContent.mid(int(fromPosition), int(toPosition - fromPosition));
}
bool UnsavedFile::hasCharacterAt(uint position, char character) const
{
if (position < uint(m_fileContent.byteSize()))

View File

@@ -46,6 +46,7 @@ public:
// 1-based line and column
uint toUtf8Position(uint line, uint column, bool *ok) const;
bool hasCharacterAt(uint line, uint column, char character) const;
Utf8String lineRange(uint fromLine, uint toLine) const;
// 0-based position
bool hasCharacterAt(uint position, char character) const;