@@ -4,60 +4,171 @@ import androidx.compose.ui.graphics.Color
4
4
import androidx.compose.ui.text.AnnotatedString
5
5
import androidx.compose.ui.text.SpanStyle
6
6
import androidx.compose.ui.text.buildAnnotatedString
7
- import com.github.difflib.text.DiffRow
7
+ import com.github.difflib.text.DiffRow.Tag
8
8
import com.github.difflib.text.DiffRowGenerator
9
9
10
10
/* *
11
- * Utility class to generate colored diff between two texts
11
+ * Generates a field-level word-diff for each node's states.
12
+ *
12
13
*/
13
-
14
14
fun computeAnnotatedDiff (
15
15
past : String ,
16
16
current : String
17
17
): AnnotatedString {
18
-
19
-
20
18
val diffGenerator = DiffRowGenerator .create()
21
19
.showInlineDiffs(true )
22
20
.inlineDiffByWord(true )
23
- // .replaceOriginalLinefeedInChangesWithSpaces (true)
21
+ .mergeOriginalRevised (true )
24
22
.oldTag { f -> " --" }
25
23
.newTag { f -> " ++" }
26
24
.build()
27
25
28
26
val pastName = extractTypeName(past)
29
- val pastFields = getFieldsAsList(past)
30
27
val currentName = extractTypeName(current)
28
+ val pastFields = getFieldsAsList(past)
31
29
val currentFields = getFieldsAsList(current)
32
- print (past + " \n\n " )
33
- println (pastFields)
34
- // println(diffGenerator.generateDiffRows(pastFields, currentFields))
30
+ val diffRows = diffGenerator.generateDiffRows(pastFields, currentFields)
31
+
35
32
var existsDiff = false
36
33
return buildAnnotatedString {
34
+ // A full change in the type means all internal data will be changed, so it's easier to just
35
+ // generalize and show the diff in the type's name
37
36
if (pastName != currentName) {
38
- append(" \n " )
39
- pushStyle(SpanStyle (background = Color .Red .copy(alpha = 0.3f )))
40
- append(" $pastName (...)" )
41
- pop()
37
+ buildString(
38
+ style = DiffStyles .DELETE ,
39
+ text = " $pastName (...)" ,
40
+ builder = this
41
+ )
42
+ // pushStyle(DiffStyles.DELETE)
43
+ // append("$pastName(...)")
44
+ // pop()
42
45
append(" → " )
43
- pushStyle(SpanStyle (background = Color .Green .copy(alpha = 0.3f )))
44
- append(" $currentName (...)" )
45
- pop()
46
+ buildString(
47
+ style = DiffStyles .INSERT ,
48
+ text = " $currentName (...)" ,
49
+ builder = this
50
+ )
51
+ return @buildAnnotatedString
52
+ }
53
+
54
+ diffRows.forEach { row ->
55
+ val tag = row.tag!!
56
+ // The 'mergeOriginalRevised' flag changes the semantics of the data, but the API still returns
57
+ // the same components
58
+ val fullDiff = row.oldLine
59
+
60
+ /*
61
+ Tag.INSERT and Tag.DELETE only happens when there is a difference in number of rows, i.e.:
62
+ Tag(["a"],["a","b"]) == INSERT
63
+ and
64
+ Tag(["a","b"],["a"]) == DELETE
65
+ but
66
+ Tag([""],["a"]) == CHANGE
67
+ */
68
+ when (tag) {
69
+ Tag .CHANGE -> {
70
+ existsDiff = true
71
+ parseChangedDiff(fullDiff).forEach { (style, text) ->
72
+ buildString(
73
+ style = style,
74
+ text = text,
75
+ builder = this
76
+ )
77
+ }
78
+ append(" \n\n " )
79
+ }
80
+
81
+ Tag .INSERT -> {
82
+ existsDiff = true
83
+ buildString(
84
+ text = fullDiff.replace(" ++" , " " ),
85
+ style = DiffStyles .INSERT ,
86
+ builder = this
87
+ )
88
+ append(" \n\n " )
89
+ }
90
+
91
+ Tag .DELETE -> {
92
+ existsDiff = true
93
+ buildString(
94
+ text = fullDiff.replace(" --" , " " ),
95
+ style = DiffStyles .DELETE ,
96
+ builder = this
97
+ )
98
+ append(" \n\n " )
99
+ }
100
+
101
+ Tag .EQUAL -> {
102
+ // NoOp
103
+ }
104
+ }
105
+ }
106
+
107
+ if (! existsDiff) {
108
+ buildString(
109
+ style = DiffStyles .NO_CHANGE ,
110
+ text = " No Diff" ,
111
+ builder = this
112
+ )
46
113
}
114
+ }
115
+ }
116
+
117
+ /* *
118
+ * Parses the full diff within Tag.CHANGED to give back a list of operations to perform
119
+ */
120
+ private fun parseChangedDiff (fullDiff : String ): List <Pair <SpanStyle , String >> {
121
+ val operations: MutableList <Pair <SpanStyle , String >> = mutableListOf ()
122
+ var i = 0
123
+ while (i < fullDiff.length) {
124
+ when {
125
+ fullDiff.startsWith(" --" , i) -> {
126
+ val end = fullDiff.indexOf(" --" , i + 2 )
127
+ if (end != - 1 ) {
128
+ val removed = fullDiff.substring(i + 2 , end)
129
+ operations.add(DiffStyles .DELETE to removed)
130
+ i = end + 2
131
+ }
132
+ }
47
133
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
- // }
134
+ fullDiff.startsWith(" ++" , i) -> {
135
+ val end = fullDiff.indexOf(" ++" , i + 2 )
136
+ if (end != - 1 ) {
137
+ val added = fullDiff.substring(i + 2 , end)
138
+ operations.add(DiffStyles .INSERT to added)
139
+ i = end + 2
140
+ }
141
+ }
142
+
143
+ else -> {
144
+ val nextTagStart = listOf (
145
+ fullDiff.indexOf(" --" , i),
146
+ fullDiff.indexOf(" ++" , i)
147
+ ).filter { it >= 0 }.minOrNull() ? : fullDiff.length
148
+ operations.add(DiffStyles .UNCHANGED to fullDiff.substring(i, nextTagStart))
149
+ i = nextTagStart
150
+ }
151
+ }
60
152
}
153
+
154
+ return operations
155
+ }
156
+
157
+ object DiffStyles {
158
+ val DELETE = SpanStyle (background = Color .Red .copy(alpha = 0.3f ))
159
+ val INSERT = SpanStyle (background = Color .Green .copy(alpha = 0.3f ))
160
+ val NO_CHANGE = SpanStyle (background = Color .LightGray )
161
+ val UNCHANGED = SpanStyle ()
162
+ }
163
+
164
+ internal fun buildString (
165
+ style : SpanStyle ,
166
+ text : String ,
167
+ builder : AnnotatedString .Builder
168
+ ) {
169
+ builder.pushStyle(style)
170
+ builder.append(text)
171
+ builder.pop()
61
172
}
62
173
63
174
/* *
@@ -95,6 +206,7 @@ private fun getFieldsAsList(field: String): List<String> {
95
206
currentField.append(char)
96
207
}
97
208
}
209
+
98
210
else -> currentField.append(char)
99
211
}
100
212
i++
0 commit comments