diff --git a/doc/manual.html b/doc/manual.html
index f7cd2846569eaee08ffc8d2fa0bd1de7cedfd474..829cf7476a9a0382b9867b3858183adef07ff86f 100644
--- a/doc/manual.html
+++ b/doc/manual.html
@@ -2048,6 +2048,17 @@ editor.setOption("extraKeys", {
       lot faster. The return value from this method will be the return
       value of your function.</dd>
 
+      <dt><code><strong>cm.startOperation</strong>()</code></dt>
+      <dt><code><strong>cm.endOperation</strong>()</code></dt>
+      <dd>In normal circumstances, use the above <code>operation</code>
+      method. But if you want to buffer operations happening asynchronously,
+      or that can't all be wrapped in a callback function, you can
+      call <code>startOperation</code> to tell CodeMirror to start
+      buffering changes, and <code>endOperation</code> to actually
+      render all the updates. <em>Be careful:</em> if you use this
+      API and forget to call <code>endOperation</code>, the editor will
+      just never update.</dd>
+
       <dt id="indentLine"><code><strong>cm.indentLine</strong>(line: integer, ?dir: string|integer)</code></dt>
       <dd>Adjust the indentation of the given line. The second
       argument (which defaults to <code>"smart"</code>) may be one of:
diff --git a/src/edit/methods.js b/src/edit/methods.js
index 99f61da49e7c934a12a51b4908dfb10186fd07fb..8dc692529edc9b0e2b70f6eae3cc82e2698cf0b3 100644
--- a/src/edit/methods.js
+++ b/src/edit/methods.js
@@ -10,7 +10,7 @@ import { onKeyDown, onKeyPress, onKeyUp } from "./key_events"
 import { onMouseDown } from "./mouse_events"
 import { getKeyMap } from "../input/keymap"
 import { endOfLine, moveLogically, moveVisually } from "../input/movement"
-import { methodOp, operation, runInOp } from "../display/operations"
+import { endOperation, methodOp, operation, runInOp, startOperation } from "../display/operations"
 import { clipLine, clipPos, equalCursorPos, Pos } from "../line/pos"
 import { charCoords, charWidth, clearCaches, clearLineMeasurementCache, coordsChar, cursorCoords, displayHeight, displayWidth, estimateLineHeights, fromCoordSystem, intoCoordSystem, scrollGap, textHeight } from "../measurement/position_measurement"
 import { Range } from "../model/selection"
@@ -405,6 +405,8 @@ export default function(CodeMirror) {
     }),
 
     operation: function(f){return runInOp(this, f)},
+    startOperation: function(){return startOperation(this)},
+    endOperation: function(){return endOperation(this)},
 
     refresh: methodOp(function() {
       let oldHeight = this.display.cachedTextHeight