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
22#include <QObject>
23#include <unordered_map>
24
25template<typename ParametersT, typename ResponseT>
26struct BaseRequest
27{
28 // allow using Parameters and Response type aliases in the
29 // implementations of the different requests.
30 using Parameters = ParametersT;
31 using Response = ResponseT;
32
33 // The version of the code on which the typedefinition request was made.
34 // Request is received: mark it with the current version of the textDocument.
35 // Then, wait for the codemodel to finish creating a snapshot version that is newer or equal to
36 // the textDocument version at request-received-time.
37 int m_minVersion;
38 Parameters m_parameters;
39 Response m_response;
40
41 bool fillFrom(QmlLsp::OpenDocument doc, const Parameters &params, Response &&response);
42};
43
44template<typename RequestType>
45struct QQmlBaseModule : public QLanguageServerModule
46{
47 using RequestParameters = typename RequestType::Parameters;
48 using RequestResponse = typename RequestType::Response;
49 using RequestPointer = std::unique_ptr<RequestType>;
50 using RequestPointerArgument = RequestPointer &&;
51 using BaseT = QQmlBaseModule<RequestType>;
52
53 QQmlBaseModule(QmlLsp::QQmlCodeModel *codeModel);
54 ~QQmlBaseModule();
55
56 void requestHandler(const RequestParameters &parameters, RequestResponse &&response);
57 decltype(auto) getRequestHandler();
58 // processes a request in a different thread.
59 virtual void process(RequestPointerArgument toBeProcessed) = 0;
60 std::optional<QList<QQmlLSUtilsItemLocation>> itemsForRequest(const RequestPointer &request);
61
62public Q_SLOTS:
63 void updatedSnapshot(const QByteArray &uri);
64
65protected:
66 QMutex m_pending_mutex;
67 std::unordered_multimap<QString, RequestPointer> m_pending;
68 QmlLsp::QQmlCodeModel *m_codeModel;
69};
70
71template<typename Parameters, typename Response>
72bool BaseRequest<Parameters, Response>::fillFrom(QmlLsp::OpenDocument doc, const Parameters &params,
73 Response &&response)
74{
75 Q_UNUSED(doc);
76 m_parameters = params;
77 m_response = std::move(response);
78
79 if (!doc.textDocument) {
80 qDebug() << "Cannot find document in qmlls's codemodel, did you open it before accessing "
81 "it?";
82 return false;
83 }
84
85 {
86 QMutexLocker l(doc.textDocument->mutex());
87 m_minVersion = doc.textDocument->version().value_or(u: 0);
88 }
89 return true;
90}
91
92template<typename RequestType>
93QQmlBaseModule<RequestType>::QQmlBaseModule(QmlLsp::QQmlCodeModel *codeModel)
94 : m_codeModel(codeModel)
95{
96 QObject::connect(m_codeModel, &QmlLsp::QQmlCodeModel::updatedSnapshot, this,
97 &QQmlBaseModule<RequestType>::updatedSnapshot);
98}
99
100template<typename RequestType>
101QQmlBaseModule<RequestType>::~QQmlBaseModule()
102{
103 QMutexLocker l(&m_pending_mutex);
104 m_pending.clear(); // empty the m_pending while the mutex is hold
105}
106
107template<typename RequestType>
108decltype(auto) QQmlBaseModule<RequestType>::getRequestHandler()
109{
110 auto handler = [this](const QByteArray &, const RequestParameters &parameters,
111 RequestResponse &&response) {
112 requestHandler(parameters, response: std::move(response));
113 };
114 return handler;
115}
116
117template<typename RequestType>
118void QQmlBaseModule<RequestType>::requestHandler(const RequestParameters &parameters,
119 RequestResponse &&response)
120{
121 auto req = std::make_unique<RequestType>();
122 QmlLsp::OpenDocument doc = m_codeModel->openDocumentByUrl(
123 url: QQmlLSUtils::lspUriToQmlUrl(uri: parameters.textDocument.uri));
124
125 if (!req->fillFrom(doc, parameters, std::move(response))) {
126 req->m_response.sendErrorResponse(0, "Received invalid request", parameters);
127 return;
128 }
129 const int minVersion = req->m_minVersion;
130 {
131 QMutexLocker l(&m_pending_mutex);
132 m_pending.insert({ QString::fromUtf8(req->m_parameters.textDocument.uri), std::move(req) });
133 }
134
135 if (doc.snapshot.docVersion && *doc.snapshot.docVersion >= minVersion)
136 updatedSnapshot(uri: QQmlLSUtils::lspUriToQmlUrl(uri: parameters.textDocument.uri));
137}
138
139template<typename RequestType>
140void QQmlBaseModule<RequestType>::updatedSnapshot(const QByteArray &url)
141{
142 QmlLsp::OpenDocumentSnapshot doc = m_codeModel->snapshotByUrl(url);
143 std::vector<RequestPointer> toCompl;
144 {
145 QMutexLocker l(&m_pending_mutex);
146 for (auto [it, end] = m_pending.equal_range(QString::fromUtf8(ba: url)); it != end;) {
147 if (auto &[key, value] = *it;
148 doc.docVersion && value->m_minVersion <= *doc.docVersion) {
149 toCompl.push_back(std::move(value));
150 it = m_pending.erase(it);
151 } else {
152 ++it;
153 }
154 }
155 }
156 for (auto it = toCompl.rbegin(), end = toCompl.rend(); it != end; ++it) {
157 process(toBeProcessed: std::move(*it));
158 }
159}
160
161template<typename RequestType>
162std::optional<QList<QQmlLSUtilsItemLocation>>
163QQmlBaseModule<RequestType>::itemsForRequest(const RequestPointer &request)
164{
165
166 QmlLsp::OpenDocument doc = m_codeModel->openDocumentByUrl(
167 url: QQmlLSUtils::lspUriToQmlUrl(uri: request->m_parameters.textDocument.uri));
168
169 QQmlJS::Dom::DomItem file = doc.snapshot.validDoc.fileObject(option: QQmlJS::Dom::GoTo::MostLikely);
170 // clear reference cache to resolve latest versions (use a local env instead?)
171 if (auto envPtr = file.environment().ownerAs<QQmlJS::Dom::DomEnvironment>())
172 envPtr->clearReferenceCache();
173 if (!file) {
174 qWarning() << u"Could not find file in Dom Environment from Codemodel :"_s
175 << doc.snapshot.doc.toString();
176 return {};
177 }
178
179 auto itemsFound = QQmlLSUtils::itemsFromTextLocation(file, line: request->m_parameters.position.line,
180 character: request->m_parameters.position.character);
181
182 if (itemsFound.isEmpty()) {
183 qWarning() << u"Could not find any items at given text location."_s;
184 return {};
185 }
186 return itemsFound;
187}
188
189#endif // QQMLBASEMODULE_P_H
190

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