clangd: introduce ClangdCompletionItem

allowing to overwrite apply and get rid of the apply helper in the
LanguageClient completion provider and processor.

Change-Id: I066fe10b116d638bd1b7a81d4488840bec5f0b63
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
This commit is contained in:
David Schulz
2021-09-27 11:08:59 +02:00
parent 191bab9ecd
commit 23a58a320e
3 changed files with 91 additions and 90 deletions

View File

@@ -807,45 +807,19 @@ public:
} }
}; };
QList<LanguageServerProtocol::CompletionItem> completionItemsTransformer( class ClangdCompletionItem : public LanguageClientCompletionItem
const Utils::FilePath &filePath,
const QString &content,
int pos,
const QList<CompletionItem> &items)
{ {
qCDebug(clangdLog) << "received" << items.count() << "completions"; public:
using LanguageClientCompletionItem::LanguageClientCompletionItem;
// If there are signals among the candidates, we employ the built-in code model to find out void apply(TextDocumentManipulatorInterface &manipulator,
// whether the cursor was on the second argument of a (dis)connect() call. int basePosition) const override;
// If so, we offer only signals, as nothing else makes sense in that context.
static const auto criterion = [](const CompletionItem &ci) {
const Utils::optional<MarkupOrString> doc = ci.documentation();
if (!doc)
return false;
QString docText;
if (Utils::holds_alternative<QString>(*doc))
docText = Utils::get<QString>(*doc);
else if (Utils::holds_alternative<MarkupContent>(*doc))
docText = Utils::get<MarkupContent>(*doc).content();
return docText.contains("Annotation: qt_signal");
};
if (pos != -1 && Utils::anyOf(items, criterion)
&& CppEditor::CppModelManager::instance()->positionRequiresSignal(filePath.toString(),
content.toUtf8(),
pos)) {
return Utils::filtered(items, criterion);
}
return items;
}; };
class ClangdClient::ClangdCompletionAssistProcessor : public LanguageClientCompletionAssistProcessor class ClangdClient::ClangdCompletionAssistProcessor : public LanguageClientCompletionAssistProcessor
{ {
public: public:
ClangdCompletionAssistProcessor(ClangdClient *client, const QString &snippetsGroup) ClangdCompletionAssistProcessor(ClangdClient *client, const QString &snippetsGroup)
: LanguageClientCompletionAssistProcessor(client, : LanguageClientCompletionAssistProcessor(client, snippetsGroup)
&completionItemsTransformer,
&applyCompletionItem,
snippetsGroup)
, m_client(client) , m_client(client)
{ {
} }
@@ -861,13 +835,53 @@ private:
return LanguageClientCompletionAssistProcessor::perform(interface); return LanguageClientCompletionAssistProcessor::perform(interface);
} }
static void applyCompletionItem(const CompletionItem &item, QList<AssistProposalItemInterface *> generateCompletionItems(
TextDocumentManipulatorInterface &manipulator, const QList<LanguageServerProtocol::CompletionItem> &items) const override;
QChar typedChar);
ClangdClient * const m_client; ClangdClient * const m_client;
}; };
QList<AssistProposalItemInterface *>
ClangdClient::ClangdCompletionAssistProcessor::generateCompletionItems(
const QList<LanguageServerProtocol::CompletionItem> &items) const
{
qCDebug(clangdLog) << "received" << items.count() << "completions";
auto itemGenerator = [](const QList<LanguageServerProtocol::CompletionItem> &items) {
return Utils::transform<QList<AssistProposalItemInterface *>>(
items, [](const LanguageServerProtocol::CompletionItem &item) {
return new ClangdCompletionItem(item);
});
};
// If there are signals among the candidates, we employ the built-in code model to find out
// whether the cursor was on the second argument of a (dis)connect() call.
// If so, we offer only signals, as nothing else makes sense in that context.
static const auto criterion = [](const CompletionItem &ci) {
const Utils::optional<MarkupOrString> doc = ci.documentation();
if (!doc)
return false;
QString docText;
if (Utils::holds_alternative<QString>(*doc))
docText = Utils::get<QString>(*doc);
else if (Utils::holds_alternative<MarkupContent>(*doc))
docText = Utils::get<MarkupContent>(*doc).content();
return docText.contains("Annotation: qt_signal");
};
const QTextDocument *doc = document();
const int pos = basePos();
if (!doc || pos < 0 || !Utils::anyOf(items, criterion))
return itemGenerator(items);
const QString content = doc->toPlainText();
const bool requiresSignal = CppEditor::CppModelManager::instance()
->positionRequiresSignal(filePath().toString(),
content.toUtf8(),
pos);
if (requiresSignal)
return itemGenerator(Utils::filtered(items, criterion));
return itemGenerator(items);
}
class ClangdClient::ClangdCompletionAssistProvider : public LanguageClientCompletionAssistProvider class ClangdClient::ClangdCompletionAssistProvider : public LanguageClientCompletionAssistProvider
{ {
public: public:
@@ -2837,9 +2851,11 @@ bool ClangdClient::ClangdCompletionAssistProvider::isContinuationChar(const QCha
return CppEditor::isValidIdentifierChar(c); return CppEditor::isValidIdentifierChar(c);
} }
void ClangdClient::ClangdCompletionAssistProcessor::applyCompletionItem(const CompletionItem &item, void ClangdCompletionItem::apply(TextDocumentManipulatorInterface &manipulator,
TextDocumentManipulatorInterface &manipulator, QChar typedChar) int /*basePosition*/) const
{ {
const LanguageServerProtocol::CompletionItem item = this->item();
QChar typedChar = triggeredCommitCharacter();
const auto edit = item.textEdit(); const auto edit = item.textEdit();
if (!edit) if (!edit)
return; return;

View File

@@ -54,9 +54,8 @@ using namespace TextEditor;
namespace LanguageClient { namespace LanguageClient {
LanguageClientCompletionItem::LanguageClientCompletionItem(CompletionItem item, LanguageClientCompletionItem::LanguageClientCompletionItem(CompletionItem item)
const CompletionApplyHelper &applyHelper) : m_item(std::move(item))
: m_item(std::move(item)), m_applyHelper(applyHelper)
{ } { }
QString LanguageClientCompletionItem::text() const QString LanguageClientCompletionItem::text() const
@@ -77,11 +76,6 @@ bool LanguageClientCompletionItem::prematurelyApplies(const QChar &typedCharacte
void LanguageClientCompletionItem::apply(TextDocumentManipulatorInterface &manipulator, void LanguageClientCompletionItem::apply(TextDocumentManipulatorInterface &manipulator,
int /*basePosition*/) const int /*basePosition*/) const
{ {
if (m_applyHelper) {
m_applyHelper(m_item, manipulator, m_triggeredCommitCharacter);
return;
}
if (auto edit = m_item.textEdit()) { if (auto edit = m_item.textEdit()) {
applyTextEdit(manipulator, *edit, isSnippet()); applyTextEdit(manipulator, *edit, isSnippet());
} else { } else {
@@ -176,6 +170,16 @@ quint64 LanguageClientCompletionItem::hash() const
return qHash(m_item.label()); // TODO: naaaa return qHash(m_item.label()); // TODO: naaaa
} }
CompletionItem LanguageClientCompletionItem::item() const
{
return m_item;
}
QChar LanguageClientCompletionItem::triggeredCommitCharacter() const
{
return m_triggeredCommitCharacter;
}
const QString &LanguageClientCompletionItem::sortText() const const QString &LanguageClientCompletionItem::sortText() const
{ {
if (m_sortText.isEmpty()) if (m_sortText.isEmpty())
@@ -289,12 +293,8 @@ public:
LanguageClientCompletionAssistProcessor::LanguageClientCompletionAssistProcessor( LanguageClientCompletionAssistProcessor::LanguageClientCompletionAssistProcessor(
Client *client, Client *client,
const CompletionItemsTransformer &itemsTransformer,
const CompletionApplyHelper &applyHelper,
const QString &snippetsGroup) const QString &snippetsGroup)
: m_client(client) : m_client(client)
, m_itemsTransformer(itemsTransformer)
, m_applyHelper(applyHelper)
, m_snippetsGroup(snippetsGroup) , m_snippetsGroup(snippetsGroup)
{} {}
@@ -303,6 +303,18 @@ LanguageClientCompletionAssistProcessor::~LanguageClientCompletionAssistProcesso
QTC_ASSERT(!running(), cancel()); QTC_ASSERT(!running(), cancel());
} }
QTextDocument *LanguageClientCompletionAssistProcessor::document() const
{
return m_document;
}
QList<AssistProposalItemInterface *> LanguageClientCompletionAssistProcessor::generateCompletionItems(
const QList<LanguageServerProtocol::CompletionItem> &items) const
{
return Utils::transform<QList<AssistProposalItemInterface *>>(
items, [](const CompletionItem &item) { return new LanguageClientCompletionItem(item); });
}
static QString assistReasonString(AssistReason reason) static QString assistReasonString(AssistReason reason)
{ {
switch (reason) { switch (reason) {
@@ -412,17 +424,12 @@ void LanguageClientCompletionAssistProcessor::handleCompletionResponse(
} else if (Utils::holds_alternative<QList<CompletionItem>>(*result)) { } else if (Utils::holds_alternative<QList<CompletionItem>>(*result)) {
items = Utils::get<QList<CompletionItem>>(*result); items = Utils::get<QList<CompletionItem>>(*result);
} }
if (m_itemsTransformer && m_document) auto proposalItems = generateCompletionItems(items);
items = m_itemsTransformer(m_filePath, m_document->toPlainText(), m_basePos, items);
auto model = new LanguageClientCompletionModel();
auto proposalItems = Utils::transform<QList<AssistProposalItemInterface *>>(items,
[this](const CompletionItem &item) {
return new LanguageClientCompletionItem(item, m_applyHelper);
});
if (!m_snippetsGroup.isEmpty()) { if (!m_snippetsGroup.isEmpty()) {
proposalItems << TextEditor::SnippetAssistCollector( proposalItems << TextEditor::SnippetAssistCollector(
m_snippetsGroup, QIcon(":/texteditor/images/snippet.png")).collect(); m_snippetsGroup, QIcon(":/texteditor/images/snippet.png")).collect();
} }
auto model = new LanguageClientCompletionModel();
model->loadContent(proposalItems); model->loadContent(proposalItems);
LanguageClientCompletionProposal *proposal = new LanguageClientCompletionProposal(m_basePos, LanguageClientCompletionProposal *proposal = new LanguageClientCompletionProposal(m_basePos,
model); model);
@@ -445,8 +452,6 @@ IAssistProcessor *LanguageClientCompletionAssistProvider::createProcessor(
const AssistInterface *) const const AssistInterface *) const
{ {
return new LanguageClientCompletionAssistProcessor(m_client, return new LanguageClientCompletionAssistProcessor(m_client,
m_itemsTransformer,
m_applyHelper,
m_snippetsGroup); m_snippetsGroup);
} }
@@ -478,16 +483,4 @@ void LanguageClientCompletionAssistProvider::setTriggerCharacters(
} }
} }
void LanguageClientCompletionAssistProvider::setItemsTransformer(
const CompletionItemsTransformer &transformer)
{
m_itemsTransformer = transformer;
}
void LanguageClientCompletionAssistProvider::setApplyHelper(
const CompletionApplyHelper &applyHelper)
{
m_applyHelper = applyHelper;
}
} // namespace LanguageClient } // namespace LanguageClient

View File

@@ -47,13 +47,6 @@ namespace LanguageClient {
class Client; class Client;
using CompletionItemsTransformer = std::function<QList<LanguageServerProtocol::CompletionItem>(
const Utils::FilePath &, const QString &, int,
const QList<LanguageServerProtocol::CompletionItem> &)>;
using CompletionApplyHelper = std::function<void(
const LanguageServerProtocol::CompletionItem &,
TextEditor::TextDocumentManipulatorInterface &, QChar)>;
class LANGUAGECLIENT_EXPORT LanguageClientCompletionAssistProvider class LANGUAGECLIENT_EXPORT LanguageClientCompletionAssistProvider
: public TextEditor::CompletionAssistProvider : public TextEditor::CompletionAssistProvider
{ {
@@ -74,14 +67,10 @@ public:
void setSnippetsGroup(const QString &group) { m_snippetsGroup = group; } void setSnippetsGroup(const QString &group) { m_snippetsGroup = group; }
protected: protected:
void setItemsTransformer(const CompletionItemsTransformer &transformer);
void setApplyHelper(const CompletionApplyHelper &applyHelper);
Client *client() const { return m_client; } Client *client() const { return m_client; }
private: private:
QList<QString> m_triggerChars; QList<QString> m_triggerChars;
CompletionItemsTransformer m_itemsTransformer;
CompletionApplyHelper m_applyHelper;
QString m_snippetsGroup; QString m_snippetsGroup;
int m_activationCharSequenceLength = 0; int m_activationCharSequenceLength = 0;
Client *m_client = nullptr; // not owned Client *m_client = nullptr; // not owned
@@ -91,16 +80,20 @@ class LANGUAGECLIENT_EXPORT LanguageClientCompletionAssistProcessor
: public TextEditor::IAssistProcessor : public TextEditor::IAssistProcessor
{ {
public: public:
LanguageClientCompletionAssistProcessor(Client *client, LanguageClientCompletionAssistProcessor(Client *client, const QString &snippetsGroup);
const CompletionItemsTransformer &itemsTransformer,
const CompletionApplyHelper &applyHelper,
const QString &snippetsGroup);
~LanguageClientCompletionAssistProcessor() override; ~LanguageClientCompletionAssistProcessor() override;
TextEditor::IAssistProposal *perform(const TextEditor::AssistInterface *interface) override; TextEditor::IAssistProposal *perform(const TextEditor::AssistInterface *interface) override;
bool running() override; bool running() override;
bool needsRestart() const override { return true; } bool needsRestart() const override { return true; }
void cancel() override; void cancel() override;
protected:
QTextDocument *document() const;
Utils::FilePath filePath() const { return m_filePath; }
int basePos() const { return m_basePos; }
virtual QList<TextEditor::AssistProposalItemInterface *> generateCompletionItems(
const QList<LanguageServerProtocol::CompletionItem> &items) const;
private: private:
void handleCompletionResponse(const LanguageServerProtocol::CompletionRequest::Response &response); void handleCompletionResponse(const LanguageServerProtocol::CompletionRequest::Response &response);
@@ -109,8 +102,6 @@ private:
QPointer<Client> m_client; QPointer<Client> m_client;
Utils::optional<LanguageServerProtocol::MessageId> m_currentRequest; Utils::optional<LanguageServerProtocol::MessageId> m_currentRequest;
QMetaObject::Connection m_postponedUpdateConnection; QMetaObject::Connection m_postponedUpdateConnection;
const CompletionItemsTransformer m_itemsTransformer;
const CompletionApplyHelper m_applyHelper;
const QString m_snippetsGroup; const QString m_snippetsGroup;
int m_pos = -1; int m_pos = -1;
int m_basePos = -1; int m_basePos = -1;
@@ -120,8 +111,7 @@ class LANGUAGECLIENT_EXPORT LanguageClientCompletionItem
: public TextEditor::AssistProposalItemInterface : public TextEditor::AssistProposalItemInterface
{ {
public: public:
LanguageClientCompletionItem(LanguageServerProtocol::CompletionItem item, LanguageClientCompletionItem(LanguageServerProtocol::CompletionItem item);
const CompletionApplyHelper &applyHelper);
// AssistProposalItemInterface interface // AssistProposalItemInterface interface
QString text() const override; QString text() const override;
@@ -136,6 +126,9 @@ public:
bool isValid() const override; bool isValid() const override;
quint64 hash() const override; quint64 hash() const override;
LanguageServerProtocol::CompletionItem item() const;
QChar triggeredCommitCharacter() const;
const QString &sortText() const; const QString &sortText() const;
bool hasSortText() const; bool hasSortText() const;
@@ -145,7 +138,6 @@ public:
private: private:
LanguageServerProtocol::CompletionItem m_item; LanguageServerProtocol::CompletionItem m_item;
const CompletionApplyHelper m_applyHelper;
mutable QChar m_triggeredCommitCharacter; mutable QChar m_triggeredCommitCharacter;
mutable QString m_sortText; mutable QString m_sortText;
mutable QString m_filterText; mutable QString m_filterText;