From bbbae099a741ed1eaf305cae72452d1ae2ca0c4e Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Mon, 20 Jun 2022 15:22:27 +0200 Subject: [PATCH] ClangCodeModel: Move decl/def switch functionality to dedicated class Change-Id: Id583ac58933e35e979083311907331b627d3c067 Reviewed-by: David Schulz --- src/plugins/clangcodemodel/CMakeLists.txt | 1 + src/plugins/clangcodemodel/clangcodemodel.qbs | 2 + src/plugins/clangcodemodel/clangdclient.cpp | 124 +----------- src/plugins/clangcodemodel/clangdclient.h | 1 + .../clangcodemodel/clangdswitchdecldef.cpp | 188 ++++++++++++++++++ .../clangcodemodel/clangdswitchdecldef.h | 59 ++++++ 6 files changed, 261 insertions(+), 114 deletions(-) create mode 100644 src/plugins/clangcodemodel/clangdswitchdecldef.cpp create mode 100644 src/plugins/clangcodemodel/clangdswitchdecldef.h diff --git a/src/plugins/clangcodemodel/CMakeLists.txt b/src/plugins/clangcodemodel/CMakeLists.txt index 896df4e18ce..e64d17b5f01 100644 --- a/src/plugins/clangcodemodel/CMakeLists.txt +++ b/src/plugins/clangcodemodel/CMakeLists.txt @@ -22,6 +22,7 @@ add_qtc_plugin(ClangCodeModel clangdquickfixfactory.cpp clangdquickfixfactory.h clangdqpropertyhighlighter.cpp clangdqpropertyhighlighter.h clangdsemantichighlighting.cpp clangdsemantichighlighting.h + clangdswitchdecldef.cpp clangdswitchdecldef.h clangeditordocumentprocessor.cpp clangeditordocumentprocessor.h clangfixitoperation.cpp clangfixitoperation.h clangdlocatorfilters.cpp clangdlocatorfilters.h diff --git a/src/plugins/clangcodemodel/clangcodemodel.qbs b/src/plugins/clangcodemodel/clangcodemodel.qbs index 19a0e7604a9..bd0f341cc50 100644 --- a/src/plugins/clangcodemodel/clangcodemodel.qbs +++ b/src/plugins/clangcodemodel/clangcodemodel.qbs @@ -46,6 +46,8 @@ QtcPlugin { "clangdquickfixfactory.h", "clangdsemantichighlighting.cpp", "clangdsemantichighlighting.h", + "clangdswitchdecldef.cpp", + "clangdswitchdecldef.h", "clangeditordocumentprocessor.cpp", "clangeditordocumentprocessor.h", "clangfixitoperation.cpp", diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 6441202bb6a..96034c1052c 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -30,6 +30,7 @@ #include "clangdast.h" #include "clangdfollowsymbol.h" #include "clangdlocatorfilters.h" +#include "clangdswitchdecldef.h" #include "clangpreprocessorassistproposalitem.h" #include "clangtextmark.h" #include "clangutils.h" @@ -59,7 +60,6 @@ #include #include #include -#include #include #include #include @@ -117,8 +117,8 @@ namespace ClangCodeModel { namespace Internal { Q_LOGGING_CATEGORY(clangdLog, "qtc.clangcodemodel.clangd", QtWarningMsg); +Q_LOGGING_CATEGORY(clangdLogAst, "qtc.clangcodemodel.clangd.ast", QtWarningMsg); static Q_LOGGING_CATEGORY(clangdLogServer, "qtc.clangcodemodel.clangd.server", QtWarningMsg); -static Q_LOGGING_CATEGORY(clangdLogAst, "qtc.clangcodemodel.clangd.ast", QtWarningMsg); static Q_LOGGING_CATEGORY(clangdLogCompletion, "qtc.clangcodemodel.clangd.completion", QtWarningMsg); static QString indexingToken() { return "backgroundIndexProgress"; } @@ -310,59 +310,6 @@ public: bool categorize = CppEditor::codeModelSettings()->categorizeFindReferences(); }; -class SwitchDeclDefData { -public: - SwitchDeclDefData(quint64 id, TextDocument *doc, const QTextCursor &cursor, - CppEditor::CppEditorWidget *editorWidget, - const Utils::LinkHandler &callback) - : id(id), document(doc), uri(DocumentUri::fromFilePath(doc->filePath())), - cursor(cursor), editorWidget(editorWidget), callback(callback) {} - - Utils::optional getFunctionNode() const - { - QTC_ASSERT(ast, return {}); - - const ClangdAstPath path = getAstPath(*ast, Range(cursor)); - for (auto it = path.rbegin(); it != path.rend(); ++it) { - if (it->role() == "declaration" - && (it->kind() == "CXXMethod" || it->kind() == "CXXConversion" - || it->kind() == "CXXConstructor" || it->kind() == "CXXDestructor" - || it->kind() == "Function")) { - return *it; - } - } - return {}; - } - - QTextCursor cursorForFunctionName(const ClangdAstNode &functionNode) const - { - QTC_ASSERT(docSymbols, return {}); - - const auto symbolList = Utils::get_if>(&*docSymbols); - if (!symbolList) - return {}; - const Range &astRange = functionNode.range(); - QList symbolsToCheck = *symbolList; - while (!symbolsToCheck.isEmpty()) { - const DocumentSymbol symbol = symbolsToCheck.takeFirst(); - if (symbol.range() == astRange) - return symbol.selectionRange().start().toTextCursor(document->document()); - if (symbol.range().contains(astRange)) - symbolsToCheck << symbol.children().value_or(QList()); - } - return {}; - } - - const quint64 id; - const QPointer document; - const DocumentUri uri; - const QTextCursor cursor; - const QPointer editorWidget; - Utils::LinkHandler callback; - Utils::optional docSymbols; - Utils::optional ast; -}; - class LocalRefsData { public: LocalRefsData(quint64 id, TextDocument *doc, const QTextCursor &cursor, @@ -700,7 +647,7 @@ public: const CppEditor::ClangdSettings::Data settings; QHash runningFindUsages; ClangdFollowSymbol *followSymbol = nullptr; - Utils::optional switchDeclDefData; + ClangdSwitchDeclDef *switchDeclDef = nullptr; Utils::optional localRefsData; Utils::optional versionNumber; @@ -1017,15 +964,6 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir) QTC_CHECK(d->runningFindUsages.isEmpty()); }); - connect(documentSymbolCache(), &DocumentSymbolCache::gotSymbols, this, - [this](const DocumentUri &uri, const DocumentSymbolsResult &symbols) { - if (!d->switchDeclDefData || d->switchDeclDefData->uri != uri) - return; - d->switchDeclDefData->docSymbols = symbols; - if (d->switchDeclDefData->ast) - d->handleDeclDefSwitchReplies(); - }); - start(); } @@ -1684,26 +1622,13 @@ void ClangdClient::switchDeclDef(TextDocument *document, const QTextCursor &curs qCDebug(clangdLog) << "switch decl/dev requested" << document->filePath() << cursor.blockNumber() << cursor.positionInBlock(); - d->switchDeclDefData.emplace(++d->nextJobId, document, cursor, editorWidget, callback); - - // Retrieve AST and document symbols. - const auto astHandler = [this, id = d->switchDeclDefData->id](const ClangdAstNode &ast, - const MessageId &) { - qCDebug(clangdLog) << "received ast for decl/def switch"; - if (!d->switchDeclDefData || d->switchDeclDefData->id != id - || !d->switchDeclDefData->document) - return; - if (!ast.isValid()) { - d->switchDeclDefData.reset(); - return; - } - d->switchDeclDefData->ast = ast; - if (d->switchDeclDefData->docSymbols) - d->handleDeclDefSwitchReplies(); - - }; - d->getAndHandleAst(document, astHandler, AstCallbackMode::SyncIfPossible); - documentSymbolCache()->requestSymbols(d->switchDeclDefData->uri, Schedule::Now); + if (d->switchDeclDef) + delete d->switchDeclDef; + d->switchDeclDef = new ClangdSwitchDeclDef(this, document, cursor, editorWidget, callback); + connect(d->switchDeclDef, &ClangdSwitchDeclDef::done, this, [this] { + delete d->switchDeclDef; + d->switchDeclDef = nullptr; + }); } void ClangdClient::switchHeaderSource(const Utils::FilePath &filePath, bool inNextSplit) @@ -1981,35 +1906,6 @@ void ClangdClient::setVirtualRanges(const Utils::FilePath &filePath, const QList d->highlightingData[doc].virtualRanges = {ranges, revision}; } -void ClangdClient::Private::handleDeclDefSwitchReplies() -{ - if (!switchDeclDefData->document) { - switchDeclDefData.reset(); - return; - } - - // Find the function declaration or definition associated with the cursor. - // For instance, the cursor could be somwehere inside a function body or - // on a function return type, or ... - if (clangdLogAst().isDebugEnabled()) - switchDeclDefData->ast->print(0); - const Utils::optional functionNode = switchDeclDefData->getFunctionNode(); - if (!functionNode) { - switchDeclDefData.reset(); - return; - } - - // Unfortunately, the AST does not contain the location of the actual function name symbol, - // so we have to look for it in the document symbols. - const QTextCursor funcNameCursor = switchDeclDefData->cursorForFunctionName(*functionNode); - if (!funcNameCursor.isNull()) { - q->followSymbol(switchDeclDefData->document.data(), funcNameCursor, - switchDeclDefData->editorWidget, std::move(switchDeclDefData->callback), - true, false); - } - switchDeclDefData.reset(); -} - Utils::optional ClangdClient::Private::getContainingFunctionName( const ClangdAstPath &astPath, const Range& range) { diff --git a/src/plugins/clangcodemodel/clangdclient.h b/src/plugins/clangcodemodel/clangdclient.h index 13530b168f7..2aec6f8b570 100644 --- a/src/plugins/clangcodemodel/clangdclient.h +++ b/src/plugins/clangcodemodel/clangdclient.h @@ -51,6 +51,7 @@ namespace Internal { class ClangdAstNode; Q_DECLARE_LOGGING_CATEGORY(clangdLog); +Q_DECLARE_LOGGING_CATEGORY(clangdLogAst); void setupClangdConfigFile(); diff --git a/src/plugins/clangcodemodel/clangdswitchdecldef.cpp b/src/plugins/clangcodemodel/clangdswitchdecldef.cpp new file mode 100644 index 00000000000..d3ce7187631 --- /dev/null +++ b/src/plugins/clangcodemodel/clangdswitchdecldef.cpp @@ -0,0 +1,188 @@ +/**************************************************************************** +** +** 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 "clangdswitchdecldef.h" + +#include "clangdast.h" +#include "clangdclient.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace CppEditor; +using namespace LanguageClient; +using namespace LanguageServerProtocol; +using namespace TextEditor; +using namespace Utils; + +namespace ClangCodeModel::Internal { + +class ClangdSwitchDeclDef::Private +{ +public: + Private(ClangdSwitchDeclDef * q, ClangdClient *client, TextDocument *doc, + const QTextCursor &cursor, CppEditorWidget *editorWidget, const LinkHandler &callback) + : q(q), client(client), document(doc), uri(DocumentUri::fromFilePath(doc->filePath())), + cursor(cursor), editorWidget(editorWidget), callback(callback) + {} + + optional getFunctionNode() const; + QTextCursor cursorForFunctionName(const ClangdAstNode &functionNode) const; + void handleDeclDefSwitchReplies(); + + ClangdSwitchDeclDef * const q; + ClangdClient * const client; + const QPointer document; + const DocumentUri uri; + const QTextCursor cursor; + const QPointer editorWidget; + const LinkHandler callback; + optional ast; + optional docSymbols; +}; + +ClangdSwitchDeclDef::ClangdSwitchDeclDef(ClangdClient *client, TextDocument *doc, + const QTextCursor &cursor, CppEditorWidget *editorWidget, const LinkHandler &callback) + : QObject(client), d(new Private(this, client, doc, cursor, editorWidget, callback)) +{ + // Abort if the user does something else with the document in the meantime. + connect(doc, &TextDocument::contentsChanged, this, &ClangdSwitchDeclDef::done, + Qt::QueuedConnection); + if (editorWidget) { + connect(editorWidget, &CppEditorWidget::cursorPositionChanged, + this, &ClangdSwitchDeclDef::done, Qt::QueuedConnection); + } + connect(qApp, &QApplication::focusChanged, + this, &ClangdSwitchDeclDef::done, Qt::QueuedConnection); + + connect(client->documentSymbolCache(), &DocumentSymbolCache::gotSymbols, this, + [this](const DocumentUri &uri, const DocumentSymbolsResult &symbols) { + if (uri != d->uri) + return; + d->docSymbols = symbols; + if (d->ast) + d->handleDeclDefSwitchReplies(); + }); + + + // Retrieve AST and document symbols. + const auto astHandler = [this, self = QPointer(this)] + (const ClangdAstNode &ast, const MessageId &) { + qCDebug(clangdLog) << "received ast for decl/def switch"; + if (!self) + return; + if (!d->document) { + emit done(); + return; + } + if (!ast.isValid()) { + emit done(); + return; + } + d->ast = ast; + if (d->docSymbols) + d->handleDeclDefSwitchReplies(); + + }; + client->getAndHandleAst(doc, astHandler, ClangdClient::AstCallbackMode::SyncIfPossible, {}); + client->documentSymbolCache()->requestSymbols(d->uri, Schedule::Now); +} + +ClangdSwitchDeclDef::~ClangdSwitchDeclDef() +{ + delete d; +} + +optional ClangdSwitchDeclDef::Private::getFunctionNode() const +{ + QTC_ASSERT(ast, return {}); + + const ClangdAstPath path = getAstPath(*ast, Range(cursor)); + for (auto it = path.rbegin(); it != path.rend(); ++it) { + if (it->role() == "declaration" + && (it->kind() == "CXXMethod" || it->kind() == "CXXConversion" + || it->kind() == "CXXConstructor" || it->kind() == "CXXDestructor" + || it->kind() == "Function")) { + return *it; + } + } + return {}; +} + +QTextCursor ClangdSwitchDeclDef::Private::cursorForFunctionName(const ClangdAstNode &functionNode) const +{ + QTC_ASSERT(docSymbols, return {}); + + const auto symbolList = Utils::get_if>(&*docSymbols); + if (!symbolList) + return {}; + const Range &astRange = functionNode.range(); + QList symbolsToCheck = *symbolList; + while (!symbolsToCheck.isEmpty()) { + const DocumentSymbol symbol = symbolsToCheck.takeFirst(); + if (symbol.range() == astRange) + return symbol.selectionRange().start().toTextCursor(document->document()); + if (symbol.range().contains(astRange)) + symbolsToCheck << symbol.children().value_or(QList()); + } + return {}; +} + +void ClangdSwitchDeclDef::Private::handleDeclDefSwitchReplies() +{ + if (!document) { + emit q->done(); + return; + } + + // Find the function declaration or definition associated with the cursor. + // For instance, the cursor could be somwehere inside a function body or + // on a function return type, or ... + if (clangdLogAst().isDebugEnabled()) + ast->print(0); + const Utils::optional functionNode = getFunctionNode(); + if (!functionNode) { + emit q->done(); + return; + } + + // Unfortunately, the AST does not contain the location of the actual function name symbol, + // so we have to look for it in the document symbols. + const QTextCursor funcNameCursor = cursorForFunctionName(*functionNode); + if (!funcNameCursor.isNull()) { + client->followSymbol(document.data(), funcNameCursor, editorWidget, callback, + true, false); + } + emit q->done(); +} + +} // namespace ClangCodeModel::Internal diff --git a/src/plugins/clangcodemodel/clangdswitchdecldef.h b/src/plugins/clangcodemodel/clangdswitchdecldef.h new file mode 100644 index 00000000000..8c278bf6555 --- /dev/null +++ b/src/plugins/clangcodemodel/clangdswitchdecldef.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** 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 + +#include + +namespace CppEditor { class CppEditorWidget; } +namespace TextEditor { class TextDocument; } + +QT_BEGIN_NAMESPACE +class QTextCursor; +QT_END_NAMESPACE + +namespace ClangCodeModel::Internal { +class ClangdClient; + +class ClangdSwitchDeclDef : public QObject +{ + Q_OBJECT +public: + ClangdSwitchDeclDef(ClangdClient *client, TextEditor::TextDocument *doc, + const QTextCursor &cursor, CppEditor::CppEditorWidget *editorWidget, + const Utils::LinkHandler &callback); + ~ClangdSwitchDeclDef(); + +signals: + void done(); + +private: + class Private; + Private * const d; +}; + +} // namespace ClangCodeModel::Internal