{"version":3,"file":"chunk-diff.mjs","names":["__dxlog_file","EditorSelection"],"sources":["../../src/util/facet.ts","../../src/util/cursor.ts","../../src/util/debug.ts","../../src/extensions/state/selection.ts","../../src/extensions/review/comments.ts","../../src/extensions/review/diff.ts"],"sourcesContent":["//\n// Copyright 2024 DXOS.org\n//\n\nimport { Facet } from '@codemirror/state';\n\nexport const singleValueFacet = <I, O = I>(defaultValue?: O) =>\n  Facet.define<I, O>({\n    // Called immediately.\n    combine: (providers) => {\n      return (providers[0] ?? defaultValue) as O;\n    },\n  });\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport { type EditorState } from '@codemirror/state';\n\nimport { type Range } from '../types/index.ts';\nimport { singleValueFacet } from './facet.ts';\n\n/**\n * Determines if two ranges overlap.\n * A range is considered to overlap if there is any intersection\n * between the two ranges, inclusive of their boundaries.\n */\nexport const overlap = (a: Range, b: Range): boolean => a.from <= b.to && a.to >= b.from;\n\n/**\n * Converts indexes into the text document into stable peer-independent cursors.\n *\n * See:\n *  - https://automerge.org/automerge/api-docs/js/functions/next.getCursor.html\n *  - https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions\n *\n * @param {assoc} number Negative values will associate the cursor with the previous character\n *  while positive - with the next one.\n */\nexport interface CursorConverter {\n  toCursor(position: number, assoc?: -1 | 1): string;\n  fromCursor(cursor: string): number;\n}\n\nconst defaultCursorConverter: CursorConverter = {\n  toCursor: (position) => position.toString(),\n  // Only a canonical decimal decodes; `parseInt` would read `2bad` as 2 and pass a wrong range on.\n  fromCursor: (cursor) => (/^\\d+$/.test(cursor) ? Number(cursor) : Number.NaN),\n};\n\nexport class Cursor {\n  static readonly converter = singleValueFacet(defaultCursorConverter);\n\n  static readonly getCursorFromRange = (state: EditorState, range: Range) => {\n    const cursorConverter = state.facet(Cursor.converter);\n    const from = cursorConverter.toCursor(range.from);\n    const to = cursorConverter.toCursor(range.to, -1);\n    return [from, to].join(':');\n  };\n\n  /**\n   * Decodes a `from:to` cursor pair into a range.\n   *\n   * Returns `undefined`, rather than throwing, for a cursor the converter rejects or that decodes to\n   * anything but integer positions: anchors arrive from the database, and one malformed anchor would\n   * otherwise fail the state update for every comment.\n   */\n  static readonly getRangeFromCursor = (state: EditorState, cursor: string): Range | undefined => {\n    const cursorConverter = state.facet(Cursor.converter);\n\n    const parts = cursor.split(':');\n    try {\n      const from = cursorConverter.fromCursor(parts[0]);\n      const to = cursorConverter.fromCursor(parts[1]);\n      return Number.isInteger(from) && Number.isInteger(to) ? { from, to } : undefined;\n    } catch {\n      return undefined;\n    }\n  };\n}\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport { type Transaction } from '@codemirror/state';\nimport { type EditorView } from '@codemirror/view';\n\nimport { log } from '@dxos/log';\n\nexport const join = (...lines: string[]) => lines.join('\\n');\n\n/**\n * CodeMirror callbacks swallow errors so wrap handlers.\n */\nexport const wrapWithCatch = (fn: (...args: any[]) => any, label?: string) => {\n  return (...args: any[]) => {\n    try {\n      return fn(...args);\n    } catch (err) {\n      log.catch(err, { label });\n    }\n  };\n};\n\n/**\n * Log all changes before dispatching them to the view.\n * https://codemirror.net/docs/ref/#view.EditorView.dispatch\n */\nexport const debugDispatcher = (trs: readonly Transaction[], view: EditorView) => {\n  logChanges(trs);\n  view.update(trs);\n};\n\n/**\n * Util to log transactions in update listener.\n */\nexport const logChanges = (trs: readonly Transaction[]) => {\n  const changes = trs\n    .flatMap((tr) => {\n      if (tr.changes.empty) {\n        return undefined;\n      }\n\n      const changes: any[] = [];\n      tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) =>\n        changes.push(JSON.stringify({ fromA, toA, fromB, toB, inserted: inserted.toString() })),\n      );\n\n      return changes;\n    })\n    .filter(Boolean);\n\n  if (changes.length) {\n    log('changes', { changes });\n  }\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport { type Extension, Transaction, type TransactionSpec } from '@codemirror/state';\nimport { EditorView, keymap } from '@codemirror/view';\nimport * as Schema from 'effect/Schema';\nimport * as Struct from 'effect/Struct';\n\nimport { isTruthy } from '@dxos/util';\n\nimport { singleValueFacet } from '../../util/index.ts';\n\n/**\n * Currently edited document id as FQ string.\n */\nexport const documentId = singleValueFacet<string>();\n\nexport type EditorSelection = {\n  anchor: number;\n  head?: number;\n};\n\nexport type EditorSelectionState = {\n  scrollTo?: number;\n  /** Pixels the anchored line is scrolled past, so a restore lands exactly where recording happened. */\n  scrollOffset?: number;\n  selection?: EditorSelection;\n};\n\nexport const EditorSelection = Schema.Struct({\n  anchor: Schema.Number,\n  head: Schema.optional(Schema.Number),\n}).mapFields(Struct.map(Schema.mutableKey));\n\nexport const EditorSelectionStateSchema = Schema.Struct({\n  scrollTo: Schema.optional(Schema.Number),\n  scrollOffset: Schema.optional(Schema.Number),\n  selection: Schema.optional(EditorSelection),\n}).mapFields(Struct.map(Schema.mutableKey));\n\nexport type EditorStateStore = {\n  setState: (id: string, state: EditorSelectionState) => void;\n  getState: (id: string) => EditorSelectionState | undefined;\n};\n\nconst stateRestoreAnnotation = 'org.dxos.cm.state-restore';\n\n/** Window after a restore during which scroll events are not recorded. */\nconst RESTORE_SUPPRESS_MS = 500;\n\n/** Delay before a recorded state is written to the store. */\nconst RECORD_DELAY_MS = 1_000;\n\n/** Dedupes the recorder's measure requests: CodeMirror keeps one request per key until its measure phase runs. */\nconst recordKey = {};\n\n/**\n * Builds the transaction that puts a recorded state back: applies the selection, anchors\n * `scrollTo` at the top of the viewport (falling back to revealing the selection when no position\n * was recorded), and tags itself so the recorder ignores the scroll it causes.\n */\nexport const createEditorStateTransaction = ({ scrollTo, selection }: EditorSelectionState): TransactionSpec => {\n  return {\n    selection,\n    scrollIntoView: scrollTo == null,\n    // `scrollTo` is the position that was at the top of the viewport, so restore it there\n    // exactly; a y-margin would offset every restore by that margin.\n    effects: scrollTo != null ? EditorView.scrollIntoView(scrollTo, { y: 'start', yMargin: 0 }) : undefined,\n    annotations: Transaction.userEvent.of(stateRestoreAnnotation),\n  };\n};\n\n/**\n * Restores a recorded position: the transaction anchors the line at the top of the viewport, then\n * the sub-line offset is re-applied once CodeMirror has measured, so repeated round-trips do not\n * creep upwards by the fraction of the line that was originally scrolled past.\n */\nexport const restoreEditorState = (view: EditorView, state: EditorSelectionState) => {\n  // An editor that does not scroll itself — auto-height, embedded in a form or a card — has no\n  // position to restore, and CodeMirror would satisfy the request by scrolling an ANCESTOR instead:\n  // the host's own scroller jumps to put the editor's first line at its top, on every mount. Only an\n  // editor with its own overflow can honour the anchor; the caret is restored either way, without a\n  // scroll to reveal it.\n  const { scrollHeight, clientHeight } = view.scrollDOM;\n  if (scrollHeight <= clientHeight + 1) {\n    if (state.selection) {\n      view.dispatch({\n        selection: state.selection,\n        scrollIntoView: false,\n        annotations: Transaction.userEvent.of(stateRestoreAnnotation),\n      });\n    }\n    return;\n  }\n\n  view.dispatch(createEditorStateTransaction(state));\n  const { scrollTo, scrollOffset } = state;\n  if (scrollTo != null && scrollOffset) {\n    requestAnimationFrame(() => {\n      view.scrollDOM.scrollTop += scrollOffset;\n    });\n  }\n};\n\n/**\n * Track scrolling and selection state to be restored when switching to document.\n */\nexport const selectionState = ({ getState, setState }: Partial<EditorStateStore> = {}): Extension => {\n  // Debounced by hand so a restore can cancel a write queued before it: the binding's content fill at\n  // mount records the top-of-document position, which would otherwise overwrite the stored state.\n  let timer: ReturnType<typeof setTimeout> | undefined;\n  const cancelPending = () => {\n    clearTimeout(timer);\n    timer = undefined;\n  };\n\n  // A restore dispatches its own scroll, which would immediately record a position measured\n  // before the document has finished laying out; ignore scroll events for a beat afterwards.\n  let suppressUntil = 0;\n\n  // Read in CodeMirror's own measure phase, once per frame however many scroll events and updates arrive: reading\n  // the coordinates straight from each event forced a synchronous layout per event while scrolling.\n  const measure = {\n    key: recordKey,\n    read: (view: EditorView): EditorSelectionState | undefined => {\n      // `posAtCoords` takes client coordinates, so measure from the scroller's own rect rather\n      // than from `scrollTop` (which only coincides when the scroller sits at the top of the window).\n      const { top, left } = view.scrollDOM.getBoundingClientRect();\n      const pos = view.posAtCoords({ x: left + 1, y: top + 1 });\n      if (pos === null) {\n        return undefined;\n      }\n      const { anchor, head } = view.state.selection.main;\n      // Measure against the position's own visual row (`coordsAtPos`), not its line block: with\n      // line wrapping the top of the viewport is often a continuation row, and a block-relative\n      // offset would then overshoot the restore by every wrapped row above it.\n      const coords = view.coordsAtPos(pos);\n      const scrollOffset = coords ? Math.round(top - coords.top) : 0;\n      return { scrollTo: pos, scrollOffset, selection: { anchor, head } };\n    },\n    write: (state: EditorSelectionState | undefined, view: EditorView) => {\n      // CodeMirror reads measure requests before applying a pending scroll target, so a request queued\n      // before a restore measures the pre-restore viewport.\n      if (performance.now() < suppressUntil) {\n        return;\n      }\n      const id = view.state.facet(documentId);\n      if (state && id && setState) {\n        cancelPending();\n        timer = setTimeout(() => setState(id, state), RECORD_DELAY_MS);\n      }\n    },\n  };\n\n  const record = (view: EditorView) => {\n    const id = view.state.facet(documentId);\n    if (!id || !setState || performance.now() < suppressUntil) {\n      return;\n    }\n    view.requestMeasure(measure);\n  };\n\n  return [\n    EditorView.domEventHandlers({\n      scroll: (_event, view) => {\n        record(view);\n      },\n    }),\n    EditorView.updateListener.of(({ view, transactions }) => {\n      if (transactions.some((tr) => tr.isUserEvent(stateRestoreAnnotation))) {\n        suppressUntil = performance.now() + RESTORE_SUPPRESS_MS;\n        cancelPending();\n        return;\n      }\n\n      record(view);\n    }),\n    getState &&\n      keymap.of([\n        {\n          key: 'Ctrl-r', // TODO(burdon): Setting to jump back to selection.\n          run: (view) => {\n            const state = getState(view.state.facet(documentId));\n            // Only restore when something was actually stored; a store may return an empty state\n            // (no scroll/selection) for an unseen document, which would otherwise dispatch a no-op.\n            if (state && (state.scrollTo != null || state.selection)) {\n              restoreEditorState(view, state);\n            }\n            return true;\n          },\n        },\n      ]),\n  ].filter(isTruthy);\n};\n","//\n// Copyright 2023 DXOS.org\n//\n\nimport { invertedEffects } from '@codemirror/commands';\nimport { getChunks } from '@codemirror/merge';\nimport {\n  type ChangeDesc,\n  EditorSelection,\n  type Extension,\n  StateEffect,\n  StateField,\n  type Text,\n} from '@codemirror/state';\nimport {\n  type Command,\n  Decoration,\n  EditorView,\n  type Rect,\n  RectangleMarker,\n  ViewPlugin,\n  keymap,\n  layer,\n} from '@codemirror/view';\nimport sortBy from 'lodash.sortby';\n\nimport { type CleanupFn, debounce } from '@dxos/async';\nimport { log } from '@dxos/log';\nimport { isNonNullable } from '@dxos/util';\n\nimport { type Comment, type Range } from '../../types/index.ts';\nimport { Cursor, singleValueFacet, wrapWithCatch } from '../../util/index.ts';\nimport { documentId } from '../state/selection.ts';\n\n//\n// Options\n//\n\nexport type CommentsOptions = {\n  /**\n   * Document id.\n   */\n  id: string;\n  /**\n   * Key shortcut to create a new thread.\n   */\n  key?: string;\n  /**\n   * Get the current comments.\n   */\n  getComments?: () => Comment[];\n  /**\n   * Subscribe to external comment updates.\n   */\n  subscribe?: (sink: () => void) => CleanupFn;\n  /**\n   * The branch under review; new comments are tagged with it (undefined = main/unbranched). Scopes\n   * a comment to the branch being reviewed so its visibility tracks the active diff.\n   */\n  reviewBranch?: string;\n  /**\n   * Prohibit creating new comments (e.g. on a per-user suggestion branch). Existing comments still\n   * render; the create command becomes a no-op.\n   */\n  readonly?: boolean;\n  /**\n   * Called to create a new thread and return the thread id.\n   */\n  onCreate?: (params: { cursor: string; from: number; location?: Rect | null; branch?: string }) => void;\n  /**\n   * Selection cut/deleted.\n   */\n  onDelete?: (params: { id: string }) => void;\n  /**\n   * Called when a comment is moved.\n   */\n  onUpdate?: (params: { id: string; cursor: string }) => void;\n  /**\n   * Called to notify which thread is currently closest to the cursor.\n   */\n  onSelect?: (state: CommentsState) => void;\n  /**\n   * Called when a comment is deliberately activated (clicked). Distinct from `onSelect` (passive\n   * cursor-proximity): a click is an explicit user intent, e.g. to reveal the thread in a companion.\n   * The editor-side selection/highlight is already applied by the time this fires.\n   */\n  onActivate?: (id: string) => void;\n};\n\n/**\n * Comment threads.\n * 1). Updates the EditorModel to store relative selections for a set of comments threads.\n *     Since the selections are relative, they do not need to be updated when the document is edited.\n * 2). Implements a StateField to track absolute selections corresponding to the comments (i.e., when the document is edited).\n * 3). Creates decoration marks to apply classes to each selection.\n * 4). Tracks the current cursor position to:\n *     a). Update the decoration to show if the cursor is within a current selection.\n *     b). Calls a handler to indicate which is the closest selection (e.g., to update the thread sidebar).\n * 5). Optionally, implements a hoverTooltip to show hints when creating a selection range.\n */\nexport const comments = (options: CommentsOptions): Extension => {\n  const { key: shortcut = \"meta-'\" } = options;\n\n  const handleSelect = debounce((state: CommentsState) => options.onSelect?.(state), 200);\n\n  return [\n    optionsFacet.of(options),\n    options.id ? documentId.of(options.id) : undefined,\n    commentsState,\n    commentsDecorations,\n    commentsHighlightLayer,\n    handleCommentClick,\n    styles,\n\n    //\n    // External comment synchronization.\n    //\n    ViewPlugin.fromClass(\n      class {\n        private readonly unsubscribe: () => void;\n        constructor(view: EditorView) {\n          const { id } = options;\n          const updateComments = () => {\n            const comments = options.getComments?.() ?? [];\n            // Local `id` const so the truthiness narrowing survives into the microtask closure.\n            if (id && id === view.state.facet(documentId)) {\n              queueMicrotask(() => view.dispatch({ effects: setComments.of({ id, comments }) }));\n            }\n          };\n\n          this.unsubscribe = options.subscribe?.(updateComments) ?? (() => {});\n          // Prime the initial decoration pass: `subscribe` may not emit for an already-resolved source\n          // (e.g. an ECHO query whose results resolved before mount), so read once now that the source is\n          // subscribed. Owning this here means adapters never have to prime manually.\n          updateComments();\n        }\n\n        destroy = () => {\n          this.unsubscribe();\n        };\n      },\n    ),\n\n    //\n    // Keymap.\n    //\n    options.onCreate &&\n      keymap.of([\n        {\n          key: shortcut,\n          run: wrapWithCatch(createComment),\n        },\n      ]),\n\n    //\n    // Track deleted ranges and update ranges for decorations.\n    //\n    EditorView.updateListener.of(({ view, state, changes }) => {\n      let mod = false;\n      const { comments, ...value } = state.field(commentsState);\n      changes.iterChanges((from, to, from2, to2) => {\n        comments.forEach(({ comment, range }) => {\n          // Only comments with a cursor reach this state (setComments filters cursor-less ones).\n          if (!comment.cursor) {\n            return;\n          }\n\n          // Test if range deleted.\n          if (from2 === to2) {\n            const newRange = Cursor.getRangeFromCursor(view.state, comment.cursor);\n            if (!newRange || newRange.to - newRange.from === 0) {\n              options.onDelete?.({ id: comment.id });\n            }\n          }\n\n          // Update range.\n          if (from <= range.to) {\n            const newRange = Cursor.getRangeFromCursor(view.state, comment.cursor);\n            Object.assign(range, newRange);\n            mod = true;\n          }\n        });\n      });\n\n      if (mod) {\n        view.dispatch({ effects: setCommentState.of({ comments, ...value }) });\n      }\n    }),\n\n    //\n    // Track selection/proximity.\n    //\n    EditorView.updateListener.of(({ view, state, transactions }) => {\n      let min = Infinity;\n      const { selection: applied, comments } = state.field(commentsState);\n      const { current, closest } = applied;\n\n      const notify = (selection: SelectionState) =>\n        handleSelect({\n          selection,\n          id: state.facet(documentId),\n          comments: comments.map(({ comment, range }) => ({\n            comment,\n            range,\n            location: view.coordsAtPos(range.from),\n          })),\n        });\n\n      // An explicit `setSelection` is authoritative: selecting a thread in the comments companion sets\n      // `current` and moves the caret in one transaction, and the caret does not always land inside the\n      // tracked range — proximity would then clear the very selection that transaction just made. Report\n      // the applied selection so the app still learns which thread is current, but leave it as set.\n      if (transactions.some((tr) => tr.effects.some((effect) => effect.is(setSelection)))) {\n        notify(applied);\n        return;\n      }\n\n      const { head } = state.selection.main;\n      const selection: SelectionState = {};\n      comments.forEach(({ comment, range }) => {\n        if (head >= range.from && head <= range.to) {\n          selection.current = comment.id;\n          selection.closest = undefined;\n        }\n\n        if (!selection.current) {\n          const d = Math.min(Math.abs(head - range.from), Math.abs(head - range.to));\n          if (d < min) {\n            selection.closest = comment.id;\n            min = d;\n          }\n        }\n      });\n\n      if (selection.current !== current || selection.closest !== closest) {\n        view.dispatch({ effects: setSelection.of(selection) });\n        notify(selection);\n      }\n    }),\n\n    options.onUpdate && trackPastedComments(options.onUpdate),\n  ].filter(isNonNullable);\n};\n\n//\n// State management.\n//\n\ntype CommentState = {\n  comment: Comment;\n  range: Range;\n  location?: Rect | null;\n};\n\ntype SelectionState = {\n  current?: string;\n  closest?: string;\n};\n\nexport type CommentsState = {\n  id?: string;\n  comments: CommentState[];\n  selection: SelectionState;\n};\n\nexport const setComments = StateEffect.define<{ id: string; comments: Comment[] }>();\n\nexport const setSelection = StateEffect.define<SelectionState>();\n\nconst setCommentState = StateEffect.define<CommentsState>();\n\n/**\n * State field (reducer) that tracks comment ranges.\n * The ranges are tracked as Automerge cursors from which the absolute indexed ranges can be computed.\n * Included by {@link comments}; exported for direct state reads.\n */\nexport const commentsState = StateField.define<CommentsState>({\n  create: (state) => ({\n    id: state.facet(documentId),\n    comments: [],\n    selection: {},\n  }),\n  update: (value, tr) => {\n    for (const effect of tr.effects) {\n      // Update selection.\n      if (effect.is(setSelection)) {\n        return { ...value, selection: effect.value };\n      }\n\n      // Update range from store.\n      if (effect.is(setComments)) {\n        const { comments } = effect.value;\n        const commentStates: CommentState[] = comments\n          .map((comment) => {\n            // Skip cut/deleted comments.\n            if (!comment.cursor) {\n              return undefined;\n            }\n\n            const range = Cursor.getRangeFromCursor(tr.state, comment.cursor);\n            return range && { comment, range };\n          })\n          .filter(isNonNullable);\n\n        return { ...value, comments: commentStates };\n      }\n\n      // Update entire state.\n      if (effect.is(setCommentState)) {\n        return effect.value;\n      }\n    }\n\n    return value;\n  },\n});\n\nconst createCommentMark = (id: string, isCurrent: boolean) =>\n  Decoration.mark({\n    class: 'cm-comment',\n    attributes: {\n      'data-testid': 'cm-comment',\n      'data-comment-id': id,\n      'data-current': isCurrent ? '1' : '0',\n    },\n  });\n\n/**\n * Transparent inline marks that carry `data-comment-id` for click routing (see `handleCommentClick`).\n * The colour comes from `commentsHighlightLayer`.\n */\nconst commentsDecorations = EditorView.decorations.compute([commentsState], (state) => {\n  const {\n    selection: { current },\n    comments,\n  } = state.field(commentsState);\n\n  const decorations = sortBy(comments ?? [], (range) => range.range.from)\n    ?.flatMap((comment) => {\n      const range = comment.range;\n      if (!range) {\n        log.warn('Invalid range:', range);\n        return undefined;\n      } else if (range.from === range.to) {\n        // Skip empty ranges. This can happen when a comment is cut or deleted.\n        return undefined;\n      }\n\n      const mark = createCommentMark(comment.comment.id, comment.comment.id === current);\n      return mark.range(range.from, range.to);\n    })\n    .filter(isNonNullable);\n\n  return Decoration.set(decorations);\n});\n\n/**\n * Selection-style highlight: draws each comment range as a layer of rectangle markers below the text.\n * `RectangleMarker.forRange` extends wrapped (non-final) lines to the line edge, so a multi-line comment\n * reads as one continuous block rather than ragged per-word inline backgrounds.\n */\nconst commentsHighlightLayer = layer({\n  above: false,\n  class: 'cm-commentsHighlightLayer',\n  update: (update) =>\n    update.docChanged ||\n    update.viewportChanged ||\n    update.geometryChanged ||\n    update.selectionSet ||\n    update.transactions.some((tr) =>\n      tr.effects.some((effect) => effect.is(setSelection) || effect.is(setComments) || effect.is(setCommentState)),\n    ),\n  markers: (view) => {\n    const {\n      selection: { current },\n      comments,\n    } = view.state.field(commentsState);\n\n    return sortBy(comments ?? [], (comment) => comment.range.from).flatMap((comment) => {\n      const { from, to } = comment.range;\n      if (from >= to) {\n        return [];\n      }\n\n      const className =\n        comment.comment.id === current ? 'cm-comment-highlight cm-comment-highlight-current' : 'cm-comment-highlight';\n      const range = EditorSelection.range(from, to);\n      const markers = RectangleMarker.forRange(view, className, range);\n      // A single rectangle means the comment fits on one visual line — round its corners.\n      return markers.length === 1\n        ? RectangleMarker.forRange(view, `${className} cm-comment-highlight-single`, range)\n        : markers;\n    });\n  },\n});\n\nconst handleCommentClick = EditorView.domEventHandlers({\n  click: (event, view) => {\n    const id = commentIdAtClick(event, view);\n    if (!id) {\n      return false;\n    }\n\n    // Select the clicked thread in the editor (moves the caret into the range so the proximity tracker\n    // keeps it current) and notify the app for deliberate-click behaviour (e.g. reveal in a companion).\n    scrollCommentIntoView(view, id);\n    view.state.facet(optionsFacet).onActivate?.(id);\n    return true;\n  },\n});\n\n/**\n * Resolve the comment id under a click. Prefers the DOM target (a direct hit on the `.cm-comment`\n * span), then falls back to a document-position hit-test: the highlight-layer rectangles\n * (pointer-events: none) sit over the text, so a click often lands on the `.cm-line` (an ancestor of\n * the span) and an upward target walk would miss it.\n */\nconst commentIdAtClick = (event: MouseEvent, view: EditorView): string | undefined => {\n  let target = event.target as HTMLElement | null;\n  const editorRoot = view.dom;\n  while (target && target !== editorRoot && !target.hasAttribute?.('data-comment-id')) {\n    target = target.parentElement;\n  }\n  const domId = target && target !== editorRoot ? target.getAttribute('data-comment-id') : null;\n  if (domId) {\n    return domId;\n  }\n\n  const pos = view.posAtCoords({ x: event.clientX, y: event.clientY });\n  if (pos != null) {\n    const commentState = view.state.field(commentsState, false);\n    return commentState?.comments.find(({ range }) => pos >= range.from && pos <= range.to)?.comment.id;\n  }\n\n  return undefined;\n};\n\n//\n// Cut-and-paste.\n//\n\ntype TrackedComment = { id: string; from: number; to: number };\n\nconst trackPastedComments = (onUpdate: NonNullable<CommentsOptions['onUpdate']>) => {\n  // Tracks indexed selections within text.\n  let tracked: { text: Text; comments: TrackedComment[] } | null = null;\n\n  // Track cut or copy (enables cut-and-paste and copy-delete-paste to restore comment selection).\n  const handleTrack = (event: Event, view: EditorView) => {\n    const comments = view.state.field(commentsState);\n    const { main } = view.state.selection;\n    const selectedRanges = comments.comments.filter(\n      ({ range }) => range.from >= main.from && range.to <= main.to && range.from < range.to,\n    );\n\n    if (!selectedRanges.length) {\n      tracked = null;\n    } else {\n      tracked = {\n        text: view.state.doc.slice(main.from, main.to),\n        comments: selectedRanges.map(({ comment, range }) => ({\n          id: comment.id,\n          from: range.from - main.from,\n          to: range.to - main.from,\n        })),\n      };\n    }\n  };\n\n  return [\n    EditorView.domEventHandlers({\n      cut: handleTrack,\n      copy: handleTrack,\n    }),\n\n    // Track deleted comments.\n    invertedEffects.of((tr) => {\n      const { comments } = tr.startState.field(commentsState);\n      const effects: StateEffect<any>[] = [];\n      tr.changes.iterChangedRanges((fromA, toA) => {\n        for (const {\n          comment: { id },\n          range: { from, to },\n        } of comments) {\n          if (from < to && from >= fromA && to <= toA) {\n            effects.push(restoreCommentEffect.of({ id, from, to }));\n          }\n        }\n      });\n\n      return effects;\n    }),\n\n    // Handle paste or the undo of comment deletion.\n    EditorView.updateListener.of((update) => {\n      const restore: TrackedComment[] = [];\n\n      for (let i = 0; i < update.transactions.length; i++) {\n        const tr = update.transactions[i];\n        for (let j = 0; j < restore.length; j++) {\n          restore[j] = mapTrackedComment(restore[j], tr.changes);\n        }\n        for (const effect of tr.effects) {\n          if (effect.is(restoreCommentEffect)) {\n            restore.push(effect.value);\n          }\n        }\n      }\n\n      if (tracked) {\n        const paste = update.transactions.find((tr) => tr.isUserEvent('input.paste'));\n        if (paste) {\n          let found = -1;\n          paste.changes.iterChanges((fromA, toA, fromB, toB, text) => {\n            if (text.eq(tracked!.text)) {\n              for (let i = update.transactions.indexOf(paste!) + 1; i < update.transactions.length; i++) {\n                fromB = update.transactions[i].changes.mapPos(fromB);\n              }\n\n              found = fromB;\n            }\n          });\n\n          if (found > -1) {\n            for (const moved of tracked.comments) {\n              restore.push({ id: moved.id, from: found + moved.from, to: found + moved.to });\n            }\n          }\n\n          tracked = null;\n        }\n      }\n\n      for (const comment of restore) {\n        const { comments } = update.startState.field(commentsState);\n        const exists = comments.some((c) => c.comment.id === comment.id && c.range.from < c.range.to);\n        if (!exists) {\n          const cursor = Cursor.getCursorFromRange(update.state, comment);\n          onUpdate({ id: comment.id, cursor });\n        }\n      }\n    }),\n  ];\n};\n\nconst mapTrackedComment = (comment: TrackedComment, changes: ChangeDesc) => ({\n  id: comment.id,\n  from: changes.mapPos(comment.from, 1),\n  to: changes.mapPos(comment.to, 1),\n});\n\n/**\n * These are attached to undone/redone transactions in the editor for the purpose of restoring comments\n * that were deleted by the original changes.\n */\nconst restoreCommentEffect = StateEffect.define<TrackedComment>({\n  map: mapTrackedComment,\n});\n\nconst optionsFacet = singleValueFacet<CommentsOptions>();\n\n/**\n * Create comment thread action.\n */\nexport const createComment: Command = (view) => {\n  const options = view.state.facet(optionsFacet);\n  // Comment creation is prohibited on this surface (e.g. a suggestion branch).\n  if (options.readonly) {\n    return false;\n  }\n\n  let { from, to } = view.state.selection.main;\n\n  // Snap to the largest logical region so commenting never depends on a precise selection. Inside a\n  // (unified) diff hunk the comment covers the whole changed hunk (a bare cursor in the hunk is\n  // enough); otherwise, with no selection, it expands to the word under the cursor. The diff case\n  // takes precedence so a comment on a change visibly covers it.\n  const merge = getChunks(view.state);\n  const chunk = merge?.chunks.find((candidate) => candidate.fromB <= to && candidate.endB >= from);\n  if (chunk) {\n    from = chunk.fromB;\n    to = Math.min(chunk.endB, view.state.doc.length);\n  } else if (from === to) {\n    const word = view.state.wordAt(from);\n    if (word) {\n      from = word.from;\n      to = word.to;\n    }\n  }\n\n  // Exclude trailing newlines so the anchor stays within content and never reaches the document end.\n  // Anchoring at the very end previously required inserting a newline to stabilise the end cursor — a\n  // visible, unwanted mutation; the end cursor instead associates with the preceding character.\n  while (to > from && view.state.doc.sliceString(to - 1, to) === '\\n') {\n    to--;\n  }\n\n  if (from === to) {\n    return false;\n  }\n\n  const cursor = Cursor.getCursorFromRange(view.state, { from, to });\n  if (cursor) {\n    // Collapse the text selection now that the range is captured by the comment (the caret stays within\n    // the new comment, keeping it the current thread).\n    view.dispatch({ selection: { anchor: from } });\n    // Create thread via callback; branch-tag it with the branch under review (undefined = main).\n    options.onCreate?.({ cursor, from, location: view.coordsAtPos(from), branch: options.reviewBranch });\n    return true;\n  }\n\n  return false;\n};\n\n//\n// Utils.\n//\n\n/**\n * Whether the [from, to] document range is entirely within the editor's scroll\n * viewport. Returns false if either endpoint isn't currently rendered (off-screen).\n */\nexport const isRangeVisible = (view: EditorView, range: { from: number; to: number }): boolean => {\n  const from = view.coordsAtPos(range.from);\n  const to = view.coordsAtPos(range.to);\n  if (!from || !to) {\n    return false;\n  }\n\n  const { top, bottom } = view.scrollDOM.getBoundingClientRect();\n  return from.top >= top && to.bottom <= bottom;\n};\n\nexport type ScrollThreadOptions = {\n  /** Vertical alignment when scrolling. */\n  y?: 'start' | 'center' | 'end' | 'nearest';\n  /** Vertical margin (px) to keep around the target when scrolling. */\n  yMargin?: number;\n};\n\n/**\n * Scroll the comment thread with the given id into view (if not already entirely\n * visible) and mark it the current comment so the editor highlights it.\n */\nexport const scrollCommentIntoView = (\n  view: EditorView,\n  id: string,\n  { y = 'center', yMargin }: ScrollThreadOptions = {},\n) => {\n  const comment = view.state.field(commentsState).comments.find((range) => range.comment.id === id);\n  if (!comment?.comment.cursor) {\n    return;\n  }\n\n  const range = Cursor.getRangeFromCursor(view.state, comment.comment.cursor);\n  if (!range) {\n    return;\n  }\n\n  const { from, to } = view.state.selection.main;\n  // Never collapse a live selection: an attention change in the comments companion can land while the\n  // user is selecting text, and moving the caret would silently retarget the comment they create next.\n  const needsSelectionUpdate = from === to && from !== range.from;\n  view.dispatch({\n    selection: needsSelectionUpdate ? { anchor: range.from } : undefined,\n    effects: [\n      isRangeVisible(view, range) ? [] : EditorView.scrollIntoView(range.from, { y, yMargin }),\n      // Always mark this thread current so the highlight follows the selected thread.\n      setSelection.of({ current: id }),\n    ].flat(),\n  });\n};\n\n/**\n * NOTE: Matches search.\n */\n// The visible highlight is drawn by `commentsHighlightLayer` (below) as a layer of rectangle markers,\n// so — like a text selection — it fills wrapped lines to the line edge instead of stopping at the last\n// glyph. The inline `cm-comment` mark is now transparent and exists only to carry `data-comment-id`\n// (click routing) and the pointer affordance. No `border-radius`: the per-line rectangles abut, so\n// rounding each would pinch the interior edges into notches on a multi-line comment.\nconst styles = EditorView.theme({\n  // The layer only paints the background; text colour comes from the inline mark, which wraps exactly\n  // the comment text. `!important` and the descendant selector win over markdown syntax-highlight tokens\n  // nested inside the range.\n  '.cm-comment, .cm-comment *': {\n    color: 'var(--color-teal-fg) !important',\n  },\n  '.cm-comment[data-current=\"1\"], .cm-comment[data-current=\"1\"] *': {\n    color: 'var(--color-orange-fg) !important',\n  },\n  '.cm-comment': {\n    cursor: 'pointer',\n  },\n  // Layer wrapper: sit behind the text and never swallow clicks meant for the `cm-comment` mark.\n  '.cm-commentsHighlightLayer': {\n    pointerEvents: 'none',\n  },\n  // `box-shadow` with a 1px spread extends the block 1px on every side in the same colour — visual\n  // padding without changing layout. Vertically adjacent line rectangles paint into each other's gap\n  // with the same colour, so a multi-line comment stays seamless.\n  // 2px horizontal breathing room around the text: `padding-inline` widens the background box and the\n  // compensating negative `margin-inline` keeps it aligned to the text; `content-box` makes the padding\n  // grow the box (rather than shrink the content) so the fill and border-radius extend with it.\n  // `pointer-events: none` on the rectangles themselves (not only the layer wrapper) so a click over a\n  // comment passes through to the `.cm-comment` mark's `data-comment-id` (and reaches `handleCommentClick`)\n  // instead of landing on a highlight rectangle that carries no id.\n  '.cm-comment-highlight': {\n    backgroundColor: 'var(--color-teal-bg)',\n    boxShadow: '0 0 0 1px var(--color-teal-bg)',\n    boxSizing: 'content-box',\n    marginInline: '-2px',\n    paddingInline: '2px',\n    pointerEvents: 'none',\n  },\n  '.cm-comment-highlight-current': {\n    backgroundColor: 'var(--color-orange-bg)',\n    boxShadow: '0 0 0 1px var(--color-orange-bg)',\n  },\n  // Only round a comment that occupies a single visual line: a multi-line comment is drawn as several\n  // abutting rectangles, so rounding each would notch the interior edges.\n  '.cm-comment-highlight-single': {\n    borderRadius: '0.25rem',\n  },\n});\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { Chunk, unifiedMergeView } from '@codemirror/merge';\nimport { type Extension, Text } from '@codemirror/state';\nimport { EditorView } from '@codemirror/view';\nimport { diffWordsWithSpace } from 'diff';\n\nexport type DiffOptions = {\n  /**\n   * The baseline document to diff against. The editor's own document is the modified side, so\n   * insertions and deletions are shown relative to this original.\n   */\n  original: string;\n};\n\n/**\n * Orient the diff as a preview of accepting the original (the compared branch) into the editor's\n * document (the current branch): the compare branch's content — which the merge view renders as\n * deletions because it is absent from the editor — is what accepting ADDS, so show it as an addition;\n * the current branch's deviating content is what accepting REMOVES. This overrides the merge view's\n * default green-editor / red-original colouring, which reads inverted for branch review.\n */\nconst reviewOrientationTheme = EditorView.theme({\n  '&.cm-merge-b .cm-changedLine, &.cm-merge-b .cm-changedText': {\n    backgroundColor: 'var(--color-cm-diff-remove-surface)',\n  },\n  '& .cm-deletedChunk, & .cm-deletedChunk .cm-deletedText': {\n    backgroundColor: 'var(--color-cm-diff-add-surface)',\n    textDecoration: 'none',\n  },\n  // Keep the change-gutter markers aligned with the inverted content colours: the current branch's\n  // deviations (removed on accept) gutter red, the compare branch's content (added on accept) green.\n  '&.cm-merge-b .cm-changedLineGutter': { background: 'var(--color-cm-diff-remove-gutter)' },\n  '& .cm-deletedLineGutter': { background: 'var(--color-cm-diff-add-gutter)' },\n});\n\n/**\n * Inline (unified) diff view: renders the editor's document with insertions and deletions marked\n * relative to {@link DiffOptions.original}. The editor stays editable — edits to the document update\n * the diff against the (static) original live — so it can overlay a live editor to compare the\n * current state against another version (e.g. another branch) without locking editing.\n */\nexport const diffView = ({ original }: DiffOptions): Extension => [\n  unifiedMergeView({ original, mergeControls: false }),\n  reviewOrientationTheme,\n];\n\n/**\n * A single reviewable change between `before` (the original) and `after` (the proposal), anchored in\n * the BEFORE text so it can be rendered as a suggestion over the original document and applied in\n * place. `[from, to)` is the original range the hunk replaces (`from === to` for a pure insertion);\n * `removed` is the original text in that range, `inserted` is the proposal text that replaces it.\n */\nexport type DiffHunk = {\n  from: number;\n  to: number;\n  removed: string;\n  inserted: string;\n};\n\n/**\n * Groups the word-level diff between `before` and `after` into contiguous {@link DiffHunk}s — each a\n * maximal run of insertions/deletions bounded by unchanged text. Used to render Google-Docs-style\n * suggestions (accept/reject per hunk) over the original document (see {@link suggestChanges}).\n */\nexport const diffHunks = (before: string, after: string): DiffHunk[] => {\n  const hunks: DiffHunk[] = [];\n  let position = 0;\n  let pending: DiffHunk | undefined;\n  // Word-level diffing treats `WorldHello` -> `WorldHelloWorld` as one changed word, yielding a\n  // replace that claims the unchanged `WorldHello` — struck through and re-inserted, that renders as\n  // doubled text. When one side is contained in the other at an edge, trim it to the pure\n  // insert/delete core; genuine replaces keep word granularity (whole-word strikes read better).\n  const minimize = (hunk: DiffHunk): DiffHunk => {\n    const { removed, inserted } = hunk;\n    let prefix = 0;\n    while (prefix < removed.length && prefix < inserted.length && removed[prefix] === inserted[prefix]) {\n      prefix++;\n    }\n    let suffix = 0;\n    while (\n      suffix < removed.length - prefix &&\n      suffix < inserted.length - prefix &&\n      removed[removed.length - 1 - suffix] === inserted[inserted.length - 1 - suffix]\n    ) {\n      suffix++;\n    }\n    if (prefix + suffix !== removed.length && prefix + suffix !== inserted.length) {\n      return hunk;\n    }\n    return {\n      from: hunk.from + prefix,\n      to: hunk.to - suffix,\n      removed: removed.slice(prefix, removed.length - suffix),\n      inserted: inserted.slice(prefix, inserted.length - suffix),\n    };\n  };\n  const flush = () => {\n    if (pending) {\n      hunks.push(minimize(pending));\n      pending = undefined;\n    }\n  };\n  for (const change of diffWordsWithSpace(before, after)) {\n    if (change.added) {\n      pending ??= { from: position, to: position, removed: '', inserted: '' };\n      pending.inserted += change.value;\n    } else if (change.removed) {\n      pending ??= { from: position, to: position, removed: '', inserted: '' };\n      pending.removed += change.value;\n      pending.to = position + change.value.length;\n      position += change.value.length;\n    } else {\n      flush();\n      position += change.value.length;\n    }\n  }\n  flush();\n  return hunks;\n};\n\n/**\n * Coalesce an author's adjacent {@link DiffHunk}s into one hunk per group, so a run of nearby edits\n * reviews as a single unit (e.g. one anchored card in the comment-style layer). Two hunks merge when\n * the unchanged `before` gap between them is at most `maxGap` characters; the merged hunk spans both\n * and absorbs the gap text into `removed` (and, unchanged, into `inserted`) so it still reconstructs\n * `after`. With `respectBlockBoundaries` (default) a gap containing a blank line (paragraph break) is\n * never bridged, keeping edits in different blocks as separate groups.\n *\n * `hunks` must be ordered and non-overlapping as {@link diffHunks} returns them. Defaults\n * (`maxGap: 0`) make grouping a no-op — each hunk is its own group — so callers opt into wider\n * coalescing explicitly.\n */\nexport type GroupPolicy = { maxGap?: number; respectBlockBoundaries?: boolean };\n\nexport const groupHunks = (hunks: DiffHunk[], before: string, policy: GroupPolicy = {}): DiffHunk[] => {\n  const { maxGap = 0, respectBlockBoundaries = true } = policy;\n  const groups: DiffHunk[] = [];\n  for (const hunk of hunks) {\n    const previous = groups.at(-1);\n    const gap = previous ? before.slice(previous.to, hunk.from) : '';\n    const bridgeable =\n      previous !== undefined && gap.length <= maxGap && !(respectBlockBoundaries && /\\n[ \\t]*\\n/.test(gap));\n    if (bridgeable) {\n      // Absorb the unchanged gap into both sides so the merged hunk still maps `removed` → `inserted`.\n      previous.to = hunk.to;\n      previous.removed += gap + hunk.removed;\n      previous.inserted += gap + hunk.inserted;\n    } else {\n      groups.push({ ...hunk });\n    }\n  }\n  return groups;\n};\n\n/** Markdown delimiter pairs that must be suggested — and applied — atomically. */\nconst MARKUP_DELIMITERS = new Set(['**', '__', '*', '_', '~~', '`']);\n\n/**\n * Coalesces two pure insertions forming a matched markdown delimiter pair around intervening text\n * (e.g. `**` before and after a phrase) into ONE replace hunk spanning the wrapped text. Without\n * this, wrapping MULTIPLE words diffs as two zero-width inserts, and accepting either half alone\n * leaves broken syntax. Only a tight wrap (same line, non-empty gap) reads as one formatting action.\n */\nexport const pairMarkupHunks = (hunks: DiffHunk[], before: string): DiffHunk[] => {\n  const result: DiffHunk[] = [];\n  for (let index = 0; index < hunks.length; index++) {\n    const open = hunks[index];\n    const close = hunks[index + 1];\n    if (\n      close !== undefined &&\n      open.from === open.to &&\n      close.from === close.to &&\n      close.from > open.from &&\n      MARKUP_DELIMITERS.has(open.inserted) &&\n      close.inserted === open.inserted\n    ) {\n      const gap = before.slice(open.from, close.from);\n      if (gap.length > 0 && !gap.includes('\\n')) {\n        result.push({\n          from: open.from,\n          to: close.from,\n          removed: gap,\n          inserted: open.inserted + gap + close.inserted,\n        });\n        index++;\n        continue;\n      }\n    }\n    result.push(open);\n  }\n  return result;\n};\n\n/**\n * Re-anchors {@link DiffHunk}s computed against `base` into `doc` coordinates, so a foreign author's\n * proposal (diffed `base` vs proposal) can be decorated over a `doc` that has itself diverged from\n * `base` (the user's own branch edits). Each hunk offset is mapped through the character-level\n * `base`↔`doc` diff ({@link computeCharHunks}): a position in an unchanged region shifts by the net\n * length of edits before it; a position inside a doc-edited region clamps to that region's edge (the\n * `from` to its start, the `to` to its end). Without this, a proposal diffed against `base` would\n * render at stale offsets over the diverged `doc`.\n */\nexport const rebaseHunks = (base: string, doc: string, hunks: DiffHunk[]): DiffHunk[] =>\n  rebaseHunksWith(computeCharHunks(base, doc), hunks);\n\n/**\n * {@link rebaseHunks} with a precomputed `base`↔`doc` character diff. Callers rebasing MANY hunk sets\n * against the same pair — e.g. every author's proposal over one branch (see {@link suggestions}) —\n * compute {@link computeCharHunks} once and reuse it, rather than re-diffing the whole document per set.\n */\nexport const rebaseHunksWith = (charHunks: Hunk[], hunks: DiffHunk[]): DiffHunk[] => {\n  const mapPos = (pos: number, side: -1 | 1): number => {\n    let delta = 0;\n    for (const hunk of charHunks) {\n      if (pos < hunk.fromA) {\n        break;\n      }\n      // Any edge sitting exactly at a doc edit's start belongs to the region BEFORE the edit, so map\n      // it to `fromB` — never across the edit. For an upper edge this keeps a foreign hunk from\n      // absorbing the user's adjacent text; for a pure-insert anchor it keeps the proposal anchored\n      // BEFORE text the user typed at that spot (typing at a trailing suggestion lands after it).\n      if (pos === hunk.fromA) {\n        return hunk.fromB;\n      }\n      if (pos < hunk.toA) {\n        // Inside a doc-edited region: clamp to its edge so the offset stays a valid doc position.\n        return side < 0 ? hunk.fromB : hunk.toB;\n      }\n      // Past this region: `toB - toA` is the net offset shift it contributes.\n      delta = hunk.toB - hunk.toA;\n    }\n    return pos + delta;\n  };\n  return hunks.map((hunk) => {\n    // A zero-width hunk (pure insertion, from === to) must map both endpoints with the SAME side —\n    // otherwise, at a doc-edit boundary, the lower edge (side -1) and upper edge (side +1) can resolve\n    // to different offsets and invert the range (from > to). Non-zero hunks keep the asymmetric mapping\n    // so an exclusive upper edge never crosses into an adjacent doc edit.\n    if (hunk.from === hunk.to) {\n      const at = mapPos(hunk.from, -1);\n      return { ...hunk, from: at, to: at };\n    }\n    return { ...hunk, from: mapPos(hunk.from, -1), to: mapPos(hunk.to, 1) };\n  });\n};\n\n/** A changed hunk between two documents as character ranges in each (A = original, B = modified). */\nexport type Hunk = { fromA: number; toA: number; fromB: number; toB: number };\n\n/**\n * LINE-level changed hunks between `original` (A) and `modified` (B) as character ranges (via\n * `@codemirror/merge` `Chunk.build`). Coarser than {@link computeWordHunks}; retained for callers that\n * want whole-line change regions.\n */\nexport const computeHunks = (original: string, modified: string): Hunk[] =>\n  Chunk.build(Text.of(original.split('\\n')), Text.of(modified.split('\\n'))).map((chunk) => ({\n    fromA: chunk.fromA,\n    toA: chunk.toA,\n    fromB: chunk.fromB,\n    toB: chunk.toB,\n  }));\n\n/**\n * WORD-level changed hunks between `original` (A) and `modified` (B), each carrying both coordinate\n * ranges. Matches the word granularity of {@link diffHunks} (same `diffWordsWithSpace` alignment), so\n * {@link cherryPickHunk} / {@link revertHunk} resolve exactly the anchored word(s) — accepting one\n * word does not pull in the rest of its line.\n */\nexport const computeWordHunks = (original: string, modified: string): Hunk[] => {\n  const hunks: Hunk[] = [];\n  let positionA = 0;\n  let positionB = 0;\n  let pending: Hunk | undefined;\n  const flush = () => {\n    if (pending) {\n      hunks.push(pending);\n      pending = undefined;\n    }\n  };\n  for (const change of diffWordsWithSpace(original, modified)) {\n    if (change.added) {\n      pending ??= { fromA: positionA, toA: positionA, fromB: positionB, toB: positionB };\n      positionB += change.value.length;\n      pending.toB = positionB;\n    } else if (change.removed) {\n      pending ??= { fromA: positionA, toA: positionA, fromB: positionB, toB: positionB };\n      positionA += change.value.length;\n      pending.toA = positionA;\n    } else {\n      flush();\n      positionA += change.value.length;\n      positionB += change.value.length;\n    }\n  }\n  flush();\n  return hunks;\n};\n\n/**\n * CHARACTER-level changed hunks between `original` (A) and `modified` (B), each carrying both\n * coordinate ranges. Finer than {@link computeWordHunks}: an in-word or whitespace/newline edit shows\n * only the changed characters, not a whole-word delete+insert. Used for live \"Suggesting mode\" tracked\n * changes (see {@link trackChanges}), where word granularity makes every keystroke read as a struck\n * word rebuilt beside it.\n */\nexport const computeCharHunks = (original: string, modified: string): Hunk[] =>\n  joinNearbyHunks(wordHunks(original, modified))\n    .map((hunk) => trimCommonAffixes(original, modified, hunk))\n    .map((hunk) => slideToWordBoundary(original, modified, hunk));\n\n/**\n * Unchanged text shorter than this between two changed regions does not separate them: a selection\n * that cuts into the words at each end leaves a word fragment on both sides of a single space, and\n * reporting those as two changes strikes text the reader never touched.\n */\nconst MIN_COMMON_RUN = 4;\n\n/** Merges hunks separated by less unchanged text than {@link MIN_COMMON_RUN}, absorbing the gap. */\nconst joinNearbyHunks = (hunks: Hunk[]): Hunk[] => {\n  const merged: Hunk[] = [];\n  for (const hunk of hunks) {\n    const previous = merged.at(-1);\n    if (previous && hunk.fromA - previous.toA < MIN_COMMON_RUN && hunk.fromB - previous.toB < MIN_COMMON_RUN) {\n      previous.toA = hunk.toA;\n      previous.toB = hunk.toB;\n    } else {\n      merged.push({ ...hunk });\n    }\n  }\n  return merged;\n};\n\n/** Accumulates a diff's changed regions into {@link Hunk}s, given a tokenisation of the two texts. */\nconst accumulate = (changes: Array<{ value: string; added?: boolean; removed?: boolean }>): Hunk[] => {\n  const hunks: Hunk[] = [];\n  let positionA = 0;\n  let positionB = 0;\n  let pending: Hunk | undefined;\n  const flush = () => {\n    if (pending) {\n      hunks.push(pending);\n      pending = undefined;\n    }\n  };\n  for (const change of changes) {\n    if (change.added) {\n      pending ??= { fromA: positionA, toA: positionA, fromB: positionB, toB: positionB };\n      positionB += change.value.length;\n      pending.toB = positionB;\n    } else if (change.removed) {\n      pending ??= { fromA: positionA, toA: positionA, fromB: positionB, toB: positionB };\n      positionA += change.value.length;\n      pending.toA = positionA;\n    } else {\n      flush();\n      positionA += change.value.length;\n      positionB += change.value.length;\n    }\n  }\n  flush();\n  return hunks;\n};\n\nconst wordHunks = (original: string, modified: string): Hunk[] => accumulate(diffWordsWithSpace(original, modified));\n\n/**\n * Shrinks a word-level hunk to the characters that actually changed, by dropping the prefix and suffix\n * its two sides share. Word granularity is what keeps one edit from fragmenting into strikes with\n * untouched letters marooned between them, but on its own it reports whole words: a deletion cutting\n * into the words at each end fuses the survivors, and the hunk then reads as \"delete both words, insert\n * this fused one\". Trimming leaves exactly the range the reader removed (or typed).\n */\nconst trimCommonAffixes = (original: string, modified: string, hunk: Hunk): Hunk => {\n  const removed = original.slice(hunk.fromA, hunk.toA);\n  const inserted = modified.slice(hunk.fromB, hunk.toB);\n  const limit = Math.min(removed.length, inserted.length);\n\n  let prefix = 0;\n  while (prefix < limit && removed[prefix] === inserted[prefix]) {\n    prefix++;\n  }\n  let suffix = 0;\n  while (suffix < limit - prefix && removed[removed.length - 1 - suffix] === inserted[inserted.length - 1 - suffix]) {\n    suffix++;\n  }\n\n  return {\n    fromA: hunk.fromA + prefix,\n    toA: hunk.toA - suffix,\n    fromB: hunk.fromB + prefix,\n    toB: hunk.toB - suffix,\n  };\n};\n\n/**\n * Slides a one-sided hunk (a pure deletion or insertion) to the word boundary that encloses it. Which\n * characters a one-sided edit removed is ambiguous whenever text repeats around it: deleting\n * `suggest change` from `suggest changes` leaves an `s` that is equally the head of the first word or\n * the tail of the last, and the minimal-edit answer strikes `uggest changes` — keeping a letter of the\n * word the reader deleted. Rotating the span while it still describes the same edit lets it settle\n * where a reader would draw it.\n */\nconst slideToWordBoundary = (original: string, modified: string, hunk: Hunk): Hunk => {\n  const deletion = hunk.toB === hunk.fromB;\n  const insertion = hunk.toA === hunk.fromA;\n  if (deletion === insertion) {\n    return hunk;\n  }\n\n  const text = deletion ? original : modified;\n  const from = deletion ? hunk.fromA : hunk.fromB;\n  const to = deletion ? hunk.toA : hunk.toB;\n  const atBoundary = (offset: number) => offset === 0 || /\\s/.test(text[offset - 1]);\n  if (atBoundary(from)) {\n    return hunk;\n  }\n\n  for (let shift = 1; shift <= from; shift++) {\n    // Rotating past a character only preserves the edit when it matches the span's last character.\n    if (text[from - shift] !== text[to - shift]) {\n      break;\n    }\n    if (atBoundary(from - shift)) {\n      return {\n        fromA: hunk.fromA - shift,\n        toA: hunk.toA - shift,\n        fromB: hunk.fromB - shift,\n        toB: hunk.toB - shift,\n      };\n    }\n  }\n\n  return hunk;\n};\n\n/**\n * Does a hunk cover the anchored range? Half-open, so a range touching a neighbouring hunk's\n * boundary is not on that hunk — except where either side is empty, which is the normal shape of a\n * pure insertion: the inserted text occupies no span on the other side, and the caller anchors it at\n * a single offset. Those collapse to a containment test, or accepting an insertion is impossible.\n */\nconst overlaps = (from: number, to: number, range: { start: number; end: number }): boolean =>\n  from === to || range.start === range.end\n    ? from <= range.end && to >= range.start\n    : from < range.end && to > range.start;\n\n/**\n * Resolve a single change to apply when cherry-picking from `compare` (A) into `current` (B): find\n * the hunk overlapping `range` (a character range in `current`) and return the splice that replaces\n * that hunk's current text with the compare version. Returns undefined if no hunk overlaps the range\n * (e.g. the change was already applied/reverted). `compare` is read live by the caller, so applying\n * the result always reflects the latest diff — not a snapshot.\n */\nexport const cherryPickHunk = (\n  current: string,\n  compare: string,\n  range: { start: number; end: number },\n): { from: number; del: number; insert: string } | undefined => {\n  // Merge EVERY hunk the range overlaps into one splice: a range spanning a delimiter pair (two\n  // word-hunks wrapping unchanged text) must apply atomically — half an accepted pair is broken\n  // syntax. A cursor-anchored range still resolves a single hunk, unchanged.\n  const matched = computeWordHunks(compare, current).filter((h) => overlaps(h.fromB, h.toB, range));\n  if (matched.length === 0) {\n    return undefined;\n  }\n  const first = matched[0];\n  const last = matched[matched.length - 1];\n  return {\n    from: first.fromB,\n    del: Math.min(last.toB, current.length) - first.fromB,\n    insert: compare.slice(first.fromA, Math.min(last.toA, compare.length)),\n  };\n};\n\n/**\n * Resolve the splice that reverts a single change on `compare` (a branch) back to `base` (main) —\n * the reject counterpart to {@link cherryPickHunk}. `baseRange` is a character range in `base`; the\n * hunk overlapping it is located and the returned splice (in `compare` coordinates) replaces the\n * branch's version with the base text. Returns undefined if no hunk overlaps the range.\n */\nexport const revertHunk = (\n  base: string,\n  compare: string,\n  baseRange: { start: number; end: number },\n): { from: number; del: number; insert: string } | undefined => {\n  // A = base, B = compare (branch); locate the hunks by the base-side range and merge them so a\n  // pair-spanning reject reverts the whole formatting action (mirror of cherryPickHunk).\n  const matched = computeWordHunks(base, compare).filter((h) => overlaps(h.fromA, h.toA, baseRange));\n  if (matched.length === 0) {\n    return undefined;\n  }\n  const first = matched[0];\n  const last = matched[matched.length - 1];\n  return {\n    from: first.fromB,\n    del: Math.min(last.toB, compare.length) - first.fromB,\n    insert: base.slice(first.fromA, Math.min(last.toA, base.length)),\n  };\n};\n"],"mappings":"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"}