diff options
Diffstat (limited to 'src/plugins/qbsprojectmanager/qbseditor.cpp')
-rw-r--r-- | src/plugins/qbsprojectmanager/qbseditor.cpp | 179 |
1 files changed, 171 insertions, 8 deletions
diff --git a/src/plugins/qbsprojectmanager/qbseditor.cpp b/src/plugins/qbsprojectmanager/qbseditor.cpp index 7eed6977a2..08f6e0a872 100644 --- a/src/plugins/qbsprojectmanager/qbseditor.cpp +++ b/src/plugins/qbsprojectmanager/qbseditor.cpp @@ -6,13 +6,23 @@ #include "qbslanguageclient.h" #include "qbsprojectmanagertr.h" +#include <languageclient/languageclientcompletionassist.h> #include <languageclient/languageclientmanager.h> +#include <projectexplorer/projectexplorerconstants.h> +#include <projectexplorer/projectnodes.h> +#include <qmljseditor/qmljscompletionassist.h> +#include <texteditor/codeassist/genericproposal.h> +#include <utils/utilsicons.h> #include <utils/mimeconstants.h> #include <QPointer> +#include <memory> +#include <optional> + using namespace LanguageClient; using namespace QmlJSEditor; +using namespace TextEditor; using namespace Utils; namespace QbsProjectManager::Internal { @@ -26,11 +36,79 @@ private: bool inNextSplit = false) override; }; +class QbsCompletionAssistProcessor : public LanguageClientCompletionAssistProcessor +{ +public: + QbsCompletionAssistProcessor(Client *client); + +private: + QList<AssistProposalItemInterface *> generateCompletionItems( + const QList<LanguageServerProtocol::CompletionItem> &items) const override; +}; + +class MergedCompletionAssistProcessor : public IAssistProcessor +{ +public: + MergedCompletionAssistProcessor(const AssistInterface *interface) : m_interface(interface) {} + ~MergedCompletionAssistProcessor(); + +private: + IAssistProposal *perform() override; + bool running() override { return m_started && (!m_qmlProposal || !m_qbsProposal); } + void checkFinished(); + + const AssistInterface * const m_interface; + std::unique_ptr<IAssistProcessor> m_qmlProcessor; + std::unique_ptr<IAssistProcessor> m_qbsProcessor; + std::optional<IAssistProposal *> m_qmlProposal; + std::optional<IAssistProposal *> m_qbsProposal; + bool m_started = false; +}; + +class QbsCompletionAssistProvider : public QmlJSCompletionAssistProvider +{ +private: + IAssistProcessor *createProcessor(const AssistInterface *interface) const override + { + return new MergedCompletionAssistProcessor(interface); + } +}; + +class QbsCompletionItem : public LanguageClientCompletionItem +{ +public: + using LanguageClientCompletionItem::LanguageClientCompletionItem; + +private: + QIcon icon() const override; +}; + +class MergedProposalModel : public GenericProposalModel +{ +public: + MergedProposalModel(const QList<GenericProposalModelPtr> &sourceModels); +}; + +static Client *clientForDocument(const TextDocument *doc) +{ + if (!doc) + return nullptr; + const QList<Client *> &candidates = LanguageClientManager::clientsSupportingDocument(doc); + for (Client * const candidate : candidates) { + if (const auto qbsClient = qobject_cast<QbsLanguageClient *>(candidate); + qbsClient && qbsClient->isActive() && qbsClient->documentOpen(doc)) { + return qbsClient; + } + } + return nullptr; +} + QbsEditorFactory::QbsEditorFactory() : QmlJSEditorFactory("QbsEditor.QbsEditor") { setDisplayName(Tr::tr("Qbs Editor")); setMimeTypes({Utils::Constants::QBS_MIMETYPE}); setEditorWidgetCreator([] { return new QbsEditorWidget; }); + setCompletionAssistProvider(new QbsCompletionAssistProvider); } void QbsEditorWidget::findLinkAt(const QTextCursor &cursor, const LinkHandler &processLinkCallback, @@ -43,18 +121,103 @@ void QbsEditorWidget::findLinkAt(const QTextCursor &cursor, const LinkHandler &p if (!self) return; const auto doc = self->textDocument(); - if (!doc) - return; - const QList<Client *> &candidates = LanguageClientManager::clientsSupportingDocument(doc); - for (Client * const candidate : candidates) { - const auto qbsClient = qobject_cast<QbsLanguageClient *>(candidate); - if (!qbsClient || !qbsClient->isActive() || !qbsClient->documentOpen(doc)) - continue; - qbsClient->findLinkAt(doc, cursor, processLinkCallback, resolveTarget, + if (Client * const client = clientForDocument(doc)) { + client->findLinkAt(doc, cursor, processLinkCallback, resolveTarget, LinkTarget::SymbolDef); } }; QmlJSEditorWidget::findLinkAt(cursor, extendedCallback, resolveTarget, inNextSplit); } +MergedCompletionAssistProcessor::~MergedCompletionAssistProcessor() +{ + if (m_qmlProposal) + delete *m_qmlProposal; + if (m_qbsProposal) + delete *m_qbsProposal; +} + +IAssistProposal *MergedCompletionAssistProcessor::perform() +{ + m_started = true; + if (Client *const qbsClient = clientForDocument( + TextDocument::textDocumentForFilePath(m_interface->filePath()))) { + m_qbsProcessor.reset(new QbsCompletionAssistProcessor(qbsClient)); + m_qbsProcessor->setAsyncCompletionAvailableHandler([this](IAssistProposal *proposal) { + m_qbsProposal = proposal; + checkFinished(); + }); + m_qbsProcessor->start(std::make_unique<AssistInterface>(m_interface->cursor(), + m_interface->filePath(), + ExplicitlyInvoked)); + } else { + m_qbsProposal = nullptr; + } + m_qmlProcessor.reset(QmlJSCompletionAssistProvider().createProcessor(m_interface)); + m_qmlProcessor->setAsyncCompletionAvailableHandler([this](IAssistProposal *proposal) { + m_qmlProposal = proposal; + checkFinished(); + }); + const auto qmlJsIface = static_cast<const QmlJSCompletionAssistInterface *>(m_interface); + return m_qmlProcessor->start( + std::make_unique<QmlJSCompletionAssistInterface>(qmlJsIface->cursor(), + qmlJsIface->filePath(), + ExplicitlyInvoked, + qmlJsIface->semanticInfo())); +} + +void MergedCompletionAssistProcessor::checkFinished() +{ + if (running()) + return; + + QList<GenericProposalModelPtr> sourceModels; + int basePosition = -1; + for (const IAssistProposal * const proposal : {*m_qmlProposal, *m_qbsProposal}) { + if (proposal) { + if (const auto model = proposal->model().dynamicCast<GenericProposalModel>()) + sourceModels << model; + if (basePosition == -1) + basePosition = proposal->basePosition(); + else + QTC_CHECK(basePosition == proposal->basePosition()); + } + } + setAsyncProposalAvailable( + new GenericProposal(basePosition >= 0 ? basePosition : m_interface->position(), + GenericProposalModelPtr(new MergedProposalModel(sourceModels)))); +} + +MergedProposalModel::MergedProposalModel(const QList<GenericProposalModelPtr> &sourceModels) +{ + QList<AssistProposalItemInterface *> items; + for (const GenericProposalModelPtr &model : sourceModels) { + items << model->originalItems(); + model->loadContent({}); + } + loadContent(items); +} + +QbsCompletionAssistProcessor::QbsCompletionAssistProcessor(Client *client) + : LanguageClientCompletionAssistProcessor(client, nullptr, {}) +{} + +QList<AssistProposalItemInterface *> QbsCompletionAssistProcessor::generateCompletionItems( + const QList<LanguageServerProtocol::CompletionItem> &items) const +{ + return Utils::transform<QList<AssistProposalItemInterface *>>( + items, [](const LanguageServerProtocol::CompletionItem &item) { + return new QbsCompletionItem(item); + }); +} + +QIcon QbsCompletionItem::icon() const +{ + if (!item().detail()) { + return ProjectExplorer::DirectoryIcon( + ProjectExplorer::Constants::FILEOVERLAY_MODULES).icon(); + } + return CodeModelIcon::iconForType(CodeModelIcon::Property); +} + } // namespace QbsProjectManager::Internal |