diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 2e3e3ae898e..43ed0c2d28f 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -768,10 +769,6 @@ public: void handleSemanticTokens(TextEditor::TextDocument *doc, const QList &tokens); - void applyCompletionItem(const CompletionItem &item, - TextEditor::TextDocumentManipulatorInterface &manipulator, - QChar typedChar); - ClangdClient * const q; const CppEditor::ClangdSettings::Data settings; DoxygenAssistProvider doxygenAssistProvider; @@ -796,6 +793,21 @@ public: } }; +class ClangdCompletionAssistProvider : public LanguageClientCompletionAssistProvider +{ +public: + ClangdCompletionAssistProvider(ClangdClient *client); + +private: + int activationCharSequenceLength() const override { return 3; } + bool isActivationCharSequence(const QString &sequence) const override; + bool isContinuationChar(const QChar &c) const override; + + void applyCompletionItem(const CompletionItem &item, + TextEditor::TextDocumentManipulatorInterface &manipulator, + QChar typedChar); +}; + ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir) : Client(clientInterface(project, jsonDbDir)), d(new Private(this, project)) { @@ -805,6 +817,7 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir) "text/x-c++hdr", "text/x-c++src", "text/x-objc++src", "text/x-objcsrc"}; setSupportedLanguage(langFilter); setActivateDocumentAutomatically(true); + setCompletionAssistProvider(new ClangdCompletionAssistProvider(this)); if (!project) { QJsonObject initOptions; const QStringList clangOptions = createClangOptions( @@ -879,34 +892,6 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir) const DocumentUri &uri) { gatherHelpItemForTooltip(response, uri); }); - setCompletionItemsTransformer([](const Utils::FilePath &filePath, const QString &content, - int pos, const QList &items) { - qCDebug(clangdLog) << "received" << items.count() << "completions"; - - // 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 doc = ci.documentation(); - if (!doc) - return false; - QString docText; - if (Utils::holds_alternative(*doc)) - docText = Utils::get(*doc); - else if (Utils::holds_alternative(*doc)) - docText = Utils::get(*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; - }); - setCompletionApplyHelper([this](const CompletionItem &item, - TextEditor::TextDocumentManipulatorInterface &manipulator, QChar typedChar) { - d->applyCompletionItem(item, manipulator, typedChar); - }); connect(this, &Client::workDone, this, [this, p = QPointer(project)](const ProgressToken &token) { @@ -2615,8 +2600,176 @@ void ClangdClient::Private::handleSemanticTokens(TextEditor::TextDocument *doc, q->sendContent(astReq, SendDocUpdates::Ignore); } -void ClangdClient::Private::applyCompletionItem(const CompletionItem &item, - TextEditor::TextDocumentManipulatorInterface &manipulator, QChar typedChar) +void ClangdClient::VirtualFunctionAssistProcessor::cancel() +{ + resetData(); +} + +void ClangdClient::VirtualFunctionAssistProcessor::update() +{ + if (!m_data->followSymbolData->editorWidget) + return; + setAsyncProposalAvailable(createProposal(false)); +} + +void ClangdClient::VirtualFunctionAssistProcessor::finalize() +{ + if (!m_data->followSymbolData->editorWidget) + return; + const auto proposal = createProposal(true); + if (m_data->followSymbolData->editorWidget->isInTestMode()) { + m_data->followSymbolData->symbolsToDisplay.clear(); + const auto immediateProposal = createProposal(false); + m_data->followSymbolData->editorWidget->setProposals(immediateProposal, proposal); + } else { + setAsyncProposalAvailable(proposal); + } + resetData(); +} + +void ClangdClient::VirtualFunctionAssistProcessor::resetData() +{ + if (!m_data) + return; + m_data->followSymbolData->virtualFuncAssistProcessor = nullptr; + m_data->followSymbolData.reset(); + m_data = nullptr; +} + +TextEditor::IAssistProposal *ClangdClient::VirtualFunctionAssistProcessor::createProposal(bool final) const +{ + QTC_ASSERT(m_data && m_data->followSymbolData, return nullptr); + + QList items; + bool needsBaseDeclEntry = !m_data->followSymbolData->defLinkNode.range() + .contains(Position(m_data->followSymbolData->cursor)); + for (const SymbolData &symbol : qAsConst(m_data->followSymbolData->symbolsToDisplay)) { + Utils::Link link = symbol.second; + if (m_data->followSymbolData->defLink == link) { + if (!needsBaseDeclEntry) + continue; + needsBaseDeclEntry = false; + } else { + const Utils::Link defLink = m_data->followSymbolData->declDefMap.value(symbol.second); + if (defLink.hasValidTarget()) + link = defLink; + } + items << createEntry(symbol.first, link); + } + if (needsBaseDeclEntry) + items << createEntry({}, m_data->followSymbolData->defLink); + if (!final) { + const auto infoItem = new CppEditor::VirtualFunctionProposalItem({}, false); + infoItem->setText(ClangdClient::tr("collecting overrides ...")); + infoItem->setOrder(-1); + items << infoItem; + } + + return new CppEditor::VirtualFunctionProposal( + m_data->followSymbolData->cursor.position(), + items, m_data->followSymbolData->openInSplit); +} + +CppEditor::VirtualFunctionProposalItem * +ClangdClient::VirtualFunctionAssistProcessor::createEntry(const QString &name, + const Utils::Link &link) const +{ + const auto item = new CppEditor::VirtualFunctionProposalItem( + link, m_data->followSymbolData->openInSplit); + QString text = name; + if (link == m_data->followSymbolData->defLink) { + item->setOrder(1000); // Ensure base declaration is on top. + if (text.isEmpty()) { + text = ClangdClient::tr(""); + } else if (m_data->followSymbolData->defLinkNode.isPureVirtualDeclaration() + || m_data->followSymbolData->defLinkNode.isPureVirtualDefinition()) { + text += " = 0"; + } + } + item->setText(text); + return item; +} + +TextEditor::IAssistProcessor *ClangdClient::VirtualFunctionAssistProvider::createProcessor() const +{ + return m_data->followSymbolData->virtualFuncAssistProcessor + = new VirtualFunctionAssistProcessor(m_data); +} + +Utils::optional > ClangdDiagnostic::codeActions() const +{ + return optionalArray("codeActions"); +} + +QString ClangdDiagnostic::category() const +{ + return typedValue("category"); +} + +ClangdCompletionAssistProvider::ClangdCompletionAssistProvider(ClangdClient *client) + : LanguageClientCompletionAssistProvider(client) +{ + setItemsTransformer([](const Utils::FilePath &filePath, const QString &content, + int pos, const QList &items) { + qCDebug(clangdLog) << "received" << items.count() << "completions"; + + // 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 doc = ci.documentation(); + if (!doc) + return false; + QString docText; + if (Utils::holds_alternative(*doc)) + docText = Utils::get(*doc); + else if (Utils::holds_alternative(*doc)) + docText = Utils::get(*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; + }); + + setApplyHelper([this](const CompletionItem &item, + TextEditor::TextDocumentManipulatorInterface &manipulator, QChar typedChar) { + applyCompletionItem(item, manipulator, typedChar); + }); +} + +bool ClangdCompletionAssistProvider::isActivationCharSequence(const QString &sequence) const +{ + const QChar &ch = sequence.at(2); + const QChar &ch2 = sequence.at(1); + const QChar &ch3 = sequence.at(0); + unsigned kind = T_EOF_SYMBOL; + const int pos = CppEditor::CppCompletionAssistProvider::activationSequenceChar( + ch, ch2, ch3, &kind, false, false); + if (pos == 0) + return false; + + // We want to minimize unneeded completion requests, as those trigger document updates, + // which trigger re-highlighting and diagnostics, which we try to delay. + // Therefore, we do not trigger on syntax elements that often occur in non-applicable + // contexts, such as '(', '<' or '/'. + switch (kind) { + case T_DOT: case T_COLON_COLON: case T_ARROW: case T_DOT_STAR: case T_ARROW_STAR: case T_POUND: + return true; + } + return false; +} + +bool ClangdCompletionAssistProvider::isContinuationChar(const QChar &c) const +{ + return CppEditor::isValidIdentifierChar(c); +} + +void ClangdCompletionAssistProvider::applyCompletionItem( + const CompletionItem &item, TextEditor::TextDocumentManipulatorInterface &manipulator, + QChar typedChar) { const auto edit = item.textEdit(); if (!edit) @@ -2734,112 +2887,6 @@ void ClangdClient::Private::applyCompletionItem(const CompletionItem &item, } } -void ClangdClient::VirtualFunctionAssistProcessor::cancel() -{ - resetData(); -} - -void ClangdClient::VirtualFunctionAssistProcessor::update() -{ - if (!m_data->followSymbolData->editorWidget) - return; - setAsyncProposalAvailable(createProposal(false)); -} - -void ClangdClient::VirtualFunctionAssistProcessor::finalize() -{ - if (!m_data->followSymbolData->editorWidget) - return; - const auto proposal = createProposal(true); - if (m_data->followSymbolData->editorWidget->isInTestMode()) { - m_data->followSymbolData->symbolsToDisplay.clear(); - const auto immediateProposal = createProposal(false); - m_data->followSymbolData->editorWidget->setProposals(immediateProposal, proposal); - } else { - setAsyncProposalAvailable(proposal); - } - resetData(); -} - -void ClangdClient::VirtualFunctionAssistProcessor::resetData() -{ - if (!m_data) - return; - m_data->followSymbolData->virtualFuncAssistProcessor = nullptr; - m_data->followSymbolData.reset(); - m_data = nullptr; -} - -TextEditor::IAssistProposal *ClangdClient::VirtualFunctionAssistProcessor::createProposal(bool final) const -{ - QTC_ASSERT(m_data && m_data->followSymbolData, return nullptr); - - QList items; - bool needsBaseDeclEntry = !m_data->followSymbolData->defLinkNode.range() - .contains(Position(m_data->followSymbolData->cursor)); - for (const SymbolData &symbol : qAsConst(m_data->followSymbolData->symbolsToDisplay)) { - Utils::Link link = symbol.second; - if (m_data->followSymbolData->defLink == link) { - if (!needsBaseDeclEntry) - continue; - needsBaseDeclEntry = false; - } else { - const Utils::Link defLink = m_data->followSymbolData->declDefMap.value(symbol.second); - if (defLink.hasValidTarget()) - link = defLink; - } - items << createEntry(symbol.first, link); - } - if (needsBaseDeclEntry) - items << createEntry({}, m_data->followSymbolData->defLink); - if (!final) { - const auto infoItem = new CppEditor::VirtualFunctionProposalItem({}, false); - infoItem->setText(ClangdClient::tr("collecting overrides ...")); - infoItem->setOrder(-1); - items << infoItem; - } - - return new CppEditor::VirtualFunctionProposal( - m_data->followSymbolData->cursor.position(), - items, m_data->followSymbolData->openInSplit); -} - -CppEditor::VirtualFunctionProposalItem * -ClangdClient::VirtualFunctionAssistProcessor::createEntry(const QString &name, - const Utils::Link &link) const -{ - const auto item = new CppEditor::VirtualFunctionProposalItem( - link, m_data->followSymbolData->openInSplit); - QString text = name; - if (link == m_data->followSymbolData->defLink) { - item->setOrder(1000); // Ensure base declaration is on top. - if (text.isEmpty()) { - text = ClangdClient::tr(""); - } else if (m_data->followSymbolData->defLinkNode.isPureVirtualDeclaration() - || m_data->followSymbolData->defLinkNode.isPureVirtualDefinition()) { - text += " = 0"; - } - } - item->setText(text); - return item; -} - -TextEditor::IAssistProcessor *ClangdClient::VirtualFunctionAssistProvider::createProcessor() const -{ - return m_data->followSymbolData->virtualFuncAssistProcessor - = new VirtualFunctionAssistProcessor(m_data); -} - -Utils::optional > ClangdDiagnostic::codeActions() const -{ - return optionalArray("codeActions"); -} - -QString ClangdDiagnostic::category() const -{ - return typedValue("category"); -} - } // namespace Internal } // namespace ClangCodeModel diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index b573185a31b..a136b1aefc3 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -1027,22 +1027,6 @@ SymbolStringifier Client::symbolStringifier() const return m_symbolStringifier; } -void Client::setCompletionItemsTransformer(const CompletionItemsTransformer &transformer) -{ - if (const auto provider = qobject_cast( - m_clientProviders.completionAssistProvider)) { - provider->setItemsTransformer(transformer); - } -} - -void Client::setCompletionApplyHelper(const CompletionApplyHelper &applyHelper) -{ - if (const auto provider = qobject_cast( - m_clientProviders.completionAssistProvider)) { - provider->setApplyHelper(applyHelper); - } -} - void Client::setCompletionProposalHandler(const ProposalHandler &handler) { if (const auto provider = qobject_cast( @@ -1067,6 +1051,12 @@ void Client::setSnippetsGroup(const QString &group) } } +void Client::setCompletionAssistProvider(LanguageClientCompletionAssistProvider *provider) +{ + delete m_clientProviders.completionAssistProvider; + m_clientProviders.completionAssistProvider = provider; +} + void Client::start() { LanguageClientManager::addClient(this); diff --git a/src/plugins/languageclient/client.h b/src/plugins/languageclient/client.h index c606c2760c4..03377ebe15b 100644 --- a/src/plugins/languageclient/client.h +++ b/src/plugins/languageclient/client.h @@ -183,11 +183,10 @@ public: void setSemanticTokensHandler(const SemanticTokensHandler &handler); void setSymbolStringifier(const LanguageServerProtocol::SymbolStringifier &stringifier); LanguageServerProtocol::SymbolStringifier symbolStringifier() const; - void setCompletionItemsTransformer(const CompletionItemsTransformer &transformer); - void setCompletionApplyHelper(const CompletionApplyHelper &applyHelper); void setCompletionProposalHandler(const ProposalHandler &handler); void setFunctionHintProposalHandler(const ProposalHandler &handler); void setSnippetsGroup(const QString &group); + void setCompletionAssistProvider(LanguageClientCompletionAssistProvider *provider); // logging void log(const QString &message) const; diff --git a/src/plugins/languageclient/languageclientcompletionassist.h b/src/plugins/languageclient/languageclientcompletionassist.h index fe979808912..4184aacbd05 100644 --- a/src/plugins/languageclient/languageclientcompletionassist.h +++ b/src/plugins/languageclient/languageclientcompletionassist.h @@ -71,11 +71,14 @@ public: void setTriggerCharacters(const Utils::optional> triggerChars); - void setItemsTransformer(const CompletionItemsTransformer &transformer); - void setApplyHelper(const CompletionApplyHelper &applyHelper); void setProposalHandler(const ProposalHandler &handler) { m_proposalHandler = handler; } void setSnippetsGroup(const QString &group) { m_snippetsGroup = group; } +protected: + void setItemsTransformer(const CompletionItemsTransformer &transformer); + void setApplyHelper(const CompletionApplyHelper &applyHelper); + Client *client() const { return m_client; } + private: QList m_triggerChars; CompletionItemsTransformer m_itemsTransformer;