QbsProjectManager: Add a language client

... and use it to follow symbols that QmlJSEditor does not know about.
For now, the only implemented case on the server side is getting to a
product or module via a Depends item.
More functionality will follow.

Change-Id: I597c7ab10f4bf6962684ed26357dfc0eef3a6c15
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Kandeler
2023-10-23 15:32:53 +02:00
parent 32ed2cb9c7
commit ba249a3514
10 changed files with 219 additions and 7 deletions

View File

@@ -2,7 +2,7 @@ add_qtc_plugin(QbsProjectManager
DEPENDS Qt::Qml Qt::Widgets QmlJS DEPENDS Qt::Qml Qt::Widgets QmlJS
DEFINES DEFINES
IDE_LIBRARY_BASENAME="${IDE_LIBRARY_BASE_PATH}" IDE_LIBRARY_BASENAME="${IDE_LIBRARY_BASE_PATH}"
PLUGIN_DEPENDS Core ProjectExplorer CppEditor QtSupport QmlJSTools PLUGIN_DEPENDS Core CppEditor LanguageClient ProjectExplorer QmlJSTools QmlJSEditor QtSupport
SOURCES SOURCES
customqbspropertiesdialog.cpp customqbspropertiesdialog.h customqbspropertiesdialog.cpp customqbspropertiesdialog.h
defaultpropertyprovider.cpp defaultpropertyprovider.h defaultpropertyprovider.cpp defaultpropertyprovider.h
@@ -10,8 +10,10 @@ add_qtc_plugin(QbsProjectManager
qbsbuildconfiguration.cpp qbsbuildconfiguration.h qbsbuildconfiguration.cpp qbsbuildconfiguration.h
qbsbuildstep.cpp qbsbuildstep.h qbsbuildstep.cpp qbsbuildstep.h
qbscleanstep.cpp qbscleanstep.h qbscleanstep.cpp qbscleanstep.h
qbseditor.cpp qbseditor.h
qbsinstallstep.cpp qbsinstallstep.h qbsinstallstep.cpp qbsinstallstep.h
qbskitaspect.cpp qbskitaspect.h qbskitaspect.cpp qbskitaspect.h
qbslanguageclient.cpp qbslanguageclient.h
qbsnodes.cpp qbsnodes.h qbsnodes.cpp qbsnodes.h
qbsnodetreebuilder.cpp qbsnodetreebuilder.h qbsnodetreebuilder.cpp qbsnodetreebuilder.h
qbspmlogging.cpp qbspmlogging.h qbspmlogging.cpp qbspmlogging.h

View File

@@ -0,0 +1,60 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qbseditor.h"
#include "qbslanguageclient.h"
#include "qbsprojectmanagertr.h"
#include <languageclient/languageclientmanager.h>
#include <utils/mimeconstants.h>
#include <QPointer>
using namespace LanguageClient;
using namespace QmlJSEditor;
using namespace Utils;
namespace QbsProjectManager::Internal {
class QbsEditorWidget : public QmlJSEditorWidget
{
private:
void findLinkAt(const QTextCursor &cursor,
const LinkHandler &processLinkCallback,
bool resolveTarget = true,
bool inNextSplit = false) override;
};
QbsEditorFactory::QbsEditorFactory() : QmlJSEditorFactory("QbsEditor.QbsEditor")
{
setDisplayName(Tr::tr("Qbs Editor"));
setMimeTypes({Utils::Constants::QBS_MIMETYPE});
setEditorWidgetCreator([] { return new QbsEditorWidget; });
}
void QbsEditorWidget::findLinkAt(const QTextCursor &cursor, const LinkHandler &processLinkCallback,
bool resolveTarget, bool inNextSplit)
{
const LinkHandler extendedCallback = [self = QPointer(this), cursor, processLinkCallback,
resolveTarget](const Link &link) {
if (link.hasValidTarget())
return processLinkCallback(link);
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,
LinkTarget::SymbolDef);
}
};
QmlJSEditorWidget::findLinkAt(cursor, extendedCallback, resolveTarget, inNextSplit);
}
} // namespace QbsProjectManager::Internal

View File

@@ -0,0 +1,16 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <qmljseditor/qmljseditor.h>
namespace QbsProjectManager::Internal {
class QbsEditorFactory : public QmlJSEditor::QmlJSEditorFactory
{
public:
QbsEditorFactory();
};
} // namespace QbsProjectManager::Internal

View File

