forked from qt-creator/qt-creator
ClangCodeModel: Use dedicated TU for code related to the clangd AST
The clangdclient.cpp file is rather large, and the AST stuff is a nicely self-contained chunk of code to move out. No functional changes. Change-Id: I0240413f561f5c67ca5ee310b5c4253ffa62fdae Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
@@ -16,6 +16,7 @@ add_qtc_plugin(ClangCodeModel
|
|||||||
clangcompletioncontextanalyzer.cpp clangcompletioncontextanalyzer.h
|
clangcompletioncontextanalyzer.cpp clangcompletioncontextanalyzer.h
|
||||||
clangconstants.h
|
clangconstants.h
|
||||||
clangdclient.cpp clangdclient.h
|
clangdclient.cpp clangdclient.h
|
||||||
|
clangdast.cpp clangdast.h
|
||||||
clangdiagnostictooltipwidget.cpp clangdiagnostictooltipwidget.h
|
clangdiagnostictooltipwidget.cpp clangdiagnostictooltipwidget.h
|
||||||
clangdquickfixfactory.cpp clangdquickfixfactory.h
|
clangdquickfixfactory.cpp clangdquickfixfactory.h
|
||||||
clangdqpropertyhighlighter.cpp clangdqpropertyhighlighter.h
|
clangdqpropertyhighlighter.cpp clangdqpropertyhighlighter.h
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ QtcPlugin {
|
|||||||
"clangcompletioncontextanalyzer.cpp",
|
"clangcompletioncontextanalyzer.cpp",
|
||||||
"clangcompletioncontextanalyzer.h",
|
"clangcompletioncontextanalyzer.h",
|
||||||
"clangconstants.h",
|
"clangconstants.h",
|
||||||
|
"clangdast.cpp",
|
||||||
|
"clangdast.h",
|
||||||
"clangdclient.cpp",
|
"clangdclient.cpp",
|
||||||
"clangdclient.h",
|
"clangdclient.h",
|
||||||
"clangdiagnostictooltipwidget.cpp",
|
"clangdiagnostictooltipwidget.cpp",
|
||||||
|
|||||||
410
src/plugins/clangcodemodel/clangdast.cpp
Normal file
410
src/plugins/clangcodemodel/clangdast.cpp
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "clangdast.h"
|
||||||
|
|
||||||
|
#include <languageclient/client.h>
|
||||||
|
#include <languageserverprotocol/icontent.h>
|
||||||
|
#include <languageserverprotocol/jsonkeys.h>
|
||||||
|
#include <languageserverprotocol/lsptypes.h>
|
||||||
|
#include <utils/filepath.h>
|
||||||
|
|
||||||
|
#include <QStringView>
|
||||||
|
|
||||||
|
using namespace Core;
|
||||||
|
using namespace LanguageClient;
|
||||||
|
using namespace LanguageServerProtocol;
|
||||||
|
using namespace Utils;
|
||||||
|
|
||||||
|
namespace ClangCodeModel::Internal {
|
||||||
|
|
||||||
|
static constexpr char roleKey[] = "role";
|
||||||
|
static constexpr char arcanaKey[] = "arcana";
|
||||||
|
|
||||||
|
QString ClangdAstNode::role() const { return typedValue<QString>(roleKey); }
|
||||||
|
QString ClangdAstNode::kind() const { return typedValue<QString>(kindKey); }
|
||||||
|
optional<QString> ClangdAstNode::detail() const { return optionalValue<QString>(detailKey); }
|
||||||
|
optional<QString> ClangdAstNode::arcana() const { return optionalValue<QString>(arcanaKey); }
|
||||||
|
Range ClangdAstNode::range() const { return typedValue<Range>(rangeKey); }
|
||||||
|
bool ClangdAstNode::hasRange() const { return contains(rangeKey); }
|
||||||
|
bool ClangdAstNode::isValid() const { return contains(roleKey) && contains(kindKey); }
|
||||||
|
|
||||||
|
optional<QList<ClangdAstNode> > ClangdAstNode::children() const
|
||||||
|
{
|
||||||
|
return optionalArray<ClangdAstNode>(childrenKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClangdAstNode::arcanaContains(const QString &s) const
|
||||||
|
{
|
||||||
|
const optional<QString> arcanaString = arcana();
|
||||||
|
return arcanaString && arcanaString->contains(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClangdAstNode::isMemberFunctionCall() const
|
||||||
|
{
|
||||||
|
return role() == "expression" && (kind() == "CXXMemberCall"
|
||||||
|
|| (kind() == "Member" && arcanaContains("member function")));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClangdAstNode::isPureVirtualDeclaration() const
|
||||||
|
{
|
||||||
|
return role() == "declaration" && kind() == "CXXMethod" && arcanaContains("virtual pure");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClangdAstNode::isPureVirtualDefinition() const
|
||||||
|
{
|
||||||
|
return role() == "declaration" && kind() == "CXXMethod" && arcanaContains("' pure");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClangdAstNode::mightBeAmbiguousVirtualCall() const
|
||||||
|
{
|
||||||
|
if (!isMemberFunctionCall())
|
||||||
|
return false;
|
||||||
|
bool hasBaseCast = false;
|
||||||
|
bool hasRecordType = false;
|
||||||
|
const QList<ClangdAstNode> childList = children().value_or(QList<ClangdAstNode>());
|
||||||
|
for (const ClangdAstNode &c : childList) {
|
||||||
|
if (!hasBaseCast && c.detailIs("UncheckedDerivedToBase"))
|
||||||
|
hasBaseCast = true;
|
||||||
|
if (!hasRecordType && c.role() == "specifier" && c.kind() == "TypeSpec")
|
||||||
|
hasRecordType = true;
|
||||||
|
if (hasBaseCast && hasRecordType)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClangdAstNode::isTemplateParameterDeclaration() const
|
||||||
|
{
|
||||||
|
return role() == "declaration" && (kind() == "TemplateTypeParm"
|
||||||
|
|| kind() == "NonTypeTemplateParm");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ClangCodeModel::Internal::ClangdAstNode::type() const
|
||||||
|
{
|
||||||
|
const optional<QString> arcanaString = arcana();
|
||||||
|
if (!arcanaString)
|
||||||
|
return {};
|
||||||
|
return typeFromPos(*arcanaString, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ClangdAstNode::typeFromPos(const QString &s, int pos) const
|
||||||
|
{
|
||||||
|
const int quote1Offset = s.indexOf('\'', pos);
|
||||||
|
if (quote1Offset == -1)
|
||||||
|
return {};
|
||||||
|
const int quote2Offset = s.indexOf('\'', quote1Offset + 1);
|
||||||
|
if (quote2Offset == -1)
|
||||||
|
return {};
|
||||||
|
if (s.mid(quote2Offset + 1, 2) == ":'")
|
||||||
|
return typeFromPos(s, quote2Offset + 2);
|
||||||
|
return s.mid(quote1Offset + 1, quote2Offset - quote1Offset - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
HelpItem::Category ClangdAstNode::qdocCategoryForDeclaration(HelpItem::Category fallback)
|
||||||
|
{
|
||||||
|
const auto childList = children();
|
||||||
|
if (!childList || childList->size() < 2)
|
||||||
|
return fallback;
|
||||||
|
const ClangdAstNode c1 = childList->first();
|
||||||
|
if (c1.role() != "type" || c1.kind() != "Auto")
|
||||||
|
return fallback;
|
||||||
|
QList<ClangdAstNode> typeCandidates = {childList->at(1)};
|
||||||
|
while (!typeCandidates.isEmpty()) {
|
||||||
|
const ClangdAstNode 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<ClangdAstNode>());
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClangdAstNode::hasConstType() const
|
||||||
|
{
|
||||||
|
QString theType = type();
|
||||||
|
if (theType.endsWith("const"))
|
||||||
|
theType.chop(5);
|
||||||
|
|
||||||
|
// We don't care about the "inner" type of templates.
|
||||||
|
const int openAngleBracketPos = theType.indexOf('<');
|
||||||
|
if (openAngleBracketPos != -1) {
|
||||||
|
const int closingAngleBracketPos = theType.lastIndexOf('>');
|
||||||
|
if (closingAngleBracketPos > openAngleBracketPos) {
|
||||||
|
theType = theType.left(openAngleBracketPos)
|
||||||
|
+ theType.mid(closingAngleBracketPos + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const int xrefCount = theType.count("&&");
|
||||||
|
const int refCount = theType.count('&') - 2 * xrefCount;
|
||||||
|
const int ptrRefCount = theType.count('*') + refCount;
|
||||||
|
const int constCount = theType.count("const");
|
||||||
|
if (ptrRefCount == 0)
|
||||||
|
return constCount > 0 || detailIs("LValueToRValue") || arcanaContains("xvalue");
|
||||||
|
return ptrRefCount <= constCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClangdAstNode::childContainsRange(int index, const LanguageServerProtocol::Range &range) const
|
||||||
|
{
|
||||||
|
const optional<QList<ClangdAstNode>> childList = children();
|
||||||
|
return childList && childList->size() > index && childList->at(index).range().contains(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClangdAstNode::hasChildWithRole(const QString &role) const
|
||||||
|
{
|
||||||
|
return Utils::contains(children().value_or(QList<ClangdAstNode>()),
|
||||||
|
[&role](const ClangdAstNode &c) { return c.role() == role; });
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ClangdAstNode::operatorString() const
|
||||||
|
{
|
||||||
|
if (kind() == "BinaryOperator")
|
||||||
|
return detail().value_or(QString());
|
||||||
|
QTC_ASSERT(kind() == "CXXOperatorCall", return {});
|
||||||
|
const optional<QString> arcanaString = arcana();
|
||||||
|
if (!arcanaString)
|
||||||
|
return {};
|
||||||
|
const int closingQuoteOffset = arcanaString->lastIndexOf('\'');
|
||||||
|
if (closingQuoteOffset <= 0)
|
||||||
|
return {};
|
||||||
|
const int openingQuoteOffset = arcanaString->lastIndexOf('\'', closingQuoteOffset - 1);
|
||||||
|
if (openingQuoteOffset == -1)
|
||||||
|
return {};
|
||||||
|
return arcanaString->mid(openingQuoteOffset + 1, closingQuoteOffset
|
||||||
|
- openingQuoteOffset - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClangdAstNode::FileStatus ClangdAstNode::fileStatus(const FilePath &thisFile) const
|
||||||
|
{
|
||||||
|
const optional<QString> arcanaString = arcana();
|
||||||
|
if (!arcanaString)
|
||||||
|
return FileStatus::Unknown;
|
||||||
|
|
||||||
|
// Example arcanas:
|
||||||
|
// "FunctionDecl 0x7fffb5d0dbd0 </tmp/test.cpp:1:1, line:5:1> line:1:6 func 'void ()'"
|
||||||
|
// "VarDecl 0x7fffb5d0dcf0 </tmp/test.cpp:2:5, /tmp/test.h:1:1> /tmp/test.cpp:2:10 b 'bool' cinit"
|
||||||
|
// The second one is for a particularly silly construction where the RHS of an
|
||||||
|
// initialization comes from an included header.
|
||||||
|
const int openPos = arcanaString->indexOf('<');
|
||||||
|
if (openPos == -1)
|
||||||
|
return FileStatus::Unknown;
|
||||||
|
const int closePos = arcanaString->indexOf('>', openPos + 1);
|
||||||
|
if (closePos == -1)
|
||||||
|
return FileStatus::Unknown;
|
||||||
|
bool hasOurs = false;
|
||||||
|
bool hasOther = false;
|
||||||
|
for (int startPos = openPos + 1; startPos < closePos;) {
|
||||||
|
int colon1Pos = arcanaString->indexOf(':', startPos);
|
||||||
|
if (colon1Pos == -1 || colon1Pos > closePos)
|
||||||
|
break;
|
||||||
|
if (HostOsInfo::isWindowsHost())
|
||||||
|
colon1Pos = arcanaString->indexOf(':', colon1Pos + 1);
|
||||||
|
if (colon1Pos == -1 || colon1Pos > closePos)
|
||||||
|
break;
|
||||||
|
const int colon2Pos = arcanaString->indexOf(':', colon1Pos + 2);
|
||||||
|
if (colon2Pos == -1 || colon2Pos > closePos)
|
||||||
|
break;
|
||||||
|
const int line = subViewEnd(*arcanaString, colon1Pos + 1, colon2Pos).toInt();
|
||||||
|
if (line == 0)
|
||||||
|
break;
|
||||||
|
const QStringView fileOrLineString = subViewEnd(*arcanaString, startPos, colon1Pos);
|
||||||
|
if (fileOrLineString != QLatin1String("line")) {
|
||||||
|
if (FilePath::fromUserInput(fileOrLineString.toString()) == thisFile)
|
||||||
|
hasOurs = true;
|
||||||
|
else
|
||||||
|
hasOther = true;
|
||||||
|
}
|
||||||
|
const int commaPos = arcanaString->indexOf(',', colon2Pos + 2);
|
||||||
|
if (commaPos != -1)
|
||||||
|
startPos = commaPos + 2;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (hasOurs)
|
||||||
|
return hasOther ? FileStatus::Mixed : FileStatus::Ours;
|
||||||
|
return hasOther ? FileStatus::Foreign : FileStatus::Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClangdAstNode::print(int indent) const
|
||||||
|
{
|
||||||
|
(qDebug().noquote() << QByteArray(indent, ' ')).quote() << role() << kind()
|
||||||
|
<< detail().value_or(QString()) << arcana().value_or(QString()) << range();
|
||||||
|
for (const ClangdAstNode &c : children().value_or(QList<ClangdAstNode>()))
|
||||||
|
c.print(indent + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringView subViewLen(const QString &s, qsizetype start, qsizetype length)
|
||||||
|
{
|
||||||
|
if (start < 0 || length < 0 || start + length > s.length())
|
||||||
|
return {};
|
||||||
|
return QStringView(s).mid(start, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringView subViewEnd(const QString &s, qsizetype start, qsizetype end)
|
||||||
|
{
|
||||||
|
return subViewLen(s, start, end - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AstPathCollector
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AstPathCollector(const ClangdAstNode &root, const Range &range)
|
||||||
|
: m_root(root), m_range(range) {}
|
||||||
|
|
||||||
|
ClangdAstPath collectPath()
|
||||||
|
{
|
||||||
|
if (!m_root.isValid())
|
||||||
|
return {};
|
||||||
|
visitNode(m_root, true);
|
||||||
|
return m_done ? m_path : m_longestSubPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void visitNode(const ClangdAstNode &node, bool isRoot = false)
|
||||||
|
{
|
||||||
|
if (!isRoot && (!node.hasRange() || !node.range().contains(m_range)))
|
||||||
|
return;
|
||||||
|
m_path << node;
|
||||||
|
|
||||||
|
class PathDropper {
|
||||||
|
public:
|
||||||
|
PathDropper(AstPathCollector &collector) : m_collector(collector) {};
|
||||||
|
~PathDropper() {
|
||||||
|
if (m_collector.m_done)
|
||||||
|
return;
|
||||||
|
if (m_collector.m_path.size() > m_collector.m_longestSubPath.size())
|
||||||
|
m_collector.m_longestSubPath = m_collector.m_path;
|
||||||
|
m_collector.m_path.removeLast();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
AstPathCollector &m_collector;
|
||||||
|
} pathDropper(*this);
|
||||||
|
|
||||||
|
// Still traverse the children, because they could have the same range.
|
||||||
|
if (node.range() == m_range)
|
||||||
|
m_done = true;
|
||||||
|
|
||||||
|
const auto children = node.children();
|
||||||
|
if (!children)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QList<ClangdAstNode> childrenToCheck;
|
||||||
|
if (node.kind() == "Function" || node.role() == "expression") {
|
||||||
|
// Functions and expressions can contain implicit nodes that make the list unsorted.
|
||||||
|
// They cannot be ignored, as we need to consider them in certain contexts.
|
||||||
|
// Therefore, the binary search cannot be used here.
|
||||||
|
childrenToCheck = *children;
|
||||||
|
} else {
|
||||||
|
for (auto it = std::lower_bound(children->cbegin(), children->cend(), m_range,
|
||||||
|
leftOfRange);
|
||||||
|
it != children->cend() && !m_range.isLeftOf(it->range()); ++it) {
|
||||||
|
childrenToCheck << *it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool wasDone = m_done;
|
||||||
|
for (const ClangdAstNode &child : qAsConst(childrenToCheck)) {
|
||||||
|
visitNode(child);
|
||||||
|
if (m_done && !wasDone)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool leftOfRange(const ClangdAstNode &node, const Range &range)
|
||||||
|
{
|
||||||
|
// Class and struct nodes can contain implicit constructors, destructors and
|
||||||
|
// operators, which appear at the end of the list, but whose range is the same
|
||||||
|
// as the class name. Therefore, we must force them not to compare less to
|
||||||
|
// anything else.
|
||||||
|
return node.range().isLeftOf(range) && !node.arcanaContains(" implicit ");
|
||||||
|
};
|
||||||
|
|
||||||
|
const ClangdAstNode &m_root;
|
||||||
|
const Range &m_range;
|
||||||
|
ClangdAstPath m_path;
|
||||||
|
ClangdAstPath m_longestSubPath;
|
||||||
|
bool m_done = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
ClangdAstPath getAstPath(const ClangdAstNode &root, const Range &range)
|
||||||
|
{
|
||||||
|
return AstPathCollector(root, range).collectPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
ClangdAstPath getAstPath(const ClangdAstNode &root, const Position &pos)
|
||||||
|
{
|
||||||
|
return getAstPath(root, Range(pos, pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageId requestAst(Client *client, const FilePath &filePath, const Range range,
|
||||||
|
const AstHandler &handler)
|
||||||
|
{
|
||||||
|
class AstParams : public JsonObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AstParams(const TextDocumentIdentifier &document, const Range &range = {})
|
||||||
|
{
|
||||||
|
setTextDocument(document);
|
||||||
|
if (range.isValid())
|
||||||
|
setRange(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
using JsonObject::JsonObject;
|
||||||
|
|
||||||
|
// The open file to inspect.
|
||||||
|
TextDocumentIdentifier textDocument() const
|
||||||
|
{ return typedValue<TextDocumentIdentifier>(textDocumentKey); }
|
||||||
|
void setTextDocument(const TextDocumentIdentifier &id) { insert(textDocumentKey, id); }
|
||||||
|
|
||||||
|
// The region of the source code whose AST is fetched. The highest-level node that entirely
|
||||||
|
// contains the range is returned.
|
||||||
|
optional<Range> range() const { return optionalValue<Range>(rangeKey); }
|
||||||
|
void setRange(const Range &range) { insert(rangeKey, range); }
|
||||||
|
|
||||||
|
bool isValid() const override { return contains(textDocumentKey); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class AstRequest : public Request<ClangdAstNode, std::nullptr_t, AstParams>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using Request::Request;
|
||||||
|
explicit AstRequest(const AstParams ¶ms) : Request("textDocument/ast", params) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
AstRequest request(AstParams(TextDocumentIdentifier(DocumentUri::fromFilePath(filePath)),
|
||||||
|
range));
|
||||||
|
request.setResponseCallback([handler, reqId = request.id()](AstRequest::Response response) {
|
||||||
|
const auto result = response.result();
|
||||||
|
handler(result ? *result : ClangdAstNode(), reqId);
|
||||||
|
});
|
||||||
|
client->sendContent(request, Client::SendDocUpdates::Ignore);
|
||||||
|
return request.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ClangCodeModel::Internal
|
||||||
123
src/plugins/clangcodemodel/clangdast.h
Normal file
123
src/plugins/clangcodemodel/clangdast.h
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <coreplugin/helpitem.h>
|
||||||
|
#include <languageserverprotocol/jsonobject.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace LanguageClient { class Client; }
|
||||||
|
namespace LanguageServerProtocol {
|
||||||
|
class MessageId;
|
||||||
|
class Position;
|
||||||
|
class Range;
|
||||||
|
}
|
||||||
|
namespace Utils { class FilePath; }
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
class QStringView;
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
namespace ClangCodeModel::Internal {
|
||||||
|
|
||||||
|
// string view helpers
|
||||||
|
QStringView subViewLen(const QString &s, qsizetype start, qsizetype length);
|
||||||
|
QStringView subViewEnd(const QString &s, qsizetype start, qsizetype end);
|
||||||
|
|
||||||
|
class ClangdAstNode : public LanguageServerProtocol::JsonObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using JsonObject::JsonObject;
|
||||||
|
|
||||||
|
// The general kind of node, such as “expression”. Corresponds to clang’s base AST node type,
|
||||||
|
// such as Expr. The most common are “expression”, “statement”, “type” and “declaration”.
|
||||||
|
QString role() const;
|
||||||
|
|
||||||
|
// The specific kind of node, such as “BinaryOperator”. Corresponds to clang’s concrete
|
||||||
|
// node class, with Expr etc suffix dropped.
|
||||||
|
QString kind() const;
|
||||||
|
|
||||||
|
// Brief additional details, such as ‘||’. Information present here depends on the node kind.
|
||||||
|
Utils::optional<QString> detail() const;
|
||||||
|
|
||||||
|
// One line dump of information, similar to that printed by clang -Xclang -ast-dump.
|
||||||
|
// Only available for certain types of nodes.
|
||||||
|
Utils::optional<QString> arcana() const;
|
||||||
|
|
||||||
|
// The part of the code that produced this node. Missing for implicit nodes, nodes produced
|
||||||
|
// by macro expansion, etc.
|
||||||
|
LanguageServerProtocol::Range range() const;
|
||||||
|
|
||||||
|
// Descendants describing the internal structure. The tree of nodes is similar to that printed
|
||||||
|
// by clang -Xclang -ast-dump, or that traversed by clang::RecursiveASTVisitor.
|
||||||
|
Utils::optional<QList<ClangdAstNode>> children() const;
|
||||||
|
|
||||||
|
bool hasRange() const;
|
||||||
|
bool arcanaContains(const QString &s) const;
|
||||||
|
bool detailIs(const QString &s) const { return detail() && *detail() == s; }
|
||||||
|
bool isMemberFunctionCall() const;
|
||||||
|
bool isPureVirtualDeclaration() const;
|
||||||
|
bool isPureVirtualDefinition() const;
|
||||||
|
bool mightBeAmbiguousVirtualCall() const;
|
||||||
|
bool isNamespace() const { return role() == "declaration" && kind() == "Namespace"; }
|
||||||
|
bool isTemplateParameterDeclaration() const;;
|
||||||
|
QString type() const;
|
||||||
|
QString typeFromPos(const QString &s, int pos) const;
|
||||||
|
Core::HelpItem::Category qdocCategoryForDeclaration(Core::HelpItem::Category fallback);
|
||||||
|
|
||||||
|
// Returns true <=> the type is "recursively const".
|
||||||
|
// E.g. returns true for "const int &", "const int *" and "const int * const *",
|
||||||
|
// and false for "int &" and "const int **".
|
||||||
|
// For non-pointer types such as "int", we check whether they are used as lvalues
|
||||||
|
// or rvalues.
|
||||||
|
bool hasConstType() const;
|
||||||
|
|
||||||
|
bool childContainsRange(int index, const LanguageServerProtocol::Range &range) const;
|
||||||
|
bool hasChildWithRole(const QString &role) const;
|
||||||
|
QString operatorString() const;
|
||||||
|
|
||||||
|
enum class FileStatus { Ours, Foreign, Mixed, Unknown };
|
||||||
|
FileStatus fileStatus(const Utils::FilePath &thisFile) const;
|
||||||
|
|
||||||
|
// For debugging.
|
||||||
|
void print(int indent = 0) const;
|
||||||
|
|
||||||
|
bool isValid() const override;
|
||||||
|
};
|
||||||
|
using ClangdAstPath = QList<ClangdAstNode>;
|
||||||
|
|
||||||
|
ClangdAstPath getAstPath(const ClangdAstNode &root, const LanguageServerProtocol::Range &range);
|
||||||
|
ClangdAstPath getAstPath(const ClangdAstNode &root, const LanguageServerProtocol::Position &pos);
|
||||||
|
|
||||||
|
using AstHandler = std::function<void(const ClangdAstNode &node,
|
||||||
|
const LanguageServerProtocol::MessageId &requestId)>;
|
||||||
|
LanguageServerProtocol::MessageId requestAst(LanguageClient::Client *client,
|
||||||
|
const Utils::FilePath &filePath, const LanguageServerProtocol::Range range,
|
||||||
|
const AstHandler &handler);
|
||||||
|
|
||||||
|
} // namespace ClangCodeModel::Internal
|
||||||
|
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
#include "clangcompletioncontextanalyzer.h"
|
#include "clangcompletioncontextanalyzer.h"
|
||||||
#include "clangconstants.h"
|
#include "clangconstants.h"
|
||||||
|
#include "clangdast.h"
|
||||||
#include "clangdlocatorfilters.h"
|
#include "clangdlocatorfilters.h"
|
||||||
#include "clangdqpropertyhighlighter.h"
|
#include "clangdqpropertyhighlighter.h"
|
||||||
#include "clangmodelmanagersupport.h"
|
#include "clangmodelmanagersupport.h"
|
||||||
@@ -116,368 +117,7 @@ static Q_LOGGING_CATEGORY(clangdLogCompletion, "qtc.clangcodemodel.clangd.comple
|
|||||||
QtWarningMsg);
|
QtWarningMsg);
|
||||||
static QString indexingToken() { return "backgroundIndexProgress"; }
|
static QString indexingToken() { return "backgroundIndexProgress"; }
|
||||||
|
|
||||||
static QStringView subViewLen(const QString &s, qsizetype start, qsizetype length)
|
static Usage::Type getUsageType(const ClangdAstPath &path)
|
||||||
{
|
|
||||||
if (start < 0 || length < 0 || start + length > s.length())
|
|
||||||
return {};
|
|
||||||
return QStringView(s).mid(start, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
static QStringView subViewEnd(const QString &s, qsizetype start, qsizetype end)
|
|
||||||
{
|
|
||||||
return subViewLen(s, start, end - start);
|
|
||||||
}
|
|
||||||
|
|
||||||
class AstNode : public JsonObject
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using JsonObject::JsonObject;
|
|
||||||
|
|
||||||
static constexpr char roleKey[] = "role";
|
|
||||||
static constexpr char arcanaKey[] = "arcana";
|
|
||||||
|
|
||||||
// The general kind of node, such as “expression”. Corresponds to clang’s base AST node type,
|
|
||||||
// such as Expr. The most common are “expression”, “statement”, “type” and “declaration”.
|
|
||||||
QString role() const { return typedValue<QString>(roleKey); }
|
|
||||||
|
|
||||||
// The specific kind of node, such as “BinaryOperator”. Corresponds to clang’s concrete
|
|
||||||
// node class, with Expr etc suffix dropped.
|
|
||||||
QString kind() const { return typedValue<QString>(kindKey); }
|
|
||||||
|
|
||||||
// Brief additional details, such as ‘||’. Information present here depends on the node kind.
|
|
||||||
Utils::optional<QString> detail() const { return optionalValue<QString>(detailKey); }
|
|
||||||
|
|
||||||
// One line dump of information, similar to that printed by clang -Xclang -ast-dump.
|
|
||||||
// Only available for certain types of nodes.
|
|
||||||
Utils::optional<QString> arcana() const { return optionalValue<QString>(arcanaKey); }
|
|
||||||
|
|
||||||
// The part of the code that produced this node. Missing for implicit nodes, nodes produced
|
|
||||||
// by macro expansion, etc.
|
|
||||||
Range range() const { return typedValue<Range>(rangeKey); }
|
|
||||||
|
|
||||||
// Descendants describing the internal structure. The tree of nodes is similar to that printed
|
|
||||||
// by clang -Xclang -ast-dump, or that traversed by clang::RecursiveASTVisitor.
|
|
||||||
Utils::optional<QList<AstNode>> children() const { return optionalArray<AstNode>(childrenKey); }
|
|
||||||
|
|
||||||
bool hasRange() const { return contains(rangeKey); }
|
|
||||||
|
|
||||||
bool arcanaContains(const QString &s) const
|
|
||||||
{
|
|
||||||
const Utils::optional<QString> arcanaString = arcana();
|
|
||||||
return arcanaString && arcanaString->contains(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool detailIs(const QString &s) const
|
|
||||||
{
|
|
||||||
return detail() && *detail() == s;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isMemberFunctionCall() const
|
|
||||||
{
|
|
||||||
return role() == "expression" && (kind() == "CXXMemberCall"
|
|
||||||
|| (kind() == "Member" && arcanaContains("member function")));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isPureVirtualDeclaration() const
|
|
||||||
{
|
|
||||||
return role() == "declaration" && kind() == "CXXMethod" && arcanaContains("virtual pure");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isPureVirtualDefinition() const
|
|
||||||
{
|
|
||||||
return role() == "declaration" && kind() == "CXXMethod" && arcanaContains("' pure");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool mightBeAmbiguousVirtualCall() const
|
|
||||||
{
|
|
||||||
if (!isMemberFunctionCall())
|
|
||||||
return false;
|
|
||||||
bool hasBaseCast = false;
|
|
||||||
bool hasRecordType = false;
|
|
||||||
const QList<AstNode> childList = children().value_or(QList<AstNode>());
|
|
||||||
for (const AstNode &c : childList) {
|
|
||||||
if (!hasBaseCast && c.detailIs("UncheckedDerivedToBase"))
|
|
||||||
hasBaseCast = true;
|
|
||||||
if (!hasRecordType && c.role() == "specifier" && c.kind() == "TypeSpec")
|
|
||||||
hasRecordType = true;
|
|
||||||
if (hasBaseCast && hasRecordType)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isNamespace() const { return role() == "declaration" && kind() == "Namespace"; }
|
|
||||||
|
|
||||||
bool isTemplateParameterDeclaration() const
|
|
||||||
{
|
|
||||||
return role() == "declaration" && (kind() == "TemplateTypeParm"
|
|
||||||
|| kind() == "NonTypeTemplateParm");
|
|
||||||
};
|
|
||||||
|
|
||||||
QString type() const
|
|
||||||
{
|
|
||||||
const Utils::optional<QString> arcanaString = arcana();
|
|
||||||
if (!arcanaString)
|
|
||||||
return {};
|
|
||||||
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 = s.indexOf('\'', quote1Offset + 1);
|
|
||||||
if (quote2Offset == -1)
|
|
||||||
return {};
|
|
||||||
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".
|
|
||||||
// E.g. returns true for "const int &", "const int *" and "const int * const *",
|
|
||||||
// and false for "int &" and "const int **".
|
|
||||||
// For non-pointer types such as "int", we check whether they are uses as lvalues
|
|
||||||
// or rvalues.
|
|
||||||
bool hasConstType() const
|
|
||||||
{
|
|
||||||
QString theType = type();
|
|
||||||
if (theType.endsWith("const"))
|
|
||||||
theType.chop(5);
|
|
||||||
|
|
||||||
// We don't care about the "inner" type of templates.
|
|
||||||
const int openAngleBracketPos = theType.indexOf('<');
|
|
||||||
if (openAngleBracketPos != -1) {
|
|
||||||
const int closingAngleBracketPos = theType.lastIndexOf('>');
|
|
||||||
if (closingAngleBracketPos > openAngleBracketPos) {
|
|
||||||
theType = theType.left(openAngleBracketPos)
|
|
||||||
+ theType.mid(closingAngleBracketPos + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const int xrefCount = theType.count("&&");
|
|
||||||
const int refCount = theType.count('&') - 2 * xrefCount;
|
|
||||||
const int ptrRefCount = theType.count('*') + refCount;
|
|
||||||
const int constCount = theType.count("const");
|
|
||||||
if (ptrRefCount == 0)
|
|
||||||
return constCount > 0 || detailIs("LValueToRValue") || arcanaContains("xvalue");
|
|
||||||
return ptrRefCount <= constCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool childContainsRange(int index, const Range &range) const
|
|
||||||
{
|
|
||||||
const Utils::optional<QList<AstNode>> childList = children();
|
|
||||||
return childList && childList->size() > index
|
|
||||||
&& childList->at(index).range().contains(range);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasChildWithRole(const QString &role) const
|
|
||||||
{
|
|
||||||
return Utils::contains(children().value_or(QList<AstNode>()), [&role](const AstNode &c) {
|
|
||||||
return c.role() == role;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QString operatorString() const
|
|
||||||
{
|
|
||||||
if (kind() == "BinaryOperator")
|
|
||||||
return detail().value_or(QString());
|
|
||||||
QTC_ASSERT(kind() == "CXXOperatorCall", return {});
|
|
||||||
const Utils::optional<QString> arcanaString = arcana();
|
|
||||||
if (!arcanaString)
|
|
||||||
return {};
|
|
||||||
const int closingQuoteOffset = arcanaString->lastIndexOf('\'');
|
|
||||||
if (closingQuoteOffset <= 0)
|
|
||||||
return {};
|
|
||||||
const int openingQuoteOffset = arcanaString->lastIndexOf('\'', closingQuoteOffset - 1);
|
|
||||||
if (openingQuoteOffset == -1)
|
|
||||||
return {};
|
|
||||||
return arcanaString->mid(openingQuoteOffset + 1, closingQuoteOffset
|
|
||||||
- openingQuoteOffset - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class FileStatus { Ours, Foreign, Mixed, Unknown };
|
|
||||||
FileStatus fileStatus(const Utils::FilePath &thisFile) const
|
|
||||||
{
|
|
||||||
const Utils::optional<QString> arcanaString = arcana();
|
|
||||||
if (!arcanaString)
|
|
||||||
return FileStatus::Unknown;
|
|
||||||
|
|
||||||
// Example arcanas:
|
|
||||||
// "FunctionDecl 0x7fffb5d0dbd0 </tmp/test.cpp:1:1, line:5:1> line:1:6 func 'void ()'"
|
|
||||||
// "VarDecl 0x7fffb5d0dcf0 </tmp/test.cpp:2:5, /tmp/test.h:1:1> /tmp/test.cpp:2:10 b 'bool' cinit"
|
|
||||||
// The second one is for a particularly silly construction where the RHS of an
|
|
||||||
// initialization comes from an included header.
|
|
||||||
const int openPos = arcanaString->indexOf('<');
|
|
||||||
if (openPos == -1)
|
|
||||||
return FileStatus::Unknown;
|
|
||||||
const int closePos = arcanaString->indexOf('>', openPos + 1);
|
|
||||||
if (closePos == -1)
|
|
||||||
return FileStatus::Unknown;
|
|
||||||
bool hasOurs = false;
|
|
||||||
bool hasOther = false;
|
|
||||||
for (int startPos = openPos + 1; startPos < closePos;) {
|
|
||||||
int colon1Pos = arcanaString->indexOf(':', startPos);
|
|
||||||
if (colon1Pos == -1 || colon1Pos > closePos)
|
|
||||||
break;
|
|
||||||
if (Utils::HostOsInfo::isWindowsHost())
|
|
||||||
colon1Pos = arcanaString->indexOf(':', colon1Pos + 1);
|
|
||||||
if (colon1Pos == -1 || colon1Pos > closePos)
|
|
||||||
break;
|
|
||||||
const int colon2Pos = arcanaString->indexOf(':', colon1Pos + 2);
|
|
||||||
if (colon2Pos == -1 || colon2Pos > closePos)
|
|
||||||
break;
|
|
||||||
const int line = subViewEnd(*arcanaString, colon1Pos + 1, colon2Pos).toString().toInt(); // TODO: Drop toString() once we require >= Qt 5.15
|
|
||||||
if (line == 0)
|
|
||||||
break;
|
|
||||||
const QStringView fileOrLineString = subViewEnd(*arcanaString, startPos, colon1Pos);
|
|
||||||
if (fileOrLineString != QLatin1String("line")) {
|
|
||||||
if (Utils::FilePath::fromUserInput(fileOrLineString.toString()) == thisFile)
|
|
||||||
hasOurs = true;
|
|
||||||
else
|
|
||||||
hasOther = true;
|
|
||||||
}
|
|
||||||
const int commaPos = arcanaString->indexOf(',', colon2Pos + 2);
|
|
||||||
if (commaPos != -1)
|
|
||||||
startPos = commaPos + 2;
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (hasOurs)
|
|
||||||
return hasOther ? FileStatus::Mixed : FileStatus::Ours;
|
|
||||||
return hasOther ? FileStatus::Foreign : FileStatus::Unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For debugging.
|
|
||||||
void print(int indent = 0) const
|
|
||||||
{
|
|
||||||
(qDebug().noquote() << QByteArray(indent, ' ')).quote() << role() << kind()
|
|
||||||
<< detail().value_or(QString()) << arcana().value_or(QString())
|
|
||||||
<< range();
|
|
||||||
for (const AstNode &c : children().value_or(QList<AstNode>()))
|
|
||||||
c.print(indent + 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isValid() const override
|
|
||||||
{
|
|
||||||
return contains(roleKey) && contains(kindKey);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class AstPathCollector
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AstPathCollector(const AstNode &root, const Range &range) : m_root(root), m_range(range) {}
|
|
||||||
|
|
||||||
QList<AstNode> collectPath()
|
|
||||||
{
|
|
||||||
if (!m_root.isValid())
|
|
||||||
return {};
|
|
||||||
visitNode(m_root, true);
|
|
||||||
return m_done ? m_path : m_longestSubPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void visitNode(const AstNode &node, bool isRoot = false)
|
|
||||||
{
|
|
||||||
if (!isRoot && (!node.hasRange() || !node.range().contains(m_range)))
|
|
||||||
return;
|
|
||||||
m_path << node;
|
|
||||||
|
|
||||||
class PathDropper {
|
|
||||||
public:
|
|
||||||
PathDropper(AstPathCollector &collector) : m_collector(collector) {};
|
|
||||||
~PathDropper() {
|
|
||||||
if (m_collector.m_done)
|
|
||||||
return;
|
|
||||||
if (m_collector.m_path.size() > m_collector.m_longestSubPath.size())
|
|
||||||
m_collector.m_longestSubPath = m_collector.m_path;
|
|
||||||
m_collector.m_path.removeLast();
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
AstPathCollector &m_collector;
|
|
||||||
} pathDropper(*this);
|
|
||||||
|
|
||||||
// Still traverse the children, because they could have the same range.
|
|
||||||
if (node.range() == m_range)
|
|
||||||
m_done = true;
|
|
||||||
|
|
||||||
const auto children = node.children();
|
|
||||||
if (!children)
|
|
||||||
return;
|
|
||||||
|
|
||||||
QList<AstNode> childrenToCheck;
|
|
||||||
if (node.kind() == "Function" || node.role() == "expression") {
|
|
||||||
// Functions and expressions can contain implicit nodes that make the list unsorted.
|
|
||||||
// They cannot be ignored, as we need to consider them in certain contexts.
|
|
||||||
// Therefore, the binary search cannot be used here.
|
|
||||||
childrenToCheck = *children;
|
|
||||||
} else {
|
|
||||||
for (auto it = std::lower_bound(children->cbegin(), children->cend(), m_range,
|
|
||||||
leftOfRange);
|
|
||||||
it != children->cend() && !m_range.isLeftOf(it->range()); ++it) {
|
|
||||||
childrenToCheck << *it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool wasDone = m_done;
|
|
||||||
for (const AstNode &child : qAsConst(childrenToCheck)) {
|
|
||||||
visitNode(child);
|
|
||||||
if (m_done && !wasDone)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool leftOfRange(const AstNode &node, const Range &range)
|
|
||||||
{
|
|
||||||
// Class and struct nodes can contain implicit constructors, destructors and
|
|
||||||
// operators, which appear at the end of the list, but whose range is the same
|
|
||||||
// as the class name. Therefore, we must force them not to compare less to
|
|
||||||
// anything else.
|
|
||||||
return node.range().isLeftOf(range) && !node.arcanaContains(" implicit ");
|
|
||||||
};
|
|
||||||
|
|
||||||
const AstNode &m_root;
|
|
||||||
const Range &m_range;
|
|
||||||
QList<AstNode> m_path;
|
|
||||||
QList<AstNode> m_longestSubPath;
|
|
||||||
bool m_done = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
static QList<AstNode> getAstPath(const AstNode &root, const Range &range)
|
|
||||||
{
|
|
||||||
return AstPathCollector(root, range).collectPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
static QList<AstNode> getAstPath(const AstNode &root, const Position &pos)
|
|
||||||
{
|
|
||||||
return getAstPath(root, Range(pos, pos));
|
|
||||||
}
|
|
||||||
|
|
||||||
static Usage::Type getUsageType(const QList<AstNode> &path)
|
|
||||||
{
|
{
|
||||||
bool potentialWrite = false;
|
bool potentialWrite = false;
|
||||||
bool isFunction = false;
|
bool isFunction = false;
|
||||||
@@ -645,7 +285,7 @@ class ReferencesFileData {
|
|||||||
public:
|
public:
|
||||||
QList<QPair<Range, QString>> rangesAndLineText;
|
QList<QPair<Range, QString>> rangesAndLineText;
|
||||||
QString fileContent;
|
QString fileContent;
|
||||||
AstNode ast;
|
ClangdAstNode ast;
|
||||||
};
|
};
|
||||||
class ReplacementData {
|
class ReplacementData {
|
||||||
public:
|
public:
|
||||||
@@ -761,8 +401,8 @@ public:
|
|||||||
Utils::Link defLink;
|
Utils::Link defLink;
|
||||||
QList<Utils::Link> allLinks;
|
QList<Utils::Link> allLinks;
|
||||||
QHash<Utils::Link, Utils::Link> declDefMap;
|
QHash<Utils::Link, Utils::Link> declDefMap;
|
||||||
Utils::optional<AstNode> cursorNode;
|
Utils::optional<ClangdAstNode> cursorNode;
|
||||||
AstNode defLinkNode;
|
ClangdAstNode defLinkNode;
|
||||||
SymbolDataList symbolsToDisplay;
|
SymbolDataList symbolsToDisplay;
|
||||||
std::set<Utils::FilePath> openedFiles;
|
std::set<Utils::FilePath> openedFiles;
|
||||||
VirtualFunctionAssistProcessor *virtualFuncAssistProcessor = nullptr;
|
VirtualFunctionAssistProcessor *virtualFuncAssistProcessor = nullptr;
|
||||||
@@ -777,11 +417,11 @@ public:
|
|||||||
: id(id), document(doc), uri(DocumentUri::fromFilePath(doc->filePath())),
|
: id(id), document(doc), uri(DocumentUri::fromFilePath(doc->filePath())),
|
||||||
cursor(cursor), editorWidget(editorWidget), callback(std::move(callback)) {}
|
cursor(cursor), editorWidget(editorWidget), callback(std::move(callback)) {}
|
||||||
|
|
||||||
Utils::optional<AstNode> getFunctionNode() const
|
Utils::optional<ClangdAstNode> getFunctionNode() const
|
||||||
{
|
{
|
||||||
QTC_ASSERT(ast, return {});
|
QTC_ASSERT(ast, return {});
|
||||||
|
|
||||||
const QList<AstNode> path = getAstPath(*ast, Range(cursor));
|
const ClangdAstPath path = getAstPath(*ast, Range(cursor));
|
||||||
for (auto it = path.rbegin(); it != path.rend(); ++it) {
|
for (auto it = path.rbegin(); it != path.rend(); ++it) {
|
||||||
if (it->role() == "declaration"
|
if (it->role() == "declaration"
|
||||||
&& (it->kind() == "CXXMethod" || it->kind() == "CXXConversion"
|
&& (it->kind() == "CXXMethod" || it->kind() == "CXXConversion"
|
||||||
@@ -792,7 +432,7 @@ public:
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
QTextCursor cursorForFunctionName(const AstNode &functionNode) const
|
QTextCursor cursorForFunctionName(const ClangdAstNode &functionNode) const
|
||||||
{
|
{
|
||||||
QTC_ASSERT(docSymbols, return {});
|
QTC_ASSERT(docSymbols, return {});
|
||||||
|
|
||||||
@@ -818,7 +458,7 @@ public:
|
|||||||
const QPointer<CppEditor::CppEditorWidget> editorWidget;
|
const QPointer<CppEditor::CppEditorWidget> editorWidget;
|
||||||
Utils::ProcessLinkCallback callback;
|
Utils::ProcessLinkCallback callback;
|
||||||
Utils::optional<DocumentSymbolsResult> docSymbols;
|
Utils::optional<DocumentSymbolsResult> docSymbols;
|
||||||
Utils::optional<AstNode> ast;
|
Utils::optional<ClangdAstNode> ast;
|
||||||
};
|
};
|
||||||
|
|
||||||
class LocalRefsData {
|
class LocalRefsData {
|
||||||
@@ -1271,7 +911,7 @@ public:
|
|||||||
|
|
||||||
enum class AstCallbackMode { SyncIfPossible, AlwaysAsync };
|
enum class AstCallbackMode { SyncIfPossible, AlwaysAsync };
|
||||||
using TextDocOrFile = const Utils::variant<const TextDocument *, Utils::FilePath>;
|
using TextDocOrFile = const Utils::variant<const TextDocument *, Utils::FilePath>;
|
||||||
using AstHandler = const std::function<void(const AstNode &ast, const MessageId &)>;
|
using AstHandler = const std::function<void(const ClangdAstNode &ast, const MessageId &)>;
|
||||||
MessageId getAndHandleAst(TextDocOrFile &doc, AstHandler &astHandler,
|
MessageId getAndHandleAst(TextDocOrFile &doc, AstHandler &astHandler,
|
||||||
AstCallbackMode callbackMode, const Range &range = {});
|
AstCallbackMode callbackMode, const Range &range = {});
|
||||||
|
|
||||||
@@ -1287,8 +927,8 @@ public:
|
|||||||
QHash<Utils::FilePath, CppEditor::BaseEditorDocumentParser::Configuration> parserConfigs;
|
QHash<Utils::FilePath, CppEditor::BaseEditorDocumentParser::Configuration> parserConfigs;
|
||||||
QHash<Utils::FilePath, Tasks> issuePaneEntries;
|
QHash<Utils::FilePath, Tasks> issuePaneEntries;
|
||||||
|
|
||||||
VersionedDataCache<const TextDocument *, AstNode> astCache;
|
VersionedDataCache<const TextDocument *, ClangdAstNode> astCache;
|
||||||
VersionedDataCache<Utils::FilePath, AstNode> externalAstCache;
|
VersionedDataCache<Utils::FilePath, ClangdAstNode> externalAstCache;
|
||||||
TaskTimer highlightingTimer{"highlighting"};
|
TaskTimer highlightingTimer{"highlighting"};
|
||||||
quint64 nextJobId = 0;
|
quint64 nextJobId = 0;
|
||||||
bool isFullyIndexed = false;
|
bool isFullyIndexed = false;
|
||||||
@@ -2053,7 +1693,7 @@ void ClangdClient::Private::handleFindUsagesResult(quint64 key, const QList<Loca
|
|||||||
q->openExtraFile(it.key().toFilePath(), it->fileContent);
|
q->openExtraFile(it.key().toFilePath(), it->fileContent);
|
||||||
it->fileContent.clear();
|
it->fileContent.clear();
|
||||||
const auto docVariant = doc ? TextDocOrFile(doc) : TextDocOrFile(it.key().toFilePath());
|
const auto docVariant = doc ? TextDocOrFile(doc) : TextDocOrFile(it.key().toFilePath());
|
||||||
const auto astHandler = [this, key, loc = it.key()](const AstNode &ast,
|
const auto astHandler = [this, key, loc = it.key()](const ClangdAstNode &ast,
|
||||||
const MessageId &reqId) {
|
const MessageId &reqId) {
|
||||||
qCDebug(clangdLog) << "AST for" << loc.toFilePath();
|
qCDebug(clangdLog) << "AST for" << loc.toFilePath();
|
||||||
const auto refData = runningFindUsages.find(key);
|
const auto refData = runningFindUsages.find(key);
|
||||||
@@ -2212,7 +1852,7 @@ void ClangdClient::followSymbol(TextDocument *document,
|
|||||||
symbolSupport().findLinkAt(document, adjustedCursor, std::move(gotoDefCallback), true);
|
symbolSupport().findLinkAt(document, adjustedCursor, std::move(gotoDefCallback), true);
|
||||||
|
|
||||||
const auto astHandler = [this, id = d->followSymbolData->id]
|
const auto astHandler = [this, id = d->followSymbolData->id]
|
||||||
(const AstNode &ast, const MessageId &) {
|
(const ClangdAstNode &ast, const MessageId &) {
|
||||||
qCDebug(clangdLog) << "received ast response for cursor";
|
qCDebug(clangdLog) << "received ast response for cursor";
|
||||||
if (!d->followSymbolData || d->followSymbolData->id != id)
|
if (!d->followSymbolData || d->followSymbolData->id != id)
|
||||||
return;
|
return;
|
||||||
@@ -2236,7 +1876,7 @@ void ClangdClient::switchDeclDef(TextDocument *document, const QTextCursor &curs
|
|||||||
std::move(callback));
|
std::move(callback));
|
||||||
|
|
||||||
// Retrieve AST and document symbols.
|
// Retrieve AST and document symbols.
|
||||||
const auto astHandler = [this, id = d->switchDeclDefData->id](const AstNode &ast,
|
const auto astHandler = [this, id = d->switchDeclDefData->id](const ClangdAstNode &ast,
|
||||||
const MessageId &) {
|
const MessageId &) {
|
||||||
qCDebug(clangdLog) << "received ast for decl/def switch";
|
qCDebug(clangdLog) << "received ast for decl/def switch";
|
||||||
if (!d->switchDeclDefData || d->switchDeclDefData->id != id
|
if (!d->switchDeclDefData || d->switchDeclDefData->id != id
|
||||||
@@ -2304,7 +1944,7 @@ void ClangdClient::findLocalUsages(TextDocument *document, const QTextCursor &cu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Get AST and check whether it's a local variable.
|
// Step 2: Get AST and check whether it's a local variable.
|
||||||
const auto astHandler = [this, link, id](const AstNode &ast, const MessageId &) {
|
const auto astHandler = [this, link, id](const ClangdAstNode &ast, const MessageId &) {
|
||||||
qCDebug(clangdLog) << "received ast response";
|
qCDebug(clangdLog) << "received ast response";
|
||||||
if (!d->localRefsData || id != d->localRefsData->id)
|
if (!d->localRefsData || id != d->localRefsData->id)
|
||||||
return;
|
return;
|
||||||
@@ -2314,7 +1954,7 @@ void ClangdClient::findLocalUsages(TextDocument *document, const QTextCursor &cu
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Position linkPos(link.targetLine - 1, link.targetColumn);
|
const Position linkPos(link.targetLine - 1, link.targetColumn);
|
||||||
const QList<AstNode> astPath = getAstPath(ast, linkPos);
|
const ClangdAstPath astPath = getAstPath(ast, linkPos);
|
||||||
bool isVar = false;
|
bool isVar = false;
|
||||||
for (auto it = astPath.rbegin(); it != astPath.rend(); ++it) {
|
for (auto it = astPath.rbegin(); it != astPath.rend(); ++it) {
|
||||||
if (it->role() == "declaration"
|
if (it->role() == "declaration"
|
||||||
@@ -2404,26 +2044,26 @@ void ClangdClient::gatherHelpItemForTooltip(const HoverRequest::Response &hoverR
|
|||||||
|
|
||||||
const TextDocument * const doc = documentForFilePath(uri.toFilePath());
|
const TextDocument * const doc = documentForFilePath(uri.toFilePath());
|
||||||
QTC_ASSERT(doc, return);
|
QTC_ASSERT(doc, return);
|
||||||
const auto astHandler = [this, uri, hoverResponse](const AstNode &ast, const MessageId &) {
|
const auto astHandler = [this, uri, hoverResponse](const ClangdAstNode &ast, const MessageId &) {
|
||||||
const MessageId id = hoverResponse.id();
|
const MessageId id = hoverResponse.id();
|
||||||
Range range;
|
Range range;
|
||||||
if (const Utils::optional<HoverResult> result = hoverResponse.result()) {
|
if (const Utils::optional<HoverResult> result = hoverResponse.result()) {
|
||||||
if (auto hover = Utils::get_if<Hover>(&(*result)))
|
if (auto hover = Utils::get_if<Hover>(&(*result)))
|
||||||
range = hover->range().value_or(Range());
|
range = hover->range().value_or(Range());
|
||||||
}
|
}
|
||||||
const QList<AstNode> path = getAstPath(ast, range);
|
const ClangdAstPath path = getAstPath(ast, range);
|
||||||
if (path.isEmpty()) {
|
if (path.isEmpty()) {
|
||||||
d->setHelpItemForTooltip(id);
|
d->setHelpItemForTooltip(id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AstNode node = path.last();
|
ClangdAstNode node = path.last();
|
||||||
if (node.role() == "expression" && node.kind() == "ImplicitCast") {
|
if (node.role() == "expression" && node.kind() == "ImplicitCast") {
|
||||||
const Utils::optional<QList<AstNode>> children = node.children();
|
const Utils::optional<QList<ClangdAstNode>> children = node.children();
|
||||||
if (children && !children->isEmpty())
|
if (children && !children->isEmpty())
|
||||||
node = children->first();
|
node = children->first();
|
||||||
}
|
}
|
||||||
while (node.kind() == "Qualified") {
|
while (node.kind() == "Qualified") {
|
||||||
const Utils::optional<QList<AstNode>> children = node.children();
|
const Utils::optional<QList<ClangdAstNode>> children = node.children();
|
||||||
if (children && !children->isEmpty())
|
if (children && !children->isEmpty())
|
||||||
node = children->first();
|
node = children->first();
|
||||||
}
|
}
|
||||||
@@ -2707,7 +2347,7 @@ void ClangdClient::Private::handleGotoImplementationResult(
|
|||||||
const Position defLinkPos(followSymbolData->defLink.targetLine - 1,
|
const Position defLinkPos(followSymbolData->defLink.targetLine - 1,
|
||||||
followSymbolData->defLink.targetColumn);
|
followSymbolData->defLink.targetColumn);
|
||||||
const auto astHandler = [this, id = followSymbolData->id]
|
const auto astHandler = [this, id = followSymbolData->id]
|
||||||
(const AstNode &ast, const MessageId &) {
|
(const ClangdAstNode &ast, const MessageId &) {
|
||||||
qCDebug(clangdLog) << "received ast response for def link";
|
qCDebug(clangdLog) << "received ast response for def link";
|
||||||
if (!followSymbolData || followSymbolData->id != id)
|
if (!followSymbolData || followSymbolData->id != id)
|
||||||
return;
|
return;
|
||||||
@@ -2754,7 +2394,7 @@ void ClangdClient::Private::handleDeclDefSwitchReplies()
|
|||||||
// on a function return type, or ...
|
// on a function return type, or ...
|
||||||
if (clangdLogAst().isDebugEnabled())
|
if (clangdLogAst().isDebugEnabled())
|
||||||
switchDeclDefData->ast->print(0);
|
switchDeclDefData->ast->print(0);
|
||||||
const Utils::optional<AstNode> functionNode = switchDeclDefData->getFunctionNode();
|
const Utils::optional<ClangdAstNode> functionNode = switchDeclDefData->getFunctionNode();
|
||||||
if (!functionNode) {
|
if (!functionNode) {
|
||||||
switchDeclDefData.reset();
|
switchDeclDefData.reset();
|
||||||
return;
|
return;
|
||||||
@@ -2815,10 +2455,10 @@ QTextCursor ClangdClient::Private::adjustedCursor(const QTextCursor &cursor,
|
|||||||
case T_DOT:
|
case T_DOT:
|
||||||
break;
|
break;
|
||||||
case T_ARROW: {
|
case T_ARROW: {
|
||||||
const Utils::optional<AstNode> clangdAst = astCache.get(doc);
|
const Utils::optional<ClangdAstNode> clangdAst = astCache.get(doc);
|
||||||
if (!clangdAst)
|
if (!clangdAst)
|
||||||
return cursor;
|
return cursor;
|
||||||
const QList<AstNode> clangdAstPath = getAstPath(*clangdAst, Range(cursor));
|
const ClangdAstPath clangdAstPath = getAstPath(*clangdAst, Range(cursor));
|
||||||
for (auto it = clangdAstPath.rbegin(); it != clangdAstPath.rend(); ++it) {
|
for (auto it = clangdAstPath.rbegin(); it != clangdAstPath.rend(); ++it) {
|
||||||
if (it->detailIs("operator->") && it->arcanaContains("CXXMethod"))
|
if (it->detailIs("operator->") && it->arcanaContains("CXXMethod"))
|
||||||
return cursor;
|
return cursor;
|
||||||
@@ -2903,29 +2543,29 @@ class ExtraHighlightingResultsCollector
|
|||||||
public:
|
public:
|
||||||
ExtraHighlightingResultsCollector(QFutureInterface<HighlightingResult> &future,
|
ExtraHighlightingResultsCollector(QFutureInterface<HighlightingResult> &future,
|
||||||
HighlightingResults &results,
|
HighlightingResults &results,
|
||||||
const Utils::FilePath &filePath, const AstNode &ast,
|
const Utils::FilePath &filePath, const ClangdAstNode &ast,
|
||||||
const QTextDocument *doc, const QString &docContent);
|
const QTextDocument *doc, const QString &docContent);
|
||||||
|
|
||||||
void collect();
|
void collect();
|
||||||
private:
|
private:
|
||||||
static bool lessThan(const HighlightingResult &r1, const HighlightingResult &r2);
|
static bool lessThan(const HighlightingResult &r1, const HighlightingResult &r2);
|
||||||
static int onlyIndexOf(const QStringView &text, const QStringView &subString, int from = 0);
|
static int onlyIndexOf(const QStringView &text, const QStringView &subString, int from = 0);
|
||||||
int posForNodeStart(const AstNode &node) const;
|
int posForNodeStart(const ClangdAstNode &node) const;
|
||||||
int posForNodeEnd(const AstNode &node) const;
|
int posForNodeEnd(const ClangdAstNode &node) const;
|
||||||
void insertResult(const HighlightingResult &result);
|
void insertResult(const HighlightingResult &result);
|
||||||
void insertResult(const AstNode &node, TextStyle style);
|
void insertResult(const ClangdAstNode &node, TextStyle style);
|
||||||
void insertAngleBracketInfo(int searchStart1, int searchEnd1, int searchStart2, int searchEnd2);
|
void insertAngleBracketInfo(int searchStart1, int searchEnd1, int searchStart2, int searchEnd2);
|
||||||
void setResultPosFromRange(HighlightingResult &result, const Range &range);
|
void setResultPosFromRange(HighlightingResult &result, const Range &range);
|
||||||
void collectFromNode(const AstNode &node);
|
void collectFromNode(const ClangdAstNode &node);
|
||||||
void visitNode(const AstNode&node);
|
void visitNode(const ClangdAstNode&node);
|
||||||
|
|
||||||
QFutureInterface<HighlightingResult> &m_future;
|
QFutureInterface<HighlightingResult> &m_future;
|
||||||
HighlightingResults &m_results;
|
HighlightingResults &m_results;
|
||||||
const Utils::FilePath m_filePath;
|
const Utils::FilePath m_filePath;
|
||||||
const AstNode &m_ast;
|
const ClangdAstNode &m_ast;
|
||||||
const QTextDocument * const m_doc;
|
const QTextDocument * const m_doc;
|
||||||
const QString &m_docContent;
|
const QString &m_docContent;
|
||||||
AstNode::FileStatus m_currentFileStatus = AstNode::FileStatus::Unknown;
|
ClangdAstNode::FileStatus m_currentFileStatus = ClangdAstNode::FileStatus::Unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
// clangd reports also the #ifs, #elses and #endifs around the disabled code as disabled,
|
// clangd reports also the #ifs, #elses and #endifs around the disabled code as disabled,
|
||||||
@@ -3012,7 +2652,7 @@ static QList<BlockRange> cleanupDisabledCode(HighlightingResults &results, const
|
|||||||
static void semanticHighlighter(QFutureInterface<HighlightingResult> &future,
|
static void semanticHighlighter(QFutureInterface<HighlightingResult> &future,
|
||||||
const Utils::FilePath &filePath,
|
const Utils::FilePath &filePath,
|
||||||
const QList<ExpandedSemanticToken> &tokens,
|
const QList<ExpandedSemanticToken> &tokens,
|
||||||
const QString &docContents, const AstNode &ast,
|
const QString &docContents, const ClangdAstNode &ast,
|
||||||
const QPointer<TextDocument> &textDocument,
|
const QPointer<TextDocument> &textDocument,
|
||||||
int docRevision, const QVersionNumber &clangdVersion,
|
int docRevision, const QVersionNumber &clangdVersion,
|
||||||
const TaskTimer &taskTimer)
|
const TaskTimer &taskTimer)
|
||||||
@@ -3035,7 +2675,7 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future,
|
|||||||
if (token.type != "variable" && token.type != "property" && token.type != "parameter")
|
if (token.type != "variable" && token.type != "property" && token.type != "parameter")
|
||||||
return false;
|
return false;
|
||||||
const Range range = tokenRange(token);
|
const Range range = tokenRange(token);
|
||||||
const QList<AstNode> path = getAstPath(ast, range);
|
const ClangdAstPath path = getAstPath(ast, range);
|
||||||
if (path.size() < 2)
|
if (path.size() < 2)
|
||||||
return false;
|
return false;
|
||||||
if (token.type == "property"
|
if (token.type == "property"
|
||||||
@@ -3056,7 +2696,7 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future,
|
|||||||
// know whether the argument is passed as const or not.
|
// know whether the argument is passed as const or not.
|
||||||
if (it->arcanaContains("dependent type"))
|
if (it->arcanaContains("dependent type"))
|
||||||
return false;
|
return false;
|
||||||
const QList<AstNode> children = it->children().value_or(QList<AstNode>());
|
const QList<ClangdAstNode> children = it->children().value_or(QList<ClangdAstNode>());
|
||||||
return children.isEmpty()
|
return children.isEmpty()
|
||||||
|| (children.first().range() != (it - 1)->range()
|
|| (children.first().range() != (it - 1)->range()
|
||||||
&& children.first().kind() != "UnresolvedLookup");
|
&& children.first().kind() != "UnresolvedLookup");
|
||||||
@@ -3065,7 +2705,7 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future,
|
|||||||
// The token should get marked for e.g. lambdas, but not for assignment operators,
|
// The token should get marked for e.g. lambdas, but not for assignment operators,
|
||||||
// where the user sees that it's being written.
|
// where the user sees that it's being written.
|
||||||
if (it->kind() == "CXXOperatorCall") {
|
if (it->kind() == "CXXOperatorCall") {
|
||||||
const QList<AstNode> children = it->children().value_or(QList<AstNode>());
|
const QList<ClangdAstNode> children = it->children().value_or(QList<ClangdAstNode>());
|
||||||
|
|
||||||
// Child 1 is the call itself, Child 2 is the named entity on which the call happens
|
// Child 1 is the call itself, Child 2 is the named entity on which the call happens
|
||||||
// (a lambda or a class instance), after that follow the actual call arguments.
|
// (a lambda or a class instance), after that follow the actual call arguments.
|
||||||
@@ -3082,9 +2722,9 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future,
|
|||||||
if (children.at(1).range().contains(range))
|
if (children.at(1).range().contains(range))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
QList<AstNode> firstChildTree{children.first()};
|
QList<ClangdAstNode> firstChildTree{children.first()};
|
||||||
while (!firstChildTree.isEmpty()) {
|
while (!firstChildTree.isEmpty()) {
|
||||||
const AstNode n = firstChildTree.takeFirst();
|
const ClangdAstNode n = firstChildTree.takeFirst();
|
||||||
const QString detail = n.detail().value_or(QString());
|
const QString detail = n.detail().value_or(QString());
|
||||||
if (detail.startsWith("operator")) {
|
if (detail.startsWith("operator")) {
|
||||||
return !detail.contains('=')
|
return !detail.contains('=')
|
||||||
@@ -3092,7 +2732,7 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future,
|
|||||||
&& !detail.contains("<<") && !detail.contains(">>")
|
&& !detail.contains("<<") && !detail.contains(">>")
|
||||||
&& !detail.contains("*");
|
&& !detail.contains("*");
|
||||||
}
|
}
|
||||||
firstChildTree << n.children().value_or(QList<AstNode>());
|
firstChildTree << n.children().value_or(QList<ClangdAstNode>());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -3107,7 +2747,7 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future,
|
|||||||
if (it->kind() == "CXXMemberCall") {
|
if (it->kind() == "CXXMemberCall") {
|
||||||
if (it == path.rbegin())
|
if (it == path.rbegin())
|
||||||
return false;
|
return false;
|
||||||
const QList<AstNode> children = it->children().value_or(QList<AstNode>());
|
const QList<ClangdAstNode> children = it->children().value_or(QList<ClangdAstNode>());
|
||||||
QTC_ASSERT(!children.isEmpty(), return false);
|
QTC_ASSERT(!children.isEmpty(), return false);
|
||||||
|
|
||||||
// The called object is never displayed as an output parameter.
|
// The called object is never displayed as an output parameter.
|
||||||
@@ -3141,9 +2781,9 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future,
|
|||||||
styles.mainStyle = token.modifiers.contains(QLatin1String("virtual"))
|
styles.mainStyle = token.modifiers.contains(QLatin1String("virtual"))
|
||||||
? C_VIRTUAL_METHOD : C_FUNCTION;
|
? C_VIRTUAL_METHOD : C_FUNCTION;
|
||||||
if (ast.isValid()) {
|
if (ast.isValid()) {
|
||||||
const QList<AstNode> path = getAstPath(ast, tokenRange(token));
|
const ClangdAstPath path = getAstPath(ast, tokenRange(token));
|
||||||
if (path.length() > 1) {
|
if (path.length() > 1) {
|
||||||
const AstNode declNode = path.at(path.length() - 2);
|
const ClangdAstNode declNode = path.at(path.length() - 2);
|
||||||
if (declNode.kind() == "Function" || declNode.kind() == "CXXMethod") {
|
if (declNode.kind() == "Function" || declNode.kind() == "CXXMethod") {
|
||||||
if (clangdVersion < QVersionNumber(14)
|
if (clangdVersion < QVersionNumber(14)
|
||||||
&& declNode.arcanaContains("' virtual")) {
|
&& declNode.arcanaContains("' virtual")) {
|
||||||
@@ -3160,13 +2800,13 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future,
|
|||||||
// clang hardly ever differentiates between constructors and the associated class,
|
// clang hardly ever differentiates between constructors and the associated class,
|
||||||
// whereas we highlight constructors as functions.
|
// whereas we highlight constructors as functions.
|
||||||
if (ast.isValid()) {
|
if (ast.isValid()) {
|
||||||
const QList<AstNode> path = getAstPath(ast, tokenRange(token));
|
const ClangdAstPath path = getAstPath(ast, tokenRange(token));
|
||||||
if (!path.isEmpty()) {
|
if (!path.isEmpty()) {
|
||||||
if (path.last().kind() == "CXXConstructor") {
|
if (path.last().kind() == "CXXConstructor") {
|
||||||
if (!path.last().arcanaContains("implicit"))
|
if (!path.last().arcanaContains("implicit"))
|
||||||
styles.mainStyle = C_FUNCTION;
|
styles.mainStyle = C_FUNCTION;
|
||||||
} else if (path.last().kind() == "Record" && path.length() > 1) {
|
} else if (path.last().kind() == "Record" && path.length() > 1) {
|
||||||
const AstNode node = path.at(path.length() - 2);
|
const ClangdAstNode node = path.at(path.length() - 2);
|
||||||
if (node.kind() == "CXXDestructor" && !node.arcanaContains("implicit")) {
|
if (node.kind() == "CXXDestructor" && !node.arcanaContains("implicit")) {
|
||||||
styles.mainStyle = C_FUNCTION;
|
styles.mainStyle = C_FUNCTION;
|
||||||
|
|
||||||
@@ -3284,7 +2924,7 @@ void ClangdClient::Private::handleSemanticTokens(TextDocument *doc,
|
|||||||
qCDebug(clangdLogHighlight()) << '\t' << t.line << t.column << t.length << t.type
|
qCDebug(clangdLogHighlight()) << '\t' << t.line << t.column << t.length << t.type
|
||||||
<< t.modifiers;
|
<< t.modifiers;
|
||||||
|
|
||||||
const auto astHandler = [this, tokens, doc, version](const AstNode &ast, const MessageId &) {
|
const auto astHandler = [this, tokens, doc, version](const ClangdAstNode &ast, const MessageId &) {
|
||||||
FinalizingSubtaskTimer t(highlightingTimer);
|
FinalizingSubtaskTimer t(highlightingTimer);
|
||||||
if (!q->documentOpen(doc))
|
if (!q->documentOpen(doc))
|
||||||
return;
|
return;
|
||||||
@@ -3759,48 +3399,10 @@ MessageId ClangdClient::Private::getAndHandleAst(const TextDocOrFile &doc,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise retrieve the AST from clangd.
|
// Otherwise retrieve the AST from clangd.
|
||||||
|
const auto wrapperHandler = [this, filePath, guardedTextDoc = QPointer(textDoc), astHandler,
|
||||||
class AstParams : public JsonObject
|
fullAstRequested, docRev = textDoc ? getRevision(textDoc) : -1,
|
||||||
{
|
fileRev = getRevision(filePath)](const ClangdAstNode &ast, const MessageId &reqId) {
|
||||||
public:
|
|
||||||
AstParams(const TextDocumentIdentifier &document, const Range &range = {})
|
|
||||||
{
|
|
||||||
setTextDocument(document);
|
|
||||||
if (range.isValid())
|
|
||||||
setRange(range);
|
|
||||||
}
|
|
||||||
|
|
||||||
using JsonObject::JsonObject;
|
|
||||||
|
|
||||||
// The open file to inspect.
|
|
||||||
TextDocumentIdentifier textDocument() const
|
|
||||||
{ return typedValue<TextDocumentIdentifier>(textDocumentKey); }
|
|
||||||
void setTextDocument(const TextDocumentIdentifier &id) { insert(textDocumentKey, id); }
|
|
||||||
|
|
||||||
// The region of the source code whose AST is fetched. The highest-level node that entirely
|
|
||||||
// contains the range is returned.
|
|
||||||
Utils::optional<Range> range() const { return optionalValue<Range>(rangeKey); }
|
|
||||||
void setRange(const Range &range) { insert(rangeKey, range); }
|
|
||||||
|
|
||||||
bool isValid() const override { return contains(textDocumentKey); }
|
|
||||||
};
|
|
||||||
|
|
||||||
class AstRequest : public Request<AstNode, std::nullptr_t, AstParams>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using Request::Request;
|
|
||||||
explicit AstRequest(const AstParams ¶ms) : Request("textDocument/ast", params) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
AstRequest request(AstParams(TextDocumentIdentifier(DocumentUri::fromFilePath(filePath)),
|
|
||||||
range));
|
|
||||||
request.setResponseCallback([this, filePath, guardedTextDoc = QPointer(textDoc), astHandler,
|
|
||||||
fullAstRequested, docRev = textDoc ? getRevision(textDoc) : -1,
|
|
||||||
fileRev = getRevision(filePath), reqId = request.id()]
|
|
||||||
(AstRequest::Response response) {
|
|
||||||
qCDebug(clangdLog) << "retrieved AST from clangd";
|
qCDebug(clangdLog) << "retrieved AST from clangd";
|
||||||
const auto result = response.result();
|
|
||||||
const AstNode ast = result ? *result : AstNode();
|
|
||||||
if (fullAstRequested) {
|
if (fullAstRequested) {
|
||||||
if (guardedTextDoc) {
|
if (guardedTextDoc) {
|
||||||
if (docRev == getRevision(guardedTextDoc))
|
if (docRev == getRevision(guardedTextDoc))
|
||||||
@@ -3810,15 +3412,14 @@ MessageId ClangdClient::Private::getAndHandleAst(const TextDocOrFile &doc,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
astHandler(ast, reqId);
|
astHandler(ast, reqId);
|
||||||
});
|
};
|
||||||
qCDebug(clangdLog) << "requesting AST for" << filePath;
|
qCDebug(clangdLog) << "requesting AST for" << filePath;
|
||||||
q->sendContent(request, SendDocUpdates::Ignore);
|
return requestAst(q, filePath, range, wrapperHandler);
|
||||||
return request.id();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ExtraHighlightingResultsCollector::ExtraHighlightingResultsCollector(
|
ExtraHighlightingResultsCollector::ExtraHighlightingResultsCollector(
|
||||||
QFutureInterface<HighlightingResult> &future, HighlightingResults &results,
|
QFutureInterface<HighlightingResult> &future, HighlightingResults &results,
|
||||||
const Utils::FilePath &filePath, const AstNode &ast, const QTextDocument *doc,
|
const Utils::FilePath &filePath, const ClangdAstNode &ast, const QTextDocument *doc,
|
||||||
const QString &docContent)
|
const QString &docContent)
|
||||||
: m_future(future), m_results(results), m_filePath(filePath), m_ast(ast), m_doc(doc),
|
: m_future(future), m_results(results), m_filePath(filePath), m_ast(ast), m_doc(doc),
|
||||||
m_docContent(docContent)
|
m_docContent(docContent)
|
||||||
@@ -3875,13 +3476,13 @@ int ExtraHighlightingResultsCollector::onlyIndexOf(const QStringView &text,
|
|||||||
// Unfortunately, the exact position of a specific token is usually not
|
// Unfortunately, the exact position of a specific token is usually not
|
||||||
// recorded in the AST, so if we need that, we have to search for it textually.
|
// recorded in the AST, so if we need that, we have to search for it textually.
|
||||||
// In corner cases, this might get sabotaged by e.g. comments, in which case we give up.
|
// In corner cases, this might get sabotaged by e.g. comments, in which case we give up.
|
||||||
int ExtraHighlightingResultsCollector::posForNodeStart(const AstNode &node) const
|
int ExtraHighlightingResultsCollector::posForNodeStart(const ClangdAstNode &node) const
|
||||||
{
|
{
|
||||||
return Utils::Text::positionInText(m_doc, node.range().start().line() + 1,
|
return Utils::Text::positionInText(m_doc, node.range().start().line() + 1,
|
||||||
node.range().start().character() + 1);
|
node.range().start().character() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
int ExtraHighlightingResultsCollector::posForNodeEnd(const AstNode &node) const
|
int ExtraHighlightingResultsCollector::posForNodeEnd(const ClangdAstNode &node) const
|
||||||
{
|
{
|
||||||
return Utils::Text::positionInText(m_doc, node.range().end().line() + 1,
|
return Utils::Text::positionInText(m_doc, node.range().end().line() + 1,
|
||||||
node.range().end().character() + 1);
|
node.range().end().character() + 1);
|
||||||
@@ -3920,7 +3521,7 @@ void ExtraHighlightingResultsCollector::insertResult(const HighlightingResult &r
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExtraHighlightingResultsCollector::insertResult(const AstNode &node, TextStyle style)
|
void ExtraHighlightingResultsCollector::insertResult(const ClangdAstNode &node, TextStyle style)
|
||||||
{
|
{
|
||||||
HighlightingResult result;
|
HighlightingResult result;
|
||||||
result.useTextSyles = true;
|
result.useTextSyles = true;
|
||||||
@@ -3979,7 +3580,7 @@ void ExtraHighlightingResultsCollector::setResultPosFromRange(HighlightingResult
|
|||||||
result.length = endPos.toPositionInDocument(m_doc) - startPos.toPositionInDocument(m_doc);
|
result.length = endPos.toPositionInDocument(m_doc) - startPos.toPositionInDocument(m_doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node)
|
void ExtraHighlightingResultsCollector::collectFromNode(const ClangdAstNode &node)
|
||||||
{
|
{
|
||||||
if (node.kind() == "UserDefinedLiteral")
|
if (node.kind() == "UserDefinedLiteral")
|
||||||
return;
|
return;
|
||||||
@@ -4010,7 +3611,7 @@ void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node)
|
|||||||
const bool isDeclaration = node.role() == "declaration";
|
const bool isDeclaration = node.role() == "declaration";
|
||||||
const int nodeStartPos = posForNodeStart(node);
|
const int nodeStartPos = posForNodeStart(node);
|
||||||
const int nodeEndPos = posForNodeEnd(node);
|
const int nodeEndPos = posForNodeEnd(node);
|
||||||
const QList<AstNode> children = node.children().value_or(QList<AstNode>());
|
const QList<ClangdAstNode> children = node.children().value_or(QList<ClangdAstNode>());
|
||||||
|
|
||||||
// Match question mark and colon in ternary operators.
|
// Match question mark and colon in ternary operators.
|
||||||
if (isExpression && node.kind() == "ConditionalOperator") {
|
if (isExpression && node.kind() == "ConditionalOperator") {
|
||||||
@@ -4060,7 +3661,7 @@ void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node)
|
|||||||
const QString classOrFunctionKind = QLatin1String(node.kind() == "FunctionTemplate"
|
const QString classOrFunctionKind = QLatin1String(node.kind() == "FunctionTemplate"
|
||||||
? "Function" : "CXXRecord");
|
? "Function" : "CXXRecord");
|
||||||
const auto functionOrClassIt = std::find_if(children.begin(), children.end(),
|
const auto functionOrClassIt = std::find_if(children.begin(), children.end(),
|
||||||
[&classOrFunctionKind](const AstNode &n) {
|
[&classOrFunctionKind](const ClangdAstNode &n) {
|
||||||
return n.role() == "declaration" && n.kind() == classOrFunctionKind;
|
return n.role() == "declaration" && n.kind() == classOrFunctionKind;
|
||||||
});
|
});
|
||||||
if (functionOrClassIt == children.end() || functionOrClassIt == children.begin())
|
if (functionOrClassIt == children.end() || functionOrClassIt == children.begin())
|
||||||
@@ -4073,7 +3674,7 @@ void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto isTemplateParamDecl = [](const AstNode &node) {
|
const auto isTemplateParamDecl = [](const ClangdAstNode &node) {
|
||||||
return node.isTemplateParameterDeclaration();
|
return node.isTemplateParameterDeclaration();
|
||||||
};
|
};
|
||||||
if (isDeclaration && node.kind() == "TypeAliasTemplate") {
|
if (isDeclaration && node.kind() == "TypeAliasTemplate") {
|
||||||
@@ -4092,7 +3693,7 @@ void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node)
|
|||||||
isTemplateParamDecl);
|
isTemplateParamDecl);
|
||||||
QTC_ASSERT(lastTemplateParam != children.rend(), return);
|
QTC_ASSERT(lastTemplateParam != children.rend(), return);
|
||||||
const auto typeAlias = std::find_if(children.begin(), children.end(),
|
const auto typeAlias = std::find_if(children.begin(), children.end(),
|
||||||
[](const AstNode &n) { return n.kind() == "TypeAlias"; });
|
[](const ClangdAstNode &n) { return n.kind() == "TypeAlias"; });
|
||||||
if (typeAlias == children.end())
|
if (typeAlias == children.end())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -4126,7 +3727,7 @@ void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node)
|
|||||||
isTemplateParamDecl);
|
isTemplateParamDecl);
|
||||||
QTC_ASSERT(lastTemplateParam != children.rend(), return);
|
QTC_ASSERT(lastTemplateParam != children.rend(), return);
|
||||||
const auto templateArg = std::find_if(children.begin(), children.end(),
|
const auto templateArg = std::find_if(children.begin(), children.end(),
|
||||||
[](const AstNode &n) { return n.role() == "template argument"; });
|
[](const ClangdAstNode &n) { return n.role() == "template argument"; });
|
||||||
|
|
||||||
const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam);
|
const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam);
|
||||||
const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam);
|
const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam);
|
||||||
@@ -4308,27 +3909,27 @@ void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node)
|
|||||||
insertResult(result);
|
insertResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExtraHighlightingResultsCollector::visitNode(const AstNode &node)
|
void ExtraHighlightingResultsCollector::visitNode(const ClangdAstNode &node)
|
||||||
{
|
{
|
||||||
if (m_future.isCanceled())
|
if (m_future.isCanceled())
|
||||||
return;
|
return;
|
||||||
const AstNode::FileStatus prevFileStatus = m_currentFileStatus;
|
const ClangdAstNode::FileStatus prevFileStatus = m_currentFileStatus;
|
||||||
m_currentFileStatus = node.fileStatus(m_filePath);
|
m_currentFileStatus = node.fileStatus(m_filePath);
|
||||||
if (m_currentFileStatus == AstNode::FileStatus::Unknown
|
if (m_currentFileStatus == ClangdAstNode::FileStatus::Unknown
|
||||||
&& prevFileStatus != AstNode::FileStatus::Ours) {
|
&& prevFileStatus != ClangdAstNode::FileStatus::Ours) {
|
||||||
m_currentFileStatus = prevFileStatus;
|
m_currentFileStatus = prevFileStatus;
|
||||||
}
|
}
|
||||||
switch (m_currentFileStatus) {
|
switch (m_currentFileStatus) {
|
||||||
case AstNode::FileStatus::Ours:
|
case ClangdAstNode::FileStatus::Ours:
|
||||||
case AstNode::FileStatus::Unknown:
|
case ClangdAstNode::FileStatus::Unknown:
|
||||||
collectFromNode(node);
|
collectFromNode(node);
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case AstNode::FileStatus::Foreign:
|
case ClangdAstNode::FileStatus::Foreign:
|
||||||
case ClangCodeModel::Internal::AstNode::FileStatus::Mixed: {
|
case ClangCodeModel::Internal::ClangdAstNode::FileStatus::Mixed: {
|
||||||
const auto children = node.children();
|
const auto children = node.children();
|
||||||
if (!children)
|
if (!children)
|
||||||
return;
|
return;
|
||||||
for (const AstNode &childNode : *children)
|
for (const ClangdAstNode &childNode : *children)
|
||||||
visitNode(childNode);
|
visitNode(childNode);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user