Browse Source

Merge a72c049204 into e2f43ed3ee

pull/1768/merge
Piyush Pradeepkumar 2 weeks ago committed by GitHub
parent
commit
8100ca7409
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 36
      idea-plugin/src/main/kotlin/org/jetbrains/compose/intentions/RemoveComposableIntention.kt
  2. 48
      idea-plugin/src/main/kotlin/org/jetbrains/compose/intentions/RemoveParentComposableIntention.kt
  3. 67
      idea-plugin/src/main/kotlin/org/jetbrains/compose/intentions/WrapWithComposableIntentionGroup.kt
  4. 36
      idea-plugin/src/main/kotlin/org/jetbrains/compose/intentions/utils/PsiUtils.kt
  5. 33
      idea-plugin/src/main/kotlin/org/jetbrains/compose/intentions/utils/composableFinder/ChildComposableFinder.kt
  6. 7
      idea-plugin/src/main/kotlin/org/jetbrains/compose/intentions/utils/composableFinder/ComposableFunctionFinder.kt
  7. 40
      idea-plugin/src/main/kotlin/org/jetbrains/compose/intentions/utils/composableFinder/ComposableFunctionFinderImpl.kt
  8. 40
      idea-plugin/src/main/kotlin/org/jetbrains/compose/intentions/utils/getRootPsiElement/GetRootPsiElement.kt
  9. 53
      idea-plugin/src/main/kotlin/org/jetbrains/compose/intentions/wrapActions/BaseWrapWithComposableAction.kt
  10. 70
      idea-plugin/src/main/kotlin/org/jetbrains/compose/intentions/wrapActions/WrapWithActions.kt
  11. 26
      idea-plugin/src/main/resources/META-INF/plugin.xml
  12. 4
      idea-plugin/src/main/resources/intentionDescriptions/RemoveComposableIntention/after.kt.template
  13. 4
      idea-plugin/src/main/resources/intentionDescriptions/RemoveComposableIntention/before.kt.template
  14. 7
      idea-plugin/src/main/resources/intentionDescriptions/RemoveComposableIntention/description.html
  15. 4
      idea-plugin/src/main/resources/intentionDescriptions/RemoveParentComposableIntention/after.kt.template
  16. 6
      idea-plugin/src/main/resources/intentionDescriptions/RemoveParentComposableIntention/before.kt.template
  17. 7
      idea-plugin/src/main/resources/intentionDescriptions/RemoveParentComposableIntention/description.html
  18. 15
      idea-plugin/src/main/resources/intentionDescriptions/WrapWithComposableIntentionGroup/description.html
  19. 71
      idea-plugin/src/main/resources/templates/ComposeMultiplatformTemplates.xml
  20. 307
      idea-plugin/src/test/kotlin/org/jetbrains/compose/intentions/utils/getRootPsiElement/GetRootPsiElementTest.kt

36
idea-plugin/src/main/kotlin/org/jetbrains/compose/intentions/RemoveComposableIntention.kt

@ -0,0 +1,36 @@
package org.jetbrains.compose.intentions
import com.intellij.codeInsight.intention.LowPriorityAction
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import org.jetbrains.compose.intentions.utils.composableFinder.ComposableFunctionFinder
import org.jetbrains.compose.intentions.utils.composableFinder.ComposableFunctionFinderImpl
import org.jetbrains.compose.intentions.utils.getRootPsiElement.GetRootPsiElement
import org.jetbrains.compose.intentions.utils.isIntentionAvailable
class RemoveComposableIntention :
PsiElementBaseIntentionAction(),
LowPriorityAction {
override fun getText(): String {
return "Remove this Composable"
}
override fun getFamilyName(): String {
return "Compose Multiplatform intentions"
}
private val composableFunctionFinder: ComposableFunctionFinder = ComposableFunctionFinderImpl()
private val getRootElement = GetRootPsiElement()
override fun isAvailable(project: Project, editor: Editor?, element: PsiElement): Boolean {
return element.isIntentionAvailable(composableFunctionFinder)
}
override fun invoke(project: Project, editor: Editor?, element: PsiElement) {
getRootElement(element.parent)?.delete()
}
}

