@pm-cm/core 0.0.1 → 0.0.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/dist/index.js CHANGED
@@ -1,36 +1,149 @@
1
1
  // src/bridge.ts
2
2
  var BRIDGE_META = "pm-cm-bridge";
3
- var defaultNormalize = (s) => s.replace(/\r\n?/g, "\n");
3
+ var DEFAULT_PARSE_CACHE_SIZE = 8;
4
+ var defaultNormalize = (s) => s.indexOf("\r") === -1 ? s : s.replace(/\r\n?/g, "\n");
4
5
  var defaultOnError = (event) => console.error(`[bridge] ${event.code}: ${event.message}`, event.cause);
6
+ function diffText(a, b) {
7
+ let start = 0;
8
+ const minLen = Math.min(a.length, b.length);
9
+ while (start < minLen && a.charCodeAt(start) === b.charCodeAt(start)) start++;
10
+ let endA = a.length;
11
+ let endB = b.length;
12
+ while (endA > start && endB > start && a.charCodeAt(endA - 1) === b.charCodeAt(endB - 1)) {
13
+ endA--;
14
+ endB--;
15
+ }
16
+ return { start, endA, endB };
17
+ }
18
+ var ParseLru = class {
19
+ map = /* @__PURE__ */ new Map();
20
+ limit;
21
+ constructor(limit) {
22
+ this.limit = limit;
23
+ }
24
+ get(key) {
25
+ const v = this.map.get(key);
26
+ if (v !== void 0) {
27
+ this.map.delete(key);
28
+ this.map.set(key, v);
29
+ }
30
+ return v;
31
+ }
32
+ set(key, value) {
33
+ if (this.map.has(key)) this.map.delete(key);
34
+ this.map.set(key, value);
35
+ if (this.map.size > this.limit) {
36
+ const first = this.map.keys().next().value;
37
+ this.map.delete(first);
38
+ }
39
+ }
40
+ };
5
41
  function createViewBridge(config) {
6
42
  const { schema, serialize, parse } = config;
7
43
  const normalize = config.normalize ?? defaultNormalize;
8
44
  const onError = config.onError ?? defaultOnError;
45
+ const incrementalParse = config.incrementalParse ?? null;
46
+ const cacheSize = config.parseCacheSize ?? DEFAULT_PARSE_CACHE_SIZE;
47
+ const serializeCache = /* @__PURE__ */ new WeakMap();
48
+ function cachedSerialize(doc) {
49
+ let text = serializeCache.get(doc);
50
+ if (text === void 0) {
51
+ text = normalize(serialize(doc));
52
+ serializeCache.set(doc, text);
53
+ }
54
+ return text;
55
+ }
56
+ const parseLru = cacheSize > 0 ? new ParseLru(cacheSize) : null;
57
+ function cachedParse(text) {
58
+ if (parseLru) {
59
+ const cached = parseLru.get(text);
60
+ if (cached) return cached;
61
+ }
62
+ const doc = parse(text, schema);
63
+ if (parseLru) parseLru.set(text, doc);
64
+ return doc;
65
+ }
66
+ let lastDoc = null;
67
+ let lastRaw = null;
68
+ let lastIncoming = null;
69
+ function markUnchanged(doc, raw, incoming) {
70
+ lastDoc = doc;
71
+ lastRaw = raw;
72
+ lastIncoming = incoming;
73
+ return { ok: false, reason: "unchanged" };
74
+ }
9
75
  return {
10
76
  applyText(view, text, options) {
11
- const incoming = normalize(text);
12
- const current = normalize(serialize(view.state.doc));
13
- if (incoming === current) {
77
+ const prevDoc = view.state.doc;
78
+ if (prevDoc === lastDoc && text === lastRaw) {
14
79
  return { ok: false, reason: "unchanged" };
15
80
  }
81
+ const incoming = options?.normalized ? text : normalize(text);
82
+ if (prevDoc === lastDoc && incoming === lastIncoming) {
83
+ lastRaw = text;
84
+ return { ok: false, reason: "unchanged" };
85
+ }
86
+ const current = cachedSerialize(prevDoc);
87
+ if (incoming === current) {
88
+ return markUnchanged(prevDoc, text, incoming);
89
+ }
16
90
  let nextDoc;
91
+ let rangeHint = null;
92
+ const diff = options?.diff ?? diffText(current, incoming);
17
93
  try {
18
- nextDoc = parse(incoming, schema);
94
+ if (incrementalParse) {
95
+ const result = incrementalParse({ prevDoc, prevText: current, text: incoming, diff, schema });
96
+ if (result == null) {
97
+ nextDoc = cachedParse(incoming);
98
+ } else if ("doc" in result && "from" in result) {
99
+ nextDoc = result.doc;
100
+ rangeHint = { from: result.from, to: result.to, toB: result.toB };
101
+ } else {
102
+ nextDoc = result;
103
+ }
104
+ } else {
105
+ nextDoc = cachedParse(incoming);
106
+ }
19
107
  } catch (error) {
20
108
  onError({ code: "parse-error", message: "failed to parse text into ProseMirror document", cause: error });
21
109
  return { ok: false, reason: "parse-error" };
22
110
  }
111
+ let from, to, toB;
112
+ if (rangeHint) {
113
+ from = rangeHint.from;
114
+ to = rangeHint.to;
115
+ toB = rangeHint.toB;
116
+ } else {
117
+ const start = prevDoc.content.findDiffStart(nextDoc.content);
118
+ if (start == null) {
119
+ return markUnchanged(prevDoc, text, incoming);
120
+ }
121
+ const end = prevDoc.content.findDiffEnd(nextDoc.content);
122
+ if (!end) {
123
+ return markUnchanged(prevDoc, text, incoming);
124
+ }
125
+ from = Math.min(start, end.a);
126
+ to = Math.max(start, end.a);
127
+ toB = Math.max(start, end.b);
128
+ }
23
129
  const tr = view.state.tr;
24
- tr.replaceWith(0, tr.doc.content.size, nextDoc.content);
130
+ tr.replace(from, to, nextDoc.slice(from, toB));
25
131
  tr.setMeta(BRIDGE_META, true);
26
132
  if (options?.addToHistory === false) {
27
133
  tr.setMeta("addToHistory", false);
28
134
  }
29
135
  view.dispatch(tr);
136
+ const newDoc = view.state.doc;
137
+ serializeCache.set(newDoc, incoming);
138
+ lastDoc = newDoc;
139
+ lastRaw = text;
140
+ lastIncoming = incoming;
30
141
  return { ok: true };
31
142
  },
32
143
  extractText(view) {
33
- return serialize(view.state.doc);
144
+ const text = serialize(view.state.doc);
145
+ serializeCache.set(view.state.doc, normalize(text));
146
+ return text;
34
147
  },
35
148
  isBridgeChange(tr) {
36
149
  return tr.getMeta(BRIDGE_META) === true;
@@ -57,39 +170,116 @@ function createBoundViewBridge(view, config) {
57
170
  }
58
171
 
59
172
  // src/cursor-map.ts
60
- var defaultLocate = (serialized, nodeText, from) => serialized.indexOf(nodeText, from);
61
- function buildCursorMap(doc, serialize, locate = defaultLocate) {
62
- const fullText = serialize(doc);
173
+ function createCursorMapWriter() {
174
+ let offset = 0;
175
+ const parts = [];
176
+ const segments = [];
177
+ let mappedCount = 0;
178
+ const writer = {
179
+ write(text) {
180
+ parts.push(text);
181
+ offset += text.length;
182
+ },
183
+ writeMapped(pmStart, pmEnd, text) {
184
+ segments.push({
185
+ pmStart,
186
+ pmEnd,
187
+ textStart: offset,
188
+ textEnd: offset + text.length
189
+ });
190
+ parts.push(text);
191
+ offset += text.length;
192
+ mappedCount++;
193
+ },
194
+ getText() {
195
+ return parts.join("");
196
+ },
197
+ getMappedCount() {
198
+ return mappedCount;
199
+ },
200
+ finish(doc) {
201
+ const textNodes = [];
202
+ function collectTextNodes(node, contentStart) {
203
+ node.forEach((child, childOffset) => {
204
+ const childPos = contentStart + childOffset;
205
+ if (child.isText && child.text) {
206
+ textNodes.push({ start: childPos, end: childPos + child.text.length });
207
+ } else if (!child.isLeaf) {
208
+ collectTextNodes(child, childPos + 1);
209
+ }
210
+ });
211
+ }
212
+ collectTextNodes(doc, 0);
213
+ let mappedNodes = 0;
214
+ let segIdx = 0;
215
+ for (const n of textNodes) {
216
+ while (segIdx < segments.length && segments[segIdx].pmEnd <= n.start) segIdx++;
217
+ let k = segIdx;
218
+ while (k < segments.length && segments[k].pmStart < n.end) {
219
+ const s = segments[k];
220
+ if (s.pmEnd > n.start && s.pmStart < n.end) {
221
+ mappedNodes++;
222
+ break;
223
+ }
224
+ k++;
225
+ }
226
+ }
227
+ return {
228
+ segments,
229
+ textLength: offset,
230
+ skippedNodes: Math.max(0, textNodes.length - mappedNodes)
231
+ };
232
+ }
233
+ };
234
+ return writer;
235
+ }
236
+ function buildCursorMap(doc, serialize) {
237
+ const writer = createCursorMapWriter();
238
+ const result = serialize(doc, writer);
239
+ if (typeof result === "string" && writer.getMappedCount() === 0) {
240
+ return forwardScanBuildMap(doc, result);
241
+ }
242
+ const map = writer.finish(doc);
243
+ for (let i = 1; i < map.segments.length; i++) {
244
+ const prev = map.segments[i - 1];
245
+ const curr = map.segments[i];
246
+ if (curr.pmStart < prev.pmEnd || curr.textStart < prev.textEnd) {
247
+ console.warn(
248
+ `[pm-cm] buildCursorMap: non-monotonic segment at index ${i} (pmStart ${curr.pmStart} < prev pmEnd ${prev.pmEnd} or textStart ${curr.textStart} < prev textEnd ${prev.textEnd}). Ensure writeMapped calls are in ascending PM document order.`
249
+ );
250
+ }
251
+ }
252
+ return map;
253
+ }
254
+ function forwardScanBuildMap(doc, text) {
63
255
  const segments = [];
64
256
  let searchFrom = 0;
257
+ let totalTextNodes = 0;
65
258
  let skippedNodes = 0;
66
- function walkChildren(node, contentStart) {
67
- node.forEach((child, offset) => {
68
- const childPos = contentStart + offset;
259
+ function visit(node, contentStart) {
260
+ node.forEach((child, childOffset) => {
261
+ const childPos = contentStart + childOffset;
69
262
  if (child.isText && child.text) {
70
- const text = child.text;
71
- const idx = locate(fullText, text, searchFrom);
263
+ totalTextNodes++;
264
+ const idx = text.indexOf(child.text, searchFrom);
72
265
  if (idx >= 0) {
73
266
  segments.push({
74
267
  pmStart: childPos,
75
- pmEnd: childPos + text.length,
268
+ pmEnd: childPos + child.text.length,
76
269
  textStart: idx,
77
- textEnd: idx + text.length
270
+ textEnd: idx + child.text.length
78
271
  });
79
- searchFrom = idx + text.length;
272
+ searchFrom = idx + child.text.length;
80
273
  } else {
81
274
  skippedNodes++;
82
275
  }
83
- return;
276
+ } else if (!child.isLeaf) {
277
+ visit(child, childPos + 1);
84
278
  }
85
- if (child.isLeaf) {
86
- return;
87
- }
88
- walkChildren(child, childPos + 1);
89
279
  });
90
280
  }
91
- walkChildren(doc, 0);
92
- return { segments, textLength: fullText.length, skippedNodes };
281
+ visit(doc, 0);
282
+ return { segments, textLength: text.length, skippedNodes };
93
283
  }
94
284
  function cursorMapLookup(map, pmPos) {
95
285
  const { segments } = map;
@@ -139,11 +329,69 @@ function reverseCursorMapLookup(map, cmOffset) {
139
329
  const distAfter = after.textStart - cmOffset;
140
330
  return distBefore <= distAfter ? before.pmEnd : after.pmStart;
141
331
  }
332
+ function wrapSerialize(serialize, matcher) {
333
+ return (doc, writer) => {
334
+ const text = serialize(doc);
335
+ const segments = collectMatchedSegments(doc, text, matcher);
336
+ let pos = 0;
337
+ for (const seg of segments) {
338
+ if (seg.textStart > pos) writer.write(text.slice(pos, seg.textStart));
339
+ writer.writeMapped(seg.pmStart, seg.pmEnd, text.slice(seg.textStart, seg.textEnd));
340
+ pos = seg.textEnd;
341
+ }
342
+ if (pos < text.length) writer.write(text.slice(pos));
343
+ };
344
+ }
345
+ function collectMatchedSegments(doc, text, matcher) {
346
+ const segments = [];
347
+ let searchFrom = 0;
348
+ function visit(node, contentStart) {
349
+ node.forEach((child, childOffset) => {
350
+ const childPos = contentStart + childOffset;
351
+ if (child.isText && child.text) {
352
+ const content = child.text;
353
+ const exactIdx = text.indexOf(content, searchFrom);
354
+ if (exactIdx >= 0) {
355
+ segments.push({
356
+ pmStart: childPos,
357
+ pmEnd: childPos + content.length,
358
+ textStart: exactIdx,
359
+ textEnd: exactIdx + content.length
360
+ });
361
+ searchFrom = exactIdx + content.length;
362
+ return;
363
+ }
364
+ if (matcher) {
365
+ const result = matcher(text, content, searchFrom);
366
+ if (result) {
367
+ for (const run of result.runs) {
368
+ segments.push({
369
+ pmStart: childPos + run.contentStart,
370
+ pmEnd: childPos + run.contentEnd,
371
+ textStart: run.textStart,
372
+ textEnd: run.textEnd
373
+ });
374
+ }
375
+ searchFrom = result.nextSearchFrom;
376
+ return;
377
+ }
378
+ }
379
+ } else if (!child.isLeaf) {
380
+ visit(child, childPos + 1);
381
+ }
382
+ });
383
+ }
384
+ visit(doc, 0);
385
+ return segments;
386
+ }
142
387
  export {
143
388
  buildCursorMap,
144
389
  createBoundViewBridge,
390
+ createCursorMapWriter,
145
391
  createViewBridge,
146
392
  cursorMapLookup,
147
- reverseCursorMapLookup
393
+ diffText,
394
+ reverseCursorMapLookup,
395
+ wrapSerialize
148
396
  };
149
397
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bridge.ts","../src/cursor-map.ts"],"sourcesContent":["import type { Node, Schema } from 'prosemirror-model'\nimport type { Transaction } from 'prosemirror-state'\nimport type { EditorView } from 'prosemirror-view'\nimport type { Normalize, Serialize, Parse, OnError } from './types.js'\n\nconst BRIDGE_META = 'pm-cm-bridge'\nconst defaultNormalize: Normalize = (s) => s.replace(/\\r\\n?/g, '\\n')\nconst defaultOnError: OnError = (event) => console.error(`[bridge] ${event.code}: ${event.message}`, event.cause)\n\n/** Configuration for {@link createViewBridge}. */\nexport type ViewBridgeConfig = {\n schema: Schema\n serialize: Serialize\n parse: Parse\n normalize?: Normalize\n /** Called on non-fatal errors (e.g. parse failures). Defaults to `console.error`. */\n onError?: OnError\n}\n\n/** Options for {@link ViewBridgeHandle.applyText}. */\nexport type ApplyTextOptions = {\n /** Set `false` to prevent the change from being added to undo history. Default `true`. */\n addToHistory?: boolean\n}\n\n/**\n * Discriminated-union result of {@link ViewBridgeHandle.applyText}.\n * `ok: true` when the text was applied; `ok: false` with a `reason` otherwise.\n */\nexport type ApplyTextResult =\n | { ok: true }\n | { ok: false; reason: 'unchanged' | 'parse-error' }\n\n/** Handle returned by {@link createViewBridge}. */\nexport type ViewBridgeHandle = {\n /** Parse `text` and replace the ProseMirror document. Returns an {@link ApplyTextResult}. */\n applyText(view: EditorView, text: string, options?: ApplyTextOptions): ApplyTextResult\n /** Serialize the current ProseMirror document to text. */\n extractText(view: EditorView): string\n /** Returns `true` if the transaction was dispatched by {@link applyText}. */\n isBridgeChange(tr: Transaction): boolean\n}\n\n/** Handle returned by {@link createBoundViewBridge}. View is bound; no need to pass it each call. */\nexport type BoundViewBridgeHandle = {\n /** Parse `text` and replace the ProseMirror document. */\n applyText(text: string, options?: ApplyTextOptions): ApplyTextResult\n /** Serialize the current ProseMirror document to text. */\n extractText(): string\n /** Returns `true` if the transaction was dispatched by {@link applyText}. */\n isBridgeChange(tr: Transaction): boolean\n /** Replace the bound EditorView. */\n setView(view: EditorView): void\n}\n\n/**\n * Create a document-sync bridge between ProseMirror and a text editor.\n *\n * Returns a {@link ViewBridgeHandle} with methods to push/pull text and\n * detect bridge-originated transactions.\n */\nexport function createViewBridge(config: ViewBridgeConfig): ViewBridgeHandle {\n const { schema, serialize, parse } = config\n const normalize = config.normalize ?? defaultNormalize\n const onError = config.onError ?? defaultOnError\n\n return {\n applyText(view: EditorView, text: string, options?: ApplyTextOptions): ApplyTextResult {\n const incoming = normalize(text)\n const current = normalize(serialize(view.state.doc))\n\n if (incoming === current) {\n return { ok: false, reason: 'unchanged' }\n }\n\n let nextDoc: Node\n try {\n nextDoc = parse(incoming, schema)\n } catch (error) {\n onError({ code: 'parse-error', message: 'failed to parse text into ProseMirror document', cause: error })\n return { ok: false, reason: 'parse-error' }\n }\n\n const tr = view.state.tr\n tr.replaceWith(0, tr.doc.content.size, nextDoc.content)\n tr.setMeta(BRIDGE_META, true)\n if (options?.addToHistory === false) {\n tr.setMeta('addToHistory', false)\n }\n view.dispatch(tr)\n return { ok: true }\n },\n\n extractText(view: EditorView): string {\n return serialize(view.state.doc)\n },\n\n isBridgeChange(tr: Transaction): boolean {\n return tr.getMeta(BRIDGE_META) === true\n },\n }\n}\n\n/**\n * Create a view-bound document-sync bridge. Wraps {@link createViewBridge}\n * so that the `EditorView` does not need to be passed to each method call.\n *\n * @param view - The initial EditorView to bind.\n * @param config - Configuration for the underlying bridge.\n */\nexport function createBoundViewBridge(view: EditorView, config: ViewBridgeConfig): BoundViewBridgeHandle {\n const inner = createViewBridge(config)\n let currentView = view\n\n return {\n applyText(text: string, options?: ApplyTextOptions): ApplyTextResult {\n return inner.applyText(currentView, text, options)\n },\n extractText(): string {\n return inner.extractText(currentView)\n },\n isBridgeChange(tr: Transaction): boolean {\n return inner.isBridgeChange(tr)\n },\n setView(v: EditorView): void {\n currentView = v\n },\n }\n}\n","import type { Node } from 'prosemirror-model'\nimport type { Serialize } from './types.js'\n\n/** A mapping between a ProseMirror position range and a serialized-text offset range. */\nexport type TextSegment = {\n pmStart: number // PM position (inclusive)\n pmEnd: number // PM position (exclusive)\n textStart: number // serialized text offset (inclusive)\n textEnd: number // serialized text offset (exclusive)\n}\n\n/**\n * Sorted list of {@link TextSegment}s produced by {@link buildCursorMap}.\n * Use {@link cursorMapLookup} and {@link reverseCursorMapLookup} for O(log n) queries.\n */\nexport type CursorMap = {\n segments: TextSegment[]\n textLength: number\n /** Number of text nodes that could not be located in the serialized output. */\n skippedNodes: number\n}\n\n/**\n * Locate a text-node string within the serialized output.\n * Return the starting index, or -1 if not found.\n * Default: `(serialized, nodeText, from) => serialized.indexOf(nodeText, from)`\n */\nexport type LocateText = (serialized: string, nodeText: string, searchFrom: number) => number\n\nconst defaultLocate: LocateText = (serialized, nodeText, from) =>\n serialized.indexOf(nodeText, from)\n\n/**\n * Build a cursor map that aligns ProseMirror positions with serialized-text offsets.\n *\n * Walks the document tree and locates each text node within the serialized output,\n * producing a sorted list of {@link TextSegment}s.\n *\n * @param doc - The ProseMirror document to map.\n * @param serialize - Serializer used to produce the full text.\n * @param locate - Optional custom text-location function. Defaults to `indexOf`.\n */\nexport function buildCursorMap(\n doc: Node,\n serialize: Serialize,\n locate: LocateText = defaultLocate,\n): CursorMap {\n const fullText = serialize(doc)\n const segments: TextSegment[] = []\n let searchFrom = 0\n let skippedNodes = 0\n\n function walkChildren(node: Node, contentStart: number): void {\n node.forEach((child, offset) => {\n const childPos = contentStart + offset\n\n if (child.isText && child.text) {\n const text = child.text\n const idx = locate(fullText, text, searchFrom)\n if (idx >= 0) {\n segments.push({\n pmStart: childPos,\n pmEnd: childPos + text.length,\n textStart: idx,\n textEnd: idx + text.length,\n })\n searchFrom = idx + text.length\n } else {\n skippedNodes++\n }\n return\n }\n\n if (child.isLeaf) {\n return\n }\n\n // Container node: content starts at childPos + 1 (open tag)\n walkChildren(child, childPos + 1)\n })\n }\n\n // doc's content starts at position 0\n walkChildren(doc, 0)\n\n return { segments, textLength: fullText.length, skippedNodes }\n}\n\n/**\n * Look up a ProseMirror position in a cursor map and return the corresponding text offset.\n * Returns `null` when the map has no segments.\n */\nexport function cursorMapLookup(map: CursorMap, pmPos: number): number | null {\n const { segments } = map\n if (segments.length === 0) return null\n\n // Binary search for the segment containing pmPos\n let lo = 0\n let hi = segments.length - 1\n\n while (lo <= hi) {\n const mid = (lo + hi) >>> 1\n const seg = segments[mid]\n\n if (pmPos < seg.pmStart) {\n hi = mid - 1\n } else if (pmPos >= seg.pmEnd) {\n lo = mid + 1\n } else {\n // Inside segment: exact mapping\n return seg.textStart + (pmPos - seg.pmStart)\n }\n }\n\n // pmPos is between segments — snap to nearest boundary\n // After binary search: hi < lo, pmPos falls between segments[hi] and segments[lo]\n const before = hi >= 0 ? segments[hi] : null\n const after = lo < segments.length ? segments[lo] : null\n\n if (!before) return after ? after.textStart : 0\n if (!after) return before.textEnd\n\n const distBefore = pmPos - before.pmEnd\n const distAfter = after.pmStart - pmPos\n return distBefore <= distAfter ? before.textEnd : after.textStart\n}\n\n/**\n * Look up a text offset (e.g. CodeMirror position) in a cursor map and return the corresponding ProseMirror position.\n * Returns `null` when the map has no segments.\n */\nexport function reverseCursorMapLookup(map: CursorMap, cmOffset: number): number | null {\n const { segments } = map\n if (segments.length === 0) return null\n\n // Binary search for the segment containing cmOffset\n let lo = 0\n let hi = segments.length - 1\n\n while (lo <= hi) {\n const mid = (lo + hi) >>> 1\n const seg = segments[mid]\n\n if (cmOffset < seg.textStart) {\n hi = mid - 1\n } else if (cmOffset >= seg.textEnd) {\n lo = mid + 1\n } else {\n // Inside segment: exact mapping\n return seg.pmStart + (cmOffset - seg.textStart)\n }\n }\n\n // cmOffset is between segments — snap to nearest boundary\n const before = hi >= 0 ? segments[hi] : null\n const after = lo < segments.length ? segments[lo] : null\n\n if (!before) return after ? after.pmStart : 0\n if (!after) return before.pmEnd\n\n const distBefore = cmOffset - before.textEnd\n const distAfter = after.textStart - cmOffset\n return distBefore <= distAfter ? before.pmEnd : after.pmStart\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/bridge.ts","../src/cursor-map.ts"],"sourcesContent":["import type { Node, Schema } from 'prosemirror-model'\nimport type { Transaction } from 'prosemirror-state'\nimport type { EditorView } from 'prosemirror-view'\nimport type { Normalize, Serialize, Parse, OnError, IncrementalParse, IncrementalParseResult, TextDiff } from './types.js'\n\nconst BRIDGE_META = 'pm-cm-bridge'\nconst DEFAULT_PARSE_CACHE_SIZE = 8\nconst defaultNormalize: Normalize = (s) => (s.indexOf('\\r') === -1 ? s : s.replace(/\\r\\n?/g, '\\n'))\nconst defaultOnError: OnError = (event) => console.error(`[bridge] ${event.code}: ${event.message}`, event.cause)\n\n/** Compute the changed region between two strings. */\nexport function diffText(a: string, b: string): TextDiff {\n let start = 0\n const minLen = Math.min(a.length, b.length)\n while (start < minLen && a.charCodeAt(start) === b.charCodeAt(start)) start++\n let endA = a.length\n let endB = b.length\n while (endA > start && endB > start && a.charCodeAt(endA - 1) === b.charCodeAt(endB - 1)) {\n endA--\n endB--\n }\n return { start, endA, endB }\n}\n\n/** Simple LRU cache for parse results. */\nclass ParseLru {\n private map = new Map<string, Node>()\n private limit: number\n constructor(limit: number) {\n this.limit = limit\n }\n get(key: string): Node | undefined {\n const v = this.map.get(key)\n if (v !== undefined) {\n this.map.delete(key)\n this.map.set(key, v)\n }\n return v\n }\n set(key: string, value: Node): void {\n if (this.map.has(key)) this.map.delete(key)\n this.map.set(key, value)\n if (this.map.size > this.limit) {\n const first = this.map.keys().next().value!\n this.map.delete(first)\n }\n }\n}\n\n/** Configuration for {@link createViewBridge}. */\nexport type ViewBridgeConfig = {\n schema: Schema\n serialize: Serialize\n parse: Parse\n normalize?: Normalize\n /** Called on non-fatal errors (e.g. parse failures). Defaults to `console.error`. */\n onError?: OnError\n /**\n * Optional incremental parser for large documents.\n * When provided, the bridge computes a text-level diff and passes it to this\n * function instead of calling the full {@link Parse}. Return `null` to fall\n * back to full parse.\n */\n incrementalParse?: IncrementalParse\n /** Maximum number of parse results to cache. Defaults to `8`. Set `0` to disable. */\n parseCacheSize?: number\n}\n\n/** Options for {@link ViewBridgeHandle.applyText}. */\nexport type ApplyTextOptions = {\n /** Set `false` to prevent the change from being added to undo history. Default `true`. */\n addToHistory?: boolean\n /**\n * Pre-computed text diff from the editor's change set.\n * When provided, skips the internal `diffText` O(n) scan.\n * The diff describes the changed region between the previous and incoming\n * **normalized** text.\n */\n diff?: TextDiff\n /**\n * Set `true` when the caller guarantees the text is already normalized\n * (no `\\r` characters). Skips the `normalize` pass entirely.\n */\n normalized?: boolean\n}\n\n/**\n * Discriminated-union result of {@link ViewBridgeHandle.applyText}.\n * `ok: true` when the text was applied; `ok: false` with a `reason` otherwise.\n */\nexport type ApplyTextResult =\n | { ok: true }\n | { ok: false; reason: 'unchanged' | 'parse-error' }\n\n/** Handle returned by {@link createViewBridge}. */\nexport type ViewBridgeHandle = {\n /** Parse `text` and replace the ProseMirror document. Returns an {@link ApplyTextResult}. */\n applyText(view: EditorView, text: string, options?: ApplyTextOptions): ApplyTextResult\n /** Serialize the current ProseMirror document to text. */\n extractText(view: EditorView): string\n /** Returns `true` if the transaction was dispatched by {@link applyText}. */\n isBridgeChange(tr: Transaction): boolean\n}\n\n/** Handle returned by {@link createBoundViewBridge}. View is bound; no need to pass it each call. */\nexport type BoundViewBridgeHandle = {\n /** Parse `text` and replace the ProseMirror document. */\n applyText(text: string, options?: ApplyTextOptions): ApplyTextResult\n /** Serialize the current ProseMirror document to text. */\n extractText(): string\n /** Returns `true` if the transaction was dispatched by {@link applyText}. */\n isBridgeChange(tr: Transaction): boolean\n /** Replace the bound EditorView. */\n setView(view: EditorView): void\n}\n\n/**\n * Create a document-sync bridge between ProseMirror and a text editor.\n *\n * Returns a {@link ViewBridgeHandle} with methods to push/pull text and\n * detect bridge-originated transactions.\n */\nexport function createViewBridge(config: ViewBridgeConfig): ViewBridgeHandle {\n const { schema, serialize, parse } = config\n const normalize = config.normalize ?? defaultNormalize\n const onError = config.onError ?? defaultOnError\n const incrementalParse = config.incrementalParse ?? null\n const cacheSize = config.parseCacheSize ?? DEFAULT_PARSE_CACHE_SIZE\n\n // --- Serialize cache: keyed by immutable Node reference ---\n const serializeCache = new WeakMap<Node, string>()\n\n function cachedSerialize(doc: Node): string {\n let text = serializeCache.get(doc)\n if (text === undefined) {\n text = normalize(serialize(doc))\n serializeCache.set(doc, text)\n }\n return text\n }\n\n // --- Parse LRU cache ---\n const parseLru = cacheSize > 0 ? new ParseLru(cacheSize) : null\n\n function cachedParse(text: string): Node {\n if (parseLru) {\n const cached = parseLru.get(text)\n if (cached) return cached\n }\n const doc = parse(text, schema)\n if (parseLru) parseLru.set(text, doc)\n return doc\n }\n\n // --- Last-applied guard ---\n let lastDoc: Node | null = null\n let lastRaw: string | null = null\n let lastIncoming: string | null = null\n\n function markUnchanged(doc: Node, raw: string, incoming: string): ApplyTextResult {\n lastDoc = doc\n lastRaw = raw\n lastIncoming = incoming\n return { ok: false, reason: 'unchanged' }\n }\n\n return {\n applyText(view: EditorView, text: string, options?: ApplyTextOptions): ApplyTextResult {\n const prevDoc = view.state.doc\n\n // Fast path: same doc + same raw text reference as last call → skip normalize\n if (prevDoc === lastDoc && text === lastRaw) {\n return { ok: false, reason: 'unchanged' }\n }\n\n const incoming = options?.normalized ? text : normalize(text)\n\n // Fast path: same doc + same normalized text as last call\n if (prevDoc === lastDoc && incoming === lastIncoming) {\n lastRaw = text\n return { ok: false, reason: 'unchanged' }\n }\n\n // Serialize cache: avoid full tree walk when doc reference is unchanged\n const current = cachedSerialize(prevDoc)\n\n if (incoming === current) {\n return markUnchanged(prevDoc, text, incoming)\n }\n\n // --- Parse (with incremental and LRU cache) ---\n let nextDoc: Node\n let rangeHint: { from: number; to: number; toB: number } | null = null\n const diff = options?.diff ?? diffText(current, incoming)\n try {\n if (incrementalParse) {\n const result: IncrementalParseResult | null =\n incrementalParse({ prevDoc, prevText: current, text: incoming, diff, schema })\n if (result == null) {\n nextDoc = cachedParse(incoming)\n } else if ('doc' in result && 'from' in result) {\n nextDoc = result.doc\n rangeHint = { from: result.from, to: result.to, toB: result.toB }\n } else {\n nextDoc = result as Node\n }\n } else {\n nextDoc = cachedParse(incoming)\n }\n } catch (error) {\n onError({ code: 'parse-error', message: 'failed to parse text into ProseMirror document', cause: error })\n return { ok: false, reason: 'parse-error' }\n }\n\n // Determine the changed document range.\n // If incrementalParse provided positions, skip the O(n) tree diff.\n let from: number, to: number, toB: number\n if (rangeHint) {\n from = rangeHint.from\n to = rangeHint.to\n toB = rangeHint.toB\n } else {\n const start = prevDoc.content.findDiffStart(nextDoc.content)\n if (start == null) {\n return markUnchanged(prevDoc, text, incoming)\n }\n const end = prevDoc.content.findDiffEnd(nextDoc.content)\n if (!end) {\n return markUnchanged(prevDoc, text, incoming)\n }\n from = Math.min(start, end.a)\n to = Math.max(start, end.a)\n toB = Math.max(start, end.b)\n }\n\n const tr = view.state.tr\n tr.replace(from, to, nextDoc.slice(from, toB))\n tr.setMeta(BRIDGE_META, true)\n if (options?.addToHistory === false) {\n tr.setMeta('addToHistory', false)\n }\n view.dispatch(tr)\n\n // Update caches after successful dispatch\n const newDoc = view.state.doc\n serializeCache.set(newDoc, incoming)\n lastDoc = newDoc\n lastRaw = text\n lastIncoming = incoming\n\n return { ok: true }\n },\n\n extractText(view: EditorView): string {\n const text = serialize(view.state.doc)\n serializeCache.set(view.state.doc, normalize(text))\n return text\n },\n\n isBridgeChange(tr: Transaction): boolean {\n return tr.getMeta(BRIDGE_META) === true\n },\n }\n}\n\n/**\n * Create a view-bound document-sync bridge. Wraps {@link createViewBridge}\n * so that the `EditorView` does not need to be passed to each method call.\n *\n * @param view - The initial EditorView to bind.\n * @param config - Configuration for the underlying bridge.\n */\nexport function createBoundViewBridge(view: EditorView, config: ViewBridgeConfig): BoundViewBridgeHandle {\n const inner = createViewBridge(config)\n let currentView = view\n\n return {\n applyText(text: string, options?: ApplyTextOptions): ApplyTextResult {\n return inner.applyText(currentView, text, options)\n },\n extractText(): string {\n return inner.extractText(currentView)\n },\n isBridgeChange(tr: Transaction): boolean {\n return inner.isBridgeChange(tr)\n },\n setView(v: EditorView): void {\n currentView = v\n },\n }\n}\n","import type { Node } from 'prosemirror-model'\nimport type { CursorMapWriter, Matcher, Serialize, SerializeWithMap } from './types.js'\n\n/** A mapping between a ProseMirror position range and a serialized-text offset range. */\nexport type TextSegment = {\n pmStart: number // PM position (inclusive)\n pmEnd: number // PM position (exclusive)\n textStart: number // serialized text offset (inclusive)\n textEnd: number // serialized text offset (exclusive)\n}\n\n/**\n * Sorted list of {@link TextSegment}s produced by {@link buildCursorMap}.\n * Use {@link cursorMapLookup} and {@link reverseCursorMapLookup} for O(log n) queries.\n */\nexport type CursorMap = {\n segments: TextSegment[]\n textLength: number\n /** Number of text nodes that could not be located in the serialized output. */\n skippedNodes: number\n}\n\n/**\n * Create a {@link CursorMapWriter} that tracks offsets and builds segments.\n *\n * Call `getText()` to retrieve the full serialized text.\n * Call `finish(doc)` to produce the final {@link CursorMap}.\n */\nexport function createCursorMapWriter(): CursorMapWriter & {\n getText(): string\n finish(doc: Node): CursorMap\n getMappedCount(): number\n} {\n let offset = 0\n const parts: string[] = []\n const segments: TextSegment[] = []\n let mappedCount = 0\n\n const writer: CursorMapWriter & { getText(): string; finish(doc: Node): CursorMap; getMappedCount(): number } = {\n write(text: string): void {\n parts.push(text)\n offset += text.length\n },\n\n writeMapped(pmStart: number, pmEnd: number, text: string): void {\n segments.push({\n pmStart,\n pmEnd,\n textStart: offset,\n textEnd: offset + text.length,\n })\n parts.push(text)\n offset += text.length\n mappedCount++\n },\n\n getText(): string {\n return parts.join('')\n },\n\n getMappedCount(): number {\n return mappedCount\n },\n\n finish(doc: Node): CursorMap {\n const textNodes: { start: number; end: number }[] = []\n function collectTextNodes(node: Node, contentStart: number): void {\n node.forEach((child, childOffset) => {\n const childPos = contentStart + childOffset\n if (child.isText && child.text) {\n textNodes.push({ start: childPos, end: childPos + child.text.length })\n } else if (!child.isLeaf) {\n collectTextNodes(child, childPos + 1)\n }\n })\n }\n collectTextNodes(doc, 0)\n\n // Count PM text nodes with at least one overlapping mapped segment.\n let mappedNodes = 0\n let segIdx = 0\n for (const n of textNodes) {\n while (segIdx < segments.length && segments[segIdx].pmEnd <= n.start) segIdx++\n let k = segIdx\n while (k < segments.length && segments[k].pmStart < n.end) {\n const s = segments[k]\n if (s.pmEnd > n.start && s.pmStart < n.end) {\n mappedNodes++\n break\n }\n k++\n }\n }\n\n return {\n segments,\n textLength: offset,\n skippedNodes: Math.max(0, textNodes.length - mappedNodes),\n }\n },\n }\n\n return writer\n}\n\n/**\n * Build a cursor map that aligns ProseMirror positions with serialized-text offsets.\n *\n * Accepts either a plain {@link Serialize} `(doc) => string` or a\n * {@link SerializeWithMap} `(doc, writer) => void`. Detection is automatic:\n * if the serializer uses the writer, the exact-by-construction path is used;\n * if it returns a string, an internal `indexOf`-based forward match is applied.\n *\n * The plain `Serialize` path uses exact `indexOf` matching (format-agnostic).\n * For better mapping quality with serializers that transform text (escaping,\n * entity encoding, etc.), use {@link wrapSerialize} with a format-specific\n * {@link Matcher}, or implement {@link SerializeWithMap} directly.\n *\n * @param doc - The ProseMirror document to map.\n * @param serialize - A plain serializer or a writer-based serializer.\n */\nexport function buildCursorMap(\n doc: Node,\n serialize: Serialize | SerializeWithMap,\n): CursorMap {\n const writer = createCursorMapWriter()\n const result = (serialize as (...args: unknown[]) => unknown)(doc, writer)\n\n // Plain Serialize: writer was not used, return value is the serialized string.\n if (typeof result === 'string' && writer.getMappedCount() === 0) {\n return forwardScanBuildMap(doc, result)\n }\n\n // SerializeWithMap: writer was used — exact-by-construction path.\n const map = writer.finish(doc)\n\n // Monotonicity validation for writer-produced segments.\n for (let i = 1; i < map.segments.length; i++) {\n const prev = map.segments[i - 1]\n const curr = map.segments[i]\n if (curr.pmStart < prev.pmEnd || curr.textStart < prev.textEnd) {\n console.warn(\n `[pm-cm] buildCursorMap: non-monotonic segment at index ${i} ` +\n `(pmStart ${curr.pmStart} < prev pmEnd ${prev.pmEnd} or ` +\n `textStart ${curr.textStart} < prev textEnd ${prev.textEnd}). ` +\n 'Ensure writeMapped calls are in ascending PM document order.',\n )\n }\n }\n\n return map\n}\n\n/**\n * Build a cursor map using plain `indexOf` forward matching.\n * Format-agnostic: no character or escape assumptions.\n */\nfunction forwardScanBuildMap(doc: Node, text: string): CursorMap {\n const segments: TextSegment[] = []\n let searchFrom = 0\n let totalTextNodes = 0\n let skippedNodes = 0\n\n function visit(node: Node, contentStart: number): void {\n node.forEach((child, childOffset) => {\n const childPos = contentStart + childOffset\n if (child.isText && child.text) {\n totalTextNodes++\n const idx = text.indexOf(child.text, searchFrom)\n if (idx >= 0) {\n segments.push({\n pmStart: childPos,\n pmEnd: childPos + child.text.length,\n textStart: idx,\n textEnd: idx + child.text.length,\n })\n searchFrom = idx + child.text.length\n } else {\n skippedNodes++\n }\n } else if (!child.isLeaf) {\n visit(child, childPos + 1)\n }\n })\n }\n\n visit(doc, 0)\n return { segments, textLength: text.length, skippedNodes }\n}\n\n/**\n * Look up a ProseMirror position in a cursor map and return the corresponding text offset.\n * Returns `null` when the map has no segments.\n */\nexport function cursorMapLookup(map: CursorMap, pmPos: number): number | null {\n const { segments } = map\n if (segments.length === 0) return null\n\n // Binary search for the segment containing pmPos\n let lo = 0\n let hi = segments.length - 1\n\n while (lo <= hi) {\n const mid = (lo + hi) >>> 1\n const seg = segments[mid]\n\n if (pmPos < seg.pmStart) {\n hi = mid - 1\n } else if (pmPos >= seg.pmEnd) {\n lo = mid + 1\n } else {\n // Inside segment: exact mapping\n return seg.textStart + (pmPos - seg.pmStart)\n }\n }\n\n // pmPos is between segments — snap to nearest boundary\n // After binary search: hi < lo, pmPos falls between segments[hi] and segments[lo]\n const before = hi >= 0 ? segments[hi] : null\n const after = lo < segments.length ? segments[lo] : null\n\n if (!before) return after ? after.textStart : 0\n if (!after) return before.textEnd\n\n const distBefore = pmPos - before.pmEnd\n const distAfter = after.pmStart - pmPos\n return distBefore <= distAfter ? before.textEnd : after.textStart\n}\n\n/**\n * Look up a text offset (e.g. CodeMirror position) in a cursor map and return the corresponding ProseMirror position.\n * Returns `null` when the map has no segments.\n */\nexport function reverseCursorMapLookup(map: CursorMap, cmOffset: number): number | null {\n const { segments } = map\n if (segments.length === 0) return null\n\n // Binary search for the segment containing cmOffset\n let lo = 0\n let hi = segments.length - 1\n\n while (lo <= hi) {\n const mid = (lo + hi) >>> 1\n const seg = segments[mid]\n\n if (cmOffset < seg.textStart) {\n hi = mid - 1\n } else if (cmOffset >= seg.textEnd) {\n lo = mid + 1\n } else {\n // Inside segment: exact mapping\n return seg.pmStart + (cmOffset - seg.textStart)\n }\n }\n\n // cmOffset is between segments — snap to nearest boundary\n const before = hi >= 0 ? segments[hi] : null\n const after = lo < segments.length ? segments[lo] : null\n\n if (!before) return after ? after.pmStart : 0\n if (!after) return before.pmEnd\n\n const distBefore = cmOffset - before.textEnd\n const distAfter = after.textStart - cmOffset\n return distBefore <= distAfter ? before.pmEnd : after.pmStart\n}\n\n/**\n * Wrap a plain {@link Serialize} function as a {@link SerializeWithMap}.\n *\n * When called without a `matcher`, the wrapper uses `indexOf` internally\n * (identical to the default `buildCursorMap` path — useful only for type\n * compatibility).\n *\n * When called with a format-specific {@link Matcher}, the wrapper uses\n * `indexOf` first for each text node, falling back to the matcher when\n * `indexOf` fails. This enables multi-run mapping for serializers that\n * transform text (escaping, entity encoding, etc.).\n *\n * @param serialize - A plain `(doc: Node) => string` serializer.\n * @param matcher - Optional format-specific matcher for improved mapping.\n * @returns A {@link SerializeWithMap} that can be passed to {@link buildCursorMap}.\n */\nexport function wrapSerialize(serialize: Serialize, matcher?: Matcher): SerializeWithMap {\n return (doc: Node, writer: CursorMapWriter): void => {\n const text = serialize(doc)\n const segments = collectMatchedSegments(doc, text, matcher)\n\n // Emit in text order: unmapped gaps then mapped text\n let pos = 0\n for (const seg of segments) {\n if (seg.textStart > pos) writer.write(text.slice(pos, seg.textStart))\n writer.writeMapped(seg.pmStart, seg.pmEnd, text.slice(seg.textStart, seg.textEnd))\n pos = seg.textEnd\n }\n if (pos < text.length) writer.write(text.slice(pos))\n }\n}\n\n/**\n * Collect matched segments for all PM text nodes using indexOf + optional matcher fallback.\n */\nfunction collectMatchedSegments(\n doc: Node,\n text: string,\n matcher: Matcher | undefined,\n): TextSegment[] {\n const segments: TextSegment[] = []\n let searchFrom = 0\n\n function visit(node: Node, contentStart: number): void {\n node.forEach((child, childOffset) => {\n const childPos = contentStart + childOffset\n if (child.isText && child.text) {\n const content = child.text\n\n // 1. Try exact indexOf first (strongest signal, no false positives)\n const exactIdx = text.indexOf(content, searchFrom)\n if (exactIdx >= 0) {\n segments.push({\n pmStart: childPos,\n pmEnd: childPos + content.length,\n textStart: exactIdx,\n textEnd: exactIdx + content.length,\n })\n searchFrom = exactIdx + content.length\n return\n }\n\n // 2. If matcher provided, try format-specific matching\n if (matcher) {\n const result = matcher(text, content, searchFrom)\n if (result) {\n for (const run of result.runs) {\n segments.push({\n pmStart: childPos + run.contentStart,\n pmEnd: childPos + run.contentEnd,\n textStart: run.textStart,\n textEnd: run.textEnd,\n })\n }\n searchFrom = result.nextSearchFrom\n return\n }\n }\n\n // 3. Both failed — node skipped (searchFrom not advanced)\n } else if (!child.isLeaf) {\n visit(child, childPos + 1)\n }\n })\n }\n\n visit(doc, 0)\n return segments\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pm-cm/core",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",