index.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. // Convert the given range of a fragment to tokens, where node open
  2. // tokens are encoded as strings holding the node name, characters as
  3. // their character code, and node close tokens as -1.
  4. function tokens(frag, start, end, target) {
  5. for (let i = 0, off = 0; i < frag.childCount; i++) {
  6. let child = frag.child(i), endOff = off + child.nodeSize;
  7. let from = Math.max(off, start), to = Math.min(endOff, end);
  8. if (from < to) {
  9. if (child.isText) {
  10. for (let j = from; j < to; j++)
  11. target.push(child.text.charCodeAt(j - off));
  12. }
  13. else if (child.isLeaf) {
  14. target.push(child.type.name);
  15. }
  16. else {
  17. if (from == off)
  18. target.push(child.type.name);
  19. tokens(child.content, Math.max(off + 1, from) - off - 1, Math.min(endOff - 1, to) - off - 1, target);
  20. if (to == endOff)
  21. target.push(-1);
  22. }
  23. }
  24. off = endOff;
  25. }
  26. return target;
  27. }
  28. // The code below will refuse to compute a diff with more than 5000
  29. // insertions or deletions, which takes about 300ms to reach on my
  30. // machine. This is a safeguard against runaway computations.
  31. const MAX_DIFF_SIZE = 5000;
  32. // This obscure mess of constants computes the minimum length of an
  33. // unchanged range (not at the start/end of the compared content). The
  34. // idea is to make it higher in bigger replacements, so that you don't
  35. // get a diff soup of coincidentally identical letters when replacing
  36. // a paragraph.
  37. function minUnchanged(sizeA, sizeB) {
  38. return Math.min(15, Math.max(2, Math.floor(Math.max(sizeA, sizeB) / 10)));
  39. }
  40. function computeDiff(fragA, fragB, range) {
  41. let tokA = tokens(fragA, range.fromA, range.toA, []);
  42. let tokB = tokens(fragB, range.fromB, range.toB, []);
  43. // Scan from both sides to cheaply eliminate work
  44. let start = 0, endA = tokA.length, endB = tokB.length;
  45. while (start < tokA.length && start < tokB.length && tokA[start] === tokB[start])
  46. start++;
  47. if (start == tokA.length && start == tokB.length)
  48. return [];
  49. while (endA > start && endB > start && tokA[endA - 1] === tokB[endB - 1])
  50. endA--, endB--;
  51. // If the result is simple _or_ too big to cheaply compute, return
  52. // the remaining region as the diff
  53. if (endA == start || endB == start || (endA == endB && endA == start + 1))
  54. return [range.slice(start, endA, start, endB)];
  55. // This is an implementation of Myers' diff algorithm
  56. // See https://neil.fraser.name/writing/diff/myers.pdf and
  57. // https://blog.jcoglan.com/2017/02/12/the-myers-diff-algorithm-part-1/
  58. let lenA = endA - start, lenB = endB - start;
  59. let max = Math.min(MAX_DIFF_SIZE, lenA + lenB), off = max + 1;
  60. let history = [];
  61. let frontier = [];
  62. for (let len = off * 2, i = 0; i < len; i++)
  63. frontier[i] = -1;
  64. for (let size = 0; size <= max; size++) {
  65. for (let diag = -size; diag <= size; diag += 2) {
  66. let next = frontier[diag + 1 + max], prev = frontier[diag - 1 + max];
  67. let x = next < prev ? prev : next + 1, y = x + diag;
  68. while (x < lenA && y < lenB && tokA[start + x] === tokB[start + y])
  69. x++, y++;
  70. frontier[diag + max] = x;
  71. // Found a match
  72. if (x >= lenA && y >= lenB) {
  73. // Trace back through the history to build up a set of changed ranges.
  74. let diff = [], minSpan = minUnchanged(endA - start, endB - start);
  75. // Used to add steps to a diff one at a time, back to front, merging
  76. // ones that are less than minSpan tokens apart
  77. let fromA = -1, toA = -1, fromB = -1, toB = -1;
  78. let add = (fA, tA, fB, tB) => {
  79. if (fromA > -1 && fromA < tA + minSpan) {
  80. fromA = fA;
  81. fromB = fB;
  82. }
  83. else {
  84. if (fromA > -1)
  85. diff.push(range.slice(fromA, toA, fromB, toB));
  86. fromA = fA;
  87. toA = tA;
  88. fromB = fB;
  89. toB = tB;
  90. }
  91. };
  92. for (let i = size - 1; i >= 0; i--) {
  93. let next = frontier[diag + 1 + max], prev = frontier[diag - 1 + max];
  94. if (next < prev) { // Deletion
  95. diag--;
  96. x = prev + start;
  97. y = x + diag;
  98. add(x, x, y, y + 1);
  99. }
  100. else { // Insertion
  101. diag++;
  102. x = next + start;
  103. y = x + diag;
  104. add(x, x + 1, y, y);
  105. }
  106. frontier = history[i >> 1];
  107. }
  108. if (fromA > -1)
  109. diff.push(range.slice(fromA, toA, fromB, toB));
  110. return diff.reverse();
  111. }
  112. }
  113. // Since only either odd or even diagonals are read from each
  114. // frontier, we only copy them every other iteration.
  115. if (size % 2 == 0)
  116. history.push(frontier.slice());
  117. }
  118. // The loop exited, meaning the maximum amount of work was done.
  119. // Just return a change spanning the entire range.
  120. return [range.slice(start, endA, start, endB)];
  121. }
  122. /**
  123. Stores metadata for a part of a change.
  124. */
  125. class Span {
  126. /**
  127. @internal
  128. */
  129. constructor(
  130. /**
  131. The length of this span.
  132. */
  133. length,
  134. /**
  135. The data associated with this span.
  136. */
  137. data) {
  138. this.length = length;
  139. this.data = data;
  140. }
  141. /**
  142. @internal
  143. */
  144. cut(length) {
  145. return length == this.length ? this : new Span(length, this.data);
  146. }
  147. /**
  148. @internal
  149. */
  150. static slice(spans, from, to) {
  151. if (from == to)
  152. return Span.none;
  153. if (from == 0 && to == Span.len(spans))
  154. return spans;
  155. let result = [];
  156. for (let i = 0, off = 0; off < to; i++) {
  157. let span = spans[i], end = off + span.length;
  158. let overlap = Math.min(to, end) - Math.max(from, off);
  159. if (overlap > 0)
  160. result.push(span.cut(overlap));
  161. off = end;
  162. }
  163. return result;
  164. }
  165. /**
  166. @internal
  167. */
  168. static join(a, b, combine) {
  169. if (a.length == 0)
  170. return b;
  171. if (b.length == 0)
  172. return a;
  173. let combined = combine(a[a.length - 1].data, b[0].data);
  174. if (combined == null)
  175. return a.concat(b);
  176. let result = a.slice(0, a.length - 1);
  177. result.push(new Span(a[a.length - 1].length + b[0].length, combined));
  178. for (let i = 1; i < b.length; i++)
  179. result.push(b[i]);
  180. return result;
  181. }
  182. /**
  183. @internal
  184. */
  185. static len(spans) {
  186. let len = 0;
  187. for (let i = 0; i < spans.length; i++)
  188. len += spans[i].length;
  189. return len;
  190. }
  191. }
  192. /**
  193. @internal
  194. */
  195. Span.none = [];
  196. /**
  197. A replaced range with metadata associated with it.
  198. */
  199. class Change {
  200. /**
  201. @internal
  202. */
  203. constructor(
  204. /**
  205. The start of the range deleted/replaced in the old document.
  206. */
  207. fromA,
  208. /**
  209. The end of the range in the old document.
  210. */
  211. toA,
  212. /**
  213. The start of the range inserted in the new document.
  214. */
  215. fromB,
  216. /**
  217. The end of the range in the new document.
  218. */
  219. toB,
  220. /**
  221. Data associated with the deleted content. The length of these
  222. spans adds up to `this.toA - this.fromA`.
  223. */
  224. deleted,
  225. /**
  226. Data associated with the inserted content. Length adds up to
  227. `this.toB - this.toA`.
  228. */
  229. inserted) {
  230. this.fromA = fromA;
  231. this.toA = toA;
  232. this.fromB = fromB;
  233. this.toB = toB;
  234. this.deleted = deleted;
  235. this.inserted = inserted;
  236. }
  237. /**
  238. @internal
  239. */
  240. get lenA() { return this.toA - this.fromA; }
  241. /**
  242. @internal
  243. */
  244. get lenB() { return this.toB - this.fromB; }
  245. /**
  246. @internal
  247. */
  248. slice(startA, endA, startB, endB) {
  249. if (startA == 0 && startB == 0 && endA == this.toA - this.fromA &&
  250. endB == this.toB - this.fromB)
  251. return this;
  252. return new Change(this.fromA + startA, this.fromA + endA, this.fromB + startB, this.fromB + endB, Span.slice(this.deleted, startA, endA), Span.slice(this.inserted, startB, endB));
  253. }
  254. /**
  255. This merges two changesets (the end document of x should be the
  256. start document of y) into a single one spanning the start of x to
  257. the end of y.
  258. */
  259. static merge(x, y, combine) {
  260. if (x.length == 0)
  261. return y;
  262. if (y.length == 0)
  263. return x;
  264. let result = [];
  265. // Iterate over both sets in parallel, using the middle coordinate
  266. // system (B in x, A in y) to synchronize.
  267. for (let iX = 0, iY = 0, curX = x[0], curY = y[0];;) {
  268. if (!curX && !curY) {
  269. return result;
  270. }
  271. else if (curX && (!curY || curX.toB < curY.fromA)) { // curX entirely in front of curY
  272. let off = iY ? y[iY - 1].toB - y[iY - 1].toA : 0;
  273. result.push(off == 0 ? curX :
  274. new Change(curX.fromA, curX.toA, curX.fromB + off, curX.toB + off, curX.deleted, curX.inserted));
  275. curX = iX++ == x.length ? null : x[iX];
  276. }
  277. else if (curY && (!curX || curY.toA < curX.fromB)) { // curY entirely in front of curX
  278. let off = iX ? x[iX - 1].toB - x[iX - 1].toA : 0;
  279. result.push(off == 0 ? curY :
  280. new Change(curY.fromA - off, curY.toA - off, curY.fromB, curY.toB, curY.deleted, curY.inserted));
  281. curY = iY++ == y.length ? null : y[iY];
  282. }
  283. else { // Touch, need to merge
  284. // The rules for merging ranges are that deletions from the
  285. // old set and insertions from the new are kept. Areas of the
  286. // middle document covered by a but not by b are insertions
  287. // from a that need to be added, and areas covered by b but
  288. // not a are deletions from b that need to be added.
  289. let pos = Math.min(curX.fromB, curY.fromA);
  290. let fromA = Math.min(curX.fromA, curY.fromA - (iX ? x[iX - 1].toB - x[iX - 1].toA : 0)), toA = fromA;
  291. let fromB = Math.min(curY.fromB, curX.fromB + (iY ? y[iY - 1].toB - y[iY - 1].toA : 0)), toB = fromB;
  292. let deleted = Span.none, inserted = Span.none;
  293. // Used to prevent appending ins/del range for the same Change twice
  294. let enteredX = false, enteredY = false;
  295. // Need to have an inner loop since any number of further
  296. // ranges might be touching this group
  297. for (;;) {
  298. let nextX = !curX ? 2e8 : pos >= curX.fromB ? curX.toB : curX.fromB;
  299. let nextY = !curY ? 2e8 : pos >= curY.fromA ? curY.toA : curY.fromA;
  300. let next = Math.min(nextX, nextY);
  301. let inX = curX && pos >= curX.fromB, inY = curY && pos >= curY.fromA;
  302. if (!inX && !inY)
  303. break;
  304. if (inX && pos == curX.fromB && !enteredX) {
  305. deleted = Span.join(deleted, curX.deleted, combine);
  306. toA += curX.lenA;
  307. enteredX = true;
  308. }
  309. if (inX && !inY) {
  310. inserted = Span.join(inserted, Span.slice(curX.inserted, pos - curX.fromB, next - curX.fromB), combine);
  311. toB += next - pos;
  312. }
  313. if (inY && pos == curY.fromA && !enteredY) {
  314. inserted = Span.join(inserted, curY.inserted, combine);
  315. toB += curY.lenB;
  316. enteredY = true;
  317. }
  318. if (inY && !inX) {
  319. deleted = Span.join(deleted, Span.slice(curY.deleted, pos - curY.fromA, next - curY.fromA), combine);
  320. toA += next - pos;
  321. }
  322. if (inX && next == curX.toB) {
  323. curX = iX++ == x.length ? null : x[iX];
  324. enteredX = false;
  325. }
  326. if (inY && next == curY.toA) {
  327. curY = iY++ == y.length ? null : y[iY];
  328. enteredY = false;
  329. }
  330. pos = next;
  331. }
  332. if (fromA < toA || fromB < toB)
  333. result.push(new Change(fromA, toA, fromB, toB, deleted, inserted));
  334. }
  335. }
  336. }
  337. }
  338. let letter;
  339. // If the runtime support unicode properties in regexps, that's a good
  340. // source of info on whether something is a letter.
  341. try {
  342. letter = new RegExp("[\\p{Alphabetic}_]", "u");
  343. }
  344. catch (_) { }
  345. // Otherwise, we see if the character changes when upper/lowercased,
  346. // or if it is part of these common single-case scripts.
  347. const nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
  348. function isLetter(code) {
  349. if (code < 128)
  350. return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 79 && code <= 122;
  351. let ch = String.fromCharCode(code);
  352. if (letter)
  353. return letter.test(ch);
  354. return ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch);
  355. }
  356. // Convert a range of document into a string, so that we can easily
  357. // access characters at a given position. Treat non-text tokens as
  358. // spaces so that they aren't considered part of a word.
  359. function getText(frag, start, end) {
  360. let out = "";
  361. function convert(frag, start, end) {
  362. for (let i = 0, off = 0; i < frag.childCount; i++) {
  363. let child = frag.child(i), endOff = off + child.nodeSize;
  364. let from = Math.max(off, start), to = Math.min(endOff, end);
  365. if (from < to) {
  366. if (child.isText) {
  367. out += child.text.slice(Math.max(0, start - off), Math.min(child.text.length, end - off));
  368. }
  369. else if (child.isLeaf) {
  370. out += " ";
  371. }
  372. else {
  373. if (from == off)
  374. out += " ";
  375. convert(child.content, Math.max(0, from - off - 1), Math.min(child.content.size, end - off));
  376. if (to == endOff)
  377. out += " ";
  378. }
  379. }
  380. off = endOff;
  381. }
  382. }
  383. convert(frag, start, end);
  384. return out;
  385. }
  386. // The distance changes have to be apart for us to not consider them
  387. // candidates for merging.
  388. const MAX_SIMPLIFY_DISTANCE = 30;
  389. /**
  390. Simplifies a set of changes for presentation. This makes the
  391. assumption that having both insertions and deletions within a word
  392. is confusing, and, when such changes occur without a word boundary
  393. between them, they should be expanded to cover the entire set of
  394. words (in the new document) they touch. An exception is made for
  395. single-character replacements.
  396. */
  397. function simplifyChanges(changes, doc) {
  398. let result = [];
  399. for (let i = 0; i < changes.length; i++) {
  400. let end = changes[i].toB, start = i;
  401. while (i < changes.length - 1 && changes[i + 1].fromB <= end + MAX_SIMPLIFY_DISTANCE)
  402. end = changes[++i].toB;
  403. simplifyAdjacentChanges(changes, start, i + 1, doc, result);
  404. }
  405. return result;
  406. }
  407. function simplifyAdjacentChanges(changes, from, to, doc, target) {
  408. let start = Math.max(0, changes[from].fromB - MAX_SIMPLIFY_DISTANCE);
  409. let end = Math.min(doc.content.size, changes[to - 1].toB + MAX_SIMPLIFY_DISTANCE);
  410. let text = getText(doc.content, start, end);
  411. for (let i = from; i < to; i++) {
  412. let startI = i, last = changes[i], deleted = last.lenA, inserted = last.lenB;
  413. while (i < to - 1) {
  414. let next = changes[i + 1], boundary = false;
  415. let prevLetter = last.toB == end ? false : isLetter(text.charCodeAt(last.toB - 1 - start));
  416. for (let pos = last.toB; !boundary && pos < next.fromB; pos++) {
  417. let nextLetter = pos == end ? false : isLetter(text.charCodeAt(pos - start));
  418. if ((!prevLetter || !nextLetter) && pos != changes[startI].fromB)
  419. boundary = true;
  420. prevLetter = nextLetter;
  421. }
  422. if (boundary)
  423. break;
  424. deleted += next.lenA;
  425. inserted += next.lenB;
  426. last = next;
  427. i++;
  428. }
  429. if (inserted > 0 && deleted > 0 && !(inserted == 1 && deleted == 1)) {
  430. let from = changes[startI].fromB, to = changes[i].toB;
  431. if (from < end && isLetter(text.charCodeAt(from - start)))
  432. while (from > start && isLetter(text.charCodeAt(from - 1 - start)))
  433. from--;
  434. if (to > start && isLetter(text.charCodeAt(to - 1 - start)))
  435. while (to < end && isLetter(text.charCodeAt(to - start)))
  436. to++;
  437. let joined = fillChange(changes.slice(startI, i + 1), from, to);
  438. let last = target.length ? target[target.length - 1] : null;
  439. if (last && last.toA == joined.fromA)
  440. target[target.length - 1] = new Change(last.fromA, joined.toA, last.fromB, joined.toB, last.deleted.concat(joined.deleted), last.inserted.concat(joined.inserted));
  441. else
  442. target.push(joined);
  443. }
  444. else {
  445. for (let j = startI; j <= i; j++)
  446. target.push(changes[j]);
  447. }
  448. }
  449. return changes;
  450. }
  451. function combine(a, b) { return a === b ? a : null; }
  452. function fillChange(changes, fromB, toB) {
  453. let fromA = changes[0].fromA - (changes[0].fromB - fromB);
  454. let last = changes[changes.length - 1];
  455. let toA = last.toA + (toB - last.toB);
  456. let deleted = Span.none, inserted = Span.none;
  457. let delData = (changes[0].deleted.length ? changes[0].deleted : changes[0].inserted)[0].data;
  458. let insData = (changes[0].inserted.length ? changes[0].inserted : changes[0].deleted)[0].data;
  459. for (let posA = fromA, posB = fromB, i = 0;; i++) {
  460. let next = i == changes.length ? null : changes[i];
  461. let endA = next ? next.fromA : toA, endB = next ? next.fromB : toB;
  462. if (endA > posA)
  463. deleted = Span.join(deleted, [new Span(endA - posA, delData)], combine);
  464. if (endB > posB)
  465. inserted = Span.join(inserted, [new Span(endB - posB, insData)], combine);
  466. if (!next)
  467. break;
  468. deleted = Span.join(deleted, next.deleted, combine);
  469. inserted = Span.join(inserted, next.inserted, combine);
  470. if (deleted.length)
  471. delData = deleted[deleted.length - 1].data;
  472. if (inserted.length)
  473. insData = inserted[inserted.length - 1].data;
  474. posA = next.toA;
  475. posB = next.toB;
  476. }
  477. return new Change(fromA, toA, fromB, toB, deleted, inserted);
  478. }
  479. /**
  480. A change set tracks the changes to a document from a given point
  481. in the past. It condenses a number of step maps down to a flat
  482. sequence of replacements, and simplifies replacments that
  483. partially undo themselves by comparing their content.
  484. */
  485. class ChangeSet {
  486. /**
  487. @internal
  488. */
  489. constructor(
  490. /**
  491. @internal
  492. */
  493. config,
  494. /**
  495. Replaced regions.
  496. */
  497. changes) {
  498. this.config = config;
  499. this.changes = changes;
  500. }
  501. /**
  502. Computes a new changeset by adding the given step maps and
  503. metadata (either as an array, per-map, or as a single value to be
  504. associated with all maps) to the current set. Will not mutate the
  505. old set.
  506. Note that due to simplification that happens after each add,
  507. incrementally adding steps might create a different final set
  508. than adding all those changes at once, since different document
  509. tokens might be matched during simplification depending on the
  510. boundaries of the current changed ranges.
  511. */
  512. addSteps(newDoc, maps, data) {
  513. // This works by inspecting the position maps for the changes,
  514. // which indicate what parts of the document were replaced by new
  515. // content, and the size of that new content. It uses these to
  516. // build up Change objects.
  517. //
  518. // These change objects are put in sets and merged together using
  519. // Change.merge, giving us the changes created by the new steps.
  520. // Those changes can then be merged with the existing set of
  521. // changes.
  522. //
  523. // For each change that was touched by the new steps, we recompute
  524. // a diff to try to minimize the change by dropping matching
  525. // pieces of the old and new document from the change.
  526. let stepChanges = [];
  527. // Add spans for new steps.
  528. for (let i = 0; i < maps.length; i++) {
  529. let d = Array.isArray(data) ? data[i] : data;
  530. let off = 0;
  531. maps[i].forEach((fromA, toA, fromB, toB) => {
  532. stepChanges.push(new Change(fromA + off, toA + off, fromB, toB, fromA == toA ? Span.none : [new Span(toA - fromA, d)], fromB == toB ? Span.none : [new Span(toB - fromB, d)]));
  533. off = (toB - fromB) - (toA - fromA);
  534. });
  535. }
  536. if (stepChanges.length == 0)
  537. return this;
  538. let newChanges = mergeAll(stepChanges, this.config.combine);
  539. let changes = Change.merge(this.changes, newChanges, this.config.combine);
  540. let updated = changes;
  541. // Minimize changes when possible
  542. for (let i = 0; i < updated.length; i++) {
  543. let change = updated[i];
  544. if (change.fromA == change.toA || change.fromB == change.toB ||
  545. // Only look at changes that touch newly added changed ranges
  546. !newChanges.some(r => r.toB > change.fromB && r.fromB < change.toB))
  547. continue;
  548. let diff = computeDiff(this.config.doc.content, newDoc.content, change);
  549. // Fast path: If they are completely different, don't do anything
  550. if (diff.length == 1 && diff[0].fromB == 0 && diff[0].toB == change.toB - change.fromB)
  551. continue;
  552. if (updated == changes)
  553. updated = changes.slice();
  554. if (diff.length == 1) {
  555. updated[i] = diff[0];
  556. }
  557. else {
  558. updated.splice(i, 1, ...diff);
  559. i += diff.length - 1;
  560. }
  561. }
  562. return new ChangeSet(this.config, updated);
  563. }
  564. /**
  565. The starting document of the change set.
  566. */
  567. get startDoc() { return this.config.doc; }
  568. /**
  569. Map the span's data values in the given set through a function
  570. and construct a new set with the resulting data.
  571. */
  572. map(f) {
  573. let mapSpan = (span) => {
  574. let newData = f(span);
  575. return newData === span.data ? span : new Span(span.length, newData);
  576. };
  577. return new ChangeSet(this.config, this.changes.map((ch) => {
  578. return new Change(ch.fromA, ch.toA, ch.fromB, ch.toB, ch.deleted.map(mapSpan), ch.inserted.map(mapSpan));
  579. }));
  580. }
  581. /**
  582. Compare two changesets and return the range in which they are
  583. changed, if any. If the document changed between the maps, pass
  584. the maps for the steps that changed it as second argument, and
  585. make sure the method is called on the old set and passed the new
  586. set. The returned positions will be in new document coordinates.
  587. */
  588. changedRange(b, maps) {
  589. if (b == this)
  590. return null;
  591. let touched = maps && touchedRange(maps);
  592. let moved = touched ? (touched.toB - touched.fromB) - (touched.toA - touched.fromA) : 0;
  593. function map(p) {
  594. return !touched || p <= touched.fromA ? p : p + moved;
  595. }
  596. let from = touched ? touched.fromB : 2e8, to = touched ? touched.toB : -2e8;
  597. function add(start, end = start) {
  598. from = Math.min(start, from);
  599. to = Math.max(end, to);
  600. }
  601. let rA = this.changes, rB = b.changes;
  602. for (let iA = 0, iB = 0; iA < rA.length && iB < rB.length;) {
  603. let rangeA = rA[iA], rangeB = rB[iB];
  604. if (rangeA && rangeB && sameRanges(rangeA, rangeB, map)) {
  605. iA++;
  606. iB++;
  607. }
  608. else if (rangeB && (!rangeA || map(rangeA.fromB) >= rangeB.fromB)) {
  609. add(rangeB.fromB, rangeB.toB);
  610. iB++;
  611. }
  612. else {
  613. add(map(rangeA.fromB), map(rangeA.toB));
  614. iA++;
  615. }
  616. }
  617. return from <= to ? { from, to } : null;
  618. }
  619. /**
  620. Create a changeset with the given base object and configuration.
  621. The `combine` function is used to compare and combine metadata—it
  622. should return null when metadata isn't compatible, and a combined
  623. version for a merged range when it is.
  624. */
  625. static create(doc, combine = (a, b) => a === b ? a : null) {
  626. return new ChangeSet({ combine, doc }, []);
  627. }
  628. }
  629. /**
  630. Exported for testing @internal
  631. */
  632. ChangeSet.computeDiff = computeDiff;
  633. // Divide-and-conquer approach to merging a series of ranges.
  634. function mergeAll(ranges, combine, start = 0, end = ranges.length) {
  635. if (end == start + 1)
  636. return [ranges[start]];
  637. let mid = (start + end) >> 1;
  638. return Change.merge(mergeAll(ranges, combine, start, mid), mergeAll(ranges, combine, mid, end), combine);
  639. }
  640. function endRange(maps) {
  641. let from = 2e8, to = -2e8;
  642. for (let i = 0; i < maps.length; i++) {
  643. let map = maps[i];
  644. if (from != 2e8) {
  645. from = map.map(from, -1);
  646. to = map.map(to, 1);
  647. }
  648. map.forEach((_s, _e, start, end) => {
  649. from = Math.min(from, start);
  650. to = Math.max(to, end);
  651. });
  652. }
  653. return from == 2e8 ? null : { from, to };
  654. }
  655. function touchedRange(maps) {
  656. let b = endRange(maps);
  657. if (!b)
  658. return null;
  659. let a = endRange(maps.map(m => m.invert()).reverse());
  660. return { fromA: a.from, toA: a.to, fromB: b.from, toB: b.to };
  661. }
  662. function sameRanges(a, b, map) {
  663. return map(a.fromB) == b.fromB && map(a.toB) == b.toB &&
  664. sameSpans(a.deleted, b.deleted) && sameSpans(a.inserted, b.inserted);
  665. }
  666. function sameSpans(a, b) {
  667. if (a.length != b.length)
  668. return false;
  669. for (let i = 0; i < a.length; i++)
  670. if (a[i].length != b[i].length || a[i].data !== b[i].data)
  671. return false;
  672. return true;
  673. }
  674. export { Change, ChangeSet, Span, simplifyChanges };