prosemirror-changeset 2.4.3 → 2.4.4

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.4 (2026-09-25)
2
+
3
+ ### Bug fixes
4
+
5
+ Increase the size of changed chunks that diffing will compare prefix and suffix equality for, even if too big to properly diff.
6
+
1
7
  ## 2.4.3 (2026-09-17)
2
8
 
3
9
  ### Bug fixes
package/dist/index.cjs CHANGED
@@ -53,13 +53,14 @@ function tokens(frag, encoder, start, end, target) {
53
53
  }
54
54
  return target;
55
55
  }
56
- var MAX_DIFF_SIZE = 2500;
56
+ var MAX_DIFF_SIZE = 2500,
57
+ MAX_TOKEN_SIZE = 10000;
57
58
  function minUnchanged(sizeA, sizeB) {
58
59
  return Math.min(15, Math.max(2, Math.floor(Math.max(sizeA, sizeB) / 10)));
59
60
  }
60
61
  function computeDiff(fragA, fragB, range) {
61
62
  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];
63
+ if (Math.max(range.toA - range.fromA, range.toB - range.fromB) > MAX_TOKEN_SIZE) return [range];
63
64
  var tokA = tokens(fragA, encoder, range.fromA, range.toA, []);
64
65
  var tokB = tokens(fragB, encoder, range.fromB, range.toB, []);
65
66
  return diff(tokA, tokB, range, encoder.compareTokens);
@@ -71,7 +72,8 @@ function diff(tokA, tokB, range, cmp) {
71
72
  while (start < tokA.length && start < tokB.length && cmp(tokA[start], tokB[start])) start++;
72
73
  if (start == tokA.length && start == tokB.length) return [];
73
74
  while (endA > start && endB > start && cmp(tokA[endA - 1], tokB[endB - 1])) endA--, endB--;
74
- if (endA == start || endB == start || endA == endB && endA == start + 1) return [range.slice(start, endA, start, endB)];
75
+ if (endA == start && endB == start) return [];
76
+ if (endA == start || endB == start || endA == endB && endA == start + 1 || Math.max(endA, endB) - start > MAX_DIFF_SIZE) return [range.slice(start, endA, start, endB)];
75
77
  var lenA = endA - start,
76
78
  lenB = endB - start;
77
79
  var max = Math.min(MAX_DIFF_SIZE, lenA + lenB);
@@ -134,7 +136,7 @@ function diff(tokA, tokB, range, cmp) {
134
136
  }
135
137
  if (size % 2 == 0) history.push(frontier.slice());
136
138
  }
137
- return [range];
139
+ return [range.slice(start, endA, start, endB)];
138
140
  }
