Skip to content

Commit e5ba9d4

Browse files
LlamaLad7Earthcomputer
authored andcommitted
New: Highlight Expression text in Flow Diagram UI with match colours.
1 parent da41f3e commit e5ba9d4

File tree

5 files changed

+147
-56
lines changed

5 files changed

+147
-56
lines changed

src/main/kotlin/platform/mixin/expression/gui/DiagramStyles.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,21 +64,29 @@ object DiagramStyles {
6464
)
6565
val FAILED
6666
get() = mapOf(
67-
mxConstants.STYLE_STROKECOLOR to JBColor.red.hexString,
67+
mxConstants.STYLE_STROKECOLOR to FlowMatchStatus.FAIL.hexColor,
6868
mxConstants.STYLE_STROKEWIDTH to "3.5",
6969
)
7070
val PARTIAL_MATCH
7171
get() = mapOf(
72-
mxConstants.STYLE_STROKECOLOR to JBColor.orange.hexString,
72+
mxConstants.STYLE_STROKECOLOR to FlowMatchStatus.PARTIAL.hexColor,
7373
mxConstants.STYLE_STROKEWIDTH to "2.5",
7474
)
7575
val SUCCESS
7676
get() = mapOf(
77-
mxConstants.STYLE_STROKECOLOR to JBColor.green.hexString,
77+
mxConstants.STYLE_STROKECOLOR to FlowMatchStatus.SUCCESS.hexColor,
7878
mxConstants.STYLE_STROKEWIDTH to "1.5",
7979
)
8080
val CURRENT_EDITOR_FONT
8181
get() = EditorColorsManager.getInstance().globalScheme.getFont(EditorFontType.PLAIN)
8282
}
8383

84-
private val Color.hexString get() = "#%06X".format(rgb)
84+
private val Color.hexString get() = "#%06X".format(rgb and 0xFFFFFF)
85+
86+
val FlowMatchStatus.hexColor
87+
get() = when (this) {
88+
FlowMatchStatus.SUCCESS -> JBColor.green
89+
FlowMatchStatus.PARTIAL -> JBColor.orange
90+
FlowMatchStatus.FAIL -> JBColor.red
91+
FlowMatchStatus.IGNORED -> UIUtil.getLabelForeground()
92+
}.hexString

src/main/kotlin/platform/mixin/expression/gui/FlowDiagram.kt

Lines changed: 53 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,8 @@ package com.demonwav.mcdev.platform.mixin.expression.gui
2222

2323
import com.demonwav.mcdev.platform.mixin.expression.MEExpressionMatchUtil
2424
import com.demonwav.mcdev.util.constantStringValue
25-
import com.intellij.openapi.application.ApplicationManager
2625
import com.intellij.openapi.application.EDT
27-
import com.intellij.openapi.application.ModalityState
28-
import com.intellij.openapi.application.ReadAction
26+
import com.intellij.openapi.application.readAction
2927
import com.intellij.openapi.module.Module
3028
import com.intellij.openapi.progress.checkCanceled
3129
import com.intellij.openapi.project.Project
@@ -41,8 +39,9 @@ import com.mxgraph.util.mxRectangle
4139
import com.mxgraph.view.mxGraph
4240
import java.awt.Dimension
4341
import java.util.SortedMap
44-
import java.util.concurrent.Callable
42+
import kotlinx.coroutines.CoroutineScope
4543
import kotlinx.coroutines.Dispatchers
44+
import kotlinx.coroutines.launch
4645
import kotlinx.coroutines.withContext
4746
import org.objectweb.asm.tree.ClassNode
4847
import org.objectweb.asm.tree.MethodNode
@@ -53,15 +52,21 @@ private const val INTRA_GROUP_SPACING = 75
5352
private const val LINE_NUMBER_STYLE = "LINE_NUMBER"
5453

