CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/sync/editor/generic/util.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
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
96
import { cmp_array } from "@cocalc/util/misc";
97
98
export function patch_cmp(a: Patch, b: Patch): number {
99
return cmp_array(
100
[a.time.valueOf(), a.user_id],
101
[b.time.valueOf(), b.user_id],
102
);
103
}
104
105
export function time_cmp(a: Date, b: Date): number {
106
const t = a.valueOf() - b.valueOf();
107
if (t < 0) {
108
return -1;
109
} else if (t > 0) {
110
return 1;
111
} else {
112
return 0;
113
}
114
}
115
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
131