2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2022 The Qt Company Ltd.
|
2022-12-21 10:12:09 +01:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2022-05-30 13:39:08 +02:00
|
|
|
|
|
|
|
#include "clangdsemantichighlighting.h"
|
|
|
|
|
|
|
|
#include "clangdast.h"
|
|
|
|
#include "clangdclient.h"
|
|
|
|
#include "clangdqpropertyhighlighter.h"
|
|
|
|
#include "clangmodelmanagersupport.h"
|
|
|
|
#include "tasktimers.h"
|
|
|
|
|
|
|
|
#include <cppeditor/semantichighlighter.h>
|
2022-08-02 16:15:38 +02:00
|
|
|
#include <languageclient/languageclientmanager.h>
|
2022-05-30 13:39:08 +02:00
|
|
|
#include <languageclient/semantichighlightsupport.h>
|
|
|
|
#include <languageserverprotocol/lsptypes.h>
|
|
|
|
#include <texteditor/blockrange.h>
|
|
|
|
#include <texteditor/textstyles.h>
|
|
|
|
|
|
|
|
#include <QtConcurrent>
|
|
|
|
#include <QTextDocument>
|
|
|
|
|
2022-09-26 16:00:49 +02:00
|
|
|
#include <new>
|
|
|
|
|
2022-05-30 13:39:08 +02:00
|
|
|
using namespace LanguageClient;
|
|
|
|
using namespace LanguageServerProtocol;
|
|
|
|
using namespace TextEditor;
|
|
|
|
|
|
|
|
namespace ClangCodeModel::Internal {
|
|
|
|
Q_LOGGING_CATEGORY(clangdLogHighlight, "qtc.clangcodemodel.clangd.highlight", QtWarningMsg);
|
|
|
|
|
|
|
|
class ExtraHighlightingResultsCollector
|
|
|
|
{
|
|
|
|
public:
|
2024-05-22 15:00:50 +02:00
|
|
|
ExtraHighlightingResultsCollector(HighlightingResults &results,
|
|
|
|
const Utils::FilePath &filePath,
|
|
|
|
const QTextDocument *doc, const QString &docContent);
|
2022-05-30 13:39:08 +02:00
|
|
|
|
|
|
|
void collect();
|
|
|
|
private:
|
|
|
|
HighlightingResults &m_results;
|
|
|
|
const Utils::FilePath m_filePath;
|
|
|
|
const QTextDocument * const m_doc;
|
|
|
|
const QString &m_docContent;
|
|
|
|
};
|
|
|
|
|
|
|
|
void doSemanticHighlighting(
|
2023-03-07 15:44:01 +01:00
|
|
|
QPromise<HighlightingResult> &promise,
|
2022-05-30 13:39:08 +02:00
|
|
|
const Utils::FilePath &filePath,
|
|
|
|
const QList<ExpandedSemanticToken> &tokens,
|
|
|
|
const QString &docContents,
|
|
|
|
int docRevision,
|
|
|
|
const TaskTimer &taskTimer)
|
|
|
|
{
|
|
|
|
ThreadedSubtaskTimer t("highlighting", taskTimer);
|
2023-03-07 15:44:01 +01:00
|
|
|
if (promise.isCanceled())
|
2022-05-30 13:39:08 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
const QTextDocument doc(docContents);
|
|
|
|
|
|
|
|
const std::function<HighlightingResult(const ExpandedSemanticToken &)> toResult
|
2022-07-20 18:04:02 +02:00
|
|
|
= [&](const ExpandedSemanticToken &token) {
|
2022-05-30 13:39:08 +02:00
|
|
|
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;
|
2024-05-22 15:00:50 +02:00
|
|
|
if (token.modifiers.contains("definition"))
|
2022-06-09 10:49:37 +02:00
|
|
|
styles.mixinStyles.push_back(C_FUNCTION_DEFINITION);
|
2022-05-30 13:39:08 +02:00
|
|
|
} else if (token.type == "class") {
|
|
|
|
styles.mainStyle = C_TYPE;
|
2024-05-22 15:00:50 +02:00
|
|
|
if (token.modifiers.contains("constructorOrDestructor"))
|
2022-08-04 15:17:26 +02:00
|
|
|
styles.mainStyle = C_FUNCTION;
|
2022-05-30 13:39:08 +02:00
|
|
|
} 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") {
|
2022-06-17 11:06:10 +02:00
|
|
|
styles.mainStyle = C_MACRO;
|
2023-06-16 16:59:13 +02:00
|
|
|
} else if (token.type == "type") {
|
2022-05-30 13:39:08 +02:00
|
|
|
styles.mainStyle = C_TYPE;
|
2023-06-16 16:59:13 +02:00
|
|
|
} else if (token.type == "concept") {
|
|
|
|
styles.mainStyle = C_CONCEPT;
|
2022-11-14 15:12:48 +01:00
|
|
|
} else if (token.type == "modifier") {
|
|
|
|
styles.mainStyle = C_KEYWORD;
|
2023-02-06 15:12:43 +01:00
|
|
|
} else if (token.type == "label") {
|
|
|
|
styles.mainStyle = C_LABEL;
|
2022-05-30 13:39:08 +02:00
|
|
|
} 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;
|
2022-07-20 18:04:02 +02:00
|
|
|
} 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;
|
2022-07-19 15:00:07 +02:00
|
|
|
} 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;
|
2022-05-30 13:39:08 +02:00
|
|
|
}
|
|
|
|
if (token.modifiers.contains(QLatin1String("declaration")))
|
|
|
|
styles.mixinStyles.push_back(C_DECLARATION);
|
|
|
|
if (token.modifiers.contains(QLatin1String("static"))) {
|
2022-08-18 17:48:23 +02:00
|
|
|
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;
|
|
|
|
}
|
2022-05-30 13:39:08 +02:00
|
|
|
}
|
2024-05-22 15:00:50 +02:00
|
|
|
if (token.modifiers.contains(QLatin1String("usedAsMutableReference"))
|
|
|
|
|| token.modifiers.contains(QLatin1String("usedAsMutablePointer"))) {
|
2022-05-30 13:39:08 +02:00
|
|
|
styles.mixinStyles.push_back(C_OUTPUT_ARGUMENT);
|
2024-05-22 15:00:50 +02:00
|
|
|
}
|
2022-05-30 13:39:08 +02:00
|
|
|
return HighlightingResult(token.line, token.column, token.length, styles);
|
|
|
|
};
|
|
|
|
|
2022-09-26 16:00:49 +02:00
|
|
|
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);
|
2024-05-22 15:00:50 +02:00
|
|
|
ExtraHighlightingResultsCollector(results, filePath, &doc, docContents).collect();
|
2023-01-04 10:18:59 +01:00
|
|
|
Utils::erase(results, [](const HighlightingResult &res) {
|
|
|
|
// QTCREATORBUG-28639
|
|
|
|
return res.textStyles.mainStyle == C_TEXT && res.textStyles.mixinStyles.empty();
|
|
|
|
});
|
2023-03-07 15:44:01 +01:00
|
|
|
if (!promise.isCanceled()) {
|
2022-05-30 13:39:08 +02:00
|
|
|
qCInfo(clangdLogHighlight) << "reporting" << results.size() << "highlighting results";
|
|
|
|
QList<Range> virtualRanges;
|
|
|
|
for (const HighlightingResult &r : results) {
|
2024-02-29 13:41:45 +01:00
|
|
|
qCDebug(clangdLogHighlight)
|
|
|
|
<< '\t' << r.line << r.column << r.length << int(r.textStyles.mainStyle);
|
2022-05-30 13:39:08 +02:00
|
|
|
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));
|
|
|
|
}
|
2022-08-02 16:15:38 +02:00
|
|
|
QMetaObject::invokeMethod(LanguageClientManager::instance(),
|
2022-05-30 13:39:08 +02:00
|
|
|
[filePath, virtualRanges, docRevision] {
|
2022-08-01 18:07:41 +02:00
|
|
|
if (ClangdClient * const client = ClangModelManagerSupport::clientForFile(filePath))
|
2022-05-30 13:39:08 +02:00
|
|
|
client->setVirtualRanges(filePath, virtualRanges, docRevision);
|
|
|
|
}, Qt::QueuedConnection);
|
2023-06-01 14:42:15 +02:00
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
|
|
|
promise.addResults(results);
|
|
|
|
#else
|
2023-03-07 15:44:01 +01:00
|
|
|
for (const HighlightingResult &r : results)
|
|
|
|
promise.addResult(r);
|
2023-06-01 14:42:15 +02:00
|
|
|
#endif
|
2022-05-30 13:39:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ExtraHighlightingResultsCollector::ExtraHighlightingResultsCollector(
|
2024-05-22 15:00:50 +02:00
|
|
|
HighlightingResults &results,
|
|
|
|
const Utils::FilePath &filePath, const QTextDocument *doc,
|
|
|
|
const QString &docContent)
|
|
|
|
: m_results(results), m_filePath(filePath), m_doc(doc),
|
|
|
|
m_docContent(docContent)
|
2022-05-30 13:39:08 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExtraHighlightingResultsCollector::collect()
|
|
|
|
{
|
|
|
|
for (int i = 0; i < m_results.length(); ++i) {
|
|
|
|
const HighlightingResult res = m_results.at(i);
|
2022-06-17 11:06:10 +02:00
|
|
|
if (res.textStyles.mainStyle != TextEditor::C_MACRO || res.length != 10)
|
2022-05-30 13:39:08 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-07 15:45:56 +02:00
|
|
|
class InactiveRegionsParams : public JsonObject
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
using JsonObject::JsonObject;
|
|
|
|
|
2023-08-11 17:30:01 +02:00
|
|
|
DocumentUri uri() const { return TextDocumentIdentifier(value("textDocument")).uri(); }
|
2023-12-12 12:58:45 +01:00
|
|
|
QList<Range> inactiveRegions() const {
|
|
|
|
return array<Range>(LanguageServerProtocol::Key{"regions"});
|
|
|
|
}
|
2023-06-07 15:45:56 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
class InactiveRegionsNotification : public Notification<InactiveRegionsParams>
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
explicit InactiveRegionsNotification(const InactiveRegionsParams ¶ms)
|
|
|
|
: 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 = r.start().toPositionInDocument(doc->document());
|
|
|
|
const int endPos = r.end().toPositionInDocument(doc->document()) + 1;
|
|
|
|
ifdefedOutBlocks.emplaceBack(startPos, endPos);
|
|
|
|
}
|
|
|
|
doc->setIfdefedOutBlocks(ifdefedOutBlocks);
|
|
|
|
}
|
|
|
|
|
|
|
|
QString inactiveRegionsMethodName()
|
|
|
|
{
|
|
|
|
return "textDocument/inactiveRegions";
|
|
|
|
}
|
|
|
|
|
2022-05-30 13:39:08 +02:00
|
|
|
} // namespace ClangCodeModel::Internal
|