5554
class FlowDiagram(
55+
private val scope: CoroutineScope,
5656
val ui: FlowDiagramUi,
5757
private val flowGraph: FlowGraph,
5858
private val clazz: ClassNode,
5959
val method: MethodNode,
6060
) {
6161
companion object {
62-
suspend fun create(project: Project, clazz: ClassNode, method: MethodNode): FlowDiagram? {
62+
suspend fun create(
63+
project: Project,
64+
scope: CoroutineScope,
65+
clazz: ClassNode,
66+
method: MethodNode
67+
): FlowDiagram? {
6368
val flowGraph = FlowGraph.parse(project, clazz, method) ?: return null
64-
return buildDiagram(flowGraph, clazz, method)
69+
return buildDiagram(scope, flowGraph, clazz, method)
6570
}
6671
}
6772

@@ -91,6 +96,12 @@ class FlowDiagram(
9196
flowGraph.highlightMatches(node, soft)
9297
ui.refresh()
9398
}
99+
100+
flowGraph.onHighlightChanged { exprText, node ->
101+
scope.launch(Dispatchers.EDT) {
102+
ui.showExpr(exprText, node)
103+
}
104+
}
94105
}
95106

96107
fun populateMatchStatuses(
@@ -105,41 +116,42 @@ class FlowDiagram(
105116
val oldHighlightRoot = flowGraph.highlightRoot
106117
ui.setMatchToolbarVisible(false)
107118
flowGraph.resetMatches()
108-
ReadAction.nonBlocking(Callable<String?> run@{
109-
val stringLit = stringRef.element ?: return@run null
110-
val modifierList = modifierListRef.element ?: return@run null
111-
val expression = stringLit.constantStringValue?.let(MEExpressionMatchUtil::createExpression)
112-
?: return@run null
113-
val pool = MEExpressionMatchUtil.createIdentifierPoolFactory(module, clazz, modifierList)(method)
114-
for ((virtualInsn, root) in flowGraph.flowMap) {
115-
val node = flowGraph.allNodes.getValue(root)
116-
MEExpressionMatchUtil.findMatchingInstructions(
117-
clazz, method, pool, flowGraph.flowMap, expression, listOf(virtualInsn),
118-
ExpressionContext.Type.MODIFY_EXPRESSION_VALUE, // most permissive
119-
false,
120-
node::reportMatchStatus,
121-
node::reportPartialMatch
122-
) {}
119+
scope.launch(Dispatchers.Default) {
120+
val success = readAction run@{
121+
val stringLit = stringRef.element ?: return@run false
122+
val modifierList = modifierListRef.element ?: return@run false
123+
val expression = stringLit.constantStringValue?.let(MEExpressionMatchUtil::createExpression)
124+
?: return@run false
125+
val pool = MEExpressionMatchUtil.createIdentifierPoolFactory(module, clazz, modifierList)(method)
126+
for ((virtualInsn, root) in flowGraph.flowMap) {
127+
val node = flowGraph.allNodes.getValue(root)
128+
MEExpressionMatchUtil.findMatchingInstructions(
129+
clazz, method, pool, flowGraph.flowMap, expression, listOf(virtualInsn),
130+
ExpressionContext.Type.MODIFY_EXPRESSION_VALUE, // most permissive
131+
false,
132+
node::reportMatchStatus,
133+
node::reportPartialMatch
134+
) {}
135+
}
136+
flowGraph.setExprText(expression.src.toString())
137+
flowGraph.highlightMatches(oldHighlightRoot, false)
138+
true
123139
}
124-
flowGraph.markHasMatchData()
125-
flowGraph.highlightMatches(oldHighlightRoot, false)
126-
StringUtil.escapeStringCharacters(expression.src.toString())
127-
})
128-
.finishOnUiThread(ModalityState.nonModal()) { exprText ->
129-
exprText ?: return@finishOnUiThread
140+
if (success) {
130141
if (jump) {
131142
showBestNode()
132143
}
133-
ui.refresh()
134-
ui.setExprText(exprText)
135144
}
136-
.submit(ApplicationManager.getApplication()::executeOnPooledThread)
145+
ui.refresh()
146+
}
137147
}
138148
this.jumpToExpression = {
139-
ReadAction.run<Nothing> {
140-
val target = stringRef.element
141-
if (target is Navigatable && target.isValid && target.canNavigate()) {
142-
target.navigate(true)
149+
scope.launch {
150+
readAction {
151+
val target = stringRef.element
152+
if (target is Navigatable && target.isValid && target.canNavigate()) {
153+
target.navigate(true)
154+
}
143155
}
144156
}
145157
}
@@ -161,7 +173,12 @@ class FlowDiagram(
161173
}
162174
}
163175

164-
private suspend fun buildDiagram(flowGraph: FlowGraph, clazz: ClassNode, method: MethodNode): FlowDiagram {
176+
private suspend fun buildDiagram(
177+
scope: CoroutineScope,
178+
flowGraph: FlowGraph,
179+
clazz: ClassNode,
180+
method: MethodNode
181+
): FlowDiagram {
165182
val graph = MxFlowGraph(flowGraph)
166183
setupStyles(graph)
167184
val groupedCells = addGraphContent(graph, flowGraph)
@@ -171,7 +188,7 @@ private suspend fun buildDiagram(flowGraph: FlowGraph, clazz: ClassNode, method:
171188
val ui = withContext(Dispatchers.EDT) {
172189
FlowDiagramUi(graph, calculateBounds, lineNumberNodes)
173190
}
174-
return FlowDiagram(ui, flowGraph, clazz, method)
191+
return FlowDiagram(scope, ui, flowGraph, clazz, method)
175192
}
176193

177194
private class MxFlowGraph(private val flowGraph: FlowGraph) : mxGraph() {

src/main/kotlin/platform/mixin/expression/gui/FlowDiagramUi.kt

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package com.demonwav.mcdev.platform.mixin.expression.gui
2222

2323
import com.intellij.icons.AllIcons
2424
import com.intellij.openapi.editor.colors.EditorColorsManager
25+
import com.intellij.openapi.util.text.StringUtil
2526
import com.intellij.ui.DocumentAdapter
2627
import com.mxgraph.model.mxCell
2728
import com.mxgraph.swing.mxGraphComponent
@@ -83,8 +84,8 @@ class FlowDiagramUi(
8384
fixBounds()
8485
}
8586

86-
fun setExprText(text: String) {
87-
matchToolbar.setExprTest(text)
87+
fun showExpr(text: String, highlightRoot: FlowNode?) {
88+
matchToolbar.setExprText("<html>" + makeExprString(text, highlightRoot) + "</html>")
8889
matchToolbar.isVisible = true
8990
}
9091

@@ -243,7 +244,7 @@ class FlowDiagramUi(
243244
add(buttonPanel, BorderLayout.EAST)
244245
}
245246

246-
fun setExprTest(text: String) {
247+
fun setExprText(text: String) {
247248
exprText.text = text
248249
exprText.toolTipText = text
249250
}
@@ -277,3 +278,54 @@ private fun makeButton(icon: Icon, tooltip: String): JButton =
277278
toolTipText = tooltip
278279
preferredSize = Dimension(32, 32)
279280
}
281+
282+
private sealed class HighlightChange : Comparable<HighlightChange> {
283+
abstract val pos: Int
284+
285+
data class Start(override val pos: Int, val length: Int, val status: FlowMatchStatus) : HighlightChange()
286+
data class End(override val pos: Int) : HighlightChange()
287+
288+
override fun compareTo(other: HighlightChange): Int =
289+
compareValuesBy(
290+
this, other,
291+
{ it.pos },
292+
{ if (it is Start) 1 else -1 },
293+
{ -((it as? Start)?.length ?: 0) },
294+
)
295+
}
296+
297+
private fun makeExprString(text: String, highlightRoot: FlowNode?): String {
298+
fun escape(str: String) = StringUtil.escapeXmlEntities(StringUtil.escapeStringCharacters(str))
299+
300+
if (highlightRoot == null) {
301+
return escape(text)
302+
}
303+
304+
val changes = mutableListOf<HighlightChange>()
305+
for ((status, src) in highlightRoot.matches) {
306+
if (src == null) {
307+
continue
308+
}
309+
changes.add(HighlightChange.Start(src.startIndex, src.endIndex - src.startIndex, status))
310+
changes.add(HighlightChange.End(src.endIndex + 1))
311+
}
312+
changes.sort()
313+
314+
val result = StringBuilder()
315+
var pos = 0
316+
for (change in changes) {
317+
result.append(escape(text.substring(pos, change.pos)))
318+
pos = change.pos
319+
when (change) {
320+
is HighlightChange.Start -> {
321+
result.append("<span style='color: ${change.status.hexColor}'>")
322+
}
323+
is HighlightChange.End -> {
324+
result.append("</span>")
325+
}
326+
}
327+
}
328+
result.append(escape(text.substring(pos)))
329+
330+
return result.toString()
331+
}

0 commit comments

Comments
 (0)