1 | // Copyright (C) 2021 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qworkspace_p.h" |
5 | #include "qqmllanguageserver_p.h" |
6 | #include "qqmllsutils_p.h" |
7 | |
8 | #include <QtLanguageServer/private/qlanguageserverspectypes_p.h> |
9 | #include <QtLanguageServer/private/qlspnotifysignals_p.h> |
10 | |
11 | #include <QtCore/qfile.h> |
12 | #include <variant> |
13 | |
14 | QT_BEGIN_NAMESPACE |
15 | using namespace Qt::StringLiterals; |
16 | using namespace QLspSpecification; |
17 | |
18 | void WorkspaceHandlers::registerHandlers(QLanguageServer *server, QLanguageServerProtocol *) |
19 | { |
20 | QObject::connect(sender: server->notifySignals(), |
21 | signal: &QLspNotifySignals::receivedDidChangeWorkspaceFoldersNotification, context: this, |
22 | slot: [server, this](const DidChangeWorkspaceFoldersParams ¶ms) { |
23 | const WorkspaceFoldersChangeEvent &event = params.event; |
24 | |
25 | const QList<WorkspaceFolder> &removed = event.removed; |
26 | QList<QByteArray> toRemove; |
27 | for (const WorkspaceFolder &folder : removed) { |
28 | toRemove.append(t: QQmlLSUtils::lspUriToQmlUrl(uri: folder.uri)); |
29 | m_codeModel->removeDirectory(path: m_codeModel->url2Path( |
30 | url: QQmlLSUtils::lspUriToQmlUrl(uri: folder.uri))); |
31 | } |
32 | m_codeModel->removeRootUrls(urls: toRemove); |
33 | const QList<WorkspaceFolder> &added = event.added; |
34 | QList<QByteArray> toAdd; |
35 | QStringList pathsToAdd; |
36 | for (const WorkspaceFolder &folder : added) { |
37 | toAdd.append(t: QQmlLSUtils::lspUriToQmlUrl(uri: folder.uri)); |
38 | pathsToAdd.append(t: m_codeModel->url2Path( |
39 | url: QQmlLSUtils::lspUriToQmlUrl(uri: folder.uri))); |
40 | } |
41 | m_codeModel->addRootUrls(urls: toAdd); |
42 | m_codeModel->addDirectoriesToIndex(paths: pathsToAdd, server); |
43 | }); |
44 | |
45 | QObject::connect(sender: server->notifySignals(), |
46 | signal: &QLspNotifySignals::receivedDidChangeWatchedFilesNotification, context: this, |
47 | slot: [this](const DidChangeWatchedFilesParams ¶ms) { |
48 | const QList<FileEvent> &changes = params.changes; |
49 | for (const FileEvent &change : changes) { |
50 | const QString filename = |
51 | m_codeModel->url2Path(url: QQmlLSUtils::lspUriToQmlUrl(uri: change.uri)); |
52 | switch (FileChangeType(change.type)) { |
53 | case FileChangeType::Created: |
54 | // m_codeModel->addFile(filename); |
55 | break; |
56 | case FileChangeType::Changed: { |
57 | QFile file(filename); |
58 | if (file.open(flags: QIODevice::ReadOnly)) |
59 | // m_modelManager->setFileContents(filename, file.readAll()); |
60 | break; |
61 | break; |
62 | } |
63 | case FileChangeType::Deleted: |
64 | // m_modelManager->removeFile(filename); |
65 | break; |
66 | } |
67 | } |
68 | // update due to dep changes... |
69 | }); |
70 | |
71 | QObject::connect(sender: server, signal: &QLanguageServer::clientInitialized, context: this, |
72 | slot: &WorkspaceHandlers::clientInitialized); |
73 | } |
74 | |
75 | QString WorkspaceHandlers::name() const |
76 | { |
77 | return u"Workspace"_s ; |
78 | } |
79 | |
80 | void WorkspaceHandlers::setupCapabilities(const QLspSpecification::InitializeParams &clientInfo, |
81 | QLspSpecification::InitializeResult &serverInfo) |
82 | { |
83 | if (!clientInfo.capabilities.workspace |
84 | || !clientInfo.capabilities.workspace->value(key: u"workspaceFolders"_s ).toBool(defaultValue: false)) |
85 | return; |
86 | WorkspaceFoldersServerCapabilities folders; |
87 | folders.supported = true; |
88 | folders.changeNotifications = true; |
89 | if (!serverInfo.capabilities.workspace) |
90 | serverInfo.capabilities.workspace = QJsonObject(); |
91 | serverInfo.capabilities.workspace->insert(key: u"workspaceFolders"_s , |
92 | value: QTypedJson::toJsonValue(params: folders)); |
93 | } |
94 | |
95 | void WorkspaceHandlers::clientInitialized(QLanguageServer *server) |
96 | { |
97 | QLanguageServerProtocol *protocol = server->protocol(); |
98 | const auto clientInfo = server->clientInfo(); |
99 | QList<Registration> registrations; |
100 | if (clientInfo.capabilities.workspace |
101 | && clientInfo.capabilities.workspace |
102 | ->value(key: u"didChangeWatchedFiles"_s )[u"dynamicRegistration" _s] |
103 | .toBool(defaultValue: false)) { |
104 | const int watchAll = |
105 | int(WatchKind::Create) | int(WatchKind::Change) | int(WatchKind::Delete); |
106 | DidChangeWatchedFilesRegistrationOptions watchedFilesParams; |
107 | FileSystemWatcher qmlWatcher; |
108 | qmlWatcher.globPattern = QByteArray("*.{qml,js,mjs}" ); |
109 | qmlWatcher.kind = watchAll; |
110 | FileSystemWatcher qmldirWatcher; |
111 | qmldirWatcher.globPattern = "qmldir" ; |
112 | qmldirWatcher.kind = watchAll; |
113 | FileSystemWatcher qmltypesWatcher; |
114 | qmltypesWatcher.globPattern = QByteArray("*.qmltypes" ); |
115 | qmltypesWatcher.kind = watchAll; |
116 | watchedFilesParams.watchers = QList<FileSystemWatcher>({ |
117 | std::move(qmlWatcher), |
118 | std::move(qmldirWatcher), |
119 | std::move(qmltypesWatcher) |
120 | }); |
121 | registrations.append(t: Registration { |
122 | // use ClientCapabilitiesInfo::WorkspaceDidChangeWatchedFiles as id too |
123 | .id: ClientCapabilitiesInfo::WorkspaceDidChangeWatchedFiles, |
124 | .method: ClientCapabilitiesInfo::WorkspaceDidChangeWatchedFiles, |
125 | .registerOptions: QTypedJson::toJsonValue(params: watchedFilesParams) }); |
126 | } |
127 | |
128 | if (!registrations.isEmpty()) { |
129 | RegistrationParams params; |
130 | params.registrations = registrations; |
131 | protocol->requestRegistration( |
132 | params, |
133 | responseHandler: []() { |
134 | // successful registration |
135 | }, |
136 | errorHandler: [protocol](const ResponseError &err) { |
137 | LogMessageParams msg; |
138 | msg.message = QByteArray("registration of file udates failed, will miss file " |
139 | "changes done outside the editor due to error " ); |
140 | msg.message.append(a: QString::number(err.code).toUtf8()); |
141 | if (!err.message.isEmpty()) |
142 | msg.message.append(s: " " ); |
143 | msg.message.append(a: err.message); |
144 | msg.type = MessageType::Warning; |
145 | qCWarning(lspServerLog) << QString::fromUtf8(ba: msg.message); |
146 | protocol->notifyLogMessage(params: msg); |
147 | }); |
148 | } |
149 | |
150 | QSet<QString> rootPaths; |
151 | if (std::holds_alternative<QByteArray>(v: clientInfo.rootUri)) { |
152 | QString path = m_codeModel->url2Path( |
153 | url: QQmlLSUtils::lspUriToQmlUrl(uri: std::get<QByteArray>(v: clientInfo.rootUri))); |
154 | rootPaths.insert(value: path); |
155 | } else if (clientInfo.rootPath && std::holds_alternative<QByteArray>(v: *clientInfo.rootPath)) { |
156 | QString path = QString::fromUtf8(ba: std::get<QByteArray>(v: *clientInfo.rootPath)); |
157 | rootPaths.insert(value: path); |
158 | } |
159 | |
160 | if (clientInfo.workspaceFolders |
161 | && std::holds_alternative<QList<WorkspaceFolder>>(v: *clientInfo.workspaceFolders)) { |
162 | for (const WorkspaceFolder &workspace : |
163 | std::as_const(t: std::get<QList<WorkspaceFolder>>(v: *clientInfo.workspaceFolders))) { |
164 | const QUrl workspaceUrl(QString::fromUtf8(ba: QQmlLSUtils::lspUriToQmlUrl(uri: workspace.uri))); |
165 | rootPaths.insert(value: workspaceUrl.toLocalFile()); |
166 | } |
167 | } |
168 | if (m_status == Status::Indexing) |
169 | m_codeModel->addDirectoriesToIndex(paths: QStringList(rootPaths.begin(), rootPaths.end()), server); |
170 | } |
171 | |
172 | QT_END_NAMESPACE |
173 | |