docs: function markText(doc, from, to, options, type) {
      // Shared markers (across linked documents) are handled separately
      // (markTextShared will call out to this again, once per
      // document).
      if (options && options.shared) return markTextShared(doc, from, to, options, type);
      // Ensure we are in an operation.
      if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type);
  
      var marker = new TextMarker(doc, type), diff = cmp(from, to);
      if (options) copyObj(options, marker, false);
      // Don't connect empty markers unless clearWhenEmpty is false
      if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false)
        return marker;
      if (marker.replacedWith) {
        // Showing up as a widget implies collapsed (widget replaces text)
        marker.collapsed = true;
        marker.widgetNode = elt("span", [marker.replacedWith], "CodeMirror-widget");
        if (!options.handleMouseEvents) marker.widgetNode.setAttribute("cm-ignore-events", "true");
        if (options.insertLeft) marker.widgetNode.insertLeft = true;
      }
      if (marker.collapsed) {
        if (conflictingCollapsedRange(doc, from.line, from, to, marker) ||
            from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker))
          throw new Error("Inserting collapsed marker partially overlapping an existing one");
        sawCollapsedSpans = true;
      }
  
      if (marker.addToHistory)
        addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN);
  
      var curLine = from.line, cm = doc.cm, updateMaxLine;
      doc.iter(curLine, to.line + 1, function(line) {
        if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine)
          updateMaxLine = true;
        if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0);
        addMarkedSpan(line, new MarkedSpan(marker,
                                           curLine == from.line ? from.ch : null,
                                           curLine == to.line ? to.ch : null));
        ++curLine;
      });
      // lineIsHidden depends on the presence of the spans, so needs a second pass
      if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) {
        if (lineIsHidden(doc, line)) updateLineHeight(line, 0);
      });
  
      if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); });
  
      if (marker.readOnly) {
        sawReadOnlySpans = true;
        if (doc.history.done.length || doc.history.undone.length)
          doc.clearHistory();
      }
      if (marker.collapsed) {
        marker.id = ++nextMarkerId;
        marker.atomic = true;
      }
      if (cm) {
        // Sync editor state
        if (updateMaxLine) cm.curOp.updateMaxLine = true;
        if (marker.collapsed)
          regChange(cm, from.line, to.line + 1);
        else if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.css)
          for (var i = from.line; i <= to.line; i++) regLineChange(cm, i, "text");
        if (marker.atomic) reCheckSelection(cm.doc);
        signalLater(cm, "markerAdded", cm, marker);
      }
      return marker;
    }