@pm-cm/core 0.0.3 → 0.0.5

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.cjs CHANGED
@@ -116,7 +116,13 @@ function createViewBridge(config) {
116
116
  lastRaw = text;
117
117
  return { ok: false, reason: "unchanged" };
118
118
  }
119
- const current = cachedSerialize(prevDoc);
119
+ let current;
120
+ try {
121
+ current = cachedSerialize(prevDoc);
122
+ } catch (error) {
123
+ onError({ code: "serialize-error", message: "failed to serialize current ProseMirror document", cause: error });
124
+ return { ok: false, reason: "serialize-error" };
125
+ }
120
126
  if (incoming === current) {
121
127
  return markUnchanged(prevDoc, text, incoming);
122
128
  }
@@ -155,9 +161,15 @@ function createViewBridge(config) {
155
161
  if (!end) {
156
162
  return markUnchanged(prevDoc, text, incoming);
157
163
  }
158
- from = Math.min(start, end.a);
159
- to = Math.max(start, end.a);
160
- toB = Math.max(start, end.b);
164
+ let { a: endA, b: endB } = end;
165
+ const overlap = start - Math.min(endA, endB);
166
+ if (overlap > 0) {
167
+ endA += overlap;
168
+ endB += overlap;
169
+ }
170
+ from = start;
171
+ to = endA;
172
+ toB = endB;
161
173
  }
162
174
  const tr = view.state.tr;
163
175
  tr.replace(from, to, nextDoc.slice(from, toB));
@@ -167,14 +179,19 @@ function createViewBridge(config) {
167
179
  }
168
180
  view.dispatch(tr);
169
181
  const newDoc = view.state.doc;
170
- serializeCache.set(newDoc, incoming);
171
182
  lastDoc = newDoc;
172
183
  lastRaw = text;
173
184
  lastIncoming = incoming;
174
185
  return { ok: true };
175
186
  },
