Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate Compose Helper Plugin #1768

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.jetbrains.compose.intentions.remove_composable

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.openapi.util.Iconable
import com.intellij.psi.PsiElement
import org.jetbrains.compose.desktop.ide.preview.PreviewIcons
import org.jetbrains.compose.intentions.utils.composable_finder.ComposableFunctionFinder
import org.jetbrains.compose.intentions.utils.composable_finder.ComposableFunctionFinderImpl
import org.jetbrains.compose.intentions.utils.get_root_psi_element.GetRootPsiElement
import org.jetbrains.compose.intentions.utils.is_intention_available.IsIntentionAvailable
import javax.swing.Icon

class RemoveComposableIntention :
PsiElementBaseIntentionAction(),
Iconable,
LowPriorityAction,
IsIntentionAvailable {

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.isAvailable(composableFunctionFinder)
}

override fun invoke(project: Project, editor: Editor?, element: PsiElement) {
getRootElement(element.parent)?.delete()
}

override fun getIcon(flags: Int): Icon = PreviewIcons.COMPOSE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.jetbrains.compose.intentions.remove_parent_composable

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 org.jetbrains.compose.desktop.ide.preview.PreviewIcons
import org.jetbrains.compose.intentions.utils.composable_finder.ChildComposableFinder
import org.jetbrains.compose.intentions.utils.composable_finder.ComposableFunctionFinder
import org.jetbrains.compose.intentions.utils.get_root_psi_element.GetRootPsiElement
import org.jetbrains.compose.intentions.utils.is_intention_available.IsIntentionAvailable
import org.jetbrains.kotlin.psi.KtCallExpression
import javax.swing.Icon

class RemoveParentComposableIntention :
PsiElementBaseIntentionAction(),
Iconable,
PriorityAction,
IsIntentionAvailable {

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.isAvailable(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 getIcon(flags: Int): Icon = PreviewIcons.COMPOSE

override fun getPriority(): PriorityAction.Priority {
return PriorityAction.Priority.NORMAL
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.jetbrains.compose.intentions.utils.composable_finder

import com.intellij.psi.PsiElement
import org.jetbrains.compose.intentions.utils.is_psi_element_composable.IsPsiElementComposable
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtLambdaArgument
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType

class ChildComposableFinder : ComposableFunctionFinder, IsPsiElementComposable {

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()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.jetbrains.compose.intentions.utils.composable_finder

import com.intellij.psi.PsiElement

interface ComposableFunctionFinder {
fun isFunctionComposable(psiElement: PsiElement): Boolean
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.jetbrains.compose.intentions.utils.composable_finder

import com.intellij.psi.PsiElement
import org.jetbrains.compose.intentions.utils.is_psi_element_composable.IsPsiElementComposable
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, IsPsiElementComposable {

override fun isFunctionComposable(psiElement: PsiElement): Boolean {
return when (psiElement) {
is KtNameReferenceExpression -> psiElement.isComposable()
is KtCallExpression -> 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()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.jetbrains.compose.intentions.utils.get_root_psi_element

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, iteration: Int = 0): PsiElement? {
// To avoid infinite loops
if (iteration > 5) {
// Looking for a better way to handle this - throw error or return null
return null
}

return when (element) {
is KtProperty -> element
is KtNameReferenceExpression,
is KtValueArgumentList -> invoke(element.parent, iteration + 1)
is KtDotQualifiedExpression,
is KtCallExpression -> {
when (element.parent) {
is KtProperty,
is KtDotQualifiedExpression -> invoke(element.parent, iteration + 1) // composable dot expression
is KtPropertyDelegate -> invoke(element.parent.parent, iteration + 1) // composable dot expression
else -> element
}
}
else -> null
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.jetbrains.compose.intentions.utils.is_intention_available

import com.intellij.psi.PsiElement
import org.jetbrains.compose.intentions.utils.composable_finder.ComposableFunctionFinder
import org.jetbrains.kotlin.idea.KotlinLanguage

interface IsIntentionAvailable {

fun PsiElement.isAvailable(
composableFunctionFinder: ComposableFunctionFinder
): Boolean {
if (language != KotlinLanguage.INSTANCE) {
return false
}

if (!isWritable) {
return false
}

return parent?.let { parentPsiElement ->
composableFunctionFinder.isFunctionComposable(parentPsiElement)
} ?: false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.jetbrains.compose.intentions.utils.is_psi_element_composable

import org.jetbrains.compose.desktop.ide.preview.isComposableFunction
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

interface IsPsiElementComposable {

fun KtCallExpression.isComposable(): Boolean {
return getChildOfType<KtNameReferenceExpression>()?.isComposable() ?: false
}

fun KtNameReferenceExpression.isComposable(): Boolean {
val ktNamedFunction = resolve() as? KtNamedFunction ?: return false
return ktNamedFunction.isComposableFunction()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package org.jetbrains.compose.intentions.wrap_with_composable

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.openapi.util.Iconable
import com.intellij.psi.PsiFile
import com.intellij.ui.popup.list.ListPopupImpl
import org.jetbrains.compose.desktop.ide.preview.PreviewIcons
import org.jetbrains.compose.intentions.wrap_with_composable.wrap_with_actions.BaseWrapWithComposableAction
import org.jetbrains.compose.intentions.wrap_with_composable.wrap_with_actions.WrapWithBoxIntention
import org.jetbrains.compose.intentions.wrap_with_composable.wrap_with_actions.WrapWithCardIntention
import org.jetbrains.compose.intentions.wrap_with_composable.wrap_with_actions.WrapWithColumnIntention
import org.jetbrains.compose.intentions.wrap_with_composable.wrap_with_actions.WrapWithLzyColumnIntention
import org.jetbrains.compose.intentions.wrap_with_composable.wrap_with_actions.WrapWithLzyRowIntention
import org.jetbrains.compose.intentions.wrap_with_composable.wrap_with_actions.WrapWithRowIntention
import javax.swing.Icon

class WrapWithComposableIntentionGroup :
IntentionActionGroup<BaseWrapWithComposableAction>(
listOf(
WrapWithBoxIntention(),
WrapWithCardIntention(),
WrapWithColumnIntention(),
WrapWithRowIntention(),
WrapWithLzyColumnIntention(),
WrapWithLzyRowIntention()
)
),
Iconable {

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"
}

override fun getIcon(flags: Int): Icon = PreviewIcons.COMPOSE
}
Loading