Revision control
++ // Copyright (C) 2024 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
++ // Qt-Security score:significant reason:default
++
++ #include <qqmlhighlightsupport_p.h>
++ #include <qqmldiffer_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++ using namespace QLspSpecification;
++ using namespace QQmlJS::Dom;
++ using namespace QmlHighlighting;
++
++ /*!
++ \internal
++ Make a list of enum names to register the supported token
++ types and modifiers. It is case-sensitive in the protocol
++ thus we need to lower the first characters of the enum names.
++ */
++ template <typename EnumType>
++ static QList<QByteArray> enumToByteArray()
++ {
++ QList<QByteArray> result;
++ QMetaEnum metaEnum = QMetaEnum::fromType<EnumType>();
++ for (auto i = 0; i < metaEnum.keyCount(); ++i) {
++ auto &&enumName = QByteArray(metaEnum.key(i));
++ enumName.front() = std::tolower(enumName.front());
++ result.emplace_back(std::move(enumName));
++ }
++
++ return result;
++ }
++
++ QList<QByteArray> defaultTokenModifiersList()
++ {
++ return enumToByteArray<QLspSpecification::SemanticTokenModifiers>();
++ }
++
++ QList<QByteArray> extendedTokenTypesList()
++ {
++ return enumToByteArray<SemanticTokenProtocolTypes>();
++ }
++
++ static QList<unsigned> generateHighlights(QmlLsp::RegisteredSemanticTokens &cached,
++ const QmlLsp::OpenDocument &doc,
++ const std::optional<HighlightsRange> &range,
++ HighlightingMode mode)
++ {
++ DomItem file = doc.snapshot.doc.fileObject(GoTo::MostLikely);
++ const auto fileObject = file.ownerAs<QmlFile>();
++ QmlHighlighting::Utils::updateResultID(cached.resultId);
++ if (!fileObject || !(fileObject && fileObject->isValid())) {
++ if (const auto lastValidItem = doc.snapshot.validDoc.ownerAs<QmlFile>()) {
++ const auto shiftedHighlights = QmlHighlighting::Utils::shiftHighlights(
++ cached.highlights, lastValidItem->code(), doc.textDocument->toPlainText());
++ return QmlHighlighting::Utils::encodeSemanticTokens(shiftedHighlights, mode);
++ } else {
++ const QString code = doc.textDocument->toPlainText();
++ const HighlightsContainer highlights =
++ QmlHighlighting::Utils::regexFallbackHighlights(code, range);
++ if (highlights.isEmpty())
++ return {};
++ return QmlHighlighting::Utils::encodeSemanticTokens(highlights, mode);
++ }
++ } else {
++ HighlightsContainer highlights = QmlHighlighting::Utils::visitTokens(file, range);
++ if (highlights.isEmpty())
++ return {};
++ // Record the highlights for future diffs, only record full highlights
++ if (!range.has_value() )
++ cached.highlights = highlights;
++
++ return QmlHighlighting::Utils::encodeSemanticTokens(highlights, mode);
++ }
++ }
++
++ /*!
++ \internal
++ A wrapper class that handles the semantic tokens request for a whole file as described in
++ Sends a QLspSpecification::SemanticTokens data as response that is generated for the entire file.
++ */
++ SemanticTokenFullHandler::SemanticTokenFullHandler(QmlLsp::QQmlCodeModelManager *codeModelManager)
++ : QQmlBaseModule(codeModelManager), m_mode(HighlightingMode::Default)
++ {
++ }
++
++ void SemanticTokenFullHandler::process(
++ QQmlBaseModule<SemanticTokensRequest>::RequestPointerArgument request)
++ {
++ if (!request) {
++ qCWarning(semanticTokens) << "No semantic token request is available!";
++ return;
++ }
++
++ Responses::SemanticTokensResultType result;
++ ResponseScopeGuard guard(result, request->m_response);
++ const QByteArray uri = QQmlLSUtils::lspUriToQmlUrl(request->m_parameters.textDocument.uri);
++ const auto doc = m_codeModelManager->openDocumentByUrl(uri);
++ auto &cached = m_codeModelManager->registeredTokens(uri);
++ auto encoded = generateHighlights(cached, doc, std::nullopt, m_mode);
++
++ if (encoded.isEmpty()) {
++ return;
++ } else {
++ result = SemanticTokens{cached.resultId, std::move(encoded)};
++ }
++ }
++
++ void SemanticTokenFullHandler::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol)
++ {
++ protocol->registerSemanticTokensRequestHandler(getRequestHandler());
++ }
++
++ /*!
++ \internal
++ A wrapper class that handles the semantic tokens delta request for a file
++ Sends either SemanticTokens or SemanticTokensDelta data as response.
++ This is generally requested when the text document is edited after receiving full highlighting data.
++ */
++ SemanticTokenDeltaHandler::SemanticTokenDeltaHandler(QmlLsp::QQmlCodeModelManager *codeModelManager)
++ : QQmlBaseModule(codeModelManager), m_mode(HighlightingMode::Default)
++ {
++ }
++
++ void SemanticTokenDeltaHandler::process(
++ QQmlBaseModule<SemanticTokensDeltaRequest>::RequestPointerArgument request)
++ {
++ if (!request) {
++ qCWarning(semanticTokens) << "No semantic token request is available!";
++ return;
++ }
++
++ Responses::SemanticTokensDeltaResultType result;
++ ResponseScopeGuard guard(result, request->m_response);
++ const QByteArray uri = QQmlLSUtils::lspUriToQmlUrl(request->m_parameters.textDocument.uri);
++ const auto doc = m_codeModelManager->openDocumentByUrl(uri);
++ auto &cached = m_codeModelManager->registeredTokens(uri);
++ if (cached.resultId != request->m_parameters.previousResultId) {
++ // The client is out of sync, send full tokens
++ cached.resultId = request->m_parameters.previousResultId;
++ const auto encoded = generateHighlights(cached, doc, std::nullopt, m_mode);
++ result = QLspSpecification::SemanticTokens{ cached.resultId, encoded };
++ } else {
++ const auto cachedHighlights = QmlHighlighting::Utils::encodeSemanticTokens(cached.highlights);
++ const auto encoded = generateHighlights(cached, doc, std::nullopt, m_mode);
++ result = QLspSpecification::SemanticTokensDelta{
++ cached.resultId,
++ QmlHighlighting::Utils::computeDiff(cachedHighlights, encoded)
++ };
++ }
++ }
++
++ void SemanticTokenDeltaHandler::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol)
++ {
++ protocol->registerSemanticTokensDeltaRequestHandler(getRequestHandler());
++ }
++
++ /*!
++ \internal
++ A wrapper class that handles the semantic tokens range request for a file
++ Sends a QLspSpecification::SemanticTokens data as response that is generated for a range of file.
++ */
++ SemanticTokenRangeHandler::SemanticTokenRangeHandler(QmlLsp::QQmlCodeModelManager *codeModelManager)
++ : QQmlBaseModule(codeModelManager), m_mode(HighlightingMode::Default)
++ {
++ }
++
++ void SemanticTokenRangeHandler::process(
++ QQmlBaseModule<SemanticTokensRangeRequest>::RequestPointerArgument request)
++ {
++ if (!request) {
++ qCWarning(semanticTokens) << "No semantic token request is available!";
++ return;
++ }
++
++ Responses::SemanticTokensRangeResultType result;
++ ResponseScopeGuard guard(result, request->m_response);
++ const QByteArray uri = QQmlLSUtils::lspUriToQmlUrl(request->m_parameters.textDocument.uri);
++ const auto doc = m_codeModelManager->openDocumentByUrl(uri);
++ const QString code = doc.textDocument->toPlainText();
++ const auto range = request->m_parameters.range;
++ int startOffset =
++ int(QQmlLSUtils::textOffsetFrom(code, range.start.line, range.end.character));
++ int endOffset = int(QQmlLSUtils::textOffsetFrom(code, range.end.line, range.end.character));
++ auto &cached = m_codeModelManager->registeredTokens(uri);
++ auto encodedTokens = generateHighlights(
++ cached,
++ doc,
++ QmlHighlighting::HighlightsRange{ startOffset, endOffset },
++ m_mode);
++ if (encodedTokens.isEmpty()) {
++ } else {
++ result = SemanticTokens{ cached.resultId, std::move(encodedTokens) };
++ }
++ }
++
++ void SemanticTokenRangeHandler::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol)
++ {
++ protocol->registerSemanticTokensRangeRequestHandler(getRequestHandler());
++ }
++
++ QQmlHighlightSupport::QQmlHighlightSupport(QmlLsp::QQmlCodeModelManager *codeModelManager)
++ : m_full(codeModelManager), m_delta(codeModelManager), m_range(codeModelManager)
++ {
++ }
++
++ void QQmlHighlightSupport::registerHandlers(QLanguageServer *server, QLanguageServerProtocol *protocol)
++ {
++ m_full.registerHandlers(server, protocol);
++ m_delta.registerHandlers(server, protocol);
++ m_range.registerHandlers(server, protocol);
++
++ QObject::connect(server, &QLanguageServer::clientInitialized, this,
++ &QQmlHighlightSupport::clientInitialized, Qt::SingleShotConnection);
++
++ QObject::connect(
++ m_full.manager(), &QmlLsp::QQmlCodeModelManager::configurationChanged, this,
++ [protocol]() { protocol->requestWorkspaceSemanticTokensRefresh({ }, []() { }); });
++ }
++
++ void QQmlHighlightSupport::clientInitialized(QLanguageServer *server)
++ {
++ if (auto clientInitOptions = server->clientInfo().initializationOptions) {
++ if ((*clientInitOptions)[u"qtCreatorHighlighting"_s].toBool(false)) {
++ const auto mode = HighlightingMode::QtCHighlighting;
++ m_delta.setHighlightingMode(mode);
++ m_full.setHighlightingMode(mode);
++ m_range.setHighlightingMode(mode);
++ }
++ }
++ }
++
++ void QQmlHighlightSupport::setupCapabilities(QLspSpecification::ServerCapabilities &caps)
++ {
++ QLspSpecification::SemanticTokensOptions options;
++ options.range = true;
++ options.full = SemanticTokensFullDelta{ true };
++
++ options.legend.tokenTypes = extendedTokenTypesList();
++ options.legend.tokenModifiers = defaultTokenModifiersList();
++ caps.semanticTokensProvider = options;
++ }
++
++ QT_END_NAMESPACE