Revision control

Copy as Markdown

// 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