| 1 | // Copyright (C) 2024 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
| 3 | |
| 4 | #include "qqmllsquickplugin_p.h" |
| 5 | #include <QtQmlLS/private/qqmllsutils_p.h> |
| 6 | #include <QtQmlLS/private/qqmllscompletion_p.h> |
| 7 | |
| 8 | using namespace QLspSpecification; |
| 9 | using namespace QQmlJS::Dom; |
| 10 | |
| 11 | QT_BEGIN_NAMESPACE |
| 12 | |
| 13 | std::unique_ptr<QQmlLSCompletionPlugin> QQmlLSQuickPlugin::createCompletionPlugin() const |
| 14 | { |
| 15 | return std::make_unique<QQmlLSQuickCompletionPlugin>(); |
| 16 | } |
| 17 | |
| 18 | void QQmlLSQuickCompletionPlugin::suggestSnippetsForLeftHandSideOfBinding( |
| 19 | const DomItem &itemAtPosition, BackInsertIterator result) const |
| 20 | { |
| 21 | auto file = itemAtPosition.containingFile().as<QmlFile>(); |
| 22 | if (!file) |
| 23 | return; |
| 24 | |
| 25 | // check if QtQuick has been imported |
| 26 | const auto &imports = file->imports(); |
| 27 | auto it = std::find_if(first: imports.constBegin(), last: imports.constEnd(), pred: [](const Import &import) { |
| 28 | return import.uri.moduleUri() == u"QtQuick" ; |
| 29 | }); |
| 30 | if (it == imports.constEnd()) { |
| 31 | return; |
| 32 | } |
| 33 | |
| 34 | // for default bindings: |
| 35 | suggestSnippetsForRightHandSideOfBinding(items: itemAtPosition, result); |
| 36 | |
| 37 | // check if the user already typed some qualifier, remove its dot and compare it to QtQuick's |
| 38 | // qualified name |
| 39 | const QString userTypedQualifier = QQmlLSUtils::qualifiersFrom(el: itemAtPosition); |
| 40 | if (!userTypedQualifier.isEmpty() |
| 41 | && !it->importId.startsWith(s: QStringView(userTypedQualifier).chopped(n: 1))) { |
| 42 | return; |
| 43 | } |
| 44 | |
| 45 | const QByteArray prefixForSnippet = |
| 46 | userTypedQualifier.isEmpty() ? it->importId.toUtf8() : QByteArray(); |
| 47 | const QByteArray prefixWithDotForSnippet = |
| 48 | it->importId.isEmpty() ? QByteArray() : it->importId.toUtf8().append(c: u'.'); |
| 49 | |
| 50 | auto resolver = file->typeResolver(); |
| 51 | if (!resolver) |
| 52 | return; |
| 53 | const auto qquickItemScope = resolver->typeForName(name: prefixWithDotForSnippet + u"Item"_s ); |
| 54 | const QQmlJSScope::ConstPtr ownerScope = itemAtPosition.qmlObject().semanticScope(); |
| 55 | if (!ownerScope || !qquickItemScope) |
| 56 | return; |
| 57 | |
| 58 | if (ownerScope->inherits(base: qquickItemScope)) { |
| 59 | result = QQmlLSCompletion::makeSnippet( |
| 60 | label: "states binding with PropertyChanges in State" , |
| 61 | insertText: "states: [\n" |
| 62 | "\t"_ba .append(a: prefixWithDotForSnippet) |
| 63 | .append(a: "State {\n" |
| 64 | "\t\tname: \"${1:name}\"\n" |
| 65 | "\t\t"_ba .append(a: prefixWithDotForSnippet) |
| 66 | .append(s: "PropertyChanges {\n" |
| 67 | "\t\t\ttarget: ${2:object}\n" |
| 68 | "\t\t}\n" |
| 69 | "\t}\n" |
| 70 | "]" ))); |
| 71 | result = QQmlLSCompletion::makeSnippet(label: "transitions binding with Transition" , |
| 72 | insertText: "transitions: [\n" |
| 73 | "\t"_ba .append(a: prefixWithDotForSnippet) |
| 74 | .append(s: "Transition {\n" |
| 75 | "\t\tfrom: \"${1:fromState}\"\n" |
| 76 | "\t\tto: \"${2:fromState}\"\n" |
| 77 | "\t}\n" |
| 78 | "]" )); |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | void QQmlLSQuickCompletionPlugin::suggestSnippetsForRightHandSideOfBinding( |
| 83 | const DomItem &itemAtPosition, BackInsertIterator result) const |
| 84 | { |
| 85 | auto file = itemAtPosition.containingFile().as<QmlFile>(); |
| 86 | if (!file) |
| 87 | return; |
| 88 | |
| 89 | // check if QtQuick has been imported |
| 90 | const auto &imports = file->imports(); |
| 91 | auto it = std::find_if(first: imports.constBegin(), last: imports.constEnd(), pred: [](const Import &import) { |
| 92 | return import.uri.moduleUri() == u"QtQuick" ; |
| 93 | }); |
| 94 | if (it == imports.constEnd()) { |
| 95 | return; |
| 96 | } |
| 97 | |
| 98 | // check if the user already typed some qualifier, remove its dot and compare it to QtQuick's |
| 99 | // qualified name |
| 100 | const QString userTypedQualifier = QQmlLSUtils::qualifiersFrom(el: itemAtPosition); |
| 101 | if (!userTypedQualifier.isEmpty() |
| 102 | && !it->importId.startsWith(s: QStringView(userTypedQualifier).chopped(n: 1))) { |
| 103 | return; |
| 104 | } |
| 105 | |
| 106 | const QByteArray prefixForSnippet = |
| 107 | userTypedQualifier.isEmpty() ? it->importId.toUtf8() : QByteArray(); |
| 108 | const QByteArray prefixWithDotForSnippet = |
| 109 | it->importId.isEmpty() ? QByteArray() : it->importId.toUtf8().append(c: u'.'); |
| 110 | |
| 111 | // Quick completions from Qt Creator's code model |
| 112 | result = QQmlLSCompletion::makeSnippet(qualifier: prefixForSnippet, label: "BorderImage snippet" , |
| 113 | insertText: "BorderImage {\n" |
| 114 | "\tid: ${1:name}\n" |
| 115 | "\tsource: \"${2:file}\"\n" |
| 116 | "\twidth: ${3:100}; height: ${4:100}\n" |
| 117 | "\tborder.left: ${5: 5}; border.top: ${5}\n" |
| 118 | "\tborder.right: ${5}; border.bottom: ${5}\n" |
| 119 | "}" ); |
| 120 | result = QQmlLSCompletion::makeSnippet(qualifier: prefixForSnippet, label: "ColorAnimation snippet" , |
| 121 | insertText: "ColorAnimation {\n" |
| 122 | "\tfrom: \"${1:white}\"\n" |
| 123 | "\tto: \"${2:black}\"\n" |
| 124 | "\tduration: ${3:200}\n" |
| 125 | "}" ); |
| 126 | result = QQmlLSCompletion::makeSnippet(qualifier: prefixForSnippet, label: "Image snippet" , |
| 127 | insertText: "Image {\n" |
| 128 | "\tid: ${1:name}\n" |
| 129 | "\tsource: \"${2:file}\"\n" |
| 130 | "}" ); |
| 131 | result = QQmlLSCompletion::makeSnippet(qualifier: prefixForSnippet, label: "Item snippet" , |
| 132 | insertText: "Item {\n" |
| 133 | "\tid: ${1:name}\n" |
| 134 | "}" ); |
| 135 | result = QQmlLSCompletion::makeSnippet(qualifier: prefixForSnippet, label: "NumberAnimation snippet" , |
| 136 | insertText: "NumberAnimation {\n" |
| 137 | "\ttarget: ${1:object}\n" |
| 138 | "\tproperty: \"${2:name}\"\n" |
| 139 | "\tduration: ${3:200}\n" |
| 140 | "\teasing.type: "_ba .append(a: prefixWithDotForSnippet) |
| 141 | .append(s: "Easing.${4:InOutQuad}\n" |
| 142 | "}" )); |
| 143 | result = QQmlLSCompletion::makeSnippet(qualifier: prefixForSnippet, label: "NumberAnimation with targets snippet" , |
| 144 | insertText: "NumberAnimation {\n" |
| 145 | "\ttargets: [${1:object}]\n" |
| 146 | "\tproperties: \"${2:name}\"\n" |
| 147 | "\tduration: ${3:200}\n" |
| 148 | "}" ); |
| 149 | result = QQmlLSCompletion::makeSnippet(qualifier: prefixForSnippet, label: "PauseAnimation snippet" , |
| 150 | insertText: "PauseAnimation {\n" |
| 151 | "\tduration: ${1:200}\n" |
| 152 | "}" ); |
| 153 | result = QQmlLSCompletion::makeSnippet(qualifier: prefixForSnippet, label: "PropertyAction snippet" , |
| 154 | insertText: "PropertyAction {\n" |
| 155 | "\ttarget: ${1:object}\n" |
| 156 | "\tproperty: \"${2:name}\"\n" |
| 157 | "}" ); |
| 158 | result = QQmlLSCompletion::makeSnippet(qualifier: prefixForSnippet, label: "PropertyAction with targets snippet" , |
| 159 | insertText: "PropertyAction {\n" |
| 160 | "\ttargets: [${1:object}]\n" |
| 161 | "\tproperties: \"${2:name}\"\n" |
| 162 | "}" ); |
| 163 | result = QQmlLSCompletion::makeSnippet(qualifier: prefixForSnippet, label: "PropertyChanges snippet" , |
| 164 | insertText: "PropertyChanges {\n" |
| 165 | "\ttarget: ${1:object}\n" |
| 166 | "}" ); |
| 167 | result = QQmlLSCompletion::makeSnippet(qualifier: prefixForSnippet, label: "State snippet" , |
| 168 | insertText: "State {\n" |
| 169 | "\tname: ${1:name}\n" |
| 170 | "\t"_ba .append(a: prefixWithDotForSnippet) |
| 171 | .append(s: "PropertyChanges {\n" |
| 172 | "\t\ttarget: ${2:object}\n" |
| 173 | "\t}\n" |
| 174 | "}" )); |
| 175 | result = QQmlLSCompletion::makeSnippet(qualifier: prefixForSnippet, label: "Text snippet" , |
| 176 | insertText: "Text {\n" |
| 177 | "\tid: ${1:name}\n" |
| 178 | "\ttext: qsTr(\"${2:text}\")\n" |
| 179 | "}" ); |
| 180 | result = QQmlLSCompletion::makeSnippet(qualifier: prefixForSnippet, label: "Transition snippet" , |
| 181 | insertText: "Transition {\n" |
| 182 | "\tfrom: \"${1:fromState}\"\n" |
| 183 | "\tto: \"${2:toState}\"\n" |
| 184 | "}" ); |
| 185 | } |
| 186 | |
| 187 | QT_END_NAMESPACE |
| 188 | |