Files
qt-creator/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp
Christian Kandeler 1c272072e5 ClangCodeModel: Fix displaying ifdef'ed regions
... that are started by an indented preprocessor directive.
clangd potentially sends regions not starting at line boundaries (in
this case erroneously, but potentially also on purpose), which
TextDocument::setIfdefedOutBlocks() does not expect.

Fixes: QTCREATORBUG-31204
Change-Id: I9126263b9501edbbab1fbc22cc59c5ee1ae814c2
Reviewed-by: David Schulz <david.schulz@qt.io>
2024-07-15 09:26:51 +00:00

275 lines
11 KiB
C++

// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "clangdsemantichighlighting.h"
#include "clangdast.h"
#include "clangdclient.h"
#include "clangdqpropertyhighlighter.h"
#include "clangmodelmanagersupport.h"
#include "tasktimers.h"
#include <cppeditor/semantichighlighter.h>
#include <languageclient/languageclientmanager.h>
#include <languageclient/semantichighlightsupport.h>
#include <languageserverprotocol/lsptypes.h>
#include <texteditor/blockrange.h>
#include <texteditor/textstyles.h>
#include <QtConcurrent>
#include <QTextDocument>
#include <new>
using namespace LanguageClient;
using namespace LanguageServerProtocol;
using namespace TextEditor;
namespace ClangCodeModel::Internal {
Q_LOGGING_CATEGORY(clangdLogHighlight, "qtc.clangcodemodel.clangd.highlight", QtWarningMsg);
class ExtraHighlightingResultsCollector
{
public:
ExtraHighlightingResultsCollector(HighlightingResults &results,
const Utils::FilePath &filePath,
const QTextDocument *doc, const QString &docContent);
void collect();
private:
HighlightingResults &m_results;
const Utils::FilePath m_filePath;
const QTextDocument * const m_doc;
const QString &m_docContent;
};
void doSemanticHighlighting(
QPromise<HighlightingResult> &promise,
const Utils::FilePath &filePath,
const QList<ExpandedSemanticToken> &tokens,
const QString &docContents,
int docRevision,
const TaskTimer &taskTimer)
{
ThreadedSubtaskTimer t("highlighting", taskTimer);
if (promise.isCanceled())
return;
const QTextDocument doc(docContents);
const std::function<HighlightingResult(const ExpandedSemanticToken &)> toResult
= [&](const ExpandedSemanticToken &token) {
TextStyles styles;
if (token.type == "variable") {
if (token.modifiers.contains(QLatin1String("functionScope"))) {
styles.mainStyle = C_LOCAL;
} else if (token.modifiers.contains(QLatin1String("classScope"))) {
styles.mainStyle = C_FIELD;
} else if (token.modifiers.contains(QLatin1String("fileScope"))
|| token.modifiers.contains(QLatin1String("globalScope"))) {
styles.mainStyle = C_GLOBAL;
}
} 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"))
styles.mixinStyles.push_back(C_FUNCTION_DEFINITION);
} else if (token.type == "class") {
styles.mainStyle = C_TYPE;
if (token.modifiers.contains("constructorOrDestructor"))
styles.mainStyle = C_FUNCTION;
} else if (token.type == "comment") { // "comment" means code disabled via the preprocessor
styles.mainStyle = C_DISABLED_CODE;
} else if (token.type == "namespace") {
styles.mainStyle = C_NAMESPACE;
} else if (token.type == "property") {
styles.mainStyle = C_FIELD;
} else if (token.type == "enum") {
styles.mainStyle = C_TYPE;
} else if (token.type == "enumMember") {
styles.mainStyle = C_ENUMERATION;
} else if (token.type == "parameter") {
styles.mainStyle = C_PARAMETER;
} else if (token.type == "macro") {
styles.mainStyle = C_MACRO;
} else if (token.type == "type") {
styles.mainStyle = C_TYPE;
} else if (token.type == "concept") {
styles.mainStyle = C_CONCEPT;
} else if (token.type == "modifier") {
styles.mainStyle = C_KEYWORD;
} else if (token.type == "label") {
styles.mainStyle = C_LABEL;
} else if (token.type == "typeParameter") {
// clangd reports both type and non-type template parameters as type parameters,
// but the latter can be distinguished by the readonly modifier.
styles.mainStyle = token.modifiers.contains(QLatin1String("readonly"))
? C_PARAMETER : C_TYPE;
} else if (token.type == "operator") {
const int pos = Utils::Text::positionInText(&doc, token.line, token.column);
QTC_ASSERT(pos >= 0 || pos < docContents.size(), return HighlightingResult());
const QChar firstChar = docContents.at(pos);
if (firstChar.isLetter())
styles.mainStyle = C_KEYWORD;
else
styles.mainStyle = C_PUNCTUATION;
styles.mixinStyles.push_back(C_OPERATOR);
if (token.modifiers.contains("userDefined"))
styles.mixinStyles.push_back(C_OVERLOADED_OPERATOR);
else if (token.modifiers.contains("declaration")) {
styles.mixinStyles.push_back(C_OVERLOADED_OPERATOR);
styles.mixinStyles.push_back(C_DECLARATION);
}
HighlightingResult result(token.line, token.column, token.length, styles);
if (token.length == 1) {
if (firstChar == '?')
result.kind = CppEditor::SemanticHighlighter::TernaryIf;
else if (firstChar == ':')
result.kind = CppEditor::SemanticHighlighter::TernaryElse;
}
return result;
} else if (token.type == "bracket") {
styles.mainStyle = C_PUNCTUATION;
HighlightingResult result(token.line, token.column, token.length, styles);
const int pos = Utils::Text::positionInText(&doc, token.line, token.column);
QTC_ASSERT(pos >= 0 || pos < docContents.size(), return HighlightingResult());
const char symbol = docContents.at(pos).toLatin1();
QTC_ASSERT(symbol == '<' || symbol == '>', return HighlightingResult());
result.kind = symbol == '<'
? CppEditor::SemanticHighlighter::AngleBracketOpen
: CppEditor::SemanticHighlighter::AngleBracketClose;
return result;
}
if (token.modifiers.contains(QLatin1String("declaration")))
styles.mixinStyles.push_back(C_DECLARATION);
if (token.modifiers.contains(QLatin1String("static"))) {
if (styles.mainStyle == C_FUNCTION) {
styles.mainStyle = C_STATIC_MEMBER;
styles.mixinStyles.push_back(C_FUNCTION);
} else if (styles.mainStyle == C_FIELD) {
styles.mainStyle = C_STATIC_MEMBER;
}
}
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);
};
const auto safeToResult = [&toResult](const ExpandedSemanticToken &token) {
try {
return toResult(token);
} catch (const std::exception &e) {
qWarning() << "caught" << e.what() << "in toResult()";
return HighlightingResult();
}
};
auto results = QtConcurrent::blockingMapped<HighlightingResults>(tokens, safeToResult);
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";
QList<Range> virtualRanges;
for (const HighlightingResult &r : results) {
qCDebug(clangdLogHighlight)
<< '\t' << r.line << r.column << r.length << int(r.textStyles.mainStyle);
if (r.textStyles.mainStyle != C_VIRTUAL_METHOD)
continue;
const Position startPos(r.line - 1, r.column - 1);
virtualRanges << Range(startPos, startPos.withOffset(r.length, &doc));
}
QMetaObject::invokeMethod(LanguageClientManager::instance(),
[filePath, virtualRanges, docRevision] {
if (ClangdClient * const client = ClangModelManagerSupport::clientForFile(filePath))
client->setVirtualRanges(filePath, virtualRanges, docRevision);
}, Qt::QueuedConnection);
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
promise.addResults(results);
#else
for (const HighlightingResult &r : results)
promise.addResult(r);
#endif
}
}
ExtraHighlightingResultsCollector::ExtraHighlightingResultsCollector(
HighlightingResults &results,
const Utils::FilePath &filePath, const QTextDocument *doc,
const QString &docContent)
: m_results(results), m_filePath(filePath), m_doc(doc),
m_docContent(docContent)
{
}
void ExtraHighlightingResultsCollector::collect()
{
for (int i = 0; i < m_results.length(); ++i) {
const HighlightingResult res = m_results.at(i);
if (res.textStyles.mainStyle != TextEditor::C_MACRO || res.length != 10)
continue;
const int pos = Utils::Text::positionInText(m_doc, res.line, res.column);
if (subViewLen(m_docContent, pos, 10) != QLatin1String("Q_PROPERTY"))
continue;
int endPos;
if (i < m_results.length() - 1) {
const HighlightingResult nextRes = m_results.at(i + 1);
endPos = Utils::Text::positionInText(m_doc, nextRes.line, nextRes.column);
} else {
endPos = m_docContent.length();
}
const QString qPropertyString = m_docContent.mid(pos, endPos - pos);
QPropertyHighlighter propHighlighter(m_doc, qPropertyString, pos);
for (const HighlightingResult &newRes : propHighlighter.highlight())
m_results.insert(++i, newRes);
}
}
class InactiveRegionsParams : public JsonObject
{
public:
using JsonObject::JsonObject;
DocumentUri uri() const { return TextDocumentIdentifier(value("textDocument")).uri(); }
QList<Range> inactiveRegions() const {
return array<Range>(LanguageServerProtocol::Key{"regions"});
}
};
class InactiveRegionsNotification : public Notification<InactiveRegionsParams>
{
public:
explicit InactiveRegionsNotification(const InactiveRegionsParams &params)
: Notification(inactiveRegionsMethodName(), params) {}
using Notification::Notification;
};
void handleInactiveRegions(LanguageClient::Client *client, const JsonRpcMessage &msg)
{
const auto params = InactiveRegionsNotification(msg.toJsonObject()).params();
if (!params)
return;
TextDocument * const doc = client->documentForFilePath(
params->uri().toFilePath(client->hostPathMapper()));
if (!doc)
return;
const QList<Range> inactiveRegions = params->inactiveRegions();
QList<BlockRange> ifdefedOutBlocks;
for (const Range &r : inactiveRegions) {
const int startPos = Position(r.start().line(), 0).toPositionInDocument(doc->document());
const int endPos = r.end().toPositionInDocument(doc->document()) + 1;
ifdefedOutBlocks.emplaceBack(startPos, endPos);
}
doc->setIfdefedOutBlocks(ifdefedOutBlocks);
}
QString inactiveRegionsMethodName()
{
return "textDocument/inactiveRegions";
}
} // namespace ClangCodeModel::Internal