48
idea-plugin/src/main/kotlin/org/jetbrains/compose/intentions/RemoveParentComposableIntention.kt

@ -0,0 +1,48 @@
package org.jetbrains.compose.intentions
import com.intellij.codeInsight.intention.PriorityAction
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Iconable
import com.intellij.psi.PsiElement
import javax.swing.Icon
import org.jetbrains.compose.desktop.ide.preview.PreviewIcons
import org.jetbrains.compose.intentions.utils.composableFinder.ChildComposableFinder
import org.jetbrains.compose.intentions.utils.composableFinder.ComposableFunctionFinder
import org.jetbrains.compose.intentions.utils.getRootPsiElement.GetRootPsiElement
import org.jetbrains.compose.intentions.utils.isIntentionAvailable
import org.jetbrains.kotlin.psi.KtCallExpression
class RemoveParentComposableIntention :
PsiElementBaseIntentionAction(),
PriorityAction {
override fun getText(): String {
return "Remove the parent Composable"
}
override fun getFamilyName(): String {
return "Compose Multiplatform intentions"
}
private val getRootElement = GetRootPsiElement()
private val composableFunctionFinder: ComposableFunctionFinder = ChildComposableFinder()
override fun isAvailable(project: Project, editor: Editor?, element: PsiElement): Boolean {
return element.isIntentionAvailable(composableFunctionFinder)
}
override fun invoke(project: Project, editor: Editor?, element: PsiElement) {
val callExpression = getRootElement(element.parent) as? KtCallExpression ?: return
val lambdaBlock =
callExpression.lambdaArguments.firstOrNull()?.getLambdaExpression()?.functionLiteral?.bodyExpression
?: return
callExpression.replace(lambdaBlock)
}
override fun getPriority(): PriorityAction.Priority {
return PriorityAction.Priority.NORMAL
}
}

67
idea-plugin/src/main/kotlin/org/jetbrains/compose/intentions/WrapWithComposableIntentionGroup.kt

@ -0,0 +1,67 @@
package org.jetbrains.compose.intentions
import com.intellij.codeInsight.intention.impl.IntentionActionGroup
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.popup.ListPopup
import com.intellij.openapi.ui.popup.PopupStep
import com.intellij.openapi.ui.popup.util.BaseListPopupStep
import com.intellij.psi.PsiFile
import com.intellij.ui.popup.list.ListPopupImpl
import org.jetbrains.compose.intentions.wrapActions.BaseWrapWithComposableAction
import org.jetbrains.compose.intentions.wrapActions.WrapWithBoxIntention
import org.jetbrains.compose.intentions.wrapActions.WrapWithCardIntention
import org.jetbrains.compose.intentions.wrapActions.WrapWithColumnIntention
import org.jetbrains.compose.intentions.wrapActions.WrapWithLzyColumnIntention
import org.jetbrains.compose.intentions.wrapActions.WrapWithLzyRowIntention
import org.jetbrains.compose.intentions.wrapActions.WrapWithRowIntention
class WrapWithComposableIntentionGroup :
IntentionActionGroup<BaseWrapWithComposableAction>(
listOf(
WrapWithBoxIntention(),
WrapWithCardIntention(),
WrapWithColumnIntention(),
WrapWithRowIntention(),
WrapWithLzyColumnIntention(),
WrapWithLzyRowIntention()
)
) {
private fun createPopup(
project: Project,
actions: List<BaseWrapWithComposableAction>,
invokeAction: (BaseWrapWithComposableAction) -> Unit
): ListPopup {
val step = object : BaseListPopupStep<BaseWrapWithComposableAction>(null, actions) {
override fun getTextFor(action: BaseWrapWithComposableAction) = action.text
override fun onChosen(selectedValue: BaseWrapWithComposableAction, finalChoice: Boolean): PopupStep<*>? {
invokeAction(selectedValue)
return FINAL_CHOICE
}
}
return ListPopupImpl(project, step)
}
override fun getFamilyName(): String {
return "Compose Multiplatform intentions"
}
override fun chooseAction(
project: Project,
editor: Editor,
file: PsiFile,
actions: List<BaseWrapWithComposableAction>,
invokeAction: (BaseWrapWithComposableAction) -> Unit
) {
createPopup(project, actions, invokeAction).showInBestPositionFor(editor)
}
override fun getGroupText(actions: List<BaseWrapWithComposableAction>): String {
return "Wrap with Composable"
}
}

