ClangCodeModel: Bump minimum clangd version

... and throw away old workarounds.
The current clangd version, to be shipped with Qt Creator 14, is 18.1, so
we can safely bump the minimum version to 17.

Change-Id: I74fd5997196d774b6c47dcb522284953ef82ad9c
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Kandeler
2024-05-22 15:00:50 +02:00
parent 01568f5d75
commit 8577ab8bcb
6 changed files with 56 additions and 850 deletions

View File

@@ -65,7 +65,7 @@ bool checkClangdVersion(const FilePath &clangd, QString *error)
QVersionNumber minimumClangdVersion()
{
return QVersionNumber(14);
return QVersionNumber(17);
}
} // namespace Utils

View File

@@ -209,11 +209,10 @@ static BaseClientInterface *clientInterface(Project *project, const Utils::FileP
"--clang-tidy=0"}};
if (settings.workerThreadLimit() != 0)
cmd.addArg("-j=" + QString::number(settings.workerThreadLimit()));
if (indexingEnabled && Utils::clangdVersion(clangdExePath) >= QVersionNumber(15)) {
if (indexingEnabled) {
cmd.addArg("--background-index-priority="
+ ClangdSettings::priorityToString(indexingPriority));
}
if (Utils::clangdVersion(clangdExePath) >= QVersionNumber(16))
cmd.addArg("--rename-file-limit=0");
if (!jsonDbDir.isEmpty())
cmd.addArg("--compile-commands-dir=" + clangdExePath.withNewMappedPath(jsonDbDir).path());
@@ -558,9 +557,7 @@ void ClangdClient::findUsages(const CppEditor::CursorInEditor &cursor,
if (searchTerm.isEmpty())
return;
if (replacement && versionNumber() >= QVersionNumber(16)
&& Utils::qtcEnvironmentVariable("QTC_CLANGD_RENAMING") != "0") {
if (replacement && Utils::qtcEnvironmentVariable("QTC_CLANGD_RENAMING") != "0") {
// If we have up-to-date highlighting data, we can prevent giving clangd
// macros or namespaces to rename, which it can't cope with.
// TODO: Fix this upstream for macros; see https://github.com/clangd/clangd/issues/729.
@@ -1523,7 +1520,7 @@ void ClangdClient::Private::handleSemanticTokens(TextDocument *doc,
return;
}
force = force || isTesting;
const auto data = highlightingData.find(doc);
auto data = highlightingData.find(doc);
if (data != highlightingData.end()) {
if (!force && data->previousTokens.first == tokens
&& data->previousTokens.second == version) {
@@ -1533,14 +1530,13 @@ void ClangdClient::Private::handleSemanticTokens(TextDocument *doc,
data->previousTokens.first = tokens;
data->previousTokens.second = version;
} else {
highlightingData.insert(doc, {{tokens, version}, {}});
data = highlightingData.insert(doc, {{tokens, version}, {}});
}
for (const ExpandedSemanticToken &t : tokens)
qCDebug(clangdLogHighlight()) << '\t' << t.line << t.column << t.length << t.type
<< t.modifiers;
const auto astHandler = [this, tokens, doc, version](const ClangdAstNode &ast, const MessageId &) {
FinalizingSubtaskTimer t(highlightingTimer);
FinalizingSubtaskTimer ft(highlightingTimer);
if (!q->documentOpen(doc))
return;
if (version != q->documentVersion(doc->filePath())) {
@@ -1548,17 +1544,13 @@ void ClangdClient::Private::handleSemanticTokens(TextDocument *doc,
<< version << q->documentVersion(doc->filePath());
return;
}
if (clangdLogAst().isDebugEnabled())
ast.print();
const auto runner = [tokens, filePath = doc->filePath(),
text = doc->document()->toPlainText(), ast,
doc = QPointer(doc), rev = doc->document()->revision(),
clangdVersion = q->versionNumber(),
this] {
text = doc->document()->toPlainText(),
rev = doc->document()->revision(), this] {
try {
return Utils::asyncRun(doSemanticHighlighting, filePath, tokens, text, ast, doc,
rev, clangdVersion, highlightingTimer);
return Utils::asyncRun(doSemanticHighlighting, filePath, tokens, text,
rev, highlightingTimer);
} catch (const std::exception &e) {
qWarning() << "caught" << e.what() << "in main highlighting thread";
return QFuture<HighlightingResult>();
@@ -1576,18 +1568,12 @@ void ClangdClient::Private::handleSemanticTokens(TextDocument *doc,
return;
}
auto &data = highlightingData[doc];
if (!data.highlighter)
data.highlighter = new CppEditor::SemanticHighlighter(doc);
if (!data->highlighter)
data->highlighter = new CppEditor::SemanticHighlighter(doc);
else
data.highlighter->updateFormatMapFromFontSettings();
data.highlighter->setHighlightingRunner(runner);
data.highlighter->run();
};
if (q->versionNumber().majorVersion() >= 17)
astHandler({}, {});
else
getAndHandleAst(doc, astHandler, AstCallbackMode::SyncIfPossible);
data->highlighter->updateFormatMapFromFontSettings();
data->highlighter->setHighlightingRunner(runner);
data->highlighter->run();
}
std::optional<QList<CodeAction> > ClangdDiagnostic::codeActions() const

View File

@@ -459,8 +459,6 @@ void ClangdFollowSymbol::Private::handleGotoImplementationResult(
// Make a symbol info request for each link to get the class names.
// Also get the AST for the base declaration, so we can find out whether it's
// pure virtual and mark it accordingly.
// In addition, we need to follow all override links, because for these, clangd
// gives us the declaration instead of the definition (until clangd 16).
for (const Link &link : std::as_const(allLinks)) {
if (!client->documentForFilePath(link.targetFilePath) && addOpenFile(link.targetFilePath))
client->openExtraFile(link.targetFilePath);
@@ -487,42 +485,6 @@ void ClangdFollowSymbol::Private::handleGotoImplementationResult(
if (link == defLink)
continue;
if (client->versionNumber().majorVersion() >= 17)
continue;
const TextDocumentIdentifier doc(client->hostPathToServerUri(link.targetFilePath));
const TextDocumentPositionParams params(doc, pos);
GotoDefinitionRequest defReq(params);
defReq.setResponseCallback(
[this, link, transformLink, sentinel = QPointer(q), reqId = defReq.id()](
const GotoDefinitionRequest::Response &response) {
qCDebug(clangdLog) << "handling additional go to definition reply for"
<< link.targetFilePath << link.targetLine;
if (!sentinel)
return;
Link newLink;
if (std::optional<GotoResult> _result = response.result()) {
const GotoResult result = _result.value();
if (const auto ploc = std::get_if<Location>(&result)) {
newLink = transformLink(*ploc);
} else if (const auto plloc = std::get_if<QList<Location>>(&result)) {
if (!plloc->isEmpty())
newLink = transformLink(plloc->value(0));
}
}
qCDebug(clangdLog) << "def link is" << newLink.targetFilePath << newLink.targetLine;
declDefMap.insert(link, newLink);
pendingGotoDefRequests.removeOne(reqId);
if (pendingSymbolInfoRequests.isEmpty() && pendingGotoDefRequests.isEmpty()
&& defLinkNode.isValid()) {
handleDocumentInfoResults();
}
});
pendingGotoDefRequests << defReq.id();
qCDebug(clangdLog) << "sending additional go to definition request"
<< link.targetFilePath << link.targetLine;
client->sendMessage(defReq, ClangdClient::SendDocUpdates::Ignore);
}
const FilePath defLinkFilePath = defLink.targetFilePath;

View File

@@ -28,117 +28,19 @@ using namespace TextEditor;
namespace ClangCodeModel::Internal {
Q_LOGGING_CATEGORY(clangdLogHighlight, "qtc.clangcodemodel.clangd.highlight", QtWarningMsg);
// clangd reports also the #ifs, #elses and #endifs around the disabled code as disabled,
// and not even in a consistent manner. We don't want this, so we have to clean up here.
// But note that we require this behavior, as otherwise we would not be able to grey out
// e.g. empty lines after an #ifdef, due to the lack of symbols.
static QList<BlockRange> cleanupDisabledCode(HighlightingResults &results, const QTextDocument *doc,
const QString &docContent)
{
QList<BlockRange> ifdefedOutRanges;
int rangeStartPos = -1;
for (auto it = results.begin(); it != results.end();) {
const bool wasIfdefedOut = rangeStartPos != -1;
const bool isIfDefedOut = it->textStyles.mainStyle == C_DISABLED_CODE;
if (!isIfDefedOut) {
if (wasIfdefedOut) {
const QTextBlock block = doc->findBlockByNumber(it->line - 1);
ifdefedOutRanges << BlockRange(rangeStartPos, block.position());
rangeStartPos = -1;
}
++it;
continue;
}
if (!wasIfdefedOut)
rangeStartPos = doc->findBlockByNumber(it->line - 1).position();
// Does the current line contain a potential "ifdefed-out switcher"?
// If not, no state change is possible and we continue with the next line.
const auto isPreprocessorControlStatement = [&] {
const int pos = Utils::Text::positionInText(doc, it->line, it->column);
const QStringView content = subViewLen(docContent, pos, it->length).trimmed();
if (content.isEmpty() || content.first() != '#')
return false;
int offset = 1;
while (offset < content.size() && content.at(offset).isSpace())
++offset;
if (offset == content.size())
return false;
const QStringView ppDirective = content.mid(offset);
return ppDirective.startsWith(QLatin1String("if"))
|| ppDirective.startsWith(QLatin1String("elif"))
|| ppDirective.startsWith(QLatin1String("else"))
|| ppDirective.startsWith(QLatin1String("endif"));
};
if (!isPreprocessorControlStatement()) {
++it;
continue;
}
if (!wasIfdefedOut) {
// The #if or #else that starts disabled code should not be disabled.
const QTextBlock nextBlock = doc->findBlockByNumber(it->line);
rangeStartPos = nextBlock.isValid() ? nextBlock.position() : -1;
it = results.erase(it);
continue;
}
if (wasIfdefedOut && (it + 1 == results.end()
|| (it + 1)->textStyles.mainStyle != C_DISABLED_CODE
|| (it + 1)->line != it->line + 1)) {
// The #else or #endif that ends disabled code should not be disabled.
const QTextBlock block = doc->findBlockByNumber(it->line - 1);
ifdefedOutRanges << BlockRange(rangeStartPos, block.position());
rangeStartPos = -1;
it = results.erase(it);
continue;
}
++it;
}
if (rangeStartPos != -1)
ifdefedOutRanges << BlockRange(rangeStartPos, doc->characterCount());
qCDebug(clangdLogHighlight) << "found" << ifdefedOutRanges.size() << "ifdefed-out ranges";
if (clangdLogHighlight().isDebugEnabled()) {
for (const BlockRange &r : std::as_const(ifdefedOutRanges))
qCDebug(clangdLogHighlight) << r.first() << r.last();
}
return ifdefedOutRanges;
}
class ExtraHighlightingResultsCollector
{
public:
ExtraHighlightingResultsCollector(QPromise<HighlightingResult> &promise,
HighlightingResults &results,
const Utils::FilePath &filePath, const ClangdAstNode &ast,
const QTextDocument *doc, const QString &docContent,
const QVersionNumber &clangdVersion);
ExtraHighlightingResultsCollector(HighlightingResults &results,
const Utils::FilePath &filePath,
const QTextDocument *doc, const QString &docContent);
void collect();
private:
static bool lessThan(const HighlightingResult &r1, const HighlightingResult &r2);
static int onlyIndexOf(const QStringView &text, const QStringView &subString, int from = 0);
int posForNodeStart(const ClangdAstNode &node) const;
int posForNodeEnd(const ClangdAstNode &node) const;
void insertResult(const HighlightingResult &result);
void insertResult(const ClangdAstNode &node, TextStyle style);
void insertAngleBracketInfo(int searchStart1, int searchEnd1, int searchStart2, int searchEnd2);
void setResultPosFromRange(HighlightingResult &result, const Range &range);
void collectFromNode(const ClangdAstNode &node);
void visitNode(const ClangdAstNode&node);
QPromise<HighlightingResult> &m_promise;
HighlightingResults &m_results;
const Utils::FilePath m_filePath;
const ClangdAstNode &m_ast;
const QTextDocument * const m_doc;
const QString &m_docContent;
const int m_clangdVersion;
ClangdAstNode::FileStatus m_currentFileStatus = ClangdAstNode::FileStatus::Unknown;
};
void doSemanticHighlighting(
@@ -146,10 +48,7 @@ void doSemanticHighlighting(
const Utils::FilePath &filePath,
const QList<ExpandedSemanticToken> &tokens,
const QString &docContents,
const ClangdAstNode &ast,
const QPointer<TextDocument> &textDocument,
int docRevision,
const QVersionNumber &clangdVersion,
const TaskTimer &taskTimer)
{
ThreadedSubtaskTimer t("highlighting", taskTimer);
@@ -157,111 +56,6 @@ void doSemanticHighlighting(
return;
const QTextDocument doc(docContents);
const auto tokenRange = [&doc](const ExpandedSemanticToken &token) {
const Position startPos(token.line - 1, token.column - 1);
const Position endPos = startPos.withOffset(token.length, &doc);
return Range(startPos, endPos);
};
const int clangdMajorVersion = clangdVersion.majorVersion();
const auto isOutputParameter = [&ast, &tokenRange, clangdMajorVersion]
(const ExpandedSemanticToken &token) {
if (token.modifiers.contains(QLatin1String("usedAsMutableReference")))
return true;
if (token.modifiers.contains(QLatin1String("usedAsMutablePointer")))
return true;
if (clangdMajorVersion >= 16)
return false;
if (token.type != "variable" && token.type != "property" && token.type != "parameter")
return false;
const Range range = tokenRange(token);
const ClangdAstPath path = getAstPath(ast, range);
if (path.size() < 2)
return false;
if (token.type == "property"
&& (path.rbegin()->kind() == "MemberInitializer"
|| path.rbegin()->kind() == "CXXConstruct")) {
return false;
}
if (path.rbegin()->hasConstType())
return false;
for (auto it = path.rbegin() + 1; it != path.rend(); ++it) {
if (it->kind() == "CXXConstruct" || it->kind() == "MemberInitializer")
return true;
if (it->kind() == "Call") {
// The first child is e.g. a called lambda or an object on which
// the call happens, and should not be highlighted as an output argument.
// If the call is not fully resolved (as in templates), we don't
// know whether the argument is passed as const or not.
if (it->arcanaContains("dependent type"))
return false;
const QList<ClangdAstNode> children = it->children().value_or(QList<ClangdAstNode>());
return children.isEmpty()
|| (children.first().range() != (it - 1)->range()
&& children.first().kind() != "UnresolvedLookup");
}
// The token should get marked for e.g. lambdas, but not for assignment operators,
// where the user sees that it's being written.
if (it->kind() == "CXXOperatorCall") {
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
// (a lambda or a class instance), after that follow the actual call arguments.
if (children.size() < 2)
return false;
// The call itself is never modifiable.
if (children.first().range() == range)
return false;
// The callable is never displayed as an output parameter.
// TODO: A good argument can be made to display objects on which a non-const
// operator or function is called as output parameters.
if (children.at(1).range().contains(range))
return false;
QList<ClangdAstNode> firstChildTree{children.first()};
while (!firstChildTree.isEmpty()) {
const ClangdAstNode n = firstChildTree.takeFirst();
const QString detail = n.detail().value_or(QString());
if (detail.startsWith("operator")) {
return !detail.contains('=')
&& !detail.contains("++") && !detail.contains("--")
&& !detail.contains("<<") && !detail.contains(">>")
&& !detail.contains("*");
}
firstChildTree << n.children().value_or(QList<ClangdAstNode>());
}
return true;
}
if (it->kind() == "Lambda")
return false;
if (it->kind() == "BinaryOperator")
return false;
if (it->hasConstType())
return false;
if (it->kind() == "CXXMemberCall") {
if (it == path.rbegin())
return false;
const QList<ClangdAstNode> children = it->children().value_or(QList<ClangdAstNode>());
QTC_ASSERT(!children.isEmpty(), return false);
// The called object is never displayed as an output parameter.
// TODO: A good argument can be made to display objects on which a non-const
// operator or function is called as output parameters.
return (it - 1)->range() != children.first().range();
}
if (it->kind() == "Member" && it->arcanaContains("(")
&& !it->arcanaContains("bound member function type")) {
return false;
}
}
return false;
};
const std::function<HighlightingResult(const ExpandedSemanticToken &)> toResult
= [&](const ExpandedSemanticToken &token) {
@@ -278,40 +72,12 @@ void doSemanticHighlighting(
} else if (token.type == "function" || token.type == "method") {
styles.mainStyle = token.modifiers.contains(QLatin1String("virtual"))
? C_VIRTUAL_METHOD : C_FUNCTION;
if (token.modifiers.contains("definition")) {
if (token.modifiers.contains("definition"))
styles.mixinStyles.push_back(C_FUNCTION_DEFINITION);
} else if (clangdMajorVersion < 16 && ast.isValid()) {
const ClangdAstPath path = getAstPath(ast, tokenRange(token));
if (path.length() > 1) {
const ClangdAstNode declNode = path.at(path.length() - 2);
if ((declNode.kind() == "Function" || declNode.kind() == "CXXMethod")
&& declNode.hasChildWithRole("statement")) {
styles.mixinStyles.push_back(C_FUNCTION_DEFINITION);
}
}
}
} else if (token.type == "class") {
styles.mainStyle = C_TYPE;
if (token.modifiers.contains("constructorOrDestructor")) {
if (token.modifiers.contains("constructorOrDestructor"))
styles.mainStyle = C_FUNCTION;
} else if (clangdMajorVersion < 16 && ast.isValid()) {
const ClangdAstPath path = getAstPath(ast, tokenRange(token));
if (!path.isEmpty()) {
if (path.last().kind() == "CXXConstructor") {
if (!path.last().arcanaContains("implicit"))
styles.mainStyle = C_FUNCTION;
} else if (path.last().kind() == "Record" && path.length() > 1) {
const ClangdAstNode node = path.at(path.length() - 2);
if (node.kind() == "CXXDestructor" && !node.arcanaContains("implicit")) {
styles.mainStyle = C_FUNCTION;
// https://github.com/clangd/clangd/issues/872
if (node.role() == "declaration")
styles.mixinStyles.push_back(C_DECLARATION);
}
}
}
}
} else if (token.type == "comment") { // "comment" means code disabled via the preprocessor
styles.mainStyle = C_DISABLED_CODE;
} else if (token.type == "namespace") {
@@ -384,8 +150,10 @@ void doSemanticHighlighting(
styles.mainStyle = C_STATIC_MEMBER;
}
}
if (isOutputParameter(token))
if (token.modifiers.contains(QLatin1String("usedAsMutableReference"))
|| token.modifiers.contains(QLatin1String("usedAsMutablePointer"))) {
styles.mixinStyles.push_back(C_OUTPUT_ARGUMENT);
}
return HighlightingResult(token.line, token.column, token.length, styles);
};
@@ -398,24 +166,13 @@ void doSemanticHighlighting(
}
};
auto results = QtConcurrent::blockingMapped<HighlightingResults>(tokens, safeToResult);
const bool handleInactiveCode = clangdMajorVersion < 17;
QList<BlockRange> ifdefedOutBlocks;
if (handleInactiveCode)
ifdefedOutBlocks = cleanupDisabledCode(results, &doc, docContents);
ExtraHighlightingResultsCollector(promise, results, filePath, ast, &doc, docContents,
clangdVersion).collect();
ExtraHighlightingResultsCollector(results, filePath, &doc, docContents).collect();
Utils::erase(results, [](const HighlightingResult &res) {
// QTCREATORBUG-28639
return res.textStyles.mainStyle == C_TEXT && res.textStyles.mixinStyles.empty();
});
if (!promise.isCanceled()) {
qCInfo(clangdLogHighlight) << "reporting" << results.size() << "highlighting results";
if (handleInactiveCode) {
QMetaObject::invokeMethod(textDocument, [textDocument, ifdefedOutBlocks, docRevision] {
if (textDocument && textDocument->document()->revision() == docRevision)
textDocument->setIfdefedOutBlocks(ifdefedOutBlocks);
}, Qt::QueuedConnection);
}
QList<Range> virtualRanges;
for (const HighlightingResult &r : results) {
qCDebug(clangdLogHighlight)
@@ -440,11 +197,11 @@ void doSemanticHighlighting(
}
ExtraHighlightingResultsCollector::ExtraHighlightingResultsCollector(
QPromise<HighlightingResult> &promise, HighlightingResults &results,
const Utils::FilePath &filePath, const ClangdAstNode &ast, const QTextDocument *doc,
const QString &docContent, const QVersionNumber &clangdVersion)
: m_promise(promise), m_results(results), m_filePath(filePath), m_ast(ast), m_doc(doc),
m_docContent(docContent), m_clangdVersion(clangdVersion.majorVersion())
HighlightingResults &results,
const Utils::FilePath &filePath, const QTextDocument *doc,
const QString &docContent)
: m_results(results), m_filePath(filePath), m_doc(doc),
m_docContent(docContent)
{
}
@@ -469,497 +226,6 @@ void ExtraHighlightingResultsCollector::collect()
for (const HighlightingResult &newRes : propHighlighter.highlight())
m_results.insert(++i, newRes);
}
if (!m_ast.isValid())
return;
QTC_ASSERT(m_clangdVersion < 17, return);
visitNode(m_ast);
}
bool ExtraHighlightingResultsCollector::lessThan(const HighlightingResult &r1,
const HighlightingResult &r2)
{
return r1.line < r2.line || (r1.line == r2.line && r1.column < r2.column)
|| (r1.line == r2.line && r1.column == r2.column && r1.length < r2.length);
}
int ExtraHighlightingResultsCollector::onlyIndexOf(const QStringView &text,
const QStringView &subString, int from)
{
const int firstIndex = text.indexOf(subString, from);
if (firstIndex == -1)
return -1;
const int nextIndex = text.indexOf(subString, firstIndex + 1);
// The second condion deals with the off-by-one error in TemplateSpecialization nodes;
// see collectFromNode().
return nextIndex == -1 || nextIndex == firstIndex + 1 ? firstIndex : -1;
}
// 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.
// In corner cases, this might get sabotaged by e.g. comments, in which case we give up.
int ExtraHighlightingResultsCollector::posForNodeStart(const ClangdAstNode &node) const
{
return Utils::Text::positionInText(m_doc, node.range().start().line() + 1,
node.range().start().character() + 1);
}
int ExtraHighlightingResultsCollector::posForNodeEnd(const ClangdAstNode &node) const
{
return Utils::Text::positionInText(m_doc, node.range().end().line() + 1,
node.range().end().character() + 1);
}
void ExtraHighlightingResultsCollector::insertResult(const HighlightingResult &result)
{
if (!result.isValid()) // Some nodes don't have a range.
return;
const auto it = std::lower_bound(m_results.begin(), m_results.end(), result, lessThan);
if (it == m_results.end() || *it != result) {
// Prevent inserting expansions for function-like macros. For instance:
// #define TEST() "blubb"
// const char *s = TEST();
// The macro name is always shorter than the expansion and starts at the same
// location, so it should occur right before the insertion position.
if (it > m_results.begin() && (it - 1)->line == result.line
&& (it - 1)->column == result.column
&& (it - 1)->textStyles.mainStyle == C_MACRO) {
return;
}
// Bogus ranges; e.g. QTCREATORBUG-27601
if (it != m_results.end()) {
const int nextStartPos = Utils::Text::positionInText(m_doc, it->line, it->column);
const int resultEndPos = Utils::Text::positionInText(m_doc, result.line, result.column)
+ result.length;
if (resultEndPos > nextStartPos)
return;
}
qCDebug(clangdLogHighlight) << "adding additional highlighting result"
<< result.line << result.column << result.length;
m_results.insert(it, result);
return;
}
}
void ExtraHighlightingResultsCollector::insertResult(const ClangdAstNode &node, TextStyle style)
{
HighlightingResult result;
result.useTextSyles = true;
result.textStyles.mainStyle = style;
setResultPosFromRange(result, node.range());
insertResult(result);
return;
}
// For matching the "<" and ">" brackets of template declarations, specializations
// and instantiations.
void ExtraHighlightingResultsCollector::insertAngleBracketInfo(int searchStart1, int searchEnd1,
int searchStart2, int searchEnd2)
{
const int openingAngleBracketPos = onlyIndexOf(
subViewEnd(m_docContent, searchStart1, searchEnd1),
QStringView(QStringLiteral("<")));
if (openingAngleBracketPos == -1)
return;
const int absOpeningAngleBracketPos = searchStart1 + openingAngleBracketPos;
if (absOpeningAngleBracketPos > searchStart2)
searchStart2 = absOpeningAngleBracketPos + 1;
if (searchStart2 >= searchEnd2)
return;
const int closingAngleBracketPos = onlyIndexOf(
subViewEnd(m_docContent, searchStart2, searchEnd2),
QStringView(QStringLiteral(">")));
if (closingAngleBracketPos == -1)
return;
const int absClosingAngleBracketPos = searchStart2 + closingAngleBracketPos;
if (absOpeningAngleBracketPos > absClosingAngleBracketPos)
return;
HighlightingResult result;
result.useTextSyles = true;
result.textStyles.mainStyle = C_PUNCTUATION;
Utils::Text::convertPosition(m_doc, absOpeningAngleBracketPos, &result.line, &result.column);
++result.column;
result.length = 1;
result.kind = CppEditor::SemanticHighlighter::AngleBracketOpen;
insertResult(result);
Utils::Text::convertPosition(m_doc, absClosingAngleBracketPos, &result.line, &result.column);
++result.column;
result.kind = CppEditor::SemanticHighlighter::AngleBracketClose;
insertResult(result);
}
void ExtraHighlightingResultsCollector::setResultPosFromRange(HighlightingResult &result,
const Range &range)
{
if (!range.isValid())
return;
const Position startPos = range.start();
const Position endPos = range.end();
result.line = startPos.line() + 1;
result.column = startPos.character() + 1;
result.length = endPos.toPositionInDocument(m_doc) - startPos.toPositionInDocument(m_doc);
}
void ExtraHighlightingResultsCollector::collectFromNode(const ClangdAstNode &node)
{
if (node.kind().endsWith("Literal"))
return;
if (node.role() == "type" && node.kind() == "Builtin")
return;
if (m_clangdVersion < 16 && node.role() == "attribute"
&& (node.kind() == "Override" || node.kind() == "Final")) {
insertResult(node, C_KEYWORD);
return;
}
const bool isExpression = node.role() == "expression";
if (m_clangdVersion < 16 && isExpression && node.kind() == "Predefined") {
insertResult(node, C_LOCAL);
return;
}
const bool isDeclaration = node.role() == "declaration";
const int nodeStartPos = posForNodeStart(node);
const int nodeEndPos = posForNodeEnd(node);
const QList<ClangdAstNode> children = node.children().value_or(QList<ClangdAstNode>());
// Match question mark and colon in ternary operators.
if (m_clangdVersion < 16 && isExpression && node.kind() == "ConditionalOperator") {
if (children.size() != 3)
return;
// The question mark is between sub-expressions 1 and 2, the colon is between
// sub-expressions 2 and 3.
const int searchStartPosQuestionMark = posForNodeEnd(children.first());
const int searchEndPosQuestionMark = posForNodeStart(children.at(1));
QStringView content = subViewEnd(m_docContent, searchStartPosQuestionMark,
searchEndPosQuestionMark);
const int questionMarkPos = onlyIndexOf(content, QStringView(QStringLiteral("?")));
if (questionMarkPos == -1)
return;
const int searchStartPosColon = posForNodeEnd(children.at(1));
const int searchEndPosColon = posForNodeStart(children.at(2));
content = subViewEnd(m_docContent, searchStartPosColon, searchEndPosColon);
const int colonPos = onlyIndexOf(content, QStringView(QStringLiteral(":")));
if (colonPos == -1)
return;
const int absQuestionMarkPos = searchStartPosQuestionMark + questionMarkPos;
const int absColonPos = searchStartPosColon + colonPos;
if (absQuestionMarkPos > absColonPos)
return;
HighlightingResult result;
result.useTextSyles = true;
result.textStyles.mainStyle = C_PUNCTUATION;
result.textStyles.mixinStyles.push_back(C_OPERATOR);
Utils::Text::convertPosition(m_doc, absQuestionMarkPos, &result.line, &result.column);
++result.column;
result.length = 1;
result.kind = CppEditor::SemanticHighlighter::TernaryIf;
insertResult(result);
Utils::Text::convertPosition(m_doc, absColonPos, &result.line, &result.column);
++result.column;
result.kind = CppEditor::SemanticHighlighter::TernaryElse;
insertResult(result);
return;
}
if (isDeclaration && (node.kind() == "FunctionTemplate" || node.kind() == "ClassTemplate")) {
// The child nodes are the template parameters and and the function or class.
// The opening angle bracket is before the first child node, the closing angle
// bracket is before the function child node and after the last param node.
const QString classOrFunctionKind = QLatin1String(node.kind() == "FunctionTemplate"
? "Function" : "CXXRecord");
const auto functionOrClassIt = std::find_if(children.begin(), children.end(),
[&classOrFunctionKind](const ClangdAstNode &n) {
return n.role() == "declaration" && n.kind() == classOrFunctionKind;
});
if (functionOrClassIt == children.end() || functionOrClassIt == children.begin())
return;
const int firstTemplateParamStartPos = posForNodeStart(children.first());
const int lastTemplateParamEndPos = posForNodeEnd(*(functionOrClassIt - 1));
const int functionOrClassStartPos = posForNodeStart(*functionOrClassIt);
insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos,
lastTemplateParamEndPos, functionOrClassStartPos);
return;
}
const auto isTemplateParamDecl = [](const ClangdAstNode &node) {
return node.isTemplateParameterDeclaration();
};
if (isDeclaration && node.kind() == "TypeAliasTemplate") {
// Children are one node of type TypeAlias and the template parameters.
// The opening angle bracket is before the first parameter and the closing
// angle bracket is after the last parameter.
// The TypeAlias node seems to appear first in the AST, even though lexically
// is comes after the parameters. We don't rely on the order here.
// Note that there is a second pair of angle brackets. That one is part of
// a TemplateSpecialization, which is handled further below.
const auto firstTemplateParam = std::find_if(children.begin(), children.end(),
isTemplateParamDecl);
if (firstTemplateParam == children.end())
return;
const auto lastTemplateParam = std::find_if(children.rbegin(), children.rend(),
isTemplateParamDecl);
QTC_ASSERT(lastTemplateParam != children.rend(), return);
const auto typeAlias = std::find_if(children.begin(), children.end(),
[](const ClangdAstNode &n) { return n.kind() == "TypeAlias"; });
if (typeAlias == children.end())
return;
const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam);
const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam);
const int searchEndPos = posForNodeStart(*typeAlias);
insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos,
lastTemplateParamEndPos, searchEndPos);
return;
}
if (isDeclaration && node.kind() == "ClassTemplateSpecialization") {
// There is one child of kind TemplateSpecialization. The first pair
// of angle brackets comes before that.
if (children.size() == 1) {
const int childNodePos = posForNodeStart(children.first());
insertAngleBracketInfo(nodeStartPos, childNodePos, nodeStartPos, childNodePos);
}
return;
}
if (isDeclaration && node.kind() == "TemplateTemplateParm") {
// The child nodes are template arguments and template parameters.
// Arguments seem to appear before parameters in the AST, even though they
// come after them in the source code. We don't rely on the order here.
const auto firstTemplateParam = std::find_if(children.begin(), children.end(),
isTemplateParamDecl);
if (firstTemplateParam == children.end())
return;
const auto lastTemplateParam = std::find_if(children.rbegin(), children.rend(),
isTemplateParamDecl);
QTC_ASSERT(lastTemplateParam != children.rend(), return);
const auto templateArg = std::find_if(children.begin(), children.end(),
[](const ClangdAstNode &n) { return n.role() == "template argument"; });
const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam);
const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam);
const int searchEndPos = templateArg == children.end()
? nodeEndPos : posForNodeStart(*templateArg);
insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos,
lastTemplateParamEndPos, searchEndPos);
return;
}
// {static,dynamic,reinterpret}_cast<>().
if (isExpression && node.kind().startsWith("CXX") && node.kind().endsWith("Cast")) {
// First child is type, second child is expression.
// The opening angle bracket is before the first child, the closing angle bracket
// is between the two children.
if (children.size() == 2) {
insertAngleBracketInfo(nodeStartPos, posForNodeStart(children.first()),
posForNodeEnd(children.first()),
posForNodeStart(children.last()));
}
return;
}
if (node.kind() == "TemplateSpecialization") {
// First comes the template type, then the template arguments.
// The opening angle bracket is before the first template argument,
// the closing angle bracket is after the last template argument.
// The first child node has no range, so we start searching at the parent node.
if (children.size() >= 2) {
int searchStart2 = posForNodeEnd(children.last());
int searchEnd2 = nodeEndPos;
// There is a weird off-by-one error on the clang side: If there is a
// nested template instantiation *and* there is no space between
// the closing angle brackets, then the inner TemplateSpecialization node's range
// will extend one character too far, covering the outer's closing angle bracket.
// This is what we are correcting for here.
// This issue is tracked at https://github.com/clangd/clangd/issues/871.
if (searchStart2 == searchEnd2)
--searchStart2;
insertAngleBracketInfo(nodeStartPos, posForNodeStart(children.at(1)),
searchStart2, searchEnd2);
}
return;
}
if (!isExpression && !isDeclaration)
return;
if (m_clangdVersion >= 16)
return;
// Operators, overloaded ones in particular.
static const QString operatorPrefix = "operator";
QString detail = node.detail().value_or(QString());
const bool isCallToNew = node.kind() == "CXXNew";
const bool isCallToDelete = node.kind() == "CXXDelete";
const auto isProperOperator = [&] {
if (isCallToNew || isCallToDelete)
return true;
if (!detail.startsWith(operatorPrefix))
return false;
if (detail == operatorPrefix)
return false;
const QChar nextChar = detail.at(operatorPrefix.length());
return !nextChar.isLetterOrNumber() && nextChar != '_';
};
if (!isProperOperator())
return;
if (!isCallToNew && !isCallToDelete)
detail.remove(0, operatorPrefix.length());
if (node.kind() == "CXXConversion")
return;
HighlightingResult result;
result.useTextSyles = true;
const bool isOverloaded = isDeclaration || ((!isCallToNew && !isCallToDelete)
|| node.arcanaContains("CXXMethod"));
result.textStyles.mainStyle = isCallToNew || isCallToDelete || detail.at(0).isSpace()
? C_KEYWORD : C_PUNCTUATION;
result.textStyles.mixinStyles.push_back(C_OPERATOR);
if (isOverloaded)
result.textStyles.mixinStyles.push_back(C_OVERLOADED_OPERATOR);
if (isDeclaration)
result.textStyles.mixinStyles.push_back(C_DECLARATION);
const QStringView nodeText = subViewEnd(m_docContent, nodeStartPos, nodeEndPos);
if (isCallToNew || isCallToDelete) {
result.line = node.range().start().line() + 1;
result.column = node.range().start().character() + 1;
result.length = isCallToNew ? 3 : 6;
insertResult(result);
if (node.arcanaContains("array")) {
const int openingBracketOffset = nodeText.indexOf('[');
if (openingBracketOffset == -1)
return;
const int closingBracketOffset = nodeText.lastIndexOf(']');
if (closingBracketOffset == -1 || closingBracketOffset < openingBracketOffset)
return;
result.textStyles.mainStyle = C_PUNCTUATION;
result.length = 1;
Utils::Text::convertPosition(m_doc,
nodeStartPos + openingBracketOffset,
&result.line, &result.column);
++result.column;
insertResult(result);
Utils::Text::convertPosition(m_doc,
nodeStartPos + closingBracketOffset,
&result.line, &result.column);
++result.column;
insertResult(result);
}
return;
}
if (isExpression && (detail == QLatin1String("()") || detail == QLatin1String("[]"))) {
result.line = node.range().start().line() + 1;
result.column = node.range().start().character() + 1;
result.length = 1;
insertResult(result);
result.line = node.range().end().line() + 1;
result.column = node.range().end().character();
insertResult(result);
return;
}
const int opStringLen = detail.at(0).isSpace() ? detail.length() - 1 : detail.length();
// The simple case: Call to operator+, +=, * etc.
if (nodeEndPos - nodeStartPos == opStringLen) {
setResultPosFromRange(result, node.range());
insertResult(result);
return;
}
const int prefixOffset = nodeText.indexOf(operatorPrefix);
if (prefixOffset == -1)
return;
const bool isArray = detail == "[]";
const bool isCall = detail == "()";
const bool isArrayNew = detail == " new[]";
const bool isArrayDelete = detail == " delete[]";
const QStringView searchTerm = isArray || isCall
? QStringView(detail).chopped(1) : isArrayNew || isArrayDelete
? QStringView(detail).chopped(2) : detail;
const int opStringOffset = nodeText.indexOf(searchTerm, prefixOffset
+ operatorPrefix.length());
if (opStringOffset == -1 || nodeText.indexOf(operatorPrefix, opStringOffset) != -1)
return;
const int opStringOffsetInDoc = nodeStartPos + opStringOffset
+ detail.length() - opStringLen;
Utils::Text::convertPosition(m_doc, opStringOffsetInDoc, &result.line, &result.column);
++result.column;
result.length = opStringLen;
if (isArray || isCall)
result.length = 1;
else if (isArrayNew || isArrayDelete)
result.length -= 2;
if (!isArray && !isCall)
insertResult(result);
if (!isArray && !isCall && !isArrayNew && !isArrayDelete)
return;
result.textStyles.mainStyle = C_PUNCTUATION;
result.length = 1;
const int openingParenOffset = nodeText.indexOf(
isCall ? '(' : '[', prefixOffset + operatorPrefix.length());
if (openingParenOffset == -1)
return;
const int closingParenOffset = nodeText.indexOf(isCall ? ')' : ']', openingParenOffset + 1);
if (closingParenOffset == -1 || closingParenOffset < openingParenOffset)
return;
Utils::Text::convertPosition(m_doc, nodeStartPos + openingParenOffset,
&result.line, &result.column);
++result.column;
insertResult(result);
Utils::Text::convertPosition(m_doc, nodeStartPos + closingParenOffset,
&result.line, &result.column);
++result.column;
insertResult(result);
}
void ExtraHighlightingResultsCollector::visitNode(const ClangdAstNode &node)
{
if (m_promise.isCanceled())
return;
const ClangdAstNode::FileStatus prevFileStatus = m_currentFileStatus;
m_currentFileStatus = node.fileStatus(m_filePath);
if (m_currentFileStatus == ClangdAstNode::FileStatus::Unknown
&& prevFileStatus != ClangdAstNode::FileStatus::Ours) {
m_currentFileStatus = prevFileStatus;
}
switch (m_currentFileStatus) {
case ClangdAstNode::FileStatus::Ours:
case ClangdAstNode::FileStatus::Unknown:
collectFromNode(node);
[[fallthrough]];
case ClangdAstNode::FileStatus::Foreign:
case ClangCodeModel::Internal::ClangdAstNode::FileStatus::Mixed: {
const auto children = node.children();
if (!children)
return;
for (const ClangdAstNode &childNode : *children)
visitNode(childNode);
break;
}
}
m_currentFileStatus = prevFileStatus;
}
class InactiveRegionsParams : public JsonObject

View File

@@ -33,10 +33,7 @@ void doSemanticHighlighting(
const Utils::FilePath &filePath,
const QList<LanguageClient::ExpandedSemanticToken> &tokens,
const QString &docContents,
const ClangdAstNode &ast,
const QPointer<TextEditor::TextDocument> &textDocument,
int docRevision,
const QVersionNumber &clangdVersion,
const TaskTimer &taskTimer
);

View File

@@ -1107,8 +1107,7 @@ void ClangdTestHighlighting::test_data()
QTest::newRow("call to function pointer alias") << 344 << 5 << 344 << 13
<< QList<int>{C_TYPE} << 0;
QTest::newRow("friend class declaration") << 350 << 18 << 350 << 27
<< (client()->versionNumber().majorVersion() >= 16
? QList<int>{C_TYPE, C_DECLARATION}: QList<int>{C_TYPE}) << 0;
<< QList<int>{C_TYPE, C_DECLARATION} << 0;
QTest::newRow("friend class reference") << 351 << 34 << 351 << 43
<< QList<int>{C_TYPE} << 0;
QTest::newRow("function parameter of friend class type") << 351 << 45 << 351 << 50
@@ -1374,10 +1373,6 @@ void ClangdTestHighlighting::test_data()
<< QList<int>{C_PUNCTUATION} << int(CppEditor::SemanticHighlighter::AngleBracketClose);
QTest::newRow("macro in struct") << 795 << 9 << 795 << 14
<< QList<int>{C_MACRO, C_DECLARATION} << 0;
if (client()->versionNumber() < QVersionNumber(17)) {
QTest::newRow("#ifdef'ed out code") << 800 << 1 << 800 << 17
<< QList<int>{C_DISABLED_CODE} << 0;
}
QTest::newRow("static function call (object)") << 819 << 5 << 819 << 6
<< QList<int>{C_LOCAL} << 0;
QTest::newRow("static function call (argument)") << 819 << 18 << 819 << 19