Moxy Strategy plugin

Sometimes simple things are very tiring, especially when they need to be done constantly. One of these things when working with the Moxy framework is adding strategies to functions. To speed up this process, a plugin was written which, by "alt + enter", provides a choice of a strategy if it is not there or a dialog with a replacement for another strategy. Those who want to know how it works, welcome to cat.



When I was starting to learn Moxy, one of the difficulties was remembering the names of the strategies and not forgetting about their existence. After practicing with them, this problem went away, but still there was a desire to somehow optimize it.


At this point, I became interested in creating plugins, and adev_one He told me that the plugin could just solve such a problem.


Inspection


So, we wrote a function and want to add a strategy. At this point, code inspections comes to the rescue . They analyze the code of the current file using the PSI (Program Structure Interface) , and if something is wrong, highlight the code in red or gray, and alt + enter offer possible corrections.
The official documentation for developing code inspections .
PSI is a structure in which each expression, keyword, etc. There is an analogue that contains information about it, its parent and child. In order to understand which class corresponds to the necessary language design, there is a good Psi viewer plugin .


For example, consider using it the structure of an interface with a function.



, KtNamedFunction.


Inpection.


. plugins.xml


    <depends>org.jetbrains.kotlin</depends>
    <depends>com.intellij.modules.lang</depends>

build.gradle


dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    implementation "org.jetbrains.kotlin:kotlin-reflect"
    implementation "com.github.moxy-community:moxy:1.0.13"
}

intellij {
 ...
    plugins = ["Kotlin"]
 ...
}

inpection.


AbstractKotlinInspection


class MvpViewStrategyInspection : AbstractKotlinInspection() {}

:


override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor 

Visitor, .


Visitor , kotlin - Visitor. KtNamedFunction :


org.jetbrains.kotlin.psi VisitorWrappersKt.class 
public fun namedFunctionVisitor(block: (KtNamedFunction) β†’ Unit): KtVisitorVoid

PSI: , MvpView. , ProblemsHolder.
:


private val fixes = MoxyStrategy.values().map { AddStrategyFix(it) }.toTypedArray()

override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
        return namedFunctionVisitor { ktNamedFunction ->
            if (!ktNamedFunction.isClassInheritMvpView() ||
                ktNamedFunction.isHasMoxyAnnotation()
            ) return@namedFunctionVisitor
            holder.registerProblem(
                ktNamedFunction, // psiElement
                StrategyIntentionType.MissingStrategy.title,  // description
                *fixes // LocalQuickFix
            )
        }
    }

AddStrategyFix? , LocalQuickFix. , :


class AddStrategyFix(
        private val strategy: MoxyStrategy
    ) : LocalQuickFix {
        override fun getFamilyName(): String = "add ${strategy.className}"

        override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
            val ktFunction = (descriptor.psiElement as KtNamedFunction)
            val editor = ktFunction.getProjectEditor()
            ktFunction.addStrategyAnnotation(strategy, project, editor)
        }
    }

getFamilyName β€” , .
applyFix . .


Inspection plguin.xml, ( Activity Manifest)


 <extensions defaultExtensionNs="com.intellij">
        <localInspection language="kotlin"
                         displayName="missing strategy for function"
                         groupPath="Moxy"
                         groupBundle="messages.InspectionsBundle"
                         groupKey="group.names.probable.bugs"
                         enabledByDefault="true"
                         level="ERROR"
                         implementationClass="com.maksimnovikov.inspection.MvpViewStrategyInspection"/>
    </extensions>

:



StateStrategyType .


AddToEndSingleTagStrategy
tag .



Intention


, . .
Intention.
Inspection, . intention. .


Intention.


PsiElementBaseIntentionAction :


class MvpViewStrategyIntention : PsiElementBaseIntentionAction(){
    override fun getText(): String 
    override fun getFamilyName(): String
    override fun isAvailable(project: Project, editor: Editor?, element: PsiElement): Boolean
    override fun invoke(project: Project, editor: Editor?, element: PsiElement)
}

getText β€”
getFamilyName β€” intention
isAvailable β€” , intention .
invoke β€” , intention


isAvailable invoke:


isAvailable


, intention . .
child , . .


override fun isAvailable(project: Project, editor: Editor?, element: PsiElement): Boolean {
        if ((element.containingFile ?: return false) !is KtFile) return false
        if (!element.isClassInheritMvpView()) return false
        val ktFunction = element.getParentOfType<KtNamedFunction>() ?: return false
        return ktFunction.isHasMoxyAnnotation()
    }

invoke


Intention , .
IDE swing. , , . β€” JBPopupFactory


fun <T> Editor.showSelectPopup(
    items: List<T>,
    onSelected: (T) -> Unit
) {
    JBPopupFactory.getInstance()
        .createPopupChooserBuilder(items)
        .setRequestFocus(true)
        .setCancelOnClickOutside(true)
        .setItemChosenCallback { onSelected(it) }
        .createPopup()
        .showInBestPositionFor(this)
}

, , . WriteCommandAction.runWriteCommandAction(project) {}


  override fun invoke(project: Project, editor: Editor, element: PsiElement) {
        editor.showSelectPopup(MoxyStrategy.values().toList()) { selectedStrategy ->
            WriteCommandAction.runWriteCommandAction(project) {
                val ktFunction = element.getParentOfType<KtNamedFunction>() ?: return@runWriteCommandAction
                ktFunction.replaceAnnotation(selectedStrategy, project, editor)
            }
        }
    }

intention plugin.xml


<extensions defaultExtensionNs="com.intellij">
        <intentionAction>
            <className>com.maksimnovikov.intention.MvpViewStrategyIntention</className>
            <category>Moxy intentions</category>
        </intentionAction>
</extensions>

:





. . , , . Moxy.


. - . . . .




Source: https://habr.com/ru/post/undefined/


All Articles