"Overlap" handling of findDiffStart() and findDiffEnd()

I am trying to do some custom diff and saw in the footnote example this line in NodeView.update():

let overlap = start - Math.min(endA, endB)

  update(node) {
    if (!node.sameMarkup(this.node)) return false
    this.node = node
    if (this.innerView) {
      let state = this.innerView.state
      let start = node.content.findDiffStart(state.doc.content)
      if (start != null) {
        let {a: endA, b: endB} = node.content.findDiffEnd(state.doc.content)
        let overlap = start - Math.min(endA, endB)
        if (overlap > 0) { endA += overlap; endB += overlap }
        this.innerView.dispatch(
          state.tr
            .replace(start, endB, node.slice(start, endA))
            .setMeta("fromOutside", true))
      }
    }
    return true
  }

Similar handling could also be seen in domchange.js. I’m quite confused with the code since I think the diff end is always after the start.

Could someone give an example, under which condition do we need to handle this “overlap” when using findDiffStart and findDiffEnd ?

They both independently scan from one side of the document. So if you are for example comparing the content “abc” to “abbc”, the scan from the start will find the first difference at 2 (c vs b after “ab”), and the scan from the end will find the first difference at 1/2 (a vs b before “bc”). Those overlap.

1 Like

thank you for the quick answer. it really helped :smile:

This subtle detail confused me a good bit as well.

I ended up writing the function below for our application to get a more or less guaranteed-to-be-correct diff range for the updated document (latest state), for example in an appendTransaction context.

/**
 * Get start and end diff position values for two states (old, new).
 * @param {EditorState} oldState - last PM state
 * @param {EditorState} newState - current PM state
 * @returns {object} - { start, end }
 */
function getDiffRange (oldState, newState) {
  // https://prosemirror.net/docs/ref/#model.Fragment.findDiffStart
  const start = oldState.doc.content.findDiffStart(newState.doc.content)
  // Important: diffEnd value is {a,b} object since end pos will differ.
  // https://prosemirror.net/docs/ref/#model.Fragment.findDiffEnd
  let { a, b } = oldState.doc.content.findDiffEnd(newState.doc.content)
  // WARNING: diffEnd may be lower than diffStart.
  // If so, add overlap to get correct range.
  // https://discuss.prosemirror.net/t/overlap-handling-of-finddiffstart-and-finddiffend/2856
  const overlap = start - Math.min(a, b)
  if (overlap > 0) b += overlap
  return { start, end: b }
}

I think this is the correct approach, please let me know if I misunderstood anything.

Hopefully this is useful to future readers too.

:vulcan_salute:

1 Like