aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlls/qqmlbasemodule_p.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmlls/qqmlbasemodule_p.h')
-rw-r--r--src/qmlls/qqmlbasemodule_p.h267
1 files changed, 267 insertions, 0 deletions
diff --git a/src/qmlls/qqmlbasemodule_p.h b/src/qmlls/qqmlbasemodule_p.h
new file mode 100644
index 0000000000..47596914cb
--- /dev/null
+++ b/src/qmlls/qqmlbasemodule_p.h
@@ -0,0 +1,267 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQMLBASEMODULE_P_H
+#define QQMLBASEMODULE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qlanguageserver_p.h"
+#include "qqmlcodemodel_p.h"
+#include "qqmllsutils_p.h"
+#include <QtQmlDom/private/qqmldom_utils_p.h>
+
+#include <QObject>
+#include <type_traits>
+#include <unordered_map>
+
+template<typename ParametersT, typename ResponseT>
+struct BaseRequest
+{
+ // allow using Parameters and Response type aliases in the
+ // implementations of the different requests.
+ using Parameters = ParametersT;
+ using Response = ResponseT;
+
+ // The version of the code on which the typedefinition request was made.
+ // Request is received: mark it with the current version of the textDocument.
+ // Then, wait for the codemodel to finish creating a snapshot version that is newer or equal to
+ // the textDocument version at request-received-time.
+ int m_minVersion;
+ Parameters m_parameters;
+ Response m_response;
+
+ bool fillFrom(QmlLsp::OpenDocument doc, const Parameters &params, Response &&response);
+};
+
+/*!
+\internal
+\brief This class sends a result or an error when going out of scope.
+
+It has a helper method \c setErrorFrom that sets an error from variant and optionals.
+*/
+
+template<typename Result, typename ResponseCallback>
+struct ResponseScopeGuard
+{
+ Q_DISABLE_COPY_MOVE(ResponseScopeGuard)
+
+ std::variant<Result *, QQmlLSUtilsErrorMessage> m_response;
+ ResponseCallback &m_callback;
+
+ ResponseScopeGuard(Result &results, ResponseCallback &callback)
+ : m_response(&results), m_callback(callback)
+ {
+ }
+
+ // note: discards the current result or error message, if there is any
+ void setError(const QQmlLSUtilsErrorMessage &error) { m_response = error; }
+
+ template<typename... T>
+ bool setErrorFrom(const std::variant<T...> &variant)
+ {
+ static_assert(std::disjunction_v<std::is_same<T, QQmlLSUtilsErrorMessage>...>,
+ "ResponseScopeGuard::setErrorFrom was passed a variant that never contains"
+ " an error message.");
+ if (auto x = std::get_if<QQmlLSUtilsErrorMessage>(&variant)) {
+ setError(*x);
+ return true;
+ }
+ return false;
+ }
+
+ /*!
+ \internal
+ Note: use it as follows:
+ \badcode
+ if (scopeGuard.setErrorFrom(xxx)) {
+ // do early exit
+ }
+ // xxx was not an error, continue
+ \endcode
+ */
+ bool setErrorFrom(const std::optional<QQmlLSUtilsErrorMessage> &error)
+ {
+ if (error) {
+ setError(*error);
+ return true;
+ }
+ return false;
+ }
+
+ ~ResponseScopeGuard()
+ {
+ std::visit(qOverloadedVisitor{ [this](Result *result) { m_callback.sendResponse(*result); },
+ [this](const QQmlLSUtilsErrorMessage &error) {
+ m_callback.sendErrorResponse(error.code,
+ error.message.toUtf8());
+ } },
+ m_response);
+ }
+};
+
+template<typename RequestType>
+struct QQmlBaseModule : public QLanguageServerModule
+{
+ using RequestParameters = typename RequestType::Parameters;
+ using RequestResponse = typename RequestType::Response;
+ using RequestPointer = std::unique_ptr<RequestType>;
+ using RequestPointerArgument = RequestPointer &&;
+ using BaseT = QQmlBaseModule<RequestType>;
+
+ QQmlBaseModule(QmlLsp::QQmlCodeModel *codeModel);
+ ~QQmlBaseModule();
+
+ void requestHandler(const RequestParameters &parameters, RequestResponse &&response);
+ decltype(auto) getRequestHandler();
+ // processes a request in a different thread.
+ virtual void process(RequestPointerArgument toBeProcessed) = 0;
+ std::variant<QList<QQmlLSUtilsItemLocation>, QQmlLSUtilsErrorMessage>
+ itemsForRequest(const RequestPointer &request);
+
+public Q_SLOTS:
+ void updatedSnapshot(const QByteArray &uri);
+
+protected:
+ QMutex m_pending_mutex;
+ std::unordered_multimap<QString, RequestPointer> m_pending;
+ QmlLsp::QQmlCodeModel *m_codeModel;
+};
+
+template<typename Parameters, typename Response>
+bool BaseRequest<Parameters, Response>::fillFrom(QmlLsp::OpenDocument doc, const Parameters &params,
+ Response &&response)
+{
+ Q_UNUSED(doc);
+ m_parameters = params;
+ m_response = std::move(response);
+
+ if (!doc.textDocument) {
+ qDebug() << "Cannot find document in qmlls's codemodel, did you open it before accessing "
+ "it?";
+ return false;
+ }
+
+ {
+ QMutexLocker l(doc.textDocument->mutex());
+ m_minVersion = doc.textDocument->version().value_or(0);
+ }
+ return true;
+}
+
+template<typename RequestType>
+QQmlBaseModule<RequestType>::QQmlBaseModule(QmlLsp::QQmlCodeModel *codeModel)
+ : m_codeModel(codeModel)
+{
+ QObject::connect(m_codeModel, &QmlLsp::QQmlCodeModel::updatedSnapshot, this,
+ &QQmlBaseModule<RequestType>::updatedSnapshot);
+}
+
+template<typename RequestType>
+QQmlBaseModule<RequestType>::~QQmlBaseModule()
+{
+ QMutexLocker l(&m_pending_mutex);
+ m_pending.clear(); // empty the m_pending while the mutex is hold
+}
+
+template<typename RequestType>
+decltype(auto) QQmlBaseModule<RequestType>::getRequestHandler()
+{
+ auto handler = [this](const QByteArray &, const RequestParameters &parameters,
+ RequestResponse &&response) {
+ requestHandler(parameters, std::move(response));
+ };
+ return handler;
+}
+
+template<typename RequestType>
+void QQmlBaseModule<RequestType>::requestHandler(const RequestParameters &parameters,
+ RequestResponse &&response)
+{
+ auto req = std::make_unique<RequestType>();
+ QmlLsp::OpenDocument doc = m_codeModel->openDocumentByUrl(
+ QQmlLSUtils::lspUriToQmlUrl(parameters.textDocument.uri));
+
+ if (!req->fillFrom(doc, parameters, std::move(response))) {
+ req->m_response.sendErrorResponse(0, "Received invalid request", parameters);
+ return;
+ }
+ const int minVersion = req->m_minVersion;
+ {
+ QMutexLocker l(&m_pending_mutex);
+ m_pending.insert({ QString::fromUtf8(req->m_parameters.textDocument.uri), std::move(req) });
+ }
+
+ if (doc.snapshot.docVersion && *doc.snapshot.docVersion >= minVersion)
+ updatedSnapshot(QQmlLSUtils::lspUriToQmlUrl(parameters.textDocument.uri));
+}
+
+template<typename RequestType>
+void QQmlBaseModule<RequestType>::updatedSnapshot(const QByteArray &url)
+{
+ QmlLsp::OpenDocumentSnapshot doc = m_codeModel->snapshotByUrl(url);
+ std::vector<RequestPointer> toCompl;
+ {
+ QMutexLocker l(&m_pending_mutex);
+ for (auto [it, end] = m_pending.equal_range(QString::fromUtf8(url)); it != end;) {
+ if (auto &[key, value] = *it;
+ doc.docVersion && value->m_minVersion <= *doc.docVersion) {
+ toCompl.push_back(std::move(value));
+ it = m_pending.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+ for (auto it = toCompl.rbegin(), end = toCompl.rend(); it != end; ++it) {
+ process(std::move(*it));
+ }
+}
+
+template<typename RequestType>
+std::variant<QList<QQmlLSUtilsItemLocation>, QQmlLSUtilsErrorMessage>
+QQmlBaseModule<RequestType>::itemsForRequest(const RequestPointer &request)
+{
+
+ QmlLsp::OpenDocument doc = m_codeModel->openDocumentByUrl(
+ QQmlLSUtils::lspUriToQmlUrl(request->m_parameters.textDocument.uri));
+
+ if (!doc.snapshot.validDocVersion || doc.snapshot.validDocVersion != doc.snapshot.docVersion) {
+ return QQmlLSUtilsErrorMessage{ 0,
+ u"Cannot proceed: current QML document is invalid! Fix"
+ u" all the errors in your QML code and try again."_s };
+ }
+
+ QQmlJS::Dom::DomItem file = doc.snapshot.validDoc.fileObject(QQmlJS::Dom::GoTo::MostLikely);
+ // clear reference cache to resolve latest versions (use a local env instead?)
+ if (auto envPtr = file.environment().ownerAs<QQmlJS::Dom::DomEnvironment>())
+ envPtr->clearReferenceCache();
+ if (!file) {
+ return QQmlLSUtilsErrorMessage{
+ 0,
+ u"Could not find file %1 in project."_s.arg(doc.snapshot.doc.toString()),
+ };
+ }
+
+ auto itemsFound = QQmlLSUtils::itemsFromTextLocation(file, request->m_parameters.position.line,
+ request->m_parameters.position.character);
+
+ if (itemsFound.isEmpty()) {
+ return QQmlLSUtilsErrorMessage{
+ 0,
+ u"Could not find any items at given text location."_s,
+ };
+ }
+ return itemsFound;
+}
+
+#endif // QQMLBASEMODULE_P_H