diff --git a/src/libs/languageserverprotocol/jsonrpcmessages.h b/src/libs/languageserverprotocol/jsonrpcmessages.h index f0247e3c56b..7a7ab2df89a 100644 --- a/src/libs/languageserverprotocol/jsonrpcmessages.h +++ b/src/libs/languageserverprotocol/jsonrpcmessages.h @@ -241,7 +241,7 @@ public: return Utils::nullopt; return Utils::make_optional(Result(result)); } - void setResult(const Result &result) { m_jsonObject.insert(resultKey, result); } + void setResult(const Result &result) { m_jsonObject.insert(resultKey, QJsonValue(result)); } void clearResult() { m_jsonObject.remove(resultKey); } using Error = ResponseError; diff --git a/src/plugins/clangcodemodel/clangcodemodelplugin.cpp b/src/plugins/clangcodemodel/clangcodemodelplugin.cpp index 2886f637cb3..59bcc4d1914 100644 --- a/src/plugins/clangcodemodel/clangcodemodelplugin.cpp +++ b/src/plugins/clangcodemodel/clangcodemodelplugin.cpp @@ -208,6 +208,7 @@ QVector ClangCodeModelPlugin::createTestObjects() const new Tests::ClangdTestFindReferences, new Tests::ClangdTestFollowSymbol, new Tests::ClangdTestLocalReferences, + new Tests::ClangdTestTooltips, }; } #endif diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 47a9809c1c0..0a3c33327d3 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -176,18 +176,50 @@ public: return true; } + bool isNamespace() const { return role() == "declaration" && kind() == "Namespace"; } + QString type() const { const Utils::optional arcanaString = arcana(); if (!arcanaString) return {}; - const int quote1Offset = arcanaString->indexOf('\''); + return typeFromPos(*arcanaString, 0); + } + + QString typeFromPos(const QString &s, int pos) const + { + const int quote1Offset = s.indexOf('\'', pos); if (quote1Offset == -1) return {}; - const int quote2Offset = arcanaString->indexOf('\'', quote1Offset + 1); + const int quote2Offset = s.indexOf('\'', quote1Offset + 1); if (quote2Offset == -1) return {}; - return arcanaString->mid(quote1Offset + 1, quote2Offset - quote1Offset - 1); + if (s.mid(quote2Offset + 1, 2) == ":'") + return typeFromPos(s, quote2Offset + 2); + return s.mid(quote1Offset + 1, quote2Offset - quote1Offset - 1); + } + + HelpItem::Category qdocCategoryForDeclaration(HelpItem::Category fallback) + { + const auto childList = children(); + if (!childList || childList->size() < 2) + return fallback; + const AstNode c1 = childList->first(); + if (c1.role() != "type" || c1.kind() != "Auto") + return fallback; + QList typeCandidates = {childList->at(1)}; + while (!typeCandidates.isEmpty()) { + const AstNode n = typeCandidates.takeFirst(); + if (n.role() == "type") { + if (n.kind() == "Enum") + return HelpItem::Enum; + if (n.kind() == "Record") + return HelpItem::ClassOrNamespace; + return fallback; + } + typeCandidates << n.children().value_or(QList()); + } + return fallback; } // Returns true <=> the type is "recursively const". @@ -652,6 +684,10 @@ public: QString searchTermFromCursor(const QTextCursor &cursor) const; + void setHelpItemForTooltip(const MessageId &token, const QString &fqn = {}, + HelpItem::Category category = HelpItem::Unknown, + const QString &type = {}); + ClangdClient * const q; QHash runningFindUsages; Utils::optional followSymbolData; @@ -695,6 +731,11 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir) const auto hideDiagsHandler = []{ ClangDiagnosticManager::clearTaskHubIssues(); }; setDiagnosticsHandlers(textMarkCreator, hideDiagsHandler); + hoverHandler()->setHelpItemProvider([this](const HoverRequest::Response &response, + const DocumentUri &uri) { + gatherHelpItemForTooltip(response, uri); + }); + connect(this, &Client::workDone, this, [this, project](const ProgressToken &token) { const QString * const val = Utils::get_if(&token); if (val && *val == indexingToken()) { @@ -1226,6 +1267,154 @@ void ClangdClient::findLocalUsages(TextEditor::TextDocument *document, const QTe symbolSupport().findLinkAt(document, cursor, std::move(gotoDefCallback), true); } +void ClangdClient::gatherHelpItemForTooltip(const HoverRequest::Response &hoverResponse, + const DocumentUri &uri) +{ + // Macros aren't locatable via the AST, so parse the formatted string. + if (const Utils::optional result = hoverResponse.result()) { + const HoverContent content = result->content(); + const MarkupContent * const markup = Utils::get_if(&content); + if (markup) { + const QString markupString = markup->content(); + static const QString magicMacroPrefix = "### macro `"; + if (markupString.startsWith(magicMacroPrefix)) { + const int nameStart = magicMacroPrefix.length(); + const int closingQuoteIndex = markupString.indexOf('`', nameStart); + if (closingQuoteIndex != -1) { + const QString macroName = markupString.mid(nameStart, + closingQuoteIndex - nameStart); + d->setHelpItemForTooltip(hoverResponse.id(), macroName, HelpItem::Macro); + return; + } + } + } + } + + AstRequest req((AstParams(TextDocumentIdentifier(uri)))); + req.setResponseCallback([this, uri, hoverResponse](const AstRequest::Response &response) { + const MessageId id = hoverResponse.id(); + const AstNode ast = response.result().value_or(AstNode()); + const Range range = hoverResponse.result()->range().value_or(Range()); + const QList path = getAstPath(ast, range); + if (path.isEmpty()) { + d->setHelpItemForTooltip(id); + return; + } + AstNode node = path.last(); + if (node.role() == "expression" && node.kind() == "ImplicitCast") { + const Utils::optional> children = node.children(); + if (children && !children->isEmpty()) + node = children->first(); + } + while (node.kind() == "Qualified") { + const Utils::optional> children = node.children(); + if (children && !children->isEmpty()) + node = children->first(); + } + if (clangdLog().isDebugEnabled()) + node.print(0); + + QString type = node.type(); + const auto stripTemplatePartOffType = [&type] { + const int angleBracketIndex = type.indexOf('<'); + if (angleBracketIndex != -1) + type = type.left(angleBracketIndex); + }; + + const bool isMemberFunction = node.role() == "expression" && node.kind() == "Member" + && (node.arcanaContains("member function") || type.contains('(')); + const bool isFunction = node.role() == "expression" && node.kind() == "DeclRef" + && type.contains('('); + if (isMemberFunction || isFunction) { + const TextDocumentPositionParams params(TextDocumentIdentifier(uri), range.start()); + SymbolInfoRequest symReq(params); + symReq.setResponseCallback([this, id, type, isFunction] + (const SymbolInfoRequest::Response &response) { + qCDebug(clangdLog) << "handling symbol info reply"; + QString fqn; + if (const auto result = response.result()) { + if (const auto list = Utils::get_if>(&result.value())) { + if (!list->isEmpty()) { + const SymbolDetails &sd = list->first(); + fqn = sd.containerName() + sd.name(); + } + } + } + + // Unfortunately, the arcana string contains the signature only for + // free functions, so we can't distinguish member function overloads. + // But since HtmlDocExtractor::getFunctionDescription() is always called + // with mainOverload = true, such information would get ignored anyway. + d->setHelpItemForTooltip(id, fqn, HelpItem::Function, isFunction ? type : "()"); + }); + sendContent(symReq); + return; + } + if ((node.role() == "expression" && node.kind() == "DeclRef") + || (node.role() == "declaration" + && (node.kind() == "Var" || node.kind() == "ParmVar" + || node.kind() == "Field"))) { + if (node.arcanaContains("EnumConstant")) { + d->setHelpItemForTooltip(id, node.detail().value_or(QString()), + HelpItem::Enum, type); + return; + } + stripTemplatePartOffType(); + type.remove("&").remove("*").remove("const ").remove(" const") + .remove("volatile ").remove(" volatile"); + type = type.simplified(); + if (type != "int" && !type.contains(" int") + && type != "char" && !type.contains(" char") + && type != "double" && !type.contains(" double") + && type != "float" && type != "bool") { + d->setHelpItemForTooltip(id, type, node.qdocCategoryForDeclaration( + HelpItem::ClassOrNamespace)); + } else { + d->setHelpItemForTooltip(id); + } + return; + } + if (node.isNamespace()) { + QString ns = node.detail().value_or(QString()); + for (auto it = path.rbegin() + 1; it != path.rend(); ++it) { + if (it->isNamespace()) { + const QString name = it->detail().value_or(QString()); + if (!name.isEmpty()) + ns.prepend("::").prepend(name); + } + } + d->setHelpItemForTooltip(hoverResponse.id(), ns, HelpItem::ClassOrNamespace); + return; + } + if (node.role() == "type") { + if (node.kind() == "Enum") { + d->setHelpItemForTooltip(id, node.detail().value_or(QString()), HelpItem::Enum); + } else if (node.kind() == "Record" || node.kind() == "TemplateSpecialization") { + stripTemplatePartOffType(); + d->setHelpItemForTooltip(id, type, HelpItem::ClassOrNamespace); + } else if (node.kind() == "Typedef") { + d->setHelpItemForTooltip(id, type, HelpItem::Typedef); + } else { + d->setHelpItemForTooltip(id); + } + return; + } + if (node.role() == "expression" && node.kind() == "CXXConstruct") { + const QString name = node.detail().value_or(QString()); + if (!name.isEmpty()) + type = name; + d->setHelpItemForTooltip(id, type, HelpItem::ClassOrNamespace); + } + if (node.role() == "specifier" && node.kind() == "NamespaceAlias") { + d->setHelpItemForTooltip(id, node.detail().value_or(QString()).chopped(2), + HelpItem::ClassOrNamespace); + return; + } + d->setHelpItemForTooltip(id); + }); + sendContent(req); +} + void ClangdClient::Private::handleGotoDefinitionResult() { QTC_ASSERT(followSymbolData->defLink.hasValidTarget(), return); @@ -1468,6 +1657,36 @@ QString ClangdClient::Private::searchTermFromCursor(const QTextCursor &cursor) c return termCursor.selectedText(); } +void ClangdClient::Private::setHelpItemForTooltip(const MessageId &token, const QString &fqn, + HelpItem::Category category, + const QString &type) +{ + QStringList helpIds; + QString mark; + if (!fqn.isEmpty()) { + helpIds << fqn; + int sepSearchStart = 0; + while (true) { + sepSearchStart = fqn.indexOf("::", sepSearchStart); + if (sepSearchStart == -1) + break; + sepSearchStart += 2; + helpIds << fqn.mid(sepSearchStart); + } + mark = helpIds.last(); + if (category == HelpItem::Function) + mark += type.mid(type.indexOf('(')); + } + if (category == HelpItem::Enum && !type.isEmpty()) + mark = type; + + HelpItem helpItem(helpIds, mark, category); + if (isTesting) + emit q->helpItemGathered(helpItem); + else + q->hoverHandler()->setHelpItem(token, helpItem); +} + void ClangdClient::VirtualFunctionAssistProcessor::cancel() { resetData(); diff --git a/src/plugins/clangcodemodel/clangdclient.h b/src/plugins/clangcodemodel/clangdclient.h index 49d60e7c314..5b0d61b814b 100644 --- a/src/plugins/clangcodemodel/clangdclient.h +++ b/src/plugins/clangcodemodel/clangdclient.h @@ -70,12 +70,17 @@ public: void findLocalUsages(TextEditor::TextDocument *document, const QTextCursor &cursor, CppTools::RefactoringEngineInterface::RenameCallback &&callback); + void gatherHelpItemForTooltip( + const LanguageServerProtocol::HoverRequest::Response &hoverResponse, + const LanguageServerProtocol::DocumentUri &uri); + void enableTesting(); signals: void indexingFinished(); void foundReferences(const QList &items); void findUsagesDone(); + void helpItemGathered(const Core::HelpItem &helpItem); private: void handleDiagnostics(const LanguageServerProtocol::PublishDiagnosticsParams ¶ms) override; diff --git a/src/plugins/clangcodemodel/clanghoverhandler.cpp b/src/plugins/clangcodemodel/clanghoverhandler.cpp index 3d8d6648d98..250f2886b3d 100644 --- a/src/plugins/clangcodemodel/clanghoverhandler.cpp +++ b/src/plugins/clangcodemodel/clanghoverhandler.cpp @@ -26,6 +26,7 @@ #include "clanghoverhandler.h" #include "clangeditordocumentprocessor.h" +#include "clangmodelmanagersupport.h" #include #include @@ -97,6 +98,12 @@ void ClangHoverHandler::identifyMatch(TextEditorWidget *editorWidget, int pos, BaseHoverHandler::ReportPriority report) { + if (ClangModelManagerSupport::instance() + ->clientForFile(editorWidget->textDocument()->filePath())) { + report(Priority_None); + return; + } + // Reset m_futureWatcher.reset(); m_cursorPosition = -1; diff --git a/src/plugins/clangcodemodel/test/clangdtests.cpp b/src/plugins/clangcodemodel/test/clangdtests.cpp index 5bce03e251f..e4772e89742 100644 --- a/src/plugins/clangcodemodel/test/clangdtests.cpp +++ b/src/plugins/clangcodemodel/test/clangdtests.cpp @@ -492,6 +492,145 @@ void ClangdTestLocalReferences::test() QCOMPARE(actualRanges, expectedRanges); } + +// This tests our help item construction, not the actual tooltip contents. Those come +// pre-formatted from clangd. +ClangdTestTooltips::ClangdTestTooltips() +{ + setProjectFileName("tooltips.pro"); + setSourceFileNames({"tooltips.cpp"}); +} + +void ClangdTestTooltips::test_data() +{ + QTest::addColumn("line"); + QTest::addColumn("column"); + QTest::addColumn("expectedIds"); + QTest::addColumn("expectedMark"); + QTest::addColumn("expectedCategory"); + + QTest::newRow("LocalParameterVariableConstRefCustomType") << 12 << 12 + << QStringList("Foo") << QString("Foo") << int(HelpItem::ClassOrNamespace); + QTest::newRow("LocalNonParameterVariableConstRefCustomType") << 14 << 5 + << QStringList("Foo") << QString("Foo") << int(HelpItem::ClassOrNamespace); + QTest::newRow("MemberVariableBuiltinType") << 12 << 16 + << QStringList() << QString() << int(HelpItem::Unknown); + QTest::newRow("MemberFunctionCall") << 21 << 9 + << QStringList{"Bar::mem", "mem"} << QString("mem()") << int(HelpItem::Function); + QTest::newRow("TemplateFunctionCall") << 30 << 5 + << QStringList{"t"} << QString("t(int)") << int(HelpItem::Function); + QTest::newRow("Enum") << 49 << 12 + << QStringList{"EnumType"} << QString("EnumType") << int(HelpItem::Enum); + QTest::newRow("Enumerator") << 49 << 22 + << QStringList{"Custom"} << QString("EnumType") << int(HelpItem::Enum); + QTest::newRow("TemplateTypeFromParameter") << 55 << 25 + << QStringList{"Baz"} << QString("Baz") << int(HelpItem::ClassOrNamespace); + QTest::newRow("TemplateTypeFromNonParameter") << 56 << 19 + << QStringList{"Baz"} << QString("Baz") << int(HelpItem::ClassOrNamespace); + QTest::newRow("IncludeDirective") << 59 << 11 << QStringList{"tooltipinfo.h"} + << QString("tooltipinfo.h") << int(HelpItem::Brief); + QTest::newRow("MacroUse") << 66 << 5 << QStringList{"MACRO_FROM_MAINFILE"} + << QString("MACRO_FROM_MAINFILE") << int(HelpItem::Macro); + QTest::newRow("TypeNameIntroducedByUsingDirectiveQualified") << 77 << 5 + << QStringList{"N::Muu", "Muu"} << QString("Muu") << int(HelpItem::ClassOrNamespace); + QTest::newRow("TypeNameIntroducedByUsingDirectiveResolvedAndQualified") << 82 << 5 + << QStringList{"N::Muu", "Muu"} << QString("Muu") << int(HelpItem::ClassOrNamespace); + QTest::newRow("TypeNameIntroducedByUsingDeclarationQualified") << 87 << 5 + << QStringList{"N::Muu", "Muu"} << QString("Muu") << int(HelpItem::ClassOrNamespace); + QTest::newRow("Namespace") << 106 << 11 + << QStringList{"X"} << QString("X") << int(HelpItem::ClassOrNamespace); + QTest::newRow("NamespaceQualified") << 107 << 11 + << QStringList{"X::Y", "Y"} << QString("Y") << int(HelpItem::ClassOrNamespace); + QTest::newRow("TypeName_ResolveTypeDef") << 122 << 5 << QStringList{"PtrFromTypeDef"} + << QString("PtrFromTypeDef") << int(HelpItem::Typedef); + QTest::newRow("TypeName_ResolveAlias") << 123 << 5 << QStringList{"PtrFromTypeAlias"} + << QString("PtrFromTypeAlias") << int(HelpItem::Typedef); + QTest::newRow("TypeName_ResolveTemplateTypeAlias") << 124 << 5 + << QStringList{"PtrFromTemplateTypeAlias"} << QString("PtrFromTemplateTypeAlias") + << int(HelpItem::Typedef); + QTest::newRow("TemplateClassReference") << 134 << 5 + << QStringList{"Zii"} << QString("Zii") << int(HelpItem::ClassOrNamespace); + QTest::newRow("TemplateClassQualified") << 135 << 5 + << QStringList{"U::Yii", "Yii"} << QString("Yii") << int(HelpItem::ClassOrNamespace); + QTest::newRow("ResolveNamespaceAliasForType") << 144 << 8 + << QStringList{"A::X", "X"} << QString("X") << int(HelpItem::ClassOrNamespace); + QTest::newRow("ResolveNamespaceAlias") << 144 << 5 + << QStringList{"B"} << QString("B") << int(HelpItem::ClassOrNamespace); + QTest::newRow("QualificationForTemplateClassInClassInNamespace") << 153 << 16 + << QStringList{"N::Outer::Inner", "Outer::Inner", "Inner"} << QString("Inner") + << int(HelpItem::ClassOrNamespace); + QTest::newRow("Function") << 165 << 5 << QStringList{"f"} << QString("f()") + << int(HelpItem::Function); + QTest::newRow("Function_QualifiedName") << 166 << 8 + << QStringList{"R::f", "f"} << QString("f()") << int(HelpItem::Function); + QTest::newRow("FunctionWithParameter") << 167 << 5 << QStringList{"f"} << QString("f(int)") + << int(HelpItem::Function); + QTest::newRow("FunctionWithDefaultValue") << 168 << 5 + << QStringList{"z"} << QString("z(int)") << int(HelpItem::Function); + QTest::newRow("PointerToPointerToClass") << 200 << 12 + << QStringList{"Nuu"} << QString("Nuu") << int(HelpItem::ClassOrNamespace); + QTest::newRow("AutoTypeEnum") << 177 << 10 + << QStringList{"EnumType"} << QString("EnumType") << int(HelpItem::Enum); + QTest::newRow("AutoTypeClass") << 178 << 10 + << QStringList{"Bar"} << QString("Bar") << int(HelpItem::ClassOrNamespace); + QTest::newRow("AutoTypeTemplate") << 179 << 10 + << QStringList{"Zii"} << QString("Zii") << int(HelpItem::ClassOrNamespace); + QTest::newRow("Function_DefaultConstructor") << 193 << 5 + << QStringList{"Con"} << QString("Con") << int(HelpItem::ClassOrNamespace); + QTest::newRow("Function_ExplicitDefaultConstructor") << 194 << 5 + << QStringList{"ExplicitCon"} << QString("ExplicitCon") + << int(HelpItem::ClassOrNamespace); + QTest::newRow("Function_CustomConstructor") << 195 << 5 + << QStringList{"ExplicitCon"} << QString("ExplicitCon") + << int(HelpItem::ClassOrNamespace); +} + +void ClangdTestTooltips::test() +{ + QFETCH(int, line); + QFETCH(int, column); + QFETCH(QStringList, expectedIds); + QFETCH(QString, expectedMark); + QFETCH(int, expectedCategory); + + TextEditor::TextDocument * const doc = document("tooltips.cpp"); + QVERIFY(doc); + const auto editor = qobject_cast(EditorManager::currentEditor()); + QVERIFY(editor); + QCOMPARE(editor->document(), doc); + QVERIFY(editor->editorWidget()); + + QSKIP("IncludeDirective", "FIXME: clangd sends empty or no hover data for includes", Abort); + + QTimer timer; + timer.setSingleShot(true); + QEventLoop loop; + QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); + HelpItem helpItem; + const auto handler = [&helpItem, &loop](const HelpItem &h) { + helpItem = h; + loop.quit(); + }; + connect(client(), &ClangdClient::helpItemGathered, handler); + + QTextCursor cursor(doc->document()); + const int pos = Utils::Text::positionInText(doc->document(), line, column); + cursor.setPosition(pos); + editor->editorWidget()->processTooltipRequest(cursor); + + timer.start(10000); + loop.exec(); + QVERIFY(timer.isActive()); + timer.stop(); + + QEXPECT_FAIL("TypeName_ResolveTemplateTypeAlias", "typedef already resolved in AST", Abort); + QCOMPARE(int(helpItem.category()), expectedCategory); + QEXPECT_FAIL("TemplateClassQualified", "Additional look-up needed?", Abort); + QEXPECT_FAIL("AutoTypeTemplate", "Additional look-up needed?", Abort); + QCOMPARE(helpItem.helpIds(), expectedIds); + QCOMPARE(helpItem.docMark(), expectedMark); +} + } // namespace Tests } // namespace Internal } // namespace ClangCodeModel diff --git a/src/plugins/clangcodemodel/test/clangdtests.h b/src/plugins/clangcodemodel/test/clangdtests.h index e794350d7eb..158fe61844f 100644 --- a/src/plugins/clangcodemodel/test/clangdtests.h +++ b/src/plugins/clangcodemodel/test/clangdtests.h @@ -115,6 +115,17 @@ private slots: void test(); }; +class ClangdTestTooltips : public ClangdTest +{ + Q_OBJECT +public: + ClangdTestTooltips(); + +private slots: + void test_data(); + void test(); +}; + } // namespace Tests } // namespace Internal } // namespace ClangCodeModel diff --git a/src/plugins/clangcodemodel/test/data/clangtestdata.qrc b/src/plugins/clangcodemodel/test/data/clangtestdata.qrc index 5357195bc1f..9f49f6078ab 100644 --- a/src/plugins/clangcodemodel/test/data/clangtestdata.qrc +++ b/src/plugins/clangcodemodel/test/data/clangtestdata.qrc @@ -39,5 +39,7 @@ follow-symbol/main.cpp local-references/local-references.pro local-references/references.cpp + tooltips/tooltips.cpp + tooltips/tooltips.pro diff --git a/src/plugins/clangcodemodel/test/data/tooltips/tooltips.cpp b/src/plugins/clangcodemodel/test/data/tooltips/tooltips.cpp new file mode 100644 index 00000000000..bc1f17066ef --- /dev/null +++ b/src/plugins/clangcodemodel/test/data/tooltips/tooltips.cpp @@ -0,0 +1,212 @@ +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 +void t(int foo) { (void)foo; } +void c() +{ + t(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 struct Baz { T member; }; +void t2(const Baz &b) { + Baz 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 struct Ptr {}; +struct Nuu {}; + +typedef Ptr PtrFromTypeDef; +using PtrFromTypeAlias = Ptr; +template using PtrFromTemplateTypeAlias = Ptr; + +void y() +{ + PtrFromTypeDef b; (void)b; + PtrFromTypeAlias a; (void)a; + PtrFromTemplateTypeAlias c; (void)c; +} + + + +template struct Zii {}; +namespace U { template struct Yii {}; } +void mc() +{ + using namespace U; + Zii zii; (void) zii; + Yii yii; (void) yii; +} + + + +namespace A { struct X {}; } +namespace B = A; +void ab() +{ + B::X x; (void)x; +} + + + +namespace N { +struct Outer +{ + template struct Inner {}; + Inner 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(); (void)d; +} + + + + +struct Con {}; +struct ExplicitCon { + ExplicitCon() = default; + ExplicitCon(int m) :member(m) {} + int member; +}; +void constructor() +{ + Con(); + ExplicitCon(); + ExplicitCon(2); +} + +Nuu **pointers(Nuu **p1) +{ + return p1; +} + +static constexpr int calcValue() { return 1 + 2; } +const auto val = calcValue() + sizeof(char); + +const int zero = 0; + +static void func() +{ + const int i = 5; + const int j = i; +} diff --git a/src/plugins/clangcodemodel/test/data/tooltips/tooltips.pro b/src/plugins/clangcodemodel/test/data/tooltips/tooltips.pro new file mode 100644 index 00000000000..d10195696e4 --- /dev/null +++ b/src/plugins/clangcodemodel/test/data/tooltips/tooltips.pro @@ -0,0 +1,3 @@ +TEMPLATE = app +CONFIG -= qt +SOURCES = tooltips.cpp diff --git a/src/plugins/languageclient/languageclienthoverhandler.cpp b/src/plugins/languageclient/languageclienthoverhandler.cpp index 15f757ece68..3fc137f9387 100644 --- a/src/plugins/languageclient/languageclienthoverhandler.cpp +++ b/src/plugins/languageclient/languageclienthoverhandler.cpp @@ -51,6 +51,18 @@ void HoverHandler::abort() if (m_client && m_client->reachable() && m_currentRequest.has_value()) m_client->cancelRequest(*m_currentRequest); m_currentRequest.reset(); + m_response = {}; +} + +void HoverHandler::setHelpItem(const LanguageServerProtocol::MessageId &msgId, + const Core::HelpItem &help) +{ + if (msgId == m_response.id()) { + setContent(m_response.result().value().content()); + m_response = {}; + setLastHelpItemIdentified(help); + m_report(priority()); + } } void HoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget, @@ -64,10 +76,11 @@ void HoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget, report(Priority_None); return; } - auto uri = DocumentUri::fromFilePath(editorWidget->textDocument()->filePath()); + m_uri = DocumentUri::fromFilePath(editorWidget->textDocument()->filePath()); + m_response = {}; QTextCursor tc = editorWidget->textCursor(); tc.setPosition(pos); - const QList &diagnostics = m_client->diagnosticsAt(uri, tc); + const QList &diagnostics = m_client->diagnosticsAt(m_uri, tc); if (!diagnostics.isEmpty()) { const QStringList messages = Utils::transform(diagnostics, &Diagnostic::message); setToolTip(messages.join('\n')); @@ -101,7 +114,7 @@ void HoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget, m_report = report; QTextCursor cursor = editorWidget->textCursor(); cursor.setPosition(pos); - HoverRequest request((TextDocumentPositionParams(TextDocumentIdentifier(uri), Position(cursor)))); + HoverRequest request((TextDocumentPositionParams(TextDocumentIdentifier(m_uri), Position(cursor)))); m_currentRequest = request.id(); request.setResponseCallback( [this](const HoverRequest::Response &response) { handleResponse(response); }); @@ -115,8 +128,14 @@ void HoverHandler::handleResponse(const HoverRequest::Response &response) if (m_client) m_client->log(error.value()); } - if (Utils::optional result = response.result()) + if (Utils::optional result = response.result()) { + if (m_helpItemProvider) { + m_response = response; + m_helpItemProvider(response, m_uri); + return; + } setContent(result.value().content()); + } m_report(priority()); } diff --git a/src/plugins/languageclient/languageclienthoverhandler.h b/src/plugins/languageclient/languageclienthoverhandler.h index 4c09f11063c..c8bcb759fc6 100644 --- a/src/plugins/languageclient/languageclienthoverhandler.h +++ b/src/plugins/languageclient/languageclienthoverhandler.h @@ -25,14 +25,21 @@ #pragma once +#include "languageclient_global.h" + #include #include +#include + namespace LanguageClient { class Client; -class HoverHandler final : public TextEditor::BaseHoverHandler +using HelpItemProvider = std::function; + +class LANGUAGECLIENT_EXPORT HoverHandler final : public TextEditor::BaseHoverHandler { Q_DECLARE_TR_FUNCTIONS(HoverHandler) public: @@ -41,6 +48,9 @@ public: void abort() override; + void setHelpItemProvider(const HelpItemProvider &provider) { m_helpItemProvider = provider; } + void setHelpItem(const LanguageServerProtocol::MessageId &msgId, const Core::HelpItem &help); + protected: void identifyMatch(TextEditor::TextEditorWidget *editorWidget, int pos, @@ -52,7 +62,10 @@ private: QPointer m_client; Utils::optional m_currentRequest; + LanguageServerProtocol::DocumentUri m_uri; + LanguageServerProtocol::HoverRequest::Response m_response; TextEditor::BaseHoverHandler::ReportPriority m_report; + HelpItemProvider m_helpItemProvider; }; } // namespace LanguageClient diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 60d5930f7b7..b98a5783056 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -5778,6 +5778,13 @@ void TextEditorWidget::removeHoverHandler(BaseHoverHandler *handler) d->m_hoverHandlers.removeAll(handler); } +#ifdef WITH_TESTS +void TextEditorWidget::processTooltipRequest(const QTextCursor &c) +{ + d->processTooltipRequest(c); +} +#endif + void TextEditorWidget::extraAreaLeaveEvent(QEvent *) { d->extraAreaPreviousMarkTooltipRequestedLine = -1; diff --git a/src/plugins/texteditor/texteditor.h b/src/plugins/texteditor/texteditor.h index ceab8fbb53f..70224a18ff9 100644 --- a/src/plugins/texteditor/texteditor.h +++ b/src/plugins/texteditor/texteditor.h @@ -491,6 +491,10 @@ public: void addHoverHandler(BaseHoverHandler *handler); void removeHoverHandler(BaseHoverHandler *handler); +#ifdef WITH_TESTS + void processTooltipRequest(const QTextCursor &c); +#endif + signals: void assistFinished(); // Used in tests. void readOnlyChanged();