From f8d9f5b738d0f9ec002bd85a3e6418690f71c319 Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Tue, 9 Apr 2024 11:24:47 +0200 Subject: [PATCH 01/22] chore: bump Detekt max return count to 3 2 seems too restrictive, and it may actually reduce readability. --- detekt.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/detekt.yml b/detekt.yml index d2c7bad..3270a66 100644 --- a/detekt.yml +++ b/detekt.yml @@ -1,5 +1,7 @@ style: active: true + ReturnCount: + max: 3 MaxLineLength: active: true maxLineLength: 180 @@ -8,4 +10,4 @@ formatting: MaximumLineLength: active: false ParameterListWrapping: - active: false \ No newline at end of file + active: false From cab23dd0a1a97a85552bc8058626a349a6162868 Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Tue, 9 Apr 2024 11:26:04 +0200 Subject: [PATCH 02/22] refactor: cleanup DiffRow --- .../kotlin/io/github/petertrr/diffutils/text/DiffRow.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/text/DiffRow.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/text/DiffRow.kt index 0171d9c..e05a440 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/text/DiffRow.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/text/DiffRow.kt @@ -19,10 +19,13 @@ package io.github.petertrr.diffutils.text /** - * Describes the diff row in form [tag, oldLine, newLine) for showing the difference between two texts + * Describes the diff row in form `[tag, oldLine, newLine)` for showing the difference between two texts. */ public data class DiffRow(val tag: Tag, val oldLine: String, val newLine: String) { public enum class Tag { - INSERT, DELETE, CHANGE, EQUAL + INSERT, + DELETE, + CHANGE, + EQUAL, } } From 0dd6404ea4ca8c3e6a2f1a0e005bf52f7313ada4 Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Tue, 9 Apr 2024 11:27:11 +0200 Subject: [PATCH 03/22] refactor: cleanup StringUtils --- .../petertrr/diffutils/text/StringUtils.kt | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/text/StringUtils.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/text/StringUtils.kt index a086f24..acb70d6 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/text/StringUtils.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/text/StringUtils.kt @@ -16,54 +16,59 @@ * * This file has been modified by Peter Trifanov when porting from Java to Kotlin. */ +@file:JvmName("StringUtils") + package io.github.petertrr.diffutils.text +import kotlin.jvm.JvmName + /** - * Replaces all opening and closing tags with `<` or `>`. - * - * @param str - * @return str with some HTML meta characters escaped. + * Replaces all opening and closing tags (`<` and `>`) + * with their escaped sequences (`<` and `>`). */ -internal fun htmlEntities(str: String): String { - return str.replace("<", "<").replace(">", ">") -} +internal fun htmlEntities(str: String): String = + str.replace("<", "<").replace(">", ">") -internal fun normalize(str: String): String { - return htmlEntities(str).replace("\t", " ") -} +/** + * Normalizes a string by escaping some HTML meta characters + * and replacing tabs with 4 spaces each. + */ +internal fun normalize(str: String): String = + htmlEntities(str).replace("\t", " ") /** * Wrap the text with the given column width - * - * @param line the text - * @param columnWidth the given column - * @return the wrapped text */ internal fun wrapText(line: String, columnWidth: Int): String { - require(columnWidth >= 0) { "columnWidth may not be less 0" } + require(columnWidth >= 0) { "Column width must be greater than or equal to 0" } + if (columnWidth == 0) { return line } + val length = line.length val delimiter = "
".length var widthIndex = columnWidth val b = StringBuilder(line) var count = 0 + while (length > widthIndex) { var breakPoint = widthIndex + delimiter * count - if (b[breakPoint - 1].isHighSurrogate() && - b[breakPoint].isLowSurrogate() - ) { + + if (b[breakPoint - 1].isHighSurrogate() && b[breakPoint].isLowSurrogate()) { // Shift a breakpoint that would split a supplemental code-point. breakPoint += 1 + if (breakPoint == b.length) { // Break before instead of after if this is the last code-point. breakPoint -= 2 } } + b.insert(breakPoint, "
") widthIndex += columnWidth count++ } + return b.toString() } From fdd1fe7a5e0003f06b0631512347259b1debc081 Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Tue, 9 Apr 2024 11:28:32 +0200 Subject: [PATCH 04/22] refactor: extract DiffException and cleanup PatchFailedException --- .../petertrr/diffutils/patch/DiffException.kt | 21 +++++++++++++++++++ .../diffutils/patch/PatchFailedException.kt | 4 +--- 2 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 src/commonMain/kotlin/io/github/petertrr/diffutils/patch/DiffException.kt diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/DiffException.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/DiffException.kt new file mode 100644 index 0000000..423d3f5 --- /dev/null +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/DiffException.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2021 Peter Trifanov. + * Copyright 2009-2017 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 abstract class DiffException(message: String) : Exception(message) diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/PatchFailedException.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/PatchFailedException.kt index 74f4ac7..aace391 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/PatchFailedException.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/PatchFailedException.kt @@ -18,6 +18,4 @@ */ package io.github.petertrr.diffutils.patch -public open class DiffException(msg: String?) : Exception(msg) - -public class PatchFailedException(msg: String?) : DiffException(msg) +public class PatchFailedException(message: String) : DiffException(message) From d0dafbdeb5d9891d08bad0c3a0b55ee7d09bb0ef Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Tue, 9 Apr 2024 11:31:14 +0200 Subject: [PATCH 05/22] refactor: cleanup Patch --- .../github/petertrr/diffutils/patch/Patch.kt | 81 +++++++++++-------- 1 file changed, 49 insertions(+), 32 deletions(-) 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 9bc34ea..4a141b8 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Patch.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Patch.kt @@ -23,111 +23,128 @@ import io.github.petertrr.diffutils.algorithm.Change /** * Describes the patch holding all deltas between the original and revised texts. * + * @param conflictOutput Alter normal conflict output behaviour to e.g. include + * some conflict statements in the result, like Git does it. * @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( - private var conflictOutput: ConflictOutput = ExceptionProducingConflictOutput(), -) { - public var deltas: MutableList> = arrayListOf() +public class Patch(private var conflictOutput: ConflictOutput = ExceptionProducingConflictOutput()) { + public var deltas: MutableList> = ArrayList() get() { field.sortBy { it.source.position } return field } /** - * Apply this patch to the given target + * Apply this patch to the given target. * - * @return the patched text + * @return The patched text */ @Throws(PatchFailedException::class) public fun applyTo(target: List): List { val result = target.toMutableList() val it = deltas.listIterator(deltas.size) + while (it.hasPrevious()) { val delta = it.previous() - delta.verifyAndApplyTo(result).takeIf { it != VerifyChunk.OK }?.let { - conflictOutput.processConflict(it, delta, result) + val verifyChunk = delta.verifyAndApplyTo(result) + + if (verifyChunk != VerifyChunk.OK) { + conflictOutput.processConflict(verifyChunk, delta, result) } } + return result } /** - * Restore the text to original. Opposite to applyTo() method. + * Restore the text to its original form. Opposite of the [applyTo] method. * - * @param target the given target - * @return the restored text + * @param target The given target + * @return The restored text */ public fun restore(target: List): List { val result = target.toMutableList() val it = deltas.listIterator(deltas.size) + while (it.hasPrevious()) { val delta = it.previous() delta.restore(result) } + return result } /** - * Add the given delta to this patch + * Add the given delta to this patch. * - * @param delta the given delta + * @param delta The delta to add */ - public fun addDelta(delta: Delta): Boolean = deltas.add(delta) + public fun addDelta(delta: Delta): Boolean = + deltas.add(delta) - override fun toString(): String { - return "Patch{deltas=$deltas}" - } + override fun toString(): String = + "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) - } + public fun generate(original: List, revised: List, changes: List): Patch = + generate(original, revised, changes, false) - private fun buildChunk(start: Int, end: Int, data: List): Chunk { - return Chunk(start, data.subList(start, end)) - } + private fun buildChunk(start: Int, end: Int, data: List): Chunk = + Chunk(start, data.subList(start, end)) - public fun generate(original: List, revised: List, changes: List, includeEquals: Boolean): Patch { + public fun generate( + original: List, + revised: List, + changes: List, + includeEquals: Boolean, + ): Patch { val patch = Patch() var startOriginal = 0 var startRevised = 0 - changes.run { - if (includeEquals) sortedBy { it.startOriginal } else this - }.forEach { change -> + + val adjustedChanges = if (includeEquals) { + changes.sortedBy { it.startOriginal } + } else { + changes + } + + for (change in adjustedChanges) { if (includeEquals && startOriginal < change.startOriginal) { patch.addDelta( EqualDelta( buildChunk(startOriginal, change.startOriginal, original), - buildChunk(startRevised, change.startRevised, revised) + buildChunk(startRevised, change.startRevised, revised), ) ) } + val orgChunk = buildChunk(change.startOriginal, change.endOriginal, original) val revChunk = buildChunk(change.startRevised, change.endRevised, revised) + when (change.deltaType) { DeltaType.DELETE -> patch.addDelta(DeleteDelta(orgChunk, revChunk)) DeltaType.INSERT -> patch.addDelta(InsertDelta(orgChunk, revChunk)) DeltaType.CHANGE -> patch.addDelta(ChangeDelta(orgChunk, revChunk)) - else -> { - } + DeltaType.EQUAL -> {} } + startOriginal = change.endOriginal startRevised = change.endRevised } + if (includeEquals && startOriginal < original.size) { patch.addDelta( EqualDelta( buildChunk(startOriginal, original.size, original), - buildChunk(startRevised, revised.size, revised) + buildChunk(startRevised, revised.size, revised), ) ) } + return patch } } From 0b801c0e265229bdc84e10537527bc59c175893a Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Tue, 9 Apr 2024 11:37:38 +0200 Subject: [PATCH 06/22] refactor: extract ConflictProducingConflictOutput and ExceptionProducingConflictOutput --- .../diffutils/patch/ConflictOutput.kt | 28 ------------- .../patch/ConflictProducingConflictOutput.kt | 39 +++++++++++++++++++ .../patch/ExceptionProducingConflictOutput.kt | 24 ++++++++++++ 3 files changed, 63 insertions(+), 28 deletions(-) create mode 100644 src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ConflictProducingConflictOutput.kt create mode 100644 src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ExceptionProducingConflictOutput.kt diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ConflictOutput.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ConflictOutput.kt index fc09772..00f8f01 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ConflictOutput.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ConflictOutput.kt @@ -22,31 +22,3 @@ 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() - repeat(delta.source.size()) { _ -> - 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/ConflictProducingConflictOutput.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ConflictProducingConflictOutput.kt new file mode 100644 index 0000000..9b3255a --- /dev/null +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ConflictProducingConflictOutput.kt @@ -0,0 +1,39 @@ +/* + * 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 class ConflictProducingConflictOutput : ConflictOutput { + override fun processConflict(verifyChunk: VerifyChunk, delta: Delta, result: MutableList) { + if (result.size <= delta.source.position) { + throw UnsupportedOperationException("Not supported yet") + } + + val orgData = ArrayList() + + repeat(delta.source.size()) { + 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) + } +} diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ExceptionProducingConflictOutput.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ExceptionProducingConflictOutput.kt new file mode 100644 index 0000000..a1ab579 --- /dev/null +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ExceptionProducingConflictOutput.kt @@ -0,0 +1,24 @@ +/* + * 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 class ExceptionProducingConflictOutput : ConflictOutput { + override fun processConflict(verifyChunk: VerifyChunk, delta: Delta, result: MutableList): Nothing = + throw PatchFailedException("Could not apply patch due to $verifyChunk") +} From 9263f6079e6ad15ecfad8792400c326d90e3d063 Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Tue, 9 Apr 2024 11:38:16 +0200 Subject: [PATCH 07/22] refactor: extract Delta types --- .../petertrr/diffutils/patch/ChangeDelta.kt | 53 ++++++++ .../petertrr/diffutils/patch/DeleteDelta.kt | 44 +++++++ .../github/petertrr/diffutils/patch/Delta.kt | 120 +----------------- .../petertrr/diffutils/patch/DeltaType.kt | 55 ++++++++ .../petertrr/diffutils/patch/EqualDelta.kt | 35 +++++ .../petertrr/diffutils/patch/InsertDelta.kt | 44 +++++++ 6 files changed, 235 insertions(+), 116 deletions(-) create mode 100644 src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ChangeDelta.kt create mode 100644 src/commonMain/kotlin/io/github/petertrr/diffutils/patch/DeleteDelta.kt create mode 100644 src/commonMain/kotlin/io/github/petertrr/diffutils/patch/DeltaType.kt create mode 100644 src/commonMain/kotlin/io/github/petertrr/diffutils/patch/EqualDelta.kt create mode 100644 src/commonMain/kotlin/io/github/petertrr/diffutils/patch/InsertDelta.kt diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ChangeDelta.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ChangeDelta.kt new file mode 100644 index 0000000..3367b14 --- /dev/null +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ChangeDelta.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2021 Peter Trifanov. + * Copyright 2018 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 data class ChangeDelta( + override val source: Chunk, + override val target: Chunk, +) : Delta(DeltaType.CHANGE) { + override fun applyTo(target: MutableList) { + val position = source.position + val size = source.size() + + for (i in 0..) { + val position = this.target.position + val size = this.target.size() + + for (i in 0.., revised: Chunk): Delta = + ChangeDelta(original, revised) +} diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/DeleteDelta.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/DeleteDelta.kt new file mode 100644 index 0000000..f24a7d5 --- /dev/null +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/DeleteDelta.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2021 Peter Trifanov. + * Copyright 2018 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 data class DeleteDelta( + override val source: Chunk, + override val target: Chunk, +) : Delta(DeltaType.DELETE) { + override fun applyTo(target: MutableList) { + val position = source.position + + for (i in 0..) { + val position = this.target.position + val lines = this.source.lines + + for ((i, line) in lines.withIndex()) { + target.add(position + i, line) + } + } + + override fun withChunks(original: Chunk, revised: Chunk): Delta = + DeleteDelta(original, revised) +} 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 5370b73..57f354a 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Delta.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Delta.kt @@ -19,50 +19,14 @@ package io.github.petertrr.diffutils.patch /** - * Specifies the type of the delta. There are three types of modifications from - * the original to get the revised text. - * - * CHANGE: a block of data of the original is replaced by another block of data. - * DELETE: a block of data of the original is removed - * INSERT: at a position of the original a block of data is inserted - * - * to be complete there is also - * - * EQUAL: a block of data of original and the revised text is equal - * - * which is no change at all. - * + * A delta between a source and a target. */ -public enum class DeltaType { - /** - * A change in the original. - */ - CHANGE, - - /** - * A delete from the original. - */ - DELETE, - - /** - * An insert into the original. - */ - INSERT, - - /** - * An do nothing. - */ - EQUAL -} - public sealed class Delta(public val type: DeltaType) { public abstract val source: Chunk public abstract val target: Chunk /** * Verify the chunk of this delta, to fit the target. - * @param target - * @throws PatchFailedException */ @Throws(PatchFailedException::class) protected open fun verifyChunkToFitTarget(target: List): VerifyChunk { @@ -71,10 +35,12 @@ public sealed class Delta(public val type: DeltaType) { @Throws(PatchFailedException::class) public open fun verifyAndApplyTo(target: MutableList): VerifyChunk { - val verify: VerifyChunk = verifyChunkToFitTarget(target) + val verify = verifyChunkToFitTarget(target) + if (verify == VerifyChunk.OK) { applyTo(target) } + return verify } @@ -88,81 +54,3 @@ 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 -) { - protected override fun applyTo(target: MutableList) { - val position: Int = source.position - val size: Int = source.size() - for (i in 0 until size) { - target.removeAt(position) - } - this.target.lines.forEachIndexed { i, line -> - target.add(position + i, line) - } - } - - override fun restore(target: MutableList) { - val position: Int = this.target.position - val size: Int = this.target.size() - for (i in 0 until size) { - target.removeAt(position) - } - source.lines.forEachIndexed { i, line -> - target.add(position + i, line) - } - } - - override fun withChunks(original: Chunk, revised: Chunk): Delta = ChangeDelta(original, revised) -} - -public data class DeleteDelta(override val source: Chunk, override val target: Chunk) : Delta( - DeltaType.DELETE -) { - protected override fun applyTo(target: MutableList) { - val position = source.position - for (i in 0 until source.size()) { - target.removeAt(position) - } - } - - override fun restore(target: MutableList) { - val position: Int = this.target.position - val lines: List = this.source.lines - lines.forEachIndexed { i, line -> - target.add(position + i, line) - } - } - - override fun withChunks(original: Chunk, revised: Chunk): Delta = DeleteDelta(original, revised) -} - -public data class InsertDelta(override val source: Chunk, override val target: Chunk) : Delta( - DeltaType.INSERT -) { - protected override fun applyTo(target: MutableList) { - val position = this.source.position - this.target.lines.forEachIndexed { i, line -> - target.add(position + i, line) - } - } - - override fun restore(target: MutableList) { - val position = this.target.position - for (i in 0 until this.target.size()) { - target.removeAt(position) - } - } - - override fun withChunks(original: Chunk, revised: Chunk): Delta = InsertDelta(original, revised) -} - -public data class EqualDelta(override val source: Chunk, override val target: Chunk) : Delta( - DeltaType.EQUAL -) { - protected override fun applyTo(target: MutableList): Unit = Unit - - override fun restore(target: MutableList): Unit = Unit - - override fun withChunks(original: Chunk, revised: Chunk): Delta = EqualDelta(original, revised) -} diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/DeltaType.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/DeltaType.kt new file mode 100644 index 0000000..f99e270 --- /dev/null +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/DeltaType.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2021 Peter Trifanov. + * Copyright 2018 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 + +/** + * Specifies the type of the delta. There are three types of modifications from + * the original to get the revised text. + * + * - CHANGE: a block of data of the original is replaced by another block of data. + * - DELETE: a block of data of the original is removed + * - INSERT: at a position of the original a block of data is inserted + * + * To be complete there is also + * + * - EQUAL: a block of data of original and the revised text is equal + * + * which is no change at all. + */ +public enum class DeltaType { + /** + * A change in the original. + */ + CHANGE, + + /** + * A delete from the original. + */ + DELETE, + + /** + * An insert into the original. + */ + INSERT, + + /** + * Do nothing. + */ + EQUAL, +} diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/EqualDelta.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/EqualDelta.kt new file mode 100644 index 0000000..86a7fff --- /dev/null +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/EqualDelta.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2021 Peter Trifanov. + * Copyright 2018 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 data class EqualDelta( + override val source: Chunk, + override val target: Chunk, +) : Delta(DeltaType.EQUAL) { + override fun applyTo(target: MutableList) { + // Noop + } + + override fun restore(target: MutableList) { + // Noop + } + + override fun withChunks(original: Chunk, revised: Chunk): Delta = + EqualDelta(original, revised) +} diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/InsertDelta.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/InsertDelta.kt new file mode 100644 index 0000000..b1b94f2 --- /dev/null +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/InsertDelta.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2021 Peter Trifanov. + * Copyright 2018 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 data class InsertDelta( + override val source: Chunk, + override val target: Chunk, +) : Delta(DeltaType.INSERT) { + override fun applyTo(target: MutableList) { + val position = this.source.position + + for ((i, line) in this.target.lines.withIndex()) { + target.add(position + i, line) + } + } + + override fun restore(target: MutableList) { + val position = this.target.position + val size = this.target.size() + + for (i in 0.., revised: Chunk): Delta = + InsertDelta(original, revised) +} From 67e24986b6fa7da3c602aa753871bf4d8cf55822 Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Tue, 9 Apr 2024 11:39:00 +0200 Subject: [PATCH 08/22] refactor: cleanup Chunk --- .../github/petertrr/diffutils/patch/Chunk.kt | 53 +++++++++---------- 1 file changed, 25 insertions(+), 28 deletions(-) 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 8cc4139..2f83153 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Chunk.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Chunk.kt @@ -19,55 +19,52 @@ package io.github.petertrr.diffutils.patch /** - * Holds the information about the part of text involved in the diff process + * Holds the information about the part of text involved in the diff process. * * Text is represented as generic class `T` because the diff engine is capable of handling more * than plain ASCII. In fact, arrays or lists of any type that implements * `hashCode()` and `equals()` correctly can be subject to differencing using this library. - * @param T The type of the compared elements in the 'lines'. + * + * @param position The start position of chunk in the text + * @param lines The affected lines + * @param changePosition The positions of changed lines of chunk in the text + * @param T The type of the compared elements in the 'lines' */ public data class Chunk( - /** - * the start position of chunk in the text - */ val position: Int, - /** - * the affected lines - */ val lines: List, - /** - * the positions of changed lines of chunk in the text - */ - val changePosition: List? = null + val changePosition: List? = null, ) { /** * Verifies that this chunk's saved text matches the corresponding text in the given sequence. * - * @param target the sequence to verify against. - * @throws PatchFailedException + * @param target The sequence to verify against */ 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] + val targetSize = target.size + + if (position > targetSize || last() > targetSize) { + return VerifyChunk.POSITION_OUT_OF_TARGET + } + + for (i in 0.. Date: Tue, 9 Apr 2024 11:40:59 +0200 Subject: [PATCH 09/22] refactor: cleanup DiffAlgorithmListener --- .../diffutils/algorithm/DiffAlgorithmListener.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/DiffAlgorithmListener.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/DiffAlgorithmListener.kt index 8dcc71b..4645f1e 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/DiffAlgorithmListener.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/DiffAlgorithmListener.kt @@ -19,17 +19,21 @@ package io.github.petertrr.diffutils.algorithm public interface DiffAlgorithmListener { + /** + * Notifies computing a diff has started. + */ public fun diffStart() /** - * This is a step within the diff algorithm. Due to different implementations the value - * is not strict incrementing to the max and is not guarantee to reach the max. It could - * stop before. + * Notifies of a step within the diff algorithm. * - * @param value - * @param max + * Due to different implementations the value is not strict incrementing + * to the max and is not guarantee to reach the max. It could stop before. */ public fun diffStep(value: Int, max: Int) + /** + * Notifies computing a diff has ended. + */ public fun diffEnd() } From 09f67221e534622373be24d136affecad6df159b Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Tue, 9 Apr 2024 11:42:26 +0200 Subject: [PATCH 10/22] refactor: cleanup DiffAlgorithm --- .../diffutils/algorithm/DiffAlgorithm.kt | 25 +++---------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/DiffAlgorithm.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/DiffAlgorithm.kt index 00233f3..4af9d68 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/DiffAlgorithm.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/DiffAlgorithm.kt @@ -19,30 +19,13 @@ package io.github.petertrr.diffutils.algorithm /** - * Interface of a diff algorithm. + * Describes a diff algorithm. * - * @param T type of data that is diffed. + * @param T The type of data that should be diffed */ public interface DiffAlgorithm { /** - * Computes the changeset to patch the source list to the target list. - * - * @param source source data - * @param target target data - * @param progress progress listener - * @return + * Computes the changeset to patch the [source] list to the [target] list. */ - public fun computeDiff(source: List, target: List, progress: DiffAlgorithmListener?): List - - /** - * Simple extension to compute a changeset using arrays. - * - * @param source - * @param target - * @param progress - * @return - */ - public fun computeDiff(source: Array, target: Array, progress: DiffAlgorithmListener?): List { - return computeDiff(source.toList(), target.toList(), progress) - } + public fun computeDiff(source: List, target: List, progress: DiffAlgorithmListener? = null): List } From 9ec8eafdfd0373f408a7d79ab3db0d3d94761b4a Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Tue, 9 Apr 2024 11:42:44 +0200 Subject: [PATCH 11/22] refactor: cleanup Change --- .../kotlin/io/github/petertrr/diffutils/algorithm/Change.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/Change.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/Change.kt index 01985b5..d4b264f 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/Change.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/Change.kt @@ -25,5 +25,5 @@ public data class Change( val startOriginal: Int, val endOriginal: Int, val startRevised: Int, - val endRevised: Int + val endRevised: Int, ) From 277c23331776afb7e83d17a38bfb95fdd855f8ad Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Tue, 9 Apr 2024 11:45:01 +0200 Subject: [PATCH 12/22] refactor: cleanup PathNode --- .../diffutils/algorithm/myers/PathNode.kt | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/myers/PathNode.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/myers/PathNode.kt index e4b4bb8..e8d4363 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/myers/PathNode.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/myers/PathNode.kt @@ -20,47 +20,42 @@ package io.github.petertrr.diffutils.algorithm.myers /** * A node in a diffpath. + * + * @param i Position in the original sequence + * @param j Position in the revised sequence + * @param snake + * @param bootstrap Is this a bootstrap node? + * In bootstrap nodes one of the two coordinates is less than zero. + * @param prev The previous node in the path, if any */ internal class PathNode( - /** - * Position in the original sequence. - */ val i: Int, - /** - * Position in the revised sequence. - */ val j: Int, val snake: Boolean, - /** - * Is this a bootstrap node? - * In bootstrap nodes one of the two coordinates is less than zero. - */ val bootstrap: Boolean, - prev: PathNode? = null + prev: PathNode? = null, ) { /** * The previous node in the path. */ - val prev: PathNode? - - init { - if (snake) { - this.prev = prev - } else { - this.prev = prev?.previousSnake() - } + val prev: PathNode? = if (snake) { + prev + } else { + prev?.previousSnake() } /** * Skips sequences of [PathNodes][PathNode] until a snake or bootstrap node is found, or the end of the * path is reached. * - * @return The next first [PathNode] or bootstrap node in the path, or `null` if none found. + * @return The next first [PathNode] or bootstrap node in the path, or `null` if none found */ + @Suppress("MemberVisibilityCanBePrivate") fun previousSnake(): PathNode? { if (bootstrap) { return null } + return if (!snake && prev != null) { prev.previousSnake() } else { @@ -68,6 +63,20 @@ internal class PathNode( } } - override fun toString() = generateSequence(this) { it.prev } - .joinToString(prefix = "[", postfix = "]") { "(${it.i}, ${it.j})" } + override fun toString(): String { + val buf = StringBuilder("[") + var node: PathNode? = this + + while (node != null) { + buf.append("(") + buf.append(node.i) + buf.append(",") + buf.append(node.j) + buf.append(")") + node = node.prev + } + + buf.append("]") + return buf.toString() + } } From 8dc3387da25a4ce8a9aee58a3c52100fb1fff12a Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Tue, 9 Apr 2024 11:48:19 +0200 Subject: [PATCH 13/22] refactor: cleanup MyersDiff --- .../diffutils/algorithm/myers/MyersDiff.kt | 76 ++++++++++++------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/myers/MyersDiff.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/myers/MyersDiff.kt index 2c615e4..cf504d0 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/myers/MyersDiff.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/myers/MyersDiff.kt @@ -26,29 +26,31 @@ import io.github.petertrr.diffutils.patch.DeltaType /** * A clean-room implementation of Eugene Myers greedy differencing algorithm. */ -internal class MyersDiff(private val equalizer: (T, T) -> Boolean = { t1, t2 -> t1 == t2 }) : DiffAlgorithm { +public class MyersDiff(private val equalizer: (T, T) -> Boolean = { t1, t2 -> t1 == t2 }) : DiffAlgorithm { /** - * Return empty diff if get the error while procession the difference. + * Returns an empty diff if we get an error while procession the difference. */ override fun computeDiff(source: List, target: List, progress: DiffAlgorithmListener?): List { progress?.diffStart() - val path = buildPath(source, target, progress) + + val path = buildPath(source, target, progress) ?: error("Expected a non-null path node") val result = buildRevision(path) progress?.diffEnd() + return result } /** * Computes the minimum diffpath that expresses the differences between the original and revised - * sequences, according to Gene Myers differencing algorithm. + * sequences, according to Eugene Myers differencing algorithm. * - * @param orig The original sequence. - * @param rev The revised sequence. - * @return A minimum [PathNode] across the differences graph. - * @throws IllegalStateException if a diff path could not be found. + * @param orig The original sequence + * @param rev The revised sequence + * @return A minimum [PathNode] across the differences graph + * @throws IllegalStateException If a diff path could not be found */ private fun buildPath(orig: List, rev: List, progress: DiffAlgorithmListener?): PathNode? { - // these are local constants + // These are local constants val origSize = orig.size val revSize = rev.size val max = origSize + revSize + 1 @@ -56,15 +58,18 @@ internal class MyersDiff(private val equalizer: (T, T) -> Boolean = { t1, t2 val middle = size / 2 val diagonal: Array = arrayOfNulls(size) diagonal[middle + 1] = PathNode(0, -1, snake = true, bootstrap = true, prev = null) - for (d in 0 until max) { + + for (d in 0..(private val equalizer: (T, T) -> Boolean = { t1, t2 i = diagonal[kminus]!!.i + 1 prev = diagonal[kminus] } - diagonal[kminus] = null // no longer used + + diagonal[kminus] = null // No longer used var j = i - k var node = PathNode(i, j, snake = false, bootstrap = false, prev = prev) + while (i < origSize && j < revSize && equalizer.invoke(orig[i], rev[j])) { i++ j++ } + if (i != node.i) { node = PathNode(i, j, snake = true, bootstrap = false, prev = node) } + diagonal[kmiddle] = node + if (i >= origSize && j >= revSize) { return diagonal[kmiddle] } + k += 2 } + diagonal[middle + d - 1] = null } - error("could not find a diff path") + + error("Could not find a diff path") } /** * Constructs a patch from a difference path. * - * @param actualPath The path. - * @param orig The original sequence. - * @param rev The revised sequence. - * @return A list of [Change]s corresponding to the path. - * @throws IllegalStateException if a patch could not be built from the given path. + * @param actualPath The path + * @return A list of [Change]s corresponding to the path + * @throws IllegalStateException If a patch could not be built from the given path */ - private fun buildRevision(actualPath: PathNode?): List { - var path: PathNode? = actualPath - val changes: MutableList = mutableListOf() - if (path!!.snake) { - path = path.prev + private fun buildRevision(actualPath: PathNode): List { + var path = if (actualPath.snake) { + actualPath.prev + } else { + actualPath } + + val changes = ArrayList() + + // This can be improved to avoid the non-null assertion on prev while (path?.prev != null && path.prev!!.j >= 0) { - check(!path.snake) { "bad diffpath: found snake when looking for diff" } - val i: Int = path.i - val j: Int = path.j - path = path.prev - val iAnchor: Int = path!!.i - val jAnchor: Int = path.j + check(!path.snake) { "Bad diffpath: found snake when looking for diff" } + + val i = path.i + val j = path.j + path = path.prev ?: error("Expected a non-null previous path node") + + val iAnchor = path.i + val jAnchor = path.j + if (iAnchor == i && jAnchor != j) { changes.add(Change(DeltaType.INSERT, iAnchor, i, jAnchor, j)) } else if (iAnchor != i && jAnchor == j) { @@ -122,10 +140,12 @@ internal class MyersDiff(private val equalizer: (T, T) -> Boolean = { t1, t2 } else { changes.add(Change(DeltaType.CHANGE, iAnchor, i, jAnchor, j)) } + if (path.snake) { path = path.prev } } + return changes } } From 45b0c832da5cf398943695186ceab2d3c0b07d02 Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Tue, 9 Apr 2024 11:58:52 +0200 Subject: [PATCH 14/22] refactor: cleanup DiffUtils --- .../io/github/petertrr/diffutils/DiffUtils.kt | 233 ++++++++---------- 1 file changed, 100 insertions(+), 133 deletions(-) diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/DiffUtils.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/DiffUtils.kt index 7790bdc..3a4f949 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/DiffUtils.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/DiffUtils.kt @@ -16,11 +16,7 @@ * * This file has been modified by Peter Trifanov when porting from Java to Kotlin. */ -/** - * Implements the difference and patching engine - */ - -@file:Suppress("TooManyFunctions") +@file:JvmName("DiffUtils") package io.github.petertrr.diffutils @@ -29,174 +25,145 @@ import io.github.petertrr.diffutils.algorithm.DiffAlgorithmListener import io.github.petertrr.diffutils.algorithm.myers.MyersDiff import io.github.petertrr.diffutils.patch.Patch import io.github.petertrr.diffutils.patch.PatchFailedException +import io.github.petertrr.diffutils.text.DiffRowGenerator +import kotlin.jvm.JvmName +import kotlin.jvm.JvmOverloads -/** - * Computes the difference between the original and revised list of elements with default diff - * algorithm - * - * @param T types to be diffed - * @param original The original text. - * @param revised The revised text. - * @param progress progress listener - * @return The patch describing the difference between the original and revised sequences. - */ -public fun diff(original: List, revised: List, progress: DiffAlgorithmListener?): Patch { - return diff(original, revised, MyersDiff(), progress) -} - -public fun diff(original: List, revised: List): Patch { - return diff(original, revised, MyersDiff(), null) -} - -public fun diff(original: List, revised: List, includeEqualParts: Boolean): Patch { - return diff(original, revised, MyersDiff(), null, includeEqualParts) -} +// Instead of asking consumers to normalize their line endings, we simply catch them all. +private val lineBreak = Regex("\r\n|\r|\n") /** - * Computes the difference between the original and revised text. + * Computes the difference between the source and target text. + * + * By default, uses the Myers algorithm. + * + * @param sourceText The original text + * @param targetText The target text + * @param algorithm The diff algorithm to use + * @param progress The diff algorithm progress listener + * @param includeEqualParts Whether to include equal data parts into the patch. `false` by default. + * @return The patch describing the difference between the original and target text */ +@JvmOverloads public fun diff( sourceText: String, targetText: String, - progress: DiffAlgorithmListener? -): Patch { - return diff( - sourceText.split("\n"), - targetText.split("\n"), - progress + algorithm: DiffAlgorithm = MyersDiff(), + progress: DiffAlgorithmListener? = null, + includeEqualParts: Boolean = false, +): Patch = + diff( + source = sourceText.split(lineBreak), + target = targetText.split(lineBreak), + algorithm = algorithm, + progress = progress, + includeEqualParts = includeEqualParts, ) -} /** - * Computes the difference between the original and revised list of elements with default diff - * algorithm - * - * @param source The original text. - * @param target The revised text. + * Computes the difference between the source and target list of elements using the Myers algorithm. * - * @param equalizer the equalizer object to replace the default compare algorithm - * (Object.equals). If `null` the default equalizer of the default algorithm is used.. - * @return The patch describing the difference between the original and revised sequences. + * @param source The original elements + * @param target The target elements + * @param equalizer The equalizer to replace the default compare algorithm [Any.equals]. + * If `null`, the default equalizer of the default algorithm is used. + * @return The patch describing the difference between the source and target sequences */ public fun diff( source: List, target: List, - equalizer: ((T, T) -> Boolean)? -): Patch { - return if (equalizer != null) { - diff( - source, - target, - MyersDiff(equalizer) - ) - } else { - diff(source, target, MyersDiff()) - } -} - -public fun diff( - original: List, - revised: List, - algorithm: DiffAlgorithm, - progress: DiffAlgorithmListener? -): Patch { - return diff(original, revised, algorithm, progress, false) -} + equalizer: ((T, T) -> Boolean), +): Patch = + diff( + source = source, + target = target, + algorithm = MyersDiff(equalizer), + ) /** - * Computes the difference between the original and revised list of elements with default diff - * algorithm + * Computes the difference between the original and target list of elements. * - * @param original The original text. Must not be `null`. - * @param revised The revised text. Must not be `null`. - * @param algorithm The diff algorithm. Must not be `null`. - * @param progress The diff algorithm listener. - * @param includeEqualParts Include equal data parts into the patch. - * @return The patch describing the difference between the original and revised sequences. Never - * `null`. - */ -public fun diff( - original: List, - revised: List, - algorithm: DiffAlgorithm, - progress: DiffAlgorithmListener?, - includeEqualParts: Boolean -): Patch { - return Patch.generate(original, revised, algorithm.computeDiff(original, revised, progress), includeEqualParts) -} - -/** - * Computes the difference between the original and revised list of elements with default diff - * algorithm + * By default, uses the Meyers algorithm. * - * @param original The original text. Must not be `null`. - * @param revised The revised text. Must not be `null`. - * @param algorithm The diff algorithm. Must not be `null`. - * @return The patch describing the difference between the original and revised sequences. Never - * `null`. + * @param source The original elements + * @param target The target elements + * @param algorithm The diff algorithm to use + * @param progress The diff algorithm progress listener + * @param includeEqualParts Whether to include equal data parts into the patch. `false` by default. + * @return The patch describing the difference between the original and target sequences */ -public fun diff(original: List, revised: List, algorithm: DiffAlgorithm): Patch { - return diff(original, revised, algorithm, null) -} +@JvmOverloads +public fun diff( + source: List, + target: List, + algorithm: DiffAlgorithm = MyersDiff(), + progress: DiffAlgorithmListener? = null, + includeEqualParts: Boolean = false, +): Patch = + Patch.generate( + original = source, + revised = target, + changes = algorithm.computeDiff(source, target, progress), + includeEquals = includeEqualParts, + ) /** - * Computes the difference between the given texts inline. This one uses the "trick" to make out - * of texts lists of characters, like DiffRowGenerator does and merges those changes at the end - * together again. + * Computes the difference between the given texts inline. * - * @param original - * @param revised - * @return + * This one uses the "trick" to make out of texts lists of characters, + * like [DiffRowGenerator] does and merges those changes at the end together again. */ public fun diffInline(original: String, revised: String): Patch { - val origList: MutableList = arrayListOf() - val revList: MutableList = arrayListOf() - for (character in original.toCharArray()) { + val origChars = original.toCharArray() + val origList = ArrayList(origChars.size) + + val revChars = revised.toCharArray() + val revList = ArrayList(revChars.size) + + for (character in origChars) { origList.add(character.toString()) } - for (character in revised.toCharArray()) { + + for (character in revChars) { revList.add(character.toString()) } - val patch: Patch = diff(origList, revList) - patch.deltas.map { delta -> - delta.withChunks( - delta.source.copy(lines = compressLines(delta.source.lines, "")), - delta.target.copy(lines = compressLines(delta.target.lines, "")) + + val patch = diff(origList, revList) + patch.deltas = patch.deltas.mapTo(ArrayList(patch.deltas.size)) { + it.withChunks( + it.source.copy(lines = compressLines(it.source.lines, "")), + it.target.copy(lines = compressLines(it.target.lines, "")), ) } - .let { patch.deltas = it.toMutableList() } - return patch -} -private fun compressLines(lines: List, delimiter: String): List { - return if (lines.isEmpty()) { - emptyList() - } else { - listOf(lines.joinToString(delimiter)) - } + return patch } /** - * Patch the original text with given patch + * Patch the original text with the given patch. * - * @param original the original text - * @param patch the given patch - * @return the revised text - * @throws PatchFailedException if can't apply patch + * @param original The original text + * @param patch The patch to apply + * @return The revised text + * @throws PatchFailedException If the patch cannot be applied */ @Throws(PatchFailedException::class) -public fun patch(original: List, patch: Patch): List { - return patch.applyTo(original) -} +public fun patch(original: List, patch: Patch): List = + patch.applyTo(original) /** * Unpatch the revised text for a given patch * - * @param revised the revised text - * @param patch the given patch - * @return the original text + * @param revised The revised text + * @param patch The given patch + * @return The original text */ -@Suppress("UNUSED") -public fun unpatch(revised: List, patch: Patch): List { - return patch.restore(revised) -} +public fun unpatch(revised: List, patch: Patch): List = + patch.restore(revised) + +private fun compressLines(lines: List, delimiter: String): List = + if (lines.isEmpty()) { + emptyList() + } else { + listOf(lines.joinToString(delimiter)) + } From ddb263a9da6f458f47b227c4766921d8fb944de8 Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Tue, 9 Apr 2024 12:10:13 +0200 Subject: [PATCH 15/22] refactor: replace nullable DiffAlgorithmListener with NoopAlgorithmListener --- .../io/github/petertrr/diffutils/DiffUtils.kt | 5 +-- .../diffutils/algorithm/DiffAlgorithm.kt | 6 +++- .../algorithm/NoopAlgorithmListener.kt | 35 +++++++++++++++++++ .../diffutils/algorithm/myers/MyersDiff.kt | 10 +++--- 4 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/NoopAlgorithmListener.kt diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/DiffUtils.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/DiffUtils.kt index 3a4f949..950f6f6 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/DiffUtils.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/DiffUtils.kt @@ -22,6 +22,7 @@ package io.github.petertrr.diffutils import io.github.petertrr.diffutils.algorithm.DiffAlgorithm import io.github.petertrr.diffutils.algorithm.DiffAlgorithmListener +import io.github.petertrr.diffutils.algorithm.NoopAlgorithmListener import io.github.petertrr.diffutils.algorithm.myers.MyersDiff import io.github.petertrr.diffutils.patch.Patch import io.github.petertrr.diffutils.patch.PatchFailedException @@ -49,7 +50,7 @@ public fun diff( sourceText: String, targetText: String, algorithm: DiffAlgorithm = MyersDiff(), - progress: DiffAlgorithmListener? = null, + progress: DiffAlgorithmListener = NoopAlgorithmListener(), includeEqualParts: Boolean = false, ): Patch = diff( @@ -97,7 +98,7 @@ public fun diff( source: List, target: List, algorithm: DiffAlgorithm = MyersDiff(), - progress: DiffAlgorithmListener? = null, + progress: DiffAlgorithmListener = NoopAlgorithmListener(), includeEqualParts: Boolean = false, ): Patch = Patch.generate( diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/DiffAlgorithm.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/DiffAlgorithm.kt index 4af9d68..612dab3 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/DiffAlgorithm.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/DiffAlgorithm.kt @@ -27,5 +27,9 @@ public interface DiffAlgorithm { /** * Computes the changeset to patch the [source] list to the [target] list. */ - public fun computeDiff(source: List, target: List, progress: DiffAlgorithmListener? = null): List + public fun computeDiff( + source: List, + target: List, + progress: DiffAlgorithmListener = NoopAlgorithmListener(), + ): List } diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/NoopAlgorithmListener.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/NoopAlgorithmListener.kt new file mode 100644 index 0000000..be17652 --- /dev/null +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/NoopAlgorithmListener.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2024 Peter Trifanov. + * + * 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.algorithm + +/** + * A diff algorithm progress listener that does nothing. + */ +public class NoopAlgorithmListener : DiffAlgorithmListener { + override fun diffStart() { + // Noop + } + + override fun diffStep(value: Int, max: Int) { + // Noop + } + + override fun diffEnd() { + // Noop + } +} diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/myers/MyersDiff.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/myers/MyersDiff.kt index cf504d0..d7288fd 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/myers/MyersDiff.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/myers/MyersDiff.kt @@ -30,12 +30,12 @@ public class MyersDiff(private val equalizer: (T, T) -> Boolean = { t1, t2 -> /** * Returns an empty diff if we get an error while procession the difference. */ - override fun computeDiff(source: List, target: List, progress: DiffAlgorithmListener?): List { - progress?.diffStart() + override fun computeDiff(source: List, target: List, progress: DiffAlgorithmListener): List { + progress.diffStart() val path = buildPath(source, target, progress) ?: error("Expected a non-null path node") val result = buildRevision(path) - progress?.diffEnd() + progress.diffEnd() return result } @@ -49,7 +49,7 @@ public class MyersDiff(private val equalizer: (T, T) -> Boolean = { t1, t2 -> * @return A minimum [PathNode] across the differences graph * @throws IllegalStateException If a diff path could not be found */ - private fun buildPath(orig: List, rev: List, progress: DiffAlgorithmListener?): PathNode? { + private fun buildPath(orig: List, rev: List, progress: DiffAlgorithmListener): PathNode? { // These are local constants val origSize = orig.size val revSize = rev.size @@ -60,7 +60,7 @@ public class MyersDiff(private val equalizer: (T, T) -> Boolean = { t1, t2 -> diagonal[middle + 1] = PathNode(0, -1, snake = true, bootstrap = true, prev = null) for (d in 0.. Date: Tue, 9 Apr 2024 12:10:57 +0200 Subject: [PATCH 16/22] tests: align test cases with the refactored code --- .../petertrr/diffutils/DiffUtilsTest.kt | 26 +++++++++---------- .../algorithm/myers/MyersDiffTest.kt | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/commonTest/kotlin/io/github/petertrr/diffutils/DiffUtilsTest.kt b/src/commonTest/kotlin/io/github/petertrr/diffutils/DiffUtilsTest.kt index 5b3350d..d6e45b0 100644 --- a/src/commonTest/kotlin/io/github/petertrr/diffutils/DiffUtilsTest.kt +++ b/src/commonTest/kotlin/io/github/petertrr/diffutils/DiffUtilsTest.kt @@ -18,16 +18,10 @@ */ package io.github.petertrr.diffutils -import io.github.petertrr.diffutils.patch.ChangeDelta -import io.github.petertrr.diffutils.patch.Chunk -import io.github.petertrr.diffutils.patch.DeleteDelta -import io.github.petertrr.diffutils.patch.EqualDelta -import io.github.petertrr.diffutils.patch.InsertDelta -import io.github.petertrr.diffutils.patch.Patch +import io.github.petertrr.diffutils.patch.* import io.github.petertrr.diffutils.utils.changeDeltaOf import io.github.petertrr.diffutils.utils.deleteDeltaOf import io.github.petertrr.diffutils.utils.insertDeltaOf - import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull @@ -128,7 +122,7 @@ class DiffUtilsTest { val patch: Patch = diff(original, revised) assertEquals(1, patch.deltas.size) assertEquals( - changeDeltaOf(1, listOf("line2", "line3"),1, listOf("line2-2", "line4")), + changeDeltaOf(1, listOf("line2", "line3"), 1, listOf("line2-2", "line4")), patch.deltas[0] ) } @@ -148,8 +142,9 @@ class DiffUtilsTest { @Test fun testDiff_Equal() { val patch: Patch = diff( - listOf("hhh", "jjj", "kkk"), - listOf("hhh", "jjj", "kkk"), true + source = listOf("hhh", "jjj", "kkk"), + target = listOf("hhh", "jjj", "kkk"), + includeEqualParts = true, ) assertNotNull(patch) assertEquals(1, patch.deltas.size) @@ -161,7 +156,11 @@ class DiffUtilsTest { @Test fun testDiff_InsertWithEqual() { - val patch: Patch = diff(listOf("hhh"), listOf("hhh", "jjj", "kkk"), true) + val patch: Patch = diff( + source = listOf("hhh"), + target = listOf("hhh", "jjj", "kkk"), + includeEqualParts = true, + ) assertNotNull(patch) assertEquals(2, patch.deltas.size) var delta = patch.deltas[0] @@ -177,8 +176,9 @@ class DiffUtilsTest { @Test fun testDiff_ProblemIssue42() { val patch: Patch = diff( - listOf("The", "dog", "is", "brown"), - listOf("The", "fox", "is", "down"), true + source = listOf("The", "dog", "is", "brown"), + target = listOf("The", "fox", "is", "down"), + includeEqualParts = true, ) println(patch) assertNotNull(patch) diff --git a/src/commonTest/kotlin/io/github/petertrr/diffutils/algorithm/myers/MyersDiffTest.kt b/src/commonTest/kotlin/io/github/petertrr/diffutils/algorithm/myers/MyersDiffTest.kt index dcb19d2..77b6216 100644 --- a/src/commonTest/kotlin/io/github/petertrr/diffutils/algorithm/myers/MyersDiffTest.kt +++ b/src/commonTest/kotlin/io/github/petertrr/diffutils/algorithm/myers/MyersDiffTest.kt @@ -31,7 +31,7 @@ class MyersDiffTest { fun testDiffMyersExample1Forward() { val original: List = listOf("A", "B", "C", "A", "B", "B", "A") val revised: List = listOf("C", "B", "A", "B", "A", "C") - val patch: Patch = Patch.generate(original, revised, MyersDiff().computeDiff(original, revised, null)) + val patch: Patch = Patch.generate(original, revised, MyersDiff().computeDiff(original, revised)) assertNotNull(patch) assertEquals(4, patch.deltas.size) assertEquals( From c476089849dd60531f7460b2b2b461075a1fd3d2 Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Tue, 9 Apr 2024 12:34:03 +0200 Subject: [PATCH 17/22] refactor: remove explicit Throws Explicit throws indicator are not idiomatic to Kotlin, and they tend to become out of sync pretty fast with the actual code that is *supposed* to throw. --- .../kotlin/io/github/petertrr/diffutils/DiffUtils.kt | 1 - .../io/github/petertrr/diffutils/patch/ConflictOutput.kt | 1 - .../kotlin/io/github/petertrr/diffutils/patch/Delta.kt | 8 ++------ .../diffutils/patch/ExceptionProducingConflictOutput.kt | 2 +- .../kotlin/io/github/petertrr/diffutils/patch/Patch.kt | 2 +- 5 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/DiffUtils.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/DiffUtils.kt index 950f6f6..cb45f59 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/DiffUtils.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/DiffUtils.kt @@ -148,7 +148,6 @@ public fun diffInline(original: String, revised: String): Patch { * @return The revised text * @throws PatchFailedException If the patch cannot be applied */ -@Throws(PatchFailedException::class) public fun patch(original: List, patch: Patch): List = patch.applyTo(original) diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ConflictOutput.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ConflictOutput.kt index 00f8f01..0c566dc 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ConflictOutput.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ConflictOutput.kt @@ -19,6 +19,5 @@ package io.github.petertrr.diffutils.patch public fun interface ConflictOutput { - @Throws(PatchFailedException::class) public fun processConflict(verifyChunk: VerifyChunk, delta: Delta, result: MutableList) } 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 57f354a..83c58ee 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Delta.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Delta.kt @@ -28,12 +28,9 @@ public sealed class Delta(public val type: DeltaType) { /** * Verify the chunk of this delta, to fit the target. */ - @Throws(PatchFailedException::class) - protected open fun verifyChunkToFitTarget(target: List): VerifyChunk { - return source.verify(target) - } + protected open fun verifyChunkToFitTarget(target: List): VerifyChunk = + source.verify(target) - @Throws(PatchFailedException::class) public open fun verifyAndApplyTo(target: MutableList): VerifyChunk { val verify = verifyChunkToFitTarget(target) @@ -44,7 +41,6 @@ public sealed class Delta(public val type: DeltaType) { return verify } - @Throws(PatchFailedException::class) protected abstract fun applyTo(target: MutableList) public abstract fun restore(target: MutableList) diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ExceptionProducingConflictOutput.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ExceptionProducingConflictOutput.kt index a1ab579..93efcd2 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ExceptionProducingConflictOutput.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ExceptionProducingConflictOutput.kt @@ -20,5 +20,5 @@ package io.github.petertrr.diffutils.patch public class ExceptionProducingConflictOutput : ConflictOutput { override fun processConflict(verifyChunk: VerifyChunk, delta: Delta, result: MutableList): Nothing = - throw PatchFailedException("Could not apply patch due to $verifyChunk") + throw PatchFailedException("Could not apply patch due to: $verifyChunk") } 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 4a141b8..f40e905 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Patch.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Patch.kt @@ -38,8 +38,8 @@ public class Patch(private var conflictOutput: ConflictOutput = ExceptionP * Apply this patch to the given target. * * @return The patched text + * @throws PatchFailedException If the patch cannot be applied */ - @Throws(PatchFailedException::class) public fun applyTo(target: List): List { val result = target.toMutableList() val it = deltas.listIterator(deltas.size) From deac3c4de81af0b88f71a799bc22a9281c3be6dd Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Tue, 9 Apr 2024 12:38:17 +0200 Subject: [PATCH 18/22] refactor: delegate checking VerifyChunk type to ConflictOutput implementations --- .../patch/ConflictProducingConflictOutput.kt | 21 +++++++++++-------- .../patch/ExceptionProducingConflictOutput.kt | 7 +++++-- .../github/petertrr/diffutils/patch/Patch.kt | 5 +---- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ConflictProducingConflictOutput.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ConflictProducingConflictOutput.kt index 9b3255a..7a98c36 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ConflictProducingConflictOutput.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ConflictProducingConflictOutput.kt @@ -24,16 +24,19 @@ public class ConflictProducingConflictOutput : ConflictOutput { throw UnsupportedOperationException("Not supported yet") } - val orgData = ArrayList() + if (verifyChunk != VerifyChunk.OK) { + val orgData = ArrayList() - repeat(delta.source.size()) { - orgData.add(result.removeAt(delta.source.position)) - } + repeat(delta.source.size()) { + orgData.add(result.removeAt(delta.source.position)) + } + + orgData.add(0, "<<<<<< HEAD") + orgData.add("======") + orgData.addAll(delta.source.lines) + orgData.add(">>>>>>> PATCH") - orgData.add(0, "<<<<<< HEAD") - orgData.add("======") - orgData.addAll(delta.source.lines) - orgData.add(">>>>>>> PATCH") - result.addAll(delta.source.position, orgData) + result.addAll(delta.source.position, orgData) + } } } diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ExceptionProducingConflictOutput.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ExceptionProducingConflictOutput.kt index 93efcd2..cda5eae 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ExceptionProducingConflictOutput.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/ExceptionProducingConflictOutput.kt @@ -19,6 +19,9 @@ package io.github.petertrr.diffutils.patch public class ExceptionProducingConflictOutput : ConflictOutput { - override fun processConflict(verifyChunk: VerifyChunk, delta: Delta, result: MutableList): Nothing = - throw PatchFailedException("Could not apply patch due to: $verifyChunk") + override fun processConflict(verifyChunk: VerifyChunk, delta: Delta, result: MutableList) { + if (verifyChunk != VerifyChunk.OK) { + throw PatchFailedException("Could not apply patch due to: $verifyChunk") + } + } } 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 f40e905..d304098 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Patch.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Patch.kt @@ -47,10 +47,7 @@ public class Patch(private var conflictOutput: ConflictOutput = ExceptionP while (it.hasPrevious()) { val delta = it.previous() val verifyChunk = delta.verifyAndApplyTo(result) - - if (verifyChunk != VerifyChunk.OK) { - conflictOutput.processConflict(verifyChunk, delta, result) - } + conflictOutput.processConflict(verifyChunk, delta, result) } return result From 78b34faad95b345592d304d6d9ef590c5bcfb838 Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Wed, 10 Apr 2024 12:01:06 +0200 Subject: [PATCH 19/22] chore: enable TrailingCommaOnDeclarationSite --- detekt.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/detekt.yml b/detekt.yml index 3270a66..f5f050b 100644 --- a/detekt.yml +++ b/detekt.yml @@ -11,3 +11,5 @@ formatting: active: false ParameterListWrapping: active: false + TrailingCommaOnDeclarationSite: + active: true From 581b6efe338ef2f3aa9cea057348df73b2710d7b Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Wed, 10 Apr 2024 12:04:40 +0200 Subject: [PATCH 20/22] chore: disable ForbiddenComment --- detekt.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/detekt.yml b/detekt.yml index f5f050b..ff5ef36 100644 --- a/detekt.yml +++ b/detekt.yml @@ -5,6 +5,8 @@ style: MaxLineLength: active: true maxLineLength: 180 + ForbiddenComment: + active: false formatting: active: true MaximumLineLength: From 473c3aa4ac99f2503a8b4bebf1d3f19ffed758c4 Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Wed, 10 Apr 2024 12:08:52 +0200 Subject: [PATCH 21/22] refactor: address review comments --- .../github/petertrr/diffutils/algorithm/myers/MyersDiff.kt | 4 ++-- .../kotlin/io/github/petertrr/diffutils/text/DiffRow.kt | 5 ++++- .../io/github/petertrr/diffutils/text/DiffRowGenerator.kt | 2 +- .../kotlin/io/github/petertrr/diffutils/DiffUtilsTest.kt | 7 ++++++- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/myers/MyersDiff.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/myers/MyersDiff.kt index d7288fd..8672d4d 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/myers/MyersDiff.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/myers/MyersDiff.kt @@ -35,8 +35,8 @@ public class MyersDiff(private val equalizer: (T, T) -> Boolean = { t1, t2 -> val path = buildPath(source, target, progress) ?: error("Expected a non-null path node") val result = buildRevision(path) - progress.diffEnd() + progress.diffEnd() return result } @@ -122,7 +122,7 @@ public class MyersDiff(private val equalizer: (T, T) -> Boolean = { t1, t2 -> val changes = ArrayList() - // This can be improved to avoid the non-null assertion on prev + // TODO: this can be improved to avoid the non-null assertion on prev while (path?.prev != null && path.prev!!.j >= 0) { check(!path.snake) { "Bad diffpath: found snake when looking for diff" } diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/text/DiffRow.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/text/DiffRow.kt index e05a440..94434f6 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/text/DiffRow.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/text/DiffRow.kt @@ -19,7 +19,7 @@ package io.github.petertrr.diffutils.text /** - * Describes the diff row in form `[tag, oldLine, newLine)` for showing the difference between two texts. + * Describes the diff row in form `[tag, oldLine, newLine]` for showing the difference between two texts. */ public data class DiffRow(val tag: Tag, val oldLine: String, val newLine: String) { public enum class Tag { @@ -28,4 +28,7 @@ public data class DiffRow(val tag: Tag, val oldLine: String, val newLine: String CHANGE, EQUAL, } + + override fun toString(): String = + "[$tag, $oldLine, $newLine]" } diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/text/DiffRowGenerator.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/text/DiffRowGenerator.kt index 7eadb06..f351389 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/text/DiffRowGenerator.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/text/DiffRowGenerator.kt @@ -168,7 +168,7 @@ public class DiffRowGenerator( original: List, endPos: Int, diffRows: MutableList, - delta: Delta + delta: Delta, ): Int { val orig: Chunk = delta.source val rev: Chunk = delta.target diff --git a/src/commonTest/kotlin/io/github/petertrr/diffutils/DiffUtilsTest.kt b/src/commonTest/kotlin/io/github/petertrr/diffutils/DiffUtilsTest.kt index d6e45b0..1ae94a3 100644 --- a/src/commonTest/kotlin/io/github/petertrr/diffutils/DiffUtilsTest.kt +++ b/src/commonTest/kotlin/io/github/petertrr/diffutils/DiffUtilsTest.kt @@ -18,7 +18,12 @@ */ package io.github.petertrr.diffutils -import io.github.petertrr.diffutils.patch.* +import io.github.petertrr.diffutils.patch.ChangeDelta +import io.github.petertrr.diffutils.patch.Chunk +import io.github.petertrr.diffutils.patch.DeleteDelta +import io.github.petertrr.diffutils.patch.EqualDelta +import io.github.petertrr.diffutils.patch.InsertDelta +import io.github.petertrr.diffutils.patch.Patch import io.github.petertrr.diffutils.utils.changeDeltaOf import io.github.petertrr.diffutils.utils.deleteDeltaOf import io.github.petertrr.diffutils.utils.insertDeltaOf From 04efa7a3063e01b805b5747557a73bbcb531f987 Mon Sep 17 00:00:00 2001 From: Edoardo Luppi Date: Wed, 10 Apr 2024 12:19:26 +0200 Subject: [PATCH 22/22] refactor: rework MyersDiff.buildRevision to avoid unnecessary non-null assertion --- .../petertrr/diffutils/algorithm/myers/MyersDiff.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/myers/MyersDiff.kt b/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/myers/MyersDiff.kt index 8672d4d..3f5fa94 100644 --- a/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/myers/MyersDiff.kt +++ b/src/commonMain/kotlin/io/github/petertrr/diffutils/algorithm/myers/MyersDiff.kt @@ -122,13 +122,19 @@ public class MyersDiff(private val equalizer: (T, T) -> Boolean = { t1, t2 -> val changes = ArrayList() - // TODO: this can be improved to avoid the non-null assertion on prev - while (path?.prev != null && path.prev!!.j >= 0) { + while (path != null) { + val prevPath = path.prev + + if (prevPath == null || prevPath.j < 0) { + break + } + check(!path.snake) { "Bad diffpath: found snake when looking for diff" } val i = path.i val j = path.j - path = path.prev ?: error("Expected a non-null previous path node") + + path = prevPath val iAnchor = path.i val jAnchor = path.j