diff options
7 files changed, 125 insertions, 49 deletions
diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/InsertionFormat.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/InsertionFormat.kt index 7c791eb8372..55365c909df 100644 --- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/InsertionFormat.kt +++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/InsertionFormat.kt @@ -28,3 +28,7 @@ internal val JsonNewObjectTemplate = LiteralNewLineFormat(": {\n}") internal val JsonStringArrayTemplate = LiteralWithCaretFormat(": ['|'],") internal val ConstrainAnchorTemplate = LiveTemplateFormat(": ['<>', '<>', <0>],") + +internal val ClearAllTemplate = LiteralWithCaretFormat( + literalFormat = ": ['${ClearOption.Constraints}', '${ClearOption.Dimensions}', '${ClearOption.Transforms}']," +)
\ No newline at end of file diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/JsonPsiUtil.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/JsonPsiUtil.kt new file mode 100644 index 00000000000..29a5b452278 --- /dev/null +++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/JsonPsiUtil.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tools.compose.code.completion.constraintlayout + +import com.intellij.codeInsight.completion.CompletionParameters +import com.intellij.json.psi.JsonProperty +import com.intellij.psi.util.parentOfType + +/** + * From the element being invoked, returns the [JsonProperty] parent that also includes the [JsonProperty] from which completion is + * triggered. + */ +internal fun getJsonPropertyParent(parameters: CompletionParameters): JsonProperty? = + parameters.position.parentOfType<JsonProperty>(withSelf = true)?.parentOfType<JsonProperty>(withSelf = false)
\ No newline at end of file diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/ConstraintSetCompletionProviders.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/CompletionProviders.kt index 4e5260d739e..512c432af00 100644 --- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/ConstraintSetCompletionProviders.kt +++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/CompletionProviders.kt @@ -15,6 +15,7 @@ */ package com.android.tools.compose.code.completion.constraintlayout.provider +import com.android.tools.compose.code.completion.constraintlayout.ClearAllTemplate import com.android.tools.compose.code.completion.constraintlayout.ClearOption import com.android.tools.compose.code.completion.constraintlayout.ConstrainAnchorTemplate import com.android.tools.compose.code.completion.constraintlayout.ConstraintLayoutKeyWord @@ -28,13 +29,12 @@ import com.android.tools.compose.code.completion.constraintlayout.RenderTransfor import com.android.tools.compose.code.completion.constraintlayout.SpecialAnchor import com.android.tools.compose.code.completion.constraintlayout.StandardAnchor import com.android.tools.compose.code.completion.constraintlayout.TransitionField -import com.android.tools.compose.code.completion.constraintlayout.provider.model.BaseJsonPropertyModel +import com.android.tools.compose.code.completion.constraintlayout.getJsonPropertyParent import com.android.tools.compose.code.completion.constraintlayout.provider.model.ConstraintSetModel import com.android.tools.compose.code.completion.constraintlayout.provider.model.ConstraintSetsPropertyModel -import com.android.tools.compose.code.completion.constraintlayout.provider.model.ConstraintsModel +import com.android.tools.compose.code.completion.constraintlayout.provider.model.JsonPropertyModel import com.android.tools.compose.completion.addLookupElement import com.android.tools.compose.completion.inserthandler.InsertionFormat -import com.android.tools.compose.completion.inserthandler.LiteralWithCaretFormat import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet @@ -107,7 +107,7 @@ internal object ConstraintSetFieldsProvider : BaseConstraintSetsCompletionProvid parameters: CompletionParameters, result: CompletionResultSet ) { - val currentConstraintSet = getJsonPropertyParent(parameters)?.let { ConstraintSetModel(it) } ?: return + val currentConstraintSet = ConstraintSetModel.getModelForCompletionOnConstraintSetProperty(parameters) ?: return val currentSetName = currentConstraintSet.name ?: return constraintSetsPropertyModel.getRemainingFieldsForConstraintSet(currentSetName).forEach { fieldName -> val template = if (fieldName == KeyWords.Extends) JsonStringValueTemplate else JsonNewObjectTemplate @@ -125,7 +125,7 @@ internal object ConstraintSetNamesProvider : BaseConstraintSetsCompletionProvide parameters: CompletionParameters, result: CompletionResultSet ) { - val currentConstraintSet = getJsonPropertyParent(parameters)?.let { ConstraintSetModel(it) } + val currentConstraintSet = ConstraintSetModel.getModelForCompletionOnConstraintSetProperty(parameters) val currentSetName = currentConstraintSet?.name val names = constraintSetsPropertyModel.getConstraintSetNames().toMutableSet() if (currentSetName != null) { @@ -144,33 +144,28 @@ internal object ConstraintsProvider : BaseConstraintSetsCompletionProvider() { parameters: CompletionParameters, result: CompletionResultSet ) { - val jsonPropertyParent = getJsonPropertyParent(parameters) - val currentConstraintsModel = jsonPropertyParent?.let { ConstraintsModel(it) } - val existingFields = currentConstraintsModel?.declaredFieldNames?.toHashSet() ?: emptySet<String>() + val parentPropertyModel = JsonPropertyModel.getModelForCompletionOnInnerJsonProperty(parameters) ?: return + val existingFieldsSet = parentPropertyModel.declaredFieldNamesSet StandardAnchor.values().forEach { - if (!existingFields.contains(it.keyWord)) { + if (!existingFieldsSet.contains(it.keyWord)) { result.addLookupElement(lookupString = it.keyWord, tailText = " [...]", format = ConstrainAnchorTemplate) } } - if (!existingFields.contains(KeyWords.Visibility)) { + if (!existingFieldsSet.contains(KeyWords.Visibility)) { result.addLookupElement(lookupString = KeyWords.Visibility, format = JsonStringValueTemplate) } - result.addEnumKeyWordsWithStringValueTemplate<SpecialAnchor>(existingFields) - result.addEnumKeyWordsWithNumericValueTemplate<Dimension>(existingFields) - result.addEnumKeyWordsWithNumericValueTemplate<RenderTransform>(existingFields) + result.addEnumKeyWordsWithStringValueTemplate<SpecialAnchor>(existingFieldsSet) + result.addEnumKeyWordsWithNumericValueTemplate<Dimension>(existingFieldsSet) + result.addEnumKeyWordsWithNumericValueTemplate<RenderTransform>(existingFieldsSet) // Complete 'clear' if the containing ConstraintSet has `extendsFrom` - val containingConstraintSetModel = jsonPropertyParent?.parentOfType<JsonProperty>(withSelf = false)?.let { ConstraintSetModel(it) } + val containingConstraintSetModel = parentPropertyModel.getParentProperty()?.let { + ConstraintSetModel(it) + } if (containingConstraintSetModel?.extendsFrom != null) { // Add an option with an empty string array and another one with all clear options result.addLookupElement(lookupString = KeyWords.Clear, format = JsonStringArrayTemplate) - result.addLookupElement( - lookupString = KeyWords.Clear, - tailText = " [<all>]", - format = LiteralWithCaretFormat( - literalFormat = ": ['${ClearOption.Constraints}', '${ClearOption.Dimensions}', '${ClearOption.Transforms}']," - ) - ) + result.addLookupElement(lookupString = KeyWords.Clear, format = ClearAllTemplate, tailText = " [<all>]") } } } @@ -247,10 +242,9 @@ internal object ClearOptionsProvider : BaseConstraintSetsCompletionProvider() { */ internal object TransitionFieldsProvider : CompletionProvider<CompletionParameters>() { override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) { - val transitionPropertyModel = getJsonPropertyParent(parameters)?.let { BaseJsonPropertyModel(it) } - val existing = transitionPropertyModel?.declaredFieldNames?.toHashSet() ?: emptySet() + val parentPropertyModel = JsonPropertyModel.getModelForCompletionOnInnerJsonProperty(parameters) ?: return TransitionField.values().forEach { - if (existing.contains(it.keyWord)) { + if (parentPropertyModel.containsPropertyOfName(it.keyWord)) { // skip return@forEach } @@ -282,13 +276,6 @@ internal class EnumValuesCompletionProvider<E>(private val enumClass: KClass<E>) } /** - * From the element being invoked, returns the [JsonProperty] parent that also includes the [JsonProperty] from which completion is - * triggered. - */ -private fun getJsonPropertyParent(parameters: CompletionParameters): JsonProperty? = - parameters.position.parentOfType<JsonProperty>(withSelf = true)?.parentOfType<JsonProperty>(withSelf = false) - -/** * Add the [ConstraintLayoutKeyWord.keyWord] of the enum constants as a completion result that takes a string for its value. */ private inline fun <reified E> CompletionResultSet.addEnumKeyWordsWithStringValueTemplate( diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/BaseJsonElementModel.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/BaseJsonElementModel.kt index 575eb10197d..16e74dd8c8f 100644 --- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/BaseJsonElementModel.kt +++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/BaseJsonElementModel.kt @@ -15,10 +15,14 @@ */ package com.android.tools.compose.code.completion.constraintlayout.provider.model +import com.android.tools.compose.code.completion.constraintlayout.getJsonPropertyParent +import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.json.psi.JsonElement import com.intellij.json.psi.JsonObject import com.intellij.json.psi.JsonProperty +import com.intellij.openapi.progress.ProgressManager import com.intellij.psi.SmartPointerManager +import com.intellij.psi.util.parentOfType import org.jetbrains.kotlin.psi.psiUtil.getChildOfType /** @@ -33,21 +37,65 @@ internal abstract class BaseJsonElementModel<E: JsonElement>(element: E) { * * Populates some common fields and provides useful function while avoiding holding to PsiElement instances. */ -internal open class BaseJsonPropertyModel(element: JsonProperty): BaseJsonElementModel<JsonProperty>(element) { +internal open class JsonPropertyModel(element: JsonProperty): BaseJsonElementModel<JsonProperty>(element) { + /** + * The [JsonObject] that describes this [JsonProperty]. + */ + private val innerJsonObject: JsonObject? = elementPointer.element?.getChildOfType<JsonObject>() + + /** + * A mapping of the containing [JsonProperty]s by their declare name. + */ + private val propertiesByName: Map<String, JsonProperty> = + innerJsonObject?.propertyList?.associateBy { it.name } ?: emptyMap() + /** * [List] of all the children of this element that are [JsonProperty]. */ - protected val innerProperties: List<JsonProperty> = - elementPointer.element?.getChildOfType<JsonObject>()?.propertyList?.toList() ?: emptyList() + protected val innerProperties: Collection<JsonProperty> = propertiesByName.values + + /** + * Name of the [JsonProperty]. + */ + val name: String? + get() = elementPointer.element?.name /** - * Names of all declared properties in this Json. + * A set of names for all declared properties in this [JsonProperty]. */ - val declaredFieldNames: List<String> = innerProperties.map { it.name } + val declaredFieldNamesSet: Set<String> = propertiesByName.keys /** * For the children of the current element, returns the [JsonProperty] which name matches the given [name]. Null if none of them does. */ - protected fun findProperty(name: String): JsonProperty? = - innerProperties.firstOrNull { it.name == name } + protected fun findProperty(name: String): JsonProperty? = propertiesByName[name] + + /** + * Returns true if this [JsonProperty] contains another [JsonProperty] declared by the given [name]. + */ + fun containsPropertyOfName(name: String): Boolean = propertiesByName.containsKey(name) + + /** + * Returns the containing [JsonProperty]. + * + * May return null if this model is for a top level [JsonProperty]. + */ + fun getParentProperty(): JsonProperty? = elementPointer.element?.parentOfType<JsonProperty>(withSelf = false) + + companion object { + /** + * Returns the [JsonPropertyModel] where the completion is performed on an inner [JsonProperty], including if the completion is on the + * value side of the inner [JsonProperty]. + * + * In other words, the model of the second [JsonProperty] parent if the element on [CompletionParameters.getPosition] is NOT a + * [JsonProperty]. + * + * Or the model of the first [JsonProperty] parent if the element on [CompletionParameters.getPosition] is a [JsonProperty]. + */ + fun getModelForCompletionOnInnerJsonProperty(parameters: CompletionParameters): JsonPropertyModel? { + val parentJsonProperty = getJsonPropertyParent(parameters) ?: return null + ProgressManager.checkCanceled() + return JsonPropertyModel(parentJsonProperty) + } + } }
\ No newline at end of file diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintSetModel.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintSetModel.kt index 26f45edb9f5..95fd5db4b53 100644 --- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintSetModel.kt +++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintSetModel.kt @@ -16,26 +16,24 @@ package com.android.tools.compose.code.completion.constraintlayout.provider.model import com.android.tools.compose.code.completion.constraintlayout.KeyWords +import com.android.tools.compose.code.completion.constraintlayout.getJsonPropertyParent +import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.json.psi.JsonProperty import com.intellij.json.psi.JsonStringLiteral +import com.intellij.openapi.progress.ProgressManager /** * Model for the JSON block corresponding to a single ConstraintSet. * * A ConstraintSet is a state that defines a specific layout of the contents in a ConstraintLayout. */ -internal class ConstraintSetModel(jsonProperty: JsonProperty) : BaseJsonPropertyModel(jsonProperty) { +internal class ConstraintSetModel(jsonProperty: JsonProperty) : JsonPropertyModel(jsonProperty) { /** * List of properties that have a constraint block assigned to it. */ private val propertiesWithConstraints = innerProperties.filter { it.name != KeyWords.Extends } /** - * Name of the ConstraintSet. - */ - val name: String? = elementPointer.element?.name - - /** * Name of the ConstraintSet this is extending constraints from. */ val extendsFrom: String? = (findProperty(KeyWords.Extends)?.value as? JsonStringLiteral)?.value @@ -57,4 +55,16 @@ internal class ConstraintSetModel(jsonProperty: JsonProperty) : BaseJsonProperty // TODO(b/207030860): Add a method that can pull all resolved constraints for each widget ID, it could be useful to make sure we are not // offering options that are implicitly present from the 'Extends' ConstraintSet + + companion object { + /** + * Returns a [ConstraintSetModel], for when the completion is performed on a property or the value of a property within a ConstraintSet + * declaration. + */ + fun getModelForCompletionOnConstraintSetProperty(parameters: CompletionParameters): ConstraintSetModel? { + val parentJsonProperty = getJsonPropertyParent(parameters) ?: return null + ProgressManager.checkCanceled() + return ConstraintSetModel(parentJsonProperty) + } + } }
\ No newline at end of file diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintSetsPropertyModel.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintSetsPropertyModel.kt index 0bfde8c3ce6..0d16fa7140c 100644 --- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintSetsPropertyModel.kt +++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintSetsPropertyModel.kt @@ -28,7 +28,7 @@ import com.intellij.json.psi.JsonProperty */ internal class ConstraintSetsPropertyModel( constraintSetsElement: JsonProperty -) : BaseJsonPropertyModel(constraintSetsElement) { +) : JsonPropertyModel(constraintSetsElement) { // TODO(b/209839226): Explore how we could use these models to validate the syntax or structure of the JSON as well as to check logic // correctness through Inspections/Lint /** @@ -39,8 +39,8 @@ internal class ConstraintSetsPropertyModel( /** * The names of all ConstraintSets in this block. */ - fun getConstraintSetNames(): List<String> { - return declaredFieldNames + fun getConstraintSetNames(): Collection<String> { + return declaredFieldNamesSet } /** @@ -51,7 +51,7 @@ internal class ConstraintSetsPropertyModel( val availableNames = mutableSetOf(KeyWords.Extends) val usedNames = mutableSetOf<String>() constraintSets.forEach { constraintSet -> - constraintSet.declaredFieldNames.forEach { propertyName -> + constraintSet.declaredFieldNamesSet.forEach { propertyName -> if (constraintSet.name == constraintSetName) { usedNames.add(propertyName) } diff --git a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintsModel.kt b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintsModel.kt index 7417d159a5e..4370246d874 100644 --- a/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintsModel.kt +++ b/compose-ide-plugin/src/com/android/tools/compose/code/completion/constraintlayout/provider/model/ConstraintsModel.kt @@ -23,7 +23,7 @@ import com.intellij.json.psi.JsonProperty * Constraints are a set of instructions that define the widget's dimensions, position with respect to other widgets and render-time * transforms. */ -internal class ConstraintsModel(jsonProperty: JsonProperty): BaseJsonPropertyModel(jsonProperty) { +internal class ConstraintsModel(jsonProperty: JsonProperty): JsonPropertyModel(jsonProperty) { // TODO(b/207030860): Fill the contents of this model as is necessary, keeping in mind that it would be useful to have fields like // 'verticalConstraints', 'hasBaseline', 'dimensionBehavior', etc... }
\ No newline at end of file |