Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/editors/slate/slate-diff/diff.ts
1698 views
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 { Node, Operation } from "slate";
7
import { diff_main } from "@cocalc/sync/editor/generic/util";
8
import { StringCharMapping } from "@cocalc/util/misc";
9
import { handleChangeOneNode } from "./handle-change-one-node";
10
import { handleChangeTextNodes, isAllText } from "./handle-change-text-nodes";
11
12
// We could instead use
13
// import stringify from "json-stable-stringify";
14
// which might sometimes avoid a safe "false positive" (i.e., slightly
15
// less efficient patch), but is significantly slower.
16
const stringify = JSON.stringify;
17
18
function docToStrings(doc: Node[]): string[] {
19
const v: string[] = [];
20
for (const node of doc) {
21
v.push(stringify(node));
22
}
23
return v;
24
}
25
26
export function slateDiff(
27
doc0: Node[],
28
doc1: Node[],
29
path: number[] = []
30
): Operation[] {
31
// const t0 = path.length == 0 ? Date.now() : 0;
32
const string_mapping = new StringCharMapping();
33
const s0 = docToStrings(doc0);
34
const s1 = docToStrings(doc1);
35
const m0 = string_mapping.to_string(s0);
36
const m1 = string_mapping.to_string(s1);
37
const diff = diff_main(m0, m1);
38
const operations: Operation[] = [];
39
//console.log({ diff, to_string: string_mapping._to_string });
40
41
function letterToNode(x: string): Node {
42
const node = JSON.parse(string_mapping._to_string[x]);
43
if (node == null) {
44
throw Error("letterToNode: bug");
45
}
46
return node;
47
}
48
49
function stringToNodes(s: string): Node[] {
50
const nodes: Node[] = [];
51
for (const x of s) {
52
nodes.push(letterToNode(x));
53
}
54
return nodes;
55
}
56
57
let index = 0;
58
let i = 0;
59
while (i < diff.length) {
60
const chunk = diff[i];
61
const op = chunk[0]; // -1 = delete, 0 = leave unchanged, 1 = insert
62
const val = chunk[1];
63
if (op === 0) {
64
// skip over context diff nodes
65
index += val.length;
66
i += 1;
67
continue;
68
}
69
const nodes = stringToNodes(val);
70
if (op === -1) {
71
if (i < diff.length - 1 && diff[i + 1][0] == 1) {
72
// next one is an insert, so this is really a "replace".
73
const nextVal = diff[i + 1][1];
74
const nextNodes = stringToNodes(nextVal);
75
if (isAllText(nodes) && isAllText(nextNodes)) {
76
// Every single node involved is a text node. This can be done
77
// via modifying and splitting and joining.
78
for (const op of handleChangeTextNodes(
79
nodes,
80
nextNodes,
81
path.concat([index])
82
)) {
83
operations.push(op);
84
}
85
index += nextNodes.length;
86
i += 2; // this consumed two entries from the diff array.
87
continue;
88
}
89
while (nodes.length > 0 && nextNodes.length > 0) {
90
// replace corresponding node
91
for (const op of handleChangeOneNode(
92
nodes[0],
93
nextNodes[0],
94
path.concat([index])
95
)) {
96
operations.push(op);
97
}
98
index += 1;
99
nodes.shift();
100
nextNodes.shift();
101
}
102
// delete anything left in nodes
103
for (const node of nodes) {
104
operations.push({
105
type: "remove_node",
106
path: path.concat([index]),
107
node,
108
} as Operation);
109
}
110
// insert anything left in nextNodes
111
for (const node of nextNodes) {
112
operations.push({
113
type: "insert_node",
114
path: path.concat([index]),
115
node,
116
} as Operation);
117
index += 1;
118
}
119
i += 2; // this consumed two entries from the diff array.
120
continue;
121
} else {
122
// Plain delete of some nodes (with no insert immediately after)
123
for (const node of nodes) {
124
operations.push({
125
type: "remove_node",
126
path: path.concat([index]),
127
node,
128
} as Operation);
129
}
130
i += 1; // consumes only one entry from diff array.
131
continue;
132
}
133
}
134
if (op === 1) {
135
// insert new nodes.
136
for (const node of nodes) {
137
operations.push({
138
type: "insert_node",
139
path: path.concat([index]),
140
node,
141
});
142
index += 1;
143
}
144
i += 1;
145
continue;
146
}
147
throw Error("BUG");
148
}
149
/* if (path.length == 0) {
150
console.log("time: slateDiff", Date.now() - t0, "ms");
151
}*/
152
153
return operations;
154
}
155
156