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
qqmlsemantictokens.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 <qqmlsemantictokens_p.h>
6#include <qqmldiffer_p.h>
7
8#include <QtQmlLS/private/qqmllsutils_p.h>
9#include <QtQmlDom/private/qqmldomscriptelements_p.h>
10#include <QtQmlDom/private/qqmldomfieldfilter_p.h>
11
12#include <QtLanguageServer/private/qlanguageserverprotocol_p.h>
13
14#include <QtCore/qregularexpression.h>
15
17
18Q_LOGGING_CATEGORY(semanticTokens, "qt.languageserver.semanticTokens")
19
20using namespace QQmlJS::Dom;
21using namespace QLspSpecification;
22
23namespace QmlHighlighting {
24
26{
27 switch (highlightKind) {
85 default:
87 }
88}
89
90static int mapToProtocolDefault(QmlHighlightKind highlightKind)
91{
92 switch (highlightKind) {
146 default:
148 }
149}
150
151/*!
152\internal
153\brief Further resolves the type of a JavaScriptIdentifier
154A global object can be in the object form or in the function form.
155For example, Date can be used as a constructor function (like new Date())
156or as a object (like Date.now()).
157*/
159 const QString &name)
160{
161 // Some objects are not constructable, they are always objects.
162 static QSet<QString> noConstructorObjects = { u"Math"_s, u"JSON"_s, u"Atomics"_s, u"Reflect"_s,
163 u"console"_s };
164 // if the method name is in the list of noConstructorObjects, then it is a global object. Do not
165 // perform further checks.
166 if (noConstructorObjects.contains(name))
168 // Check if the method is called with new, then it is a constructor function
169 if (item.directParent().internalKind() == DomType::ScriptNewMemberExpression) {
171 }
172 if (DomItem containingCallExpression = item.filterUp(
173 [](DomType k, const DomItem &) { return k == DomType::ScriptCallExpression; },
174 FilterUpOptions::ReturnOuter)) {
175 // Call expression
176 // if callee is binary expression, then the rightest part is the method name
177 const auto callee = containingCallExpression.field(Fields::callee);
178 if (callee.internalKind() == DomType::ScriptBinaryExpression) {
179 const auto right = callee.field(Fields::right);
180 if (right.internalKind() == DomType::ScriptIdentifierExpression
181 && right.field(Fields::identifier).value().toString() == name) {
183 } else {
185 }
186 } else {
188 }
189 }
190 return std::nullopt;
191}
192
193static int fromQmlModifierKindToLspTokenType(QmlHighlightModifiers highlightModifier)
194{
195 using namespace QLspSpecification;
196 using namespace Utils;
197 int modifier = 0;
198
199 if (highlightModifier.testFlag(QmlHighlightModifier::QmlPropertyDefinition))
200 addModifier(SemanticTokenModifiers::Definition, &modifier);
201
202 if (highlightModifier.testFlag(QmlHighlightModifier::QmlDefaultProperty))
203 addModifier(SemanticTokenModifiers::DefaultLibrary, &modifier);
204
205 if (highlightModifier.testFlag(QmlHighlightModifier::QmlVirtualProperty))
206 addModifier(SemanticTokenModifiers::Static, &modifier);
207
208 if (highlightModifier.testFlag(QmlHighlightModifier::QmlOverrideProperty))
209 addModifier(SemanticTokenModifiers::Static, &modifier);
210
211 if (highlightModifier.testFlag(QmlHighlightModifier::QmlFinalProperty))
212 addModifier(SemanticTokenModifiers::Static, &modifier);
213
214 if (highlightModifier.testFlag(QmlHighlightModifier::QmlRequiredProperty))
215 addModifier(SemanticTokenModifiers::Abstract, &modifier);
216
217 if (highlightModifier.testFlag(QmlHighlightModifier::QmlReadonlyProperty))
218 addModifier(SemanticTokenModifiers::Readonly, &modifier);
219
220 return modifier;
221}
222
224{
225 QMultiMap<QString, QString> fieldFilterAdd{};
226 QMultiMap<QString, QString> fieldFilterRemove{
227 { QString(), Fields::propertyInfos.toString() },
228 { QString(), Fields::fileLocationsTree.toString() },
229 { QString(), Fields::importScope.toString() },
230 { QString(), Fields::defaultPropertyName.toString() },
231 { QString(), Fields::get.toString() },
232 };
233 return FieldFilter{ fieldFilterAdd, fieldFilterRemove };
234}
235
236HighlightToken::HighlightToken(const QQmlJS::SourceLocation &loc,
237 QmlHighlightKind kind,
238 QmlHighlightModifiers modifiers)
239 : loc(loc), kind(kind), modifiers(modifiers)
240{
241}
242
243HighlightingVisitor::HighlightingVisitor(const QQmlJS::Dom::DomItem &item,
244 const std::optional<HighlightsRange> &range)
245 : m_range(range)
246{
247 item.visitTree(
248 Path(),
249 [this](const Path &path, const DomItem &item, bool b) {
250 return this->visitor(path, item, b);
251 },
252 VisitOption::Default | VisitOption::NoPath, emptyChildrenVisitor, emptyChildrenVisitor,
253 highlightingFilter());
254}
255
256bool HighlightingVisitor::visitor(Path, const DomItem &item, bool)
257{
258 if (m_range.has_value()) {
259 const auto fLocs = FileLocations::treeOf(item);
260 if (!fLocs)
261 return true;
262 const auto regions = fLocs->info().regions;
263 if (!Utils::rangeOverlapsWithSourceLocation(regions[MainRegion],
264 m_range.value()))
265 return true;
266 }
267 switch (item.internalKind()) {
268 case DomType::Comment: {
269 highlightComment(item);
270 return true;
271 }
272 case DomType::Import: {
273 highlightImport(item);
274 return true;
275 }
276 case DomType::Binding: {
277 highlightBinding(item);
278 return true;
279 }
280 case DomType::Pragma: {
281 highlightPragma(item);
282 return true;
283 }
284 case DomType::EnumDecl: {
285 highlightEnumDecl(item);
286 return true;
287 }
288 case DomType::EnumItem: {
289 highlightEnumItem(item);
290 return true;
291 }
292 case DomType::QmlObject: {
293 highlightQmlObject(item);
294 return true;
295 }
296 case DomType::QmlComponent: {
297 highlightComponent(item);
298 return true;
299 }
300 case DomType::PropertyDefinition: {
301 highlightPropertyDefinition(item);
302 return true;
303 }
304 case DomType::MethodInfo: {
305 highlightMethod(item);
306 return true;
307 }
308 case DomType::ScriptLiteral: {
309 highlightScriptLiteral(item);
310 return true;
311 }
312 case DomType::ScriptCallExpression: {
313 highlightCallExpression(item);
314 return true;
315 }
316 case DomType::ScriptIdentifierExpression: {
317 highlightIdentifier(item);
318 return true;
319 }
320 default:
321 if (item.ownerAs<ScriptExpression>())
322 highlightScriptExpressions(item);
323 return true;
324 }
325 Q_UNREACHABLE_RETURN(false);
326}
327
328void HighlightingVisitor::highlightComment(const DomItem &item)
329{
330 const auto comment = item.as<Comment>();
331 Q_ASSERT(comment);
332 const auto locs = Utils::sourceLocationsFromMultiLineToken(
333 comment->info().comment(), comment->info().sourceLocation());
334 for (const auto &loc : locs)
335 addHighlight(loc, QmlHighlightKind::Comment);
336}
337
338void HighlightingVisitor::highlightImport(const DomItem &item)
339{
340 const auto fLocs = FileLocations::treeOf(item);
341 if (!fLocs)
342 return;
343 const auto regions = fLocs->info().regions;
344 const auto import = item.as<Import>();
345 Q_ASSERT(import);
346 addHighlight(regions[ImportTokenRegion], QmlHighlightKind::QmlKeyword);
347 if (import->uri.isModule())
348 addHighlight(regions[ImportUriRegion], QmlHighlightKind::QmlImportId);
349 else
350 addHighlight(regions[ImportUriRegion], QmlHighlightKind::String);
351 if (regions.contains(VersionRegion))
352 addHighlight(regions[VersionRegion], QmlHighlightKind::Number);
353 if (regions.contains(AsTokenRegion)) {
354 addHighlight(regions[AsTokenRegion], QmlHighlightKind::QmlKeyword);
355 addHighlight(regions[IdNameRegion], QmlHighlightKind::QmlNamespace);
356 }
357}
358
359void HighlightingVisitor::highlightBinding(const DomItem &item)
360{
361 const auto binding = item.as<Binding>();
362 Q_ASSERT(binding);
363 const auto fLocs = FileLocations::treeOf(item);
364 if (!fLocs) {
365 qCDebug(semanticTokens) << "Can't find the locations for" << item.internalKind();
366 return;
367 }
368 const auto regions = fLocs->info().regions;
369 // If dotted name, then defer it to be handled in ScriptIdentifierExpression
370 if (binding->name().contains("."_L1))
371 return;
372
373 if (binding->bindingType() != BindingType::Normal) {
374 addHighlight(regions[OnTokenRegion], QmlHighlightKind::QmlKeyword);
375 addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlProperty);
376 return;
377 }
378
379 return addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlProperty);
380}
381
382void HighlightingVisitor::highlightPragma(const DomItem &item)
383{
384 const auto fLocs = FileLocations::treeOf(item);
385 if (!fLocs)
386 return;
387 const auto regions = fLocs->info().regions;
388 addHighlight(regions[PragmaKeywordRegion], QmlHighlightKind::QmlKeyword);
389 addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlPragmaName );
390 const auto pragma = item.as<Pragma>();
391 for (auto i = 0; i < pragma->values.size(); ++i) {
392 DomItem value = item.field(Fields::values).index(i);
393 const auto valueRegions = FileLocations::treeOf(value)->info().regions;
394 addHighlight(valueRegions[PragmaValuesRegion], QmlHighlightKind::QmlPragmaValue);
395 }
396 return;
397}
398
399void HighlightingVisitor::highlightEnumDecl(const DomItem &item)
400{
401 const auto fLocs = FileLocations::treeOf(item);
402 if (!fLocs)
403 return;
404 const auto regions = fLocs->info().regions;
405 addHighlight(regions[EnumKeywordRegion], QmlHighlightKind::QmlKeyword);
406 addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlEnumName);
407}
408
409void HighlightingVisitor::highlightEnumItem(const DomItem &item)
410{
411 const auto fLocs = FileLocations::treeOf(item);
412 if (!fLocs)
413 return;
414 const auto regions = fLocs->info().regions;
415 addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlEnumMember);
416 if (regions.contains(EnumValueRegion))
417 addHighlight(regions[EnumValueRegion], QmlHighlightKind::Number);
418}
419
420void HighlightingVisitor::highlightQmlObject(const DomItem &item)
421{
422 const auto qmlObject = item.as<QmlObject>();
423 Q_ASSERT(qmlObject);
424 const auto fLocs = FileLocations::treeOf(item);
425 if (!fLocs)
426 return;
427 const auto regions = fLocs->info().regions;
428 // Handle ids here
429 if (!qmlObject->idStr().isEmpty()) {
430 addHighlight(regions[IdTokenRegion], QmlHighlightKind::QmlProperty);
431 addHighlight(regions[IdNameRegion], QmlHighlightKind::QmlLocalId);
432 }
433 // If dotted name, then defer it to be handled in ScriptIdentifierExpression
434 if (qmlObject->name().contains("."_L1))
435 return;
436
437 addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlType);
438}
439
440void HighlightingVisitor::highlightComponent(const DomItem &item)
441{
442 const auto fLocs = FileLocations::treeOf(item);
443 if (!fLocs)
444 return;
445 const auto regions = fLocs->info().regions;
446 const auto componentKeywordIt = regions.constFind(ComponentKeywordRegion);
447 if (componentKeywordIt == regions.constEnd())
448 return; // not an inline component, no need for highlighting
449 addHighlight(*componentKeywordIt, QmlHighlightKind::QmlKeyword);
450 addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlType);
451}
452
453void HighlightingVisitor::highlightPropertyDefinition(const DomItem &item)
454{
455 const auto propertyDef = item.as<PropertyDefinition>();
456 Q_ASSERT(propertyDef);
457 const auto fLocs = FileLocations::treeOf(item);
458 if (!fLocs)
459 return;
460 const auto regions = fLocs->info().regions;
461 QmlHighlightModifiers modifier = QmlHighlightModifier::QmlPropertyDefinition;
462 if (propertyDef->isDefaultMember) {
464 addHighlight(regions[DefaultKeywordRegion], QmlHighlightKind::QmlKeyword);
465 }
466 if (propertyDef->isVirtual) {
468 addHighlight(regions[VirtualKeywordRegion], QmlHighlightKind::QmlKeyword);
469 }
470 if (propertyDef->isOverride) {
472 addHighlight(regions[OverrideKeywordRegion], QmlHighlightKind::QmlKeyword);
473 }
474 if (propertyDef->isFinal) {
476 addHighlight(regions[FinalKeywordRegion], QmlHighlightKind::QmlKeyword);
477 }
478 if (propertyDef->isRequired) {
480 addHighlight(regions[RequiredKeywordRegion], QmlHighlightKind::QmlKeyword);
481 }
482 if (propertyDef->isReadonly) {
484 addHighlight(regions[ReadonlyKeywordRegion], QmlHighlightKind::QmlKeyword);
485 }
486 addHighlight(regions[PropertyKeywordRegion], QmlHighlightKind::QmlKeyword);
487 if (propertyDef->isAlias())
488 addHighlight(regions[TypeIdentifierRegion], QmlHighlightKind::QmlKeyword);
489 else
490 addHighlight(regions[TypeIdentifierRegion], QmlHighlightKind::QmlType);
491
492 addHighlight(regions[TypeModifierRegion], QmlHighlightKind::QmlTypeModifier);
493 addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlProperty,
494 modifier);
495}
496
497void HighlightingVisitor::highlightMethod(const DomItem &item)
498{
499 const auto method = item.as<MethodInfo>();
500 Q_ASSERT(method);
501 const auto fLocs = FileLocations::treeOf(item);
502 if (!fLocs)
503 return;
504 const auto regions = fLocs->info().regions;
505 switch (method->methodType) {
506 case MethodInfo::Signal: {
507 addHighlight(regions[SignalKeywordRegion], QmlHighlightKind::QmlKeyword);
508 addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlMethod);
509 break;
510 }
511 case MethodInfo::Method: {
512 addHighlight(regions[FunctionKeywordRegion], QmlHighlightKind::QmlKeyword);
513 addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlMethod);
514 addHighlight(regions[TypeIdentifierRegion], QmlHighlightKind::QmlType);
515 break;
516 }
517 default:
518 Q_UNREACHABLE();
519 }
520
521 for (auto i = 0; i < method->parameters.size(); ++i) {
522 DomItem parameter = item.field(Fields::parameters).index(i);
523 const auto paramRegions = FileLocations::treeOf(parameter)->info().regions;
524 addHighlight(paramRegions[IdentifierRegion],
525 QmlHighlightKind::QmlMethodParameter);
526 addHighlight(paramRegions[TypeIdentifierRegion], QmlHighlightKind::QmlType);
527 }
528 return;
529}
530
531void HighlightingVisitor::highlightScriptLiteral(const DomItem &item)
532{
533 const auto literal = item.as<ScriptElements::Literal>();
534 Q_ASSERT(literal);
535 const auto fLocs = FileLocations::treeOf(item);
536 if (!fLocs)
537 return;
538 const auto regions = fLocs->info().regions;
539 if (std::holds_alternative<QString>(literal->literalValue())) {
540 const auto file = item.containingFile().as<QmlFile>();
541 if (!file)
542 return;
543 const auto &code = file->engine()->code();
544 const auto offset = regions[MainRegion].offset;
545 const auto length = regions[MainRegion].length;
546 const QStringView literalCode = QStringView{code}.mid(offset, length);
547 const auto &locs = Utils::sourceLocationsFromMultiLineToken(
548 literalCode, regions[MainRegion]);
549 for (const auto &loc : locs)
550 addHighlight(loc, QmlHighlightKind::String);
551 } else if (std::holds_alternative<double>(literal->literalValue()))
552 addHighlight(regions[MainRegion], QmlHighlightKind::Number);
553 else if (std::holds_alternative<bool>(literal->literalValue()))
554 addHighlight(regions[MainRegion], QmlHighlightKind::QmlKeyword);
555 else if (std::holds_alternative<std::nullptr_t>(literal->literalValue()))
556 addHighlight(regions[MainRegion], QmlHighlightKind::QmlKeyword);
557 else
558 qCWarning(semanticTokens) << "Invalid literal variant";
559}
560
561void HighlightingVisitor::highlightIdentifier(const DomItem &item)
562{
563 using namespace QLspSpecification;
564 const auto id = item.as<ScriptElements::IdentifierExpression>();
565 Q_ASSERT(id);
566 const auto loc = id->mainRegionLocation();
567 // Many of the scriptIdentifiers expressions are already handled by
568 // other cases. In those cases, if the location offset is already in the list
569 // we don't need to perform expensive resolveExpressionType operation.
570 if (m_highlights.contains(loc.offset))
571 return;
572
573 // If the item is a field member base, we need to resolve the expression type
574 // If the item is a field member access, we don't need to resolve the expression type
575 // because it is already resolved in the first element.
576 if (QQmlLSUtils::isFieldMemberAccess(item))
577 highlightFieldMemberAccess(item, loc);
578 else
579 highlightBySemanticAnalysis(item, loc);
580}
581
582void HighlightingVisitor::highlightCallExpression(const DomItem &item)
583{
584 const auto highlight = [this](const DomItem &item) {
585 if (item.internalKind() == DomType::ScriptIdentifierExpression) {
586 const auto id = item.as<ScriptElements::IdentifierExpression>();
587 Q_ASSERT(id);
588 const auto loc = id->mainRegionLocation();
589 addHighlight(loc, QmlHighlightKind::QmlMethod);
590 }
591 };
592
593 if (item.internalKind() == DomType::ScriptCallExpression) {
594 // If the item is a call expression, we need to highlight the callee.
595 const auto callee = item.field(Fields::callee);
596 if (callee.internalKind() == DomType::ScriptIdentifierExpression) {
597 highlight(callee);
598 return;
599 } else if (callee.internalKind() == DomType::ScriptBinaryExpression) {
600 // If the callee is a binary expression, we need to highlight the right part.
601 const auto right = callee.field(Fields::right);
602 if (right.internalKind() == DomType::ScriptIdentifierExpression)
603 highlight(right);
604 return;
605 }
606 }
607}
608
609void HighlightingVisitor::highlightFieldMemberAccess(const DomItem &item,
610 QQmlJS::SourceLocation loc)
611{
612 // enum fields and qualified module identifiers are not just fields. Do semantic analysis if
613 // the identifier name is an uppercase string.
614 const auto name = item.field(Fields::identifier).value().toString();
615 if (!name.isEmpty() && name.at(0).category() == QChar::Letter_Uppercase) {
616 // maybe the identifier is an attached type or enum members, use semantic analysis to figure
617 // out.
618 return highlightBySemanticAnalysis(item, loc);
619 }
620 // Check if the name is a method
621 const auto expression =
622 QQmlLSUtils::resolveExpressionType(item, QQmlLSUtils::ResolveOptions::ResolveOwnerType);
623
624 if (!expression) {
625 addHighlight(loc, QmlHighlightKind::Field);
626 return;
627 }
628
629 if (expression->type == QQmlLSUtils::MethodIdentifier
630 || expression->type == QQmlLSUtils::LambdaMethodIdentifier) {
631 addHighlight(loc, QmlHighlightKind::QmlMethod);
632 return;
633 } else {
634 return addHighlight(loc, QmlHighlightKind::Field);
635 }
636}
637
638void HighlightingVisitor::highlightBySemanticAnalysis(const DomItem &item, QQmlJS::SourceLocation loc)
639{
640 const auto expression = QQmlLSUtils::resolveExpressionType(
641 item, QQmlLSUtils::ResolveOptions::ResolveOwnerType);
642
643 if (!expression) {
644 addHighlight(loc, QmlHighlightKind::Unknown);
645 return;
646 }
647 switch (expression->type) {
648 case QQmlLSUtils::QmlComponentIdentifier:
649 addHighlight(loc, QmlHighlightKind::QmlType);
650 return;
651 case QQmlLSUtils::JavaScriptIdentifier: {
653 QmlHighlightModifiers modifier = QmlHighlightModifier::None;
654 if (const auto scope = expression->semanticScope) {
655 if (const auto jsIdentifier = scope->jsIdentifier(*expression->name)) {
656 if (jsIdentifier->kind == QQmlJSScope::JavaScriptIdentifier::Parameter)
658 if (jsIdentifier->isConst) {
660 }
661 addHighlight(loc, tokenType, modifier);
662 return;
663 }
664 }
665 if (const auto name = expression->name) {
666 if (const auto highlightKind = resolveJsGlobalObjectKind(item, *name))
667 return addHighlight(loc, *highlightKind);
668 }
669 return;
670 }
671 case QQmlLSUtils::PropertyIdentifier: {
672 if (const auto scope = expression->semanticScope) {
674 if (scope == item.qmlObject().semanticScope()) {
676 } else if (scope == item.rootQmlObject(GoTo::MostLikely).semanticScope()) {
678 } else {
680 }
681 const auto property = scope->property(expression->name.value());
682 QmlHighlightModifiers modifier = QmlHighlightModifier::None;
683 if (!property.isWritable())
685 addHighlight(loc, tokenType, modifier);
686 }
687 return;
688 }
689 case QQmlLSUtils::PropertyChangedSignalIdentifier:
690 addHighlight(loc, QmlHighlightKind::QmlSignal);
691 return;
692 case QQmlLSUtils::PropertyChangedHandlerIdentifier:
693 addHighlight(loc, QmlHighlightKind::QmlSignalHandler);
694 return;
695 case QQmlLSUtils::SignalIdentifier:
696 addHighlight(loc, QmlHighlightKind::QmlSignal);
697 return;
698 case QQmlLSUtils::SignalHandlerIdentifier:
699 addHighlight(loc, QmlHighlightKind::QmlSignalHandler);
700 return;
701 case QQmlLSUtils::MethodIdentifier:
702 addHighlight(loc, QmlHighlightKind::QmlMethod);
703 return;
704 case QQmlLSUtils::QmlObjectIdIdentifier: {
705 if (!expression->semanticScope) {
706 // In PropertyChanges and friends, this id looks like a generalized grouped property but
707 // is actually custom parsed, so don't highlight it.
708 addHighlight(loc, QmlHighlightKind::Unknown);
709 return;
710 }
711 const auto qmlfile = item.fileObject().as<QmlFile>();
712 if (!qmlfile) {
713 addHighlight(loc, QmlHighlightKind::Unknown);
714 return;
715 }
716 const auto resolver = qmlfile->typeResolver();
717 if (!resolver) {
718 addHighlight(loc, QmlHighlightKind::Unknown);
719 return;
720 }
721 const auto &objects = resolver->objectsById();
722 if (expression->name.has_value()) {
723 const auto &name = expression->name.value();
724 const auto boundName =
725 objects.id(expression->semanticScope, item.qmlObject().semanticScope());
726 if (!boundName.isEmpty() && name == boundName) {
727 // If the name is the same as the bound name, then it is a local id.
728 addHighlight(loc, QmlHighlightKind::QmlLocalId);
729 return;
730 } else {
731 addHighlight(loc, QmlHighlightKind::QmlExternalId);
732 return;
733 }
734 } else {
735 addHighlight(loc, QmlHighlightKind::QmlExternalId);
736 return;
737 }
738 }
739 case QQmlLSUtils::SingletonIdentifier:
740 addHighlight(loc, QmlHighlightKind::QmlType);
741 return;
742 case QQmlLSUtils::EnumeratorIdentifier:
743 addHighlight(loc, QmlHighlightKind::QmlEnumName);
744 return;
745 case QQmlLSUtils::EnumeratorValueIdentifier:
746 addHighlight(loc, QmlHighlightKind::QmlEnumMember);
747 return;
748 case QQmlLSUtils::AttachedTypeIdentifier:
749 case QQmlLSUtils::AttachedTypeIdentifierInBindingTarget:
750 addHighlight(loc, QmlHighlightKind::QmlType);
751 return;
752 case QQmlLSUtils::GroupedPropertyIdentifier:
753 addHighlight(loc, QmlHighlightKind::QmlProperty);
754 return;
755 case QQmlLSUtils::QualifiedModuleIdentifier:
756 addHighlight(loc, QmlHighlightKind::QmlNamespace);
757 return;
758 default:
759 qCWarning(semanticTokens)
760 << QString::fromLatin1("Semantic token for %1 has not been implemented yet")
761 .arg(int(expression->type));
762 }
763}
764
765void HighlightingVisitor::highlightScriptExpressions(const DomItem &item)
766{
767 const auto fLocs = FileLocations::treeOf(item);
768 if (!fLocs)
769 return;
770 const auto regions = fLocs->info().regions;
771 switch (item.internalKind()) {
772 case DomType::ScriptLiteral:
773 highlightScriptLiteral(item);
774 return;
775 case DomType::ScriptForStatement:
776 addHighlight(regions[ForKeywordRegion], QmlHighlightKind::QmlKeyword);
777 addHighlight(regions[TypeIdentifierRegion],
778 QmlHighlightKind::QmlKeyword);
779 return;
780
781 case DomType::ScriptVariableDeclaration: {
782 addHighlight(regions[TypeIdentifierRegion],
783 QmlHighlightKind::QmlKeyword);
784 return;
785 }
786 case DomType::ScriptReturnStatement:
787 addHighlight(regions[ReturnKeywordRegion], QmlHighlightKind::QmlKeyword);
788 return;
789 case DomType::ScriptCaseClause:
790 addHighlight(regions[CaseKeywordRegion], QmlHighlightKind::QmlKeyword);
791 return;
792 case DomType::ScriptDefaultClause:
793 addHighlight(regions[DefaultKeywordRegion], QmlHighlightKind::QmlKeyword);
794 return;
795 case DomType::ScriptSwitchStatement:
796 addHighlight(regions[SwitchKeywordRegion], QmlHighlightKind::QmlKeyword);
797 return;
798 case DomType::ScriptWhileStatement:
799 addHighlight(regions[WhileKeywordRegion], QmlHighlightKind::QmlKeyword);
800 return;
801 case DomType::ScriptDoWhileStatement:
802 addHighlight(regions[DoKeywordRegion], QmlHighlightKind::QmlKeyword);
803 addHighlight(regions[WhileKeywordRegion], QmlHighlightKind::QmlKeyword);
804 return;
805 case DomType::ScriptTryCatchStatement:
806 addHighlight(regions[TryKeywordRegion], QmlHighlightKind::QmlKeyword);
807 addHighlight(regions[CatchKeywordRegion], QmlHighlightKind::QmlKeyword);
808 addHighlight(regions[FinallyKeywordRegion], QmlHighlightKind::QmlKeyword);
809 return;
810 case DomType::ScriptForEachStatement:
811 addHighlight(regions[TypeIdentifierRegion], QmlHighlightKind::QmlKeyword);
812 addHighlight(regions[ForKeywordRegion], QmlHighlightKind::QmlKeyword);
813 addHighlight(regions[InOfTokenRegion], QmlHighlightKind::QmlKeyword);
814 return;
815 case DomType::ScriptThrowStatement:
816 addHighlight(regions[ThrowKeywordRegion], QmlHighlightKind::QmlKeyword);
817 return;
818 case DomType::ScriptBreakStatement:
819 addHighlight(regions[BreakKeywordRegion], QmlHighlightKind::QmlKeyword);
820 return;
821 case DomType::ScriptContinueStatement:
822 addHighlight(regions[ContinueKeywordRegion], QmlHighlightKind::QmlKeyword);
823 return;
824 case DomType::ScriptIfStatement:
825 addHighlight(regions[IfKeywordRegion], QmlHighlightKind::QmlKeyword);
826 addHighlight(regions[ElseKeywordRegion], QmlHighlightKind::QmlKeyword);
827 return;
828 case DomType::ScriptLabelledStatement:
829 addHighlight(regions[IdentifierRegion], QmlHighlightKind::JsLabel);
830 return;
831 case DomType::ScriptConditionalExpression:
832 addHighlight(regions[QuestionMarkTokenRegion], QmlHighlightKind::Operator);
833 addHighlight(regions[ColonTokenRegion], QmlHighlightKind::Operator);
834 return;
835 case DomType::ScriptUnaryExpression:
836 case DomType::ScriptPostExpression:
837 addHighlight(regions[OperatorTokenRegion], QmlHighlightKind::Operator);
838 return;
839 case DomType::ScriptType:
840 addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlType);
841 addHighlight(regions[TypeIdentifierRegion], QmlHighlightKind::QmlType);
842 return;
843 case DomType::ScriptFunctionExpression: {
844 addHighlight(regions[FunctionKeywordRegion], QmlHighlightKind::QmlKeyword);
845 addHighlight(regions[IdentifierRegion], QmlHighlightKind::QmlMethod);
846 return;
847 }
848 case DomType::ScriptYieldExpression:
849 addHighlight(regions[YieldKeywordRegion], QmlHighlightKind::QmlKeyword);
850 return;
851 case DomType::ScriptThisExpression:
852 addHighlight(regions[ThisKeywordRegion], QmlHighlightKind::QmlKeyword);
853 return;
854 case DomType::ScriptSuperLiteral:
855 addHighlight(regions[SuperKeywordRegion], QmlHighlightKind::QmlKeyword);
856 return;
857 case DomType::ScriptNewMemberExpression:
858 case DomType::ScriptNewExpression:
859 addHighlight(regions[NewKeywordRegion], QmlHighlightKind::QmlKeyword);
860 return;
861 case DomType::ScriptTemplateExpressionPart:
862 addHighlight(regions[DollarLeftBraceTokenRegion], QmlHighlightKind::Operator);
863 visitor(Path(), item.field(Fields::expression), false);
864 addHighlight(regions[RightBraceRegion], QmlHighlightKind::Operator);
865 return;
866 case DomType::ScriptTemplateLiteral:
867 addHighlight(regions[LeftBacktickTokenRegion], QmlHighlightKind::String);
868 addHighlight(regions[RightBacktickTokenRegion], QmlHighlightKind::String);
869 return;
870 case DomType::ScriptTemplateStringPart: {
871 // handle multiline case
872 QString code = item.field(Fields::value).value().toString();
873 const auto &locs = Utils::sourceLocationsFromMultiLineToken(
874 code, regions[MainRegion]);
875 for (const auto &loc : locs)
876 addHighlight(loc, QmlHighlightKind::String);
877 return;
878 }
879 default:
880 qCDebug(semanticTokens) << "Script Expressions with kind" << item.internalKind()
881 << "not implemented";
882 }
883}
884
885void HighlightingVisitor::addHighlight(const QQmlJS::SourceLocation &loc, QmlHighlightKind highlightKind,
886 QmlHighlightModifiers modifierKind)
887{
888 return Utils::addHighlight(m_highlights, loc, highlightKind, modifierKind);
889}
890
891// A single physical line's worth of content within a multiline span, as found by
892// splitIntoLineSegments(): offset is absolute (into the QStringView that was split), and length
893// excludes whatever line-break characters follow it.
899
900// Splits [start, end) of `text` into per-physical-line segments, so a construct spanning
901// multiple lines (a comment, a string literal, a template literal's literal portions, ...) can
902// be reported one token per line. Treats both "\n" and "\r\n" as line breaks, excluding the break
903// itself from the segment. The final (partial) line is included unless the span ends exactly on
904// a line break, with nothing following it.
905static std::vector<LineSegment> splitIntoLineSegments(QStringView text, qsizetype start,
906 qsizetype end)
907{
908 std::vector<LineSegment> segments;
909 qsizetype segmentStart = start;
910 qsizetype pos = text.indexOf(u'\n', start);
911 while (pos != -1 && pos < end) {
912 qsizetype contentEnd = pos;
913 if (contentEnd > start + 1 && text[contentEnd - 1] == u'\r')
914 --contentEnd;
915 segments.push_back({ segmentStart, contentEnd - segmentStart });
916 segmentStart = pos + 1;
917 pos = text.indexOf(u'\n', segmentStart);
918 }
919 // Push the last line
920 if (segmentStart < end)
921 segments.push_back({ segmentStart, end - segmentStart });
922 return segments;
923}
924
925/*!
926\internal
927\brief Returns multiple source locations for a given raw comment
928
929Needed by semantic highlighting of comments. LSP clients usually don't support multiline
930tokens. In QML, we can have multiline tokens like string literals and comments.
931This method generates multiple source locations of sub-elements of token split by a newline
932delimiter.
933*/
935Utils::sourceLocationsFromMultiLineToken(QStringView stringLiteral,
936 const QQmlJS::SourceLocation &locationInDocument)
937{
938 QList<QQmlJS::SourceLocation> result;
939 // First token location should start from the "stringLiteral"'s
940 // location in the qml document.
941 QQmlJS::SourceLocation lineLoc = locationInDocument;
942 for (const auto &segment : splitIntoLineSegments(stringLiteral, 0, stringLiteral.size())) {
943 lineLoc.offset = locationInDocument.offset + quint32(segment.offset);
944 lineLoc.length = quint32(segment.length);
945 result.push_back(lineLoc);
946
947 ++lineLoc.startLine;
948 lineLoc.startColumn = 1;
949 }
950 return result;
951}
952
953QList<unsigned> Utils::encodeSemanticTokens(const HighlightsContainer &highlights,
954 HighlightingMode mode)
955{
956 QList<unsigned> result;
957 constexpr auto tokenEncodingLength = 5;
958 result.reserve(tokenEncodingLength * highlights.size());
959
960 unsigned prevLine = 0;
961 unsigned prevColumn = 0;
962 const auto m_mapToProtocol = mode == HighlightingMode::Default
963 ? mapToProtocolDefault
964 : mapToProtocolForQtCreator;
965 std::for_each(highlights.constBegin(), highlights.constEnd(), [&](const auto &token) {
966 unsigned length = token.loc.length;
967 unsigned line = token.loc.startLine - 1u; // protocol is 0-based
968 unsigned col = token.loc.startColumn - 1u; // protocol is 0-based
969 Q_ASSERT(line >= prevLine);
970 if (line != prevLine)
971 prevColumn = 0;
972 result.emplace_back(line - prevLine);
973 result.emplace_back(col - prevColumn);
974 result.emplace_back(length);
975 result.emplace_back(m_mapToProtocol(token.kind));
976 result.emplace_back(fromQmlModifierKindToLspTokenType(token.modifiers));
977 prevLine = line;
978 prevColumn = col;
979 });
980
981 return result;
982}
983
984/*!
985\internal
986Computes the modifier value. Modifier is read as binary value in the protocol. The location
987of the bits set are interpreted as the indices of the tokenModifiers list registered by the
988server. Then, the client modifies the highlighting of the token.
989
990tokenModifiersList: ["declaration", definition, readonly, static ,,,]
991
992To set "definition" and "readonly", we need to send 0b00000110
993*/
994void Utils::addModifier(SemanticTokenModifiers modifier, int *baseModifier)
995{
996 if (!baseModifier)
997 return;
998 *baseModifier |= (1 << int(modifier));
999}
1000
1001/*!
1002\internal
1003Check if the ranges overlap by ensuring that one range starts before the other ends
1004*/
1005bool Utils::rangeOverlapsWithSourceLocation(const QQmlJS::SourceLocation &loc,
1006 const HighlightsRange &r)
1007{
1008 int startOffsetItem = int(loc.offset);
1009 int endOffsetItem = startOffsetItem + int(loc.length);
1010 return (startOffsetItem <= r.endOffset) && (r.startOffset <= endOffsetItem);
1011}
1012
1013/*
1014\internal
1015Increments the resultID by one.
1016*/
1017void Utils::updateResultID(QByteArray &resultID)
1018{
1019 int length = resultID.length();
1020 for (int i = length - 1; i >= 0; --i) {
1021 if (resultID[i] == '9') {
1022 resultID[i] = '0';
1023 } else {
1024 resultID[i] = resultID[i] + 1;
1025 return;
1026 }
1027 }
1028 resultID.prepend('1');
1029}
1030
1031/*
1032\internal
1033A utility method that computes the difference of two list. The first argument is the encoded token data
1034of the file before edited. The second argument is the encoded token data after the file is edited. Returns
1035a list of SemanticTokensEdit as expected by the protocol.
1036*/
1037QList<SemanticTokensEdit> Utils::computeDiff(const QList<unsigned> &oldData,
1038 const QList<unsigned> &newData)
1039{
1040 // Find the iterators pointing the first mismatch, from the start
1041 const auto [oldStart, newStart] =
1042 std::mismatch(oldData.cbegin(), oldData.cend(), newData.cbegin(), newData.cend());
1043
1044 // Find the iterators pointing the first mismatch, from the end
1045 // but the iterators shouldn't pass over the start iterators found above.
1046 const auto [r1, r2] = std::mismatch(oldData.crbegin(), std::make_reverse_iterator(oldStart),
1047 newData.crbegin(), std::make_reverse_iterator(newStart));
1048 const auto oldEnd = r1.base();
1049 const auto newEnd = r2.base();
1050
1051 // no change
1052 if (oldStart == oldEnd && newStart == newEnd)
1053 return {};
1054
1055 SemanticTokensEdit edit;
1056 edit.start = int(std::distance(newData.cbegin(), newStart));
1057 edit.deleteCount = int(std::distance(oldStart, oldEnd));
1058
1059 if (newStart >= newData.cbegin() && newEnd <= newData.cend() && newStart < newEnd)
1060 edit.data.emplace(newStart, newEnd);
1061
1062 return { std::move(edit) };
1063}
1064
1065void Utils::addHighlight(HighlightsContainer &out,
1066 const QQmlJS::SourceLocation &loc,
1067 QmlHighlightKind highlightKind,
1068 QmlHighlightModifiers modifierKind)
1069{
1070 if (!loc.isValid() || loc.length == 0) {
1071 qCDebug(semanticTokens)
1072 << "Invalid locations: Cannot add highlight to token";
1073 return;
1074 }
1075 if (!out.contains(loc.offset))
1076 out.insert(loc.offset, HighlightToken(loc, highlightKind, modifierKind));
1077}
1078
1079HighlightsContainer Utils::visitTokens(const QQmlJS::Dom::DomItem &item,
1080 const std::optional<HighlightsRange> &range)
1081{
1082 using namespace QQmlJS::Dom;
1083 HighlightingVisitor highlightDomElements(item, range);
1084 return highlightDomElements.highlights();
1085}
1086
1087HighlightsContainer Utils::shiftHighlights(const HighlightsContainer &cachedHighlights,
1088 const QString &lastValidCode, const QString &currentCode)
1089{
1090 using namespace QQmlLSUtils;
1091 Differ differ;
1092 const QList<Diff> diffs = differ.diff(lastValidCode, currentCode);
1093 HighlightsContainer shifts = cachedHighlights;
1094 applyDiffs(shifts, diffs);
1095 return shifts;
1096}
1097
1098namespace {
1099
1100struct LineAnchor
1101{
1102 qsizetype offset = 0;
1103 quint32 number = 1;
1104};
1105
1106struct FallbackRule
1107{
1108 QRegularExpression regex;
1109 void (*action)(HighlightsContainer &, const QRegularExpressionMatch &, LineAnchor line);
1110};
1111
1112static void addFallbackToken(HighlightsContainer &out, LineAnchor line, qsizetype offset,
1113 qsizetype length, QmlHighlightKind kind,
1114 QmlHighlightModifiers modifiers = QmlHighlightModifier::None)
1115{
1116 Q_ASSERT(length > 0);
1117 const auto loc = QQmlJS::SourceLocation::fromQSizeType(offset, length, line.number,
1118 offset - line.offset + 1);
1119 Utils::addHighlight(out, loc, kind, modifiers);
1120}
1121
1122static void addGroup(HighlightsContainer &out, const QRegularExpressionMatch &m, int group,
1123 LineAnchor line, QmlHighlightKind kind,
1124 QmlHighlightModifiers modifiers = QmlHighlightModifier::None)
1125{
1126 if (!m.hasCaptured(group))
1127 return;
1128 addFallbackToken(out, line, line.offset + m.capturedStart(group), m.capturedLength(group), kind,
1129 modifiers);
1130}
1131
1132static void highlightSignalParameters(HighlightsContainer &out, const QRegularExpressionMatch &m,
1133 LineAnchor line)
1134{
1135 static const QRegularExpression paramRe(
1136 uR"re((?:([\w.<>]+)\s+(\w+))|(?:(\w+)\s*:\s*([\w.<>]+)))re"_s);
1137 const qsizetype paramsStart = m.capturedStart(3);
1138 Q_ASSERT(paramsStart >= 0);
1139 auto it = paramRe.globalMatchView(m.capturedView(3));
1140 while (it.hasNext()) {
1141 const auto pm = it.next();
1142 if (pm.capturedStart(1) >= 0) {
1143 addFallbackToken(out, line, line.offset + paramsStart + pm.capturedStart(1),
1144 pm.capturedLength(1), QmlHighlightKind::QmlType);
1145 addFallbackToken(out, line, line.offset + paramsStart + pm.capturedStart(2),
1146 pm.capturedLength(2), QmlHighlightKind::QmlMethodParameter);
1147 } else {
1148 addFallbackToken(out, line, line.offset + paramsStart + pm.capturedStart(3),
1149 pm.capturedLength(3), QmlHighlightKind::QmlMethodParameter);
1150 addFallbackToken(out, line, line.offset + paramsStart + pm.capturedStart(4),
1151 pm.capturedLength(4), QmlHighlightKind::QmlType);
1152 }
1153 }
1154}
1155
1156static QmlHighlightModifiers propertyModifiers(QStringView modifierWord)
1157{
1158 QmlHighlightModifiers modifiers = QmlHighlightModifier::QmlPropertyDefinition;
1159 if (modifierWord == u"readonly")
1161 else if (modifierWord == u"required")
1163 else if (modifierWord == u"default")
1165 else if (modifierWord == u"final")
1167 else if (modifierWord == u"virtual")
1169 else if (modifierWord == u"override")
1171 return modifiers;
1172}
1173
1174// Rules are tried in order at every position; the earliest match in the line wins, and ties are
1175// broken by priority (the rule listed first). This lets specific QML constructs (property
1176// definitions, signals, imports, ...) take precedence over the generic keyword/identifier rules
1177// that follow them.
1178const QList<FallbackRule> &fallbackRules()
1179{
1180 static const QList<FallbackRule> rules = [] {
1181 QList<FallbackRule> r;
1182 // Line comments.
1183 r.append({ QRegularExpression(uR"re(//.*)re"_s),
1184 [](HighlightsContainer &out, const QRegularExpressionMatch &m, LineAnchor line) {
1185 addGroup(out, m, 0, line, QmlHighlightKind::Comment);
1186 } });
1187 // Single-line strings.
1188 r.append({ QRegularExpression(uR"re("(?:[^"\\‍]|\\.)*"|'(?:[^'\\‍]|\\.)*')re"_s),
1189 [](HighlightsContainer &out, const QRegularExpressionMatch &m, LineAnchor line) {
1190 addGroup(out, m, 0, line, QmlHighlightKind::String);
1191 } });
1192
1193 // import <ModuleOrPath> [version] [as Alias]
1194 r.append(
1195 { QRegularExpression(
1196 uR"re(\b(import)\b\s+(?:("(?:[^"\\‍]|\\.)*")|([\w.]+))(?:\s+(\d+\.\d+))?(?:\s+(as)\s+(\w+))?)re"_s),
1197 [](HighlightsContainer &out, const QRegularExpressionMatch &m, LineAnchor line) {
1198 addGroup(out, m, 1, line, QmlHighlightKind::QmlKeyword);
1199 addGroup(out, m, 2, line, QmlHighlightKind::String);
1200 addGroup(out, m, 3, line, QmlHighlightKind::QmlImportId);
1201 addGroup(out, m, 4, line, QmlHighlightKind::Number);
1202 addGroup(out, m, 5, line, QmlHighlightKind::QmlKeyword);
1203 addGroup(out, m, 6, line, QmlHighlightKind::QmlNamespace);
1204 } });
1205 // [default|readonly|required|final|virtual|override] property <type|alias> <name>
1206 r.append(
1207 { QRegularExpression(
1208 uR"re(\b(?:(default|readonly|required|final|virtual|override)\s+)*(property)\s+(alias|[A-Za-z_][\w.<>]*)\s+(\w+))re"_s),
1209 [](HighlightsContainer &out, const QRegularExpressionMatch &m, LineAnchor line) {
1210 addGroup(out, m, 1, line, QmlHighlightKind::QmlKeyword);
1211 addGroup(out, m, 2, line, QmlHighlightKind::QmlKeyword);
1212 const QStringView type = m.capturedView(3);
1213 addGroup(out, m, 3, line,
1214 type == u"alias" ? QmlHighlightKind::QmlKeyword
1216 addGroup(out, m, 4, line, QmlHighlightKind::QmlProperty,
1217 propertyModifiers(m.capturedView(1)));
1218 } });
1219 // required <name>
1220 r.append({ QRegularExpression(uR"re((?:^|[{;])\s*(required)\s+(?!property\b)(\w+))re"_s),
1221 [](HighlightsContainer &out, const QRegularExpressionMatch &m, LineAnchor line) {
1222 addGroup(out, m, 1, line, QmlHighlightKind::QmlKeyword);
1223 addGroup(out, m, 2, line, QmlHighlightKind::QmlProperty,
1225 } });
1226 // signal <name>(<type name>, ...)
1227 r.append({ QRegularExpression(uR"re(\b(signal)\s+(\w+)\s*\‍(([^)]*)\‍))re"_s),
1228 [](HighlightsContainer &out, const QRegularExpressionMatch &m, LineAnchor line) {
1229 addGroup(out, m, 1, line, QmlHighlightKind::QmlKeyword);
1230 addGroup(out, m, 2, line, QmlHighlightKind::QmlSignal);
1231 highlightSignalParameters(out, m, line);
1232 } });
1233 // function <name>(
1234 r.append({ QRegularExpression(uR"re(\b(function)\s+(\w+)\s*(?=\‍())re"_s),
1235 [](HighlightsContainer &out, const QRegularExpressionMatch &m, LineAnchor line) {
1236 addGroup(out, m, 1, line, QmlHighlightKind::QmlKeyword);
1237 addGroup(out, m, 2, line, QmlHighlightKind::QmlMethod);
1238 } });
1239 // id: <identifier>
1240 r.append({ QRegularExpression(uR"re((?:^|[{;])\s*(id)\s*:\s*(\w+))re"_s),
1241 [](HighlightsContainer &out, const QRegularExpressionMatch &m, LineAnchor line) {
1242 addGroup(out, m, 1, line, QmlHighlightKind::QmlKeyword);
1243 addGroup(out, m, 2, line, QmlHighlightKind::QmlLocalId);
1244 } });
1245 // onSomething: <handler> signal handler properties.
1246 r.append({ QRegularExpression(uR"re(\bon[A-Z]\w*(?=\s*:))re"_s),
1247 [](HighlightsContainer &out, const QRegularExpressionMatch &m, LineAnchor line) {
1248 addGroup(out, m, 0, line, QmlHighlightKind::QmlSignalHandler);
1249 } });
1250 // pragma <Name> [Value]
1251 r.append({ QRegularExpression(uR"re((?:^|[{;])\s*(pragma)\s+(\w+)(?:\s+(\w+))?)re"_s),
1252 [](HighlightsContainer &out, const QRegularExpressionMatch &m, LineAnchor line) {
1253 addGroup(out, m, 1, line, QmlHighlightKind::QmlKeyword);
1254 addGroup(out, m, 2, line, QmlHighlightKind::QmlPragmaName);
1255 addGroup(out, m, 3, line, QmlHighlightKind::QmlPragmaValue);
1256 } });
1257 // <name>: property/binding assignment.
1258 r.append({ QRegularExpression(uR"re((?:^|[{;])\s*([a-z_]\w*(?:\.[a-z_]\w*)*)\s*(?=:))re"_s),
1259 [](HighlightsContainer &out, const QRegularExpressionMatch &m, LineAnchor line) {
1260 addGroup(out, m, 1, line, QmlHighlightKind::QmlProperty);
1261 } });
1262 // QML/JS keywords.
1263 r.append(
1264 { QRegularExpression(
1265 uR"re(\b(?:as|async|await|break|case|catch|class|component|const|continue|
1266 debugger|default|delete|do|else|enum|export|extends|false|final|finally|
1267 for|function|id|if|import|in|instanceof|let|new|null|on|override|pragma|
1268 property|readonly|required|return|signal|super|switch|this|throw|true|
1269 try|typeof|undefined|var|virtual|void|while|with|yield)\b)re"_s),
1270 [](HighlightsContainer &out, const QRegularExpressionMatch &m, LineAnchor line) {
1271 addGroup(out, m, 0, line, QmlHighlightKind::QmlKeyword);
1272 } });
1273 // Numbers.
1274 r.append({ QRegularExpression(uR"re(\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b)re"_s),
1275 [](HighlightsContainer &out, const QRegularExpressionMatch &m, LineAnchor line) {
1276 addGroup(out, m, 0, line, QmlHighlightKind::Number);
1277 } });
1278 // Property/enum access after a dot, e.g. mouse.x or Text.AlignHCenter.
1279 r.append({ QRegularExpression(uR"re((?<=\.)\w+)re"_s),
1280 [](HighlightsContainer &out, const QRegularExpressionMatch &m, LineAnchor line) {
1281 addGroup(out, m, 0, line, QmlHighlightKind::Field);
1282 } });
1283 // Capitalized identifiers are QML type names by convention.
1284 r.append({ QRegularExpression(uR"re(\b[A-Z]\w*\b)re"_s),
1285 [](HighlightsContainer &out, const QRegularExpressionMatch &m, LineAnchor line) {
1286 addGroup(out, m, 0, line, QmlHighlightKind::QmlType);
1287 } });
1288 // Function call sites, including the tag of a tagged template literal (tag`...`).
1289 r.append({ QRegularExpression(uR"re(\b[A-Za-z_]\w*(?=\s*[(`]))re"_s),
1290 [](HighlightsContainer &out, const QRegularExpressionMatch &m, LineAnchor line) {
1291 addGroup(out, m, 0, line, QmlHighlightKind::QmlMethod);
1292 } });
1293 return r;
1294 }();
1295 return rules;
1296}
1297
1298// Emits one token per physical line for the span [start, end), so a construct that runs across
1299// a line break (a block comment, or the literal text portions of a template literal) is still
1300// reported as one token per line, matching how every other multi-line construct is split.
1301static void addMultilineToken(HighlightsContainer &out, QStringView text, qsizetype start,
1302 qsizetype end, LineAnchor &line, QmlHighlightKind kind)
1303{
1304 const auto segments = splitIntoLineSegments(text, start, end);
1305 for (size_t i = 0; i < segments.size(); ++i) {
1306 if (segments[i].length > 0)
1307 addFallbackToken(out, line, segments[i].offset, segments[i].length, kind);
1308 if (i + 1 < segments.size()) {
1309 ++line.number;
1310 line.offset = segments[i + 1].offset;
1311 }
1312 }
1313 if (!segments.empty() && segments.back().offset + segments.back().length < end) {
1314 // the span ends exactly on a line break, with nothing following it on that new line
1315 ++line.number;
1316 line.offset = end;
1317 }
1318}
1319
1320// Highlights a "/* ... */" block comment starting at the opening slash. May span multiple
1321// lines; returns the offset just past the closing "*/" (or text.size() if left unterminated).
1322static qsizetype scanBlockComment(HighlightsContainer &out, QStringView text, qsizetype pos,
1323 LineAnchor &line)
1324{
1325 const qsizetype closeIdx = text.indexOf(u"*/", pos + 2);
1326 const qsizetype end = closeIdx == -1 ? text.size() : closeIdx + 2;
1327 addMultilineToken(out, text, pos, end, line, QmlHighlightKind::Comment);
1328 return end;
1329}
1330
1331// Forward declared: scanTemplateLiteral() recurses into this to highlight the contents of a
1332// `${ expr }` interpolation
1333static qsizetype scanSpan(HighlightsContainer &out, QStringView text, qsizetype pos,
1334 LineAnchor &line, bool stopAtUnbalancedBrace);
1335
1336// Highlights a "`...`" template literal starting at the opening backtick, including any
1337// "${ expr }" interpolations, which may themselves span lines or contain further, nested
1338// template literals. Returns the offset just past the closing backtick (or text.size() if the
1339// literal is left unterminated).
1340static qsizetype scanTemplateLiteral(HighlightsContainer &out, QStringView text, qsizetype pos,
1341 LineAnchor &line)
1342{
1343 const qsizetype size = text.size();
1344 qsizetype segmentStart = pos;
1345 ++pos; // step over the opening backtick
1346 while (pos < size) {
1347 const QChar c = text[pos];
1348 if (c == u'\\') {
1349 pos += 2; // an escaped character (e.g. \` or \\‍) never terminates the literal
1350 continue;
1351 }
1352 if (c == u'`') {
1353 ++pos;
1354 addMultilineToken(out, text, segmentStart, pos, line, QmlHighlightKind::String);
1355 return pos;
1356 }
1357 if (c == u'$' && pos + 1 < size && text[pos + 1] == u'{') {
1358 addMultilineToken(out, text, segmentStart, pos, line, QmlHighlightKind::String);
1359 addFallbackToken(out, line, pos, 2, QmlHighlightKind::Operator);
1360 pos = scanSpan(out, text, pos + 2, line, /* stopAtUnbalancedBrace = */ true);
1361 if (pos < size) {
1362 addFallbackToken(out, line, pos, 1, QmlHighlightKind::Operator);
1363 ++pos;
1364 }
1365 segmentStart = pos;
1366 continue;
1367 }
1368 ++pos;
1369 }
1370 // Unterminated: whatever is left is highlighted as string content.
1371 addMultilineToken(out, text, segmentStart, size, line, QmlHighlightKind::String);
1372 return size;
1373}
1374
1375enum class SpecialChar { None, BlockComment, TemplateLiteral, OpenBrace, CloseBrace };
1376struct SpecialCharMatch
1377{
1378 qsizetype col = -1;
1379 SpecialChar kind = SpecialChar::None;
1380};
1381
1382static SpecialCharMatch nextSpecialChar(QStringView lineView, qsizetype col, bool trackBraces)
1383{
1384 for (qsizetype i = col; i < lineView.size(); ++i) {
1385 const QChar c = lineView[i];
1386 if (c == u'`')
1387 return { i, SpecialChar::TemplateLiteral };
1388 if (c == u'/' && i + 1 < lineView.size() && lineView[i + 1] == u'*')
1389 return { i, SpecialChar::BlockComment };
1390 if (trackBraces && c == u'{')
1391 return { i, SpecialChar::OpenBrace };
1392 if (trackBraces && c == u'}')
1393 return { i, SpecialChar::CloseBrace };
1394 }
1395 return {};
1396}
1397
1398// Applies the fallback rule table to `text`, starting at `pos`.
1399// When stopAtUnbalancedBrace is true (used to scan the contents of a `${ expr }`
1400// interpolation), scanning stops at the first "}" that doesn't close a "{" seen since `pos`,
1401// returning its offset without consuming it. Returns the offset scanning stopped at (text.size()
1402// for a top-level/unbounded scan that ran off the end).
1403static qsizetype scanSpan(HighlightsContainer &out, QStringView text, qsizetype pos,
1404 LineAnchor &line, bool stopAtUnbalancedBrace)
1405{
1406 const qsizetype size = text.size();
1407 const QList<FallbackRule> &rules = fallbackRules();
1408 int braceDepth = 0;
1409
1410 while (pos <= size) {
1411 qsizetype physLineEnd = text.indexOf(u'\n', pos);
1412 if (physLineEnd == -1)
1413 physLineEnd = size;
1414 const QStringView lineView = text.mid(line.offset, physLineEnd - line.offset);
1415 const qsizetype col = pos - line.offset;
1416
1417 const FallbackRule *bestRule = nullptr;
1418 QRegularExpressionMatch bestMatch;
1419 for (const auto &rule : rules) {
1420 const auto m = rule.regex.matchView(lineView, col);
1421 if (!m.hasMatch())
1422 continue;
1423 if (!bestRule || m.capturedStart(0) < bestMatch.capturedStart(0)) {
1424 bestRule = &rule;
1425 bestMatch = m;
1426 }
1427 }
1428
1429 const SpecialCharMatch special = nextSpecialChar(lineView, col, stopAtUnbalancedBrace);
1430 const bool ruleWins = bestRule
1431 && (special.kind == SpecialChar::None || bestMatch.capturedStart(0) <= special.col);
1432
1433 if (!bestRule && special.kind == SpecialChar::None) {
1434 if (physLineEnd >= size)
1435 return size;
1436 line.offset = physLineEnd + 1;
1437 pos = line.offset;
1438 ++line.number;
1439 continue;
1440 }
1441
1442 if (ruleWins) {
1443 bestRule->action(out, bestMatch, line);
1444 pos = line.offset + qMax(bestMatch.capturedEnd(0), col + 1);
1445 continue;
1446 }
1447
1448 const qsizetype absPos = line.offset + special.col;
1449 switch (special.kind) {
1450 case SpecialChar::BlockComment:
1451 pos = scanBlockComment(out, text, absPos, line);
1452 break;
1453 case SpecialChar::TemplateLiteral:
1454 pos = scanTemplateLiteral(out, text, absPos, line);
1455 break;
1456 case SpecialChar::OpenBrace:
1457 ++braceDepth;
1458 pos = absPos + 1;
1459 break;
1460 case SpecialChar::CloseBrace:
1461 if (braceDepth == 0)
1462 return absPos;
1463 --braceDepth;
1464 pos = absPos + 1;
1465 break;
1466 case SpecialChar::None:
1467 Q_UNREACHABLE();
1468 }
1469 }
1470 return pos;
1471}
1472
1473} // namespace
1474
1475// Highlighting for code that cannot be parsed into a DomItem at all
1476// Scans the document against a table of QML/JS regular expressions. Requires no
1477// semantic information, so it degrades gracefully on incomplete or invalid QML/JS.
1479 const std::optional<HighlightsRange> &range)
1480{
1481 HighlightsContainer highlights;
1482 LineAnchor line;
1483 scanSpan(highlights, code, 0, line, /* stopAtUnbalancedBrace = */ false);
1484
1485 if (range) {
1486 for (auto it = highlights.begin(); it != highlights.end();) {
1487 if (!rangeOverlapsWithSourceLocation(it->loc, *range))
1488 it = highlights.erase(it);
1489 else
1490 ++it;
1491 }
1492 }
1493
1494 return highlights;
1495}
1496
1498{
1499 auto [row, col] = QQmlJS::SourceLocation::rowAndColumnFrom(text, text.size());
1500 return { row - 1, col - 1 }; // rows are 1-based, so subtract 1 to get the number of newlines
1501}
1502
1503static void updateCursorPositionByDiff(const QString &text, QQmlJS::SourceLocation &cursor)
1504{
1505 auto [newLines, lastLineLength] = newlineCountAndLastLineLength(text);
1506 if (newLines > 0) {
1507 cursor.startLine += newLines;
1508 cursor.startColumn = lastLineLength + 1; // +1 because columns are 1-based
1509 } else {
1510 cursor.startColumn += text.size();
1511 }
1512 cursor.offset += text.size();
1513};
1514
1515//
1516// Utilities for insertion handling
1517//
1518
1519static bool tokenBeforeOffset(const QQmlJS::SourceLocation &t, quint32 offset)
1520{
1521 return t.end() < offset;
1522}
1523
1524static bool tokenAfterOffset(const QQmlJS::SourceLocation &t, quint32 offset)
1525{
1526 return t.begin() > offset;
1527}
1528
1529static bool insertionInsideToken(const QQmlJS::SourceLocation &token,
1530 const QQmlJS::SourceLocation &cursor)
1531{
1532 return token.begin() < cursor.begin() && token.end() >= cursor.begin();
1533}
1534
1535static bool insertionTouchesTokenLeft(const QQmlJS::SourceLocation &token,
1536 const QQmlJS::SourceLocation &cursor)
1537{
1538 return token.begin() >= cursor.begin() && token.begin() <= cursor.end();
1539}
1540
1541static void shiftTokenAfterInsert(QQmlJS::SourceLocation &t, const QQmlJS::SourceLocation &cursor,
1542 int newlines, int lastLen, int diffLen)
1543{
1544 if (t.startLine == cursor.startLine) {
1545 if (newlines > 0) {
1546 t.startColumn = lastLen + t.startColumn - cursor.startColumn + 1;
1547 } else {
1548 t.startColumn += lastLen;
1549 }
1550 }
1551 t.startLine += newlines;
1552 t.offset += diffLen;
1553}
1554
1555static void expandTokenForMiddleInsert(QQmlJS::SourceLocation &t, const QQmlLSUtils::Diff &diff,
1556 const QQmlJS::SourceLocation &cursor)
1557{
1558 auto begin = diff.text.cbegin();
1559 auto end = diff.text.cend();
1560
1561 auto ptr = std::find_if(begin, end, [](QChar c) { return c.isSpace(); });
1562
1563 if (ptr != end) {
1564 t.length = cursor.begin() - t.begin() + std::distance(begin, ptr);
1565 } else {
1566 t.length += diff.text.size();
1567 }
1568}
1569
1570static void expandTokenForLeftOverlap(QQmlJS::SourceLocation &t, const QQmlLSUtils::Diff &diff,
1571 const QQmlJS::SourceLocation &cursor, int newlines,
1572 int lastLen)
1573{
1574 const int diffLen = diff.text.size();
1575 t.offset = cursor.begin();
1576 t.length += diffLen;
1577 t.startLine = cursor.startLine;
1578 t.startColumn = cursor.startColumn;
1579
1580 // find last space inside diff text
1581 auto rbegin = diff.text.rbegin();
1582 auto rend = diff.text.rend();
1583 auto ptr = std::find_if(rbegin, rend, [](QChar c) { return c.isSpace(); });
1584
1585 if (ptr != rend) {
1586 std::ptrdiff_t omitted = std::distance(ptr, rend);
1587 t.offset += omitted;
1588 t.length -= omitted;
1589 t.startColumn += omitted;
1590 }
1591
1592 // adjust if diff contains newlines
1593 if (newlines > 0) {
1594 t.startLine += newlines;
1595 t.startColumn = lastLen - std::distance(ptr.base(), diff.text.end()) + 1;
1596 }
1597}
1598
1599static void updateHighlightsOnInsert(HighlightsContainer &highlights,
1600 QQmlJS::SourceLocation &cursor, const QQmlLSUtils::Diff &diff)
1601{
1602 const auto [newlines, lastLen] = newlineCountAndLastLineLength(diff.text);
1603 const auto diffLen = diff.text.size();
1604 cursor.length = quint32(diffLen); // set length for insertion range, used in overlap checks
1605
1606 HighlightsContainer shifted;
1607
1608 for (auto item : highlights) {
1609 auto &token = item.loc;
1610 if (tokenBeforeOffset(token, cursor.begin())) {
1611 shifted.insert(token.offset, item);
1612 continue;
1613 }
1614
1615 if (tokenAfterOffset(token, cursor.begin())) {
1616 shiftTokenAfterInsert(token, cursor, newlines, lastLen, diffLen);
1617 shifted.insert(token.offset, item);
1618 continue;
1619 }
1620
1621 // Overlap cases
1622 if (insertionInsideToken(token, cursor)) {
1623 expandTokenForMiddleInsert(token, diff, cursor);
1624 } else if (insertionTouchesTokenLeft(token, cursor)) {
1625 expandTokenForLeftOverlap(token, diff, cursor, newlines, lastLen);
1626 }
1627
1628 shifted.insert(token.offset, item);
1629 }
1630
1631 highlights.swap(shifted);
1632
1633 // Advance cursor for the next Diff
1634 updateCursorPositionByDiff(diff.text, cursor);
1635}
1636
1637//
1638// Utilities for deletion handling
1639//
1640static bool spansAcrossDeletion(const QQmlJS::SourceLocation &t, quint32 delStart, quint32 delEnd)
1641{
1642 return t.begin() < delStart && t.end() > delEnd;
1643}
1644
1645static bool leftFragmentRemains(const QQmlJS::SourceLocation &t, quint32 delStart, quint32 delEnd)
1646{
1647 return t.begin() < delStart && t.end() <= delEnd;
1648}
1649
1650static bool rightFragmentRemains(const QQmlJS::SourceLocation &t, quint32 delStart, quint32 delEnd)
1651{
1652 return t.begin() >= delStart && t.end() > delEnd;
1653}
1654
1655//
1656// Shift token after deletion
1657//
1658static void shiftTokenAfterDelete(QQmlJS::SourceLocation &t, int newlines, int lastLen,
1659 const QQmlJS::SourceLocation &cursor, int diffLen)
1660{
1661 t.offset -= diffLen;
1662
1663 // Adjust column on deletion end line
1664 if (t.startLine == cursor.startLine + newlines) {
1665 if (newlines > 0) {
1666 t.startColumn = cursor.startColumn + (t.startColumn - lastLen) - 1;
1667 } else {
1668 t.startColumn -= lastLen;
1669 }
1670 }
1671
1672 // Shift line upwards
1673 t.startLine -= newlines;
1674}
1675
1676//
1677// Apply overlap logic
1678//
1679static void applyDeletionOverlap(QQmlJS::SourceLocation &t, quint32 delStart, quint32 delEnd,
1680 int newlines, quint32 delStartLine, quint32 delStartColumn)
1681{
1682 const quint32 deletedLen = delEnd - delStart;
1683
1684 if (spansAcrossDeletion(t, delStart, delEnd)) {
1685 // Middle removed
1686 t.length -= deletedLen;
1687 return;
1688 }
1689
1690 if (leftFragmentRemains(t, delStart, delEnd)) {
1691 // Left side remains
1692 t.length = delStart - t.begin();
1693 return;
1694 }
1695
1696 if (rightFragmentRemains(t, delStart, delEnd)) {
1697 // Right side remains, shifted to the deletion start
1698 quint32 overlap = delEnd - t.begin();
1699 t.offset = delStart;
1700 t.length -= overlap;
1701
1702 t.startColumn = delStartColumn;
1703 if (newlines > 0)
1704 t.startLine = delStartLine;
1705
1706 return;
1707 }
1708
1709 // Fully removed
1710 t.length = 0;
1711}
1712
1713static void updateHighlightsOnDelete(HighlightsContainer &highlights,
1714 QQmlJS::SourceLocation &cursor, const QQmlLSUtils::Diff &diff)
1715{
1716 const auto [newlines, lastLen] = newlineCountAndLastLineLength(diff.text);
1717 const int diffLen = diff.text.size();
1718
1719 cursor.length = diffLen;
1720
1721 const quint32 delStart = cursor.offset;
1722 const quint32 delEnd = cursor.offset + diffLen;
1723
1724 HighlightsContainer shifts;
1725
1726 for (auto item : highlights) {
1727 auto &token = item.loc;
1728
1729 //
1730 // Case A: token fully before deleted region
1731 //
1732 if (tokenBeforeOffset(token, delStart)) {
1733 shifts.insert(token.offset, item);
1734 continue;
1735 }
1736
1737 //
1738 // Case B: token fully after deleted region
1739 //
1740 if (tokenAfterOffset(token, delEnd)) {
1741 shiftTokenAfterDelete(token, newlines, lastLen, cursor, diffLen);
1742 shifts.insert(token.offset, item);
1743 continue;
1744 }
1745
1746 //
1747 // Case C: deletion overlaps token
1748 //
1749 applyDeletionOverlap(token, delStart, delEnd, newlines, cursor.startLine,
1750 cursor.startColumn);
1751
1752 if (token.length == 0)
1753 continue; // fully removed
1754
1755 shifts.insert(token.offset, item);
1756 }
1757
1758 highlights.swap(shifts);
1759}
1760
1761/*
1762Equal:
1763- Just advance the running offset by length.
1764- No changes to the map.
1765
1766Insert:
1767- Insert new entries at the current offset.
1768- case A: token before insertion offset: no highlight change
1769- case B: token after insertion offset: slide all offsets forward by the length of the inserted text.
1770 sub case: if the insertion is on the same line as the token, adjust the column accordingly.
1771- case C: insertion overlaps token: expand the token length by the length of the inserted text
1772 sub case 1: insertion is inside the token: expand length
1773 sub case 2: insertion touches left of the token: adjust offset to insertion start,
1774 expand length, adjust line/column if needed.
1775
1776Delete:
1777- Case A: token before deletion offset: no highlight change
1778- case B: token after deletion offset: slide all offsets backward by the length of the deleted text.
1779 sub case: if the deletion ends on the same line as the token, adjust the column accordingly.
1780- case C: deletion overlaps token:
1781 sub case 1: spans across deletion: reduce length by deleted length
1782 sub case 2: left fragment remains: adjust length to the left fragment length
1783 sub case 3: right fragment remains: adjust offset to deletion start, adjust length to right fragment length,
1784 adjust line/column if needed.
1785 sub case 4: fully removed: remove the token from the map.
1786*/
1787void Utils::applyDiffs(HighlightsContainer &highlights, const QList<QQmlLSUtils::Diff> &diffs)
1788{
1789 using namespace QQmlLSUtils;
1790 if (highlights.isEmpty())
1791 return;
1792
1793 QQmlJS::SourceLocation cursor;
1794 cursor.offset = 0;
1795 cursor.length = 0;
1796 cursor.startLine = 1;
1797 cursor.startColumn = 1;
1798
1799 for (const Diff &diff : diffs) {
1800 switch (diff.command) {
1801 case Diff::Equal:
1802 // Just advance cursor
1803 updateCursorPositionByDiff(diff.text, cursor);
1804 break;
1805 case Diff::Insert: {
1806 updateHighlightsOnInsert(highlights, cursor, diff);
1807 break;
1808 }
1809 case Diff::Delete: {
1810 updateHighlightsOnDelete(highlights, cursor, diff);
1811 break;
1812 }
1813 }
1814 }
1815}
1816
1817} // namespace QmlHighlighting
1818
1819QT_END_NAMESPACE
HighlightingVisitor(const QQmlJS::Dom::DomItem &item, const std::optional< HighlightsRange > &range)
Combined button and popup list for selecting options.
void applyDiffs(HighlightsContainer &highlights, const QList< QQmlLSUtils::Diff > &diffs)
HighlightsContainer visitTokens(const QQmlJS::Dom::DomItem &item, const std::optional< HighlightsRange > &range)
void addHighlight(HighlightsContainer &out, const QQmlJS::SourceLocation &loc, QmlHighlightKind, QmlHighlightModifiers=QmlHighlightModifier::None)
void updateResultID(QByteArray &resultID)
QList< QQmlJS::SourceLocation > sourceLocationsFromMultiLineToken(QStringView code, const QQmlJS::SourceLocation &tokenLocation)
Returns multiple source locations for a given raw comment.
void addModifier(QLspSpecification::SemanticTokenModifiers modifier, int *baseModifier)
HighlightsContainer regexFallbackHighlights(QStringView code, const std::optional< HighlightsRange > &range)
QList< QLspSpecification::SemanticTokensEdit > computeDiff(const QList< unsigned > &, const QList< unsigned > &)
HighlightsContainer shiftHighlights(const HighlightsContainer &cachedHighlights, const QString &lastValidCode, const QString &currentCode)
bool rangeOverlapsWithSourceLocation(const QQmlJS::SourceLocation &loc, const HighlightsRange &r)
QList< unsigned > encodeSemanticTokens(const HighlightsContainer &highlights, HighlightingMode mode=HighlightingMode::Default)
static bool rightFragmentRemains(const QQmlJS::SourceLocation &t, quint32 delStart, quint32 delEnd)
static void shiftTokenAfterInsert(QQmlJS::SourceLocation &t, const QQmlJS::SourceLocation &cursor, int newlines, int lastLen, int diffLen)
static FieldFilter highlightingFilter()
static std::pair< quint32, quint32 > newlineCountAndLastLineLength(const QString &text)
static bool insertionTouchesTokenLeft(const QQmlJS::SourceLocation &token, const QQmlJS::SourceLocation &cursor)
static void applyDeletionOverlap(QQmlJS::SourceLocation &t, quint32 delStart, quint32 delEnd, int newlines, quint32 delStartLine, quint32 delStartColumn)
static void updateCursorPositionByDiff(const QString &text, QQmlJS::SourceLocation &cursor)
static void updateHighlightsOnInsert(HighlightsContainer &highlights, QQmlJS::SourceLocation &cursor, const QQmlLSUtils::Diff &diff)
static std::optional< QmlHighlightKind > resolveJsGlobalObjectKind(const DomItem &item, const QString &name)
Further resolves the type of a JavaScriptIdentifier A global object can be in the object form or in t...
static void expandTokenForLeftOverlap(QQmlJS::SourceLocation &t, const QQmlLSUtils::Diff &diff, const QQmlJS::SourceLocation &cursor, int newlines, int lastLen)
static bool tokenAfterOffset(const QQmlJS::SourceLocation &t, quint32 offset)
static bool insertionInsideToken(const QQmlJS::SourceLocation &token, const QQmlJS::SourceLocation &cursor)
static bool spansAcrossDeletion(const QQmlJS::SourceLocation &t, quint32 delStart, quint32 delEnd)
static std::vector< LineSegment > splitIntoLineSegments(QStringView text, qsizetype start, qsizetype end)
static void shiftTokenAfterDelete(QQmlJS::SourceLocation &t, int newlines, int lastLen, const QQmlJS::SourceLocation &cursor, int diffLen)
static int mapToProtocolForQtCreator(QmlHighlightKind highlightKind)
static void expandTokenForMiddleInsert(QQmlJS::SourceLocation &t, const QQmlLSUtils::Diff &diff, const QQmlJS::SourceLocation &cursor)
static int fromQmlModifierKindToLspTokenType(QmlHighlightModifiers highlightModifier)
static bool tokenBeforeOffset(const QQmlJS::SourceLocation &t, quint32 offset)
static bool leftFragmentRemains(const QQmlJS::SourceLocation &t, quint32 delStart, quint32 delEnd)
static void updateHighlightsOnDelete(HighlightsContainer &highlights, QQmlJS::SourceLocation &cursor, const QQmlLSUtils::Diff &diff)
static int mapToProtocolDefault(QmlHighlightKind highlightKind)
HighlightToken(const QQmlJS::SourceLocation &loc, QmlHighlightKind, QmlHighlightModifiers=QmlHighlightModifier::None)