diff options
71 files changed, 2820 insertions, 108 deletions
diff --git a/src/libs/clangsupport/clangcodemodelclientinterface.cpp b/src/libs/clangsupport/clangcodemodelclientinterface.cpp index 0de9f39044a..492ea7896fe 100644 --- a/src/libs/clangsupport/clangcodemodelclientinterface.cpp +++ b/src/libs/clangsupport/clangcodemodelclientinterface.cpp @@ -53,6 +53,9 @@ void ClangCodeModelClientInterface::dispatch(const MessageEnvelop &messageEnvelo case MessageType::FollowSymbolMessage: followSymbol(messageEnvelop.message<FollowSymbolMessage>()); break; + case MessageType::ToolTipMessage: + tooltip(messageEnvelop.message<ToolTipMessage>()); + break; default: qWarning() << "Unknown ClangCodeModelClientMessage"; } diff --git a/src/libs/clangsupport/clangcodemodelclientinterface.h b/src/libs/clangsupport/clangcodemodelclientinterface.h index b6b878baaf0..6e8f3dd2485 100644 --- a/src/libs/clangsupport/clangcodemodelclientinterface.h +++ b/src/libs/clangsupport/clangcodemodelclientinterface.h @@ -43,6 +43,8 @@ class RegisterUnsavedFilesForEditorMessage; class RequestDocumentAnnotationsMessage; class RequestReferencesMessage; class RequestFollowSymbolMessage; +class RequestToolTipMessage; +class ToolTipMessage; class UnregisterProjectPartsForEditorMessage; class UnregisterTranslationUnitsForEditorMessage; class UnregisterUnsavedFilesForEditorMessage; @@ -60,6 +62,7 @@ public: virtual void documentAnnotationsChanged(const DocumentAnnotationsChangedMessage &message) = 0; virtual void references(const ReferencesMessage &message) = 0; virtual void followSymbol(const FollowSymbolMessage &message) = 0; + virtual void tooltip(const ToolTipMessage &message) = 0; }; } // namespace ClangBackEnd diff --git a/src/libs/clangsupport/clangcodemodelclientmessages.h b/src/libs/clangsupport/clangcodemodelclientmessages.h index 5b5c9b42edb..2903cda2079 100644 --- a/src/libs/clangsupport/clangcodemodelclientmessages.h +++ b/src/libs/clangsupport/clangcodemodelclientmessages.h @@ -31,3 +31,4 @@ #include "documentannotationschangedmessage.h" #include "referencesmessage.h" #include "followsymbolmessage.h" +#include "tooltipmessage.h" diff --git a/src/libs/clangsupport/clangcodemodelclientproxy.cpp b/src/libs/clangsupport/clangcodemodelclientproxy.cpp index 6a285c23668..fc75a226ed5 100644 --- a/src/libs/clangsupport/clangcodemodelclientproxy.cpp +++ b/src/libs/clangsupport/clangcodemodelclientproxy.cpp @@ -95,6 +95,11 @@ void ClangCodeModelClientProxy::followSymbol(const FollowSymbolMessage &message) m_writeMessageBlock.write(message); } +void ClangCodeModelClientProxy::tooltip(const ToolTipMessage &message) +{ + m_writeMessageBlock.write(message); +} + void ClangCodeModelClientProxy::readMessages() { for (const MessageEnvelop &message : m_readMessageBlock.readAll()) diff --git a/src/libs/clangsupport/clangcodemodelclientproxy.h b/src/libs/clangsupport/clangcodemodelclientproxy.h index 0665d6e8faa..58b87a05d8b 100644 --- a/src/libs/clangsupport/clangcodemodelclientproxy.h +++ b/src/libs/clangsupport/clangcodemodelclientproxy.h @@ -57,6 +57,7 @@ public: void documentAnnotationsChanged(const DocumentAnnotationsChangedMessage &message) override; void references(const ReferencesMessage &message) override; void followSymbol(const FollowSymbolMessage &message) override; + void tooltip(const ToolTipMessage &message) override; void readMessages(); diff --git a/src/libs/clangsupport/clangcodemodelserverinterface.cpp b/src/libs/clangsupport/clangcodemodelserverinterface.cpp index bdaa802a47e..d50e97367d5 100644 --- a/src/libs/clangsupport/clangcodemodelserverinterface.cpp +++ b/src/libs/clangsupport/clangcodemodelserverinterface.cpp @@ -71,6 +71,9 @@ void ClangCodeModelServerInterface::dispatch(const MessageEnvelop &messageEnvelo case MessageType::RequestFollowSymbolMessage: requestFollowSymbol(messageEnvelop.message<RequestFollowSymbolMessage>()); break; + case MessageType::RequestToolTipMessage: + requestToolTip(messageEnvelop.message<RequestToolTipMessage>()); + break; case MessageType::UpdateVisibleTranslationUnitsMessage: updateVisibleTranslationUnits(messageEnvelop.message<UpdateVisibleTranslationUnitsMessage>()); break; diff --git a/src/libs/clangsupport/clangcodemodelserverinterface.h b/src/libs/clangsupport/clangcodemodelserverinterface.h index e4e897afc4d..eb24fa4dfed 100644 --- a/src/libs/clangsupport/clangcodemodelserverinterface.h +++ b/src/libs/clangsupport/clangcodemodelserverinterface.h @@ -50,6 +50,7 @@ public: virtual void requestDocumentAnnotations(const RequestDocumentAnnotationsMessage &message) = 0; virtual void requestReferences(const RequestReferencesMessage &message) = 0; virtual void requestFollowSymbol(const RequestFollowSymbolMessage &message) = 0; + virtual void requestToolTip(const RequestToolTipMessage &message) = 0; virtual void updateVisibleTranslationUnits(const UpdateVisibleTranslationUnitsMessage &message) = 0; }; diff --git a/src/libs/clangsupport/clangcodemodelservermessages.h b/src/libs/clangsupport/clangcodemodelservermessages.h index b1ba7a07016..0e577b08ddd 100644 --- a/src/libs/clangsupport/clangcodemodelservermessages.h +++ b/src/libs/clangsupport/clangcodemodelservermessages.h @@ -36,6 +36,7 @@ #include "requestdocumentannotations.h" #include "requestreferencesmessage.h" #include "requestfollowsymbolmessage.h" +#include "requesttooltipmessage.h" #include "unregisterunsavedfilesforeditormessage.h" #include "updatetranslationunitsforeditormessage.h" #include "updatevisibletranslationunitsmessage.h" diff --git a/src/libs/clangsupport/clangcodemodelserverproxy.cpp b/src/libs/clangsupport/clangcodemodelserverproxy.cpp index f51bcb2802b..23a80284802 100644 --- a/src/libs/clangsupport/clangcodemodelserverproxy.cpp +++ b/src/libs/clangsupport/clangcodemodelserverproxy.cpp @@ -96,6 +96,11 @@ void ClangCodeModelServerProxy::requestFollowSymbol(const RequestFollowSymbolMes m_writeMessageBlock.write(message); } +void ClangCodeModelServerProxy::requestToolTip(const RequestToolTipMessage &message) +{ + m_writeMessageBlock.write(message); +} + void ClangCodeModelServerProxy::updateVisibleTranslationUnits(const UpdateVisibleTranslationUnitsMessage &message) { m_writeMessageBlock.write(message); diff --git a/src/libs/clangsupport/clangcodemodelserverproxy.h b/src/libs/clangsupport/clangcodemodelserverproxy.h index e93e05d3f54..62ea90eebe2 100644 --- a/src/libs/clangsupport/clangcodemodelserverproxy.h +++ b/src/libs/clangsupport/clangcodemodelserverproxy.h @@ -60,6 +60,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; void updateVisibleTranslationUnits(const UpdateVisibleTranslationUnitsMessage &message) override; }; diff --git a/src/libs/clangsupport/clangsupport-lib.pri b/src/libs/clangsupport/clangsupport-lib.pri index a32f78c1868..952fd72a10d 100644 --- a/src/libs/clangsupport/clangsupport-lib.pri +++ b/src/libs/clangsupport/clangsupport-lib.pri @@ -68,6 +68,7 @@ SOURCES += \ $$PWD/requestsourcelocationforrenamingmessage.cpp \ $$PWD/requestsourcerangesanddiagnosticsforquerymessage.cpp \ $$PWD/requestsourcerangesforquerymessage.cpp \ + $$PWD/requesttooltipmessage.cpp \ $$PWD/sourcelocationcontainer.cpp \ $$PWD/sourcelocationcontainerv2.cpp \ $$PWD/sourcelocationscontainer.cpp \ @@ -82,6 +83,8 @@ SOURCES += \ $$PWD/sourcerangesforquerymessage.cpp \ $$PWD/sourcerangewithtextcontainer.cpp \ $$PWD/tokeninfocontainer.cpp \ + $$PWD/tooltipmessage.cpp \ + $$PWD/tooltipinfo.cpp \ $$PWD/unregisterunsavedfilesforeditormessage.cpp \ $$PWD/updatepchprojectpartsmessage.cpp \ $$PWD/updatetranslationunitsforeditormessage.cpp \ @@ -159,6 +162,7 @@ HEADERS += \ $$PWD/requestsourcelocationforrenamingmessage.h \ $$PWD/requestsourcerangesanddiagnosticsforquerymessage.h \ $$PWD/requestsourcerangesforquerymessage.h \ + $$PWD/requesttooltipmessage.h \ $$PWD/sourcelocationcontainer.h \ $$PWD/sourcelocationcontainerv2.h \ $$PWD/sourcelocationscontainer.h \ @@ -176,6 +180,8 @@ HEADERS += \ $$PWD/sourcerangewithtextcontainer.h \ $$PWD/stringcache.h \ $$PWD/tokeninfocontainer.h \ + $$PWD/tooltipmessage.h \ + $$PWD/tooltipinfo.h \ $$PWD/unregisterunsavedfilesforeditormessage.h \ $$PWD/updatepchprojectpartsmessage.h \ $$PWD/updatetranslationunitsforeditormessage.h \ diff --git a/src/libs/clangsupport/clangsupport_global.h b/src/libs/clangsupport/clangsupport_global.h index 15c2679e2e3..1a368162e1e 100644 --- a/src/libs/clangsupport/clangsupport_global.h +++ b/src/libs/clangsupport/clangsupport_global.h @@ -120,6 +120,9 @@ enum class MessageType : quint8 { RequestFollowSymbolMessage, FollowSymbolMessage, + RequestToolTipMessage, + ToolTipMessage, + UpdateVisibleTranslationUnitsMessage, CompleteCodeMessage, diff --git a/src/libs/clangsupport/filecontainer.cpp b/src/libs/clangsupport/filecontainer.cpp index e2efc70b896..05f888aebf8 100644 --- a/src/libs/clangsupport/filecontainer.cpp +++ b/src/libs/clangsupport/filecontainer.cpp @@ -37,7 +37,8 @@ QDebug operator<<(QDebug debug, const FileContainer &container) << container.filePath() << ", " << container.projectPartId() << ", " << container.fileArguments() << ", " - << container.documentRevision(); + << container.documentRevision() << ", " + << container.textCodecName(); if (container.hasUnsavedFileContent()) { const Utf8String fileWithContent = debugWriteFileForInspection( diff --git a/src/libs/clangsupport/filecontainer.h b/src/libs/clangsupport/filecontainer.h index c7cb3f10053..b19c0daa46a 100644 --- a/src/libs/clangsupport/filecontainer.h +++ b/src/libs/clangsupport/filecontainer.h @@ -42,10 +42,12 @@ public: const Utf8String &projectPartId, const Utf8String &unsavedFileContent = Utf8String(), bool hasUnsavedFileContent = false, - quint32 documentRevision = 0) + quint32 documentRevision = 0, + const Utf8String &textCodecName = Utf8String()) : m_filePath(filePath), m_projectPartId(projectPartId), m_unsavedFileContent(unsavedFileContent), + m_textCodecName(textCodecName), m_documentRevision(documentRevision), m_hasUnsavedFileContent(hasUnsavedFileContent) { @@ -98,6 +100,11 @@ public: return m_unsavedFileContent; } + const Utf8String &textCodecName() const + { + return m_textCodecName; + } + bool hasUnsavedFileContent() const { return m_hasUnsavedFileContent; @@ -114,6 +121,7 @@ public: out << container.m_projectPartId; out << container.m_fileArguments; out << container.m_unsavedFileContent; + out << container.m_textCodecName; out << container.m_documentRevision; out << container.m_hasUnsavedFileContent; @@ -126,6 +134,7 @@ public: in >> container.m_projectPartId; in >> container.m_fileArguments; in >> container.m_unsavedFileContent; + in >> container.m_textCodecName; in >> container.m_documentRevision; in >> container.m_hasUnsavedFileContent; @@ -142,6 +151,7 @@ private: Utf8String m_projectPartId; Utf8StringVector m_fileArguments; Utf8String m_unsavedFileContent; + Utf8String m_textCodecName; quint32 m_documentRevision = 0; bool m_hasUnsavedFileContent = false; }; diff --git a/src/libs/clangsupport/messageenvelop.cpp b/src/libs/clangsupport/messageenvelop.cpp index 58139c4c8fd..134616a5b93 100644 --- a/src/libs/clangsupport/messageenvelop.cpp +++ b/src/libs/clangsupport/messageenvelop.cpp @@ -68,6 +68,9 @@ QDebug operator<<(QDebug debug, const MessageEnvelop &messageEnvelop) case MessageType::RequestReferencesMessage: qDebug() << messageEnvelop.message<RequestReferencesMessage>(); break; + case MessageType::RequestToolTipMessage: + qDebug() << messageEnvelop.message<RequestToolTipMessage>(); + break; case MessageType::UpdateVisibleTranslationUnitsMessage: qDebug() << messageEnvelop.message<UpdateVisibleTranslationUnitsMessage>(); break; @@ -83,6 +86,9 @@ QDebug operator<<(QDebug debug, const MessageEnvelop &messageEnvelop) case MessageType::ReferencesMessage: qDebug() << messageEnvelop.message<ReferencesMessage>(); break; + case MessageType::ToolTipMessage: + qDebug() << messageEnvelop.message<ToolTipMessage>(); + break; case MessageType::DocumentAnnotationsChangedMessage: qDebug() << messageEnvelop.message<DocumentAnnotationsChangedMessage>(); break; diff --git a/src/libs/clangsupport/requesttooltipmessage.cpp b/src/libs/clangsupport/requesttooltipmessage.cpp new file mode 100644 index 00000000000..77ffdb46d80 --- /dev/null +++ b/src/libs/clangsupport/requesttooltipmessage.cpp @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** 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 "requesttooltipmessage.h" + +#include <QDebug> + +namespace ClangBackEnd { + +quint64 RequestToolTipMessage::ticketCounter = 0; + +QDebug operator<<(QDebug debug, const RequestToolTipMessage &message) +{ + debug.nospace() << "RequestToolTipMessage("; + + debug.nospace() << message.m_fileContainer << ", "; + debug.nospace() << message.m_ticketNumber << ", "; + debug.nospace() << message.m_line << ", "; + debug.nospace() << message.m_column << ", "; + + debug.nospace() << ")"; + + return debug; +} + +} // namespace ClangBackEnd diff --git a/src/libs/clangsupport/requesttooltipmessage.h b/src/libs/clangsupport/requesttooltipmessage.h new file mode 100644 index 00000000000..066d7358786 --- /dev/null +++ b/src/libs/clangsupport/requesttooltipmessage.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** 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_global.h" + +#include "filecontainer.h" + +#include <QDataStream> + +namespace ClangBackEnd { + +// TODO: De-duplicate with RequestReferencesMessage? +class RequestToolTipMessage +{ +public: + RequestToolTipMessage() = default; + RequestToolTipMessage(const FileContainer &fileContainer, quint32 line, quint32 column) + : m_fileContainer(fileContainer) + , m_ticketNumber(++ticketCounter) + , m_line(line) + , m_column(column) + { + } + + const FileContainer fileContainer() const { return m_fileContainer; } + quint32 line() const { return m_line; } + quint32 column() const { return m_column; } + quint64 ticketNumber() const { return m_ticketNumber; } + + friend QDataStream &operator<<(QDataStream &out, const RequestToolTipMessage &message) + { + out << message.m_fileContainer; + out << message.m_ticketNumber; + out << message.m_line; + out << message.m_column; + + return out; + } + + friend QDataStream &operator>>(QDataStream &in, RequestToolTipMessage &message) + { + in >> message.m_fileContainer; + in >> message.m_ticketNumber; + in >> message.m_line; + in >> message.m_column; + + return in; + } + + friend bool operator==(const RequestToolTipMessage &first, + const RequestToolTipMessage &second) + { + return first.m_ticketNumber == second.m_ticketNumber + && first.m_line == second.m_line + && first.m_column == second.m_column + && first.m_fileContainer == second.m_fileContainer; + } + + friend CLANGSUPPORT_EXPORT QDebug operator<<(QDebug debug, const RequestToolTipMessage &message); + +private: + FileContainer m_fileContainer; + quint64 m_ticketNumber = 0; + quint32 m_line = 0; + quint32 m_column = 0; + static CLANGSUPPORT_EXPORT quint64 ticketCounter; +}; + +DECLARE_MESSAGE(RequestToolTipMessage); +} // namespace ClangBackEnd diff --git a/src/libs/clangsupport/tooltipinfo.cpp b/src/libs/clangsupport/tooltipinfo.cpp new file mode 100644 index 00000000000..fc9a3394214 --- /dev/null +++ b/src/libs/clangsupport/tooltipinfo.cpp @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** 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 "tooltipinfo.h" + +#include <QDebug> + +namespace ClangBackEnd { + +#define RETURN_TEXT_FOR_CASE(enumValue) case ToolTipInfo::enumValue: return #enumValue +const char *qdocCategoryToString(ToolTipInfo::QdocCategory 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 "UnhandledQdocCategory"; +} +#undef RETURN_TEXT_FOR_CASE + +QDebug operator<<(QDebug debug, const ToolTipInfo &message) +{ + debug.nospace() << "ToolTipInfo("; + + debug.nospace() << message.m_text << ", "; + debug.nospace() << message.m_briefComment << ", "; + debug.nospace() << message.m_qdocIdCandidates << ", "; + debug.nospace() << message.m_qdocMark << ", "; + debug.nospace() << qdocCategoryToString(message.m_qdocCategory) << ", "; + debug.nospace() << message.m_sizeInBytes << ", "; + + debug.nospace() << ")"; + + return debug; +} + +} // namespace ClangBackEnd diff --git a/src/libs/clangsupport/tooltipinfo.h b/src/libs/clangsupport/tooltipinfo.h new file mode 100644 index 00000000000..b210d0dfe66 --- /dev/null +++ b/src/libs/clangsupport/tooltipinfo.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** 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 <utf8stringvector.h> + +namespace ClangBackEnd { + +class ToolTipInfo +{ +public: + enum QdocCategory : quint8 { + Unknown, + ClassOrNamespace, + Enum, + Typedef, + Macro, + Brief, + Function, + }; + +public: + ToolTipInfo() = default; + ToolTipInfo(const Utf8String &text) : m_text(text) {} + + const Utf8String &text() const { return m_text; } + void setText(const Utf8String &text) { m_text = text; } + + const Utf8String &briefComment() const { return m_briefComment; } + void setBriefComment(const Utf8String &briefComment) { m_briefComment = briefComment; } + + const Utf8StringVector &qdocIdCandidates() const { return m_qdocIdCandidates; } + void setQdocIdCandidates(const Utf8StringVector &qdocIdCandidates) + { m_qdocIdCandidates = qdocIdCandidates; } + + const Utf8String &qdocMark() const { return m_qdocMark; } + void setQdocMark(const Utf8String &qdocMark) { m_qdocMark = qdocMark; } + + const QdocCategory &qdocCategory() const { return m_qdocCategory; } + void setQdocCategory(const QdocCategory &qdocCategory) { m_qdocCategory = qdocCategory; } + + const Utf8String &sizeInBytes() const { return m_sizeInBytes; } + void setSizeInBytes(const Utf8String &sizeInBytes) { m_sizeInBytes = sizeInBytes; } + + friend QDataStream &operator<<(QDataStream &out, const ToolTipInfo &message) + { + out << message.m_text; + out << message.m_briefComment; + out << message.m_qdocIdCandidates; + out << message.m_qdocMark; + out << static_cast<quint8>(message.m_qdocCategory); + out << message.m_sizeInBytes; + + return out; + } + + friend QDataStream &operator>>(QDataStream &in, ToolTipInfo &message) + { + quint8 qdocCategory; + + in >> message.m_text; + in >> message.m_briefComment; + in >> message.m_qdocIdCandidates; + in >> message.m_qdocMark; + in >> qdocCategory; + in >> message.m_sizeInBytes; + + message.m_qdocCategory = static_cast<QdocCategory>(qdocCategory); + + return in; + } + + friend bool operator==(const ToolTipInfo &first, const ToolTipInfo &second) + { + return first.m_text == second.m_text + && first.m_briefComment == second.m_briefComment + && first.m_qdocIdCandidates == second.m_qdocIdCandidates + && first.m_qdocMark == second.m_qdocMark + && first.m_qdocCategory == second.m_qdocCategory + && first.m_sizeInBytes == second.m_sizeInBytes; + } + + friend QDebug operator<<(QDebug debug, const ToolTipInfo &message); + friend std::ostream &operator<<(std::ostream &os, const ToolTipInfo &message); + +private: + Utf8String m_text; + Utf8String m_briefComment; + + Utf8StringVector m_qdocIdCandidates; + Utf8String m_qdocMark; + QdocCategory m_qdocCategory = Unknown; + + // For class definition and for class fields. + Utf8String m_sizeInBytes; +}; + +const char *qdocCategoryToString(ToolTipInfo::QdocCategory category); + +} // namespace ClangBackEnd diff --git a/src/libs/clangsupport/tooltipmessage.cpp b/src/libs/clangsupport/tooltipmessage.cpp new file mode 100644 index 00000000000..6e64fe33694 --- /dev/null +++ b/src/libs/clangsupport/tooltipmessage.cpp @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** 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 "tooltipmessage.h" + +#include <QDebug> + +namespace ClangBackEnd { + +QDebug operator<<(QDebug debug, const ToolTipMessage &message) +{ + debug.nospace() << "ToolTipMessage(" + << message.fileContainer() + << ", " << message.m_ticketNumber + << ", " << message.m_toolTipInfo; + + debug.nospace() << ")"; + + return debug; +} + +} // namespace ClangBackEnd diff --git a/src/libs/clangsupport/tooltipmessage.h b/src/libs/clangsupport/tooltipmessage.h new file mode 100644 index 00000000000..5d748179a66 --- /dev/null +++ b/src/libs/clangsupport/tooltipmessage.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** 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 "filecontainer.h" +#include "tooltipinfo.h" + +#include <QDataStream> + +namespace ClangBackEnd { + +class ToolTipMessage +{ +public: + ToolTipMessage() = default; + ToolTipMessage(const FileContainer &fileContainer, + const ToolTipInfo &toolTipInfo, + quint64 ticketNumber) + : m_fileContainer(fileContainer) + , m_toolTipInfo(toolTipInfo) + , m_ticketNumber(ticketNumber) + { + } + + const FileContainer &fileContainer() const { return m_fileContainer; } + const ToolTipInfo &toolTipInfo() const { return m_toolTipInfo; } + quint64 ticketNumber() const { return m_ticketNumber; } + + friend QDataStream &operator<<(QDataStream &out, const ToolTipMessage &message) + { + out << message.m_fileContainer; + out << message.m_toolTipInfo;; + out << message.m_ticketNumber; + + return out; + } + + friend QDataStream &operator>>(QDataStream &in, ToolTipMessage &message) + { + in >> message.m_fileContainer; + in >> message.m_toolTipInfo; + in >> message.m_ticketNumber; + + return in; + } + + friend bool operator==(const ToolTipMessage &first, const ToolTipMessage &second) + { + return first.m_ticketNumber == second.m_ticketNumber + && first.m_fileContainer == second.m_fileContainer + && first.m_toolTipInfo == second.m_toolTipInfo; + } + + friend CLANGSUPPORT_EXPORT QDebug operator<<(QDebug debug, const ToolTipMessage &message); + friend std::ostream &operator<<(std::ostream &os, const ToolTipMessage &message); + +private: + FileContainer m_fileContainer; + ToolTipInfo m_toolTipInfo; + quint64 m_ticketNumber = 0; +}; + +DECLARE_MESSAGE(ToolTipMessage) +} // namespace ClangBackEnd diff --git a/src/libs/sqlite/utf8string.h b/src/libs/sqlite/utf8string.h index 3c0f48519a2..5aa38956d15 100644 --- a/src/libs/sqlite/utf8string.h +++ b/src/libs/sqlite/utf8string.h @@ -171,7 +171,8 @@ public: byteArray.reserve(reserveSize); } - static Utf8String number(int number, int base=10) + template<typename T> + static Utf8String number(T number, int base = 10) { return Utf8String::fromByteArray(QByteArray::number(number, base)); } diff --git a/src/plugins/clangcodemodel/clangbackendcommunicator.cpp b/src/plugins/clangcodemodel/clangbackendcommunicator.cpp index 56504793122..1abd6982edf 100644 --- a/src/plugins/clangcodemodel/clangbackendcommunicator.cpp +++ b/src/plugins/clangcodemodel/clangbackendcommunicator.cpp @@ -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, diff --git a/src/plugins/clangcodemodel/clangbackendcommunicator.h b/src/plugins/clangcodemodel/clangbackendcommunicator.h index bd7494a6c20..b3cb166a391 100644 --- a/src/plugins/clangcodemodel/clangbackendcommunicator.h +++ b/src/plugins/clangcodemodel/clangbackendcommunicator.h @@ -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); diff --git a/src/plugins/clangcodemodel/clangbackendreceiver.cpp b/src/plugins/clangcodemodel/clangbackendreceiver.cpp index 8d5ec92d00d..f2606d74cc5 100644 --- a/src/plugins/clangcodemodel/clangbackendreceiver.cpp +++ b/src/plugins/clangcodemodel/clangbackendreceiver.cpp @@ -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" diff --git a/src/plugins/clangcodemodel/clangbackendreceiver.h b/src/plugins/clangcodemodel/clangbackendreceiver.h index d7a966eae98..921150a52dc 100644 --- a/src/plugins/clangcodemodel/clangbackendreceiver.h +++ b/src/plugins/clangcodemodel/clangbackendreceiver.h @@ -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; }; diff --git a/src/plugins/clangcodemodel/clangbackendsender.cpp b/src/plugins/clangcodemodel/clangbackendsender.cpp index 089377083fb..f6f8aea1cf8 100644 --- a/src/plugins/clangcodemodel/clangbackendsender.cpp +++ b/src/plugins/clangcodemodel/clangbackendsender.cpp @@ -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()); diff --git a/src/plugins/clangcodemodel/clangbackendsender.h b/src/plugins/clangcodemodel/clangbackendsender.h index ca9238ae651..f01326a4dc3 100644 --- a/src/plugins/clangcodemodel/clangbackendsender.h +++ b/src/plugins/clangcodemodel/clangbackendsender.h @@ -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; diff --git a/src/plugins/clangcodemodel/clangcodemodel.pro b/src/plugins/clangcodemodel/clangcodemodel.pro index 9136db5db5c..0e444a350e2 100644 --- a/src/plugins/clangcodemodel/clangcodemodel.pro +++ b/src/plugins/clangcodemodel/clangcodemodel.pro @@ -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 \ diff --git a/src/plugins/clangcodemodel/clangcodemodel.qbs b/src/plugins/clangcodemodel/clangcodemodel.qbs index cc81025925f..90105b86d38 100644 --- a/src/plugins/clangcodemodel/clangcodemodel.qbs +++ b/src/plugins/clangcodemodel/clangcodemodel.qbs @@ -80,6 +80,8 @@ QtcPlugin { "clangfollowsymbol.h", "clangfunctionhintmodel.cpp", "clangfunctionhintmodel.h", + "clanghoverhandler.cpp", + "clanghoverhandler.h", "clangtokeninfosreporter.cpp", "clangtokeninfosreporter.h", "clangisdiagnosticrelatedtolocation.h", diff --git a/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp b/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp index 6eb98b2c92f..a282350f246 100644 --- a/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp +++ b/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp @@ -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) diff --git a/src/plugins/clangcodemodel/clangeditordocumentprocessor.h b/src/plugins/clangcodemodel/clangeditordocumentprocessor.h index 2bb7ca04633..67745b582cb 100644 --- a/src/plugins/clangcodemodel/clangeditordocumentprocessor.h +++ b/src/plugins/clangcodemodel/clangeditordocumentprocessor.h @@ -88,6 +88,9 @@ public: QFuture<CppTools::CursorInfo> cursorInfo(const CppTools::CursorInfoParams ¶ms) 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; diff --git a/src/plugins/clangcodemodel/clanghoverhandler.cpp b/src/plugins/clangcodemodel/clanghoverhandler.cpp new file mode 100644 index 00000000000..6610b555c22 --- /dev/null +++ b/src/plugins/clangcodemodel/clanghoverhandler.cpp @@ -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 diff --git a/src/plugins/clangcodemodel/clanghoverhandler.h b/src/plugins/clangcodemodel/clanghoverhandler.h new file mode 100644 index 00000000000..85b7999f94b --- /dev/null +++ b/src/plugins/clangcodemodel/clanghoverhandler.h @@ -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 diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp index 9271975dfde..9495e6d5ebc 100644 --- a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp +++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp @@ -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() diff --git a/src/plugins/cpptools/baseeditordocumentprocessor.cpp b/src/plugins/cpptools/baseeditordocumentprocessor.cpp index 5bffb51a382..20632289c68 100644 --- a/src/plugins/cpptools/baseeditordocumentprocessor.cpp +++ b/src/plugins/cpptools/baseeditordocumentprocessor.cpp @@ -97,6 +97,13 @@ void BaseEditorDocumentProcessor::setParserConfig( parser()->setConfiguration(config); } +QFuture<ToolTipInfo> BaseEditorDocumentProcessor::toolTipInfo(const QByteArray &/*codecName*/, + int /*line*/, + int /*column*/) +{ + return QFuture<ToolTipInfo>(); +} + void BaseEditorDocumentProcessor::runParser(QFutureInterface<void> &future, BaseEditorDocumentParser::Ptr parser, BaseEditorDocumentParser::UpdateParams updateParams) diff --git a/src/plugins/cpptools/baseeditordocumentprocessor.h b/src/plugins/cpptools/baseeditordocumentprocessor.h index b3f96d466de..d799c636b25 100644 --- a/src/plugins/cpptools/baseeditordocumentprocessor.h +++ b/src/plugins/cpptools/baseeditordocumentprocessor.h @@ -32,6 +32,7 @@ #include "cpptools_global.h" #include <texteditor/codeassist/assistinterface.h> +#include <texteditor/helpitem.h> #include <texteditor/quickfix.h> #include <texteditor/texteditor.h> #include <texteditor/textdocument.h> @@ -48,6 +49,18 @@ class TextDocument; namespace CppTools { +// For clang code model only, move? +struct CPPTOOLS_EXPORT ToolTipInfo { + QString text; + QString briefComment; + + QStringList qDocIdCandidates; + QString qDocMark; + TextEditor::HelpItem::Category qDocCategory; + + QString sizeInBytes; +}; + class CPPTOOLS_EXPORT BaseEditorDocumentProcessor : public QObject { Q_OBJECT @@ -78,6 +91,7 @@ public: virtual QFuture<CursorInfo> cursorInfo(const CursorInfoParams ¶ms) = 0; virtual QFuture<CursorInfo> requestLocalReferences(const QTextCursor &cursor) = 0; virtual QFuture<SymbolInfo> requestFollowSymbol(int line, int column) = 0; + virtual QFuture<ToolTipInfo> toolTipInfo(const QByteArray &codecName, int line, int column); public: using HeaderErrorDiagnosticWidgetCreator = std::function<QWidget*()>; diff --git a/src/plugins/cpptools/cpphoverhandler.cpp b/src/plugins/cpptools/cpphoverhandler.cpp index 9b9a8edff74..1ff41b7fdc3 100644 --- a/src/plugins/cpptools/cpphoverhandler.cpp +++ b/src/plugins/cpptools/cpphoverhandler.cpp @@ -28,78 +28,47 @@ #include "cppelementevaluator.h" #include <coreplugin/helpmanager.h> -#include <cpptools/baseeditordocumentprocessor.h> -#include <cpptools/cppmodelmanager.h> -#include <cpptools/editordocumenthandle.h> #include <texteditor/texteditor.h> #include <utils/textutils.h> -#include <utils/qtcassert.h> -#include <utils/tooltip/tooltip.h> #include <QTextCursor> #include <QUrl> -#include <QVBoxLayout> using namespace Core; using namespace TextEditor; -namespace { - -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; -} +namespace CppTools { -bool editorDocumentProcessorHasDiagnosticAt(TextEditorWidget *editorWidget, int pos) +QString CppHoverHandler::tooltipTextForHelpItem(const HelpItem &helpItem) { - if (CppTools::BaseEditorDocumentProcessor *processor = editorDocumentProcessor(editorWidget)) { - int line, column; - if (Utils::Text::convertPosition(editorWidget->document(), pos, &line, &column)) - return processor->hasDiagnosticsAt(line, column); + // If Qt is built with a namespace, we still show the tip without it, as + // it is in the docs and for consistency with the doc extraction mechanism. + const HelpItem::Category category = helpItem.category(); + const QString &contents = helpItem.extractContent(false); + if (!contents.isEmpty()) { + if (category == HelpItem::ClassOrNamespace) + return helpItem.helpId() + contents; + else + return contents; + } else if (category == HelpItem::Typedef || + category == HelpItem::Enum || + category == HelpItem::ClassOrNamespace) { + // This approach is a bit limited since it cannot be used for functions + // because the help id doesn't really help in that case. + QString prefix; + if (category == HelpItem::Typedef) + prefix = QLatin1String("typedef "); + else if (category == HelpItem::Enum) + prefix = QLatin1String("enum "); + return prefix + helpItem.helpId(); } - return false; -} - -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); - } - } + return QString(); } -} // anonymous namespace - -namespace CppTools { - void CppHoverHandler::identifyMatch(TextEditorWidget *editorWidget, int pos) { - m_positionForEditorDocumentProcessor = -1; - - if (editorDocumentProcessorHasDiagnosticAt(editorWidget, pos)) { - setPriority(Priority_Diagnostic); - m_positionForEditorDocumentProcessor = pos; - return; - } - QTextCursor tc(editorWidget->document()); tc.setPosition(pos); @@ -135,9 +104,6 @@ void CppHoverHandler::identifyMatch(TextEditorWidget *editorWidget, int pos) void CppHoverHandler::decorateToolTip() { - if (m_positionForEditorDocumentProcessor != -1) - return; - if (Qt::mightBeRichText(toolTip())) setToolTip(toolTip().toHtmlEscaped()); @@ -146,42 +112,10 @@ void CppHoverHandler::decorateToolTip() const HelpItem &help = lastHelpItemIdentified(); if (help.isValid()) { - // If Qt is built with a namespace, we still show the tip without it, as - // it is in the docs and for consistency with the doc extraction mechanism. - const HelpItem::Category category = help.category(); - const QString &contents = help.extractContent(false); - if (!contents.isEmpty()) { - if (category == HelpItem::ClassOrNamespace) - setToolTip(help.helpId() + contents); - else - setToolTip(contents); - } else if (category == HelpItem::Typedef || - category == HelpItem::Enum || - category == HelpItem::ClassOrNamespace) { - // This approach is a bit limited since it cannot be used for functions - // because the help id doesn't really help in that case. - QString prefix; - if (category == HelpItem::Typedef) - prefix = QLatin1String("typedef "); - else if (category == HelpItem::Enum) - prefix = QLatin1String("enum "); - setToolTip(prefix + help.helpId()); - } + const QString text = tooltipTextForHelpItem(help); + if (!text.isEmpty()) + setToolTip(text); } } -void CppHoverHandler::operateTooltip(TextEditor::TextEditorWidget *editorWidget, - const QPoint &point) -{ - if (m_positionForEditorDocumentProcessor == -1) { - BaseHoverHandler::operateTooltip(editorWidget, point); - return; - } - - const HelpItem helpItem = lastHelpItemIdentified(); - const QString helpId = helpItem.isValid() ? helpItem.helpId() : QString(); - processWithEditorDocumentProcessor(editorWidget, point, m_positionForEditorDocumentProcessor, - helpId); -} - } // namespace CppTools diff --git a/src/plugins/cpptools/cpphoverhandler.h b/src/plugins/cpptools/cpphoverhandler.h index b728b970fe4..edfa6901cf8 100644 --- a/src/plugins/cpptools/cpphoverhandler.h +++ b/src/plugins/cpptools/cpphoverhandler.h @@ -33,13 +33,12 @@ namespace CppTools { class CPPTOOLS_EXPORT CppHoverHandler : public TextEditor::BaseHoverHandler { +public: + static QString tooltipTextForHelpItem(const TextEditor::HelpItem &help); + private: void identifyMatch(TextEditor::TextEditorWidget *editorWidget, int pos) override; void decorateToolTip() override; - void operateTooltip(TextEditor::TextEditorWidget *editorWidget, const QPoint &point) override; - -private: - int m_positionForEditorDocumentProcessor = -1; }; } // namespace CppTools diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index c3fb788d25b..4b2b428f019 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -309,6 +309,7 @@ public: const int documentRevision = textCursor.document()->revision(); const int position = Text::wordStartCursor(textCursor).position(); if (m_lastHandlerInfo.applies(documentRevision, position)) { + qDebug() << "Last handler applies, showing it"; m_lastHandlerInfo.handler->showToolTip(m_widget, point, /*decorate=*/ false); return; } @@ -364,6 +365,7 @@ public: // All were queried, run the best if (m_bestHandler) { + qDebug() << "setting last handler info:" << m_documentRevision << m_position; m_lastHandlerInfo = LastHandlerInfo(m_bestHandler, m_documentRevision, m_position); m_bestHandler->showToolTip(m_widget, m_point); } diff --git a/src/tools/clangbackend/source/clangbackend_global.h b/src/tools/clangbackend/source/clangbackend_global.h index e62c9a35130..a7ca7859587 100644 --- a/src/tools/clangbackend/source/clangbackend_global.h +++ b/src/tools/clangbackend/source/clangbackend_global.h @@ -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 diff --git a/src/tools/clangbackend/source/clangbackendclangipc-source.pri b/src/tools/clangbackend/source/clangbackendclangipc-source.pri index 3460e9d297d..154b5cc0194 100644 --- a/src/tools/clangbackend/source/clangbackendclangipc-source.pri +++ b/src/tools/clangbackend/source/clangbackendclangipc-source.pri @@ -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 \ diff --git a/src/tools/clangbackend/source/clangcodemodelserver.cpp b/src/tools/clangbackend/source/clangcodemodelserver.cpp index 6e3a4290b80..609ac1da85c 100644 --- a/src/tools/clangbackend/source/clangcodemodelserver.cpp +++ b/src/tools/clangbackend/source/clangcodemodelserver.cpp @@ -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"; diff --git a/src/tools/clangbackend/source/clangcodemodelserver.h b/src/tools/clangbackend/source/clangcodemodelserver.h index 1591d25743c..98508f348c5 100644 --- a/src/tools/clangbackend/source/clangcodemodelserver.h +++ b/src/tools/clangbackend/source/clangcodemodelserver.h @@ -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; diff --git a/src/tools/clangbackend/source/clangjobrequest.cpp b/src/tools/clangbackend/source/clangjobrequest.cpp index b40ea424257..e2c6f8f6d49 100644 --- a/src/tools/clangbackend/source/clangjobrequest.cpp +++ b/src/tools/clangbackend/source/clangjobrequest.cpp @@ -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, diff --git a/src/tools/clangbackend/source/clangjobrequest.h b/src/tools/clangbackend/source/clangjobrequest.h index 4b79f22792e..81c78ba9dbc 100644 --- a/src/tools/clangbackend/source/clangjobrequest.h +++ b/src/tools/clangbackend/source/clangjobrequest.h @@ -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; }; diff --git a/src/tools/clangbackend/source/clangrequesttooltipjob.cpp b/src/tools/clangbackend/source/clangrequesttooltipjob.cpp new file mode 100644 index 00000000000..0e1249e4e8b --- /dev/null +++ b/src/tools/clangbackend/source/clangrequesttooltipjob.cpp @@ -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 diff --git a/src/tools/clangbackend/source/clangrequesttooltipjob.h b/src/tools/clangbackend/source/clangrequesttooltipjob.h new file mode 100644 index 00000000000..eb121402cb2 --- /dev/null +++ b/src/tools/clangbackend/source/clangrequesttooltipjob.h @@ -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 diff --git a/src/tools/clangbackend/source/clangtooltipinfocollector.cpp b/src/tools/clangbackend/source/clangtooltipinfocollector.cpp new file mode 100644 index 00000000000..44dde1edbbc --- /dev/null +++ b/src/tools/clangbackend/source/clangtooltipinfocollector.cpp @@ -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 diff --git a/src/tools/clangbackend/source/clangtooltipinfocollector.h b/src/tools/clangbackend/source/clangtooltipinfocollector.h new file mode 100644 index 00000000000..8009aa8bf4a --- /dev/null +++ b/src/tools/clangbackend/source/clangtooltipinfocollector.h @@ -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 diff --git a/src/tools/clangbackend/source/clangtranslationunit.cpp b/src/tools/clangbackend/source/clangtranslationunit.cpp index 94827801136..da06999b614 100644 --- a/src/tools/clangbackend/source/clangtranslationunit.cpp +++ b/src/tools/clangbackend/source/clangtranslationunit.cpp @@ -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); diff --git a/src/tools/clangbackend/source/clangtranslationunit.h b/src/tools/clangbackend/source/clangtranslationunit.h index 76ba3f00551..150645cc362 100644 --- a/src/tools/clangbackend/source/clangtranslationunit.h +++ b/src/tools/clangbackend/source/clangtranslationunit.h @@ -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; diff --git a/src/tools/clangbackend/source/clangtype.cpp b/src/tools/clangbackend/source/clangtype.cpp index 8fc09d8c4af..970b9452c1d 100644 --- a/src/tools/clangbackend/source/clangtype.cpp +++ b/src/tools/clangbackend/source/clangtype.cpp @@ -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; diff --git a/src/tools/clangbackend/source/clangtype.h b/src/tools/clangbackend/source/clangtype.h index f26fe080f6b..6be968d3901 100644 --- a/src/tools/clangbackend/source/clangtype.h +++ b/src/tools/clangbackend/source/clangtype.h @@ -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: diff --git a/src/tools/clangbackend/source/cursor.cpp b/src/tools/clangbackend/source/cursor.cpp index 6ad50307cdd..41a0465e7a0 100644 --- a/src/tools/clangbackend/source/cursor.cpp +++ b/src/tools/clangbackend/source/cursor.cpp @@ -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); diff --git a/src/tools/clangbackend/source/cursor.h b/src/tools/clangbackend/source/cursor.h index 6520366b36d..bef5a006f4f 100644 --- a/src/tools/clangbackend/source/cursor.h +++ b/src/tools/clangbackend/source/cursor.h @@ -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; }; diff --git a/src/tools/clangbackend/source/unsavedfile.cpp b/src/tools/clangbackend/source/unsavedfile.cpp index 923d4456e8a..efcad4cad47 100644 --- a/src/tools/clangbackend/source/unsavedfile.cpp +++ b/src/tools/clangbackend/source/unsavedfile.cpp @@ -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())) diff --git a/src/tools/clangbackend/source/unsavedfile.h b/src/tools/clangbackend/source/unsavedfile.h index 3021da89b5c..325139d240d 100644 --- a/src/tools/clangbackend/source/unsavedfile.h +++ b/src/tools/clangbackend/source/unsavedfile.h @@ -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; diff --git a/tests/unit/echoserver/echoclangcodemodelserver.cpp b/tests/unit/echoserver/echoclangcodemodelserver.cpp index 2c0fea98c93..38d2d395444 100644 --- a/tests/unit/echoserver/echoclangcodemodelserver.cpp +++ b/tests/unit/echoserver/echoclangcodemodelserver.cpp @@ -98,6 +98,11 @@ void EchoClangCodeModelServer::requestFollowSymbol(const RequestFollowSymbolMess echoMessage(message); } +void EchoClangCodeModelServer::requestToolTip(const RequestToolTipMessage &message) +{ + echoMessage(message); +} + void EchoClangCodeModelServer::updateVisibleTranslationUnits(const UpdateVisibleTranslationUnitsMessage &message) { echoMessage(message); diff --git a/tests/unit/echoserver/echoclangcodemodelserver.h b/tests/unit/echoserver/echoclangcodemodelserver.h index 2dc8c30913a..d3452d2d321 100644 --- a/tests/unit/echoserver/echoclangcodemodelserver.h +++ b/tests/unit/echoserver/echoclangcodemodelserver.h @@ -47,6 +47,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; void updateVisibleTranslationUnits(const UpdateVisibleTranslationUnitsMessage &message) override; private: diff --git a/tests/unit/unittest/clangtooltipinfo-test.cpp b/tests/unit/unittest/clangtooltipinfo-test.cpp new file mode 100644 index 00000000000..4f6c9806496 --- /dev/null +++ b/tests/unit/unittest/clangtooltipinfo-test.cpp @@ -0,0 +1,628 @@ +/**************************************************************************** +** +** 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 "googletest.h" +#include "rundocumentparse-utility.h" + +#include <clangdocument.h> +#include <clangdocuments.h> +#include <clangsupport_global.h> +#include <clangtooltipinfocollector.h> +#include <clangtranslationunit.h> +#include <fixitcontainer.h> +#include <projectpart.h> +#include <projects.h> +#include <sourcelocationcontainer.h> +#include <sourcerangecontainer.h> +#include <unsavedfiles.h> + +#include <utils/qtcassert.h> + +#include <clang-c/Index.h> + +using ::ClangBackEnd::ProjectPart; +using ::ClangBackEnd::SourceLocationContainer; +using ::ClangBackEnd::Document; +using ::ClangBackEnd::UnsavedFiles; +using ::ClangBackEnd::ToolTipInfo; +using ::ClangBackEnd::SourceRangeContainer; + +namespace { + +#define CHECK_MEMBER(actual, expected, memberName) \ + if (actual.memberName() != expected.memberName()) { \ + *result_listener << #memberName " is " + PrintToString(actual.memberName()) \ + << " and not " + PrintToString(expected.memberName()); \ + return false; \ + } + +MATCHER_P(IsToolTip, expected, std::string(negation ? "isn't" : "is") + PrintToString(expected)) +{ + CHECK_MEMBER(arg, expected, text); + CHECK_MEMBER(arg, expected, briefComment); + + CHECK_MEMBER(arg, expected, qdocIdCandidates); + CHECK_MEMBER(arg, expected, qdocMark); + CHECK_MEMBER(arg, expected, qdocCategory); + + CHECK_MEMBER(arg, expected, sizeInBytes); + + return true; +} + +MATCHER_P(IsQdocToolTip, expected, std::string(negation ? "isn't" : "is") + PrintToString(expected)) +{ + CHECK_MEMBER(arg, expected, qdocIdCandidates); + CHECK_MEMBER(arg, expected, qdocMark); + CHECK_MEMBER(arg, expected, qdocCategory); + + return true; +} + +#undef CHECK_MEMBER + +struct Data { + ProjectPart projectPart{Utf8StringLiteral("projectPartId"), {Utf8StringLiteral("-std=c++14")}}; + ClangBackEnd::ProjectParts projects; + ClangBackEnd::UnsavedFiles unsavedFiles; + ClangBackEnd::Documents documents{projects, unsavedFiles}; + Document document{Utf8StringLiteral(TESTDATA_DIR "/tooltipinfo.cpp"), + projectPart, + {}, + documents}; + UnitTest::RunDocumentParse _1{document}; +}; + +class ToolTipInfo : public ::testing::Test +{ +protected: + ::ToolTipInfo tooltip(uint line, uint column) + { + return d->document.translationUnit().tooltip(d->unsavedFiles, + Utf8StringLiteral("UTF-8"), + line, + column); + } + + static void SetUpTestCase(); + static void TearDownTestCase(); + +private: + static std::unique_ptr<Data> d; +}; + +TEST_F(ToolTipInfo, LocalVariableInt) +{ + const ::ToolTipInfo actual = tooltip(3, 5); + + ASSERT_THAT(actual, IsToolTip(::ToolTipInfo(Utf8StringLiteral("int")))); +} + +TEST_F(ToolTipInfo, LocalVariablePointerToConstInt) +{ + const ::ToolTipInfo actual = tooltip(4, 5); + + ASSERT_THAT(actual, IsToolTip(::ToolTipInfo(Utf8StringLiteral("const int *")))); +} + +TEST_F(ToolTipInfo, LocalParameterVariableConstRefCustomType) +{ + ::ToolTipInfo expected(Utf8StringLiteral("const Foo &")); + expected.setQdocIdCandidates({Utf8StringLiteral("Foo")}); + expected.setQdocMark(Utf8StringLiteral("Foo")); + expected.setQdocCategory(::ToolTipInfo::ClassOrNamespace); + + const ::ToolTipInfo actual = tooltip(12, 12); + + ASSERT_THAT(actual, IsToolTip(expected)); +} + +TEST_F(ToolTipInfo, LocalNonParameterVariableConstRefCustomType) +{ + ::ToolTipInfo expected(Utf8StringLiteral("const Foo")); + expected.setQdocIdCandidates({Utf8StringLiteral("Foo")}); + expected.setQdocMark(Utf8StringLiteral("Foo")); + expected.setQdocCategory(::ToolTipInfo::ClassOrNamespace); + + const ::ToolTipInfo actual = tooltip(14, 5); + + ASSERT_THAT(actual, IsToolTip(expected)); +} + +TEST_F(ToolTipInfo, MemberVariable) +{ + const ::ToolTipInfo actual = tooltip(12, 16); + + ASSERT_THAT(actual, IsToolTip(::ToolTipInfo(Utf8StringLiteral("int")))); +} + +TEST_F(ToolTipInfo, DISABLED_WITHOUT_PRETTYDECL_PATCH(MemberFunctionCall_QualifiedName)) +{ + const ::ToolTipInfo actual = tooltip(21, 9); + + ASSERT_THAT(actual.text(), Utf8StringLiteral("int Bar::mem()")); +} + +// ChangeLog: Show extra specifiers. For functions e.g.: virtual, inline, explicit, const, volatile +TEST_F(ToolTipInfo, DISABLED_WITHOUT_PRETTYDECL_PATCH(MemberFunctionCall_ExtraSpecifiers)) +{ + const ::ToolTipInfo actual = tooltip(22, 9); + + ASSERT_THAT(actual.text(), Utf8StringLiteral("virtual int Bar::virtualConstMem() const")); +} + +TEST_F(ToolTipInfo, MemberFunctionCall_qdocIdCandidates) +{ + const ::ToolTipInfo actual = tooltip(21, 9); + + ASSERT_THAT(actual.qdocIdCandidates(), ElementsAre(Utf8StringLiteral("Bar::mem"), + Utf8StringLiteral("mem"))); +} + +TEST_F(ToolTipInfo, MemberFunctionCall_qdocMark_FIXLIBCLANG_CHECKED) +{ + const ::ToolTipInfo actual = tooltip(21, 9); + + ASSERT_THAT(actual.qdocMark(), Utf8StringLiteral("mem()")); +} + +// TODO: Check what is really needed for qdoc before implementing this one. +TEST_F(ToolTipInfo, DISABLED_MemberFunctionCall_qdocMark_extraSpecifiers) +{ + const ::ToolTipInfo actual = tooltip(22, 9); + + ASSERT_THAT(actual.qdocMark(), Utf8StringLiteral("virtualConstMem() const")); +} + +TEST_F(ToolTipInfo, MemberFunctionCall_qdocCategory) +{ + const ::ToolTipInfo actual = tooltip(21, 9); + + ASSERT_THAT(actual.qdocCategory(), ::ToolTipInfo::Function); +} + +// TODO: Show the template parameter type, too: "template<typename T>...)" +TEST_F(ToolTipInfo, DISABLED_WITHOUT_PRETTYDECL_PATCH(TemplateFunctionCall)) +{ + const ::ToolTipInfo actual = tooltip(30, 5); + + ASSERT_THAT(actual.text(), Utf8StringLiteral("template<> void t<Foo>(int foo)")); +} + +TEST_F(ToolTipInfo, TemplateFunctionCall_qdocIdCandidates) +{ + const ::ToolTipInfo actual = tooltip(30, 5); + + ASSERT_THAT(actual.qdocIdCandidates(), ElementsAre(Utf8StringLiteral("t"))); +} + +TEST_F(ToolTipInfo, TemplateFunctionCall_qdocMark_FIXLIBCLANG_CHECKED) +{ + const ::ToolTipInfo actual = tooltip(30, 5); + + ASSERT_THAT(actual.qdocMark(), Utf8StringLiteral("t(int)")); +} + +TEST_F(ToolTipInfo, TemplateFunctionCall_qdocCategory) +{ + const ::ToolTipInfo actual = tooltip(30, 5); + + ASSERT_THAT(actual.qdocCategory(), ::ToolTipInfo::Function); +} + +TEST_F(ToolTipInfo, BriefComment) +{ + const ::ToolTipInfo actual = tooltip(41, 5); + + ASSERT_THAT(actual.briefComment(), Utf8StringLiteral("This is a crazy function.")); +} + +TEST_F(ToolTipInfo, Enum) +{ + ::ToolTipInfo expected(Utf8StringLiteral("EnumType")); + expected.setQdocIdCandidates({Utf8StringLiteral("EnumType")}); + expected.setQdocMark(Utf8StringLiteral("EnumType")); + expected.setQdocCategory(::ToolTipInfo::Enum); + + const ::ToolTipInfo actual = tooltip(49, 12); + + ASSERT_THAT(actual, IsToolTip(expected)); +} + +TEST_F(ToolTipInfo, Enumerator) +{ + ::ToolTipInfo expected(Utf8StringLiteral("6")); + expected.setQdocIdCandidates({Utf8StringLiteral("Custom")}); + expected.setQdocMark(Utf8StringLiteral("EnumType")); + expected.setQdocCategory(::ToolTipInfo::Enum); + + const ::ToolTipInfo actual = tooltip(49, 22); + + ASSERT_THAT(actual, IsToolTip(expected)); +} + +TEST_F(ToolTipInfo, TemplateTypeFromParameter) +{ + ::ToolTipInfo expected(Utf8StringLiteral("const Baz<int> &")); + expected.setQdocIdCandidates({Utf8StringLiteral("Baz")}); + expected.setQdocMark(Utf8StringLiteral("Baz")); + expected.setQdocCategory(::ToolTipInfo::ClassOrNamespace); + + const ::ToolTipInfo actual = tooltip(55, 25); + + ASSERT_THAT(actual, IsQdocToolTip(expected)); +} + +TEST_F(ToolTipInfo, TemplateTypeFromNonParameter) +{ + ::ToolTipInfo expected(Utf8StringLiteral("Baz<int>")); + expected.setQdocIdCandidates({Utf8StringLiteral("Baz")}); + expected.setQdocMark(Utf8StringLiteral("Baz")); + expected.setQdocCategory(::ToolTipInfo::ClassOrNamespace); + + const ::ToolTipInfo actual = tooltip(56, 19); + + ASSERT_THAT(actual, IsToolTip(expected)); +} + +TEST_F(ToolTipInfo, IncludeDirective) +{ + ::ToolTipInfo expected(Utf8StringLiteral(TESTDATA_DIR"/tooltipinfo.h")); + expected.setQdocIdCandidates({Utf8StringLiteral("tooltipinfo.h")}); + expected.setQdocMark(Utf8StringLiteral("tooltipinfo.h")); + expected.setQdocCategory(::ToolTipInfo::Brief); + + const ::ToolTipInfo actual = tooltip(59, 11); + + ASSERT_THAT(actual, IsToolTip(expected)); +} + +TEST_F(ToolTipInfo, MacroUse_WithMacroFromSameFile) +{ + const ::ToolTipInfo actual = tooltip(66, 5); + + ASSERT_THAT(actual.text(), Utf8StringLiteral("#define MACRO_FROM_MAINFILE(x) x + 3")); +} + +TEST_F(ToolTipInfo, MacroUse_WithMacroFromHeader) +{ + const ::ToolTipInfo actual = tooltip(67, 5); + + ASSERT_THAT(actual.text(), Utf8StringLiteral("#define MACRO_FROM_HEADER(x) x + \\\n x + \\\n x")); +} + +TEST_F(ToolTipInfo, MacroUse_qdoc) +{ + ::ToolTipInfo expected; + expected.setQdocIdCandidates({Utf8StringLiteral("MACRO_FROM_MAINFILE")}); + expected.setQdocMark(Utf8StringLiteral("MACRO_FROM_MAINFILE")); + expected.setQdocCategory(::ToolTipInfo::Macro); + + const ::ToolTipInfo actual = tooltip(66, 5); + + ASSERT_THAT(actual, IsQdocToolTip(expected)); +} + +TEST_F(ToolTipInfo, TypeNameIntroducedByUsingDirectiveIsQualified) +{ + ::ToolTipInfo expected(Utf8StringLiteral("N::Muu")); + expected.setQdocIdCandidates({Utf8StringLiteral("N::Muu"), Utf8StringLiteral("Muu")}); + expected.setQdocMark(Utf8StringLiteral("Muu")); + expected.setQdocCategory(::ToolTipInfo::ClassOrNamespace); + + const ::ToolTipInfo actual = tooltip(77, 5); + + ASSERT_THAT(actual, IsToolTip(expected)); +} + +TEST_F(ToolTipInfo, TypeNameIntroducedByUsingDirectiveOfAliasIsResolvedAndQualified) +{ + ::ToolTipInfo expected(Utf8StringLiteral("N::Muu")); + expected.setQdocIdCandidates({Utf8StringLiteral("N::Muu"), Utf8StringLiteral("Muu")}); + expected.setQdocMark(Utf8StringLiteral("Muu")); + expected.setQdocCategory(::ToolTipInfo::ClassOrNamespace); + + const ::ToolTipInfo actual = tooltip(82, 5); + + ASSERT_THAT(actual, IsToolTip(expected)); +} + +TEST_F(ToolTipInfo, TypeNameIntroducedByUsingDeclarationIsQualified) +{ + ::ToolTipInfo expected(Utf8StringLiteral("N::Muu")); + expected.setQdocIdCandidates({Utf8StringLiteral("N::Muu"), Utf8StringLiteral("Muu")}); + expected.setQdocMark(Utf8StringLiteral("Muu")); + expected.setQdocCategory(::ToolTipInfo::ClassOrNamespace); + + const ::ToolTipInfo actual = tooltip(87, 5); + + ASSERT_THAT(actual, IsToolTip(expected)); +} + +TEST_F(ToolTipInfo, SizeForClassDefinition) +{ + const ::ToolTipInfo actual = tooltip(92, 8); + + ASSERT_THAT(actual.sizeInBytes(), Utf8StringLiteral("2")); +} + +TEST_F(ToolTipInfo, SizeForMemberField) +{ + const ::ToolTipInfo actual = tooltip(95, 10); + + ASSERT_THAT(actual.sizeInBytes(), Utf8StringLiteral("1")); +} + +TEST_F(ToolTipInfo, SizeForEnum) +{ + const ::ToolTipInfo actual = tooltip(97, 12); + + ASSERT_THAT(actual.sizeInBytes(), Utf8StringLiteral("4")); +} + +TEST_F(ToolTipInfo, SizeForUnion) +{ + const ::ToolTipInfo actual = tooltip(98, 7); + + ASSERT_THAT(actual.sizeInBytes(), Utf8StringLiteral("1")); +} + +TEST_F(ToolTipInfo, Namespace) +{ + ::ToolTipInfo expected(Utf8StringLiteral("X")); + expected.setQdocIdCandidates({Utf8StringLiteral("X")}); + expected.setQdocMark(Utf8StringLiteral("X")); + expected.setQdocCategory(::ToolTipInfo::ClassOrNamespace); + + const ::ToolTipInfo actual = tooltip(106, 11); + + ASSERT_THAT(actual, IsToolTip(expected)); +} + +TEST_F(ToolTipInfo, NamespaceQualified) +{ + ::ToolTipInfo expected(Utf8StringLiteral("X::Y")); + expected.setQdocIdCandidates({Utf8StringLiteral("X::Y"), Utf8StringLiteral("Y")}); + expected.setQdocMark(Utf8StringLiteral("Y")); + expected.setQdocCategory(::ToolTipInfo::ClassOrNamespace); + + const ::ToolTipInfo actual = tooltip(107, 11); + + ASSERT_THAT(actual, IsToolTip(expected)); +} + +// TODO: Show unresolved and resolved name, for F1 try both. +TEST_F(ToolTipInfo, TypeName_ResolveTypeDef) +{ + ::ToolTipInfo expected(Utf8StringLiteral("Ptr<Nuu>")); + expected.setQdocIdCandidates({Utf8StringLiteral("PtrFromTypeDef")}); + expected.setQdocMark(Utf8StringLiteral("PtrFromTypeDef")); + expected.setQdocCategory(::ToolTipInfo::Typedef); + + const ::ToolTipInfo actual = tooltip(122, 5); + + ASSERT_THAT(actual, IsToolTip(expected)); +} + +// TODO: Show unresolved and resolved name, for F1 try both. +TEST_F(ToolTipInfo, TypeName_ResolveAlias) +{ + ::ToolTipInfo expected(Utf8StringLiteral("Ptr<Nuu>")); + expected.setQdocIdCandidates({Utf8StringLiteral("PtrFromTypeAlias")}); + expected.setQdocMark(Utf8StringLiteral("PtrFromTypeAlias")); + expected.setQdocCategory(::ToolTipInfo::Typedef); + + const ::ToolTipInfo actual = tooltip(123, 5); + + ASSERT_THAT(actual, IsToolTip(expected)); +} + +// The referenced cursor is a CXCursor_TypeAliasTemplateDecl, its type is invalid +// and so probably clang_getTypedefDeclUnderlyingType() does not return anything useful. +// TODO: Fix the cursor's type or add new API in libclang for querying the template type alias. +TEST_F(ToolTipInfo, DISABLED_TypeName_ResolveTemplateTypeAlias) +{ + const ::ToolTipInfo actual = tooltip(124, 5); + + ASSERT_THAT(actual.text(), Utf8StringLiteral("Ptr<Nuu>")); +} + +TEST_F(ToolTipInfo, TypeName_ResolveTemplateTypeAlias_qdoc) +{ + ::ToolTipInfo expected; + expected.setQdocIdCandidates({Utf8StringLiteral("PtrFromTemplateTypeAlias")}); + expected.setQdocMark(Utf8StringLiteral("PtrFromTemplateTypeAlias")); + expected.setQdocCategory(::ToolTipInfo::Typedef); + + const ::ToolTipInfo actual = tooltip(124, 5); + + ASSERT_THAT(actual, IsQdocToolTip(expected)); +} + +TEST_F(ToolTipInfo, TemplateClassReference) +{ + ::ToolTipInfo expected(Utf8StringLiteral("Zii<T>")); + expected.setQdocIdCandidates({Utf8StringLiteral("Zii")}); + expected.setQdocMark(Utf8StringLiteral("Zii")); + expected.setQdocCategory(::ToolTipInfo::ClassOrNamespace); + + const ::ToolTipInfo actual = tooltip(134, 5); + + ASSERT_THAT(actual, IsToolTip(expected)); +} + +TEST_F(ToolTipInfo, TemplateClassQualified) +{ + ::ToolTipInfo expected(Utf8StringLiteral("U::Yii<T>")); + expected.setQdocIdCandidates({Utf8StringLiteral("U::Yii"), Utf8StringLiteral("Yii")}); + expected.setQdocMark(Utf8StringLiteral("Yii")); + expected.setQdocCategory(::ToolTipInfo::ClassOrNamespace); + + const ::ToolTipInfo actual = tooltip(135, 5); + + ASSERT_THAT(actual, IsToolTip(expected)); +} + +TEST_F(ToolTipInfo, ResolveNamespaceAliasForType) +{ + ::ToolTipInfo expected(Utf8StringLiteral("A::X")); + expected.setQdocIdCandidates({Utf8StringLiteral("A::X"), Utf8StringLiteral("X")}); + expected.setQdocMark(Utf8StringLiteral("X")); + expected.setQdocCategory(::ToolTipInfo::ClassOrNamespace); + + const ::ToolTipInfo actual = tooltip(144, 8); + + ASSERT_THAT(actual, IsToolTip(expected)); +} + +// TODO: Show unresolved and resolved name, for F1 try both. +TEST_F(ToolTipInfo, ResolveNamespaceAlias) +{ + ::ToolTipInfo expected(Utf8StringLiteral("A")); + expected.setQdocIdCandidates({Utf8StringLiteral("B")}); + expected.setQdocMark(Utf8StringLiteral("B")); + expected.setQdocCategory(::ToolTipInfo::ClassOrNamespace); + + const ::ToolTipInfo actual = tooltip(144, 5); + + ASSERT_THAT(actual, IsToolTip(expected)); +} + +TEST_F(ToolTipInfo, QualificationForTemplateClassInClassInNamespace) +{ + ::ToolTipInfo expected(Utf8StringLiteral("N::Outer::Inner<int>")); + expected.setQdocIdCandidates({Utf8StringLiteral("N::Outer::Inner"), + Utf8StringLiteral("Outer::Inner"), + Utf8StringLiteral("Inner")}); + expected.setQdocMark(Utf8StringLiteral("Inner")); + expected.setQdocCategory(::ToolTipInfo::ClassOrNamespace); + expected.setSizeInBytes(Utf8StringLiteral("1")); + + const ::ToolTipInfo actual = tooltip(153, 16); + + ASSERT_THAT(actual, IsToolTip(expected)); +} + +TEST_F(ToolTipInfo, Function) +{ + ::ToolTipInfo expected(Utf8StringLiteral("void f()")); + expected.setQdocIdCandidates({Utf8StringLiteral("f")}); + expected.setQdocMark(Utf8StringLiteral("f()")); + expected.setQdocCategory(::ToolTipInfo::Function); + + const ::ToolTipInfo actual = tooltip(165, 5); + + ASSERT_THAT(actual, IsToolTip(expected)); +} + +TEST_F(ToolTipInfo, Function_QualifiedName) +{ + const ::ToolTipInfo actual = tooltip(166, 8); + + ASSERT_THAT(actual.text(), Utf8StringLiteral("void R::f()")); +} + +TEST_F(ToolTipInfo, Function_qdocIdCandidatesAreQualified) +{ + const ::ToolTipInfo actual = tooltip(166, 8); + + ASSERT_THAT(actual.qdocIdCandidates(), ElementsAre(Utf8StringLiteral("R::f"), + Utf8StringLiteral("f"))); +} + +TEST_F(ToolTipInfo, DISABLED_WITHOUT_PRETTYDECL_PATCH(Function_HasParameterName)) +{ + const ::ToolTipInfo actual = tooltip(167, 5); + + ASSERT_THAT(actual.text(), Utf8StringLiteral("void f(int param)")); +} + +// TODO: Implement with CXPrintingPolicy +TEST_F(ToolTipInfo, DISABLED_Function_HasDefaultArgument) +{ + const ::ToolTipInfo actual = tooltip(168, 5); + + ASSERT_THAT(actual.text(), Utf8StringLiteral("void z(int = 1)")); +} + +TEST_F(ToolTipInfo, Function_qdocMarkHasNoParameterName) +{ + const ::ToolTipInfo actual = tooltip(167, 5); + + ASSERT_THAT(actual.qdocMark(), Utf8StringLiteral("f(int)")); +} + +TEST_F(ToolTipInfo, Function_qdocMarkHasNoDefaultArgument) +{ + const ::ToolTipInfo actual = tooltip(168, 5); + + ASSERT_THAT(actual.qdocMark(), Utf8StringLiteral("z(int)")); +} + +TEST_F(ToolTipInfo, AutoTypeBuiltin) +{ + const ::ToolTipInfo actual = tooltip(176, 5); + + ASSERT_THAT(actual.text(), Utf8StringLiteral("int")); +} + +// TODO: Test for qdoc entries, too. +TEST_F(ToolTipInfo, AutoTypeEnum) +{ + const ::ToolTipInfo actual = tooltip(177, 5); + + ASSERT_THAT(actual.text(), Utf8StringLiteral("EnumType")); +} + +// TODO: Test for qdoc entries, too. +TEST_F(ToolTipInfo, AutoTypeClassType) +{ + const ::ToolTipInfo actual = tooltip(178, 5); + + ASSERT_THAT(actual.text(), Utf8StringLiteral("Bar")); +} + +// TODO: Test for qdoc entries, too. +// TODO: Deduced template arguments work, too?! +TEST_F(ToolTipInfo, AutoTypeClassTemplateType) +{ + const ::ToolTipInfo actual = tooltip(179, 5); + + ASSERT_THAT(actual.text(), Utf8StringLiteral("Zii<int>")); +} + +std::unique_ptr<Data> ToolTipInfo::d; + +void ToolTipInfo::SetUpTestCase() +{ + d.reset(new Data); +} + +void ToolTipInfo::TearDownTestCase() +{ + d.reset(); +} + +} // anonymous namespace diff --git a/tests/unit/unittest/conditionally-disabled-tests.h b/tests/unit/unittest/conditionally-disabled-tests.h index 27e6fe173b2..8f55db10d7a 100644 --- a/tests/unit/unittest/conditionally-disabled-tests.h +++ b/tests/unit/unittest/conditionally-disabled-tests.h @@ -33,3 +33,9 @@ #else # define DISABLED_ON_WINDOWS(x) x #endif + +#ifdef IS_PRETTY_DECL_SUPPORTED +# define DISABLED_WITHOUT_PRETTYDECL_PATCH(x) x +#else +# define DISABLED_WITHOUT_PRETTYDECL_PATCH(x) DISABLED_##x +#endif diff --git a/tests/unit/unittest/data/tooltipinfo.cpp b/tests/unit/unittest/data/tooltipinfo.cpp new file mode 100644 index 00000000000..1945577b635 --- /dev/null +++ b/tests/unit/unittest/data/tooltipinfo.cpp @@ -0,0 +1,180 @@ +void f(int foo, const int *cfoo) +{ + foo++; + cfoo++; +} + + + +struct Foo { int member = 0; }; +int g(const Foo &foo) +{ + return foo.member; + const Foo bar; + bar; +} + +struct Bar { virtual ~Bar(); int mem(){} virtual int virtualConstMem() const; }; +void h(const Foo &foo, Bar &bar) +{ + g(foo); + bar.mem(); + bar.virtualConstMem(); +} + + +template <typename T> +void t(int foo) { (void)foo; } +void c() +{ + t<Foo>(3); +} + + + +/** + * \brief This is a crazy function. + */ +void documentedFunction(); +void d() +{ + documentedFunction(); +} + + + +enum EnumType { V1, V2, Custom = V2 + 5 }; +EnumType e() +{ + return EnumType::Custom; +} + + + +template <typename T> struct Baz { T member; }; +void t2(const Baz<int> &b) { + Baz<int> baz; baz = b; +} + +#include "tooltipinfo.h" + + + +#define MACRO_FROM_MAINFILE(x) x + 3 +void foo() +{ + MACRO_FROM_MAINFILE(7); + MACRO_FROM_HEADER(7); +} + + + +namespace N { struct Muu{}; } +namespace G = N; +void o() +{ + using namespace N; + Muu muu; (void)muu; +} +void n() +{ + using namespace G; + Muu muu; (void)muu; +} +void q() +{ + using N::Muu; + Muu muu; (void)muu; +} + + + +struct Sizes +{ + char memberChar1; + char memberChar2; +}; +enum class FancyEnumType { V1, V2 }; +union Union +{ + char memberChar1; + char memberChar2; +}; + + + +namespace X { +namespace Y { +} +} + + + +template<typename T> struct Ptr {}; +struct Nuu {}; + +typedef Ptr<Nuu> PtrFromTypeDef; +using PtrFromTypeAlias = Ptr<Nuu>; +template<typename T> using PtrFromTemplateTypeAlias = Ptr<T>; + +void y() +{ + PtrFromTypeDef b; (void)b; + PtrFromTypeAlias a; (void)a; + PtrFromTemplateTypeAlias<Nuu> c; (void)c; +} + + + +template <typename T> struct Zii {}; +namespace U { template <typename T> struct Yii {}; } +void mc() +{ + using namespace U; + Zii<int> zii; (void) zii; + Yii<int> yii; (void) yii; +} + + + +namespace A { struct X {}; } +namespace B = A; +void ab() +{ + B::X x; (void)x; +} + + + +namespace N { +struct Outer +{ + template <typename T> struct Inner {}; + Inner<int> inner; +}; +} + + + +void f(); +namespace R { void f(); } +void f(int param); +void z(int = 1); +void user() +{ + f(); + R::f(); + f(1); + z(); +} + + + + +void autoTypes() +{ + auto a = 3; (void)a; + auto b = EnumType::V1; (void)b; + auto c = Bar(); (void)c; + auto d = Zii<int>(); (void)d; +} diff --git a/tests/unit/unittest/data/tooltipinfo.h b/tests/unit/unittest/data/tooltipinfo.h new file mode 100644 index 00000000000..47d3bb92e75 --- /dev/null +++ b/tests/unit/unittest/data/tooltipinfo.h @@ -0,0 +1,3 @@ +#define MACRO_FROM_HEADER(x) x + \ + x + \ + x diff --git a/tests/unit/unittest/dummyclangipcclient.h b/tests/unit/unittest/dummyclangipcclient.h index 3908f9e89f2..e840b0a6c7a 100644 --- a/tests/unit/unittest/dummyclangipcclient.h +++ b/tests/unit/unittest/dummyclangipcclient.h @@ -38,4 +38,5 @@ public: void documentAnnotationsChanged(const ClangBackEnd::DocumentAnnotationsChangedMessage &) override {} void references(const ClangBackEnd::ReferencesMessage &) override {} void followSymbol(const ClangBackEnd::FollowSymbolMessage &) override {} + void tooltip(const ClangBackEnd::ToolTipMessage &) override {} }; diff --git a/tests/unit/unittest/gtest-creator-printing.cpp b/tests/unit/unittest/gtest-creator-printing.cpp index 2c106ec0e53..1bcf5f2b07d 100644 --- a/tests/unit/unittest/gtest-creator-printing.cpp +++ b/tests/unit/unittest/gtest-creator-printing.cpp @@ -41,6 +41,7 @@ #include <sourcelocationentry.h> #include <sourcelocationscontainer.h> #include <tokeninfos.h> +#include <tooltipinfo.h> #include <cpptools/usages.h> @@ -269,6 +270,17 @@ std::ostream &operator<<(std::ostream &os, const ReferencesMessage &message) return os; } +std::ostream &operator<<(std::ostream &os, const ToolTipMessage &message) +{ + os << "(" + << message.fileContainer() << ", " + << message.ticketNumber() << ", " + << message.toolTipInfo() << ", " + << ")"; + + return os; +} + std::ostream &operator<<(std::ostream &os, const EchoMessage &/*message*/) { return os << "()"; @@ -385,7 +397,8 @@ std::ostream &operator<<(std::ostream &os, const FileContainer &container) << container.filePath() << ", " << container.projectPartId() << ", " << container.fileArguments() << ", " - << container.documentRevision(); + << container.documentRevision() << ", " + << container.textCodecName(); if (container.hasUnsavedFileContent()) os << ", " @@ -552,6 +565,37 @@ std::ostream &operator<<(std::ostream &os, const RequestReferencesMessage &messa return os; } +std::ostream &operator<<(std::ostream &out, const RequestToolTipMessage &message) +{ + out << "(" + << message.fileContainer() << ", " + << message.ticketNumber() << ", " + << message.line() << ", " + << message.column() << ", " + << ")"; + + return out; +} + +std::ostream &operator<<(std::ostream &os, const ToolTipInfo::QdocCategory category) +{ + return os << qdocCategoryToString(category); +} + +std::ostream &operator<<(std::ostream &out, const ToolTipInfo &info) +{ + out << "(" + << info.m_text << ", " + << info.m_briefComment << ", " + << info.m_qdocIdCandidates << ", " + << info.m_qdocMark << ", " + << info.m_qdocCategory + << info.m_sizeInBytes << ", " + << ")"; + + return out; +} + std::ostream &operator<<(std::ostream &os, const RequestSourceLocationsForRenamingMessage &message) { diff --git a/tests/unit/unittest/gtest-creator-printing.h b/tests/unit/unittest/gtest-creator-printing.h index e08b4b33c0b..d58daaf40c2 100644 --- a/tests/unit/unittest/gtest-creator-printing.h +++ b/tests/unit/unittest/gtest-creator-printing.h @@ -73,6 +73,7 @@ class CodeCompletedMessage; class EchoMessage; class DocumentAnnotationsChangedMessage; class ReferencesMessage; +class ToolTipMessage; class FollowSymbolMessage; class CompleteCodeMessage; class EndMessage; @@ -97,6 +98,7 @@ class RemovePchProjectPartsMessage; class RequestDocumentAnnotationsMessage; class RequestFollowSymbolMessage; class RequestReferencesMessage; +class RequestToolTipMessage; class RequestSourceLocationsForRenamingMessage; class RequestSourceRangesAndDiagnosticsForQueryMessage; class RequestSourceRangesForQueryMessage; @@ -114,6 +116,7 @@ class UpdateVisibleTranslationUnitsMessage; class FilePath; class TokenInfo; class TokenInfos; +class ToolTipInfo; std::ostream &operator<<(std::ostream &out, const SourceLocationEntry &entry); std::ostream &operator<<(std::ostream &out, const IdPaths &idPaths); @@ -127,6 +130,7 @@ std::ostream &operator<<(std::ostream &out, const CodeCompletedMessage &message) std::ostream &operator<<(std::ostream &out, const EchoMessage &message); std::ostream &operator<<(std::ostream &out, const DocumentAnnotationsChangedMessage &message); std::ostream &operator<<(std::ostream &out, const ReferencesMessage &message); +std::ostream &operator<<(std::ostream &out, const ToolTipMessage &message); std::ostream &operator<<(std::ostream &out, const FollowSymbolMessage &message); std::ostream &operator<<(std::ostream &out, const CompleteCodeMessage &message); std::ostream &operator<<(std::ostream &out, const EndMessage &message); @@ -153,6 +157,8 @@ std::ostream &operator<<(std::ostream &out, const RemovePchProjectPartsMessage & std::ostream &operator<<(std::ostream &out, const RequestDocumentAnnotationsMessage &message); std::ostream &operator<<(std::ostream &out, const RequestFollowSymbolMessage &message); std::ostream &operator<<(std::ostream &out, const RequestReferencesMessage &message); +std::ostream &operator<<(std::ostream &out, const RequestToolTipMessage &message); +std::ostream &operator<<(std::ostream &out, const ToolTipInfo &info); std::ostream &operator<<(std::ostream &out, const RequestSourceLocationsForRenamingMessage &message); std::ostream &operator<<(std::ostream &out, const RequestSourceRangesAndDiagnosticsForQueryMessage &message); std::ostream &operator<<(std::ostream &out, const RequestSourceRangesForQueryMessage &message); diff --git a/tests/unit/unittest/mockclangcodemodelclient.h b/tests/unit/unittest/mockclangcodemodelclient.h index 2e287909ddf..3b5a3cfbf5a 100644 --- a/tests/unit/unittest/mockclangcodemodelclient.h +++ b/tests/unit/unittest/mockclangcodemodelclient.h @@ -45,4 +45,6 @@ public: void(const ClangBackEnd::ReferencesMessage &message)); MOCK_METHOD1(followSymbol, void(const ClangBackEnd::FollowSymbolMessage &message)); + MOCK_METHOD1(tooltip, + void(const ClangBackEnd::ToolTipMessage &message)); }; diff --git a/tests/unit/unittest/mockclangcodemodelserver.h b/tests/unit/unittest/mockclangcodemodelserver.h index 8b6c66b2a33..468f683bf53 100644 --- a/tests/unit/unittest/mockclangcodemodelserver.h +++ b/tests/unit/unittest/mockclangcodemodelserver.h @@ -55,6 +55,8 @@ public: void(const ClangBackEnd::RequestReferencesMessage &message)); MOCK_METHOD1(requestFollowSymbol, void(const ClangBackEnd::RequestFollowSymbolMessage &message)); + MOCK_METHOD1(requestToolTip, + void(const ClangBackEnd::RequestToolTipMessage &message)); MOCK_METHOD1(updateVisibleTranslationUnits, void(const ClangBackEnd::UpdateVisibleTranslationUnitsMessage &message)); }; diff --git a/tests/unit/unittest/unittest.pro b/tests/unit/unittest/unittest.pro index a16a13e88bf..b6a698361ae 100644 --- a/tests/unit/unittest/unittest.pro +++ b/tests/unit/unittest/unittest.pro @@ -118,6 +118,7 @@ SOURCES += \ clangstring-test.cpp \ clangsupportivetranslationunitinitializer-test.cpp \ clangsuspenddocumentjob-test.cpp \ + clangtooltipinfo-test.cpp \ clangtranslationunits-test.cpp \ clangtranslationunit-test.cpp \ clangupdatedocumentannotationsjob-test.cpp \ diff --git a/tests/unit/unittest/unsavedfile-test.cpp b/tests/unit/unittest/unsavedfile-test.cpp index 05783a33c32..7e4d3dae685 100644 --- a/tests/unit/unittest/unsavedfile-test.cpp +++ b/tests/unit/unittest/unsavedfile-test.cpp @@ -203,4 +203,32 @@ TEST_F(UnsavedFile, HasCharacterForLastLineColumn) ASSERT_TRUE(unsavedFile.hasCharacterAt(1, 7, 't')); } +TEST_F(UnsavedFile, LineRangeForInvalidLines) +{ + ::UnsavedFile unsavedFile(filePath, fileContent); + + ASSERT_THAT(unsavedFile.lineRange(2, 1), Utf8String()); +} + +TEST_F(UnsavedFile, LineRangeForSingleLine) +{ + ::UnsavedFile unsavedFile(filePath, Utf8StringLiteral("foo")); + + ASSERT_THAT(unsavedFile.lineRange(1, 1), Utf8StringLiteral("foo")); +} + +TEST_F(UnsavedFile, LineRangeForSingleLineInMultipleLines) +{ + ::UnsavedFile unsavedFile(filePath, Utf8StringLiteral("foo\nbar\n\baz")); + + ASSERT_THAT(unsavedFile.lineRange(2, 2), Utf8StringLiteral("bar")); +} + +TEST_F(UnsavedFile, LineRangeForTwoLines) +{ + ::UnsavedFile unsavedFile(filePath, Utf8StringLiteral("foo\nbar\n\baz")); + + ASSERT_THAT(unsavedFile.lineRange(2, 3), Utf8StringLiteral("bar\n\baz")); +} + } // anonymous namespace |