36
idea-plugin/src/main/kotlin/org/jetbrains/compose/intentions/utils/PsiUtils.kt

@ -0,0 +1,36 @@
package org.jetbrains.compose.intentions.utils
import com.intellij.psi.PsiElement
import org.jetbrains.compose.desktop.ide.preview.isComposableFunction
import org.jetbrains.compose.intentions.utils.composableFinder.ComposableFunctionFinder
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.nj2k.postProcessing.resolve
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
internal fun KtCallExpression.isComposable(): Boolean {
return getChildOfType<KtNameReferenceExpression>()?.isComposable() ?: false
}
internal fun KtNameReferenceExpression.isComposable(): Boolean {
val ktNamedFunction = resolve() as? KtNamedFunction ?: return false
return ktNamedFunction.isComposableFunction()
}
internal fun PsiElement.isIntentionAvailable(
composableFunctionFinder: ComposableFunctionFinder
): Boolean {
if (language != KotlinLanguage.INSTANCE) {
return false
}
if (!isWritable) {
return false
}
return parent?.let { parentPsiElement ->
composableFunctionFinder.isFunctionComposable(parentPsiElement)
} ?: false
}

33
idea-plugin/src/main/kotlin/org/jetbrains/compose/intentions/utils/composableFinder/ChildComposableFinder.kt

@ -0,0 +1,33 @@
package org.jetbrains.compose.intentions.utils.composableFinder
import com.intellij.psi.PsiElement
import org.jetbrains.compose.intentions.utils.isComposable
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtLambdaArgument
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
class ChildComposableFinder : ComposableFunctionFinder {
override fun isFunctionComposable(psiElement: PsiElement): Boolean {
if (psiElement is KtCallExpression) {
psiElement.getChildOfType<KtLambdaArgument>()?.let { lambdaChild ->
return getComposableFromChildLambda(lambdaChild)
}
}
if (psiElement.parent is KtCallExpression) {
psiElement.parent.getChildOfType<KtLambdaArgument>()?.let { lambdaChild ->
return getComposableFromChildLambda(lambdaChild)
}
}
return false
}
private fun getComposableFromChildLambda(lambdaArgument: KtLambdaArgument): Boolean {
val bodyExpression = lambdaArgument.getLambdaExpression()?.functionLiteral?.bodyExpression
val ktCallExpression = bodyExpression?.getChildOfType<KtCallExpression>() ?: return false
return ktCallExpression.isComposable()
}
}

7
idea-plugin/src/main/kotlin/org/jetbrains/compose/intentions/utils/composableFinder/ComposableFunctionFinder.kt

@ -0,0 +1,7 @@
package org.jetbrains.compose.intentions.utils.composableFinder
import com.intellij.psi.PsiElement
interface ComposableFunctionFinder {
fun isFunctionComposable(psiElement: PsiElement): Boolean
}

40
idea-plugin/src/main/kotlin/org/jetbrains/compose/intentions/utils/composableFinder/ComposableFunctionFinderImpl.kt

