Skip to content

Commit ac68fa3

Browse files
committed
fix #6327 -- replace vendored ancient diff-match-patch with an upstream modern typescript library that fixes a number of significant issues
- complete diffTimeout implementation - surrogate pairs unicode support - unit testing - typescript
1 parent d11440f commit ac68fa3

File tree

10 files changed

+107
-2506
lines changed

10 files changed

+107
-2506
lines changed

src/packages/backend/conat/test/sync/dkv-merge.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pnpm test ./dkv-merge.test.ts
99

1010
import { dkv as createDkv } from "@cocalc/backend/conat/sync";
1111
import { once } from "@cocalc/util/async-utils";
12-
import { diff_match_patch } from "@cocalc/util/dmp";
12+
import { DiffMatchPatch } from "@cocalc/util/dmp";
1313
import { before, after } from "@cocalc/backend/conat/test/setup";
1414

1515
beforeAll(before);
@@ -118,7 +118,7 @@ describe("test a trivial merge conflict resolution function", () => {
118118
});
119119

120120
describe("test a 3-way merge of strings conflict resolution function", () => {
121-
const dmp = new diff_match_patch();
121+
const dmp = new DiffMatchPatch();
122122
const threeWayMerge = (opts: {
123123
prev: string;
124124
local: string;

src/packages/pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/packages/sync/editor/db/doc.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* License: MS-RSL – see LICENSE.md for details
44
*/
55

6-
import { CompressedPatch, Document } from "../generic/types";
6+
import { Document } from "../generic/types";
77
import * as immutable from "immutable";
88
import { isEqual } from "lodash";
99
import { is_array, is_object, copy_without, len } from "@cocalc/util/misc";
@@ -25,6 +25,7 @@ type Index = immutable.Map<string, immutable.Set<number>>;
2525
type Indexes = immutable.Map<string, Index>;
2626

2727
type jsmap = { [field: string]: any };
28+
type DBPatch = any // TODO: It's really this, but... [-1 | 1, jsmap];
2829

2930
export type WhereCondition = { [field: string]: any };
3031
export type SetCondition =
@@ -163,7 +164,7 @@ export class DBDocument implements Document {
163164
.equals(immutable.Set(other.records).add(undefined));
164165
}
165166

166-
public apply_patch(patch: CompressedPatch): DBDocument {
167+
public apply_patch(patch: DBPatch): DBDocument {
167168
let i = 0;
168169
let db: DBDocument = this;
169170
while (i < patch.length) {
@@ -177,7 +178,7 @@ export class DBDocument implements Document {
177178
return db;
178179
}
179180

180-
public make_patch(other: DBDocument): CompressedPatch {
181+
public make_patch(other: DBDocument): DBPatch {
181182
if (other.size === 0) {
182183
// Special case -- delete everything
183184
return [-1, [{}]];

src/packages/sync/editor/generic/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
// A Patch is an entry in the patches table, as represented in memory locally here.
1111

1212
import { SyncTable } from "@cocalc/sync/table/synctable";
13+
import { type CompressedPatch } from "@cocalc/util/dmp";
14+
export { type CompressedPatch };
1315

1416
import type { ExecuteCodeOptionsWithCallback } from "@cocalc/util/types/execute-code";
1517
import type {
@@ -74,8 +76,6 @@ export interface Document {
7476
count(): number;
7577
}
7678

77-
export type CompressedPatch = any[];
78-
7979
export interface FileWatcher {
8080
on: (event: string, handler: Function) => void;
8181
close: () => void;

src/packages/sync/editor/generic/util.ts

Lines changed: 4 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -3,97 +3,11 @@
33
* License: MS-RSL – see LICENSE.md for details
44
*/
55

6-
import { CompressedPatch, Patch } from "./types";
7-
import { diff_match_patch } from "@cocalc/util/dmp";
8-
9-
const dmp = new diff_match_patch();
10-
dmp.Diff_Timeout = 0.2; // computing a diff won't block longer than about 0.2s
11-
12-
// Here's what a diff-match-patch patch looks like
13-
//
14-
// [{"diffs":[[1,"{\"x\":5,\"y\":3}"]],"start1":0,"start2":0,"length1":0,"length2":13},...]
15-
//
16-
17-
// TODO: we must explicitly type these as "Function" or typescript gives errors.
18-
// We should of course explicitly type the inputs and outputs of each, which
19-
// will make other code more robust. See above and look at the source...
20-
export const diff_main: Function = dmp.diff_main.bind(dmp);
21-
export const patch_make: Function = dmp.patch_make.bind(dmp);
22-
23-
// The diff-match-patch library changed the format, but we must keep it the same
24-
// for backward compat and two stay JSON friendly.
25-
26-
const Diff = diff_match_patch.Diff;
27-
28-
function diffs_to_arrays(diffs: any[]): any[] {
29-
const v: any[] = [];
30-
for (const d of diffs) {
31-
v.push([d[0], d[1]]);
32-
}
33-
return v;
34-
}
35-
36-
function arrays_to_diffs(arrays: any[]): any[] {
37-
const v: any[] = [];
38-
for (const x of arrays) {
39-
v.push(new Diff(x[0], x[1]));
40-
}
41-
return v;
42-
}
43-
44-
export function compress_patch(patch: CompressedPatch): CompressedPatch {
45-
return patch.map((p) => [
46-
diffs_to_arrays(p.diffs),
47-
p.start1,
48-
p.start2,
49-
p.length1,
50-
p.length2,
51-
]);
52-
}
53-
54-
export function decompress_patch(patch: CompressedPatch): CompressedPatch {
55-
return patch.map((p) => ({
56-
diffs: arrays_to_diffs(p[0]),
57-
start1: p[1],
58-
start2: p[2],
59-
length1: p[3],
60-
length2: p[4],
61-
}));
62-
}
63-
64-
// return *a* compressed patch that transforms string s0 into string s1.
65-
export function make_patch(s0: string, s1: string): CompressedPatch {
66-
// @ts-ignore
67-
return compress_patch(dmp.patch_make(s0, s1));
68-
}
69-
70-
// apply a compressed patch to a string.
71-
export function apply_patch(
72-
patch: CompressedPatch,
73-
s: string,
74-
): [string, boolean] {
75-
let x;
76-
try {
77-
x = dmp.patch_apply(decompress_patch(patch), s);
78-
//console.log('patch_apply ', misc.to_json(decompress_patch(patch)), x)
79-
} catch (err) {
80-
// If a patch is so corrupted it can't be parsed -- e.g., due to a bug in SMC -- we at least
81-
// want to make application the identity map (i.e., "best effort"), so
82-
// the document isn't completely unreadable!
83-
console.warn(`apply_patch -- ${err}, ${JSON.stringify(patch)}`);
84-
return [s, false];
85-
}
86-
let clean = true;
87-
for (const a of x[1]) {
88-
if (!a) {
89-
clean = false;
90-
break;
91-
}
92-
}
93-
return [x[0], clean];
94-
}
95-
6+
import { Patch } from "./types";
967
import { cmp_array } from "@cocalc/util/misc";
8+
export * from "@cocalc/util/dmp";
9+
import { type CompressedPatch } from "@cocalc/util/dmp";
10+
export { type CompressedPatch };
9711

9812
export function patch_cmp(a: Patch, b: Patch): number {
9913
return cmp_array(
@@ -113,21 +27,6 @@ export function time_cmp(a: Date, b: Date): number {
11327
}
11428
}
11529

116-
// Do a 3-way **string** merge by computing patch that transforms
117-
// base to remote, then applying that patch to local.
118-
export function three_way_merge(opts: {
119-
base: string;
120-
local: string;
121-
remote: string;
122-
}): string {
123-
if (opts.base === opts.remote) {
124-
// trivial special case...
125-
return opts.local;
126-
}
127-
// @ts-ignore
128-
return dmp.patch_apply(dmp.patch_make(opts.base, opts.remote), opts.local)[0];
129-
}
130-
13130
export function isTestClient(client: any) {
13231
return !!client?.isTestClient?.();
13332
}

src/packages/util/dmp.d.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)