diff --git a/deno.jsonc b/deno.jsonc index 00e7158..18070f9 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -10,7 +10,7 @@ "./utils": "./src/utils.ts" }, "tasks": { - "test": "deno test --doc src tests" + "test": "deno test --doc src" }, "imports": { "@clearlylocal/diff-match-patch-unicode": "./src/mod.ts", diff --git a/tests/Differ.test.ts b/src/Differ.test.ts similarity index 96% rename from tests/Differ.test.ts rename to src/Differ.test.ts index 7964907..8b0d120 100644 --- a/tests/Differ.test.ts +++ b/src/Differ.test.ts @@ -1,7 +1,7 @@ import { assertEquals } from '@std/assert' -import { Differ, segmenters } from '../src/Differ.ts' -import { assertDiffsEqual, assertDiffsEqual2d } from './testUtils.ts' -import { DiffMatchPatchFull } from '../src/DiffMatchPatchFull.ts' +import { Differ, segmenters } from './Differ.ts' +import { assertDiffsEqual, assertDiffsEqual2d } from './_testUtils.ts' +import { DiffMatchPatchFull } from './_full/DiffMatchPatchFull.ts' const differ = new Differ() diff --git a/src/Differ.ts b/src/Differ.ts index 5ecd37d..58cc27d 100644 --- a/src/Differ.ts +++ b/src/Differ.ts @@ -1,6 +1,6 @@ import { Diff, DiffOperation } from './Diff.ts' -import { DiffMatchPatch, MAX_SEGMENTS, MAX_SEGMENTS_2_3 } from './DiffMatchPatch.ts' -import { SegmentCodec, StringIter } from './SegmentCodec.ts' +import { DiffMatchPatch, MAX_SEGMENTS, MAX_SEGMENTS_2_3 } from './_DiffMatchPatch.ts' +import { SegmentCodec, StringIter } from './_SegmentCodec.ts' /** * Instance-level configuration options for the {@linkcode Differ} class to pass to the underlying @@ -23,7 +23,9 @@ export type DiffOptions = { */ segmenter: Segmenter /** - * Whether to join adjacent diffs. + * Whether to count consecutive diff operations containing multiple segments as a single diff. + * If `true`, a diff may contain multiple segments; if `false`, each segment will be a separate diff. + * * @default {true} */ join: boolean @@ -81,15 +83,18 @@ export class Differ { } } - #diffInternal(before: string, after: string, options: DiffOptions, maxBefore: number, maxAfter: number): { + #diffInternal(before: string, after: string, options: DiffOptions & { maxBefore: number; maxAfter: number }): { encodedDiffs: Diff[] - codec?: SegmentCodec + decode: (encoded: string) => string[] } { - const { segmenter, checkLines } = options + const { segmenter, checkLines, join, maxBefore, maxAfter } = options // if no surrogate pairs present, we're entirely within the BMP, so no need to encode if (segmenter === segmenters.char && !/[\uD800-\uDBFF]/.test([before, after].join(''))) { - return { encodedDiffs: this.#dmp.diff_main(before, after, checkLines, this.#deadline) } + return { + encodedDiffs: this.#dmp.diff_main(before, after, checkLines, this.#deadline), + decode: join ? (x) => [x] : (x) => x.split(''), + } } const segment = this.#toSegmentFn(segmenter) @@ -101,7 +106,7 @@ export class Differ { const encodedDiffs = this.#dmp.diff_main(chars1, chars2, checkLines, this.#deadline) - return { encodedDiffs, codec } + return { encodedDiffs, decode: codec.decode.bind(codec) } } /** @@ -137,19 +142,18 @@ export class Differ { return before ? [new Diff(DiffOperation.Equal, before)] : [] } - const opts = { ...defaultDiffOptions, ...options } + const opts = { ...defaultDiffOptions, ...options, maxBefore: MAX_SEGMENTS_2_3, maxAfter: MAX_SEGMENTS } const { join } = opts - const { encodedDiffs, codec } = this.#diffInternal(before, after, opts, MAX_SEGMENTS_2_3, MAX_SEGMENTS) - if (codec == null) return encodedDiffs + const { encodedDiffs, decode } = this.#diffInternal(before, after, opts) if (!join) { return encodedDiffs.flatMap( - ({ op, text }) => codec.decode(text).filter(Boolean).map((segment) => new Diff(op, segment)), + ({ op, text }) => decode(text).filter(Boolean).map((segment) => new Diff(op, segment)), ) } - return encodedDiffs.map(({ op, text }) => new Diff(op, codec.decode(text).join(''))).filter((x) => x.text) + return encodedDiffs.map(({ op, text }) => new Diff(op, decode(text).join(''))).filter((x) => x.text) } /** diff --git a/src/DiffMatchPatch.ts b/src/_DiffMatchPatch.ts similarity index 99% rename from src/DiffMatchPatch.ts rename to src/_DiffMatchPatch.ts index e869c9b..92f1d5c 100644 --- a/src/DiffMatchPatch.ts +++ b/src/_DiffMatchPatch.ts @@ -1,6 +1,6 @@ import { Diff, DiffOperation } from './Diff.ts' -// reduced version - removed props/methods that are currently unused by `Differ` +// reduced version - removed props/methods that are currently unused by `Differ` class /** * Diff Match and Patch @@ -26,9 +26,9 @@ import { Diff, DiffOperation } from './Diff.ts' * @author fraser@google.com (Neil Fraser) */ -// max BMP code point. hard limit w/o significant refactoring +/** `0xFFFF`: Max BMP code point. Hard limit w/o significant refactoring */ export const MAX_SEGMENTS = 0xFFFF -// round(MAX_SEGMENTS * 2 / 3) +/** `= round(MAX_SEGMENTS * 2 / 3)` */ export const MAX_SEGMENTS_2_3 = 0xAAAA /** diff --git a/tests/SegmentCodec.test.ts b/src/_SegmentCodec.test.ts similarity index 93% rename from tests/SegmentCodec.test.ts rename to src/_SegmentCodec.test.ts index 5b49d37..33a7838 100644 --- a/tests/SegmentCodec.test.ts +++ b/src/_SegmentCodec.test.ts @@ -1,6 +1,6 @@ import { AssertionError } from '@std/assert/assertion-error' import { assertEquals, assertThrows } from '@std/assert' -import { SegmentCodec } from '../src/SegmentCodec.ts' +import { SegmentCodec } from './_SegmentCodec.ts' Deno.test(SegmentCodec.name, async (t) => { await t.step('happy path', () => { diff --git a/src/SegmentCodec.ts b/src/_SegmentCodec.ts similarity index 92% rename from src/SegmentCodec.ts rename to src/_SegmentCodec.ts index 94ea4c5..6a50022 100644 --- a/src/SegmentCodec.ts +++ b/src/_SegmentCodec.ts @@ -50,7 +50,7 @@ export class SegmentCodec { return out } - decode(text: string): string[] { - return [...text].map((char) => this.#decoded.get(char) ?? '') + decode(encoded: string): string[] { + return [...encoded].map((char) => this.#decoded.get(char) ?? '') } } diff --git a/tests/DiffMatchPatchFull.test.ts b/src/_full/DiffMatchPatchFull.test.ts similarity index 99% rename from tests/DiffMatchPatchFull.test.ts rename to src/_full/DiffMatchPatchFull.test.ts index feb1816..226a113 100644 --- a/tests/DiffMatchPatchFull.test.ts +++ b/src/_full/DiffMatchPatchFull.test.ts @@ -1,9 +1,9 @@ import { assert, assertEquals } from '@std/assert' -import { DiffMatchPatchFull as DiffMatchPatch } from '../src/DiffMatchPatchFull.ts' -import { Diff, DiffOperation } from '../src/Diff.ts' -import { Patch } from '../src/Patch.ts' -import { assertDiffsEqual } from './testUtils.ts' -import { makeDiffs } from '../src/utils.ts' +import { DiffMatchPatchFull as DiffMatchPatch } from './DiffMatchPatchFull.ts' +import { Diff, DiffOperation } from '../Diff.ts' +import { Patch } from './Patch.ts' +import { assertDiffsEqual } from '../_testUtils.ts' +import { makeDiffs } from '../utils.ts' // Tests here are modified from `google/diff-match-patch` tests // to ensure parity after the various refactoring (ESM, classes, TS, etc.) diff --git a/src/DiffMatchPatchFull.ts b/src/_full/DiffMatchPatchFull.ts similarity index 99% rename from src/DiffMatchPatchFull.ts rename to src/_full/DiffMatchPatchFull.ts index d7503e1..dcbe114 100644 --- a/src/DiffMatchPatchFull.ts +++ b/src/_full/DiffMatchPatchFull.ts @@ -1,5 +1,5 @@ -import { Diff, DiffOperation } from './Diff.ts' -import { DiffMatchPatch } from './DiffMatchPatch.ts' +import { Diff, DiffOperation } from '../Diff.ts' +import { DiffMatchPatch } from '../_DiffMatchPatch.ts' import { Patch } from './Patch.ts' // full version (extending reduced version) - re-add props/methods that are currently unused by `Differ` diff --git a/src/Patch.ts b/src/_full/Patch.ts similarity index 93% rename from src/Patch.ts rename to src/_full/Patch.ts index 124b0c0..87d9d64 100644 --- a/src/Patch.ts +++ b/src/_full/Patch.ts @@ -1,5 +1,5 @@ -import { DiffOperation } from './Diff.ts' -import type { Diff } from './Diff.ts' +import { DiffOperation } from '../Diff.ts' +import type { Diff } from '../Diff.ts' /** * Class representing one patch operation. diff --git a/tests/testUtils.ts b/src/_testUtils.ts similarity index 86% rename from tests/testUtils.ts rename to src/_testUtils.ts index 8a1fd20..5bac5d7 100644 --- a/tests/testUtils.ts +++ b/src/_testUtils.ts @@ -1,5 +1,5 @@ import { assertEquals } from '@std/assert/equals' -import { DiffLike, makeDiffs } from '../src/utils.ts' +import { DiffLike, makeDiffs } from './utils.ts' export function assertDiffsEqual(d1: readonly DiffLike[], d2: readonly DiffLike[]) { assertEquals(makeDiffs(d1), makeDiffs(d2)) diff --git a/src/utils.ts b/src/utils.ts index 9b28235..15268a4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,5 @@ +import { Diff, DiffOperation } from './Diff.ts' import { assert } from '@std/assert/assert' -import { Diff, type DiffOperation } from './Diff.ts' /** * A `Diff` or a tuple of `[operation, text]` (loosely typed for convenience as an input to `makeDiff`). @@ -20,10 +20,6 @@ export function makeDiff(d: DiffLike): Diff { if (d instanceof Diff) return d const [op, text] = d assert(typeof text === 'string') - return new Diff(getOpWithAssertion(op), text) -} - -function getOpWithAssertion(op: unknown): DiffOperation { - assert(op === -1 || op === 0 || op === 1) - return op + assert(op === DiffOperation.Delete || op === DiffOperation.Insert || op === DiffOperation.Equal) + return new Diff(op, text) }