@ -0,0 +1,40 @@
package org.jetbrains.compose.intentions.utils.composableFinder
import com.intellij.psi.PsiElement
import org.jetbrains.compose.intentions.utils.isComposable
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPropertyDelegate
import org.jetbrains.kotlin.psi.KtValueArgumentList
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
class ComposableFunctionFinderImpl : ComposableFunctionFinder {
override fun isFunctionComposable(psiElement: PsiElement): Boolean {
return when (psiElement) {
is KtNameReferenceExpression -> psiElement.isComposable()
is KtProperty -> detectComposableFromKtProperty(psiElement)
is KtValueArgumentList -> {
val parent = psiElement.parent as? KtCallExpression ?: return false
parent.isComposable()
}
else -> false
}
}
/**
* To handle both property and property delegates
*/
private fun detectComposableFromKtProperty(psiElement: KtProperty): Boolean {
psiElement.getChildOfType<KtCallExpression>().let { propertyChildExpression ->
return if (propertyChildExpression == null) {
val propertyDelegate = psiElement.getChildOfType<KtPropertyDelegate>() ?: return false
val ktCallExpression = propertyDelegate.getChildOfType<KtCallExpression>() ?: return false
ktCallExpression.isComposable()
} else {
propertyChildExpression.isComposable()
}
}
}
}

40
idea-plugin/src/main/kotlin/org/jetbrains/compose/intentions/utils/getRootPsiElement/GetRootPsiElement.kt

@ -0,0 +1,40 @@
package org.jetbrains.compose.intentions.utils.getRootPsiElement
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPropertyDelegate
import org.jetbrains.kotlin.psi.KtValueArgumentList
/**
* To get the root element of a selected Psi element
*/
class GetRootPsiElement {
/**
* @param element can be
* 1. KtCallExpression, KtNameReferenceExpression - Box()
* 2. KtDotQualifiedExpression - repeatingAnimation.animateFloat
* 3. KtProperty - val systemUiController = rememberSystemUiController()
* 4. KtValueArgumentList - ()
*/
tailrec operator fun invoke(element: PsiElement): PsiElement? {
return when (element) {
is KtProperty -> element
is KtNameReferenceExpression,
is KtValueArgumentList -> invoke(element.parent)
is KtDotQualifiedExpression,
is KtCallExpression -> {
when (element.parent) {
is KtProperty,
is KtDotQualifiedExpression -> invoke(element.parent) // composable dot expression
is KtPropertyDelegate -> invoke(element.parent.parent) // composable dot expression
else -> element
}
}
else -> null
}
}
}

53
idea-plugin/src/main/kotlin/org/jetbrains/compose/intentions/wrapActions/BaseWrapWithComposableAction.kt

@ -0,0 +1,53 @@
package org.jetbrains.compose.intentions.wrapActions
import com.intellij.codeInsight.intention.HighPriorityAction
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction
import com.intellij.codeInsight.template.impl.InvokeTemplateAction
import com.intellij.codeInsight.template.impl.TemplateImpl
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import org.jetbrains.compose.intentions.utils.composableFinder.ComposableFunctionFinder
import org.jetbrains.compose.intentions.utils.composableFinder.ComposableFunctionFinderImpl
import org.jetbrains.compose.intentions.utils.getRootPsiElement.GetRootPsiElement
import org.jetbrains.compose.intentions.utils.isIntentionAvailable
abstract class BaseWrapWithComposableAction :
PsiElementBaseIntentionAction(),
HighPriorityAction {
private val composableFunctionFinder: ComposableFunctionFinder by lazy {
ComposableFunctionFinderImpl()
}
private val getRootElement by lazy {
GetRootPsiElement()
}
override fun getFamilyName(): String {
return "Compose Multiplatform intentions"
}
override fun isAvailable(project: Project, editor: Editor?, element: PsiElement): Boolean {
return element.isIntentionAvailable(composableFunctionFinder)
}
override fun startInWriteAction(): Boolean = true
override fun invoke(project: Project, editor: Editor?, element: PsiElement) {
getRootElement(element.parent)?.let { rootElement ->
val selectionModel = editor!!.selectionModel
val textRange = rootElement.textRange
selectionModel.setSelection(textRange.startOffset, textRange.endOffset)
InvokeTemplateAction(
getTemplate(),
editor,
project,
HashSet()
).perform()
}
}
protected abstract fun getTemplate(): TemplateImpl?
}

