Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlhighlightsupport.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 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// Qt-Security score:significant reason:default
4
5#include <qqmlhighlightsupport_p.h>
6
8
9using namespace Qt::StringLiterals;
10using namespace QLspSpecification;
11using namespace QQmlJS::Dom;
12
13/*!
14\internal
15Make a list of enum names to register the supported token
16types and modifiers. It is case-sensitive in the protocol
17thus we need to lower the first characters of the enum names.
18*/
19template <typename EnumType>
21{
22 QList<QByteArray> result;
23 QMetaEnum metaEnum = QMetaEnum::fromType<EnumType>();
24 for (auto i = 0; i < metaEnum.keyCount(); ++i) {
25 auto &&enumName = QByteArray(metaEnum.key(i));
26 enumName.front() = std::tolower(enumName.front());
27 result.emplace_back(std::move(enumName));
28 }
29
30 return result;
31}
32
34{
35 return enumToByteArray<QLspSpecification::SemanticTokenModifiers>();
36}
37
42
43/*!
44\internal
45A wrapper class that handles the semantic tokens request for a whole file as described in
46https://microsoft.github.io/language-server-protocol/specifications/specification-3-16/#semanticTokens_fullRequest
47Sends a QLspSpecification::SemanticTokens data as response that is generated for the entire file.
48*/
53
55 QQmlBaseModule<SemanticTokensRequest>::RequestPointerArgument request)
56{
57 if (!request) {
58 qCWarning(semanticTokens) << "No semantic token request is available!";
59 return;
60 }
61
62 Responses::SemanticTokensResultType result;
63 ResponseScopeGuard guard(result, request->m_response);
64 const QByteArray uri = QQmlLSUtils::lspUriToQmlUrl(request->m_parameters.textDocument.uri);
65 const auto doc = m_codeModelManager->openDocumentByUrl(uri);
66 DomItem file = doc.snapshot.doc.fileObject(GoTo::MostLikely);
67 const auto fileObject = file.ownerAs<QmlFile>();
68 if (!fileObject || !(fileObject && fileObject->isValid())) {
69 guard.setError({
70 int(QLspSpecification::ErrorCodes::RequestCancelled),
71 "Cannot proceed: current QML document is invalid!"_L1,
72 });
73 return;
74 }
75 auto &&encoded = HighlightingUtils::collectTokens(file, std::nullopt, m_mode);
76 auto &registeredTokens = m_codeModelManager->registeredTokens(uri);
77 if (!encoded.isEmpty()) {
78 HighlightingUtils::updateResultID(registeredTokens.resultId);
79 result = SemanticTokens{ registeredTokens.resultId, encoded };
80 registeredTokens.lastTokens = std::move(encoded);
81 } else {
82 result = nullptr;
83 }
84}
85
86void SemanticTokenFullHandler::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol)
87{
88 protocol->registerSemanticTokensRequestHandler(getRequestHandler());
89}
90
91/*!
92\internal
93A wrapper class that handles the semantic tokens delta request for a file
94https://microsoft.github.io/language-server-protocol/specifications/specification-3-16/#semanticTokens_deltaRequest
95Sends either SemanticTokens or SemanticTokensDelta data as response.
96This is generally requested when the text document is edited after receiving full highlighting data.
97*/
102
104 QQmlBaseModule<SemanticTokensDeltaRequest>::RequestPointerArgument request)
105{
106 if (!request) {
107 qCWarning(semanticTokens) << "No semantic token request is available!";
108 return;
109 }
110
111 Responses::SemanticTokensDeltaResultType result;
112 ResponseScopeGuard guard(result, request->m_response);
113 const QByteArray uri = QQmlLSUtils::lspUriToQmlUrl(request->m_parameters.textDocument.uri);
114 const auto doc = m_codeModelManager->openDocumentByUrl(uri);
115 DomItem file = doc.snapshot.doc.fileObject(GoTo::MostLikely);
116 const auto fileObject = file.ownerAs<QmlFile>();
117 if (!fileObject || !(fileObject && fileObject->isValid())) {
118 guard.setError({
119 int(QLspSpecification::ErrorCodes::RequestCancelled),
120 "Cannot proceed: current QML document is invalid!"_L1,
121 });
122 return;
123 }
124 auto newEncoded = HighlightingUtils::collectTokens(file, std::nullopt, m_mode);
125 auto &registeredTokens = m_codeModelManager->registeredTokens(uri);
126 const auto lastResultId = registeredTokens.resultId;
127 HighlightingUtils::updateResultID(registeredTokens.resultId);
128
129 // Return full token list if result ids not align
130 // otherwise compute the delta.
131 if (lastResultId == request->m_parameters.previousResultId) {
132 result = QLspSpecification::SemanticTokensDelta{
133 registeredTokens.resultId,
134 HighlightingUtils::computeDiff(registeredTokens.lastTokens, newEncoded)
135 };
136 } else if (!newEncoded.isEmpty()) {
137 result = QLspSpecification::SemanticTokens{ registeredTokens.resultId, newEncoded };
138 } else {
139 result = nullptr;
140 }
141 registeredTokens.lastTokens = std::move(newEncoded);
142}
143
144void SemanticTokenDeltaHandler::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol)
145{
146 protocol->registerSemanticTokensDeltaRequestHandler(getRequestHandler());
147}
148
149/*!
150\internal
151A wrapper class that handles the semantic tokens range request for a file
152https://microsoft.github.io/language-server-protocol/specifications/specification-3-16/#semanticTokens_rangeRequest
153Sends a QLspSpecification::SemanticTokens data as response that is generated for a range of file.
154*/
159
161 QQmlBaseModule<SemanticTokensRangeRequest>::RequestPointerArgument request)
162{
163 if (!request) {
164 qCWarning(semanticTokens) << "No semantic token request is available!";
165 return;
166 }
167
168 Responses::SemanticTokensRangeResultType result;
169 ResponseScopeGuard guard(result, request->m_response);
170 const QByteArray uri = QQmlLSUtils::lspUriToQmlUrl(request->m_parameters.textDocument.uri);
171 const auto doc = m_codeModelManager->openDocumentByUrl(uri);
172 DomItem file = doc.snapshot.doc.fileObject(GoTo::MostLikely);
173 const auto qmlFile = file.as<QmlFile>();
174 if (!qmlFile || !(qmlFile && qmlFile->isValid())) {
175 guard.setError({
176 int(QLspSpecification::ErrorCodes::RequestCancelled),
177 "Cannot proceed: current QML document is invalid!"_L1,
178 });
179 return;
180 }
181 const QString &code = qmlFile->code();
182 const auto range = request->m_parameters.range;
183 int startOffset =
184 int(QQmlLSUtils::textOffsetFrom(code, range.start.line, range.end.character));
185 int endOffset = int(QQmlLSUtils::textOffsetFrom(code, range.end.line, range.end.character));
186 auto &&encoded = HighlightingUtils::collectTokens(
187 file, HighlightsRange{ startOffset, endOffset }, m_mode);
188 auto &registeredTokens = m_codeModelManager->registeredTokens(uri);
189 if (!encoded.isEmpty()) {
190 HighlightingUtils::updateResultID(registeredTokens.resultId);
191 result = SemanticTokens{ registeredTokens.resultId, std::move(encoded) };
192 } else {
193 result = nullptr;
194 }
195}
196
197void SemanticTokenRangeHandler::registerHandlers(QLanguageServer *, QLanguageServerProtocol *protocol)
198{
199 protocol->registerSemanticTokensRangeRequestHandler(getRequestHandler());
200}
201
206
208{
209 return "QQmlHighlightSupport"_L1;
210}
211
212void QQmlHighlightSupport::registerHandlers(QLanguageServer *server, QLanguageServerProtocol *protocol)
213{
214 m_full.registerHandlers(server, protocol);
215 m_delta.registerHandlers(server, protocol);
216 m_range.registerHandlers(server, protocol);
217}
218
220 const QLspSpecification::InitializeParams &clientCapabilities,
221 QLspSpecification::InitializeResult &serverCapabilities)
222{
223 QLspSpecification::SemanticTokensOptions options;
224 options.range = true;
225 options.full = QJsonObject({ { u"delta"_s, true } });
226
227 if (auto clientInitOptions = clientCapabilities.initializationOptions) {
228 if ((*clientInitOptions)[u"qtCreatorHighlighting"_s].toBool(false)) {
229 const auto mode = HighlightingUtils::HighlightingMode::QtCHighlighting;
230 m_delta.setHighlightingMode(mode);
231 m_full.setHighlightingMode(mode);
232 m_range.setHighlightingMode(mode);
233 }
234 }
235 options.legend.tokenTypes = extendedTokenTypesList();
236 options.legend.tokenModifiers = defaultTokenModifiersList();
237 serverCapabilities.capabilities.semanticTokensProvider = options;
238}
239
240QT_END_NAMESPACE
Implements a server for the language server protocol.
void registerHandlers(QLanguageServer *server, QLanguageServerProtocol *protocol) override
QString name() const override
QQmlHighlightSupport(QmlLsp::QQmlCodeModelManager *codeModel)
void setupCapabilities(const QLspSpecification::InitializeParams &clientInfo, QLspSpecification::InitializeResult &) override
void process(QQmlBaseModule< SemanticTokensDeltaRequest >::RequestPointerArgument req) override
SemanticTokenDeltaHandler(QmlLsp::QQmlCodeModelManager *codeModel)
void registerHandlers(QLanguageServer *, QLanguageServerProtocol *) override
void process(QQmlBaseModule< SemanticTokensRequest >::RequestPointerArgument req) override
SemanticTokenFullHandler(QmlLsp::QQmlCodeModelManager *codeModel)
void registerHandlers(QLanguageServer *, QLanguageServerProtocol *) override
SemanticTokenRangeHandler(QmlLsp::QQmlCodeModelManager *codeModel)
void process(QQmlBaseModule< SemanticTokensRangeRequest >::RequestPointerArgument req) override
void registerHandlers(QLanguageServer *, QLanguageServerProtocol *) override
void updateResultID(QByteArray &resultID)
QList< QByteArray > extendedTokenTypesList()
QList< QByteArray > defaultTokenModifiersList()
static QList< QByteArray > enumToByteArray()
This class sends a result or an error when going out of scope.