From 8b63dfccc64ecfa514a05b868142e91a7e3c94d2 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Mon, 31 Jan 2022 13:10:28 +0100 Subject: [PATCH] ClangCodeModel: Make clangd refactoring actions available Introduce an assist processor that merges our built-in quickfixes with refactoring actions from clangd ("tweaks"). For now, we make it clear which ones are coming from clangd, and we do not filter duplicate functionality. In the future, we might want to disable redundant built-in actions if clangd is enabled for the respective file. Change-Id: I04842132798c8635dfddf8cfc98cc7a6313fac09 Reviewed-by: Reviewed-by: Qt CI Bot Reviewed-by: David Schulz --- src/plugins/clangcodemodel/clangdclient.cpp | 49 +++++++++++++++++++ src/plugins/cppeditor/cppeditordocument.cpp | 2 + .../cppeditor/cppquickfixassistant.cpp | 20 +++++--- src/plugins/cppeditor/cppquickfixassistant.h | 3 ++ src/plugins/cppeditor/cpptoolsreuse.cpp | 6 +++ src/plugins/cppeditor/cpptoolsreuse.h | 3 ++ src/plugins/languageclient/client.cpp | 6 +++ src/plugins/languageclient/client.h | 1 + .../languageclient/languageclientquickfix.cpp | 32 +++++------- .../languageclient/languageclientquickfix.h | 29 +++++++++++ 10 files changed, 124 insertions(+), 27 deletions(-) diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 78d3793b502..c22f1f74456 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -1257,6 +1257,54 @@ private: ClangdClient * const m_client; }; +class ClangdQuickFixProcessor : public LanguageClientQuickFixAssistProcessor +{ +public: + ClangdQuickFixProcessor(LanguageClient::Client *client) + : LanguageClientQuickFixAssistProcessor(client) + { + // Fixes are already provided inline with the diagnostics. + setOnlyKinds({CodeActionKinds::Refactor}); + } + +private: + IAssistProposal *perform(const AssistInterface *interface) override + { + m_interface = interface; + + // Step 1: Collect clangd code actions asynchronously + LanguageClientQuickFixAssistProcessor::perform(interface); + + // Step 2: Collect built-in quickfixes synchronously + m_builtinOps = CppEditor::quickFixOperations(interface); + + return nullptr; + } + + void handleProposalReady(const QuickFixOperations &ops) override + { + // Step 3: Merge the results upon callback from clangd. + for (const auto &op : ops) + op->setDescription("clangd: " + op->description()); + setAsyncProposalAvailable(GenericProposal::createProposal(m_interface, ops + m_builtinOps)); + } + + QuickFixOperations m_builtinOps; + const AssistInterface *m_interface = nullptr; +}; + +class ClangdQuickFixProvider : public LanguageClientQuickFixProvider +{ +public: + ClangdQuickFixProvider(ClangdClient *client) : LanguageClientQuickFixProvider(client) {}; + +private: + IAssistProcessor *createProcessor(const TextEditor::AssistInterface *) const override + { + return new ClangdQuickFixProcessor(client()); + } +}; + ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir) : Client(clientInterface(project, jsonDbDir)), d(new Private(this, project)) { @@ -1268,6 +1316,7 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir) setActivateDocumentAutomatically(true); setLogTarget(LogTarget::Console); setCompletionAssistProvider(new ClangdCompletionAssistProvider(this)); + setQuickFixAssistProvider(new ClangdQuickFixProvider(this)); if (!project) { QJsonObject initOptions; const QStringList clangOptions = createClangOptions( diff --git a/src/plugins/cppeditor/cppeditordocument.cpp b/src/plugins/cppeditor/cppeditordocument.cpp index e79d8a17675..a79e26802d6 100644 --- a/src/plugins/cppeditor/cppeditordocument.cpp +++ b/src/plugins/cppeditor/cppeditordocument.cpp @@ -162,6 +162,8 @@ CompletionAssistProvider *CppEditorDocument::functionHintAssistProvider() const TextEditor::IAssistProvider *CppEditorDocument::quickFixAssistProvider() const { + if (const auto baseProvider = TextDocument::quickFixAssistProvider()) + return baseProvider; return CppEditorPlugin::instance()->quickFixProvider(); } diff --git a/src/plugins/cppeditor/cppquickfixassistant.cpp b/src/plugins/cppeditor/cppquickfixassistant.cpp index f254abc8df3..9d5d2271a6c 100644 --- a/src/plugins/cppeditor/cppquickfixassistant.cpp +++ b/src/plugins/cppeditor/cppquickfixassistant.cpp @@ -53,14 +53,8 @@ class CppQuickFixAssistProcessor : public IAssistProcessor { IAssistProposal *perform(const AssistInterface *interface) override { - QSharedPointer assistInterface(interface); - auto cppInterface = assistInterface.staticCast(); - - QuickFixOperations quickFixes; - for (CppQuickFixFactory *factory : CppQuickFixFactory::cppQuickFixFactories()) - factory->match(*cppInterface, quickFixes); - - return GenericProposal::createProposal(interface, quickFixes); + QSharedPointer dummy(interface); // FIXME: Surely this cannot be our way of doing memory management??? + return GenericProposal::createProposal(interface, quickFixOperations(interface)); } }; @@ -137,5 +131,15 @@ bool CppQuickFixInterface::isCursorOn(const AST *ast) const return currentFile()->isCursorOn(ast); } +QuickFixOperations quickFixOperations(const TextEditor::AssistInterface *interface) +{ + const auto cppInterface = dynamic_cast(interface); + QTC_ASSERT(cppInterface, return {}); + QuickFixOperations quickFixes; + for (CppQuickFixFactory *factory : CppQuickFixFactory::cppQuickFixFactories()) + factory->match(*cppInterface, quickFixes); + return quickFixes; +} + } // namespace Internal } // namespace CppEditor diff --git a/src/plugins/cppeditor/cppquickfixassistant.h b/src/plugins/cppeditor/cppquickfixassistant.h index d5e626cb0e0..d6e556c7b19 100644 --- a/src/plugins/cppeditor/cppquickfixassistant.h +++ b/src/plugins/cppeditor/cppquickfixassistant.h @@ -29,6 +29,7 @@ #include #include +#include #include @@ -73,5 +74,7 @@ public: TextEditor::IAssistProcessor *createProcessor(const TextEditor::AssistInterface *) const override; }; +TextEditor::QuickFixOperations quickFixOperations(const TextEditor::AssistInterface *interface); + } // Internal } // CppEditor diff --git a/src/plugins/cppeditor/cpptoolsreuse.cpp b/src/plugins/cppeditor/cpptoolsreuse.cpp index bd5b702360d..7f94a6e05c8 100644 --- a/src/plugins/cppeditor/cpptoolsreuse.cpp +++ b/src/plugins/cppeditor/cpptoolsreuse.cpp @@ -31,6 +31,7 @@ #include "cppeditorplugin.h" #include "cpphighlighter.h" #include "cppqtstyleindenter.h" +#include "cppquickfixassistant.h" #include "cpprefactoringchanges.h" #include "projectinfo.h" @@ -339,6 +340,11 @@ bool isInCommentOrString(const TextEditor::AssistInterface *interface, return true; } +TextEditor::QuickFixOperations quickFixOperations(const TextEditor::AssistInterface *interface) +{ + return Internal::quickFixOperations(interface); +} + CppCodeModelSettings *codeModelSettings() { return Internal::CppEditorPlugin::instance()->codeModelSettings(); diff --git a/src/plugins/cppeditor/cpptoolsreuse.h b/src/plugins/cppeditor/cpptoolsreuse.h index e7023b7be78..59ea0a9994e 100644 --- a/src/plugins/cppeditor/cpptoolsreuse.h +++ b/src/plugins/cppeditor/cpptoolsreuse.h @@ -31,6 +31,7 @@ #include "compileroptionsbuilder.h" #include "projectpart.h" +#include #include #include @@ -76,6 +77,8 @@ const CPlusPlus::Macro CPPEDITOR_EXPORT *findCanonicalMacro(const QTextCursor &c bool CPPEDITOR_EXPORT isInCommentOrString(const TextEditor::AssistInterface *interface, CPlusPlus::LanguageFeatures features); +TextEditor::QuickFixOperations CPPEDITOR_EXPORT +quickFixOperations(const TextEditor::AssistInterface *interface); enum class CacheUsage { ReadWrite, ReadOnly }; diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index 97c7c2676b7..bc63a2486be 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -1079,6 +1079,12 @@ void Client::setCompletionAssistProvider(LanguageClientCompletionAssistProvider m_clientProviders.completionAssistProvider = provider; } +void Client::setQuickFixAssistProvider(LanguageClientQuickFixProvider *provider) +{ + delete m_clientProviders.quickFixAssistProvider; + m_clientProviders.quickFixAssistProvider = provider; +} + void Client::start() { LanguageClientManager::addClient(this); diff --git a/src/plugins/languageclient/client.h b/src/plugins/languageclient/client.h index cde987670b8..a3fb2e6988e 100644 --- a/src/plugins/languageclient/client.h +++ b/src/plugins/languageclient/client.h @@ -192,6 +192,7 @@ public: LanguageServerProtocol::SymbolStringifier symbolStringifier() const; void setSnippetsGroup(const QString &group); void setCompletionAssistProvider(LanguageClientCompletionAssistProvider *provider); + void setQuickFixAssistProvider(LanguageClientQuickFixProvider *provider); // logging enum class LogTarget { Console, Ui }; diff --git a/src/plugins/languageclient/languageclientquickfix.cpp b/src/plugins/languageclient/languageclientquickfix.cpp index 93d85b15f21..ed3d87df38a 100644 --- a/src/plugins/languageclient/languageclientquickfix.cpp +++ b/src/plugins/languageclient/languageclientquickfix.cpp @@ -30,7 +30,6 @@ #include #include -#include #include @@ -74,22 +73,6 @@ private: QPointer m_client; }; -class LanguageClientQuickFixAssistProcessor : public IAssistProcessor -{ -public: - explicit LanguageClientQuickFixAssistProcessor(Client *client) : m_client(client) {} - bool running() override { return m_currentRequest.has_value(); } - IAssistProposal *perform(const AssistInterface *interface) override; - void cancel() override; - -private: - void handleCodeActionResponse(const CodeActionRequest::Response &response); - - QSharedPointer m_assistInterface; - Client *m_client = nullptr; // not owned - Utils::optional m_currentRequest; -}; - IAssistProposal *LanguageClientQuickFixAssistProcessor::perform(const AssistInterface *interface) { m_assistInterface = QSharedPointer(interface); @@ -109,6 +92,8 @@ IAssistProposal *LanguageClientQuickFixAssistProcessor::perform(const AssistInte auto uri = DocumentUri::fromFilePath(interface->filePath()); params.setTextDocument(TextDocumentIdentifier(uri)); CodeActionParams::CodeActionContext context; + if (!m_onlyKinds.isEmpty()) + context.setOnly(m_onlyKinds); context.setDiagnostics(m_client->diagnosticsAt(uri, cursor)); params.setContext(context); @@ -131,8 +116,12 @@ void LanguageClientQuickFixAssistProcessor::cancel() } } -void LanguageClientQuickFixAssistProcessor::handleCodeActionResponse( - const CodeActionRequest::Response &response) +void LanguageClientQuickFixAssistProcessor::setOnlyKinds(const QList &only) +{ + m_onlyKinds = only; +} + +void LanguageClientQuickFixAssistProcessor::handleCodeActionResponse(const CodeActionRequest::Response &response) { m_currentRequest.reset(); if (const Utils::optional &error = response.error()) @@ -150,6 +139,11 @@ void LanguageClientQuickFixAssistProcessor::handleCodeActionResponse( } } m_client->removeAssistProcessor(this); + handleProposalReady(ops); +} + +void LanguageClientQuickFixAssistProcessor::handleProposalReady(const QuickFixOperations &ops) +{ setAsyncProposalAvailable(GenericProposal::createProposal(m_assistInterface.data(), ops)); } diff --git a/src/plugins/languageclient/languageclientquickfix.h b/src/plugins/languageclient/languageclientquickfix.h index 955f12bc87e..988fb667a25 100644 --- a/src/plugins/languageclient/languageclientquickfix.h +++ b/src/plugins/languageclient/languageclientquickfix.h @@ -28,12 +28,15 @@ #include "languageclient_global.h" #include +#include #include #include #include +namespace TextEditor { class IAssistProposal; } + namespace LanguageClient { class Client; @@ -56,8 +59,34 @@ public: IAssistProvider::RunType runType() const override; TextEditor::IAssistProcessor *createProcessor(const TextEditor::AssistInterface *) const override; +protected: + Client *client() const { return m_client; } + private: Client *m_client = nullptr; // not owned }; +class LANGUAGECLIENT_EXPORT LanguageClientQuickFixAssistProcessor + : public TextEditor::IAssistProcessor +{ +public: + explicit LanguageClientQuickFixAssistProcessor(Client *client) : m_client(client) {} + bool running() override { return m_currentRequest.has_value(); } + TextEditor::IAssistProposal *perform(const TextEditor::AssistInterface *interface) override; + void cancel() override; + +protected: + void setOnlyKinds(const QList &only); + +private: + void handleCodeActionResponse(const LanguageServerProtocol::CodeActionRequest::Response &response); + virtual void handleProposalReady(const TextEditor::QuickFixOperations &ops); + + QSharedPointer m_assistInterface; + Client *m_client = nullptr; // not owned + Utils::optional m_currentRequest; + QList m_onlyKinds; +}; + + } // namespace LanguageClient