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 | |
25 | template<typename ParametersT, typename ResponseT> |
26 | struct 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 ¶ms, Response &&response); |
42 | }; |
43 | |
44 | template<typename RequestType> |
45 | struct 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 ¶meters, 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 | |
62 | public Q_SLOTS: |
63 | void updatedSnapshot(const QByteArray &uri); |
64 | |
65 | protected: |
66 | QMutex m_pending_mutex; |
67 | std::unordered_multimap<QString, RequestPointer> m_pending; |
68 | QmlLsp::QQmlCodeModel *m_codeModel; |
69 | }; |
70 | |
71 | template<typename Parameters, typename Response> |
72 | bool BaseRequest<Parameters, Response>::fillFrom(QmlLsp::OpenDocument doc, const Parameters ¶ms, |
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 | |
92 | template<typename RequestType> |
93 | QQmlBaseModule<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 | |
100 | template<typename RequestType> |
101 | QQmlBaseModule<RequestType>::~QQmlBaseModule() |
102 | { |
103 | QMutexLocker l(&m_pending_mutex); |
104 | m_pending.clear(); // empty the m_pending while the mutex is hold |
105 | } |
106 | |
107 | template<typename RequestType> |
108 | decltype(auto) QQmlBaseModule<RequestType>::getRequestHandler() |
109 | { |
110 | auto handler = [this](const QByteArray &, const RequestParameters ¶meters, |
111 | RequestResponse &&response) { |
112 | requestHandler(parameters, response: std::move(response)); |
113 | }; |
114 | return handler; |
115 | } |
116 | |
117 | template<typename RequestType> |
118 | void QQmlBaseModule<RequestType>::requestHandler(const RequestParameters ¶meters, |
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 | |
139 | template<typename RequestType> |
140 | void 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 | |
161 | template<typename RequestType> |
162 | std::optional<QList<QQmlLSUtilsItemLocation>> |
163 | QQmlBaseModule<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 | |