176
187
  extractText(view) {
177
- const text = serialize(view.state.doc);
188
+ let text;
189
+ try {
190
+ text = serialize(view.state.doc);
191
+ } catch (error) {
192
+ onError({ code: "serialize-error", message: "failed to serialize ProseMirror document in extractText", cause: error });
193
+ throw error;
194
+ }
178
195
  serializeCache.set(view.state.doc, normalize(text));
179
196
  return text;
180
197
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/bridge.ts","../src/cursor-map.ts"],"sourcesContent":["export { createViewBridge, createBoundViewBridge, diffText } from './bridge.js'\nexport type { ViewBridgeConfig, ViewBridgeHandle, BoundViewBridgeHandle, ApplyTextOptions, ApplyTextResult } from './bridge.js'\nexport type { Serialize, Parse, Normalize, OnError, ErrorCode, ErrorEvent, IncrementalParse, IncrementalParseResult, TextDiff, CursorMapWriter, SerializeWithMap, Matcher, MatchResult, MatchRun } from './types.js'\nexport { buildCursorMap, createCursorMapWriter, cursorMapLookup, reverseCursorMapLookup, wrapSerialize } from './cursor-map.js'\nexport type { TextSegment, CursorMap } from './cursor-map.js'\n","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":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/bridge.ts","../src/cursor-map.ts"],"sourcesContent":["export { createViewBridge, createBoundViewBridge, diffText } from './bridge.js'\nexport type { ViewBridgeConfig, ViewBridgeHandle, BoundViewBridgeHandle, ApplyTextOptions, ApplyTextResult } from './bridge.js'\nexport type { Serialize, Parse, Normalize, OnError, ErrorCode, ErrorEvent, IncrementalParse, IncrementalParseResult, TextDiff, CursorMapWriter, SerializeWithMap, Matcher, MatchResult, MatchRun } from './types.js'\nexport { buildCursorMap, createCursorMapWriter, cursorMapLookup, reverseCursorMapLookup, wrapSerialize } from './cursor-map.js'\nexport type { TextSegment, CursorMap } from './cursor-map.js'\n","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' | 'serialize-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 let current: string\n try {\n current = cachedSerialize(prevDoc)\n } catch (error) {\n onError({ code: 'serialize-error', message: 'failed to serialize current ProseMirror document', cause: error })\n return { ok: false, reason: 'serialize-error' }\n }\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 // When findDiffStart enters a node deeper than findDiffEnd's boundary,\n // the positions overlap. Adjust end positions forward to avoid producing\n // incorrect slices that lose or merge paragraphs.\n let { a: endA, b: endB } = end\n const overlap = start - Math.min(endA, endB)\n if (overlap > 0) {\n endA += overlap\n endB += overlap\n }\n from = start\n to = endA\n toB = endB\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 last-applied guard after successful dispatch.\n // Do NOT pre-populate serializeCache: appendTransaction plugins may\n // have further modified the doc, making `incoming` inaccurate.\n const newDoc = view.state.doc\n lastDoc = newDoc\n lastRaw = text\n lastIncoming = incoming\n\n return { ok: true }\n },\n\n extractText(view: EditorView): string {\n let text: string\n try {\n text = serialize(view.state.doc)\n } catch (error) {\n onError({ code: 'serialize-error', message: 'failed to serialize ProseMirror document in extractText', cause: error })\n throw error\n }\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/dist/index.d.cts CHANGED
@@ -145,7 +145,7 @@ type ApplyTextResult = {
145
145
  ok: true;
146
146
  } | {
147
147
  ok: false;
148
- reason: 'unchanged' | 'parse-error';
148
+ reason: 'unchanged' | 'parse-error' | 'serialize-error';
149
149
  };
150
150
  /** Handle returned by {@link createViewBridge}. */
151
151
  type ViewBridgeHandle = {
package/dist/index.d.ts CHANGED
@@ -145,7 +145,7 @@ type ApplyTextResult = {
145
145
  ok: true;
146
146
  } | {
147
147
  ok: false;
148
- reason: 'unchanged' | 'parse-error';
148
+ reason: 'unchanged' | 'parse-error' | 'serialize-error';
149
149
  };
150
150
  /** Handle returned by {@link createViewBridge}. */
151
151
  type ViewBridgeHandle = {
package/dist/index.js CHANGED
@@ -83,7 +83,13 @@ function createViewBridge(config) {
83
83
  lastRaw = text;
84
84
  return { ok: false, reason: "unchanged" };
85
85
  }
86
- const current = cachedSerialize(prevDoc);
86
+ let current;
87
+ try {
88
+ current = cachedSerialize(prevDoc);
89
+ } catch (error) {
90
+ onError({ code: "serialize-error", message: "failed to serialize current ProseMirror document", cause: error });
91
+ return { ok: false, reason: "serialize-error" };
92
+ }
87
93
  if (incoming === current) {
88
94
  return markUnchanged(prevDoc, text, incoming);
89
95
  }
@@ -122,9 +128,15 @@ function createViewBridge(config) {
122
128
  if (!end) {
123
129
  return markUnchanged(prevDoc, text, incoming);
124
130
  }
125
- from = Math.min(start, end.a);
126
- to = Math.max(start, end.a);
127
- toB = Math.max(start, end.b);
131
+ let { a: endA, b: endB } = end;
132
+ const overlap = start - Math.min(endA, endB);
133
+ if (overlap > 0) {
134
+ endA += overlap;
135
+ endB += overlap;
136
+ }
137
+ from = start;
138
+ to = endA;
139
+ toB = endB;
128
140
  }
129
141
  const tr = view.state.tr;
130
142
  tr.replace(from, to, nextDoc.slice(from, toB));
@@ -134,14 +146,19 @@ function createViewBridge(config) {
134
146
  }
135
147
  view.dispatch(tr);
136
148
  const newDoc = view.state.doc;
137
- serializeCache.set(newDoc, incoming);
138
149
  lastDoc = newDoc;
139
150
  lastRaw = text;
140
151
  lastIncoming = incoming;
141
152
  return { ok: true };
142
153
  },
143
154
  extractText(view) {
144
- const text = serialize(view.state.doc);
155
+ let text;
156
+ try {
157
+ text = serialize(view.state.doc);
158
+ } catch (error) {
159
+ onError({ code: "serialize-error", message: "failed to serialize ProseMirror document in extractText", cause: error });
160
+ throw error;
161
+ }
145
162
  serializeCache.set(view.state.doc, normalize(text));
146
163
  return text;
147
164
  },
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, 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":[]}
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' | 'serialize-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 let current: string\n try {\n current = cachedSerialize(prevDoc)\n } catch (error) {\n onError({ code: 'serialize-error', message: 'failed to serialize current ProseMirror document', cause: error })\n return { ok: false, reason: 'serialize-error' }\n }\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 // When findDiffStart enters a node deeper than findDiffEnd's boundary,\n // the positions overlap. Adjust end positions forward to avoid producing\n // incorrect slices that lose or merge paragraphs.\n let { a: endA, b: endB } = end\n const overlap = start - Math.min(endA, endB)\n if (overlap > 0) {\n endA += overlap\n endB += overlap\n }\n from = start\n to = endA\n toB = endB\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 last-applied guard after successful dispatch.\n // Do NOT pre-populate serializeCache: appendTransaction plugins may\n // have further modified the doc, making `incoming` inaccurate.\n const newDoc = view.state.doc\n lastDoc = newDoc\n lastRaw = text\n lastIncoming = incoming\n\n return { ok: true }\n },\n\n extractText(view: EditorView): string {\n let text: string\n try {\n text = serialize(view.state.doc)\n } catch (error) {\n onError({ code: 'serialize-error', message: 'failed to serialize ProseMirror document in extractText', cause: error })\n throw error\n }\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.3",
3
+ "version": "0.0.5",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",