Skip to content

Commit 2aaa120

Browse files
committed
WIP text diff
1 parent 58c58a9 commit 2aaa120

File tree

3 files changed

+143
-10
lines changed

3 files changed

+143
-10
lines changed

workflow-trace-viewer/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ kotlin {
2727
implementation(compose.materialIconsExtended)
2828
implementation(libs.squareup.moshi.kotlin)
2929
implementation(libs.filekit.dialogs.compose)
30+
implementation(libs.java.diff.utils)
3031
}
3132
}
3233
jvmTest {

workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/ui/WorkflowInfoPanel.kt

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ import androidx.compose.runtime.setValue
2828
import androidx.compose.ui.Alignment
2929
import androidx.compose.ui.Modifier
3030
import androidx.compose.ui.graphics.Color
31-
import androidx.compose.ui.text.TextStyle
32-
import androidx.compose.ui.text.font.FontStyle
3331
import androidx.compose.ui.text.font.FontWeight
3432
import androidx.compose.ui.text.style.TextAlign
33+
import androidx.compose.ui.text.style.TextOverflow
3534
import androidx.compose.ui.unit.dp
3635
import com.squareup.workflow1.traceviewer.model.Node
3736
import com.squareup.workflow1.traceviewer.model.NodeUpdate
37+
import com.squareup.workflow1.traceviewer.util.computeAnnotatedDiff
3838

3939
/**
4040
* A panel that displays information about the selected workflow node.
@@ -163,7 +163,7 @@ private fun DetailCard(
163163
text = label,
164164
style = MaterialTheme.typography.h6,
165165
color = Color.Black,
166-
fontWeight = FontWeight.Medium
166+
fontWeight = FontWeight.Bold
167167
)
168168
if (!open) {
169169
return@Card
@@ -173,21 +173,41 @@ private fun DetailCard(
173173
if (pastValue != null) {
174174
Column {
175175
Text(
176-
text = "Before:",
177-
style = TextStyle(fontStyle = FontStyle.Italic),
178-
color = Color.Black,
176+
text = "Changes",
177+
style = MaterialTheme.typography.subtitle1,
178+
color = Color.Gray,
179+
fontWeight = FontWeight.Medium
180+
)
181+
Text(
182+
text = computeAnnotatedDiff(pastValue, currValue),
183+
style = MaterialTheme.typography.body2
184+
)
185+
186+
Spacer(modifier = Modifier.height(16.dp))
187+
Text(
188+
text = "━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
189+
maxLines = 1,
190+
overflow = TextOverflow.Clip
191+
)
192+
193+
Text(
194+
text = "Before",
195+
style = MaterialTheme.typography.subtitle1,
196+
color = Color.Gray,
179197
fontWeight = FontWeight.Medium
180198
)
181199
Text(
182200
text = pastValue,
183201
style = MaterialTheme.typography.body2,
184202
color = Color.Black
185203
)
186-
Spacer(modifier = Modifier.height(8.dp))
204+
205+
Spacer(modifier = Modifier.height(16.dp))
206+
187207
Text(
188-
text = "After:",
189-
style = TextStyle(fontStyle = FontStyle.Italic),
190-
color = Color.Black,
208+
text = "After",
209+
style = MaterialTheme.typography.subtitle1,
210+
color = Color.Gray,
191211
fontWeight = FontWeight.Medium
192212
)
193213
Text(
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package com.squareup.workflow1.traceviewer.util
2+
3+
import androidx.compose.ui.graphics.Color
4+
import androidx.compose.ui.text.AnnotatedString
5+
import androidx.compose.ui.text.SpanStyle
6+
import androidx.compose.ui.text.buildAnnotatedString
7+
import com.github.difflib.text.DiffRow
8+
import com.github.difflib.text.DiffRowGenerator
9+
10+
/**
11+
* Utility class to generate colored diff between two texts
12+
*/
13+
14+
fun computeAnnotatedDiff(
15+
past: String,
16+
current: String
17+
): AnnotatedString {
18+
19+
20+
val diffGenerator = DiffRowGenerator.create()
21+
.showInlineDiffs(true)
22+
.inlineDiffByWord(true)
23+
// .replaceOriginalLinefeedInChangesWithSpaces(true)
24+
.oldTag { f -> "--" }
25+
.newTag { f -> "++" }
26+
.build()
27+
28+
val pastName = extractTypeName(past)
29+
val pastFields = getFieldsAsList(past)
30+
val currentName = extractTypeName(current)
31+
val currentFields = getFieldsAsList(current)
32+
print(past + "\n\n")
33+
println(pastFields)
34+
// println(diffGenerator.generateDiffRows(pastFields, currentFields))
35+
var existsDiff = false
36+
return buildAnnotatedString {
37+
if (pastName != currentName) {
38+
append("\n")
39+
pushStyle(SpanStyle(background = Color.Red.copy(alpha = 0.3f)))
40+
append("$pastName(...)")
41+
pop()
42+
append("")
43+
pushStyle(SpanStyle(background = Color.Green.copy(alpha = 0.3f)))
44+
append("$currentName(...)")
45+
pop()
46+
}
47+
48+
/*
49+
zip shortens both to the shortest one, so
50+
a) if past > current, then we pushStyle(delete) for the rest of past
51+
b) if current > past, then we pushStyle(add) for the rest of current
52+
*/
53+
// pastFields.zip(currentFields).forEach { (pastField, currentField) ->
54+
// val diff = diffGenerator.generateDiffRows(pastField, currentField)
55+
// val tag = diff[0]
56+
// if (tag == DiffRow.Tag.CHANGE) {
57+
//
58+
// }
59+
// }
60+
}
61+
}
62+
63+
/**
64+
* Pull out each "key=value" pair within the field data by looking for a comma. Since plenty of data
65+
* include nesting, doing .split or simple regex won't suffice.
66+
*
67+
* Manually iterates through the fields and changes the depth of the current comma accordingly
68+
*/
69+
private fun getFieldsAsList(field: String): List<String> {
70+
val fields = mutableListOf<String>()
71+
val currentField = StringBuilder()
72+
var depth = 0
73+
// We skip past the field's Type's name
74+
var i = field.indexOf('(') + 1
75+
76+
while (i < field.length) {
77+
val char = field[i]
78+
when (char) {
79+
'(', '[', '{' -> {
80+
depth++
81+
currentField.append(char)
82+
}
83+
84+
')', ']', '}' -> {
85+
depth--
86+
currentField.append(char)
87+
}
88+
89+
',' -> {
90+
if (depth == 0) { // end of key=value pair
91+
fields += currentField.toString().trim()
92+
currentField.clear()
93+
i++ // skip space, e.g. "key=value, key2=value2, etc..."
94+
} else { // nested list
95+
currentField.append(char)
96+
}
97+
}
98+
else -> currentField.append(char)
99+
}
100+
i++
101+
}
102+
103+
// Just append whatever is left, since there are no trailing commas
104+
if (currentField.isNotBlank()) fields += currentField.toString().trim()
105+
return fields
106+
}
107+
108+
private fun extractTypeName(field: String): String {
109+
val stateRegex = Regex("""^(\w+)\(""")
110+
// If regex doesn't match, that means it's likely "kotlin.Unit" or "0"
111+
return stateRegex.find(field)?.groupValues?.get(1) ?: field
112+
}

0 commit comments

Comments
 (0)