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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtdeclarative/src/qmlls/qqmlhighlightsupport.cpp