forked from qt-creator/qt-creator
ClangCodeModel: Move highlighting code into its own set of files
No functional changes. Change-Id: If6e5da7e79bf39e564f0f38520ae088f76543642 Reviewed-by: David Schulz <david.schulz@qt.io> Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
@@ -15,11 +15,12 @@ add_qtc_plugin(ClangCodeModel
|
||||
clangcodemodelplugin.cpp clangcodemodelplugin.h
|
||||
clangcompletioncontextanalyzer.cpp clangcompletioncontextanalyzer.h
|
||||
clangconstants.h
|
||||
clangdclient.cpp clangdclient.h
|
||||
clangdast.cpp clangdast.h
|
||||
clangdclient.cpp clangdclient.h
|
||||
clangdiagnostictooltipwidget.cpp clangdiagnostictooltipwidget.h
|
||||
clangdquickfixfactory.cpp clangdquickfixfactory.h
|
||||
clangdqpropertyhighlighter.cpp clangdqpropertyhighlighter.h
|
||||
clangdsemantichighlighting.cpp clangdsemantichighlighting.h
|
||||
clangeditordocumentprocessor.cpp clangeditordocumentprocessor.h
|
||||
clangfixitoperation.cpp clangfixitoperation.h
|
||||
clangdlocatorfilters.cpp clangdlocatorfilters.h
|
||||
@@ -28,6 +29,7 @@ add_qtc_plugin(ClangCodeModel
|
||||
clangtextmark.cpp clangtextmark.h
|
||||
clanguiheaderondiskmanager.cpp clanguiheaderondiskmanager.h
|
||||
clangutils.cpp clangutils.h
|
||||
tasktimers.cpp tasktimers.h
|
||||
EXPLICIT_MOC clangcodemodelplugin.h
|
||||
)
|
||||
|
||||
|
@@ -42,6 +42,8 @@ QtcPlugin {
|
||||
"clangdqpropertyhighlighter.h",
|
||||
"clangdquickfixfactory.cpp",
|
||||
"clangdquickfixfactory.h",
|
||||
"clangdsemantichighlighting.cpp",
|
||||
"clangdsemantichighlighting.h",
|
||||
"clangeditordocumentprocessor.cpp",
|
||||
"clangeditordocumentprocessor.h",
|
||||
"clangfixitoperation.cpp",
|
||||
@@ -56,6 +58,8 @@ QtcPlugin {
|
||||
"clanguiheaderondiskmanager.h",
|
||||
"clangutils.cpp",
|
||||
"clangutils.h",
|
||||
"tasktimers.cpp",
|
||||
"tasktimers.h",
|
||||
]
|
||||
|
||||
Group {
|
||||
|
File diff suppressed because it is too large
Load Diff
916
src/plugins/clangcodemodel/clangdsemantichighlighting.cpp
Normal file
916
src/plugins/clangcodemodel/clangdsemantichighlighting.cpp
Normal file
@@ -0,0 +1,916 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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 "clangdsemantichighlighting.h"
|
||||
|
||||
#include "clangdast.h"
|
||||
#include "clangdclient.h"
|
||||
#include "clangdqpropertyhighlighter.h"
|
||||
#include "clangmodelmanagersupport.h"
|
||||
#include "tasktimers.h"
|
||||
|
||||
#include <cppeditor/semantichighlighter.h>
|
||||
#include <languageclient/semantichighlightsupport.h>
|
||||
#include <languageserverprotocol/lsptypes.h>
|
||||
#include <texteditor/blockrange.h>
|
||||
#include <texteditor/textstyles.h>
|
||||
|
||||
#include <QtConcurrent>
|
||||
#include <QTextDocument>
|
||||
|
||||
using namespace LanguageClient;
|
||||
using namespace LanguageServerProtocol;
|
||||
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 : qAsConst(ifdefedOutRanges))
|
||||
qCDebug(clangdLogHighlight) << r.first() << r.last();
|
||||
}
|
||||
|
||||
return ifdefedOutRanges;
|
||||
}
|
||||
|
||||
class ExtraHighlightingResultsCollector
|
||||
{
|
||||
public:
|
||||
ExtraHighlightingResultsCollector(QFutureInterface<HighlightingResult> &future,
|
||||
HighlightingResults &results,
|
||||
const Utils::FilePath &filePath, const ClangdAstNode &ast,
|
||||
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);
|
||||
|
||||
QFutureInterface<HighlightingResult> &m_future;
|
||||
HighlightingResults &m_results;
|
||||
const Utils::FilePath m_filePath;
|
||||
const ClangdAstNode &m_ast;
|
||||
const QTextDocument * const m_doc;
|
||||
const QString &m_docContent;
|
||||
ClangdAstNode::FileStatus m_currentFileStatus = ClangdAstNode::FileStatus::Unknown;
|
||||
};
|
||||
|
||||
void doSemanticHighlighting(
|
||||
QFutureInterface<HighlightingResult> &future,
|
||||
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);
|
||||
if (future.isCanceled()) {
|
||||
future.reportFinished();
|
||||
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 auto isOutputParameter = [&ast, &tokenRange](const ExpandedSemanticToken &token) {
|
||||
if (token.modifiers.contains(QLatin1String("usedAsMutableReference")))
|
||||
return true;
|
||||
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
|
||||
= [&ast, &isOutputParameter, &clangdVersion, &tokenRange]
|
||||
(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 (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") {
|
||||
if (clangdVersion < QVersionNumber(14)
|
||||
&& declNode.arcanaContains("' virtual")) {
|
||||
styles.mainStyle = C_VIRTUAL_METHOD;
|
||||
}
|
||||
if (declNode.hasChildWithRole("statement"))
|
||||
styles.mixinStyles.push_back(C_FUNCTION_DEFINITION);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (token.type == "class") {
|
||||
styles.mainStyle = C_TYPE;
|
||||
|
||||
// clang hardly ever differentiates between constructors and the associated class,
|
||||
// whereas we highlight constructors as functions.
|
||||
if (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") {
|
||||
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_PREPROCESSOR;
|
||||
} else if (token.type == "type") {
|
||||
styles.mainStyle = C_TYPE;
|
||||
} 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;
|
||||
}
|
||||
if (token.modifiers.contains(QLatin1String("declaration")))
|
||||
styles.mixinStyles.push_back(C_DECLARATION);
|
||||
if (token.modifiers.contains(QLatin1String("static"))) {
|
||||
if (styles.mainStyle != C_FIELD && styles.mainStyle != C_TEXT)
|
||||
styles.mixinStyles.push_back(styles.mainStyle);
|
||||
styles.mainStyle = C_STATIC_MEMBER;
|
||||
}
|
||||
if (isOutputParameter(token))
|
||||
styles.mixinStyles.push_back(C_OUTPUT_ARGUMENT);
|
||||
qCDebug(clangdLogHighlight) << "adding highlighting result"
|
||||
<< token.line << token.column << token.length << int(styles.mainStyle);
|
||||
return HighlightingResult(token.line, token.column, token.length, styles);
|
||||
};
|
||||
|
||||
auto results = QtConcurrent::blockingMapped<HighlightingResults>(tokens, toResult);
|
||||
const QList<BlockRange> ifdefedOutBlocks = cleanupDisabledCode(results, &doc, docContents);
|
||||
ExtraHighlightingResultsCollector(future, results, filePath, ast, &doc, docContents).collect();
|
||||
if (!future.isCanceled()) {
|
||||
qCInfo(clangdLogHighlight) << "reporting" << results.size() << "highlighting results";
|
||||
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) {
|
||||
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(ClangModelManagerSupport::instance(),
|
||||
[filePath, virtualRanges, docRevision] {
|
||||
if (ClangdClient * const client
|
||||
= ClangModelManagerSupport::instance()->clientForFile(filePath)) {
|
||||
client->setVirtualRanges(filePath, virtualRanges, docRevision);
|
||||
}
|
||||
}, Qt::QueuedConnection);
|
||||
future.reportResults(QVector<HighlightingResult>(results.cbegin(), results.cend()));
|
||||
}
|
||||
future.reportFinished();
|
||||
}
|
||||
|
||||
ExtraHighlightingResultsCollector::ExtraHighlightingResultsCollector(
|
||||
QFutureInterface<HighlightingResult> &future, HighlightingResults &results,
|
||||
const Utils::FilePath &filePath, const ClangdAstNode &ast, const QTextDocument *doc,
|
||||
const QString &docContent)
|
||||
: m_future(future), m_results(results), m_filePath(filePath), m_ast(ast), 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 != C_PREPROCESSOR || 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);
|
||||
}
|
||||
|
||||
if (!m_ast.isValid())
|
||||
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_PREPROCESSOR) {
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(clangdLogHighlight) << "adding additional highlighting result"
|
||||
<< result.line << result.column << result.length;
|
||||
m_results.insert(it, result);
|
||||
return;
|
||||
}
|
||||
|
||||
// This is for conversion operators, whose type part is only reported as a type by clangd.
|
||||
if ((it->textStyles.mainStyle == C_TYPE
|
||||
|| it->textStyles.mainStyle == C_PRIMITIVE_TYPE)
|
||||
&& !result.textStyles.mixinStyles.empty()
|
||||
&& result.textStyles.mixinStyles.at(0) == C_OPERATOR) {
|
||||
it->textStyles.mixinStyles = result.textStyles.mixinStyles;
|
||||
}
|
||||
}
|
||||
|
||||
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.length = 1;
|
||||
result.kind = CppEditor::SemanticHighlighter::AngleBracketOpen;
|
||||
insertResult(result);
|
||||
Utils::Text::convertPosition(m_doc, absClosingAngleBracketPos, &result.line, &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() == "UserDefinedLiteral")
|
||||
return;
|
||||
if (node.kind().endsWith("Literal")) {
|
||||
const bool isKeyword = node.kind() == "CXXBoolLiteral"
|
||||
|| node.kind() == "CXXNullPtrLiteral";
|
||||
const bool isStringLike = !isKeyword && (node.kind().startsWith("String")
|
||||
|| node.kind().startsWith("Character"));
|
||||
const TextStyle style = isKeyword ? C_KEYWORD : isStringLike ? C_STRING : C_NUMBER;
|
||||
insertResult(node, style);
|
||||
return;
|
||||
}
|
||||
if (node.role() == "type" && node.kind() == "Builtin") {
|
||||
insertResult(node, C_PRIMITIVE_TYPE);
|
||||
return;
|
||||
}
|
||||
if (node.role() == "attribute" && (node.kind() == "Override" || node.kind() == "Final")) {
|
||||
insertResult(node, C_KEYWORD);
|
||||
return;
|
||||
}
|
||||
|
||||
const bool isExpression = node.role() == "expression";
|
||||
if (isExpression && node.kind() == "Predefined") {
|
||||
insertResult(node, C_PREPROCESSOR);
|
||||
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 (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.length = 1;
|
||||
result.kind = CppEditor::SemanticHighlighter::TernaryIf;
|
||||
insertResult(result);
|
||||
Utils::Text::convertPosition(m_doc, absColonPos, &result.line, &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;
|
||||
|
||||
// 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());
|
||||
|
||||
HighlightingResult result;
|
||||
result.useTextSyles = true;
|
||||
const bool isConversionOp = node.kind() == "CXXConversion";
|
||||
const bool isOverloaded = !isConversionOp
|
||||
&& (isDeclaration || ((!isCallToNew && !isCallToDelete)
|
||||
|| node.arcanaContains("CXXMethod")));
|
||||
result.textStyles.mainStyle = isConversionOp
|
||||
? C_PRIMITIVE_TYPE
|
||||
: 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);
|
||||
insertResult(result);
|
||||
Utils::Text::convertPosition(m_doc,
|
||||
nodeStartPos + closingBracketOffset,
|
||||
&result.line, &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.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);
|
||||
insertResult(result);
|
||||
Utils::Text::convertPosition(m_doc, nodeStartPos + closingParenOffset,
|
||||
&result.line, &result.column);
|
||||
insertResult(result);
|
||||
}
|
||||
|
||||
void ExtraHighlightingResultsCollector::visitNode(const ClangdAstNode &node)
|
||||
{
|
||||
if (m_future.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;
|
||||
}
|
||||
|
||||
} // namespace ClangCodeModel::Internal
|
57
src/plugins/clangcodemodel/clangdsemantichighlighting.h
Normal file
57
src/plugins/clangcodemodel/clangdsemantichighlighting.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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 <QFutureInterface>
|
||||
#include <QLoggingCategory>
|
||||
#include <QPointer>
|
||||
#include <QVersionNumber>
|
||||
|
||||
namespace LanguageClient { class ExpandedSemanticToken; }
|
||||
namespace TextEditor {
|
||||
class HighlightingResult;
|
||||
class TextDocument;
|
||||
}
|
||||
namespace Utils { class FilePath; }
|
||||
|
||||
namespace ClangCodeModel::Internal {
|
||||
class ClangdAstNode;
|
||||
class TaskTimer;
|
||||
Q_DECLARE_LOGGING_CATEGORY(clangdLogHighlight);
|
||||
|
||||
void doSemanticHighlighting(
|
||||
QFutureInterface<TextEditor::HighlightingResult> &future,
|
||||
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
|
||||
);
|
||||
|
||||
} // namespace ClangCodeModel::Internal
|
108
src/plugins/clangcodemodel/tasktimers.cpp
Normal file
108
src/plugins/clangcodemodel/tasktimers.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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 "tasktimers.h"
|
||||
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
namespace ClangCodeModel::Internal {
|
||||
|
||||
Q_LOGGING_CATEGORY(clangdLogTiming, "qtc.clangcodemodel.clangd.timing", QtWarningMsg);
|
||||
|
||||
void ClangCodeModel::Internal::TaskTimer::stopTask()
|
||||
{
|
||||
// This can happen due to the RAII mechanism employed with SubtaskTimer.
|
||||
// The subtask timers will expire immediately after, so this does not distort
|
||||
// the timing data.
|
||||
if (m_subtasks > 0) {
|
||||
QTC_CHECK(m_timer.isValid());
|
||||
m_elapsedMs += m_timer.elapsed();
|
||||
m_timer.invalidate();
|
||||
m_subtasks = 0;
|
||||
}
|
||||
m_started = false;
|
||||
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": took " << m_elapsedMs
|
||||
<< " ms in UI thread";
|
||||
m_elapsedMs = 0;
|
||||
}
|
||||
|
||||
void TaskTimer::startSubtask()
|
||||
{
|
||||
// We have some callbacks that are either synchronous or asynchronous, depending on
|
||||
// dynamic conditions. In the sync case, we will have nested subtasks, in which case
|
||||
// the inner ones must not collect timing data, as their code blocks are already covered.
|
||||
if (++m_subtasks > 1)
|
||||
return;
|
||||
if (!m_started) {
|
||||
QTC_ASSERT(m_elapsedMs == 0, m_elapsedMs = 0);
|
||||
m_started = true;
|
||||
m_finalized = false;
|
||||
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": starting";
|
||||
|
||||
// Used by ThreadedSubtaskTimer to mark the end of the whole highlighting operation
|
||||
m_startTimer.restart();
|
||||
}
|
||||
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": subtask started at "
|
||||
<< QDateTime::currentDateTime().time().toString("hh:mm:ss.zzz");
|
||||
QTC_CHECK(!m_timer.isValid());
|
||||
m_timer.start();
|
||||
}
|
||||
|
||||
void TaskTimer::stopSubtask(bool isFinalizing)
|
||||
{
|
||||
if (m_subtasks == 0) // See stopTask().
|
||||
return;
|
||||
if (isFinalizing)
|
||||
m_finalized = true;
|
||||
if (--m_subtasks > 0) // See startSubtask().
|
||||
return;
|
||||
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": subtask stopped at "
|
||||
<< QDateTime::currentDateTime().time().toString("hh:mm:ss.zzz");
|
||||
QTC_CHECK(m_timer.isValid());
|
||||
m_elapsedMs += m_timer.elapsed();
|
||||
m_timer.invalidate();
|
||||
if (m_finalized)
|
||||
stopTask();
|
||||
}
|
||||
|
||||
ThreadedSubtaskTimer::ThreadedSubtaskTimer(const QString &task, const TaskTimer &taskTimer)
|
||||
: m_task(task), m_taskTimer(taskTimer)
|
||||
{
|
||||
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": starting thread";
|
||||
m_timer.start();
|
||||
}
|
||||
|
||||
ThreadedSubtaskTimer::~ThreadedSubtaskTimer()
|
||||
{
|
||||
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": took " << m_timer.elapsed()
|
||||
<< " ms in dedicated thread";
|
||||
|
||||
qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": Start to end: "
|
||||
<< m_taskTimer.startTimer().elapsed() << " ms";
|
||||
}
|
||||
|
||||
} // namespace ClangCodeModel::Internal
|
90
src/plugins/clangcodemodel/tasktimers.h
Normal file
90
src/plugins/clangcodemodel/tasktimers.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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 <QElapsedTimer>
|
||||
#include <QLoggingCategory>
|
||||
#include <QString>
|
||||
|
||||
namespace ClangCodeModel::Internal {
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(clangdLogTiming);
|
||||
|
||||
// TODO: Generalize, document interface and move to Utils?
|
||||
class TaskTimer
|
||||
{
|
||||
public:
|
||||
TaskTimer(const QString &task) : m_task(task) {}
|
||||
|
||||
void stopTask();
|
||||
void startSubtask();
|
||||
void stopSubtask(bool isFinalizing);
|
||||
|
||||
QElapsedTimer startTimer() const { return m_startTimer; }
|
||||
|
||||
private:
|
||||
const QString m_task;
|
||||
QElapsedTimer m_timer;
|
||||
QElapsedTimer m_startTimer;
|
||||
qint64 m_elapsedMs = 0;
|
||||
int m_subtasks = 0;
|
||||
bool m_started = false;
|
||||
bool m_finalized = false;
|
||||
};
|
||||
|
||||
class SubtaskTimer
|
||||
{
|
||||
public:
|
||||
SubtaskTimer(TaskTimer &timer) : m_timer(timer) { m_timer.startSubtask(); }
|
||||
~SubtaskTimer() { m_timer.stopSubtask(m_isFinalizing); }
|
||||
|
||||
protected:
|
||||
void makeFinalizing() { m_isFinalizing = true; }
|
||||
|
||||
private:
|
||||
TaskTimer &m_timer;
|
||||
bool m_isFinalizing = false;
|
||||
};
|
||||
|
||||
class FinalizingSubtaskTimer : public SubtaskTimer
|
||||
{
|
||||
public:
|
||||
FinalizingSubtaskTimer(TaskTimer &timer) : SubtaskTimer(timer) { makeFinalizing(); }
|
||||
};
|
||||
|
||||
class ThreadedSubtaskTimer
|
||||
{
|
||||
public:
|
||||
ThreadedSubtaskTimer(const QString &task, const TaskTimer &taskTimer);
|
||||
~ThreadedSubtaskTimer();
|
||||
|
||||
private:
|
||||
const QString m_task;
|
||||
QElapsedTimer m_timer;
|
||||
const TaskTimer &m_taskTimer;
|
||||
};
|
||||
|
||||
} // namespace ClangCodeModel::Internal
|
Reference in New Issue
Block a user