thelumiereguy
3 years ago
21 changed files with 812 additions and 0 deletions
@ -0,0 +1,40 @@ |
|||||||
|
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 javax.swing.Icon |
||||||
|
import org.jetbrains.compose.desktop.ide.preview.PreviewIcons |
||||||
|
import org.jetbrains.compose.intentions.utils.is_intention_available.IsIntentionAvailable |
||||||
|
import org.jetbrains.compose.intentions.utils.composable_finder.ComposableFunctionFinder |
||||||
|
import org.jetbrains.compose.intentions.utils.composable_finder.DeepComposableFunctionFinder |
||||||
|
import org.jetbrains.compose.intentions.utils.get_root_element.GetRootElement |
||||||
|
|
||||||
|
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 = DeepComposableFunctionFinder() |
||||||
|
|
||||||
|
private val getRootElement = GetRootElement() |
||||||
|
|
||||||
|
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 |
||||||
|
|
||||||
|
} |
@ -0,0 +1,56 @@ |
|||||||
|
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 com.intellij.psi.util.parentOfType |
||||||
|
import javax.swing.Icon |
||||||
|
import org.jetbrains.compose.desktop.ide.preview.PreviewIcons |
||||||
|
import org.jetbrains.compose.intentions.utils.is_intention_available.IsIntentionAvailable |
||||||
|
import org.jetbrains.compose.intentions.utils.composable_finder.ComposableFunctionFinder |
||||||
|
import org.jetbrains.compose.intentions.utils.composable_finder.NestedComposableFinder |
||||||
|
import org.jetbrains.kotlin.psi.KtCallExpression |
||||||
|
import org.jetbrains.kotlin.psi.KtNameReferenceExpression |
||||||
|
import org.jetbrains.kotlin.psi.KtValueArgumentList |
||||||
|
|
||||||
|
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 composableFunctionFinder: ComposableFunctionFinder = NestedComposableFinder() |
||||||
|
|
||||||
|
override fun isAvailable(project: Project, editor: Editor?, element: PsiElement): Boolean { |
||||||
|
return element.isAvailable(composableFunctionFinder) |
||||||
|
} |
||||||
|
|
||||||
|
override fun invoke(project: Project, editor: Editor?, element: PsiElement) { |
||||||
|
val wrapper = if (element.parent is KtValueArgumentList) { |
||||||
|
element.parent.prevSibling as? KtNameReferenceExpression ?: return |
||||||
|
} else { |
||||||
|
element.parentOfType() ?: return |
||||||
|
} |
||||||
|
val callExpression = (wrapper.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 |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
package org.jetbrains.compose.intentions.utils.composable_finder |
||||||
|
|
||||||
|
import com.intellij.psi.PsiElement |
||||||
|
|
||||||
|
interface ComposableFunctionFinder { |
||||||
|
|
||||||
|
fun isFunctionComposable(psiElement: PsiElement): Boolean |
||||||
|
} |
@ -0,0 +1,42 @@ |
|||||||
|
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 DeepComposableFunctionFinder : 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() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
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 NestedComposableFinder : 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() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
package org.jetbrains.compose.intentions.utils.get_root_element |
||||||
|
|
||||||
|
import com.intellij.psi.PsiElement |
||||||
|
import org.jetbrains.kotlin.psi.* |
||||||
|
|
||||||
|
/** |
||||||
|
* KtValueArgumentList -> Parent -> KtNameReferenceExpression -> Parent -> KtCallExpression -> Parent -> KtPropertyDelegate -> Parent -> Property |
||||||
|
* KtNameReferenceExpression -> Parent -> KtCallExpression -> Parent -> KtDotQualifiedExpression -> Parent -> KtPropertyDelegate -> Property |
||||||
|
* KtNameReferenceExpression -> Parent -> KtCallExpression -> Parent -> KtPropertyDelegate -> Parent -> Property |
||||||
|
* KtNameReferenceExpression -> Parent -> KtCallExpression -> Parent -> Property |
||||||
|
* KtNameReferenceExpression -> Parent -> KtCallExpression |
||||||
|
**/ |
||||||
|
class GetRootElement { |
||||||
|
|
||||||
|
/** |
||||||
|
* element can be CallExpression (Composable Function) or Property (Composable Property like remember) |
||||||
|
*/ |
||||||
|
tailrec operator fun invoke(element: PsiElement, iteration: Int = 0): PsiElement? { |
||||||
|
if (iteration > 5) { // fail safe |
||||||
|
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 -> element |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
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 |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
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() |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,72 @@ |
|||||||
|
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 javax.swing.Icon |
||||||
|
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 |
||||||
|
|
||||||
|
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 |
||||||
|
} |
@ -0,0 +1,54 @@ |
|||||||
|
package org.jetbrains.compose.intentions.wrap_with_composable.wrap_with_actions |
||||||
|
|
||||||
|
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.is_intention_available.IsIntentionAvailable |
||||||
|
import org.jetbrains.compose.intentions.utils.composable_finder.ComposableFunctionFinder |
||||||
|
import org.jetbrains.compose.intentions.utils.composable_finder.DeepComposableFunctionFinder |
||||||
|
import org.jetbrains.compose.intentions.utils.get_root_element.GetRootElement |
||||||
|
|
||||||
|
abstract class BaseWrapWithComposableAction : PsiElementBaseIntentionAction(), |
||||||
|
HighPriorityAction, |
||||||
|
IsIntentionAvailable { |
||||||
|
|
||||||
|
private val composableFunctionFinder: ComposableFunctionFinder by lazy { |
||||||
|
DeepComposableFunctionFinder() |
||||||
|
} |
||||||
|
|
||||||
|
private val getRootElement by lazy { |
||||||
|
GetRootElement() |
||||||
|
} |
||||||
|
|
||||||
|
override fun getFamilyName(): String { |
||||||
|
return "Compose Multiplatform intentions" |
||||||
|
} |
||||||
|
|
||||||
|
override fun isAvailable(project: Project, editor: Editor?, element: PsiElement): Boolean { |
||||||
|
return element.isAvailable(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? |
||||||
|
|
||||||
|
} |
@ -0,0 +1,70 @@ |
|||||||
|
package org.jetbrains.compose.intentions.wrap_with_composable.wrap_with_actions |
||||||
|
|
||||||
|
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("WwB", "ComposeMultiplatformTemplates") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class WrapWithCardIntention : BaseWrapWithComposableAction() { |
||||||
|
|
||||||
|
override fun getText(): String { |
||||||
|
return "Wrap with Card" |
||||||
|
} |
||||||
|
|
||||||
|
override fun getTemplate(): TemplateImpl? { |
||||||
|
return TemplateSettings.getInstance().getTemplate("WwC", "ComposeMultiplatformTemplates") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class WrapWithColumnIntention : BaseWrapWithComposableAction() { |
||||||
|
|
||||||
|
override fun getText(): String { |
||||||
|
return "Wrap with Column" |
||||||
|
} |
||||||
|
|
||||||
|
override fun getTemplate(): TemplateImpl? { |
||||||
|
return TemplateSettings.getInstance().getTemplate("WwCol", "ComposeMultiplatformTemplates") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class WrapWithRowIntention : BaseWrapWithComposableAction() { |
||||||
|
|
||||||
|
override fun getText(): String { |
||||||
|
return "Wrap with Row" |
||||||
|
} |
||||||
|
|
||||||
|
override fun getTemplate(): TemplateImpl? { |
||||||
|
return TemplateSettings.getInstance().getTemplate("WwRow", "ComposeMultiplatformTemplates") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class WrapWithLzyColumnIntention : BaseWrapWithComposableAction() { |
||||||
|
|
||||||
|
override fun getText(): String { |
||||||
|
return "Wrap with LazyColumn" |
||||||
|
} |
||||||
|
|
||||||
|
override fun getTemplate(): TemplateImpl? { |
||||||
|
return TemplateSettings.getInstance().getTemplate("WwLazyCol", "ComposeMultiplatformTemplates") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class WrapWithLzyRowIntention : BaseWrapWithComposableAction() { |
||||||
|
|
||||||
|
override fun getText(): String { |
||||||
|
return "Wrap with LazyRow" |
||||||
|
} |
||||||
|
|
||||||
|
override fun getTemplate(): TemplateImpl? { |
||||||
|
return TemplateSettings.getInstance().getTemplate("WwLazyRow", "ComposeMultiplatformTemplates") |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,5 @@ |
|||||||
|
@Composable |
||||||
|
fun Column() { |
||||||
|
Text("Abc") |
||||||
|
Text("Abc") |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
<html lang="en"> |
||||||
|
<body> |
||||||
|
<p> |
||||||
|
A simple intention to remove the composable altogether. |
||||||
|
</p> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,4 @@ |
|||||||
|
@Composable |
||||||
|
Button() { |
||||||
|
Text("Abc") |
||||||
|
} |
@ -0,0 +1,6 @@ |
|||||||
|
@Composable |
||||||
|
fun Column() { |
||||||
|
Button(){ |
||||||
|
Text("Abc") |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
<html lang="en"> |
||||||
|
<body> |
||||||
|
<p> |
||||||
|
A simple intention to just remove the parent composable, and unwrap its children. |
||||||
|
</p> |
||||||
|
</body> |
||||||
|
</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> |
@ -0,0 +1,65 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<templateSet group="ComposeMultiplatformTemplates"> |
||||||
|
|
||||||
|
<template name="WwB" description="Wrap with Box" |
||||||
|
value="$COMPOSABLE$(modifier = androidx.compose.ui.Modifier) { $SELECTION$ }" |
||||||
|
toReformat="true" toShortenFQNames="true"> |
||||||
|
<variable name="COMPOSABLE" expression="" defaultValue=""androidx.compose.foundation.layout.Box"" alwaysStopAt="true"/> |
||||||
|
<context> |
||||||
|
<option name="KOTLIN" value="true"/> |
||||||
|
<option name="KOTLIN_COMMENT" value="false"/> |
||||||
|
</context> |
||||||
|
</template> |
||||||
|
|
||||||
|
|
||||||
|
<template name="WwC" description="Wrap with Card" |
||||||
|
value="$COMPOSABLE$(modifier = androidx.compose.ui.Modifier) { $SELECTION$ }" |
||||||
|
toReformat="true" toShortenFQNames="true"> |
||||||
|
<variable name="COMPOSABLE" expression="" defaultValue=""androidx.compose.material.Card"" alwaysStopAt="true"/> |
||||||
|
<context> |
||||||
|
<option name="KOTLIN" value="true"/> |
||||||
|
<option name="KOTLIN_COMMENT" value="false"/> |
||||||
|
</context> |
||||||
|
</template> |
||||||
|
|
||||||
|
<template name="WwCol" description="Wrap with Column" |
||||||
|
value="$COMPOSABLE$(modifier = androidx.compose.ui.Modifier) { $SELECTION$ }" |
||||||
|
toReformat="true" toShortenFQNames="true"> |
||||||
|
<variable name="COMPOSABLE" expression="" defaultValue=""androidx.compose.foundation.layout.Column"" alwaysStopAt="true"/> |
||||||
|
<context> |
||||||
|
<option name="KOTLIN" value="true"/> |
||||||
|
<option name="KOTLIN_COMMENT" value="false"/> |
||||||
|
</context> |
||||||
|
</template> |
||||||
|
|
||||||
|
<template name="WwRow" description="Wrap with Row" |
||||||
|
value="$COMPOSABLE$(modifier = androidx.compose.ui.Modifier) { $SELECTION$ }" |
||||||
|
toReformat="true" toShortenFQNames="true"> |
||||||
|
<variable name="COMPOSABLE" expression="" defaultValue=""androidx.compose.foundation.layout.Row"" alwaysStopAt="true"/> |
||||||
|
<context> |
||||||
|
<option name="KOTLIN" value="true"/> |
||||||
|
<option name="KOTLIN_COMMENT" value="false"/> |
||||||
|
</context> |
||||||
|
</template> |
||||||
|
|
||||||
|
<template name="WwLazyCol" description="Wrap with Lazy Column" |
||||||
|
value="$COMPOSABLE$(modifier = androidx.compose.ui.Modifier) { item { $SELECTION$ } }" |
||||||
|
toReformat="true" toShortenFQNames="true"> |
||||||
|
<variable name="COMPOSABLE" expression="" defaultValue=""androidx.compose.foundation.lazy.LazyColumn"" alwaysStopAt="true"/> |
||||||
|
<context> |
||||||
|
<option name="KOTLIN" value="true"/> |
||||||
|
<option name="KOTLIN_COMMENT" value="false"/> |
||||||
|
</context> |
||||||
|
</template> |
||||||
|
|
||||||
|
<template name="WwLazyRow" description="Wrap with Lazy Row" |
||||||
|
value="$COMPOSABLE$(modifier = androidx.compose.ui.Modifier) { item { $SELECTION$ } }" |
||||||
|
toReformat="true" toShortenFQNames="true"> |
||||||
|
<variable name="COMPOSABLE" expression="" defaultValue=""androidx.compose.foundation.lazy.LazyRow"" alwaysStopAt="true"/> |
||||||
|
<context> |
||||||
|
<option name="KOTLIN" value="true"/> |
||||||
|
<option name="KOTLIN_COMMENT" value="false"/> |
||||||
|
</context> |
||||||
|
</template> |
||||||
|
|
||||||
|
</templateSet> |
@ -0,0 +1,216 @@ |
|||||||
|
package org.jetbrains.compose.intentions.utils.get_root_element |
||||||
|
|
||||||
|
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 GetRootElementTest : LightJavaCodeInsightFixtureTestCase() { |
||||||
|
|
||||||
|
private val getRootElement = GetRootElement() |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `when a name reference expression is selected , but root is a property , the property should be returned as root element`() { |
||||||
|
val ktPsiFactory = KtPsiFactory(project) |
||||||
|
|
||||||
|
@Language("Kotlin") |
||||||
|
val template = """ |
||||||
|
val systemUiController = rememberSystemUiController() |
||||||
|
""".trimIndent() |
||||||
|
.trim() |
||||||
|
|
||||||
|
val file = ktPsiFactory.createFile(template) |
||||||
|
|
||||||
|
val property = file.lastChild as KtProperty |
||||||
|
|
||||||
|
val ktNameReferenceExpression = (property.lastChild as KtCallExpression).firstChild as KtNameReferenceExpression |
||||||
|
|
||||||
|
TestCase.assertEquals(property, getRootElement(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 = """ |
||||||
|
@Composable |
||||||
|
fun Box(block:()->Unit) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
fun OuterComposable() { |
||||||
|
// Call Expression - Box |
||||||
|
// | |
||||||
|
// v |
||||||
|
Box() { |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
""".trimIndent().trim() |
||||||
|
|
||||||
|
val file = ktPsiFactory.createFile(template) |
||||||
|
|
||||||
|
val ktNamedFunction = file.lastChild as KtNamedFunction |
||||||
|
|
||||||
|
val callExpression = ktNamedFunction.lastChild.children.find { it is KtCallExpression }!! |
||||||
|
|
||||||
|
TestCase.assertEquals( |
||||||
|
callExpression, |
||||||
|
getRootElement(callExpression.firstChild as 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() { |
||||||
|
|
||||||
|
} // Name Reference Expression |
||||||
|
} |
||||||
|
""".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( |
||||||
|
callExpression, |
||||||
|
getRootElement(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 = """ |
||||||
|
// Delegated property |
||||||
|
// | |
||||||
|
// v |
||||||
|
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( |
||||||
|
property, |
||||||
|
getRootElement(referenceExpression) |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `when a name reference expression is selected, with a delegated property with dot qualified expression as root, property should be returned`() { |
||||||
|
val ktPsiFactory = KtPsiFactory(project) |
||||||
|
|
||||||
|
@Language("Kotlin") |
||||||
|
val template = """ |
||||||
|
val repeatingAnimation = rememberInfiniteTransition() |
||||||
|
|
||||||
|
// Dot qualified expression |
||||||
|
// | |
||||||
|
// v |
||||||
|
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( |
||||||
|
property, |
||||||
|
getRootElement(referenceExpression) |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Test |
||||||
|
fun `when a name 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() |
||||||
|
|
||||||
|
// Dot qualified expression |
||||||
|
// | |
||||||
|
// v |
||||||
|
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( |
||||||
|
property, |
||||||
|
getRootElement(referenceExpression) |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue