Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions android-templates/intellij.android.templates.iml
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,7 @@
<orderEntry type="module" module-name="kotlin.base.plugin" />
<orderEntry type="module" module-name="kotlin.base.project-structure" />
<orderEntry type="module" module-name="kotlin.base.facet" />
<orderEntry type="module" module-name="kotlin.code-insight.live-templates.shared" />
<orderEntry type="module" module-name="kotlin.base.util" />
</component>
</module>
2 changes: 2 additions & 0 deletions android-templates/intellij.android.templates.tests.iml
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,7 @@
<orderEntry type="module" module-name="intellij.platform.ide.core.impl" scope="TEST" />
<orderEntry type="module" module-name="intellij.java.testFramework" scope="TEST" />
<orderEntry type="library" scope="TEST" name="Guava" level="project" />
<orderEntry type="module" module-name="kotlin.code-insight.live-templates.shared" scope="TEST" />
</component>
<component name="TestModuleProperties" production-module="intellij.android.templates" />
</module>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
templates.live.context.android=Android
8 changes: 8 additions & 0 deletions android-templates/src/META-INF/android-templates.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
<defaultLiveTemplates file="liveTemplates/AndroidParcelable"/>
<defaultLiveTemplates file="liveTemplates/AndroidXML"/>
<liveTemplateContext contextId="XML_ATTRIBUTE" implementation="com.android.tools.idea.templates.live.XmlAttributeContextType"/>
<liveTemplateContext contextId="ANDROID" implementation="com.android.tools.idea.templates.live.AndroidSourceSetTemplateContextType" />
<liveTemplateContext contextId="ANDROID_KOTLIN" baseContextId="ANDROID" implementation="com.android.tools.idea.templates.live.AndroidKotlinTemplateContextType$Generic"/>
<liveTemplateContext contextId="ANDROID_KOTLIN_TOP_LEVEL" baseContextId="ANDROID_KOTLIN" implementation="com.android.tools.idea.templates.live.AndroidKotlinTemplateContextType$TopLevel"/>
<liveTemplateContext contextId="ANDROID_KOTLIN_STATEMENT" baseContextId="ANDROID_KOTLIN" implementation="com.android.tools.idea.templates.live.AndroidKotlinTemplateContextType$Statement"/>
<liveTemplateContext contextId="ANDROID_KOTLIN_CLASS" baseContextId="ANDROID_KOTLIN" implementation="com.android.tools.idea.templates.live.AndroidKotlinTemplateContextType$Class"/>
<liveTemplateContext contextId="ANDROID_KOTLIN_EXPRESSION" baseContextId="ANDROID_KOTLIN" implementation="com.android.tools.idea.templates.live.AndroidKotlinTemplateContextType$Expression"/>
<liveTemplateContext contextId="ANDROID_KOTLIN_COMMENT" baseContextId="ANDROID_KOTLIN" implementation="com.android.tools.idea.templates.live.AndroidKotlinTemplateContextType$Comment"/>
<liveTemplateContext contextId="ANDROID_KOTLIN_OBJECT_DECLARATION" baseContextId="ANDROID_KOTLIN" implementation="com.android.tools.idea.templates.live.AndroidKotlinTemplateContextType$ObjectDeclaration"/>
</extensions>

<extensionPoints>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.android.tools.idea.templates

import com.intellij.DynamicBundle
import com.intellij.openapi.util.NlsContexts
import org.jetbrains.annotations.PropertyKey

private const val BUNDLE_NAME = "messages.TemplatesBundle"

