forked from qt-creator/qt-creator
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:
@@ -241,7 +241,7 @@ public:
|
|||||||
return Utils::nullopt;
|
return Utils::nullopt;
|
||||||
return Utils::make_optional(Result(result));
|
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); }
|
void clearResult() { m_jsonObject.remove(resultKey); }
|
||||||
|
|
||||||
using Error = ResponseError<ErrorDataType>;
|
using Error = ResponseError<ErrorDataType>;
|
||||||
|
|||||||
@@ -208,6 +208,7 @@ QVector<QObject *> ClangCodeModelPlugin::createTestObjects() const
|
|||||||
new Tests::ClangdTestFindReferences,
|
new Tests::ClangdTestFindReferences,
|
||||||
new Tests::ClangdTestFollowSymbol,
|
new Tests::ClangdTestFollowSymbol,
|
||||||
new Tests::ClangdTestLocalReferences,
|
new Tests::ClangdTestLocalReferences,
|
||||||
|
new Tests::ClangdTestTooltips,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -176,18 +176,50 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isNamespace() const { return role() == "declaration" && kind() == "Namespace"; }
|
||||||
|
|
||||||
QString type() const
|
QString type() const
|
||||||
{
|
{
|
||||||
const Utils::optional<QString> arcanaString = arcana();
|
const Utils::optional<QString> arcanaString = arcana();
|
||||||
if (!arcanaString)
|
if (!arcanaString)
|
||||||
return {};
|
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)
|
if (quote1Offset == -1)
|
||||||
return {};
|
return {};
|
||||||
const int quote2Offset = arcanaString->indexOf('\'', quote1Offset + 1);
|
const int quote2Offset = s.indexOf('\'', quote1Offset + 1);
|
||||||
if (quote2Offset == -1)
|
if (quote2Offset == -1)
|
||||||
return {};
|
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".
|
// Returns true <=> the type is "recursively const".
|
||||||
@@ -652,6 +684,10 @@ public:
|
|||||||
|
|
||||||
QString searchTermFromCursor(const QTextCursor &cursor) const;
|
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;
|
ClangdClient * const q;
|
||||||
QHash<quint64, ReferencesData> runningFindUsages;
|
QHash<quint64, ReferencesData> runningFindUsages;
|
||||||
Utils::optional<FollowSymbolData> followSymbolData;
|
Utils::optional<FollowSymbolData> followSymbolData;
|
||||||
@@ -695,6 +731,11 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir)
|
|||||||
const auto hideDiagsHandler = []{ ClangDiagnosticManager::clearTaskHubIssues(); };
|
const auto hideDiagsHandler = []{ ClangDiagnosticManager::clearTaskHubIssues(); };
|
||||||
setDiagnosticsHandlers(textMarkCreator, hideDiagsHandler);
|
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) {
|
connect(this, &Client::workDone, this, [this, project](const ProgressToken &token) {
|
||||||
const QString * const val = Utils::get_if<QString>(&token);
|
const QString * const val = Utils::get_if<QString>(&token);
|
||||||
if (val && *val == indexingToken()) {
|
if (val && *val == indexingToken()) {
|
||||||
@@ -1226,6 +1267,154 @@ void ClangdClient::findLocalUsages(TextEditor::TextDocument *document, const QTe
|
|||||||
symbolSupport().findLinkAt(document, cursor, std::move(gotoDefCallback), true);
|
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()
|
void ClangdClient::Private::handleGotoDefinitionResult()
|
||||||
{
|
{
|
||||||
QTC_ASSERT(followSymbolData->defLink.hasValidTarget(), return);
|
QTC_ASSERT(followSymbolData->defLink.hasValidTarget(), return);
|
||||||
@@ -1468,6 +1657,36 @@ QString ClangdClient::Private::searchTermFromCursor(const QTextCursor &cursor) c
|
|||||||
return termCursor.selectedText();
|
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()
|
void ClangdClient::VirtualFunctionAssistProcessor::cancel()
|
||||||
{
|
{
|
||||||
resetData();
|
resetData();
|
||||||
|
|||||||
@@ -70,12 +70,17 @@ public:
|
|||||||
void findLocalUsages(TextEditor::TextDocument *document, const QTextCursor &cursor,
|
void findLocalUsages(TextEditor::TextDocument *document, const QTextCursor &cursor,
|
||||||
CppTools::RefactoringEngineInterface::RenameCallback &&callback);
|
CppTools::RefactoringEngineInterface::RenameCallback &&callback);
|
||||||
|
|
||||||
|
void gatherHelpItemForTooltip(
|
||||||
|
const LanguageServerProtocol::HoverRequest::Response &hoverResponse,
|
||||||
|
const LanguageServerProtocol::DocumentUri &uri);
|
||||||
|
|
||||||
void enableTesting();
|
void enableTesting();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void indexingFinished();
|
void indexingFinished();
|
||||||
void foundReferences(const QList<Core::SearchResultItem> &items);
|
void foundReferences(const QList<Core::SearchResultItem> &items);
|
||||||
void findUsagesDone();
|
void findUsagesDone();
|
||||||
|
void helpItemGathered(const Core::HelpItem &helpItem);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void handleDiagnostics(const LanguageServerProtocol::PublishDiagnosticsParams ¶ms) override;
|
void handleDiagnostics(const LanguageServerProtocol::PublishDiagnosticsParams ¶ms) override;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include "clanghoverhandler.h"
|
#include "clanghoverhandler.h"
|
||||||
|
|
||||||
#include "clangeditordocumentprocessor.h"
|
#include "clangeditordocumentprocessor.h"
|
||||||
|
#include "clangmodelmanagersupport.h"
|
||||||
|
|
||||||
#include <coreplugin/helpmanager.h>
|
#include <coreplugin/helpmanager.h>
|
||||||
#include <cpptools/cppmodelmanager.h>
|
#include <cpptools/cppmodelmanager.h>
|
||||||
@@ -97,6 +98,12 @@ void ClangHoverHandler::identifyMatch(TextEditorWidget *editorWidget,
|
|||||||
int pos,
|
int pos,
|
||||||
BaseHoverHandler::ReportPriority report)
|
BaseHoverHandler::ReportPriority report)
|
||||||
{
|
{
|
||||||
|
if (ClangModelManagerSupport::instance()
|
||||||
|
->clientForFile(editorWidget->textDocument()->filePath())) {
|
||||||
|
report(Priority_None);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Reset
|
// Reset
|
||||||
m_futureWatcher.reset();
|
m_futureWatcher.reset();
|
||||||
m_cursorPosition = -1;
|
m_cursorPosition = -1;
|
||||||
|
|||||||
@@ -492,6 +492,145 @@ void ClangdTestLocalReferences::test()
|
|||||||
QCOMPARE(actualRanges, expectedRanges);
|
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 Tests
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
} // namespace ClangCodeModel
|
} // namespace ClangCodeModel
|
||||||
|
|||||||
@@ -115,6 +115,17 @@ private slots:
|
|||||||
void test();
|
void test();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ClangdTestTooltips : public ClangdTest
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
ClangdTestTooltips();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void test_data();
|
||||||
|
void test();
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Tests
|
} // namespace Tests
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
} // namespace ClangCodeModel
|
} // namespace ClangCodeModel
|
||||||
|
|||||||
@@ -39,5 +39,7 @@
|
|||||||
<file>follow-symbol/main.cpp</file>
|
<file>follow-symbol/main.cpp</file>
|
||||||
<file>local-references/local-references.pro</file>
|
<file>local-references/local-references.pro</file>
|
||||||
<file>local-references/references.cpp</file>
|
<file>local-references/references.cpp</file>
|
||||||
|
<file>tooltips/tooltips.cpp</file>
|
||||||
|
<file>tooltips/tooltips.pro</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
212
src/plugins/clangcodemodel/test/data/tooltips/tooltips.cpp
Normal file
212
src/plugins/clangcodemodel/test/data/tooltips/tooltips.cpp
Normal 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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
TEMPLATE = app
|
||||||
|
CONFIG -= qt
|
||||||
|
SOURCES = tooltips.cpp
|
||||||
@@ -51,6 +51,18 @@ void HoverHandler::abort()
|
|||||||
if (m_client && m_client->reachable() && m_currentRequest.has_value())
|
if (m_client && m_client->reachable() && m_currentRequest.has_value())
|
||||||
m_client->cancelRequest(*m_currentRequest);
|
m_client->cancelRequest(*m_currentRequest);
|
||||||
m_currentRequest.reset();
|
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,
|
void HoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget,
|
||||||
@@ -64,10 +76,11 @@ void HoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget,
|
|||||||
report(Priority_None);
|
report(Priority_None);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto uri = DocumentUri::fromFilePath(editorWidget->textDocument()->filePath());
|
m_uri = DocumentUri::fromFilePath(editorWidget->textDocument()->filePath());
|
||||||
|
m_response = {};
|
||||||
QTextCursor tc = editorWidget->textCursor();
|
QTextCursor tc = editorWidget->textCursor();
|
||||||
tc.setPosition(pos);
|
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()) {
|
if (!diagnostics.isEmpty()) {
|
||||||
const QStringList messages = Utils::transform(diagnostics, &Diagnostic::message);
|
const QStringList messages = Utils::transform(diagnostics, &Diagnostic::message);
|
||||||
setToolTip(messages.join('\n'));
|
setToolTip(messages.join('\n'));
|
||||||
@@ -101,7 +114,7 @@ void HoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget,
|
|||||||
m_report = report;
|
m_report = report;
|
||||||
QTextCursor cursor = editorWidget->textCursor();
|
QTextCursor cursor = editorWidget->textCursor();
|
||||||
cursor.setPosition(pos);
|
cursor.setPosition(pos);
|
||||||
HoverRequest request((TextDocumentPositionParams(TextDocumentIdentifier(uri), Position(cursor))));
|
HoverRequest request((TextDocumentPositionParams(TextDocumentIdentifier(m_uri), Position(cursor))));
|
||||||
m_currentRequest = request.id();
|
m_currentRequest = request.id();
|
||||||
request.setResponseCallback(
|
request.setResponseCallback(
|
||||||
[this](const HoverRequest::Response &response) { handleResponse(response); });
|
[this](const HoverRequest::Response &response) { handleResponse(response); });
|
||||||
@@ -115,8 +128,14 @@ void HoverHandler::handleResponse(const HoverRequest::Response &response)
|
|||||||
if (m_client)
|
if (m_client)
|
||||||
m_client->log(error.value());
|
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());
|
setContent(result.value().content());
|
||||||
|
}
|
||||||
m_report(priority());
|
m_report(priority());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,14 +25,21 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "languageclient_global.h"
|
||||||
|
|
||||||
#include <languageserverprotocol/languagefeatures.h>
|
#include <languageserverprotocol/languagefeatures.h>
|
||||||
#include <texteditor/basehoverhandler.h>
|
#include <texteditor/basehoverhandler.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
namespace LanguageClient {
|
namespace LanguageClient {
|
||||||
|
|
||||||
class Client;
|
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)
|
Q_DECLARE_TR_FUNCTIONS(HoverHandler)
|
||||||
public:
|
public:
|
||||||
@@ -41,6 +48,9 @@ public:
|
|||||||
|
|
||||||
void abort() override;
|
void abort() override;
|
||||||
|
|
||||||
|
void setHelpItemProvider(const HelpItemProvider &provider) { m_helpItemProvider = provider; }
|
||||||
|
void setHelpItem(const LanguageServerProtocol::MessageId &msgId, const Core::HelpItem &help);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void identifyMatch(TextEditor::TextEditorWidget *editorWidget,
|
void identifyMatch(TextEditor::TextEditorWidget *editorWidget,
|
||||||
int pos,
|
int pos,
|
||||||
@@ -52,7 +62,10 @@ private:
|
|||||||
|
|
||||||
QPointer<Client> m_client;
|
QPointer<Client> m_client;
|
||||||
Utils::optional<LanguageServerProtocol::MessageId> m_currentRequest;
|
Utils::optional<LanguageServerProtocol::MessageId> m_currentRequest;
|
||||||
|
LanguageServerProtocol::DocumentUri m_uri;
|
||||||
|
LanguageServerProtocol::HoverRequest::Response m_response;
|
||||||
TextEditor::BaseHoverHandler::ReportPriority m_report;
|
TextEditor::BaseHoverHandler::ReportPriority m_report;
|
||||||
|
HelpItemProvider m_helpItemProvider;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace LanguageClient
|
} // namespace LanguageClient
|
||||||
|
|||||||
@@ -5778,6 +5778,13 @@ void TextEditorWidget::removeHoverHandler(BaseHoverHandler *handler)
|
|||||||
d->m_hoverHandlers.removeAll(handler);
|
d->m_hoverHandlers.removeAll(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef WITH_TESTS
|
||||||
|
void TextEditorWidget::processTooltipRequest(const QTextCursor &c)
|
||||||
|
{
|
||||||
|
d->processTooltipRequest(c);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void TextEditorWidget::extraAreaLeaveEvent(QEvent *)
|
void TextEditorWidget::extraAreaLeaveEvent(QEvent *)
|
||||||
{
|
{
|
||||||
d->extraAreaPreviousMarkTooltipRequestedLine = -1;
|
d->extraAreaPreviousMarkTooltipRequestedLine = -1;
|
||||||
|
|||||||
@@ -491,6 +491,10 @@ public:
|
|||||||
void addHoverHandler(BaseHoverHandler *handler);
|
void addHoverHandler(BaseHoverHandler *handler);
|
||||||
void removeHoverHandler(BaseHoverHandler *handler);
|
void removeHoverHandler(BaseHoverHandler *handler);
|
||||||
|
|
||||||
|
#ifdef WITH_TESTS
|
||||||
|
void processTooltipRequest(const QTextCursor &c);
|
||||||
|
#endif
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void assistFinished(); // Used in tests.
|
void assistFinished(); // Used in tests.
|
||||||
void readOnlyChanged();
|
void readOnlyChanged();
|
||||||
|
|||||||
Reference in New Issue
Block a user