|
|
|
@@ -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
|
|
|
|
|