diff --git a/README.md b/README.md index 22d286a..811fd82 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This is a port of [java-diff-utils](https://github.com/java-diff-utils/java-diff with multiplatform support. All credit for the implementation goes to original authors. ## Features -All features from version 4.9 of the original library are present, except for: +All features from version 4.10 of the original library are present, except for: * Unified diff, which heavily uses file read/write and therefore needs a more complicated rewrite for kotlin-multiplatform * diff-utils-jgit, which uses JVM-only jgit library diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Chunk.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Chunk.kt index ae60881..8cc4139 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Chunk.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Chunk.kt @@ -46,17 +46,17 @@ public data class Chunk( * @param target the sequence to verify against. * @throws PatchFailedException */ - @Throws(PatchFailedException::class) - public fun verify(target: List) { - if (position > target.size || last() > target.size) { - throw PatchFailedException("Incorrect Chunk: the position of chunk > target size") - } - for (i in 0 until size()) { - if (target[position + i] != lines[i]) { - throw PatchFailedException( - "Incorrect Chunk: the chunk content doesn't match the target" - ) + public fun verify(target: List): VerifyChunk { + return if (position > target.size || last() > target.size) { + VerifyChunk.POSITION_OUT_OF_TARGET + } else if ( + (0 until size()).any { i -> + target[position + i] != lines[i] } + ) { + VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET + } else { + VerifyChunk.OK } } diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ConflictOutput.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ConflictOutput.kt new file mode 100644 index 0000000..3d490c8 --- /dev/null +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ConflictOutput.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2021 Peter Trifanov. + * Copyright 2021 java-diff-utils. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file has been modified by Peter Trifanov when porting from Java to Kotlin. + */ +package io.github.petertrr.diffutils.patch + +public fun interface ConflictOutput { + @Throws(PatchFailedException::class) + public fun processConflict(verifyChunk: VerifyChunk, delta: Delta, result: MutableList) +} + +public class ExceptionProducingConflictOutput : ConflictOutput { + override fun processConflict(verifyChunk: VerifyChunk, delta: Delta, result: MutableList) { + throw PatchFailedException( + "could not apply patch due to $verifyChunk" + ) + } +} + +public class ConflictProducingConflictOutput : ConflictOutput { + override fun processConflict(verifyChunk: VerifyChunk, delta: Delta, result: MutableList) { + if (result.size > delta.source.position) { + val orgData = mutableListOf() + (0 until delta.source.size()).forEach { _ -> + orgData.add( + result.removeAt(delta.source.position) + ) + } + orgData.add(0, "<<<<<< HEAD") + orgData.add("======") + orgData.addAll(delta.source.lines) + orgData.add(">>>>>>> PATCH") + result.addAll(delta.source.position, orgData) + } else { + TODO("Not supported yet.") + } + } +} diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Delta.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Delta.kt index 29cb6df..645cf6f 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Delta.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Delta.kt @@ -62,12 +62,21 @@ public sealed class Delta(public val type: DeltaType) { * @throws PatchFailedException */ @Throws(PatchFailedException::class) - protected open fun verifyChunk(target: List) { - source.verify(target) + protected open fun verifyChunkToFitTarget(target: List): VerifyChunk { + return source.verify(target) } @Throws(PatchFailedException::class) - public abstract fun applyTo(target: MutableList) + public open fun verifyAndApplyTo(target: MutableList): VerifyChunk { + val verify: VerifyChunk = verifyChunkToFitTarget(target) + if (verify == VerifyChunk.OK) { + applyTo(target) + } + return verify + } + + @Throws(PatchFailedException::class) + protected abstract fun applyTo(target: MutableList) public abstract fun restore(target: MutableList) @@ -77,8 +86,7 @@ public sealed class Delta(public val type: DeltaType) { public abstract fun withChunks(original: Chunk, revised: Chunk): Delta } public data class ChangeDelta(override val source: Chunk, override val target: Chunk) : Delta(DeltaType.CHANGE) { - override fun applyTo(target: MutableList) { - verifyChunk(target) + protected override fun applyTo(target: MutableList) { val position: Int = source.position val size: Int = source.size() for (i in 0 until size) { @@ -104,8 +112,7 @@ public data class ChangeDelta(override val source: Chunk, override val tar } public data class DeleteDelta(override val source: Chunk, override val target: Chunk) : Delta(DeltaType.DELETE) { - override fun applyTo(target: MutableList) { - verifyChunk(target) + protected override fun applyTo(target: MutableList) { val position = source.position for (i in 0 until source.size()) { target.removeAt(position) @@ -124,8 +131,7 @@ public data class DeleteDelta(override val source: Chunk, override val tar } public data class InsertDelta(override val source: Chunk, override val target: Chunk) : Delta(DeltaType.INSERT) { - override fun applyTo(target: MutableList) { - verifyChunk(target) + protected override fun applyTo(target: MutableList) { val position = this.source.position this.target.lines.forEachIndexed { i, line -> target.add(position + i, line) @@ -143,7 +149,7 @@ public data class InsertDelta(override val source: Chunk, override val tar } public data class EqualDelta(override val source: Chunk, override val target: Chunk) : Delta(DeltaType.EQUAL) { - override fun applyTo(target: MutableList): Unit = verifyChunk(target) + protected override fun applyTo(target: MutableList): Unit = Unit override fun restore(target: MutableList): Unit = Unit diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Patch.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Patch.kt index 6da12f9..9bc34ea 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Patch.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Patch.kt @@ -24,8 +24,11 @@ import io.github.petertrr.diffutils.algorithm.Change * Describes the patch holding all deltas between the original and revised texts. * * @param T The type of the compared elements in the 'lines'. + * @property conflictOutput Alter normal conflict output behaviour to e.g. inclide some conflict statements in the result, like git does it. */ -public class Patch { +public class Patch( + private var conflictOutput: ConflictOutput = ExceptionProducingConflictOutput(), +) { public var deltas: MutableList> = arrayListOf() get() { field.sortBy { it.source.position } @@ -43,7 +46,9 @@ public class Patch { val it = deltas.listIterator(deltas.size) while (it.hasPrevious()) { val delta = it.previous() - delta.applyTo(result) + delta.verifyAndApplyTo(result).takeIf { it != VerifyChunk.OK }?.let { + conflictOutput.processConflict(it, delta, result) + } } return result } @@ -75,6 +80,10 @@ public class Patch { return "Patch{deltas=$deltas}" } + public fun withConflictOutput(conflictOutput: ConflictOutput) { + this.conflictOutput = conflictOutput + } + public companion object { public fun generate(original: List, revised: List, changes: List): Patch { return generate(original, revised, changes, false) diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/VerifyChunk.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/VerifyChunk.kt new file mode 100644 index 0000000..2dc0aa3 --- /dev/null +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/VerifyChunk.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2021 Peter Trifanov. + * Copyright 2021 java-diff-utils. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file has been modified by Peter Trifanov when porting from Java to Kotlin. + */ +package io.github.petertrr.diffutils.patch + +public enum class VerifyChunk { + OK, + POSITION_OUT_OF_TARGET, + CONTENT_DOES_NOT_MATCH_TARGET, + ; +} diff --git a/src/commonTest/kotlin/io/github/petertrr/diffutils/patch/PatchTest.kt b/src/commonTest/kotlin/io/github/petertrr/diffutils/patch/PatchTest.kt index 61ad019..829a51c 100644 --- a/src/commonTest/kotlin/io/github/petertrr/diffutils/patch/PatchTest.kt +++ b/src/commonTest/kotlin/io/github/petertrr/diffutils/patch/PatchTest.kt @@ -60,4 +60,34 @@ class PatchTest { fail(e.message) } } + + @Test + fun testPatch_Change_withExceptionProcessor() { + val changeTest_from = listOf("aaa", "bbb", "ccc", "ddd") + val changeTest_to = listOf("aaa", "bxb", "cxc", "ddd") + val patch: Patch = diff(changeTest_from, changeTest_to) + patch.withConflictOutput(ConflictProducingConflictOutput()) + try { + val data: List = patch( + changeTest_from.toMutableList().apply { this[2] = "CDC" }, + patch + ) + assertEquals(9, data.size) + assertEquals( + listOf( + "aaa", + "<<<<<< HEAD", + "bbb", + "CDC", + "======", + "bbb", + "ccc", + ">>>>>>> PATCH", + "ddd" + ), data + ) + } catch (e: PatchFailedException) { + fail(e.message) + } + } } diff --git a/src/commonTest/kotlin/io/github/petertrr/diffutils/text/DiffRowGeneratorTest.kt b/src/commonTest/kotlin/io/github/petertrr/diffutils/text/DiffRowGeneratorTest.kt index 7eccaa5..2569a74 100644 --- a/src/commonTest/kotlin/io/github/petertrr/diffutils/text/DiffRowGeneratorTest.kt +++ b/src/commonTest/kotlin/io/github/petertrr/diffutils/text/DiffRowGeneratorTest.kt @@ -683,4 +683,35 @@ Bengal tiger panther but singapura but bombay munchkin for cougar. And more.""". ) assertEquals(DiffRow(DiffRow.Tag.DELETE, "~B~", ""), rows[1]) } + + @Test + fun testIssue119WrongContextLength() { + val original: String = + """ + const world: string = 'world', + p: number | undefined = 42; + + console.log(`Hello, ${'$'}world}!`); + """.trimIndent() + val revised: String = + """ + const world: string = 'world'; + const p: number | undefined = 42; + + console.log(`Hello, ${'$'}world}!`); + """.trimIndent() + val generator = DiffRowGenerator( + showInlineDiffs = true, + mergeOriginalRevised = true, + inlineDiffByWord = true, + oldTag = { _, _ -> "~" }, + newTag = { _, _ -> "**" }, + ) + val rows: List = generator.generateDiffRows( + original.split("\n"), + revised.split("\n") + ) + rows.filter { it.tag != DiffRow.Tag.EQUAL } + .forEach { println(it) } + } }