aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorFawzi Mohamed <fawzi.mohamed@qt.io>2022-01-11 14:15:42 +0100
committerFawzi Mohamed <fawzi.mohamed@qt.io>2022-05-19 11:03:30 +0200
commit2717985373dc3e2765ced402c775a8b4386b1e93 (patch)
tree1dcee34c4e17f7d5f692e27cda0a2c42ae9297a8 /tools
parent9290a86ac496cdbf313a902babd6f40be0608254 (diff)
qmlls: workspace support
Add support for workspace (project files, changes detection) to qmlls Change-Id: Ife6b40a1ace7339d2185f790bce3291e7c680438 Reviewed-by: Maximilian Goldstein <max.goldstein@qt.io>
Diffstat (limited to 'tools')
-rw-r--r--tools/qmlls/CMakeLists.txt1
-rw-r--r--tools/qmlls/qqmllanguageserver.cpp9
-rw-r--r--tools/qmlls/qqmllanguageserver.h3
-rw-r--r--tools/qmlls/workspace.cpp189
-rw-r--r--tools/qmlls/workspace.h57
5 files changed, 258 insertions, 1 deletions
diff --git a/tools/qmlls/CMakeLists.txt b/tools/qmlls/CMakeLists.txt
index 7e74677e29..57c8e28d0e 100644
--- a/tools/qmlls/CMakeLists.txt
+++ b/tools/qmlls/CMakeLists.txt
@@ -10,6 +10,7 @@ qt_internal_add_tool(${target_name}
qlanguageserver.h qlanguageserver_p.h qlanguageserver.cpp
qqmllanguageserver.h qqmllanguageserver.cpp
qmllanguageservertool.cpp
+ workspace.cpp workspace.h
textblock.h textblock.cpp
textcursor.h textcursor.cpp
textcursor.cpp textcursor.h
diff --git a/tools/qmlls/qqmllanguageserver.cpp b/tools/qmlls/qqmllanguageserver.cpp
index 47955a6723..5777f8747d 100644
--- a/tools/qmlls/qqmllanguageserver.cpp
+++ b/tools/qmlls/qqmllanguageserver.cpp
@@ -68,11 +68,13 @@ QQmlLanguageServer::QQmlLanguageServer(std::function<void(const QByteArray &)> s
: m_codeModel(nullptr, settings),
m_server(sendData),
m_textSynchronization(&m_codeModel),
- m_lint(&m_server, &m_codeModel)
+ m_lint(&m_server, &m_codeModel),
+ m_workspace(&m_codeModel)
{
m_server.addServerModule(this);
m_server.addServerModule(&m_textSynchronization);
m_server.addServerModule(&m_lint);
+ m_server.addServerModule(&m_workspace);
m_server.finishSetup();
qCWarning(lspServerLog) << "Did Setup";
}
@@ -138,6 +140,11 @@ QmlLintSuggestions *QQmlLanguageServer::lint()
return &m_lint;
}
+WorkspaceHandlers *QQmlLanguageServer::worspace()
+{
+ return &m_workspace;
+}
+
} // namespace QmlLsp
QT_END_NAMESPACE
diff --git a/tools/qmlls/qqmllanguageserver.h b/tools/qmlls/qqmllanguageserver.h
index 1794729f71..d9d17cc0e6 100644
--- a/tools/qmlls/qqmllanguageserver.h
+++ b/tools/qmlls/qqmllanguageserver.h
@@ -32,6 +32,7 @@
#include "qqmlcodemodel.h"
#include "textsynchronization.h"
#include "qmllintsuggestions.h"
+#include "workspace.h"
#include "../shared/qqmltoolingsettings.h"
QT_BEGIN_NAMESPACE
@@ -68,6 +69,7 @@ public:
QLanguageServer *server();
TextSynchronization *textSynchronization();
QmlLintSuggestions *lint();
+ WorkspaceHandlers *worspace();
public slots:
void exit();
@@ -78,6 +80,7 @@ private:
QLanguageServer m_server;
TextSynchronization m_textSynchronization;
QmlLintSuggestions m_lint;
+ WorkspaceHandlers m_workspace;
int m_returnValue = 1;
};
diff --git a/tools/qmlls/workspace.cpp b/tools/qmlls/workspace.cpp
new file mode 100644
index 0000000000..88c3671c4f
--- /dev/null
+++ b/tools/qmlls/workspace.cpp
@@ -0,0 +1,189 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "workspace.h"
+#include "qqmllanguageserver.h"
+#include <QtLanguageServer/private/qlanguageserverspectypes_p.h>
+#include <QtLanguageServer/private/qlspnotifysignals_p.h>
+
+#include <QtCore/qfile.h>
+#include <variant>
+
+QT_BEGIN_NAMESPACE
+using namespace Qt::StringLiterals;
+using namespace QLspSpecification;
+
+void WorkspaceHandlers::registerHandlers(QLanguageServer *server, QLanguageServerProtocol *)
+{
+ QObject::connect(server->notifySignals(),
+ &QLspNotifySignals::receivedDidChangeWorkspaceFoldersNotification, this,
+ [server, this](const DidChangeWorkspaceFoldersParams &params) {
+ const WorkspaceFoldersChangeEvent &event = params.event;
+
+ const QList<WorkspaceFolder> &removed = event.removed;
+ QList<QByteArray> toRemove;
+ for (const WorkspaceFolder &folder : removed) {
+ toRemove.append(QmlLsp::lspUriToQmlUrl(folder.uri));
+ m_codeModel->removeDirectory(
+ m_codeModel->url2Path(QmlLsp::lspUriToQmlUrl(folder.uri)));
+ }
+ m_codeModel->removeRootUrls(toRemove);
+ const QList<WorkspaceFolder> &added = event.added;
+ QList<QByteArray> toAdd;
+ QStringList pathsToAdd;
+ for (const WorkspaceFolder &folder : added) {
+ toAdd.append(QmlLsp::lspUriToQmlUrl(folder.uri));
+ pathsToAdd.append(
+ m_codeModel->url2Path(QmlLsp::lspUriToQmlUrl(folder.uri)));
+ }
+ m_codeModel->addRootUrls(toAdd);
+ m_codeModel->addDirectoriesToIndex(pathsToAdd, server);
+ });
+
+ QObject::connect(server->notifySignals(),
+ &QLspNotifySignals::receivedDidChangeWatchedFilesNotification, this,
+ [this](const DidChangeWatchedFilesParams &params) {
+ const QList<FileEvent> &changes = params.changes;
+ for (const FileEvent &change : changes) {
+ const QString filename =
+ m_codeModel->url2Path(QmlLsp::lspUriToQmlUrl(change.uri));
+ switch (FileChangeType(change.type)) {
+ case FileChangeType::Created:
+ // m_codeModel->addFile(filename);
+ break;
+ case FileChangeType::Changed: {
+ QFile file(filename);
+ if (file.open(QIODevice::ReadOnly))
+ // m_modelManager->setFileContents(filename, file.readAll());
+ break;
+ }
+ case FileChangeType::Deleted:
+ // m_modelManager->removeFile(filename);
+ break;
+ }
+ }
+ // update due to dep changes...
+ });
+
+ QObject::connect(server, &QLanguageServer::clientInitialized, this,
+ &WorkspaceHandlers::clientInitialized);
+}
+
+QString WorkspaceHandlers::name() const
+{
+ return u"Workspace"_s;
+}
+
+void WorkspaceHandlers::setupCapabilities(const QLspSpecification::InitializeParams &clientInfo,
+ QLspSpecification::InitializeResult &serverInfo)
+{
+ if (!clientInfo.capabilities.workspace
+ || !clientInfo.capabilities.workspace->value("workspaceFolders").toBool(false))
+ return;
+ WorkspaceFoldersServerCapabilities folders;
+ folders.supported = true;
+ folders.changeNotifications = true;
+ if (!serverInfo.capabilities.workspace)
+ serverInfo.capabilities.workspace = QJsonObject();
+ serverInfo.capabilities.workspace->insert("workspaceFolders", QTypedJson::toJsonValue(folders));
+}
+
+void WorkspaceHandlers::clientInitialized(QLanguageServer *server)
+{
+ QLanguageServerProtocol *protocol = server->protocol();
+ const auto clientInfo = server->clientInfo();
+ QList<Registration> registrations;
+ if (clientInfo.capabilities.workspace
+ && clientInfo.capabilities.workspace->value("didChangeWatchedFiles")["dynamicRegistration"]
+ .toBool(false)) {
+ const int watchAll =
+ int(WatchKind::Create) | int(WatchKind::Change) | int(WatchKind::Delete);
+ DidChangeWatchedFilesRegistrationOptions watchedFilesParams;
+ FileSystemWatcher qmlWatcher;
+ qmlWatcher.globPattern = QByteArray("*.{qml,js,mjs}");
+ qmlWatcher.kind = watchAll;
+ FileSystemWatcher qmldirWatcher;
+ qmldirWatcher.globPattern = "qmldir";
+ qmldirWatcher.kind = watchAll;
+ FileSystemWatcher qmltypesWatcher;
+ qmltypesWatcher.globPattern = QByteArray("*.qmltypes");
+ qmltypesWatcher.kind = watchAll;
+ watchedFilesParams.watchers =
+ QList<FileSystemWatcher>({ qmlWatcher, qmldirWatcher, qmltypesWatcher });
+ registrations.append(Registration {
+ // use ClientCapabilitiesInfo::WorkspaceDidChangeWatchedFiles as id too
+ ClientCapabilitiesInfo::WorkspaceDidChangeWatchedFiles,
+ ClientCapabilitiesInfo::WorkspaceDidChangeWatchedFiles,
+ QTypedJson::toJsonValue(watchedFilesParams) });
+ }
+
+ if (!registrations.isEmpty()) {
+ RegistrationParams params;
+ params.registrations = registrations;
+ protocol->requestRegistration(
+ params,
+ []() {
+ // successful registration
+ },
+ [protocol](const ResponseError &err) {
+ LogMessageParams msg;
+ msg.message = QByteArray("registration of file udates failed, will miss file "
+ "changes done outside the editor due to error ");
+ msg.message.append(QString::number(err.code).toUtf8());
+ if (!err.message.isEmpty())
+ msg.message.append(" ");
+ msg.message.append(err.message);
+ msg.type = MessageType::Warning;
+ qCWarning(lspServerLog) << QString::fromUtf8(msg.message);
+ protocol->notifyLogMessage(msg);
+ });
+ }
+
+ QSet<QString> rootPaths;
+ if (std::holds_alternative<QByteArray>(clientInfo.rootUri)) {
+ QString path = m_codeModel->url2Path(
+ QmlLsp::lspUriToQmlUrl(std::get<QByteArray>(clientInfo.rootUri)));
+ rootPaths.insert(path);
+ } else if (clientInfo.rootPath && std::holds_alternative<QByteArray>(*clientInfo.rootPath)) {
+ QString path = QString::fromUtf8(std::get<QByteArray>(*clientInfo.rootPath));
+ rootPaths.insert(path);
+ }
+
+ if (clientInfo.workspaceFolders
+ && std::holds_alternative<QList<WorkspaceFolder>>(*clientInfo.workspaceFolders)) {
+ for (const WorkspaceFolder &workspace :
+ qAsConst(std::get<QList<WorkspaceFolder>>(*clientInfo.workspaceFolders))) {
+ const QUrl workspaceUrl(QString::fromUtf8(QmlLsp::lspUriToQmlUrl(workspace.uri)));
+ rootPaths.insert(workspaceUrl.toLocalFile());
+ }
+ }
+ if (m_status == Status::Indexing)
+ m_codeModel->addDirectoriesToIndex(QStringList(rootPaths.begin(), rootPaths.end()), server);
+}
+
+QT_END_NAMESPACE
diff --git a/tools/qmlls/workspace.h b/tools/qmlls/workspace.h
new file mode 100644
index 0000000000..c1dda16eab
--- /dev/null
+++ b/tools/qmlls/workspace.h
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef WORKSPACE_H
+#define WORKSPACE_H
+
+#include "qqmlcodemodel.h"
+#include "qlanguageserver.h"
+
+QT_BEGIN_NAMESPACE
+
+class WorkspaceHandlers : public QLanguageServerModule
+{
+ Q_OBJECT
+public:
+ enum class Status { NoIndex, Indexing };
+ WorkspaceHandlers(QmlLsp::QQmlCodeModel *codeModel) : m_codeModel(codeModel) { }
+ QString name() const override;
+ void registerHandlers(QLanguageServer *server, QLanguageServerProtocol *protocol) override;
+ void setupCapabilities(const QLspSpecification::InitializeParams &clientInfo,
+ QLspSpecification::InitializeResult &) override;
+public slots:
+ void clientInitialized(QLanguageServer *);
+
+private:
+ QmlLsp::QQmlCodeModel *m_codeModel = nullptr;
+ Status m_status = Status::NoIndex;
+};
+
+QT_END_NAMESPACE
+
+#endif // WORKSPACE_H