docs: function makeChangeSingleDocInEditor(cm, change, spans) {
    let doc = cm.doc, display = cm.display, from = change.from, to = change.to
  
    let recomputeMaxLength = false, checkWidthStart = from.line
    if (!cm.options.lineWrapping) {
      checkWidthStart = lineNo(visualLine(getLine(doc, from.line)))
      doc.iter(checkWidthStart, to.line + 1, line => {
        if (line == display.maxLine) {
          recomputeMaxLength = true
          return true
        }
      })
    }
  
    if (doc.sel.contains(change.from, change.to) > -1)
      signalCursorActivity(cm)
  
    updateDoc(doc, change, spans, estimateHeight(cm))
  
    if (!cm.options.lineWrapping) {
      doc.iter(checkWidthStart, from.line + change.text.length, line => {
        let len = lineLength(line)
        if (len > display.maxLineLength) {
          display.maxLine = line
          display.maxLineLength = len
          display.maxLineChanged = true
          recomputeMaxLength = false
        }
      })
      if (recomputeMaxLength) cm.curOp.updateMaxLine = true
    }
  
    retreatFrontier(doc, from.line)
    startWorker(cm, 400)
  
    let lendiff = change.text.length - (to.line - from.line) - 1
    // Remember that these lines changed, for updating the display
    if (change.full)
      regChange(cm)
    else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change))
      regLineChange(cm, from.line, "text")
    else
      regChange(cm, from.line, to.line + 1, lendiff)
  
    let changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change")
    if (changeHandler || changesHandler) {
      let obj = {
        from: from, to: to,
        text: change.text,
        removed: change.removed,
        origin: change.origin
      }
      if (changeHandler) signalLater(cm, "change", cm, obj)
      if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj)
    }
    cm.display.selForContextMenu = null
  }