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
DEFINES
IDE_LIBRARY_BASENAME="${IDE_LIBRARY_BASE_PATH}"
PLUGIN_DEPENDS Core ProjectExplorer CppEditor QtSupport QmlJSTools
PLUGIN_DEPENDS Core CppEditor LanguageClient ProjectExplorer QmlJSTools QmlJSEditor QtSupport
SOURCES
customqbspropertiesdialog.cpp customqbspropertiesdialog.h
defaultpropertyprovider.cpp defaultpropertyprovider.h
@@ -10,8 +10,10 @@ add_qtc_plugin(QbsProjectManager
qbsbuildconfiguration.cpp qbsbuildconfiguration.h
qbsbuildstep.cpp qbsbuildstep.h
qbscleanstep.cpp qbscleanstep.h
qbseditor.cpp qbseditor.h
qbsinstallstep.cpp qbsinstallstep.h
qbskitaspect.cpp qbskitaspect.h
qbslanguageclient.cpp qbslanguageclient.h
qbsnodes.cpp qbsnodes.h
qbsnodetreebuilder.cpp qbsnodetreebuilder.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: "Utils" }
Depends { name: "ProjectExplorer" }
Depends { name: "Core" }
Depends { name: "CppEditor" }
Depends { name: "QtSupport" }
Depends { name: "LanguageClient" }
Depends { name: "ProjectExplorer" }
Depends { name: "QmlJSEditor" }
Depends { name: "QmlJSTools" }
Depends { name: "QtSupport" }
files: [
"customqbspropertiesdialog.h",
@@ -29,10 +31,14 @@ QtcPlugin {
"qbsbuildstep.h",
"qbscleanstep.cpp",
"qbscleanstep.h",
"qbseditor.cpp",
"qbseditor.h",
"qbsinstallstep.cpp",
"qbsinstallstep.h",
"qbskitaspect.cpp",
"qbskitaspect.h",
"qbslanguageclient.cpp",
"qbslanguageclient.h",
"qbsnodes.cpp",
"qbsnodes.h",
"qbsnodetreebuilder.cpp",
@@ -48,7 +54,8 @@ QtcPlugin {
"qbsprojectimporter.cpp",
"qbsprojectimporter.h",
"qbsprojectmanager.qrc",
"qbsprojectmanager_global.h", "qbsprojectmanagertr.h",
"qbsprojectmanager_global.h",
"qbsprojectmanagertr.h",
"qbsprojectmanagerconstants.h",
"qbsprojectmanagerplugin.cpp",
"qbsprojectmanagerplugin.h",

View File

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

View File

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

View File

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

View File

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