docs: function readInput(cm) {
      var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc;
      // Since this is called a *lot*, try to bail out as cheaply as
      // possible when it is clear that nothing happened. hasSelection
      // will be the case when there is a lot of text in the textarea,
      // in which case reading its value would be expensive.
      if (!cm.state.focused || (hasSelection(input) && !prevInput) || isReadOnly(cm) || cm.options.disableInput || cm.state.keySeq)
        return false;
      // See paste handler for more on the fakedLastChar kludge
      if (cm.state.pasteIncoming && cm.state.fakedLastChar) {
        input.value = input.value.substring(0, input.value.length - 1);
        cm.state.fakedLastChar = false;
      }
      var text = input.value;
      // If nothing changed, bail.
      if (text == prevInput && !cm.somethingSelected()) return false;
      // Work around nonsensical selection resetting in IE9/10, and
      // inexplicable appearance of private area unicode characters on
      // some key combos in Mac (#2689).
      if (ie && ie_version >= 9 && cm.display.inputHasSelection === text ||
          mac && /[\uf700-\uf7ff]/.test(text)) {
        resetInput(cm);
        return false;
      }
  
      var withOp = !cm.curOp;
      if (withOp) startOperation(cm);
      cm.display.shift = false;
  
      if (text.charCodeAt(0) == 0x200b && doc.sel == cm.display.selForContextMenu && !prevInput)
        prevInput = "\u200b";
      // Find the part of the input that is actually new
      var same = 0, l = Math.min(prevInput.length, text.length);
      while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
      var inserted = text.slice(same), textLines = splitLines(inserted);
  
      // When pasing N lines into N selections, insert one line per selection
      var multiPaste = null;
      if (cm.state.pasteIncoming && doc.sel.ranges.length > 1) {
        if (lastCopied && lastCopied.join("\n") == inserted)
          multiPaste = doc.sel.ranges.length % lastCopied.length == 0 && map(lastCopied, splitLines);
        else if (textLines.length == doc.sel.ranges.length)
          multiPaste = map(textLines, function(l) { return [l]; });
      }
  
      // Normal behavior is to insert the new text into every selection
      for (var i = doc.sel.ranges.length - 1; i >= 0; i--) {
        var range = doc.sel.ranges[i];
        var from = range.from(), to = range.to();
        // Handle deletion
        if (same < prevInput.length)
          from = Pos(from.line, from.ch - (prevInput.length - same));
        // Handle overwrite
        else if (cm.state.overwrite && range.empty() && !cm.state.pasteIncoming)
          to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length));
        var updateInput = cm.curOp.updateInput;
        var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines,
                           origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"};
        makeChange(cm.doc, changeEvent);
        signalLater(cm, "inputRead", cm, changeEvent);
        // When an 'electric' character is inserted, immediately trigger a reindent
        if (inserted && !cm.state.pasteIncoming && cm.options.electricChars &&
            cm.options.smartIndent && range.head.ch < 100 &&
            (!i || doc.sel.ranges[i - 1].head.line != range.head.line)) {
          var mode = cm.getModeAt(range.head);
          var end = changeEnd(changeEvent);
          if (mode.electricChars) {
            for (var j = 0; j < mode.electricChars.length; j++)
              if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {
                indentLine(cm, end.line, "smart");
                break;
              }
          } else if (mode.electricInput) {
            if (mode.electricInput.test(getLine(doc, end.line).text.slice(0, end.ch)))
              indentLine(cm, end.line, "smart");
          }
        }
      }
      ensureCursorVisible(cm);
      cm.curOp.updateInput = updateInput;
      cm.curOp.typing = true;
  
      // Don't leave long text in the textarea, since it makes further polling slow
      if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = "";
      else cm.display.prevInput = text;
      if (withOp) endOperation(cm);
      cm.state.pasteIncoming = cm.state.cutIncoming = false;
      return true;
    }