ClangCodeModel: Provide tooltips via clangd

Note that we temporarily lose the ability to hover over an include and
get the full path of the header file. This is a valuable feature that we
need to restore, preferably by fixing clangd itself.
Fixing the remaining few test failures would likely require more
complicated code as well as additional LSP round-trips, and as of now
I'm not convinced it is worth the effort.

Change-Id: I08c72c4bd1268bbd67baeb57bbfd29d9b11303a5
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Kandeler
2021-06-04 12:40:26 +02:00
parent eef0769d7a
commit 673d596c84
14 changed files with 651 additions and 9 deletions

View File

@@ -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<ErrorDataType>;

View File

@@ -208,6 +208,7 @@ QVector<QObject *> ClangCodeModelPlugin::createTestObjects() const
new Tests::ClangdTestFindReferences,
new Tests::ClangdTestFollowSymbol,
new Tests::ClangdTestLocalReferences,
new Tests::ClangdTestTooltips,
};
}
#endif

View File

@@ -176,18 +176,50 @@ public:
return true;
}
bool isNamespace() const { return role() == "declaration" && kind() == "Namespace"; }
QString type() const
{
const Utils::optional<QString> 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<AstNode> 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<AstNode>());
}
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<quint64, ReferencesData> runningFindUsages;
Utils::optional<FollowSymbolData> 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<QString>(&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<Hover> result = hoverResponse.result()) {
const HoverContent content = result->content();
const MarkupContent * const markup = Utils::get_if<MarkupContent>(&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<AstNode> 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<QList<AstNode>> children = node.children();
if (children && !children->isEmpty())
node = children->first();
}
while (node.kind() == "Qualified") {
const Utils::optional<QList<AstNode>> 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<QList<SymbolDetails>>(&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();

View File

@@ -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<Core::SearchResultItem> &items);
void findUsagesDone();
void helpItemGathered(const Core::HelpItem &helpItem);
private:
void handleDiagnostics(const LanguageServerProtocol::PublishDiagnosticsParams &params) override;

View File

@@ -26,6 +26,7 @@
#include "clanghoverhandler.h"
#include "clangeditordocumentprocessor.h"
#include "clangmodelmanagersupport.h"
#include <coreplugin/helpmanager.h>
#include <cpptools/cppmodelmanager.h>
@@ -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;

View File

@@ -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<int>("line");
QTest::addColumn<int>("column");
QTest::addColumn<QStringList>("expectedIds");
QTest::addColumn<QString>("expectedMark");
QTest::addColumn<int>("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<TextEditor::BaseTextEditor *>(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

View File

@@ -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

View File

@@ -39,5 +39,7 @@
<file>follow-symbol/main.cpp</file>
<file>local-references/local-references.pro</file>
<file>local-references/references.cpp</file>
<file>tooltips/tooltips.cpp</file>
<file>tooltips/tooltips.pro</file>
</qresource>
</RCC>

View File

@@ -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 <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;
}
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;
}

View File

@@ -0,0 +1,3 @@
TEMPLATE = app
CONFIG -= qt
SOURCES = tooltips.cpp

View File

@@ -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<Diagnostic> &diagnostics = m_client->diagnosticsAt(uri, tc);
const QList<Diagnostic> &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<Hover> result = response.result())
if (Utils::optional<Hover> result = response.result()) {
if (m_helpItemProvider) {
m_response = response;
m_helpItemProvider(response, m_uri);
return;
}
setContent(result.value().content());
}
m_report(priority());
}

View File

@@ -25,14 +25,21 @@
#pragma once
#include "languageclient_global.h"
#include <languageserverprotocol/languagefeatures.h>
#include <texteditor/basehoverhandler.h>
#include <functional>
namespace LanguageClient {
class Client;
class HoverHandler final : public TextEditor::BaseHoverHandler
using HelpItemProvider = std::function<void(const LanguageServerProtocol::HoverRequest::Response &,
const LanguageServerProtocol::DocumentUri &uri)>;
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<Client> m_client;
Utils::optional<LanguageServerProtocol::MessageId> m_currentRequest;
LanguageServerProtocol::DocumentUri m_uri;
LanguageServerProtocol::HoverRequest::Response m_response;
TextEditor::BaseHoverHandler::ReportPriority m_report;
HelpItemProvider m_helpItemProvider;
};
} // namespace LanguageClient

View File

@@ -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;

View File

@@ -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();