prosemirror-changeset 2.4.1 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 2.4.2 (2026-08-21)
2
+
3
+ ### Bug fixes
4
+
5
+ Fix an issue where the diffing of changed ranges would waste a bunch of time building up tokens and trying to compute a diff even if the ranges were too big to find a solution for.
6
+
1
7
  ## 2.4.1 (2026-04-14)
2
8
 
3
9
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -53,29 +53,30 @@ function tokens(frag, encoder, start, end, target) {
53
53
  }
54
54
  return target;
55
55
  }
56
- var MAX_DIFF_SIZE = 5000;
56
+ var MAX_DIFF_SIZE = 2500;
57
57
  function minUnchanged(sizeA, sizeB) {
58
58
  return Math.min(15, Math.max(2, Math.floor(Math.max(sizeA, sizeB) / 10)));
59
59
  }
60
60
  function computeDiff(fragA, fragB, range) {
61
61
  var encoder = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : DefaultEncoder;
62
+ if (Math.max(range.toA - range.fromA, range.toB, range.fromB) > MAX_DIFF_SIZE) return [range];
62
63
  var tokA = tokens(fragA, encoder, range.fromA, range.toA, []);
63
64
  var tokB = tokens(fragB, encoder, range.fromB, range.toB, []);
65
+ return diff(tokA, tokB, range, encoder.compareTokens);
66
+ }
67
+ function diff(tokA, tokB, range, cmp) {
64
68
  var start = 0,
65
69
  endA = tokA.length,
66
70
  endB = tokB.length;
67
- var cmp = encoder.compareTokens;
68
71
  while (start < tokA.length && start < tokB.length && cmp(tokA[start], tokB[start])) start++;
69
72
  if (start == tokA.length && start == tokB.length) return [];
70
73
  while (endA > start && endB > start && cmp(tokA[endA - 1], tokB[endB - 1])) endA--, endB--;
71
74
  if (endA == start || endB == start || endA == endB && endA == start + 1) return [range.slice(start, endA, start, endB)];
72
75
  var lenA = endA - start,
73
76
  lenB = endB - start;
74
- var max = Math.min(MAX_DIFF_SIZE, lenA + lenB),
75
- off = max + 1;
77
+ var max = Math.min(MAX_DIFF_SIZE, lenA + lenB);
76
78
  var history = [];
77
- var frontier = [];
78
- for (var len = off * 2, i = 0; i < len; i++) frontier[i] = -1;
79
+ var frontier = new Int16Array((max + 1) * 2).fill(-1);
79
80
  for (var size = 0; size <= max; size++) {
80
81
  var _loop = function _loop(_diag) {
81
82
  var next = frontier[_diag + 1 + max],
@@ -85,7 +86,7 @@ function computeDiff(fragA, fragB, range) {
85
86
  while (x < lenA && y < lenB && cmp(tokA[start + x], tokB[start + y])) x++, y++;
86
87
  frontier[_diag + max] = x;
87
88
  if (x >= lenA && y >= lenB) {
88
- var diff = [],
89
+ var _diff = [],
89
90
  minSpan = minUnchanged(endA - start, endB - start);
90
91
  var fromA = -1,
91
92
  toA = -1,
@@ -96,14 +97,14 @@ function computeDiff(fragA, fragB, range) {
96
97
  fromA = fA;
97
98
  fromB = fB;
98
99
  } else {
99
- if (fromA > -1) diff.push(range.slice(fromA, toA, fromB, toB));
100
+ if (fromA > -1) _diff.push(range.slice(fromA, toA, fromB, toB));
100
101
  fromA = fA;
101
102
  toA = tA;
102
103
  fromB = fB;
103
104
  toB = tB;
104
105
  }
105
106
  };
106
- for (var _i = size - 1; _i >= 0; _i--) {
107
+ for (var i = size - 1; i >= 0; i--) {
107
108
  var _next = frontier[_diag + 1 + max],
108
109
  _prev = frontier[_diag - 1 + max];
109
110
  if (_next < _prev) {
@@ -117,11 +118,11 @@ function computeDiff(fragA, fragB, range) {
117
118
  y = x + _diag;
118
119
  add(x, x + 1, y, y);
119
120
  }
120
- frontier = history[_i >> 1];
121
+ frontier = history[i >> 1];
121
122
  }
122
- if (fromA > -1) diff.push(range.slice(fromA, toA, fromB, toB));
123
+ if (fromA > -1) _diff.push(range.slice(fromA, toA, fromB, toB));
123
124
  return {
124
- v: diff.reverse()
125
+ v: _diff.reverse()
125
126
  };
126
127
  }
127
128
  diag = _diag;
@@ -133,7 +134,7 @@ function computeDiff(fragA, fragB, range) {
133
134
  }
134
135
  if (size % 2 == 0) history.push(frontier.slice());
135
136
  }
136
- return [range.slice(start, endA, start, endB)];
137
+ return [range];
137
138
  }
138
139
  var Span = function () {
139
140
  function Span(length, data) {
@@ -431,32 +432,32 @@ var ChangeSet = function () {
431
432
  var newChanges = mergeAll(stepChanges, this.config.combine);
432
433
  var changes = Change.merge(this.changes, newChanges, this.config.combine);
433
434
  var updated = changes;
434
- var _loop3 = function _loop3(_i3) {
435
- var change = updated[_i3];
435
+ var _loop3 = function _loop3(_i2) {
436
+ var change = updated[_i2];
436
437
  if (change.fromA == change.toA || change.fromB == change.toB || !newChanges.some(function (r) {
437
438
  return r.toB > change.fromB && r.fromB < change.toB;
438
439
  })) {
439
- _i2 = _i3;
440
+ _i = _i2;
440
441
  return 0;
441
442
  }
442
443
  var diff = computeDiff(_this.config.doc.content, newDoc.content, change, _this.config.encoder);
443
444
  if (diff.length == 1 && diff[0].fromB == 0 && diff[0].toB == change.toB - change.fromB) {
444
- _i2 = _i3;
445
+ _i = _i2;
445
446
  return 0;
446
447
  }
447
448
  if (updated == changes) updated = changes.slice();
448
449
  if (diff.length == 1) {
449
- updated[_i3] = diff[0];
450
+ updated[_i2] = diff[0];
450
451
  } else {
451
452
  var _updated;
452
- (_updated = updated).splice.apply(_updated, [_i3, 1].concat(_toConsumableArray(diff)));
453
- _i3 += diff.length - 1;
453
+ (_updated = updated).splice.apply(_updated, [_i2, 1].concat(_toConsumableArray(diff)));
454
+ _i2 += diff.length - 1;
454
455
  }
455
- _i2 = _i3;
456
+ _i = _i2;
456
457
  },
457
458
  _ret2;
458
- for (var _i2 = 0; _i2 < updated.length; _i2++) {
459
- _ret2 = _loop3(_i2);
459
+ for (var _i = 0; _i < updated.length; _i++) {
460
+ _ret2 = _loop3(_i);
460
461
  if (_ret2 === 0) continue;
461
462
  }
462
463
  return new ChangeSet(this.config, updated);
package/dist/index.js CHANGED
@@ -42,7 +42,7 @@ function tokens(frag, encoder, start, end, target) {
42
42
  // The code below will refuse to compute a diff with more than 5000
43
43
  // insertions or deletions, which takes about 300ms to reach on my
44
44
  // machine. This is a safeguard against runaway computations.
45
- const MAX_DIFF_SIZE = 5000;
45
+ const MAX_DIFF_SIZE = 2500;
46
46
  // This obscure mess of constants computes the minimum length of an
47
47
  // unchanged range (not at the start/end of the compared content). The
48
48
  // idea is to make it higher in bigger replacements, so that you don't
@@ -52,11 +52,17 @@ function minUnchanged(sizeA, sizeB) {
52
52
  return Math.min(15, Math.max(2, Math.floor(Math.max(sizeA, sizeB) / 10)));
53
53
  }
54
54
  function computeDiff(fragA, fragB, range, encoder = DefaultEncoder) {
55
+ // When one side is longer than our max scan distance, the algorithm
56
+ // will never find a diff.
57
+ if (Math.max(range.toA - range.fromA, range.toB, range.fromB) > MAX_DIFF_SIZE)
58
+ return [range];
55
59
  let tokA = tokens(fragA, encoder, range.fromA, range.toA, []);
56
60
  let tokB = tokens(fragB, encoder, range.fromB, range.toB, []);
61
+ return diff(tokA, tokB, range, encoder.compareTokens);
62
+ }
63
+ function diff(tokA, tokB, range, cmp) {
57
64
  // Scan from both sides to cheaply eliminate work
58
65
  let start = 0, endA = tokA.length, endB = tokB.length;
59
- let cmp = encoder.compareTokens;
60
66
  while (start < tokA.length && start < tokB.length && cmp(tokA[start], tokB[start]))
61
67
  start++;
62
68
  if (start == tokA.length && start == tokB.length)
@@ -71,11 +77,9 @@ function computeDiff(fragA, fragB, range, encoder = DefaultEncoder) {
71
77
  // See https://neil.fraser.name/writing/diff/myers.pdf and
72
78
  // https://blog.jcoglan.com/2017/02/12/the-myers-diff-algorithm-part-1/
73
79
  let lenA = endA - start, lenB = endB - start;
74
- let max = Math.min(MAX_DIFF_SIZE, lenA + lenB), off = max + 1;
80
+ let max = Math.min(MAX_DIFF_SIZE, lenA + lenB);
75
81
  let history = [];
76
- let frontier = [];
77
- for (let len = off * 2, i = 0; i < len; i++)
78
- frontier[i] = -1;
82
+ let frontier = new Int16Array((max + 1) * 2).fill(-1);
79
83
  for (let size = 0; size <= max; size++) {
80
84
  for (let diag = -size; diag <= size; diag += 2) {
81
85
  let next = frontier[diag + 1 + max], prev = frontier[diag - 1 + max];
@@ -132,7 +136,7 @@ function computeDiff(fragA, fragB, range, encoder = DefaultEncoder) {
132
136
  }
133
137
  // The loop exited, meaning the maximum amount of work was done.
134
138
  // Just return a change spanning the entire range.
135
- return [range.slice(start, endA, start, endB)];
139
+ return [range];
136
140
  }
137
141
 
138
142
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-changeset",
3
- "version": "2.4.1",
3
+ "version": "2.4.2",
4
4
  "description": "Distills a series of editing steps into deleted and added ranges",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
package/src/diff.ts CHANGED
@@ -64,7 +64,7 @@ function tokens<T>(frag: Fragment, encoder: TokenEncoder<T>, start: number, end:
64
64
  // The code below will refuse to compute a diff with more than 5000
65
65
  // insertions or deletions, which takes about 300ms to reach on my
66
66
  // machine. This is a safeguard against runaway computations.
67
- const MAX_DIFF_SIZE = 5000
67
+ const MAX_DIFF_SIZE = 2500
68
68
 
69
69
  // This obscure mess of constants computes the minimum length of an
70
70
  // unchanged range (not at the start/end of the compared content). The
@@ -76,12 +76,18 @@ function minUnchanged(sizeA: number, sizeB: number) {
76
76
  }
77
77
 
78
78
  export function computeDiff(fragA: Fragment, fragB: Fragment, range: Change, encoder: TokenEncoder<any> = DefaultEncoder) {
79
+ // When one side is longer than our max scan distance, the algorithm
80
+ // will never find a diff.
81
+ if (Math.max(range.toA - range.fromA, range.toB, range.fromB) > MAX_DIFF_SIZE)
82
+ return [range]
79
83
  let tokA = tokens(fragA, encoder, range.fromA, range.toA, [])
80
84
  let tokB = tokens(fragB, encoder, range.fromB, range.toB, [])
85
+ return diff(tokA, tokB, range, encoder.compareTokens)
86
+ }
81
87
 
88
+ function diff<T>(tokA: T[], tokB: T[], range: Change, cmp: (a: T, b: T) => boolean): Change[] {
82
89
  // Scan from both sides to cheaply eliminate work
83
90
  let start = 0, endA = tokA.length, endB = tokB.length
84
- let cmp = encoder.compareTokens
85
91
  while (start < tokA.length && start < tokB.length && cmp(tokA[start], tokB[start])) start++
86
92
  if (start == tokA.length && start == tokB.length) return []
87
93
  while (endA > start && endB > start && cmp(tokA[endA - 1], tokB[endB - 1])) endA--, endB--
@@ -95,10 +101,9 @@ export function computeDiff(fragA: Fragment, fragB: Fragment, range: Change, enc
95
101
  // https://blog.jcoglan.com/2017/02/12/the-myers-diff-algorithm-part-1/
96
102
 
97
103
  let lenA = endA - start, lenB = endB - start
98
- let max = Math.min(MAX_DIFF_SIZE, lenA + lenB), off = max + 1
99
- let history: number[][] = []
100
- let frontier: number[] = []
101
- for (let len = off * 2, i = 0; i < len; i++) frontier[i] = -1
104
+ let max = Math.min(MAX_DIFF_SIZE, lenA + lenB)
105
+ let history: Int16Array[] = []
106
+ let frontier = new Int16Array((max + 1) * 2).fill(-1)
102
107
 
103
108
  for (let size = 0; size <= max; size++) {
104
109
  for (let diag = -size; diag <= size; diag += 2) {
@@ -109,7 +114,7 @@ export function computeDiff(fragA: Fragment, fragB: Fragment, range: Change, enc
109
114
  // Found a match
110
115
  if (x >= lenA && y >= lenB) {
111
116
  // Trace back through the history to build up a set of changed ranges.
112
- let diff = [], minSpan = minUnchanged(endA - start, endB - start)
117
+ let diff: Change[] = [], minSpan = minUnchanged(endA - start, endB - start)
113
118
  // Used to add steps to a diff one at a time, back to front, merging
114
119
  // ones that are less than minSpan tokens apart
115
120
  let fromA = -1, toA = -1, fromB = -1, toB = -1
@@ -147,5 +152,5 @@ export function computeDiff(fragA: Fragment, fragB: Fragment, range: Change, enc
147
152
  }
148
153
  // The loop exited, meaning the maximum amount of work was done.
149
154
  // Just return a change spanning the entire range.
150
- return [range.slice(start, endA, start, endB)]
155
+ return [range]
151
156
  }