Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qqmllscompletion.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
5
6using namespace QLspSpecification;
7using namespace QQmlJS::Dom;
8using namespace Qt::StringLiterals;
9
11
12Q_LOGGING_CATEGORY(QQmlLSCompletionLog, "qt.languageserver.completions")
13
14
42 QUtf8StringView insertText)
43{
45 if (!qualifier.isEmpty()) {
46 res.label = qualifier.data();
47 res.label += '.';
48 }
49 res.label += label.data();
50 res.insertTextFormat = InsertTextFormat::Snippet;
51 if (!qualifier.isEmpty()) {
52 res.insertText = qualifier.data();
53 *res.insertText += '.';
54 *res.insertText += insertText.data();
55 } else {
56 res.insertText = insertText.data();
57 }
58 res.kind = int(CompletionItemKind::Snippet);
59 res.insertTextMode = InsertTextMode::AdjustIndentation;
60 return res;
61}
62
67
88bool QQmlLSCompletion::betweenLocations(QQmlJS::SourceLocation left,
89 const QQmlLSCompletionPosition &positionInfo,
91{
92 if (!left.isValid())
93 return false;
94 // note: left.end() == ctx.offset() means that the cursor lies exactly after left
95 if (!(left.end() <= positionInfo.offset()))
96 return false;
97 if (!right.isValid())
98 return true;
99
100 // note: ctx.offset() == right.begin() means that the cursor lies exactly before right
101 return positionInfo.offset() <= right.begin();
102}
103
108bool QQmlLSCompletion::afterLocation(QQmlJS::SourceLocation left,
109 const QQmlLSCompletionPosition &positionInfo) const
110{
111 return betweenLocations(left, positionInfo, QQmlJS::SourceLocation{});
112}
113
118bool QQmlLSCompletion::beforeLocation(const QQmlLSCompletionPosition &ctx,
120{
121 if (!right.isValid())
122 return true;
123
124 // note: ctx.offset() == right.begin() means that the cursor lies exactly before right
125 if (ctx.offset() <= right.begin())
126 return true;
127
128 return false;
129}
130
131bool QQmlLSCompletion::ctxBeforeStatement(const QQmlLSCompletionPosition &positionInfo,
132 const DomItem &parentForContext,
133 FileLocationRegion firstRegion) const
134{
135 const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
136 const bool result = beforeLocation(positionInfo, regions[firstRegion]);
137 return result;
138}
139
140void
141QQmlLSCompletion::suggestBindingCompletion(const DomItem &itemAtPosition, BackInsertIterator it) const
142{
143 suggestReachableTypes(itemAtPosition, LocalSymbolsType::AttachedType, CompletionItemKind::Class,
144 it);
145
146 const QQmlJSScope::ConstPtr scope = [&]() {
147 if (!QQmlLSUtils::isFieldMemberAccess(itemAtPosition))
148 return itemAtPosition.qmlObject().semanticScope();
149
150 const DomItem owner = itemAtPosition.directParent().field(Fields::left);
151 auto expressionType = QQmlLSUtils::resolveExpressionType(
153 return expressionType ? expressionType->semanticScope : QQmlJSScope::ConstPtr{};
154 }();
155
156 if (!scope)
157 return;
158
159 propertyCompletion(scope, nullptr, it);
160 signalHandlerCompletion(scope, nullptr, it);
161}
162
163void QQmlLSCompletion::insideImportCompletionHelper(const DomItem &file,
164 const QQmlLSCompletionPosition &positionInfo,
165 BackInsertIterator it) const
166{
167 // returns completions for import statements, ctx is supposed to be in an import statement
168 const CompletionContextStrings &ctx = positionInfo.cursorPosition;
170 QRegularExpression spaceRe(uR"(\W+)"_s);
171 QList<QStringView> linePieces = ctx.preLine().split(spaceRe, Qt::SkipEmptyParts);
172 qsizetype effectiveLength = linePieces.size()
173 + ((!ctx.preLine().isEmpty() && ctx.preLine().last().isSpace()) ? 1 : 0);
174 if (effectiveLength < 2) {
175 CompletionItem comp;
176 comp.label = "import";
177 comp.kind = int(CompletionItemKind::Keyword);
178 it = comp;
179 }
180 if (linePieces.isEmpty() || linePieces.first() != u"import")
181 return;
182 if (effectiveLength == 2) {
183 // the cursor is after the import, possibly in a partial module name
184 importCompletionType = ImportCompletionType::Module;
185 } else if (effectiveLength == 3) {
186 if (linePieces.last() != u"as") {
187 // the cursor is after the module, possibly in a partial version token (or partial as)
188 CompletionItem comp;
189 comp.label = "as";
190 comp.kind = int(CompletionItemKind::Keyword);
191 it = comp;
192 importCompletionType = ImportCompletionType::Version;
193 }
194 }
195 DomItem env = file.environment();
196 if (std::shared_ptr<DomEnvironment> envPtr = env.ownerAs<DomEnvironment>()) {
197 switch (importCompletionType) {
199 break;
201 QDuplicateTracker<QString> modulesSeen;
202 for (const QString &uri : envPtr->moduleIndexUris(env)) {
203 QStringView base = ctx.base(); // if we allow spaces we should get rid of them
204 if (uri.startsWith(base)) {
205 QStringList rest = uri.mid(base.size()).split(u'.');
206 if (rest.isEmpty())
207 continue;
208
209 const QString label = rest.first();
210 if (!modulesSeen.hasSeen(label)) {
211 CompletionItem comp;
212 comp.label = label.toUtf8();
213 comp.kind = int(CompletionItemKind::Module);
214 it = comp;
215 }
216 }
217 }
218 break;
219 }
221 if (ctx.base().isEmpty()) {
222 for (int majorV :
223 envPtr->moduleIndexMajorVersions(env, linePieces.at(1).toString())) {
224 CompletionItem comp;
225 comp.label = QString::number(majorV).toUtf8();
226 comp.kind = int(CompletionItemKind::Constant);
227 it = comp;
228 }
229 } else {
230 bool hasMajorVersion = ctx.base().endsWith(u'.');
231 int majorV = -1;
232 if (hasMajorVersion)
233 majorV = ctx.base().mid(0, ctx.base().size() - 1).toInt(&hasMajorVersion);
234 if (!hasMajorVersion)
235 break;
236 if (std::shared_ptr<ModuleIndex> mIndex =
237 envPtr->moduleIndexWithUri(env, linePieces.at(1).toString(), majorV)) {
238 for (int minorV : mIndex->minorVersions()) {
239 CompletionItem comp;
240 comp.label = QString::number(minorV).toUtf8();
241 comp.kind = int(CompletionItemKind::Constant);
242 it = comp;
243 }
244 }
245 }
246 break;
247 }
248 }
249}
250
251void QQmlLSCompletion::idsCompletions(const DomItem &component, BackInsertIterator it) const
252{
253 qCDebug(QQmlLSCompletionLog) << "adding ids completions";
254 for (const QString &k : component.field(Fields::ids).keys()) {
255 CompletionItem comp;
256 comp.label = k.toUtf8();
257 comp.kind = int(CompletionItemKind::Value);
258 it = comp;
259 }
260}
261
262static bool testScopeSymbol(const QQmlJSScope::ConstPtr &scope, LocalSymbolsTypes options,
263 CompletionItemKind kind)
264{
265 const bool currentIsSingleton = scope->isSingleton();
266 const bool currentIsAttached = !scope->attachedType().isNull();
267 if ((options & LocalSymbolsType::Singleton) && currentIsSingleton) {
268 return true;
269 }
270 if ((options & LocalSymbolsType::AttachedType) && currentIsAttached) {
271 return true;
272 }
273 const bool isObjectType = scope->isReferenceType();
274 if (options & LocalSymbolsType::ObjectType && !currentIsSingleton && isObjectType) {
275 return kind != CompletionItemKind::Constructor || scope->isCreatable();
276 }
277 if (options & LocalSymbolsType::ValueType && !currentIsSingleton && !isObjectType) {
278 return true;
279 }
280 return false;
281}
282
287void QQmlLSCompletion::suggestReachableTypes(const DomItem &el, LocalSymbolsTypes options,
288 CompletionItemKind kind, BackInsertIterator it) const
289{
290 auto file = el.containingFile().as<QmlFile>();
291 if (!file)
292 return;
293 auto resolver = file->typeResolver();
294 if (!resolver)
295 return;
296
297 const QString requiredQualifiers = QQmlLSUtils::qualifiersFrom(el);
298 const auto keyValueRange = resolver->importedTypes().asKeyValueRange();
299 for (const auto &type : keyValueRange) {
300 // ignore special QQmlJSImporterMarkers
301 const bool isMarkerType = type.first.contains(u"$internal$.")
302 || type.first.contains(u"$anonymous$.") || type.first.contains(u"$module$.");
303 if (isMarkerType || !type.first.startsWith(requiredQualifiers))
304 continue;
305
306 auto &scope = type.second.scope;
307 if (!scope)
308 continue;
309
310 if (!testScopeSymbol(scope, options, kind))
311 continue;
312
313 CompletionItem completion;
314 completion.label = QStringView(type.first).sliced(requiredQualifiers.size()).toUtf8();
315 completion.kind = int(kind);
316 it = completion;
317 }
318}
319
320void QQmlLSCompletion::jsIdentifierCompletion(const QQmlJSScope::ConstPtr &scope,
321 QDuplicateTracker<QString> *usedNames,
322 BackInsertIterator it) const
323{
324 for (const auto &[name, jsIdentifier] : scope->ownJSIdentifiers().asKeyValueRange()) {
325 CompletionItem completion;
326 if (usedNames && usedNames->hasSeen(name)) {
327 continue;
328 }
329 completion.label = name.toUtf8();
330 completion.kind = int(CompletionItemKind::Variable);
331 QString detail = u"has type "_s;
332 if (jsIdentifier.typeName) {
333 if (jsIdentifier.isConst) {
334 detail.append(u"const ");
335 }
336 detail.append(*jsIdentifier.typeName);
337 } else {
338 detail.append(jsIdentifier.isConst ? u"const"_s : u"var"_s);
339 }
340 completion.detail = detail.toUtf8();
341 it = completion;
342 }
343}
344
345void QQmlLSCompletion::methodCompletion(const QQmlJSScope::ConstPtr &scope,
346 QDuplicateTracker<QString> *usedNames,
347 BackInsertIterator it) const
348{
349 // JS functions in current and base scopes
350 for (const auto &[name, method] : scope->methods().asKeyValueRange()) {
351 if (method.access() != QQmlJSMetaMethod::Public)
352 continue;
353 if (usedNames && usedNames->hasSeen(name)) {
354 continue;
355 }
356 CompletionItem completion;
357 completion.label = name.toUtf8();
358 completion.kind = int(CompletionItemKind::Method);
359 it = completion;
360 // TODO: QQmlLSUtils::reachableSymbols seems to be able to do documentation and detail
361 // and co, it should also be done here if possible.
362 }
363}
364
365void QQmlLSCompletion::propertyCompletion(const QQmlJSScope::ConstPtr &scope,
366 QDuplicateTracker<QString> *usedNames,
367 BackInsertIterator it) const
368{
369 for (const auto &[name, property] : scope->properties().asKeyValueRange()) {
370 if (usedNames && usedNames->hasSeen(name)) {
371 continue;
372 }
373 CompletionItem completion;
374 completion.label = name.toUtf8();
375 completion.kind = int(CompletionItemKind::Property);
376 QString detail{ u"has type "_s };
377 if (!property.isWritable())
378 detail.append(u"readonly "_s);
379 detail.append(property.typeName().isEmpty() ? u"var"_s : property.typeName());
380 completion.detail = detail.toUtf8();
381 it = completion;
382 }
383}
384
385void QQmlLSCompletion::enumerationCompletion(const QQmlJSScope::ConstPtr &scope,
386 QDuplicateTracker<QString> *usedNames,
387 BackInsertIterator it) const
388{
389 for (const QQmlJSMetaEnum &enumerator : scope->enumerations()) {
390 if (usedNames && usedNames->hasSeen(enumerator.name())) {
391 continue;
392 }
393 CompletionItem completion;
394 completion.label = enumerator.name().toUtf8();
395 completion.kind = static_cast<int>(CompletionItemKind::Enum);
396 it = completion;
397 }
398}
399
400void QQmlLSCompletion::enumerationValueCompletionHelper(const QStringList &enumeratorKeys,
401 BackInsertIterator it) const
402{
403 for (const QString &enumeratorKey : enumeratorKeys) {
404 CompletionItem completion;
405 completion.label = enumeratorKey.toUtf8();
406 completion.kind = static_cast<int>(CompletionItemKind::EnumMember);
407 it = completion;
408 }
409}
410
431void QQmlLSCompletion::enumerationValueCompletion(const QQmlJSScope::ConstPtr &scope,
432 const QString &enumeratorName,
433 BackInsertIterator result) const
434{
435 auto enumerator = scope->enumeration(enumeratorName);
436 if (enumerator.isValid()) {
437 enumerationValueCompletionHelper(enumerator.keys(), result);
438 return;
439 }
440
441 for (const QQmlJSMetaEnum &enumerator : scope->enumerations()) {
442 enumerationValueCompletionHelper(enumerator.keys(), result);
443 }
444}
445
459template<typename F>
461{
462 for (QQmlJSScope::ConstPtr current = scope; current; current = current->parentScope()) {
463 f(current);
464 if (current->scopeType() == QQmlSA::ScopeType::QMLScope)
465 return;
466 }
467}
468
478void QQmlLSCompletion::suggestJSExpressionCompletion(const DomItem &scriptIdentifier,
479 BackInsertIterator result) const
480{
481 QDuplicateTracker<QString> usedNames;
482 QQmlJSScope::ConstPtr nearestScope;
483
484 // note: there is an edge case, where the user asks for completion right after the dot
485 // of some qualified expression like `root.hello`. In this case, scriptIdentifier is actually
486 // the BinaryExpression instead of the left-hand-side that has not be written down yet.
487 const bool askForCompletionOnDot = QQmlLSUtils::isFieldMemberExpression(scriptIdentifier);
488 const bool hasQualifier =
489 QQmlLSUtils::isFieldMemberAccess(scriptIdentifier) || askForCompletionOnDot;
490
491 if (!hasQualifier) {
492 for (QUtf8StringView view : std::array<QUtf8StringView, 3>{ "null", "false", "true" }) {
493 CompletionItem completion;
494 completion.label = view.data();
495 completion.kind = int(CompletionItemKind::Value);
496 result = completion;
497 }
498 idsCompletions(scriptIdentifier.component(), result);
499 suggestReachableTypes(scriptIdentifier,
500 LocalSymbolsType::Singleton | LocalSymbolsType::AttachedType,
501 CompletionItemKind::Class, result);
502
503 auto scope = scriptIdentifier.nearestSemanticScope();
504 if (!scope)
505 return;
506 nearestScope = scope;
507
508 enumerationCompletion(nearestScope, &usedNames, result);
509 } else {
510 const DomItem owner =
511 (askForCompletionOnDot ? scriptIdentifier : scriptIdentifier.directParent())
512 .field(Fields::left);
513 auto expressionType = QQmlLSUtils::resolveExpressionType(
515 if (!expressionType || !expressionType->semanticScope)
516 return;
517 nearestScope = expressionType->semanticScope;
518 // Use root element scope to use find the enumerations
519 // This should be changed when we support usages in external files
520 if (expressionType->type == QmlComponentIdentifier)
521 nearestScope = owner.rootQmlObject(GoTo::MostLikely).semanticScope();
522 if (expressionType->name) {
523 // note: you only get enumeration values in qualified expressions, never alone
524 enumerationValueCompletion(nearestScope, *expressionType->name, result);
525
526 // skip enumeration types if already inside an enumeration type
527 if (auto enumerator = nearestScope->enumeration(*expressionType->name);
528 !enumerator.isValid()) {
529 enumerationCompletion(nearestScope, &usedNames, result);
530 }
531
532 if (expressionType->type == EnumeratorIdentifier)
533 return;
534 }
535 }
536
537 Q_ASSERT(nearestScope);
538
539 methodCompletion(nearestScope, &usedNames, result);
540 propertyCompletion(nearestScope, &usedNames, result);
541
542 if (!hasQualifier) {
543 // collect all of the stuff from parents
545 [this, &usedNames, result](const QQmlJSScope::ConstPtr &scope) {
546 jsIdentifierCompletion(scope, &usedNames, result);
547 },
548 nearestScope);
550 [this, &usedNames, result](const QQmlJSScope::ConstPtr &scope) {
551 methodCompletion(scope, &usedNames, result);
552 },
553 nearestScope);
555 [this, &usedNames, result](const QQmlJSScope::ConstPtr &scope) {
556 propertyCompletion(scope, &usedNames, result);
557 },
558 nearestScope);
559
560 auto file = scriptIdentifier.containingFile().as<QmlFile>();
561 if (!file)
562 return;
563 auto resolver = file->typeResolver();
564 if (!resolver)
565 return;
566
567 const auto globals = resolver->jsGlobalObject();
568 methodCompletion(globals, &usedNames, result);
569 propertyCompletion(globals, &usedNames, result);
570 }
571}
572
573static const QQmlJSScope *resolve(const QQmlJSScope *current, const QStringList &names)
574{
575 for (const QString &name : names) {
576 if (auto property = current->property(name); property.isValid()) {
577 if (auto propertyType = property.type().get()) {
578 current = propertyType;
579 continue;
580 }
581 }
582 return {};
583 }
584 return current;
585}
586
587bool QQmlLSCompletion::cursorInFrontOfItem(const DomItem &parentForContext,
588 const QQmlLSCompletionPosition &positionInfo)
589{
590 auto fileLocations = FileLocations::treeOf(parentForContext)->info().fullRegion;
591 return positionInfo.offset() <= fileLocations.offset;
592}
593
594bool QQmlLSCompletion::cursorAfterColon(const DomItem &currentItem,
595 const QQmlLSCompletionPosition &positionInfo)
596{
597 auto location = FileLocations::treeOf(currentItem)->info();
598 auto region = location.regions.constFind(ColonTokenRegion);
599
600 if (region == location.regions.constEnd())
601 return false;
602
603 if (region.value().isValid() && region.value().offset < positionInfo.offset()) {
604 return true;
605 }
606 return false;
607}
608
620static const QMap<QString, QList<QString>> valuesForPragmas{
621 { u"ComponentBehavior"_s, { u"Unbound"_s, u"Bound"_s } },
622 { u"NativeMethodBehavior"_s, { u"AcceptThisObject"_s, u"RejectThisObject"_s } },
623 { u"ListPropertyAssignBehavior"_s, { u"Append"_s, u"Replace"_s, u"ReplaceIfNotDefault"_s } },
624 { u"Singleton"_s, {} },
625 { u"ValueTypeBehavior"_s, { u"Addressable"_s, u"Inaddressable"_s } },
626};
627
628void QQmlLSCompletion::insidePragmaCompletion(QQmlJS::Dom::DomItem currentItem,
629 const QQmlLSCompletionPosition &positionInfo,
630 BackInsertIterator result) const
631{
632 if (cursorAfterColon(currentItem, positionInfo)) {
633 const QString name = currentItem.field(Fields::name).value().toString();
634 auto values = valuesForPragmas.constFind(name);
635 if (values == valuesForPragmas.constEnd())
636 return;
637
638 for (const auto &value : *values) {
639 CompletionItem comp;
640 comp.label = value.toUtf8();
641 comp.kind = static_cast<int>(CompletionItemKind::Value);
642 result = comp;
643 }
644 return;
645 }
646
647 for (const auto &pragma : valuesForPragmas.asKeyValueRange()) {
648 CompletionItem comp;
649 comp.label = pragma.first.toUtf8();
650 if (!pragma.second.isEmpty()) {
651 comp.insertText = QString(pragma.first).append(u": ").toUtf8();
652 }
653 comp.kind = static_cast<int>(CompletionItemKind::Value);
654 result = comp;
655 }
656}
657
658void QQmlLSCompletion::insideQmlObjectCompletion(const DomItem &parentForContext,
659 const QQmlLSCompletionPosition &positionInfo,
660 BackInsertIterator result) const
661{
662
663 const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
664
665 const QQmlJS::SourceLocation leftBrace = regions[LeftBraceRegion];
666 const QQmlJS::SourceLocation rightBrace = regions[RightBraceRegion];
667
668 if (beforeLocation(positionInfo, leftBrace)) {
669 LocalSymbolsTypes options;
670 options.setFlag(LocalSymbolsType::ObjectType);
671 suggestReachableTypes(positionInfo.itemAtPosition, options, CompletionItemKind::Constructor,
672 result);
673 suggestSnippetsForLeftHandSideOfBinding(positionInfo.itemAtPosition, result);
674
675 if (QQmlLSUtils::isFieldMemberExpression(positionInfo.itemAtPosition)) {
697 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
698 }
699 return;
700 }
701
702 if (betweenLocations(leftBrace, positionInfo, rightBrace)) {
703 // default/required property completion
704 for (QUtf8StringView view :
705 std::array<QUtf8StringView, 6>{ "", "readonly ", "default ", "default required ",
706 "required default ", "required " }) {
707 // readonly properties require an initializer
708 if (view != QUtf8StringView("readonly ")) {
710 QByteArray(view.data()).append("property type name;"),
711 QByteArray(view.data()).append("property ${1:type} ${0:name};"));
712 }
713
715 QByteArray(view.data()).append("property type name: value;"),
716 QByteArray(view.data()).append("property ${1:type} ${2:name}: ${0:value};"));
717 }
718
719 // signal
720 result = makeSnippet("signal name(arg1:type1, ...)", "signal ${1:name}($0)");
721
722 // signal without parameters
723 result = makeSnippet("signal name;", "signal ${0:name};");
724
725 // make already existing property required
726 result = makeSnippet("required name;", "required ${0:name};");
727
728 // function
729 result = makeSnippet("function name(args...): returnType { statements...}",
730 "function ${1:name}($2): ${3:returnType} {\n\t$0\n}");
731
732 // enum
733 result = makeSnippet("enum name { Values...}", "enum ${1:name} {\n\t${0:values}\n}");
734
735 // inline component
736 result = makeSnippet("component Name: BaseType { ... }",
737 "component ${1:name}: ${2:baseType} {\n\t$0\n}");
738
739 // add bindings
740 const DomItem containingObject = parentForContext.qmlObject();
741 suggestBindingCompletion(containingObject, result);
742
743 // add Qml Types for default binding
744 const DomItem containingFile = parentForContext.containingFile();
745 suggestReachableTypes(containingFile, LocalSymbolsType::ObjectType,
746 CompletionItemKind::Constructor, result);
747 suggestSnippetsForLeftHandSideOfBinding(positionInfo.itemAtPosition, result);
748 return;
749 }
750}
751
752void QQmlLSCompletion::insidePropertyDefinitionCompletion(
753 const DomItem &currentItem, const QQmlLSCompletionPosition &positionInfo,
754 BackInsertIterator result) const
755{
756 auto info = FileLocations::treeOf(currentItem)->info();
757 const QQmlJS::SourceLocation propertyKeyword = info.regions[PropertyKeywordRegion];
758
759 // do completions for the keywords
760 if (positionInfo.offset() < propertyKeyword.offset + propertyKeyword.length) {
761 const QQmlJS::SourceLocation readonlyKeyword = info.regions[ReadonlyKeywordRegion];
762 const QQmlJS::SourceLocation defaultKeyword = info.regions[DefaultKeywordRegion];
763 const QQmlJS::SourceLocation requiredKeyword = info.regions[RequiredKeywordRegion];
764
765 bool completeReadonly = true;
766 bool completeRequired = true;
767 bool completeDefault = true;
768
769 // if there is already a readonly keyword before the cursor: do not auto complete it again
770 if (readonlyKeyword.isValid() && readonlyKeyword.offset < positionInfo.offset()) {
771 completeReadonly = false;
772 // also, required keywords do not like readonly keywords
773 completeRequired = false;
774 }
775
776 // same for required
777 if (requiredKeyword.isValid() && requiredKeyword.offset < positionInfo.offset()) {
778 completeRequired = false;
779 // also, required keywords do not like readonly keywords
780 completeReadonly = false;
781 }
782
783 // same for default
784 if (defaultKeyword.isValid() && defaultKeyword.offset < positionInfo.offset()) {
785 completeDefault = false;
786 }
787 auto addCompletionKeyword = [&result](QUtf8StringView view, bool complete) {
788 if (!complete)
789 return;
791 item.label = view.data();
792 item.kind = int(CompletionItemKind::Keyword);
793 result = item;
794 };
795 addCompletionKeyword(u8"readonly", completeReadonly);
796 addCompletionKeyword(u8"required", completeRequired);
797 addCompletionKeyword(u8"default", completeDefault);
798 addCompletionKeyword(u8"property", true);
799
800 return;
801 }
802
803 const QQmlJS::SourceLocation propertyIdentifier = info.regions[IdentifierRegion];
804 if (propertyKeyword.end() <= positionInfo.offset()
805 && positionInfo.offset() < propertyIdentifier.offset) {
806 suggestReachableTypes(currentItem,
807 LocalSymbolsType::ObjectType | LocalSymbolsType::ValueType,
808 CompletionItemKind::Class, result);
809 }
810 // do not autocomplete the rest
811 return;
812}
813
814void QQmlLSCompletion::insideBindingCompletion(const DomItem &currentItem,
815 const QQmlLSCompletionPosition &positionInfo,
816 BackInsertIterator result) const
817{
818 const DomItem containingBinding = currentItem.filterUp(
819 [](DomType type, const QQmlJS::Dom::DomItem &) { return type == DomType::Binding; },
820 FilterUpOptions::ReturnOuter);
821
822 // do scriptidentifiercompletion after the ':' of a binding
823 if (cursorAfterColon(containingBinding, positionInfo)) {
824 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
825
827 const QStringList names = currentItem.field(Fields::name).toString().split(u'.');
828 const QQmlJSScope *current = resolve(type->semanticScope.get(), names);
829 // add type names when binding to an object type or a property with var type
830 if (!current || current->accessSemantics() == QQmlSA::AccessSemantics::Reference) {
831 LocalSymbolsTypes options;
832 options.setFlag(LocalSymbolsType::ObjectType);
833 suggestReachableTypes(positionInfo.itemAtPosition, options,
834 CompletionItemKind::Constructor, result);
835 suggestSnippetsForRightHandSideOfBinding(positionInfo.itemAtPosition, result);
836 }
837 }
838 return;
839 }
840
841 // ignore the binding if asking for completion in front of the binding
842 if (cursorInFrontOfItem(containingBinding, positionInfo)) {
843 insideQmlObjectCompletion(currentItem.containingObject(), positionInfo, result);
844 return;
845 }
846
847 const DomItem containingObject = currentItem.qmlObject();
848
849 suggestBindingCompletion(positionInfo.itemAtPosition, result);
850
851 // add Qml Types for default binding
852 suggestReachableTypes(positionInfo.itemAtPosition, LocalSymbolsType::ObjectType,
853 CompletionItemKind::Constructor, result);
854 suggestSnippetsForLeftHandSideOfBinding(positionInfo.itemAtPosition, result);
855}
856
857void QQmlLSCompletion::insideImportCompletion(const DomItem &currentItem,
858 const QQmlLSCompletionPosition &positionInfo,
859 BackInsertIterator result) const
860{
861 const DomItem containingFile = currentItem.containingFile();
862 insideImportCompletionHelper(containingFile, positionInfo, result);
863
864 // when in front of the import statement: propose types for root Qml Object completion
865 if (cursorInFrontOfItem(currentItem, positionInfo)) {
866 suggestReachableTypes(containingFile, LocalSymbolsType::ObjectType,
867 CompletionItemKind::Constructor, result);
868 }
869}
870
871void QQmlLSCompletion::insideQmlFileCompletion(const DomItem &currentItem,
872 const QQmlLSCompletionPosition &positionInfo,
873 BackInsertIterator result) const
874{
875 const DomItem containingFile = currentItem.containingFile();
876 // completions for code outside the root Qml Object
877 // global completions
878 if (positionInfo.cursorPosition.atLineStart()) {
879 if (positionInfo.cursorPosition.base().isEmpty()) {
880 for (const QStringView &s : std::array<QStringView, 2>({ u"pragma", u"import" })) {
881 CompletionItem comp;
882 comp.label = s.toUtf8();
883 comp.kind = int(CompletionItemKind::Keyword);
884 result = comp;
885 }
886 }
887 }
888 // Types for root Qml Object completion
889 suggestReachableTypes(containingFile, LocalSymbolsType::ObjectType,
890 CompletionItemKind::Constructor, result);
891}
892
897void QQmlLSCompletion::suggestVariableDeclarationStatementCompletion(
898 BackInsertIterator result, QQmlLSUtilsAppendOption option) const
899{
900 // let/var/const statement
901 for (auto view : std::array<QUtf8StringView, 3>{ "let", "var", "const" }) {
902 auto snippet = makeSnippet(QByteArray(view.data()).append(" variable = value"),
903 QByteArray(view.data()).append(" ${1:variable} = $0"));
904 if (option == AppendSemicolon) {
905 snippet.insertText->append(";");
906 snippet.label.append(";");
907 }
908 result = snippet;
909 }
910}
911
916void QQmlLSCompletion::suggestCaseAndDefaultStatementCompletion(BackInsertIterator result) const
917{
918 // case snippet
919 result = makeSnippet("case value: statements...", "case ${1:value}:\n\t$0");
920 // case + brackets snippet
921 result = makeSnippet("case value: { statements... }", "case ${1:value}: {\n\t$0\n}");
922
923 // default snippet
924 result = makeSnippet("default: statements...", "default:\n\t$0");
925 // default + brackets snippet
926 result = makeSnippet("default: { statements... }", "default: {\n\t$0\n}");
927}
928
938void QQmlLSCompletion::suggestContinueAndBreakStatementIfNeeded(const DomItem &itemAtPosition,
939 BackInsertIterator result) const
940{
941 bool alreadyInLabel = false;
942 bool alreadyInSwitch = false;
943 for (DomItem current = itemAtPosition; current; current = current.directParent()) {
944 switch (current.internalKind()) {
945 case DomType::ScriptExpression:
946 // reached end of script expression
947 return;
948
949 case DomType::ScriptForStatement:
950 case DomType::ScriptForEachStatement:
951 case DomType::ScriptWhileStatement:
952 case DomType::ScriptDoWhileStatement: {
953 CompletionItem continueKeyword;
954 continueKeyword.label = "continue";
955 continueKeyword.kind = int(CompletionItemKind::Keyword);
956 result = continueKeyword;
957
958 // do not add break twice
959 if (!alreadyInSwitch && !alreadyInLabel) {
960 CompletionItem breakKeyword;
961 breakKeyword.label = "break";
962 breakKeyword.kind = int(CompletionItemKind::Keyword);
963 result = breakKeyword;
964 }
965 // early exit: cannot suggest more completions
966 return;
967 }
968 case DomType::ScriptSwitchStatement: {
969 // check if break was already inserted
970 if (alreadyInSwitch || alreadyInLabel)
971 break;
972 alreadyInSwitch = true;
973
974 CompletionItem breakKeyword;
975 breakKeyword.label = "break";
976 breakKeyword.kind = int(CompletionItemKind::Keyword);
977 result = breakKeyword;
978 break;
979 }
980 case DomType::ScriptLabelledStatement: {
981 // check if break was already inserted because of switch or loop
982 if (alreadyInSwitch || alreadyInLabel)
983 break;
984 alreadyInLabel = true;
985
986 CompletionItem breakKeyword;
987 breakKeyword.label = "break";
988 breakKeyword.kind = int(CompletionItemKind::Keyword);
989 result = breakKeyword;
990 break;
991 }
992 default:
993 break;
994 }
995 }
996}
997
1014void QQmlLSCompletion::suggestJSStatementCompletion(const DomItem &itemAtPosition,
1015 BackInsertIterator result) const
1016{
1017 suggestJSExpressionCompletion(itemAtPosition, result);
1018
1019 if (QQmlLSUtils::isFieldMemberAccess(itemAtPosition))
1020 return;
1021
1022 // expression statements
1023 suggestVariableDeclarationStatementCompletion(result);
1024 // block statement
1025 result = makeSnippet("{ statements... }", "{\n\t$0\n}");
1026
1027 // if + brackets statement
1028 result = makeSnippet("if (condition) { statements }", "if ($1) {\n\t$0\n}");
1029
1030 // do statement
1031 result = makeSnippet("do { statements } while (condition);", "do {\n\t$1\n} while ($0);");
1032
1033 // while + brackets statement
1034 result = makeSnippet("while (condition) { statements...}", "while ($1) {\n\t$0\n}");
1035
1036 // for + brackets loop statement
1037 result = makeSnippet("for (initializer; condition; increment) { statements... }",
1038 "for ($1;$2;$3) {\n\t$0\n}");
1039
1040 // for ... in + brackets loop statement
1041 result = makeSnippet("for (property in object) { statements... }", "for ($1 in $2) {\n\t$0\n}");
1042
1043 // for ... of + brackets loop statement
1044 result = makeSnippet("for (element of array) { statements... }", "for ($1 of $2) {\n\t$0\n}");
1045
1046 // try + catch statement
1047 result = makeSnippet("try { statements... } catch(error) { statements... }",
1048 "try {\n\t$1\n} catch($2) {\n\t$0\n}");
1049
1050 // try + finally statement
1051 result = makeSnippet("try { statements... } finally { statements... }",
1052 "try {\n\t$1\n} finally {\n\t$0\n}");
1053
1054 // try + catch + finally statement
1056 "try { statements... } catch(error) { statements... } finally { statements... }",
1057 "try {\n\t$1\n} catch($2) {\n\t$3\n} finally {\n\t$0\n}");
1058
1059 // one can always assume that JS code in QML is inside a function, so always propose `return`
1060 for (auto &&view : { "return"_ba, "throw"_ba }) {
1062 item.label = std::move(view);
1063 item.kind = int(CompletionItemKind::Keyword);
1064 result = item;
1065 }
1066
1067 // rules for case+default statements:
1068 // 1) when inside a CaseBlock, or
1069 // 2) inside a CaseClause, as an (non-nested) element of the CaseClause statementlist.
1070 // 3) inside a DefaultClause, as an (non-nested) element of the DefaultClause statementlist,
1071 //
1072 // switch (x) {
1073 // // (1)
1074 // case 1:
1075 // myProperty = 5;
1076 // // (2) -> could be another statement of current case, but also a new case or default!
1077 // default:
1078 // myProperty = 5;
1079 // // (3) -> could be another statement of current default, but also a new case or default!
1080 // }
1081 const DomType currentKind = itemAtPosition.internalKind();
1082 const DomType parentKind = itemAtPosition.directParent().internalKind();
1083 if (currentKind == DomType::ScriptCaseBlock || currentKind == DomType::ScriptCaseClause
1084 || currentKind == DomType::ScriptDefaultClause
1085 || (currentKind == DomType::List
1086 && (parentKind == DomType::ScriptCaseClause
1087 || parentKind == DomType::ScriptDefaultClause))) {
1088 suggestCaseAndDefaultStatementCompletion(result);
1089 }
1090 suggestContinueAndBreakStatementIfNeeded(itemAtPosition, result);
1091}
1092
1093void QQmlLSCompletion::insideForStatementCompletion(const DomItem &parentForContext,
1094 const QQmlLSCompletionPosition &positionInfo,
1095 BackInsertIterator result) const
1096{
1097 const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
1098
1099 const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion];
1100 const QQmlJS::SourceLocation firstSemicolon = regions[FirstSemicolonTokenRegion];
1101 const QQmlJS::SourceLocation secondSemicolon = regions[SecondSemicolonRegion];
1102 const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion];
1103
1104 if (betweenLocations(leftParenthesis, positionInfo, firstSemicolon)) {
1105 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1106 suggestVariableDeclarationStatementCompletion(result,
1108 return;
1109 }
1110 if (betweenLocations(firstSemicolon, positionInfo, secondSemicolon)
1111 || betweenLocations(secondSemicolon, positionInfo, rightParenthesis)) {
1112 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1113 return;
1114 }
1115
1116 if (afterLocation(rightParenthesis, positionInfo)) {
1117 suggestJSStatementCompletion(positionInfo.itemAtPosition, result);
1118 return;
1119 }
1120}
1121
1122void QQmlLSCompletion::insideScriptLiteralCompletion(const DomItem &currentItem,
1123 const QQmlLSCompletionPosition &positionInfo,
1124 BackInsertIterator result) const
1125{
1126 Q_UNUSED(currentItem);
1127 if (positionInfo.cursorPosition.base().isEmpty()) {
1128 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1129 return;
1130 }
1131}
1132
1133void QQmlLSCompletion::insideCallExpression(const DomItem &currentItem,
1134 const QQmlLSCompletionPosition &positionInfo,
1135 BackInsertIterator result) const
1136{
1137 const auto regions = FileLocations::treeOf(currentItem)->info().regions;
1138 const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion];
1139 const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion];
1140 if (beforeLocation(positionInfo, leftParenthesis)) {
1141 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1142 return;
1143 }
1144 if (betweenLocations(leftParenthesis, positionInfo, rightParenthesis)) {
1145 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1146 return;
1147 }
1148}
1149
1150void QQmlLSCompletion::insideIfStatement(const DomItem &currentItem,
1151 const QQmlLSCompletionPosition &positionInfo,
1152 BackInsertIterator result) const
1153{
1154 const auto regions = FileLocations::treeOf(currentItem)->info().regions;
1155 const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion];
1156 const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion];
1157 const QQmlJS::SourceLocation elseKeyword = regions[ElseKeywordRegion];
1158
1159 if (betweenLocations(leftParenthesis, positionInfo, rightParenthesis)) {
1160 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1161 return;
1162 }
1163 if (betweenLocations(rightParenthesis, positionInfo, elseKeyword)) {
1164 suggestJSStatementCompletion(positionInfo.itemAtPosition, result);
1165 return;
1166 }
1167 if (afterLocation(elseKeyword, positionInfo)) {
1168 suggestJSStatementCompletion(positionInfo.itemAtPosition, result);
1169 return;
1170 }
1171}
1172
1173void QQmlLSCompletion::insideReturnStatement(const DomItem &currentItem,
1174 const QQmlLSCompletionPosition &positionInfo,
1175 BackInsertIterator result) const
1176{
1177 const auto regions = FileLocations::treeOf(currentItem)->info().regions;
1178 const QQmlJS::SourceLocation returnKeyword = regions[ReturnKeywordRegion];
1179
1180 if (afterLocation(returnKeyword, positionInfo)) {
1181 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1182 return;
1183 }
1184}
1185
1186void QQmlLSCompletion::insideWhileStatement(const DomItem &currentItem,
1187 const QQmlLSCompletionPosition &positionInfo,
1188 BackInsertIterator result) const
1189{
1190 const auto regions = FileLocations::treeOf(currentItem)->info().regions;
1191 const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion];
1192 const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion];
1193
1194 if (betweenLocations(leftParenthesis, positionInfo, rightParenthesis)) {
1195 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1196 return;
1197 }
1198 if (afterLocation(rightParenthesis, positionInfo)) {
1199 suggestJSStatementCompletion(positionInfo.itemAtPosition, result);
1200 return;
1201 }
1202}
1203
1204void QQmlLSCompletion::insideDoWhileStatement(const DomItem &parentForContext,
1205 const QQmlLSCompletionPosition &positionInfo,
1206 BackInsertIterator result) const
1207{
1208 const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
1209 const QQmlJS::SourceLocation doKeyword = regions[DoKeywordRegion];
1210 const QQmlJS::SourceLocation whileKeyword = regions[WhileKeywordRegion];
1211 const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion];
1212 const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion];
1213
1214 if (betweenLocations(doKeyword, positionInfo, whileKeyword)) {
1215 suggestJSStatementCompletion(positionInfo.itemAtPosition, result);
1216 return;
1217 }
1218 if (betweenLocations(leftParenthesis, positionInfo, rightParenthesis)) {
1219 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1220 return;
1221 }
1222}
1223
1224void QQmlLSCompletion::insideForEachStatement(const DomItem &parentForContext,
1225 const QQmlLSCompletionPosition &positionInfo,
1226 BackInsertIterator result) const
1227{
1228 const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
1229
1230 const QQmlJS::SourceLocation inOf = regions[InOfTokenRegion];
1231 const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion];
1232 const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion];
1233
1234 if (betweenLocations(leftParenthesis, positionInfo, inOf)) {
1235 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1236 suggestVariableDeclarationStatementCompletion(result);
1237 return;
1238 }
1239 if (betweenLocations(inOf, positionInfo, rightParenthesis)) {
1240 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1241 return;
1242 }
1243
1244 if (afterLocation(rightParenthesis, positionInfo)) {
1245 suggestJSStatementCompletion(positionInfo.itemAtPosition, result);
1246 return;
1247 }
1248}
1249
1250void QQmlLSCompletion::insideSwitchStatement(const DomItem &parentForContext,
1251 const QQmlLSCompletionPosition positionInfo,
1252 BackInsertIterator result) const
1253{
1254 const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
1255
1256 const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion];
1257 const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion];
1258
1259 if (betweenLocations(leftParenthesis, positionInfo, rightParenthesis)) {
1260 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1261 return;
1262 }
1263}
1264
1265void QQmlLSCompletion::insideCaseClause(const DomItem &parentForContext,
1266 const QQmlLSCompletionPosition &positionInfo,
1267 BackInsertIterator result) const
1268{
1269 const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
1270
1271 const QQmlJS::SourceLocation caseKeyword = regions[CaseKeywordRegion];
1272 const QQmlJS::SourceLocation colonToken = regions[ColonTokenRegion];
1273
1274 if (betweenLocations(caseKeyword, positionInfo, colonToken)) {
1275 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1276 return;
1277 }
1278 if (afterLocation(colonToken, positionInfo)) {
1279 suggestJSStatementCompletion(positionInfo.itemAtPosition, result);
1280 return;
1281 }
1282
1283}
1284
1289bool QQmlLSCompletion::isCaseOrDefaultBeforeCtx(const DomItem &currentClause,
1290 const QQmlLSCompletionPosition &positionInfo,
1291 FileLocationRegion keywordRegion) const
1292{
1294 || keywordRegion == QQmlJS::Dom::DefaultKeywordRegion);
1295
1296 if (!currentClause)
1297 return false;
1298
1299 const auto token = FileLocations::treeOf(currentClause)->info().regions[keywordRegion];
1300 if (afterLocation(token, positionInfo))
1301 return true;
1302
1303 return false;
1304}
1305
1314DomItem
1315QQmlLSCompletion::previousCaseOfCaseBlock(const DomItem &parentForContext,
1316 const QQmlLSCompletionPosition &positionInfo) const
1317{
1318 const DomItem caseClauses = parentForContext.field(Fields::caseClauses);
1319 for (int i = 0; i < caseClauses.indexes(); ++i) {
1320 const DomItem currentClause = caseClauses.index(i);
1321 if (isCaseOrDefaultBeforeCtx(currentClause, positionInfo, QQmlJS::Dom::CaseKeywordRegion)) {
1322 return currentClause;
1323 }
1324 }
1325
1326 const DomItem defaultClause = parentForContext.field(Fields::defaultClause);
1327 if (isCaseOrDefaultBeforeCtx(defaultClause, positionInfo, QQmlJS::Dom::DefaultKeywordRegion))
1328 return parentForContext.field(Fields::defaultClause);
1329
1330 const DomItem moreCaseClauses = parentForContext.field(Fields::moreCaseClauses);
1331 for (int i = 0; i < moreCaseClauses.indexes(); ++i) {
1332 const DomItem currentClause = moreCaseClauses.index(i);
1333 if (isCaseOrDefaultBeforeCtx(currentClause, positionInfo, QQmlJS::Dom::CaseKeywordRegion)) {
1334 return currentClause;
1335 }
1336 }
1337
1338 return {};
1339}
1340
1341void QQmlLSCompletion::insideCaseBlock(const DomItem &parentForContext,
1342 const QQmlLSCompletionPosition &positionInfo,
1343 BackInsertIterator result) const
1344{
1345 const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
1346
1347 const QQmlJS::SourceLocation leftBrace = regions[LeftBraceRegion];
1348 const QQmlJS::SourceLocation rightBrace = regions[RightBraceRegion];
1349
1350 if (!betweenLocations(leftBrace, positionInfo, rightBrace))
1351 return;
1352
1353 // TODO: looks fishy
1354 // if there is a previous case or default clause, you can still add statements to it
1355 if (const auto previousCase = previousCaseOfCaseBlock(parentForContext, positionInfo)) {
1356 suggestJSStatementCompletion(previousCase, result);
1357 return;
1358 }
1359
1360 // otherwise, only complete case and default
1361 suggestCaseAndDefaultStatementCompletion(result);
1362}
1363
1364void QQmlLSCompletion::insideDefaultClause(const DomItem &parentForContext,
1365 const QQmlLSCompletionPosition &positionInfo,
1366 BackInsertIterator result) const
1367{
1368 const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
1369
1370 const QQmlJS::SourceLocation colonToken = regions[ColonTokenRegion];
1371
1372 if (afterLocation(colonToken, positionInfo)) {
1373 suggestJSStatementCompletion(positionInfo.itemAtPosition, result);
1374 return ;
1375 }
1376}
1377
1378void QQmlLSCompletion::insideBinaryExpressionCompletion(
1379 const DomItem &parentForContext, const QQmlLSCompletionPosition &positionInfo,
1380 BackInsertIterator result) const
1381{
1382 const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
1383
1384 const QQmlJS::SourceLocation operatorLocation = regions[OperatorTokenRegion];
1385
1386 if (beforeLocation(positionInfo, operatorLocation)) {
1387 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1388 return;
1389 }
1390 if (afterLocation(operatorLocation, positionInfo)) {
1391 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1392 return;
1393 }
1394}
1395
1436void QQmlLSCompletion::insideScriptPattern(const DomItem &parentForContext,
1437 const QQmlLSCompletionPosition &positionInfo,
1438 BackInsertIterator result) const
1439{
1440 const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
1441
1443
1444 if (!afterLocation(equal, positionInfo))
1445 return;
1446
1447 // otherwise, only complete case and default
1448 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1449}
1450
1455void QQmlLSCompletion::insideVariableDeclarationEntry(const DomItem &parentForContext,
1456 const QQmlLSCompletionPosition &positionInfo,
1457 BackInsertIterator result) const
1458{
1459 insideScriptPattern(parentForContext, positionInfo, result);
1460}
1461
1462void QQmlLSCompletion::insideThrowStatement(const DomItem &parentForContext,
1463 const QQmlLSCompletionPosition &positionInfo,
1464 BackInsertIterator result) const
1465{
1466 const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
1467
1468 const QQmlJS::SourceLocation throwKeyword = regions[ThrowKeywordRegion];
1469
1470 if (afterLocation(throwKeyword, positionInfo)) {
1471 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1472 return;
1473 }
1474}
1475
1476void QQmlLSCompletion::insideLabelledStatement(const DomItem &parentForContext,
1477 const QQmlLSCompletionPosition &positionInfo,
1478 BackInsertIterator result) const
1479{
1480 const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
1481
1482 const QQmlJS::SourceLocation colon = regions[ColonTokenRegion];
1483
1484 if (afterLocation(colon, positionInfo)) {
1485 suggestJSStatementCompletion(positionInfo.itemAtPosition, result);
1486 return;
1487 }
1488 // note: the case "beforeLocation(ctx, colon)" probably never happens:
1489 // this is because without the colon, the parser will probably not parse this as a
1490 // labelledstatement but as a normal expression statement.
1491 // So this case only happens when the colon already exists, and the user goes back to the
1492 // label name and requests completion for that label.
1493}
1494
1500{
1501 for (DomItem current = context; current; current = current.directParent()) {
1502 if (current.internalKind() == DomType::ScriptLabelledStatement) {
1503 const QString label = current.field(Fields::label).value().toString();
1504 if (label.isEmpty())
1505 continue;
1506 CompletionItem item;
1507 item.label = label.toUtf8();
1508 item.kind = int(CompletionItemKind::Value); // variable?
1509 // TODO: more stuff here?
1510 result = item;
1511 } else if (current.internalKind() == DomType::ScriptExpression) {
1512 // quick exit when leaving the JS part
1513 return;
1514 }
1515 }
1516 return;
1517}
1518
1519void QQmlLSCompletion::insideContinueStatement(const DomItem &parentForContext,
1520 const QQmlLSCompletionPosition &positionInfo,
1521 BackInsertIterator result) const
1522{
1523 const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
1524
1525 const QQmlJS::SourceLocation continueKeyword = regions[ContinueKeywordRegion];
1526
1527 if (afterLocation(continueKeyword, positionInfo)) {
1528 collectLabels(parentForContext, result);
1529 return;
1530 }
1531}
1532
1533void QQmlLSCompletion::insideBreakStatement(const DomItem &parentForContext,
1534 const QQmlLSCompletionPosition &positionInfo,
1535 BackInsertIterator result) const
1536{
1537 const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
1538
1539 const QQmlJS::SourceLocation breakKeyword = regions[BreakKeywordRegion];
1540
1541 if (afterLocation(breakKeyword, positionInfo)) {
1542 collectLabels(parentForContext, result);
1543 return;
1544 }
1545}
1546
1547void QQmlLSCompletion::insideConditionalExpression(const DomItem &parentForContext,
1548 const QQmlLSCompletionPosition &positionInfo,
1549 BackInsertIterator result) const
1550{
1551 const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
1552
1553 const QQmlJS::SourceLocation questionMark = regions[QuestionMarkTokenRegion];
1554 const QQmlJS::SourceLocation colon = regions[ColonTokenRegion];
1555
1556 if (beforeLocation(positionInfo, questionMark)) {
1557 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1558 return;
1559 }
1560 if (betweenLocations(questionMark, positionInfo, colon)) {
1561 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1562 return;
1563 }
1564 if (afterLocation(colon, positionInfo)) {
1565 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1566 return;
1567 }
1568}
1569
1570void QQmlLSCompletion::insideUnaryExpression(const DomItem &parentForContext,
1571 const QQmlLSCompletionPosition &positionInfo,
1572 BackInsertIterator result) const
1573{
1574 const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
1575
1576 const QQmlJS::SourceLocation operatorToken = regions[OperatorTokenRegion];
1577
1578 if (afterLocation(operatorToken, positionInfo)) {
1579 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1580 return;
1581 }
1582}
1583
1584void QQmlLSCompletion::insidePostExpression(const DomItem &parentForContext,
1585 const QQmlLSCompletionPosition &positionInfo,
1586 BackInsertIterator result) const
1587{
1588 const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
1589
1590 const QQmlJS::SourceLocation operatorToken = regions[OperatorTokenRegion];
1591
1592 if (beforeLocation(positionInfo, operatorToken)) {
1593 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1594 return;
1595 }
1596}
1597
1598void QQmlLSCompletion::insideParenthesizedExpression(const DomItem &parentForContext,
1599 const QQmlLSCompletionPosition &positionInfo,
1600 BackInsertIterator result) const
1601{
1602 const auto regions = FileLocations::treeOf(parentForContext)->info().regions;
1603
1604 const QQmlJS::SourceLocation leftParenthesis = regions[LeftParenthesisRegion];
1605 const QQmlJS::SourceLocation rightParenthesis = regions[RightParenthesisRegion];
1606
1607 if (betweenLocations(leftParenthesis, positionInfo, rightParenthesis)) {
1608 suggestJSExpressionCompletion(positionInfo.itemAtPosition, result);
1609 return;
1610 }
1611}
1612
1613void QQmlLSCompletion::signalHandlerCompletion(const QQmlJSScope::ConstPtr &scope,
1614 QDuplicateTracker<QString> *usedNames,
1615 BackInsertIterator result) const
1616{
1617 const auto keyValues = scope->methods().asKeyValueRange();
1618 for (const auto &[name, method] : keyValues) {
1619 if (method.access() != QQmlJSMetaMethod::Public
1620 || method.methodType() != QQmlJSMetaMethodType::Signal) {
1621 continue;
1622 }
1623 if (usedNames && usedNames->hasSeen(name)) {
1624 continue;
1625 }
1626
1627 CompletionItem completion;
1628 completion.label = QQmlSignalNames::signalNameToHandlerName(name).toUtf8();
1629 completion.kind = int(CompletionItemKind::Method);
1630 result = completion;
1631 }
1632}
1633
1638QList<CompletionItem>
1640 const CompletionContextStrings &contextStrings) const
1641{
1642 QList<CompletionItem> result;
1643 collectCompletions(currentItem, contextStrings, std::back_inserter(result));
1644 return result;
1645}
1646
1647void QQmlLSCompletion::collectCompletions(const DomItem &currentItem,
1648 const CompletionContextStrings &contextStrings,
1649 BackInsertIterator result) const
1650{
1672 const QQmlLSCompletionPosition positionInfo{ currentItem, contextStrings };
1673 for (DomItem currentParent = currentItem; currentParent;
1674 currentParent = currentParent.directParent()) {
1675 const DomType currentType = currentParent.internalKind();
1676
1677 switch (currentType) {
1678 case DomType::Id:
1679 // suppress completions for ids
1680 return;
1681 case DomType::Pragma:
1682 insidePragmaCompletion(currentParent, positionInfo, result);
1683 return;
1684 case DomType::ScriptType: {
1685 if (currentParent.directParent().internalKind() == DomType::QmlObject) {
1686 insideQmlObjectCompletion(currentParent.directParent(), positionInfo, result);
1687 return;
1688 }
1689
1690 LocalSymbolsTypes options;
1691 options.setFlag(LocalSymbolsType::ObjectType);
1692 options.setFlag(LocalSymbolsType::ValueType);
1693 suggestReachableTypes(currentItem, options, CompletionItemKind::Class, result);
1694 return;
1695 }
1696 case DomType::ScriptFormalParameter:
1697 // no autocompletion inside of function parameter definition
1698 return;
1699 case DomType::Binding:
1700 insideBindingCompletion(currentParent, positionInfo, result);
1701 return;
1702 case DomType::Import:
1703 insideImportCompletion(currentParent, positionInfo, result);
1704 return;
1705 case DomType::ScriptForStatement:
1706 insideForStatementCompletion(currentParent, positionInfo, result);
1707 return;
1708 case DomType::ScriptBlockStatement:
1709 suggestJSStatementCompletion(positionInfo.itemAtPosition, result);
1710 return;
1711 case DomType::QmlFile:
1712 insideQmlFileCompletion(currentParent, positionInfo, result);
1713 return;
1714 case DomType::QmlObject:
1715 insideQmlObjectCompletion(currentParent, positionInfo, result);
1716 return;
1717 case DomType::MethodInfo:
1718 // suppress completions
1719 return;
1720 case DomType::PropertyDefinition:
1721 insidePropertyDefinitionCompletion(currentParent, positionInfo, result);
1722 return;
1723 case DomType::ScriptBinaryExpression:
1724 // ignore field member expressions: these need additional context from its parents
1725 if (QQmlLSUtils::isFieldMemberExpression(currentParent))
1726 continue;
1727 insideBinaryExpressionCompletion(currentParent, positionInfo, result);
1728 return;
1729 case DomType::ScriptLiteral:
1730 insideScriptLiteralCompletion(currentParent, positionInfo, result);
1731 return;
1732 case DomType::ScriptCallExpression:
1733 insideCallExpression(currentParent, positionInfo, result);
1734 return;
1735 case DomType::ScriptIfStatement:
1736 insideIfStatement(currentParent, positionInfo, result);
1737 return;
1738 case DomType::ScriptReturnStatement:
1739 insideReturnStatement(currentParent, positionInfo, result);
1740 return;
1741 case DomType::ScriptWhileStatement:
1742 insideWhileStatement(currentParent, positionInfo, result);
1743 return;
1744 case DomType::ScriptDoWhileStatement:
1745 insideDoWhileStatement(currentParent, positionInfo, result);
1746 return;
1747 case DomType::ScriptForEachStatement:
1748 insideForEachStatement(currentParent, positionInfo, result);
1749 return;
1750 case DomType::ScriptTryCatchStatement:
1769 return;
1770 case DomType::ScriptSwitchStatement:
1771 insideSwitchStatement(currentParent, positionInfo, result);
1772 return;
1773 case DomType::ScriptCaseClause:
1774 insideCaseClause(currentParent, positionInfo, result);
1775 return;
1776 case DomType::ScriptDefaultClause:
1777 if (ctxBeforeStatement(positionInfo, currentParent, QQmlJS::Dom::DefaultKeywordRegion))
1778 continue;
1779 insideDefaultClause(currentParent, positionInfo, result);
1780 return;
1781 case DomType::ScriptCaseBlock:
1782 insideCaseBlock(currentParent, positionInfo, result);
1783 return;
1784 case DomType::ScriptVariableDeclaration:
1785 // not needed: thats a list of ScriptVariableDeclarationEntry, and those entries cannot
1786 // be suggested because they all start with `{`, `[` or an identifier that should not be
1787 // in use yet.
1788 return;
1789 case DomType::ScriptVariableDeclarationEntry:
1790 insideVariableDeclarationEntry(currentParent, positionInfo, result);
1791 return;
1792 case DomType::ScriptProperty:
1793 // fallthrough: a ScriptProperty is a ScriptPattern but inside a JS Object. It gets the
1794 // same completions as a ScriptPattern.
1795 case DomType::ScriptPattern:
1796 insideScriptPattern(currentParent, positionInfo, result);
1797 return;
1798 case DomType::ScriptThrowStatement:
1799 insideThrowStatement(currentParent, positionInfo, result);
1800 return;
1801 case DomType::ScriptLabelledStatement:
1802 insideLabelledStatement(currentParent, positionInfo, result);
1803 return;
1804 case DomType::ScriptContinueStatement:
1805 insideContinueStatement(currentParent, positionInfo, result);
1806 return;
1807 case DomType::ScriptBreakStatement:
1808 insideBreakStatement(currentParent, positionInfo, result);
1809 return;
1810 case DomType::ScriptConditionalExpression:
1811 insideConditionalExpression(currentParent, positionInfo, result);
1812 return;
1813 case DomType::ScriptUnaryExpression:
1814 insideUnaryExpression(currentParent, positionInfo, result);
1815 return;
1816 case DomType::ScriptPostExpression:
1817 insidePostExpression(currentParent, positionInfo, result);
1818 return;
1819 case DomType::ScriptParenthesizedExpression:
1820 insideParenthesizedExpression(currentParent, positionInfo, result);
1821 return;
1822
1823 // TODO: Implement those statements.
1824 // In the meanwhile, suppress completions to avoid weird behaviors.
1825 case DomType::ScriptArray:
1826 case DomType::ScriptObject:
1827 case DomType::ScriptElision:
1828 case DomType::ScriptArrayEntry:
1829 return;
1830
1831 default:
1832 continue;
1833 }
1834 Q_UNREACHABLE();
1835 }
1836
1837 // no completion could be found
1838 qCDebug(QQmlLSUtilsLog) << "No completion was found for current request.";
1839 return;
1840}
1841
1843{
1844 const auto keys = pluginLoader.metaDataKeys();
1845 for (qsizetype i = 0; i < keys.size(); ++i) {
1846 auto instance = std::unique_ptr<QQmlLSPlugin>(
1847 qobject_cast<QQmlLSPlugin *>(pluginLoader.instance(i)));
1848 if (!instance)
1849 continue;
1850 if (auto completionInstance = instance->createCompletionPlugin())
1851 m_plugins.push_back(std::move(completionInstance));
1852 }
1853}
1854
1859void QQmlLSCompletion::collectFromPlugins(qxp::function_ref<CompletionFromPluginFunction> f,
1860 BackInsertIterator result) const
1861{
1862 for (const auto &plugin : m_plugins) {
1863 Q_ASSERT(plugin);
1864 f(plugin.get(), result);
1865 }
1866}
1867
1868void QQmlLSCompletion::suggestSnippetsForLeftHandSideOfBinding(const DomItem &itemAtPosition,
1869 BackInsertIterator result) const
1870{
1871 collectFromPlugins(
1872 [&itemAtPosition](QQmlLSCompletionPlugin *p, BackInsertIterator result) {
1873 p->suggestSnippetsForLeftHandSideOfBinding(itemAtPosition, result);
1874 },
1875 result);
1876}
1877
1878void QQmlLSCompletion::suggestSnippetsForRightHandSideOfBinding(const DomItem &itemAtPosition,
1879 BackInsertIterator result) const
1880{
1881 collectFromPlugins(
1882 [&itemAtPosition](QQmlLSCompletionPlugin *p, BackInsertIterator result) {
1883 p->suggestSnippetsForRightHandSideOfBinding(itemAtPosition, result);
1884 },
1885 result);
1886}
1887
static JNINativeMethod methods[]
\inmodule QtCore
Definition qbytearray.h:57
QByteArray & append(char c)
This is an overloaded member function, provided for convenience. It differs from the above function o...
QString toString(const QString &defaultValue={}) const
Returns the string value stored in this QCborValue, if it is of the string type.
Tracks the types for the QmlCompiler.
QHash< QString, QQmlJSMetaMethod > methods() const
Returns all methods visible from this scope including those of base types and extensions.
bool isSingleton() const
QQmlJSScope::Ptr parentScope()
bool isReferenceType() const
AccessSemantics accessSemantics() const
QQmlJSMetaEnum enumeration(const QString &name) const
bool isCreatable() const
QQmlJSScope::ConstPtr attachedType() const
QQmlJSMetaProperty property(const QString &name) const
Represents a consistent set of types organized in modules, it is the top level of the DOM.
DomItem field(QStringView name) const
QCborValue value() const
static FileLocations::Tree treeOf(const DomItem &)
A QmlFile, when loaded in a DomEnvironment that has the DomCreationOption::WithSemanticAnalysis,...
QQmlLSCompletion provides completions for all kinds of QML and JS constructs.
QLspSpecification::CompletionItem CompletionItem
QList< CompletionItem > completions(const DomItem &currentItem, const CompletionContextStrings &ctx) const
static CompletionItem makeSnippet(QUtf8StringView qualifier, QUtf8StringView label, QUtf8StringView insertText)
QQmlLSCompletion(const QFactoryLoader &pluginLoader)
std::back_insert_iterator< QList< CompletionItem > > BackInsertIterator
static std::optional< QQmlLSUtilsExpressionType > resolveExpressionType(const DomItem &item, QQmlLSUtilsResolveOptions)
static QString qualifiersFrom(const DomItem &el)
static bool isFieldMemberExpression(const DomItem &item)
static bool isFieldMemberAccess(const DomItem &item)
static QString signalNameToHandlerName(QAnyStringView signal)
\inmodule QtCore \reentrant
\inmodule QtCore
\inmodule QtCore
Definition qstringview.h:78
constexpr QStringView sliced(qsizetype pos) const noexcept
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
QString first(qsizetype n) const &
Definition qstring.h:390
QChar * data()
Returns a pointer to the data stored in the QString.
Definition qstring.h:1240
static QString number(int, int base=10)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:8084
QString & append(QChar c)
Definition qstring.cpp:3252
QByteArray toUtf8() const &
Definition qstring.h:634
EGLContext ctx
QSet< QString >::iterator it
Token token
Definition keywords.cpp:444
Combined button and popup list for selecting options.
@ SkipEmptyParts
Definition qnamespace.h:128
static void * context
static const QCssKnownValue properties[NumProperties - 1]
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char * method
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
const char * typeName
GLint location
GLenum GLsizei GLsizei GLint * values
[15]
GLenum GLenum GLsizei const GLuint * ids
GLdouble GLdouble right
GLfloat GLfloat f
GLint left
GLenum type
GLuint GLsizei const GLchar * label
[43]
GLuint name
GLdouble s
[6]
Definition qopenglext.h:235
GLuint res
GLuint GLuint * names
GLenum array
GLuint64EXT * result
[6]
GLfloat GLfloat p
[1]
GLuint GLenum option
static qreal component(const QPointF &point, unsigned int i)
void collectFromAllJavaScriptParents(const F &&f, const QQmlJSScope::ConstPtr &scope)
static const QQmlJSScope * resolve(const QQmlJSScope *current, const QStringList &names)
static void collectLabels(const DomItem &context, QQmlLSCompletion::BackInsertIterator result)
static bool testScopeSymbol(const QQmlJSScope::ConstPtr &scope, LocalSymbolsTypes options, CompletionItemKind kind)
static const QMap< QString, QList< QString > > valuesForPragmas
Mapping from pragma names to allowed pragma values.
QQmlLSUtilsAppendOption
@ AppendSemicolon
@ AppendNothing
@ EnumeratorIdentifier
@ QmlComponentIdentifier
ImportCompletionType
@ ResolveOwnerType
@ ResolveActualTypeForFieldMemberExpression
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QBasicUtf8StringView< false > QUtf8StringView
Definition qstringfwd.h:46
#define Q_UNUSED(x)
ptrdiff_t qsizetype
Definition qtypes.h:165
static const uint base
Definition qurlidna.cpp:20
static bool equal(const QChar *a, int l, const char *b)
Definition qurlidna.cpp:338
const char property[13]
Definition qwizard.cpp:101
QFile file
[0]
QStringList keys
QGraphicsItem * item
QAction * at
QHostInfo info
[0]
QQuickView * view
[0]
char * toString(const MyType &t)
[31]
QStringView el