@@ -0,0 +1,90 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qbslanguageclient.h"
#include "qbsproject.h"
#include "qbssettings.h"
#include <coreplugin/editormanager/documentmodel.h>
#include <coreplugin/editormanager/editormanager.h>
#include <languageclient/languageclientinterface.h>
#include <languageclient/languageclientsettings.h>
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/target.h>
#include <utils/filepath.h>
#include <utils/mimeconstants.h>
#include <QPointer>
using namespace Core;
using namespace LanguageClient;
using namespace TextEditor;
using namespace Utils;
namespace QbsProjectManager::Internal {
class QbsLanguageClientInterface : public LocalSocketClientInterface
{
public:
QbsLanguageClientInterface(const QString &serverPath)
: LocalSocketClientInterface(serverPath),
m_qbsExecutable(QbsSettings::qbsExecutableFilePath()) {}
private:
Utils::FilePath serverDeviceTemplate() const override{ return m_qbsExecutable; };
const FilePath m_qbsExecutable;
};
class QbsLanguageClient::Private
{
public:
Private(QbsLanguageClient * q) : q(q) {}
void checkDocument(IDocument *document);
QbsLanguageClient * const q;
QPointer<QbsBuildSystem> buildSystem;
};
QbsLanguageClient::QbsLanguageClient(const QString &serverPath, QbsBuildSystem *buildSystem)
: Client(new QbsLanguageClientInterface(serverPath)), d(new Private(this))
{
d->buildSystem = buildSystem;
setName(QString::fromLatin1("qbs@%1").arg(serverPath));
setCurrentProject(buildSystem->project());
LanguageFilter langFilter;
langFilter.mimeTypes << Utils::Constants::QBS_MIMETYPE;
setSupportedLanguage(langFilter);
connect(EditorManager::instance(), &EditorManager::documentOpened,
this, [this](IDocument *document) { d->checkDocument(document); });
const QList<IDocument *> &allDocuments = DocumentModel::openedDocuments();
for (IDocument * const document : allDocuments)
d->checkDocument(document);
start();
}
QbsLanguageClient::~QbsLanguageClient() { delete d; }
bool QbsLanguageClient::isActive() const
{
if (!d->buildSystem)
return false;
if (!d->buildSystem->target()->activeBuildConfiguration())
return false;
if (d->buildSystem->target()->activeBuildConfiguration()->buildSystem() != d->buildSystem)
return false;
if (d->buildSystem->project()->activeTarget() != d->buildSystem->target())
return false;
return true;
}
void QbsLanguageClient::Private::checkDocument(IDocument *document)
{
if (const auto doc = qobject_cast<TextDocument *>(document))
q->openDocument(doc);
}
} // namespace QbsProjectManager::Internal

View File

@@ -0,0 +1,25 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <languageclient/client.h>
namespace QbsProjectManager::Internal {
class QbsBuildSystem;
class QbsLanguageClient : public LanguageClient::Client
{
Q_OBJECT
public:
QbsLanguageClient(const QString &serverPath, QbsBuildSystem *buildSystem);
~QbsLanguageClient() override;
bool isActive() const;
private:
class Private;
Private * const d;
};
} // namespace QbsProjectManager::Internal

View File