70
idea-plugin/src/main/kotlin/org/jetbrains/compose/intentions/wrapActions/WrapWithActions.kt

@ -0,0 +1,70 @@
package org.jetbrains.compose.intentions.wrapActions
import com.intellij.codeInsight.template.impl.TemplateImpl
import com.intellij.codeInsight.template.impl.TemplateSettings
class WrapWithBoxIntention : BaseWrapWithComposableAction() {
override fun getText(): String {
return "Wrap with Box"
}
override fun getTemplate(): TemplateImpl? {
return TemplateSettings.getInstance().getTemplate("boxcomp", "ComposeMultiplatformTemplates")
}
}
class WrapWithCardIntention : BaseWrapWithComposableAction() {
override fun getText(): String {
return "Wrap with Card"
}
override fun getTemplate(): TemplateImpl? {
return TemplateSettings.getInstance().getTemplate("cardcomp", "ComposeMultiplatformTemplates")
}
}
class WrapWithColumnIntention : BaseWrapWithComposableAction() {
override fun getText(): String {
return "Wrap with Column"
}
override fun getTemplate(): TemplateImpl? {
return TemplateSettings.getInstance().getTemplate("columncomp", "ComposeMultiplatformTemplates")
}
}
class WrapWithRowIntention : BaseWrapWithComposableAction() {
override fun getText(): String {
return "Wrap with Row"
}
override fun getTemplate(): TemplateImpl? {
return TemplateSettings.getInstance().getTemplate("rowcomp", "ComposeMultiplatformTemplates")
}
}
class WrapWithLzyColumnIntention : BaseWrapWithComposableAction() {
override fun getText(): String {
return "Wrap with LazyColumn"
}
override fun getTemplate(): TemplateImpl? {
return TemplateSettings.getInstance().getTemplate("lazycolumncomp", "ComposeMultiplatformTemplates")
}
}
class WrapWithLzyRowIntention : BaseWrapWithComposableAction() {
override fun getText(): String {
return "Wrap with LazyRow"
}
override fun getTemplate(): TemplateImpl? {
return TemplateSettings.getInstance().getTemplate("lazyrowcomp", "ComposeMultiplatformTemplates")
}
}

26
idea-plugin/src/main/resources/META-INF/plugin.xml

@ -52,4 +52,30 @@
<action class="org.jetbrains.compose.desktop.ide.preview.RefreshOrRunPreviewAction"/>
</group>
</actions>
<extensions defaultExtensionNs="com.intellij">
<defaultLiveTemplates file="templates/ComposeMultiplatformTemplates.xml"/>
<intentionAction>
<className>org.jetbrains.compose.intentions.WrapWithComposableIntentionGroup
</className>
<category>Compose Multiplatform</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.compose.intentions.RemoveComposableIntention
</className>
<category>Compose Multiplatform</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.compose.intentions.RemoveParentComposableIntention
</className>
<category>Compose Multiplatform</category>
</intentionAction>
</extensions>
</idea-plugin>

4
idea-plugin/src/main/resources/intentionDescriptions/RemoveComposableIntention/after.kt.template

@ -0,0 +1,4 @@
@Composable
fun Column() {
}

4
idea-plugin/src/main/resources/intentionDescriptions/RemoveComposableIntention/before.kt.template

@ -0,0 +1,4 @@
@Composable
fun Column() {
Text("Abc")
}

7
idea-plugin/src/main/resources/intentionDescriptions/RemoveComposableIntention/description.html

@ -0,0 +1,7 @@
<html lang="en">
<body>
<p>
A simple intention to remove a Composable altogether.
</p>
</body>
</html>

4
idea-plugin/src/main/resources/intentionDescriptions/RemoveParentComposableIntention/after.kt.template

@ -0,0 +1,4 @@
@Composable
fun Column() {
Text("Abc")
}

