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 | } |
62 | case FileChangeType::Deleted: |
63 | // m_modelManager->removeFile(filename); |
64 | break; |
65 | } |
66 | } |
67 | // update due to dep changes... |
68 | }); |
69 | |
70 | QObject::connect(sender: server, signal: &QLanguageServer::clientInitialized, context: this, |
71 | slot: &WorkspaceHandlers::clientInitialized); |
72 | } |
73 | |
74 | QString WorkspaceHandlers::name() const |
75 | { |
76 | return u"Workspace"_s ; |
77 | } |
78 | |
79 | void WorkspaceHandlers::setupCapabilities(const QLspSpecification::InitializeParams &clientInfo, |
80 | QLspSpecification::InitializeResult &serverInfo) |
81 | { |
82 | if (!clientInfo.capabilities.workspace |
83 | || !clientInfo.capabilities.workspace->value(key: u"workspaceFolders"_s ).toBool(defaultValue: false)) |
84 | return; |
85 | WorkspaceFoldersServerCapabilities folders; |
86 | folders.supported = true; |
87 | folders.changeNotifications = true; |
88 | if (!serverInfo.capabilities.workspace) |
89 | serverInfo.capabilities.workspace = QJsonObject(); |
90 | serverInfo.capabilities.workspace->insert(key: u"workspaceFolders"_s , |
91 | value: QTypedJson::toJsonValue(params: folders)); |
92 | } |
93 | |
94 | void WorkspaceHandlers::clientInitialized(QLanguageServer *server) |
95 | { |
96 | QLanguageServerProtocol *protocol = server->protocol(); |
97 | const auto clientInfo = server->clientInfo(); |
98 | QList<Registration> registrations; |
99 | if (clientInfo.capabilities.workspace |
100 | && clientInfo.capabilities.workspace |
101 | ->value(key: u"didChangeWatchedFiles"_s )[u"dynamicRegistration"_s ] |
102 | .toBool(defaultValue: false)) { |
103 | const int watchAll = |
104 | int(WatchKind::Create) | int(WatchKind::Change) | int(WatchKind::Delete); |
105 | DidChangeWatchedFilesRegistrationOptions watchedFilesParams; |
106 | FileSystemWatcher qmlWatcher; |
107 | qmlWatcher.globPattern = QByteArray("*.{qml,js,mjs}" ); |
108 | qmlWatcher.kind = watchAll; |
109 | FileSystemWatcher qmldirWatcher; |
110 | qmldirWatcher.globPattern = "qmldir" ; |
111 | qmldirWatcher.kind = watchAll; |
112 | FileSystemWatcher qmltypesWatcher; |
113 | qmltypesWatcher.globPattern = QByteArray("*.qmltypes" ); |
114 | qmltypesWatcher.kind = watchAll; |
115 | watchedFilesParams.watchers = |
116 | QList<FileSystemWatcher>({ qmlWatcher, qmldirWatcher, qmltypesWatcher }); |
117 | registrations.append(t: Registration { |
118 | // use ClientCapabilitiesInfo::WorkspaceDidChangeWatchedFiles as id too |
119 | .id: ClientCapabilitiesInfo::WorkspaceDidChangeWatchedFiles, |
120 | .method: ClientCapabilitiesInfo::WorkspaceDidChangeWatchedFiles, |
121 | .registerOptions: QTypedJson::toJsonValue(params: watchedFilesParams) }); |
122 | } |
123 | |
124 | if (!registrations.isEmpty()) { |
125 | RegistrationParams params; |
126 | params.registrations = registrations; |
127 | protocol->requestRegistration( |
128 | params, |
129 | responseHandler: []() { |
130 | // successful registration |
131 | }, |
132 | errorHandler: [protocol](const ResponseError &err) { |
133 | LogMessageParams msg; |
134 | msg.message = QByteArray("registration of file udates failed, will miss file " |
135 | "changes done outside the editor due to error " ); |
136 | msg.message.append(a: QString::number(err.code).toUtf8()); |
137 | if (!err.message.isEmpty()) |
138 | msg.message.append(s: " " ); |
139 | msg.message.append(a: err.message); |
140 | msg.type = MessageType::Warning; |
141 | qCWarning(lspServerLog) << QString::fromUtf8(ba: msg.message); |
142 | protocol->notifyLogMessage(params: msg); |
143 | }); |
144 | } |
145 | |
146 | QSet<QString> rootPaths; |
147 | if (std::holds_alternative<QByteArray>(v: clientInfo.rootUri)) { |
148 | QString path = m_codeModel->url2Path( |
149 | url: QQmlLSUtils::lspUriToQmlUrl(uri: std::get<QByteArray>(v: clientInfo.rootUri))); |
150 | rootPaths.insert(value: path); |
151 | } else if (clientInfo.rootPath && std::holds_alternative<QByteArray>(v: *clientInfo.rootPath)) { |
152 | QString path = QString::fromUtf8(ba: std::get<QByteArray>(v: *clientInfo.rootPath)); |
153 | rootPaths.insert(value: path); |
154 | } |
155 | |
156 | if (clientInfo.workspaceFolders |
157 | && std::holds_alternative<QList<WorkspaceFolder>>(v: *clientInfo.workspaceFolders)) { |
158 | for (const WorkspaceFolder &workspace : |
159 | std::as_const(t: std::get<QList<WorkspaceFolder>>(v: *clientInfo.workspaceFolders))) { |
160 | const QUrl workspaceUrl(QString::fromUtf8(ba: QQmlLSUtils::lspUriToQmlUrl(uri: workspace.uri))); |
161 | rootPaths.insert(value: workspaceUrl.toLocalFile()); |
162 | } |
163 | } |
164 | if (m_status == Status::Indexing) |
165 | m_codeModel->addDirectoriesToIndex(paths: QStringList(rootPaths.begin(), rootPaths.end()), server); |
166 | } |
167 | |
168 | QT_END_NAMESPACE |
169 | |