diff --git a/src/libs/utils/clangutils.cpp b/src/libs/utils/clangutils.cpp index 4c8c7d801db..86e8e6204e9 100644 --- a/src/libs/utils/clangutils.cpp +++ b/src/libs/utils/clangutils.cpp @@ -65,7 +65,7 @@ bool checkClangdVersion(const FilePath &clangd, QString *error) QVersionNumber minimumClangdVersion() { - return QVersionNumber(14); + return QVersionNumber(17); } } // namespace Utils diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index c6cbd2185da..8f837ba2e87 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -209,12 +209,11 @@ static BaseClientInterface *clientInterface(Project *project, const Utils::FileP "--clang-tidy=0"}}; if (settings.workerThreadLimit() != 0) cmd.addArg("-j=" + QString::number(settings.workerThreadLimit())); - if (indexingEnabled && Utils::clangdVersion(clangdExePath) >= QVersionNumber(15)) { + if (indexingEnabled) { cmd.addArg("--background-index-priority=" + ClangdSettings::priorityToString(indexingPriority)); } - if (Utils::clangdVersion(clangdExePath) >= QVersionNumber(16)) - cmd.addArg("--rename-file-limit=0"); + cmd.addArg("--rename-file-limit=0"); if (!jsonDbDir.isEmpty()) cmd.addArg("--compile-commands-dir=" + clangdExePath.withNewMappedPath(jsonDbDir).path()); if (clangdLogServer().isDebugEnabled()) @@ -558,9 +557,7 @@ void ClangdClient::findUsages(const CppEditor::CursorInEditor &cursor, if (searchTerm.isEmpty()) return; - if (replacement && versionNumber() >= QVersionNumber(16) - && Utils::qtcEnvironmentVariable("QTC_CLANGD_RENAMING") != "0") { - + if (replacement && Utils::qtcEnvironmentVariable("QTC_CLANGD_RENAMING") != "0") { // If we have up-to-date highlighting data, we can prevent giving clangd // macros or namespaces to rename, which it can't cope with. // TODO: Fix this upstream for macros; see https://github.com/clangd/clangd/issues/729. @@ -1523,7 +1520,7 @@ void ClangdClient::Private::handleSemanticTokens(TextDocument *doc, return; } force = force || isTesting; - const auto data = highlightingData.find(doc); + auto data = highlightingData.find(doc); if (data != highlightingData.end()) { if (!force && data->previousTokens.first == tokens && data->previousTokens.second == version) { @@ -1533,61 +1530,50 @@ void ClangdClient::Private::handleSemanticTokens(TextDocument *doc, data->previousTokens.first = tokens; data->previousTokens.second = version; } else { - highlightingData.insert(doc, {{tokens, version}, {}}); + data = highlightingData.insert(doc, {{tokens, version}, {}}); } for (const ExpandedSemanticToken &t : tokens) qCDebug(clangdLogHighlight()) << '\t' << t.line << t.column << t.length << t.type << t.modifiers; - const auto astHandler = [this, tokens, doc, version](const ClangdAstNode &ast, const MessageId &) { - FinalizingSubtaskTimer t(highlightingTimer); - if (!q->documentOpen(doc)) - return; - if (version != q->documentVersion(doc->filePath())) { - qCInfo(clangdLogHighlight) << "AST not up to date; aborting highlighting procedure" - << version << q->documentVersion(doc->filePath()); - return; + FinalizingSubtaskTimer ft(highlightingTimer); + if (!q->documentOpen(doc)) + return; + if (version != q->documentVersion(doc->filePath())) { + qCInfo(clangdLogHighlight) << "AST not up to date; aborting highlighting procedure" + << version << q->documentVersion(doc->filePath()); + return; + } + + const auto runner = [tokens, filePath = doc->filePath(), + text = doc->document()->toPlainText(), + rev = doc->document()->revision(), this] { + try { + return Utils::asyncRun(doSemanticHighlighting, filePath, tokens, text, + rev, highlightingTimer); + } catch (const std::exception &e) { + qWarning() << "caught" << e.what() << "in main highlighting thread"; + return QFuture(); } - if (clangdLogAst().isDebugEnabled()) - ast.print(); - - const auto runner = [tokens, filePath = doc->filePath(), - text = doc->document()->toPlainText(), ast, - doc = QPointer(doc), rev = doc->document()->revision(), - clangdVersion = q->versionNumber(), - this] { - try { - return Utils::asyncRun(doSemanticHighlighting, filePath, tokens, text, ast, doc, - rev, clangdVersion, highlightingTimer); - } catch (const std::exception &e) { - qWarning() << "caught" << e.what() << "in main highlighting thread"; - return QFuture(); - } - }; - - if (isTesting) { - const auto watcher = new QFutureWatcher(q); - connect(watcher, &QFutureWatcher::finished, - q, [this, watcher, fp = doc->filePath()] { - emit q->highlightingResultsReady(watcher->future().results(), fp); - watcher->deleteLater(); - }); - watcher->setFuture(runner()); - return; - } - - auto &data = highlightingData[doc]; - if (!data.highlighter) - data.highlighter = new CppEditor::SemanticHighlighter(doc); - else - data.highlighter->updateFormatMapFromFontSettings(); - data.highlighter->setHighlightingRunner(runner); - data.highlighter->run(); }; - if (q->versionNumber().majorVersion() >= 17) - astHandler({}, {}); + + if (isTesting) { + const auto watcher = new QFutureWatcher(q); + connect(watcher, &QFutureWatcher::finished, + q, [this, watcher, fp = doc->filePath()] { + emit q->highlightingResultsReady(watcher->future().results(), fp); + watcher->deleteLater(); + }); + watcher->setFuture(runner()); + return; + } + + if (!data->highlighter) + data->highlighter = new CppEditor::SemanticHighlighter(doc); else - getAndHandleAst(doc, astHandler, AstCallbackMode::SyncIfPossible); + data->highlighter->updateFormatMapFromFontSettings(); + data->highlighter->setHighlightingRunner(runner); + data->highlighter->run(); } std::optional > ClangdDiagnostic::codeActions() const diff --git a/src/plugins/clangcodemodel/clangdfollowsymbol.cpp b/src/plugins/clangcodemodel/clangdfollowsymbol.cpp index 0da02cba330..2bedda4d986 100644 --- a/src/plugins/clangcodemodel/clangdfollowsymbol.cpp +++ b/src/plugins/clangcodemodel/clangdfollowsymbol.cpp @@ -459,8 +459,6 @@ void ClangdFollowSymbol::Private::handleGotoImplementationResult( // Make a symbol info request for each link to get the class names. // Also get the AST for the base declaration, so we can find out whether it's // pure virtual and mark it accordingly. - // In addition, we need to follow all override links, because for these, clangd - // gives us the declaration instead of the definition (until clangd 16). for (const Link &link : std::as_const(allLinks)) { if (!client->documentForFilePath(link.targetFilePath) && addOpenFile(link.targetFilePath)) client->openExtraFile(link.targetFilePath); @@ -487,42 +485,6 @@ void ClangdFollowSymbol::Private::handleGotoImplementationResult( if (link == defLink) continue; - - if (client->versionNumber().majorVersion() >= 17) - continue; - - const TextDocumentIdentifier doc(client->hostPathToServerUri(link.targetFilePath)); - const TextDocumentPositionParams params(doc, pos); - GotoDefinitionRequest defReq(params); - defReq.setResponseCallback( - [this, link, transformLink, sentinel = QPointer(q), reqId = defReq.id()]( - const GotoDefinitionRequest::Response &response) { - qCDebug(clangdLog) << "handling additional go to definition reply for" - << link.targetFilePath << link.targetLine; - if (!sentinel) - return; - Link newLink; - if (std::optional _result = response.result()) { - const GotoResult result = _result.value(); - if (const auto ploc = std::get_if(&result)) { - newLink = transformLink(*ploc); - } else if (const auto plloc = std::get_if>(&result)) { - if (!plloc->isEmpty()) - newLink = transformLink(plloc->value(0)); - } - } - qCDebug(clangdLog) << "def link is" << newLink.targetFilePath << newLink.targetLine; - declDefMap.insert(link, newLink); - pendingGotoDefRequests.removeOne(reqId); - if (pendingSymbolInfoRequests.isEmpty() && pendingGotoDefRequests.isEmpty() - && defLinkNode.isValid()) { - handleDocumentInfoResults(); - } - }); - pendingGotoDefRequests << defReq.id(); - qCDebug(clangdLog) << "sending additional go to definition request" - << link.targetFilePath << link.targetLine; - client->sendMessage(defReq, ClangdClient::SendDocUpdates::Ignore); } const FilePath defLinkFilePath = defLink.targetFilePath; diff --git a/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp b/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp index c5bc3d72354..2b52ee4d143 100644 --- a/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp +++ b/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp @@ -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 cleanupDisabledCode(HighlightingResults &results, const QTextDocument *doc, - const QString &docContent) -{ - QList 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 &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 &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 &tokens, const QString &docContents, - const ClangdAstNode &ast, - const QPointer &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 children = it->children().value_or(QList()); - 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 children = it->children().value_or(QList()); - - // 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 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()); - } - 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 children = it->children().value_or(QList()); - 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 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(tokens, safeToResult); - const bool handleInactiveCode = clangdMajorVersion < 17; - QList 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 virtualRanges; for (const HighlightingResult &r : results) { qCDebug(clangdLogHighlight) @@ -440,11 +197,11 @@ void doSemanticHighlighting( } ExtraHighlightingResultsCollector::ExtraHighlightingResultsCollector( - QPromise &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 children = node.children().value_or(QList()); - - // 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 diff --git a/src/plugins/clangcodemodel/clangdsemantichighlighting.h b/src/plugins/clangcodemodel/clangdsemantichighlighting.h index 285ba2323e7..b303f920f38 100644 --- a/src/plugins/clangcodemodel/clangdsemantichighlighting.h +++ b/src/plugins/clangcodemodel/clangdsemantichighlighting.h @@ -33,10 +33,7 @@ void doSemanticHighlighting( const Utils::FilePath &filePath, const QList &tokens, const QString &docContents, - const ClangdAstNode &ast, - const QPointer &textDocument, int docRevision, - const QVersionNumber &clangdVersion, const TaskTimer &taskTimer ); diff --git a/src/plugins/clangcodemodel/test/clangdtests.cpp b/src/plugins/clangcodemodel/test/clangdtests.cpp index f18c1641559..84a155b5353 100644 --- a/src/plugins/clangcodemodel/test/clangdtests.cpp +++ b/src/plugins/clangcodemodel/test/clangdtests.cpp @@ -1107,8 +1107,7 @@ void ClangdTestHighlighting::test_data() QTest::newRow("call to function pointer alias") << 344 << 5 << 344 << 13 << QList{C_TYPE} << 0; QTest::newRow("friend class declaration") << 350 << 18 << 350 << 27 - << (client()->versionNumber().majorVersion() >= 16 - ? QList{C_TYPE, C_DECLARATION}: QList{C_TYPE}) << 0; + << QList{C_TYPE, C_DECLARATION} << 0; QTest::newRow("friend class reference") << 351 << 34 << 351 << 43 << QList{C_TYPE} << 0; QTest::newRow("function parameter of friend class type") << 351 << 45 << 351 << 50 @@ -1374,10 +1373,6 @@ void ClangdTestHighlighting::test_data() << QList{C_PUNCTUATION} << int(CppEditor::SemanticHighlighter::AngleBracketClose); QTest::newRow("macro in struct") << 795 << 9 << 795 << 14 << QList{C_MACRO, C_DECLARATION} << 0; - if (client()->versionNumber() < QVersionNumber(17)) { - QTest::newRow("#ifdef'ed out code") << 800 << 1 << 800 << 17 - << QList{C_DISABLED_CODE} << 0; - } QTest::newRow("static function call (object)") << 819 << 5 << 819 << 6 << QList{C_LOCAL} << 0; QTest::newRow("static function call (argument)") << 819 << 18 << 819 << 19