@@ -11,11 +11,13 @@ QtcPlugin {
Depends { name: "QmlJS" } Depends { name: "QmlJS" }
Depends { name: "Utils" } Depends { name: "Utils" }
Depends { name: "ProjectExplorer" }
Depends { name: "Core" } Depends { name: "Core" }
Depends { name: "CppEditor" } Depends { name: "CppEditor" }
Depends { name: "QtSupport" } Depends { name: "LanguageClient" }
Depends { name: "ProjectExplorer" }
Depends { name: "QmlJSEditor" }
Depends { name: "QmlJSTools" } Depends { name: "QmlJSTools" }
Depends { name: "QtSupport" }
files: [ files: [
"customqbspropertiesdialog.h", "customqbspropertiesdialog.h",
@@ -29,10 +31,14 @@ QtcPlugin {
"qbsbuildstep.h", "qbsbuildstep.h",
"qbscleanstep.cpp", "qbscleanstep.cpp",
"qbscleanstep.h", "qbscleanstep.h",
"qbseditor.cpp",
"qbseditor.h",
"qbsinstallstep.cpp", "qbsinstallstep.cpp",
"qbsinstallstep.h", "qbsinstallstep.h",
"qbskitaspect.cpp", "qbskitaspect.cpp",
"qbskitaspect.h", "qbskitaspect.h",
"qbslanguageclient.cpp",
"qbslanguageclient.h",
"qbsnodes.cpp", "qbsnodes.cpp",
"qbsnodes.h", "qbsnodes.h",
"qbsnodetreebuilder.cpp", "qbsnodetreebuilder.cpp",
@@ -48,7 +54,8 @@ QtcPlugin {
"qbsprojectimporter.cpp", "qbsprojectimporter.cpp",
"qbsprojectimporter.h", "qbsprojectimporter.h",
"qbsprojectmanager.qrc", "qbsprojectmanager.qrc",
"qbsprojectmanager_global.h", "qbsprojectmanagertr.h", "qbsprojectmanager_global.h",
"qbsprojectmanagertr.h",
"qbsprojectmanagerconstants.h", "qbsprojectmanagerconstants.h",
"qbsprojectmanagerplugin.cpp", "qbsprojectmanagerplugin.cpp",
"qbsprojectmanagerplugin.h", "qbsprojectmanagerplugin.h",

View File

@@ -6,6 +6,7 @@
#include "qbsbuildconfiguration.h" #include "qbsbuildconfiguration.h"
#include "qbsbuildstep.h" #include "qbsbuildstep.h"
#include "qbscleanstep.h" #include "qbscleanstep.h"
#include "qbseditor.h"
#include "qbsinstallstep.h" #include "qbsinstallstep.h"
#include "qbsnodes.h" #include "qbsnodes.h"
#include "qbsprofilessettingspage.h" #include "qbsprofilessettingspage.h"
@@ -68,6 +69,7 @@ public:
QbsInstallStepFactory installStepFactory; QbsInstallStepFactory installStepFactory;
QbsSettingsPage settingsPage; QbsSettingsPage settingsPage;
QbsProfilesSettingsPage profilesSetttingsPage; QbsProfilesSettingsPage profilesSetttingsPage;
QbsEditorFactory editorFactory;
}; };
QbsProjectManagerPlugin::~QbsProjectManagerPlugin() QbsProjectManagerPlugin::~QbsProjectManagerPlugin()

View File

@@ -3,7 +3,9 @@
#include "qbssession.h" #include "qbssession.h"
#include "qbslanguageclient.h"
#include "qbspmlogging.h" #include "qbspmlogging.h"
#include "qbsproject.h"
#include "qbsprojectmanagerconstants.h" #include "qbsprojectmanagerconstants.h"
#include "qbsprojectmanagertr.h" #include "qbsprojectmanagertr.h"
#include "qbssettings.h" #include "qbssettings.h"
@@ -130,6 +132,7 @@ class QbsSession::Private
{ {
public: public:
Process *qbsProcess = nullptr; Process *qbsProcess = nullptr;
QbsLanguageClient *languageClient = nullptr;
PacketReader *packetReader = nullptr; PacketReader *packetReader = nullptr;
QJsonObject currentRequest; QJsonObject currentRequest;
QJsonObject projectData; QJsonObject projectData;
@@ -140,7 +143,7 @@ public:
State state = State::Inactive; State state = State::Inactive;
}; };
QbsSession::QbsSession(QObject *parent) : QObject(parent), d(new Private) QbsSession::QbsSession(QbsBuildSystem *buildSystem) : QObject(buildSystem), d(new Private)
{ {
initialize(); initialize();
} }
@@ -449,6 +452,12 @@ void QbsSession::handlePacket(const QJsonObject &packet)
setError(Error::VersionMismatch); setError(Error::VersionMismatch);
return; return;
} }
if (packet.value("api-level").toInt() > 4) {
const QString lspSocket = packet.value("lsp-socket").toString();
if (!lspSocket.isEmpty())
d->languageClient = new QbsLanguageClient(lspSocket,
static_cast<QbsBuildSystem *>(parent()));
}
d->state = State::Active; d->state = State::Active;
sendQueuedRequest(); sendQueuedRequest();
} else if (type == "project-resolved") { } else if (type == "project-resolved") {
@@ -567,6 +576,7 @@ void QbsSession::setInactive()
if (d->qbsProcess->state() == QProcess::Running) if (d->qbsProcess->state() == QProcess::Running)
sendQuitPacket(); sendQuitPacket();
d->qbsProcess = nullptr; d->qbsProcess = nullptr;
d->languageClient = nullptr; // Owned by LanguageClientManager
} }
FileChangeResult QbsSession::updateFileList(const char *action, const QStringList &files, FileChangeResult QbsSession::updateFileList(const char *action, const QStringList &files,

View File

@@ -19,6 +19,7 @@ namespace ProjectExplorer { class Target; }
namespace QbsProjectManager { namespace QbsProjectManager {
namespace Internal { namespace Internal {
class QbsBuildSystem;
class ErrorInfoItem class ErrorInfoItem
{ {
@@ -96,7 +97,7 @@ class QbsSession : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit QbsSession(QObject *parent = nullptr); explicit QbsSession(QbsBuildSystem *buildSystem);
~QbsSession() override; ~QbsSession() override;
enum class State { Initializing, Active, Inactive }; enum class State { Initializing, Active, Inactive };

View File

@@ -1134,7 +1134,6 @@ QmlJSEditorFactory::QmlJSEditorFactory(Utils::Id _id)
using namespace Utils::Constants; using namespace Utils::Constants;
addMimeType(QML_MIMETYPE); addMimeType(QML_MIMETYPE);
addMimeType(QMLPROJECT_MIMETYPE); addMimeType(QMLPROJECT_MIMETYPE);
addMimeType(QBS_MIMETYPE);
addMimeType(QMLTYPES_MIMETYPE); addMimeType(QMLTYPES_MIMETYPE);
addMimeType(JS_MIMETYPE); addMimeType(JS_MIMETYPE);