Skip to content

Commit a1976e9

Browse files
authored
Merge pull request #4 from JahidHasanCO/copilot/fix-3
Fix unified view to show both old and new text for changed lines with syntax highlighting
2 parents 2b11535 + 470d75e commit a1976e9

3 files changed

Lines changed: 288 additions & 67 deletions

File tree

app/src/main/java/dev/jahidhasanco/diffly/presentation/component/UnifiedCharDiffText.kt

Lines changed: 180 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -48,71 +48,182 @@ fun UnifiedCharDiffText(
4848
}
4949

5050
items(diffResult) { entry ->
51-
val prefix = when (entry.type) {
52-
DiffType.ADDED -> "+"
53-
DiffType.DELETED -> "-"
54-
DiffType.UNCHANGED -> " "
55-
DiffType.CHANGED -> "~"
56-
}
51+
// For CHANGED entries, show both old and new lines
52+
if (entry.type == DiffType.CHANGED) {
53+
// Show old line first with deletion styling
54+
entry.oldLine?.let { oldLine ->
55+
Box(
56+
modifier = Modifier
57+
.fillMaxWidth()
58+
.padding(horizontal = 2.dp, vertical = 1.dp)
59+
.background(delete.copy(alpha = 0.1f))
60+
) {
61+
Row(
62+
verticalAlignment = Alignment.Top
63+
) {
64+
// Old line number
65+
Text(
66+
text = oldLineNumber.toString(),
67+
modifier = Modifier
68+
.width(40.dp)
69+
.padding(end = 2.dp),
70+
color = Color.Gray
71+
)
5772

58-
val line = when (entry.type) {
59-
DiffType.ADDED -> entry.newLine
60-
DiffType.DELETED -> entry.oldLine
61-
DiffType.UNCHANGED -> entry.oldLine
62-
DiffType.CHANGED -> entry.newLine
63-
}
73+
// New line number (empty for old line)
74+
Text(
75+
text = "",
76+
modifier = Modifier
77+
.width(40.dp)
78+
.padding(end = 2.dp),
79+
color = Color.Gray
80+
)
6481

65-
val color = when (entry.type) {
66-
DiffType.ADDED -> added.copy(alpha = 0.1f)
67-
DiffType.DELETED -> delete.copy(alpha = 0.1f)
68-
DiffType.CHANGED -> delete.copy(alpha = 0.1f)
69-
else -> Color.Transparent
70-
}
82+
// Prefix for deletion
83+
Text(
84+
text = "-",
85+
modifier = Modifier.padding(end = 4.dp),
86+
color = Color.Gray
87+
)
88+
89+
// Old line content with syntax highlighting
90+
if (isSyntaxHighlightEnabled) {
91+
val syntaxAnnotatedString =
92+
remember(oldLine, language, theme) {
93+
parseCodeAsAnnotatedString(
94+
parser, theme, language, oldLine
95+
)
96+
}
97+
Text(syntaxAnnotatedString)
98+
} else {
99+
Text(oldLine)
100+
}
101+
}
102+
}
103+
}
71104

72-
line?.let {
73-
Box(
74-
modifier = Modifier
75-
.fillMaxWidth()
76-
.padding(horizontal = 2.dp, vertical = 1.dp)
77-
.background(color)
78-
) {
79-
Row(
80-
verticalAlignment = Alignment.Top
105+
// Show new line second with addition styling
106+
entry.newLine?.let { newLine ->
107+
Box(
108+
modifier = Modifier
109+
.fillMaxWidth()
110+
.padding(horizontal = 2.dp, vertical = 1.dp)
111+
.background(added.copy(alpha = 0.1f))
81112
) {
82-
// Old line number
83-
Text(text = entry.oldLine?.let { oldLineNumber.toString() }
84-
?: "",
85-
modifier = Modifier
86-
.width(40.dp)
87-
.padding(end = 2.dp),
88-
color = Color.Gray)
89-
90-
// New line number
91-
Text(text = entry.newLine?.let { newLineNumber.toString() }
92-
?: "",
93-
modifier = Modifier
94-
.width(40.dp)
95-
.padding(end = 2.dp),
96-
color = Color.Gray)
97-
98-
// Prefix
99-
Text(
100-
text = prefix,
101-
modifier = Modifier.padding(end = 4.dp),
102-
color = Color.Gray
103-
)
104-
105-
// Line content or char-level diff
106-
if (entry.type == DiffType.CHANGED && !entry.charDiffs.isNullOrEmpty()) {
107-
InlineCharDiffText(
108-
isSyntaxHighlightEnabled,
109-
it,
110-
charDiffs = entry.charDiffs,
111-
language,
112-
parser,
113-
theme
113+
Row(
114+
verticalAlignment = Alignment.Top
115+
) {
116+
// Old line number (empty for new line)
117+
Text(
118+
text = "",
119+
modifier = Modifier
120+
.width(40.dp)
121+
.padding(end = 2.dp),
122+
color = Color.Gray
114123
)
115-
} else {
124+
125+
// New line number
126+
Text(
127+
text = newLineNumber.toString(),
128+
modifier = Modifier
129+
.width(40.dp)
130+
.padding(end = 2.dp),
131+
color = Color.Gray
132+
)
133+
134+
// Prefix for addition
135+
Text(
136+
text = "+",
137+
modifier = Modifier.padding(end = 4.dp),
138+
color = Color.Gray
139+
)
140+
141+
// New line content with char-level diff or syntax highlighting
142+
if (!entry.charDiffs.isNullOrEmpty()) {
143+
InlineCharDiffText(
144+
isSyntaxHighlightEnabled,
145+
newLine,
146+
charDiffs = entry.charDiffs,
147+
language,
148+
parser,
149+
theme
150+
)
151+
} else {
152+
if (isSyntaxHighlightEnabled) {
153+
val syntaxAnnotatedString =
154+
remember(newLine, language, theme) {
155+
parseCodeAsAnnotatedString(
156+
parser, theme, language, newLine
157+
)
158+
}
159+
Text(syntaxAnnotatedString)
160+
} else {
161+
Text(newLine)
162+
}
163+
}
164+
}
165+
}
166+
}
167+
168+
// Update line numbers for CHANGED
169+
oldLineNumber++
170+
newLineNumber++
171+
} else {
172+
// Handle other diff types as before
173+
val prefix = when (entry.type) {
174+
DiffType.ADDED -> "+"
175+
DiffType.DELETED -> "-"
176+
DiffType.UNCHANGED -> " "
177+
else -> " " // This shouldn't happen now
178+
}
179+
180+
val line = when (entry.type) {
181+
DiffType.ADDED -> entry.newLine
182+
DiffType.DELETED -> entry.oldLine
183+
DiffType.UNCHANGED -> entry.oldLine
184+
else -> null // This shouldn't happen now
185+
}
186+
187+
val color = when (entry.type) {
188+
DiffType.ADDED -> added.copy(alpha = 0.1f)
189+
DiffType.DELETED -> delete.copy(alpha = 0.1f)
190+
else -> Color.Transparent
191+
}
192+
193+
line?.let {
194+
Box(
195+
modifier = Modifier
196+
.fillMaxWidth()
197+
.padding(horizontal = 2.dp, vertical = 1.dp)
198+
.background(color)
199+
) {
200+
Row(
201+
verticalAlignment = Alignment.Top
202+
) {
203+
// Old line number
204+
Text(text = entry.oldLine?.let { oldLineNumber.toString() }
205+
?: "",
206+
modifier = Modifier
207+
.width(40.dp)
208+
.padding(end = 2.dp),
209+
color = Color.Gray)
210+
211+
// New line number
212+
Text(text = entry.newLine?.let { newLineNumber.toString() }
213+
?: "",
214+
modifier = Modifier
215+
.width(40.dp)
216+
.padding(end = 2.dp),
217+
color = Color.Gray)
218+
219+
// Prefix
220+
Text(
221+
text = prefix,
222+
modifier = Modifier.padding(end = 4.dp),
223+
color = Color.Gray
224+
)
225+
226+
// Line content with syntax highlighting
116227
if (isSyntaxHighlightEnabled) {
117228
val syntaxAnnotatedString =
118229
remember(line, language, theme) {
@@ -127,14 +238,16 @@ fun UnifiedCharDiffText(
127238
}
128239
}
129240
}
130-
}
131241

132-
when (entry.type) {
133-
DiffType.ADDED -> newLineNumber++
134-
DiffType.DELETED -> oldLineNumber++
135-
DiffType.UNCHANGED, DiffType.CHANGED -> {
136-
oldLineNumber++
137-
newLineNumber++
242+
// Update line numbers for other types
243+
when (entry.type) {
244+
DiffType.ADDED -> newLineNumber++
245+
DiffType.DELETED -> oldLineNumber++
246+
DiffType.UNCHANGED -> {
247+
oldLineNumber++
248+
newLineNumber++
249+
}
250+
else -> {} // CHANGED is handled above
138251
}
139252
}
140253
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package dev.jahidhasanco.diffly.presentation.component
2+
3+
import dev.jahidhasanco.diffly.domain.model.DiffEntry
4+
import dev.jahidhasanco.diffly.domain.model.DiffType
5+
import dev.jahidhasanco.diffly.domain.model.CharDiff
6+
import dev.jahidhasanco.diffly.domain.model.CharDiffType
7+
import kotlin.test.Test
8+
9+
/**
10+
* Unit test to verify UnifiedCharDiffText logic for CHANGED entries
11+
*/
12+
class UnifiedCharDiffTextTest {
13+
14+
@Test
15+
fun testUnifiedViewLogicWithChangedEntries() {
16+
// Test data that represents different types of diffs
17+
val diffResult = listOf(
18+
DiffEntry("line 1", "line 1", DiffType.UNCHANGED),
19+
DiffEntry("old line 2", "new line 2", DiffType.CHANGED, listOf(
20+
CharDiff('o', CharDiffType.DELETED),
21+
CharDiff('l', CharDiffType.DELETED),
22+
CharDiff('d', CharDiffType.DELETED),
23+
CharDiff('n', CharDiffType.INSERTED),
24+
CharDiff('e', CharDiffType.INSERTED),
25+
CharDiff('w', CharDiffType.INSERTED),
26+
CharDiff(' ', CharDiffType.UNCHANGED),
27+
CharDiff('l', CharDiffType.UNCHANGED),
28+
CharDiff('i', CharDiffType.UNCHANGED),
29+
CharDiff('n', CharDiffType.UNCHANGED),
30+
CharDiff('e', CharDiffType.UNCHANGED),
31+
CharDiff(' ', CharDiffType.UNCHANGED),
32+
CharDiff('2', CharDiffType.UNCHANGED)
33+
)),
34+
DiffEntry(null, "added line", DiffType.ADDED),
35+
DiffEntry("deleted line", null, DiffType.DELETED),
36+
DiffEntry("line 5", "line 5", DiffType.UNCHANGED)
37+
)
38+
39+
// Simulate the line numbering logic from UnifiedCharDiffText
40+
var oldLineNumber = 1
41+
var newLineNumber = 1
42+
val renderedLines = mutableListOf<String>()
43+
44+
for (entry in diffResult) {
45+
when (entry.type) {
46+
DiffType.CHANGED -> {
47+
// Should render old line first
48+
entry.oldLine?.let { oldLine ->
49+
renderedLines.add("${oldLineNumber.toString().padStart(3)} ${" ".repeat(3)} - $oldLine")
50+
}
51+
52+
// Should render new line second
53+
entry.newLine?.let { newLine ->
54+
renderedLines.add("${" ".repeat(3)} ${newLineNumber.toString().padStart(3)} + $newLine")
55+
}
56+
57+
oldLineNumber++
58+
newLineNumber++
59+
}
60+
DiffType.ADDED -> {
61+
val line = entry.newLine ?: ""
62+
renderedLines.add("${" ".repeat(3)} ${newLineNumber.toString().padStart(3)} + $line")
63+
newLineNumber++
64+
}
65+
DiffType.DELETED -> {
66+
val line = entry.oldLine ?: ""
67+
renderedLines.add("${oldLineNumber.toString().padStart(3)} ${" ".repeat(3)} - $line")
68+
oldLineNumber++
69+
}
70+
DiffType.UNCHANGED -> {
71+
val line = entry.oldLine ?: ""
72+
renderedLines.add("${oldLineNumber.toString().padStart(3)} ${newLineNumber.toString().padStart(3)} $line")
73+
oldLineNumber++
74+
newLineNumber++
75+
}
76+
}
77+
}
78+
79+
// Expected output for the unified view
80+
val expected = listOf(
81+
" 1 1 line 1",
82+
" 2 - old line 2",
83+
" 2 + new line 2",
84+
" 3 + added line",
85+
" 3 - deleted line",
86+
" 4 4 line 5"
87+
)
88+
89+
println("=== Unified View Test Results ===")
90+
println("Expected:")
91+
expected.forEach { println(" $it") }
92+
println("\nActual:")
93+
renderedLines.forEach { println(" $it") }
94+
95+
// Verify the results
96+
assert(renderedLines.size == expected.size) {
97+
"Expected ${expected.size} lines but got ${renderedLines.size}"
98+
}
99+
100+
for (i in expected.indices) {
101+
assert(renderedLines[i] == expected[i]) {
102+
"Line $i mismatch:\nExpected: '${expected[i]}'\nActual: '${renderedLines[i]}'"
103+
}
104+
}
105+
106+
println("\n✅ Test passed! Unified view correctly shows both old and new lines for CHANGED entries.")
107+
}
108+
}

gradlew

100644100755
File mode changed.

0 commit comments

Comments
 (0)