forked from qt-creator/qt-creator
QbsProjectManager: Support completion via qbs' LSP server
Task-number: QBS-395 Change-Id: I2571dc46c9fb2867daeb3a5d00709337b12a750b Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
@@ -6,13 +6,23 @@
|
|||||||
#include "qbslanguageclient.h"
|
#include "qbslanguageclient.h"
|
||||||
#include "qbsprojectmanagertr.h"
|
#include "qbsprojectmanagertr.h"
|
||||||
|
|
||||||
|
#include <languageclient/languageclientcompletionassist.h>
|
||||||
#include <languageclient/languageclientmanager.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 <utils/mimeconstants.h>
|
||||||
|
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
using namespace LanguageClient;
|
using namespace LanguageClient;
|
||||||
using namespace QmlJSEditor;
|
using namespace QmlJSEditor;
|
||||||
|
using namespace TextEditor;
|
||||||
using namespace Utils;
|
using namespace Utils;
|
||||||
|
|
||||||
namespace QbsProjectManager::Internal {
|
namespace QbsProjectManager::Internal {
|
||||||
@@ -26,11 +36,79 @@ private:
|
|||||||
bool inNextSplit = false) override;
|
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")
|
QbsEditorFactory::QbsEditorFactory() : QmlJSEditorFactory("QbsEditor.QbsEditor")
|
||||||
{
|
{
|
||||||
setDisplayName(Tr::tr("Qbs Editor"));
|
setDisplayName(Tr::tr("Qbs Editor"));
|
||||||
setMimeTypes({Utils::Constants::QBS_MIMETYPE});
|
setMimeTypes({Utils::Constants::QBS_MIMETYPE});
|
||||||
setEditorWidgetCreator([] { return new QbsEditorWidget; });
|
setEditorWidgetCreator([] { return new QbsEditorWidget; });
|
||||||
|
setCompletionAssistProvider(new QbsCompletionAssistProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QbsEditorWidget::findLinkAt(const QTextCursor &cursor, const LinkHandler &processLinkCallback,
|
void QbsEditorWidget::findLinkAt(const QTextCursor &cursor, const LinkHandler &processLinkCallback,
|
||||||
@@ -43,18 +121,103 @@ void QbsEditorWidget::findLinkAt(const QTextCursor &cursor, const LinkHandler &p
|
|||||||
if (!self)
|
if (!self)
|
||||||
return;
|
return;
|
||||||
const auto doc = self->textDocument();
|
const auto doc = self->textDocument();
|
||||||
if (!doc)
|
if (Client * const client = clientForDocument(doc)) {
|
||||||
return;
|
client->findLinkAt(doc, cursor, processLinkCallback, resolveTarget,
|
||||||
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,
|
|
||||||
LinkTarget::SymbolDef);
|
LinkTarget::SymbolDef);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
QmlJSEditorWidget::findLinkAt(cursor, extendedCallback, resolveTarget, inNextSplit);
|
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
|
} // namespace QbsProjectManager::Internal
|
||||||
|
@@ -45,6 +45,7 @@ public:
|
|||||||
virtual int indexOf(const std::function<bool (AssistProposalItemInterface *)> &predicate) const;
|
virtual int indexOf(const std::function<bool (AssistProposalItemInterface *)> &predicate) const;
|
||||||
|
|
||||||
void loadContent(const QList<AssistProposalItemInterface *> &items);
|
void loadContent(const QList<AssistProposalItemInterface *> &items);
|
||||||
|
const QList<AssistProposalItemInterface *> &originalItems() const { return m_originalItems; }
|
||||||
|
|
||||||
bool isPerfectMatch(const QString &prefix) const;
|
bool isPerfectMatch(const QString &prefix) const;
|
||||||
bool hasItemsToPropose(const QString &prefix, AssistReason reason) const;
|
bool hasItemsToPropose(const QString &prefix, AssistReason reason) const;
|
||||||
|
Reference in New Issue
Block a user