Skip to content

Commit 0e7d426

Browse files
authored
Recomposition Cascade Visualizer (#119)
* Implement cascade * Improve with reviews
1 parent 4b760c4 commit 0e7d426

8 files changed

Lines changed: 1140 additions & 4 deletions

File tree

compose-stability-analyzer-idea/api/compose-stability-analyzer-idea.api

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,118 @@ public final class com/skydoves/compose/stability/idea/StabilityLineMarkerProvid
7171
public fun getLineMarkerInfo (Lcom/intellij/psi/PsiElement;)Lcom/intellij/codeInsight/daemon/LineMarkerInfo;
7272
}
7373

74+
public final class com/skydoves/compose/stability/idea/cascade/AnalyzeCascadeAction : com/intellij/openapi/actionSystem/AnAction {
75+
public fun <init> ()V
76+
public fun actionPerformed (Lcom/intellij/openapi/actionSystem/AnActionEvent;)V
77+
public fun getActionUpdateThread ()Lcom/intellij/openapi/actionSystem/ActionUpdateThread;
78+
public fun update (Lcom/intellij/openapi/actionSystem/AnActionEvent;)V
79+
}
80+
81+
public final class com/skydoves/compose/stability/idea/cascade/CascadeNode {
82+
public fun <init> (Lcom/skydoves/compose/stability/runtime/ComposableStabilityInfo;Ljava/lang/String;IILjava/util/List;ZLjava/lang/String;)V
83+
public synthetic fun <init> (Lcom/skydoves/compose/stability/runtime/ComposableStabilityInfo;Ljava/lang/String;IILjava/util/List;ZLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
84+
public final fun component1 ()Lcom/skydoves/compose/stability/runtime/ComposableStabilityInfo;
85+
public final fun component2 ()Ljava/lang/String;
86+
public final fun component3 ()I
87+
public final fun component4 ()I
88+
public final fun component5 ()Ljava/util/List;
89+
public final fun component6 ()Z
90+
public final fun component7 ()Ljava/lang/String;
91+
public final fun copy (Lcom/skydoves/compose/stability/runtime/ComposableStabilityInfo;Ljava/lang/String;IILjava/util/List;ZLjava/lang/String;)Lcom/skydoves/compose/stability/idea/cascade/CascadeNode;
92+
public static synthetic fun copy$default (Lcom/skydoves/compose/stability/idea/cascade/CascadeNode;Lcom/skydoves/compose/stability/runtime/ComposableStabilityInfo;Ljava/lang/String;IILjava/util/List;ZLjava/lang/String;ILjava/lang/Object;)Lcom/skydoves/compose/stability/idea/cascade/CascadeNode;
93+
public fun equals (Ljava/lang/Object;)Z
94+
public final fun getChildren ()Ljava/util/List;
95+
public final fun getDepth ()I
96+
public final fun getFilePath ()Ljava/lang/String;
97+
public final fun getLine ()I
98+
public final fun getStabilityInfo ()Lcom/skydoves/compose/stability/runtime/ComposableStabilityInfo;
99+
public final fun getTruncationReason ()Ljava/lang/String;
100+
public fun hashCode ()I
101+
public final fun isTruncated ()Z
102+
public fun toString ()Ljava/lang/String;
103+
}
104+
105+
public final class com/skydoves/compose/stability/idea/cascade/CascadeResult {
106+
public fun <init> (Lcom/skydoves/compose/stability/idea/cascade/CascadeNode;Lcom/skydoves/compose/stability/idea/cascade/CascadeSummary;)V
107+
public final fun component1 ()Lcom/skydoves/compose/stability/idea/cascade/CascadeNode;
108+
public final fun component2 ()Lcom/skydoves/compose/stability/idea/cascade/CascadeSummary;
109+
public final fun copy (Lcom/skydoves/compose/stability/idea/cascade/CascadeNode;Lcom/skydoves/compose/stability/idea/cascade/CascadeSummary;)Lcom/skydoves/compose/stability/idea/cascade/CascadeResult;
110+
public static synthetic fun copy$default (Lcom/skydoves/compose/stability/idea/cascade/CascadeResult;Lcom/skydoves/compose/stability/idea/cascade/CascadeNode;Lcom/skydoves/compose/stability/idea/cascade/CascadeSummary;ILjava/lang/Object;)Lcom/skydoves/compose/stability/idea/cascade/CascadeResult;
111+
public fun equals (Ljava/lang/Object;)Z
112+
public final fun getRoot ()Lcom/skydoves/compose/stability/idea/cascade/CascadeNode;
113+
public final fun getSummary ()Lcom/skydoves/compose/stability/idea/cascade/CascadeSummary;
114+
public fun hashCode ()I
115+
public fun toString ()Ljava/lang/String;
116+
}
117+
118+
public final class com/skydoves/compose/stability/idea/cascade/CascadeSummary {
119+
public fun <init> (IIIIZ)V
120+
public final fun component1 ()I
121+
public final fun component2 ()I
122+
public final fun component3 ()I
123+
public final fun component4 ()I
124+
public final fun component5 ()Z
125+
public final fun copy (IIIIZ)Lcom/skydoves/compose/stability/idea/cascade/CascadeSummary;
126+
public static synthetic fun copy$default (Lcom/skydoves/compose/stability/idea/cascade/CascadeSummary;IIIIZILjava/lang/Object;)Lcom/skydoves/compose/stability/idea/cascade/CascadeSummary;
127+
public fun equals (Ljava/lang/Object;)Z
128+
public final fun getHasTruncatedBranches ()Z
129+
public final fun getMaxDepth ()I
130+
public final fun getSkippableCount ()I
131+
public final fun getTotalCount ()I
132+
public final fun getUnskippableCount ()I
133+
public fun hashCode ()I
134+
public fun toString ()Ljava/lang/String;
135+
}
136+
137+
public abstract class com/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData {
138+
}
139+
140+
public final class com/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData$Composable : com/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData {
141+
public fun <init> (Lcom/skydoves/compose/stability/idea/cascade/CascadeNode;)V
142+
public final fun component1 ()Lcom/skydoves/compose/stability/idea/cascade/CascadeNode;
143+
public final fun copy (Lcom/skydoves/compose/stability/idea/cascade/CascadeNode;)Lcom/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData$Composable;
144+
public static synthetic fun copy$default (Lcom/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData$Composable;Lcom/skydoves/compose/stability/idea/cascade/CascadeNode;ILjava/lang/Object;)Lcom/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData$Composable;
145+
public fun equals (Ljava/lang/Object;)Z
146+
public final fun getNode ()Lcom/skydoves/compose/stability/idea/cascade/CascadeNode;
147+
public fun hashCode ()I
148+
public fun toString ()Ljava/lang/String;
149+
}
150+
151+
public final class com/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData$EmptyMessage : com/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData {
152+
public fun <init> (Ljava/lang/String;)V
153+
public final fun component1 ()Ljava/lang/String;
154+
public final fun copy (Ljava/lang/String;)Lcom/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData$EmptyMessage;
155+
public static synthetic fun copy$default (Lcom/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData$EmptyMessage;Ljava/lang/String;ILjava/lang/Object;)Lcom/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData$EmptyMessage;
156+
public fun equals (Ljava/lang/Object;)Z
157+
public final fun getMessage ()Ljava/lang/String;
158+
public fun hashCode ()I
159+
public fun toString ()Ljava/lang/String;
160+
}
161+
162+
public final class com/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData$Summary : com/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData {
163+
public fun <init> (Ljava/lang/String;Lcom/skydoves/compose/stability/idea/cascade/CascadeSummary;)V
164+
public final fun component1 ()Ljava/lang/String;
165+
public final fun component2 ()Lcom/skydoves/compose/stability/idea/cascade/CascadeSummary;
166+
public final fun copy (Ljava/lang/String;Lcom/skydoves/compose/stability/idea/cascade/CascadeSummary;)Lcom/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData$Summary;
167+
public static synthetic fun copy$default (Lcom/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData$Summary;Ljava/lang/String;Lcom/skydoves/compose/stability/idea/cascade/CascadeSummary;ILjava/lang/Object;)Lcom/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData$Summary;
168+
public fun equals (Ljava/lang/Object;)Z
169+
public final fun getComposableName ()Ljava/lang/String;
170+
public final fun getSummary ()Lcom/skydoves/compose/stability/idea/cascade/CascadeSummary;
171+
public fun hashCode ()I
172+
public fun toString ()Ljava/lang/String;
173+
}
174+
175+
public final class com/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData$Truncated : com/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData {
176+
public fun <init> (Ljava/lang/String;)V
177+
public final fun component1 ()Ljava/lang/String;
178+
public final fun copy (Ljava/lang/String;)Lcom/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData$Truncated;
179+
public static synthetic fun copy$default (Lcom/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData$Truncated;Ljava/lang/String;ILjava/lang/Object;)Lcom/skydoves/compose/stability/idea/cascade/CascadeTreeNodeData$Truncated;
180+
public fun equals (Ljava/lang/Object;)Z
181+
public final fun getReason ()Ljava/lang/String;
182+
public fun hashCode ()I
183+
public fun toString ()Ljava/lang/String;
184+
}
185+
74186
public final class com/skydoves/compose/stability/idea/quickfix/AddTraceRecompositionIntention : com/intellij/codeInsight/intention/PsiElementBaseIntentionAction, com/intellij/codeInsight/intention/IntentionAction {
75187
public fun <init> ()V
76188
public fun getFamilyName ()Ljava/lang/String;
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Designed and developed by 2025 skydoves (Jaewoong Eum)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.skydoves.compose.stability.idea.cascade
17+
18+
import com.intellij.openapi.actionSystem.ActionUpdateThread
19+
import com.intellij.openapi.actionSystem.AnAction
20+
import com.intellij.openapi.actionSystem.AnActionEvent
21+
import com.intellij.openapi.actionSystem.CommonDataKeys
22+
import com.intellij.openapi.wm.ToolWindowManager
23+
import com.intellij.psi.util.PsiTreeUtil
24+
import com.skydoves.compose.stability.idea.isComposable
25+
import org.jetbrains.kotlin.psi.KtFile
26+
import org.jetbrains.kotlin.psi.KtNamedFunction
27+
28+
/**
29+
* Context menu action to analyze the recomposition cascade
30+
* of a @Composable function.
31+
*/
32+
public class AnalyzeCascadeAction : AnAction() {
33+
34+
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
35+
36+
override fun actionPerformed(e: AnActionEvent) {
37+
val project = e.project ?: return
38+
val editor = e.getData(CommonDataKeys.EDITOR) ?: return
39+
val psiFile = e.getData(CommonDataKeys.PSI_FILE) ?: return
40+
41+
// Find the composable function at the caret
42+
val offset = editor.caretModel.offset
43+
val element = psiFile.findElementAt(offset) ?: return
44+
val function = PsiTreeUtil.getParentOfType(element, KtNamedFunction::class.java) ?: return
45+
if (!function.isComposable()) return
46+
47+
// Open the tool window and switch to the Cascade tab
48+
val toolWindow = ToolWindowManager.getInstance(project)
49+
.getToolWindow("Compose Stability Analyzer") ?: return
50+
toolWindow.show {
51+
// Find the Cascade tab
52+
val cascadeContent = toolWindow.contentManager.findContent("Cascade") ?: return@show
53+
toolWindow.contentManager.setSelectedContent(cascadeContent)
54+
55+
// Get the CascadePanel from the content component's client property
56+
val panel = cascadeContent.component
57+
.getClientProperty(CascadePanel::class.java) as? CascadePanel ?: return@show
58+
59+
// Start analysis
60+
panel.analyzeFunction(function)
61+
}
62+
}
63+
64+
override fun update(e: AnActionEvent) {
65+
val psiFile = e.getData(CommonDataKeys.PSI_FILE)
66+
val editor = e.getData(CommonDataKeys.EDITOR)
67+
68+
if (psiFile == null || editor == null) {
69+
e.presentation.isVisible = false
70+
return
71+
}
72+
73+
// Always show in Kotlin files, but only enable inside @Composable functions
74+
e.presentation.isVisible = psiFile is KtFile
75+
76+
val offset = editor.caretModel.offset
77+
val element = psiFile.findElementAt(offset)
78+
val function = element?.let {
79+
PsiTreeUtil.getParentOfType(it, KtNamedFunction::class.java)
80+
}
81+
82+
e.presentation.isEnabled = function?.isComposable() == true
83+
}
84+
}

0 commit comments

Comments
 (0)