1// Copyright (C) 2023 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#ifndef QQMLBASEMODULE_P_H
5#define QQMLBASEMODULE_P_H
6
7//
8// W A R N I N G
9// -------------
10//
11// This file is not part of the Qt API. It exists purely as an
12// implementation detail. This header file may change from version to
13// version without notice, or even be removed.
14//
15// We mean it.
16//
17
18#include "qlanguageserver_p.h"
19#include "qqmlcodemodel_p.h"
20#include "qqmllsutils_p.h"
21#include <QtQmlDom/private/qqmldom_utils_p.h>
22
23#include <QObject>
24#include <type_traits>
25#include <unordered_map>
26
27template<typename ParametersT, typename ResponseT>
28struct BaseRequest
29{
30 // allow using Parameters and Response type aliases in the
31 // implementations of the different requests.
32 using Parameters = ParametersT;
33 using Response = ResponseT;
34
35 // The version of the code on which the typedefinition request was made.
36 // Request is received: mark it with the current version of the textDocument.
37 // Then, wait for the codemodel to finish creating a snapshot version that is newer or equal to
38 // the textDocument version at request-received-time.
39 int m_minVersion;
40 Parameters m_parameters;
41 Response m_response;
42
43 bool fillFrom(QmlLsp::OpenDocument doc, const Parameters &params, Response &&response);
44};
45
46/*!
47\internal
48\brief This class sends a result or an error when going out of scope.
49
50It has a helper method \c setErrorFrom that sets an error from variant and optionals.
51*/
52
53template<typename Result, typename ResponseCallback>
54struct ResponseScopeGuard
55{
56 Q_DISABLE_COPY_MOVE(ResponseScopeGuard)
57
58 std::variant<Result *, QQmlLSUtils::ErrorMessage> m_response;
59 ResponseCallback &m_callback;
60
61 ResponseScopeGuard(Result &results, ResponseCallback &callback)
62 : m_response(&results), m_callback(callback)
63 {
64 }
65
66 // note: discards the current result or error message, if there is any
67 void setError(const QQmlLSUtils::ErrorMessage &error) { m_response = error; }
68
69 template<typename... T>
70 bool setErrorFrom(const std::variant<T...> &variant)
71 {
72 static_assert(std::disjunction_v<std::is_same<T, QQmlLSUtils::ErrorMessage>...>,
73 "ResponseScopeGuard::setErrorFrom was passed a variant that never contains"
74 " an error message.");
75 if (auto x = std::get_if<QQmlLSUtils::ErrorMessage>(&variant)) {
76 setError(*x);
77 return true;
78 }
79 return false;
80 }
81
82 /*!
83 \internal
84 Note: use it as follows:
85 \badcode
86 if (scopeGuard.setErrorFrom(xxx)) {
87 // do early exit
88 }
89 // xxx was not an error, continue
90 \endcode
91 */
92 bool setErrorFrom(const std::optional<QQmlLSUtils::ErrorMessage> &error)
93 {
94 if (error) {
95 setError(*error);
96 return true;
97 }
98 return false;
99 }
100
101 ~ResponseScopeGuard()
102 {
103 std::visit(qOverloadedVisitor{ [this](Result *result) { m_callback.sendResponse(*result); },
104 [this](const QQmlLSUtils::ErrorMessage &error) {
105 m_callback.sendErrorResponse(error.code,
106 error.message.toUtf8());
107 } },
108 m_response);
109 }
110};
111
112template<typename RequestType>
113struct QQmlBaseModule : public QLanguageServerModule
114{
115 using RequestParameters = typename RequestType::Parameters;
116 using RequestResponse = typename RequestType::Response;
117 using RequestPointer = std::unique_ptr<RequestType>;
118 using RequestPointerArgument = RequestPointer &&;
119 using BaseT = QQmlBaseModule<RequestType>;
120
121 QQmlBaseModule(QmlLsp::QQmlCodeModel *codeModel);
122 ~QQmlBaseModule();
123
124 void requestHandler(const RequestParameters &parameters, RequestResponse &&response);
125 decltype(auto) getRequestHandler();
126 // processes a request in a different thread.
127 virtual void process(RequestPointerArgument toBeProcessed) = 0;
128 std::variant<QList<QQmlLSUtils::ItemLocation>, QQmlLSUtils::ErrorMessage>
129 itemsForRequest(const RequestPointer &request);
130
131public Q_SLOTS:
132 void updatedSnapshot(const QByteArray &uri);
133
134protected:
135 QMutex m_pending_mutex;
136 std::unordered_multimap<QString, RequestPointer> m_pending;
137 QmlLsp::QQmlCodeModel *m_codeModel;
138};
139
140template<typename Parameters, typename Response>
141bool BaseRequest<Parameters, Response>::fillFrom(QmlLsp::OpenDocument doc, const Parameters &params,
142 Response &&response)
143{
144 Q_UNUSED(doc);
145 m_parameters = params;
146 m_response = std::move(response);
147
148 if (!doc.textDocument) {
149 qDebug() << "Cannot find document in qmlls's codemodel, did you open it before accessing "
150 "it?";
151 return false;
152 }
153
154 {
155 QMutexLocker l(doc.textDocument->mutex());
156 m_minVersion = doc.textDocument->version().value_or(u: 0);
157 }
158 return true;
159}
160
161template<typename RequestType>
162QQmlBaseModule<RequestType>::QQmlBaseModule(QmlLsp::QQmlCodeModel *codeModel)
163 : m_codeModel(codeModel)
164{
165 QObject::connect(m_codeModel, &QmlLsp::QQmlCodeModel::updatedSnapshot, this,
166 &QQmlBaseModule<RequestType>::updatedSnapshot);
167}
168
169template<typename RequestType>
170QQmlBaseModule<RequestType>::~QQmlBaseModule()
171{
172 QMutexLocker l(&m_pending_mutex);
173 m_pending.clear(); // empty the m_pending while the mutex is hold
174}
175
176template<typename RequestType>
177decltype(auto) QQmlBaseModule<RequestType>::getRequestHandler()
178{
179 auto handler = [this](const QByteArray &, const RequestParameters &parameters,
180 RequestResponse &&response) {
181 requestHandler(parameters, response: std::move(response));
182 };
183 return handler;
184}
185
186template<typename RequestType>
187void QQmlBaseModule<RequestType>::requestHandler(const RequestParameters &parameters,
188 RequestResponse &&response)
189{
190 auto req = std::make_unique<RequestType>();
191 QmlLsp::OpenDocument doc = m_codeModel->openDocumentByUrl(
192 url: QQmlLSUtils::lspUriToQmlUrl(uri: parameters.textDocument.uri));
193
194 if (!req->fillFrom(doc, parameters, std::move(response))) {
195 req->m_response.sendErrorResponse(0, "Received invalid request", parameters);
196 return;
197 }
198 const int minVersion = req->m_minVersion;
199 {
200 QMutexLocker l(&m_pending_mutex);
201 m_pending.insert({ QString::fromUtf8(req->m_parameters.textDocument.uri), std::move(req) });
202 }
203
204 if (doc.snapshot.docVersion && *doc.snapshot.docVersion >= minVersion)
205 updatedSnapshot(uri: QQmlLSUtils::lspUriToQmlUrl(uri: parameters.textDocument.uri));
206}
207
208template<typename RequestType>
209void QQmlBaseModule<RequestType>::updatedSnapshot(const QByteArray &url)
210{
211 QmlLsp::OpenDocumentSnapshot doc = m_codeModel->snapshotByUrl(url);
212 std::vector<RequestPointer> toCompl;
213 {
214 QMutexLocker l(&m_pending_mutex);
215 for (auto [it, end] = m_pending.equal_range(QString::fromUtf8(ba: url)); it != end;) {
216 if (auto &[key, value] = *it;
217 doc.docVersion && value->m_minVersion <= *doc.docVersion) {
218 toCompl.push_back(std::move(value));
219 it = m_pending.erase(it);
220 } else {
221 ++it;
222 }
223 }
224 }
225 for (auto it = toCompl.rbegin(), end = toCompl.rend(); it != end; ++it) {
226 process(toBeProcessed: std::move(*it));
227 }
228}
229
230template<typename RequestType>
231std::variant<QList<QQmlLSUtils::ItemLocation>, QQmlLSUtils::ErrorMessage>
232QQmlBaseModule<RequestType>::itemsForRequest(const RequestPointer &request)
233{
234
235 QmlLsp::OpenDocument doc = m_codeModel->openDocumentByUrl(
236 url: QQmlLSUtils::lspUriToQmlUrl(uri: request->m_parameters.textDocument.uri));
237
238 if (!doc.snapshot.validDocVersion || doc.snapshot.validDocVersion != doc.snapshot.docVersion) {
239 return QQmlLSUtils::ErrorMessage{ .code: 0,
240 .message: u"Cannot proceed: current QML document is invalid! Fix"
241 u" all the errors in your QML code and try again."_s };
242 }
243
244 QQmlJS::Dom::DomItem file = doc.snapshot.validDoc.fileObject(option: QQmlJS::Dom::GoTo::MostLikely);
245 // clear reference cache to resolve latest versions (use a local env instead?)
246 if (auto envPtr = file.environment().ownerAs<QQmlJS::Dom::DomEnvironment>())
247 envPtr->clearReferenceCache();
248 if (!file) {
249 return QQmlLSUtils::ErrorMessage{
250 .code: 0,
251 .message: u"Could not find file %1 in project."_s.arg(a: doc.snapshot.doc.toString()),
252 };
253 }
254
255 auto itemsFound = QQmlLSUtils::itemsFromTextLocation(file, line: request->m_parameters.position.line,
256 character: request->m_parameters.position.character);
257
258 if (itemsFound.isEmpty()) {
259 return QQmlLSUtils::ErrorMessage{
260 .code: 0,
261 .message: u"Could not find any items at given text location."_s,
262 };
263 }
264 return itemsFound;
265}
266
267#endif // QQMLBASEMODULE_P_H
268

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtdeclarative/src/qmlls/qqmlbasemodule_p.h