139
141
  var Span = function () {
140
142
  function Span(length, data) {
package/dist/index.d.cts CHANGED
@@ -183,4 +183,5 @@ declare class ChangeSet<Data = any> {
183
183
  static create<Data = any>(doc: Node, combine?: (dataA: Data, dataB: Data) => Data, tokenEncoder?: TokenEncoder<any>, changes?: readonly Change<Data>[]): ChangeSet<Data>;
184
184
  }
185
185
 
186
- export { Change, type ChangeJSON, ChangeSet, Span, type TokenEncoder, simplifyChanges };
186
+ export { Change, ChangeSet, Span, simplifyChanges };
187
+ export type { ChangeJSON, TokenEncoder };
package/dist/index.d.ts CHANGED
@@ -183,4 +183,5 @@ declare class ChangeSet<Data = any> {
183
183
  static create<Data = any>(doc: Node, combine?: (dataA: Data, dataB: Data) => Data, tokenEncoder?: TokenEncoder<any>, changes?: readonly Change<Data>[]): ChangeSet<Data>;
184
184
  }
185
185
 
186
- export { Change, type ChangeJSON, ChangeSet, Span, type TokenEncoder, simplifyChanges };
186
+ export { Change, ChangeSet, Span, simplifyChanges };
187
+ export type { ChangeJSON, TokenEncoder };
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 = 2500;
45
+ const MAX_DIFF_SIZE = 2500, MAX_TOKEN_SIZE = 10000;
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
@@ -54,7 +54,7 @@ function minUnchanged(sizeA, sizeB) {
54
54
  function computeDiff(fragA, fragB, range, encoder = DefaultEncoder) {
55
55
  // When one side is longer than our max scan distance, the algorithm
56
56
  // will never find a diff.
57
- if (Math.max(range.toA - range.fromA, range.toB - range.fromB) > MAX_DIFF_SIZE)
57
+ if (Math.max(range.toA - range.fromA, range.toB - range.fromB) > MAX_TOKEN_SIZE)
58
58
  return [range];
59
59
  let tokA = tokens(fragA, encoder, range.fromA, range.toA, []);
60
60
  let tokB = tokens(fragB, encoder, range.fromB, range.toB, []);
@@ -69,9 +69,12 @@ function diff(tokA, tokB, range, cmp) {
69
69
  return [];
70
70
  while (endA > start && endB > start && cmp(tokA[endA - 1], tokB[endB - 1]))
71
71
  endA--, endB--;
72
+ // No difference
73
+ if (endA == start && endB == start)
74
+ return [];
72
75
  // If the result is simple _or_ too big to cheaply compute, return
73
76
  // the remaining region as the diff
74
- if (endA == start || endB == start || (endA == endB && endA == start + 1))
77
+ if (endA == start || endB == start || (endA == endB && endA == start + 1) || Math.max(endA, endB) - start > MAX_DIFF_SIZE)
75
78
  return [range.slice(start, endA, start, endB)];
76
79
  // This is an implementation of Myers' diff algorithm
77
80
  // See https://neil.fraser.name/writing/diff/myers.pdf and
@@ -136,7 +139,7 @@ function diff(tokA, tokB, range, cmp) {
136
139
  }
137
140
  // The loop exited, meaning the maximum amount of work was done.
138
141
  // Just return a change spanning the entire range.
139
- return [range];
142
+ return [range.slice(start, endA, start, endB)];
140
143
  }
141
144
 
142
145
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-changeset",
3
- "version": "2.4.3",
3
+ "version": "2.4.4",
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 = 2500
67
+ const MAX_DIFF_SIZE = 2500, MAX_TOKEN_SIZE = 10000
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
@@ -78,7 +78,7 @@ function minUnchanged(sizeA: number, sizeB: number) {
78
78
  export function computeDiff(fragA: Fragment, fragB: Fragment, range: Change, encoder: TokenEncoder<any> = DefaultEncoder) {
79
79
  // When one side is longer than our max scan distance, the algorithm
80
80
  // will never find a diff.
81
- if (Math.max(range.toA - range.fromA, range.toB - range.fromB) > MAX_DIFF_SIZE)
81
+ if (Math.max(range.toA - range.fromA, range.toB - range.fromB) > MAX_TOKEN_SIZE)
82
82
  return [range]
83
83
  let tokA = tokens(fragA, encoder, range.fromA, range.toA, [])
84
84
  let tokB = tokens(fragB, encoder, range.fromB, range.toB, [])
@@ -91,9 +91,11 @@ function diff<T>(tokA: T[], tokB: T[], range: Change, cmp: (a: T, b: T) => boole
91
91
  while (start < tokA.length && start < tokB.length && cmp(tokA[start], tokB[start])) start++
92
92
  if (start == tokA.length && start == tokB.length) return []
93
93
  while (endA > start && endB > start && cmp(tokA[endA - 1], tokB[endB - 1])) endA--, endB--
94
+ // No difference
95
+ if (endA == start && endB == start) return []
94
96
  // If the result is simple _or_ too big to cheaply compute, return
95
97
  // the remaining region as the diff
96
- if (endA == start || endB == start || (endA == endB && endA == start + 1))
98
+ if (endA == start || endB == start || (endA == endB && endA == start + 1) || Math.max(endA, endB) - start > MAX_DIFF_SIZE)
97
99
  return [range.slice(start, endA, start, endB)]
98
100
 
99
101
  // This is an implementation of Myers' diff algorithm
@@ -152,5 +154,5 @@ function diff<T>(tokA: T[], tokB: T[], range: Change, cmp: (a: T, b: T) => boole
152
154
  }
153
155
  // The loop exited, meaning the maximum amount of work was done.
154
156
  // Just return a change spanning the entire range.
155
- return [range]
157
+ return [range.slice(start, endA, start, endB)]
156
158
  }
package/test/test-diff.ts CHANGED
@@ -66,4 +66,12 @@ describe("computeDiff", () => {
66
66
 
67
67
  it("sees the difference between different closing tokens", () =>
68
68
  test(doc(p("a")), doc(h1("oo")), [0, 3, 0, 4]))
69
+
70
+ it("finds no changes on unchanged content", () =>
71
+ test(doc(p("abc")), doc(p("abc"))))
72
+
73
+ it("can compute a diff for a big change with unchanged prefix/suffix", () =>
74
+ test(doc(p("x".repeat(2000) + "a" + "y".repeat(2000))),
75
+ doc(p("x".repeat(2000) + "b" + "y".repeat(2000))),
76
+ [2001, 2002, 2001, 2002]))
69
77
  })