6
idea-plugin/src/main/resources/intentionDescriptions/RemoveParentComposableIntention/before.kt.template

@ -0,0 +1,6 @@
@Composable
fun Column() {
Button(){
Text("Abc")
}
}

7
idea-plugin/src/main/resources/intentionDescriptions/RemoveParentComposableIntention/description.html

@ -0,0 +1,7 @@
<html lang="en">
<body>
<p>
An intention to remove a parent Composable, and unwrap its children.
</p>
</body>
</html>

15
idea-plugin/src/main/resources/intentionDescriptions/WrapWithComposableIntentionGroup/description.html

@ -0,0 +1,15 @@
<html lang="en">
<body>
<p>
A simple intention to wrap your <a href="https://developer.android.com/jetpack/compose/">Composables</a> with another
Composable. Just keep your caret in the Editor on the composable, and press on the yellow bulb on the left, or press
Alt+Enter to show hints/intentions. You can choose to -
1. Wrap with Box
2. Wrap with Card
3. Wrap with Column
4. Wrap with Row
5. Wrap with LazyColumn
6. Wrap with LazyRow
</p>
</body>
</html>

71
idea-plugin/src/main/resources/templates/ComposeMultiplatformTemplates.xml

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<templateSet group="ComposeMultiplatformTemplates">
<template name="boxcomp" description="Insert Box Composable"
value="$COMPOSABLE$ {&#10; $SELECTION$ &#10; }"
toReformat="true" toShortenFQNames="true">
<variable name="COMPOSABLE" expression="" defaultValue="&quot;androidx.compose.foundation.layout.Box&quot;"
alwaysStopAt="true"/>
<context>
<option name="KOTLIN" value="true"/>
<option name="KOTLIN_COMMENT" value="false"/>
</context>
</template>
<template name="cardcomp" description="Insert Card Composable"
value="$COMPOSABLE$ {&#10; $SELECTION$ &#10; }"
toReformat="true" toShortenFQNames="true">
<variable name="COMPOSABLE" expression="" defaultValue="&quot;androidx.compose.material.Card&quot;"
alwaysStopAt="true"/>
<context>
<option name="KOTLIN" value="true"/>
<option name="KOTLIN_COMMENT" value="false"/>
</context>
</template>
<template name="columncomp" description="Insert Column Composable"
value="$COMPOSABLE$ {&#10; $SELECTION$ &#10; }"
toReformat="true" toShortenFQNames="true">
<variable name="COMPOSABLE" expression="" defaultValue="&quot;androidx.compose.foundation.layout.Column&quot;"
alwaysStopAt="true"/>
<context>
<option name="KOTLIN" value="true"/>
<option name="KOTLIN_COMMENT" value="false"/>
</context>
</template>
<template name="rowcomp" description="Insert Row Composable"
value="$COMPOSABLE$ {&#10; $SELECTION$ &#10; }"
toReformat="true" toShortenFQNames="true">
<variable name="COMPOSABLE" expression="" defaultValue="&quot;androidx.compose.foundation.layout.Row&quot;"
alwaysStopAt="true"/>
<context>
<option name="KOTLIN" value="true"/>
<option name="KOTLIN_COMMENT" value="false"/>
</context>
</template>
<template name="lazycolumncomp" description="Insert Lazy Column Composable"
value="$COMPOSABLE$ { item {&#10; $SELECTION$ &#10;} }"
toReformat="true" toShortenFQNames="true">
<variable name="COMPOSABLE" expression="" defaultValue="&quot;androidx.compose.foundation.lazy.LazyColumn&quot;"
alwaysStopAt="true"/>
<context>
<option name="KOTLIN" value="true"/>
<option name="KOTLIN_COMMENT" value="false"/>
</context>
</template>
<template name="lazyrowcomp" description="Insert Lazy Row Composable"
value="$COMPOSABLE$ { item {&#10; $SELECTION$ &#10;} }"
toReformat="true" toShortenFQNames="true">
<variable name="COMPOSABLE" expression="" defaultValue="&quot;androidx.compose.foundation.lazy.LazyRow&quot;"
alwaysStopAt="true"/>
<context>
<option name="KOTLIN" value="true"/>
<option name="KOTLIN_COMMENT" value="false"/>
</context>
</template>
</templateSet>

