fix(jetbrains): render multi-hunk diffs

This commit is contained in:
kirillk
2026-08-04 09:38:14 -04:00
parent 4cdbe9d325
commit 6414e64b29
4 changed files with 74 additions and 6 deletions
@@ -0,0 +1,5 @@
---
"@kilocode/kilo-jetbrains": patch
---
Render multi-hunk modified-file diffs correctly in the JetBrains diff viewer.
@@ -53,11 +53,14 @@ internal object DiffPatchReconstruct {
}
}
}
// Both producers (CLI snapshot and branchDiff) emit a single full-context hunk. A patch with
// several hunks, or one whose header lengths don't match the reconstructed body, has elided
// context: reconstructing would place every line at the wrong number, so fall back to the
// raw-patch view (renderable = false) instead of showing a misaligned side-by-side diff.
if (hunks != 1 || oldSeen != oldLen || newSeen != newLen) return DiffSides("", "", false)
// A patch may carry several hunks (limited-context git output) or a single full-context hunk.
// We concatenate every hunk body into contiguous before/after text: unchanged context lines
// anchor each region so the resulting side-by-side still colors adds/removes correctly. The
// elided gaps between hunks collapse (line numbers restart at 1), which is acceptable for a
// "what changed" view and far better than the all-green raw-patch fallback. We still bail when
// there is no hunk, or when the header lengths don't match the reconstructed body (truncated
// context), because that would place lines against the wrong side.
if (hunks < 1 || oldSeen != oldLen || newSeen != newLen) return DiffSides("", "", false)
val left = if (added(patch)) "" else before.toString().removeSuffix("\n")
val right = if (deleted(patch)) "" else after.toString().removeSuffix("\n")
return DiffSides(left, right, true)
@@ -133,7 +133,42 @@ class DiffPatchReconstructTest {
}
@Test
fun `multi hunk partial context patch is not renderable`() {
fun `multi hunk patch reconstructs concatenated changed regions`() {
// Turn/tool diffs are ordinary limited-context git output with several hunks. The reconstruction
// stitches each hunk body into contiguous before/after text so the diff editor colors the
// changes instead of dumping the raw patch as all-added lines. Inter-hunk gaps collapse.
val dto = DiffFileDto(
file = "src/A.kt",
additions = 2,
deletions = 2,
patch = """
diff --git a/src/A.kt b/src/A.kt
--- a/src/A.kt
+++ b/src/A.kt
@@ -1,3 +1,3 @@
one
-two
+TWO
three
@@ -20,3 +20,3 @@
twenty
-x
+X
z
""".trimIndent(),
)
val sides = DiffPatchReconstruct.sides(dto)
assertTrue(sides.renderable)
assertEquals("one\ntwo\nthree\ntwenty\nx\nz", sides.before)
assertEquals("one\nTWO\nthree\ntwenty\nX\nz", sides.after)
}
@Test
fun `multi hunk patch with truncated context is not renderable`() {
// header claims 3 old / 3 new lines per hunk but the body carries only 2 of each: reconstructing
// would misalign the sides, so fall back to the raw-patch view.
val dto = DiffFileDto(
file = "src/A.kt",
additions = 2,
@@ -291,6 +291,31 @@ class KiloDiffEditorContentTest : BasePlatformTestCase() {
assertEquals("hello\nworld", contents[1])
}
fun `test diff request reconstructs multi hunk modified patch`() {
// Regression: multi-hunk modified diffs used to fall back to an empty original + raw patch on
// the right, rendering every line as added. They now reconstruct into a real side-by-side diff.
val patch = """
diff --git a/src/App.kt b/src/App.kt
--- a/src/App.kt
+++ b/src/App.kt
@@ -1,3 +1,3 @@
one
-two
+TWO
three
@@ -20,3 +20,3 @@
twenty
-x
+X
z
""".trimIndent()
val request = diffRequest(project, file("src/App.kt", 2, 2, patch = patch)) as SimpleDiffRequest
val contents = request.contents.map(::content)
assertEquals("one\ntwo\nthree\ntwenty\nx\nz", contents[0])
assertEquals("one\nTWO\nthree\ntwenty\nX\nz", contents[1])
}
fun `test tree displays absolute files relative to workspace`() {
val parent = Disposer.newDisposable()
try {