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: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Kandeler
2022-01-31 13:10:28 +01:00
parent 116b498b34
commit 8b63dfccc6
10 changed files with 124 additions and 27 deletions

View File

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

View File

@@ -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();
}

View File

@@ -53,14 +53,8 @@ class CppQuickFixAssistProcessor : public IAssistProcessor
{
IAssistProposal *perform(const AssistInterface *interface) override
{
QSharedPointer<const AssistInterface> assistInterface(interface);
auto cppInterface = assistInterface.staticCast<const CppQuickFixInterface>();
QuickFixOperations quickFixes;
for (CppQuickFixFactory *factory : CppQuickFixFactory::cppQuickFixFactories())
factory->match(*cppInterface, quickFixes);
return GenericProposal::createProposal(interface, quickFixes);
QSharedPointer<const AssistInterface> 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<const CppQuickFixInterface *>(interface);
QTC_ASSERT(cppInterface, return {});
QuickFixOperations quickFixes;
for (CppQuickFixFactory *factory : CppQuickFixFactory::cppQuickFixFactories())
factory->match(*cppInterface, quickFixes);
return quickFixes;
}
} // namespace Internal
} // namespace CppEditor

View File

@@ -29,6 +29,7 @@
#include <texteditor/codeassist/assistinterface.h>
#include <texteditor/codeassist/iassistprovider.h>
#include <texteditor/quickfix.h>
#include <cplusplus/LookupContext.h>
@@ -73,5 +74,7 @@ public:
TextEditor::IAssistProcessor *createProcessor(const TextEditor::AssistInterface *) const override;
};
TextEditor::QuickFixOperations quickFixOperations(const TextEditor::AssistInterface *interface);
} // Internal
} // CppEditor

View File

@@ -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();

View File

@@ -31,6 +31,7 @@
#include "compileroptionsbuilder.h"
#include "projectpart.h"
#include <texteditor/quickfix.h>
#include <texteditor/texteditor.h>
#include <cplusplus/ASTVisitor.h>
@@ -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 };

View File

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

View File

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

View File

@@ -30,7 +30,6 @@
#include <texteditor/codeassist/assistinterface.h>
#include <texteditor/codeassist/genericproposal.h>
#include <texteditor/codeassist/iassistprocessor.h>
#include <texteditor/quickfix.h>
@@ -74,22 +73,6 @@ private:
QPointer<Client> 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<const AssistInterface> m_assistInterface;
Client *m_client = nullptr; // not owned
Utils::optional<MessageId> m_currentRequest;
};
IAssistProposal *LanguageClientQuickFixAssistProcessor::perform(const AssistInterface *interface)
{
m_assistInterface = QSharedPointer<const AssistInterface>(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<CodeActionKind> &only)
{
m_onlyKinds = only;
}
void LanguageClientQuickFixAssistProcessor::handleCodeActionResponse(const CodeActionRequest::Response &response)
{
m_currentRequest.reset();
if (const Utils::optional<CodeActionRequest::Response::Error> &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));
}

View File

@@ -28,12 +28,15 @@
#include "languageclient_global.h"
#include <texteditor/codeassist/iassistprovider.h>
#include <texteditor/codeassist/iassistprocessor.h>
#include <texteditor/quickfix.h>
#include <languageserverprotocol/languagefeatures.h>
#include <QPointer>
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<LanguageServerProtocol::CodeActionKind> &only);
private:
void handleCodeActionResponse(const LanguageServerProtocol::CodeActionRequest::Response &response);
virtual void handleProposalReady(const TextEditor::QuickFixOperations &ops);
QSharedPointer<const TextEditor::AssistInterface> m_assistInterface;
Client *m_client = nullptr; // not owned
Utils::optional<LanguageServerProtocol::MessageId> m_currentRequest;
QList<LanguageServerProtocol::CodeActionKind> m_onlyKinds;
};
} // namespace LanguageClient