307
idea-plugin/src/test/kotlin/org/jetbrains/compose/intentions/utils/getRootPsiElement/GetRootPsiElementTest.kt

@ -0,0 +1,307 @@
package org.jetbrains.compose.intentions.utils.getRootPsiElement
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase
import junit.framework.TestCase
import org.intellij.lang.annotations.Language
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtValueArgumentList
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@RunWith(JUnit4::class)
class GetRootPsiElementTest : LightJavaCodeInsightFixtureTestCase() {
private val getRootElement = GetRootPsiElement()
@Test
fun `when a name reference expression is selected , but root is a property , the property should be returned`() {
val ktPsiFactory = KtPsiFactory(project)
@Language("Kotlin")
val template = """
val systemUiController = rememberSystemUiController()
""".trimIndent()
val file = ktPsiFactory.createFile(template)
val property = file.lastChild as KtProperty
val ktNameReferenceExpression = (property.lastChild as KtCallExpression).firstChild as KtNameReferenceExpression
TestCase.assertEquals("rememberSystemUiController", ktNameReferenceExpression.text)
TestCase.assertEquals(property, getRootElement.invoke(ktNameReferenceExpression))
}
@Test
fun `when a name reference expression is selected, with a call expression as root, call expression should be returned`() {
val ktPsiFactory = KtPsiFactory(project)
@Language("Kotlin")
val template = """
fun Box(block:()->Unit) {
}
fun OuterComposable() {
Box() {
}
}
""".trimIndent().trim()
val file = ktPsiFactory.createFile(template)
val ktNamedFunction = file.lastChild as KtNamedFunction
val callExpression = ktNamedFunction.lastChild.children.find { it is KtCallExpression }!!
val ktNameReferenceExpression = callExpression.firstChild as KtNameReferenceExpression
TestCase.assertEquals("Box", ktNameReferenceExpression.text)
TestCase.assertEquals(
callExpression,
getRootElement.invoke(ktNameReferenceExpression)
)
}
@Test
fun `when an argument list element is selected, with a call expression as root, call expression should be returned`() {
val ktPsiFactory = KtPsiFactory(project)
@Language("Kotlin")
val template = """
@Composable
fun Box(block:()->Unit) {
}
fun OuterComposable() {
// Argument List Element
// |
// v
Box() {
}
}
""".trimIndent().trim()
val file = ktPsiFactory.createFile(template)
val ktNamedFunction = file.lastChild as KtNamedFunction
val callExpression = ktNamedFunction.lastChild.children.find { it is KtCallExpression }!!
val argumentListElement = callExpression.firstChild.nextSibling as KtValueArgumentList
TestCase.assertEquals("()", argumentListElement.text)
TestCase.assertEquals(
callExpression,
getRootElement.invoke(argumentListElement)
)
}
@Test
fun `when a name reference expression is selected, with a delegated property as root, property should be returned`() {
val ktPsiFactory = KtPsiFactory(project)
@Language("Kotlin")
val template = """
var isComposable by remember {
true
}
""".trimIndent().trim()
val file = ktPsiFactory.createFile(template)
val property = file.lastChild as KtProperty
val referenceExpression = property.lastChild.lastChild.firstChild as KtNameReferenceExpression
TestCase.assertEquals("remember", referenceExpression.text)
TestCase.assertEquals(
property,
getRootElement.invoke(referenceExpression)
)
}
@Test
fun `when a name reference expression with dot reference expression is selected, with a delegated property as root, property should be returned`() {
val ktPsiFactory = KtPsiFactory(project)
@Language("Kotlin")
val template = """
val repeatingAnimation = rememberInfiniteTransition()
val offset by repeatingAnimation.animateFloat(
0f,
-20f,
infiniteRepeatable(
repeatMode = RepeatMode.Reverse,
animation = tween(
durationMillis = 1000,
easing = LinearEasing
)
)
)
""".trimIndent().trim()
val file = ktPsiFactory.createFile(template)
val property = file.lastChild as KtProperty
val dotQualifiedExpression = property.lastChild.lastChild as KtDotQualifiedExpression
val referenceExpression = dotQualifiedExpression.lastChild.firstChild as KtNameReferenceExpression
TestCase.assertEquals("animateFloat", referenceExpression.text)
TestCase.assertEquals(
property,
getRootElement.invoke(referenceExpression)
)
}
@Test
fun `when a name reference expression with dot reference expression is selected, with a property and dot qualified expression as root, property should be returned`() {
val ktPsiFactory = KtPsiFactory(project)
@Language("Kotlin")
val template = """
val repeatingAnimation = rememberInfiniteTransition()
val offset = repeatingAnimation.animateFloat(
0f,
-20f,
infiniteRepeatable(
repeatMode = RepeatMode.Reverse,
animation = tween(
durationMillis = 1000,
easing = LinearEasing
)
)
)
""".trimIndent().trim()
val file = ktPsiFactory.createFile(template)
val property = file.lastChild as KtProperty
val dotQualifiedExpression = property.lastChild as KtDotQualifiedExpression
val referenceExpression = dotQualifiedExpression.lastChild.firstChild as KtNameReferenceExpression
TestCase.assertEquals("animateFloat", referenceExpression.text)
TestCase.assertEquals(
property,
getRootElement.invoke(referenceExpression)
)
}
@Test
fun `when a dot qualified expression is selected, with a delegated property as root, property should be returned`() {
val ktPsiFactory = KtPsiFactory(project)
@Language("Kotlin")
val template = """
val repeatingAnimation = rememberInfiniteTransition()
val offset by repeatingAnimation.animateFloat(
0f,
-20f,
infiniteRepeatable(
repeatMode = RepeatMode.Reverse,
animation = tween(
durationMillis = 1000,
easing = LinearEasing
)
)
)
""".trimIndent().trim()
val file = ktPsiFactory.createFile(template)
val property = file.lastChild as KtProperty
val dotQualifiedExpression = property.lastChild.lastChild as KtDotQualifiedExpression
TestCase.assertEquals(
"""
repeatingAnimation.animateFloat(
0f,
-20f,
infiniteRepeatable(
repeatMode = RepeatMode.Reverse,
animation = tween(
durationMillis = 1000,
easing = LinearEasing
)
)
)
""".trimIndent().trim(), dotQualifiedExpression.text
)
TestCase.assertEquals(
property,
getRootElement.invoke(dotQualifiedExpression)
)
}
@Test
fun `when a dot qualified expression is selected, with a property as root, property should be returned`() {
val ktPsiFactory = KtPsiFactory(project)
@Language("Kotlin")
val template = """
val repeatingAnimation = rememberInfiniteTransition()
val offset = repeatingAnimation.animateFloat(
0f,
-20f,
infiniteRepeatable(
repeatMode = RepeatMode.Reverse,
animation = tween(
durationMillis = 1000,
easing = LinearEasing
)
)
)
""".trimIndent().trim()
val file = ktPsiFactory.createFile(template)
val property = file.lastChild as KtProperty
val dotQualifiedExpression = property.lastChild as KtDotQualifiedExpression
TestCase.assertEquals(
"""
repeatingAnimation.animateFloat(
0f,
-20f,
infiniteRepeatable(
repeatMode = RepeatMode.Reverse,
animation = tween(
durationMillis = 1000,
easing = LinearEasing
)
)
)
""".trimIndent().trim(), dotQualifiedExpression.text
)
TestCase.assertEquals(
property,
getRootElement.invoke(dotQualifiedExpression)
)
}
}
Loading…
Cancel
Save