class TemplatesBundle private constructor() {
companion object {
private val ourBundle = DynamicBundle(TemplatesBundle::class.java, BUNDLE_NAME)

@NlsContexts.Label
@JvmStatic
fun message(@PropertyKey(resourceBundle = BUNDLE_NAME) key: String, vararg params: Any?): String {
return ourBundle.getMessage(key, *params)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2020 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.idea.templates.live

import com.android.tools.idea.templates.TemplatesBundle
import com.intellij.codeInsight.template.TemplateActionContext
import com.intellij.codeInsight.template.TemplateContextType
import com.intellij.openapi.roots.ProjectFileIndex
import org.jetbrains.android.facet.AndroidFacet
import org.jetbrains.kotlin.idea.base.util.module
import org.jetbrains.kotlin.idea.liveTemplates.KotlinTemplateContextType

/**
* This [TemplateContextType] replicates the structure of [KotlinTemplateContextType],
* intersecting it with the [AndroidSourceSetTemplateContextType].
*/
internal sealed class AndroidKotlinTemplateContextType(
private val kotlin: KotlinTemplateContextType,
) : TemplateContextType(kotlin.presentableName) {
private val android = AndroidSourceSetTemplateContextType()
override fun isInContext(templateActionContext: TemplateActionContext): Boolean {
return android.isInContext(templateActionContext) && kotlin.isInContext(templateActionContext)
}

class Generic : AndroidKotlinTemplateContextType(KotlinTemplateContextType.Generic())

class TopLevel : AndroidKotlinTemplateContextType(KotlinTemplateContextType.TopLevel())

class ObjectDeclaration : AndroidKotlinTemplateContextType(KotlinTemplateContextType.ObjectDeclaration())

class Class : AndroidKotlinTemplateContextType(KotlinTemplateContextType.Class())

class Statement : AndroidKotlinTemplateContextType(KotlinTemplateContextType.Statement())

class Expression : AndroidKotlinTemplateContextType(KotlinTemplateContextType.Expression())

class Comment : AndroidKotlinTemplateContextType(KotlinTemplateContextType.Comment())
}

/**
* Checks if the template is applied to an Android-specific source set.
* This template is used to hide the Android-related templates from unrelated to Android source sets (like common, jvm, ios, etc.)
*/
internal class AndroidSourceSetTemplateContextType : TemplateContextType(
TemplatesBundle.message("templates.live.context.android")
) {
override fun isInContext(templateActionContext: TemplateActionContext): Boolean {
val file = templateActionContext.file
val module = file.module ?: ProjectFileIndex.getInstance(file.project)
.getModuleForFile(file.virtualFile ?: file.viewProvider.virtualFile)
if (module == null || module.isDisposed) return false
return AndroidFacet.getInstance(module) != null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.android.tools.idea.templates.live

import com.intellij.codeInsight.template.TemplateActionContext
import org.jetbrains.kotlin.idea.liveTemplates.KotlinTemplateContextType
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.mockito.Mockito.mock
import org.mockito.Mockito.mockConstruction
import org.mockito.Mockito.`when`
import kotlin.reflect.KClass

/**
* Unit-test for [AndroidKotlinTemplateContextType]
*/
@RunWith(Parameterized::class)
class AndroidKotlinTemplateContextTypeTest(
private val kotlinTemplateClass: KClass<out KotlinTemplateContextType>,
private val androidInContext: Boolean,
private val kotlinInContext: Boolean,
private val expectedResult: Boolean,
) {
@Test
fun test() {
val templateActionContext = mock<TemplateActionContext>()
withMockedAndroidSourceSet(templateActionContext) {
withMockedKotlinTemplate(templateActionContext) {
// Prepare
val context = AndroidKotlinTemplateContextType.Generic()

// Do
val result = context.isInContext(templateActionContext)

// Check
assertEquals(expectedResult, result)
}
}
}

private fun withMockedAndroidSourceSet(templateActionContext: TemplateActionContext, block: () -> Unit) {
mockConstruction(AndroidSourceSetTemplateContextType::class.java) { mock, _ ->
`when`(mock.isInContext(templateActionContext)).thenReturn(androidInContext)
`when`(mock.presentableName).thenReturn("name")
block()
}.close()
}

private fun withMockedKotlinTemplate(templateActionContext: TemplateActionContext, block: () -> Unit) {
mockConstruction(kotlinTemplateClass.java) { mock, _ ->
`when`(mock.isInContext(templateActionContext)).thenReturn(kotlinInContext)
`when`(mock.presentableName).thenReturn("name")
block()
}.close()
}

companion object {
@JvmStatic
@Parameterized.Parameters(name = "class={0} android={1} kotlin={2}; result={3}")
fun testData(): Array<Array<Any>> = arrayOf(
// Generic
arrayOf(KotlinTemplateContextType.Generic::class, true, true, true),
arrayOf(KotlinTemplateContextType.Generic::class, true, false, false),
arrayOf(KotlinTemplateContextType.Generic::class, false, true, false),
arrayOf(KotlinTemplateContextType.Generic::class, false, false, false),

// TopLevel
arrayOf(KotlinTemplateContextType.TopLevel::class, true, true, true),
arrayOf(KotlinTemplateContextType.TopLevel::class, true, false, false),
arrayOf(KotlinTemplateContextType.TopLevel::class, false, true, false),
arrayOf(KotlinTemplateContextType.TopLevel::class, false, false, false),

// ObjectDeclaration
arrayOf(KotlinTemplateContextType.ObjectDeclaration::class, true, true, true),
arrayOf(KotlinTemplateContextType.ObjectDeclaration::class, true, false, false),
arrayOf(KotlinTemplateContextType.ObjectDeclaration::class, false, true, false),
arrayOf(KotlinTemplateContextType.ObjectDeclaration::class, false, false, false),

// Class
arrayOf(KotlinTemplateContextType.Class::class, true, true, true),
arrayOf(KotlinTemplateContextType.Class::class, true, false, false),
arrayOf(KotlinTemplateContextType.Class::class, false, true, false),
arrayOf(KotlinTemplateContextType.Class::class, false, false, false),

// Statement
arrayOf(KotlinTemplateContextType.Statement::class, true, true, true),
arrayOf(KotlinTemplateContextType.Statement::class, true, false, false),
arrayOf(KotlinTemplateContextType.Statement::class, false, true, false),
arrayOf(KotlinTemplateContextType.Statement::class, false, false, false),

// Expression
arrayOf(KotlinTemplateContextType.Expression::class, true, true, true),
arrayOf(KotlinTemplateContextType.Expression::class, true, false, false),
arrayOf(KotlinTemplateContextType.Expression::class, false, true, false),
arrayOf(KotlinTemplateContextType.Expression::class, false, false, false),

// Comment
arrayOf(KotlinTemplateContextType.Comment::class, true, true, true),
arrayOf(KotlinTemplateContextType.Comment::class, true, false, false),
arrayOf(KotlinTemplateContextType.Comment::class, false, true, false),
arrayOf(KotlinTemplateContextType.Comment::class, false, false, false),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.android.tools.idea.templates.live

import com.intellij.codeInsight.template.TemplateActionContext
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.psi.PsiFile
import org.jetbrains.android.facet.AndroidFacet
import org.junit.AfterClass
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.BeforeClass
import org.junit.Test
import org.mockito.MockedStatic
import org.mockito.Mockito.mock
import org.mockito.Mockito.mockStatic
import org.mockito.Mockito.`when`

/**
* Unit-test for [AndroidSourceSetTemplateContextType]
*/
class AndroidSourceSetTemplateContextTypeTest {
private val context = AndroidSourceSetTemplateContextType()

@Test
fun `android facet is present`() {
// Prepare
val templateActionContext = mockedTemplateActionContext(hasAndroidFacet = true)

// Do
val result = context.isInContext(templateActionContext)

// Check
assertTrue(result)
}

@Test
fun `android facet is not present`() {
// Prepare
val templateActionContext = mockedTemplateActionContext(hasAndroidFacet = false)

// Do
val result = context.isInContext(templateActionContext)

// Check
assertFalse(result)
}

private fun mockedTemplateActionContext(hasAndroidFacet: Boolean): TemplateActionContext {
val templateActionContext = mock<TemplateActionContext>()
val file = mock<PsiFile>()
val module = mock<Module>()
`when`(templateActionContext.file).thenReturn(file)
`when`(ModuleUtilCore.findModuleForPsiElement(file)).thenReturn(module)
if (hasAndroidFacet) {
val androidFacet = mock<AndroidFacet>()
`when`(AndroidFacet.getInstance(module)).thenReturn(androidFacet)
} else {
`when`(AndroidFacet.getInstance(module)).thenReturn(null)
}
return templateActionContext
}

companion object {
private lateinit var mockedModuleUtilCore: MockedStatic<ModuleUtilCore>
private lateinit var mockedAndroidFacet: MockedStatic<AndroidFacet>

@JvmStatic
@BeforeClass
fun setUp() {
mockedModuleUtilCore = mockStatic(ModuleUtilCore::class.java)
mockedAndroidFacet = mockStatic(AndroidFacet::class.java)
}

@JvmStatic
@AfterClass
fun tearDown() {
mockedModuleUtilCore.close()
mockedAndroidFacet.close()
}
}
}
17 changes: 8 additions & 9 deletions compose-ide-plugin/resources/templates/AndroidComposePreview.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
toShortenFQNames="true">
<variable name="NAME" expression="" defaultValue="" alwaysStopAt="true"/>
<context>
<option name="KOTLIN" value="true"/>
<option name="KOTLIN_COMMENT" value="false"/>
<option name="ANDROID_KOTLIN" value="true" />
<option name="ANDROID_KOTLIN_COMMENT" value="false"/>
</context>
</template>

Expand All @@ -22,14 +22,13 @@
<variable name="TYPE" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="DATA" expression="" defaultValue="" alwaysStopAt="true"/>
<context>
<option name="KOTLIN" value="true"/>
<option name="KOTLIN_CLASS" value="false"/>
<option name="KOTLIN_COMMENT" value="false"/>
<option name="KOTLIN_EXPRESSION" value="false"/>
<option name="KOTLIN_OBJECT_DECLARATION" value="false"/>
<option name="KOTLIN_STATEMENT" value="false"/>
<option name="ANDROID_KOTLIN" value="true" />
<option name="ANDROID_KOTLIN_CLASS" value="false"/>
<option name="ANDROID_KOTLIN_COMMENT" value="false"/>
<option name="ANDROID_KOTLIN_EXPRESSION" value="false"/>
<option name="ANDROID_KOTLIN_OBJECT_DECLARATION" value="false"/>
<option name="ANDROID_KOTLIN_STATEMENT" value="false"/>
</context>
</template>
<!-- endregion -->
</templateSet>

Loading