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