diff --git a/addon/merge/merge.js b/addon/merge/merge.js
index 1d0a34636bcc06e4aa9b6c01b5f371e0ce36e774..12ef1306148a0ed03e1bd4ff8393166b16c1a3bd 100644
--- a/addon/merge/merge.js
+++ b/addon/merge/merge.js
@@ -42,6 +42,7 @@
       this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: !this.mv.options.allowEditingOriginals}, copyObj(options)));
 
       this.diff = getDiff(asString(orig), asString(options.value));
+      this.chunks = getChunks(this.diff);
       this.diffOutOfDate = false;
 
       this.showDifferences = options.showDifferences !== false;
@@ -61,6 +62,7 @@
   function ensureDiff(dv) {
     if (dv.diffOutOfDate) {
       dv.diff = getDiff(dv.orig.getValue(), dv.edit.getValue());
+      dv.chunks = getChunks(dv.diff);
       dv.diffOutOfDate = false;
       CodeMirror.signal(dv.edit, "updateDiff", dv.diff);
     }
@@ -134,7 +136,7 @@
     } else {
       var halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen;
       var mid = editor.lineAtHeight(midY, "local");
-      var around = chunkBoundariesAround(dv.diff, mid, type == DIFF_INSERT);
+      var around = chunkBoundariesAround(dv.chunks, mid, type == DIFF_INSERT);
       var off = getOffsets(editor, type == DIFF_INSERT ? around.edit : around.orig);
       var offOther = getOffsets(other, type == DIFF_INSERT ? around.orig : around.edit);
       var ratio = (midY - off.top) / (off.bot - off.top);
@@ -281,15 +283,16 @@
 
     var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport();
     var sTopEdit = dv.edit.getScrollInfo().top, sTopOrig = dv.orig.getScrollInfo().top;
-    iterateChunks(dv.diff, function(topOrig, botOrig, topEdit, botEdit) {
-      if (topEdit <= vpEdit.to && botEdit >= vpEdit.from &&
-          topOrig <= vpOrig.to && botOrig >= vpOrig.from)
-        drawConnectorsForChunk(dv, topOrig, botOrig, topEdit, botEdit, sTopOrig, sTopEdit, w);
-      if (align && (topEdit <= vpEdit.to || topOrig <= vpOrig.to)) {
-        var above = (botEdit < vpEdit.from && botOrig < vpOrig.from);
-        alignChunks(dv, topOrig, botOrig, topEdit, botEdit, above && extraSpaceAbove);
+    for (var i = 0; i < dv.chunks.length; i++) {
+      var ch = dv.chunks[i];
+      if (ch.editFrom <= vpEdit.to && ch.editTo >= vpEdit.from &&
+          ch.origFrom <= vpOrig.to && ch.origTo >= vpOrig.from)
+        drawConnectorsForChunk(dv, ch, sTopOrig, sTopEdit, w);
+      if (align && (ch.editFrom <= vpEdit.to || ch.origFrom <= vpOrig.to)) {
+        var above = (ch.editTo < vpEdit.from && ch.origTo < vpOrig.from);
+        alignChunks(dv, ch, above && extraSpaceAbove);
       }
-    });
+    }
     if (align) {
       if (extraSpaceAbove.edit)
         dv.aligners.push(padBelow(dv.edit, 0, extraSpaceAbove.edit));
@@ -300,15 +303,15 @@
     }
   }
 
-  function drawConnectorsForChunk(dv, topOrig, botOrig, topEdit, botEdit, sTopOrig, sTopEdit, w) {
+  function drawConnectorsForChunk(dv, chunk, sTopOrig, sTopEdit, w) {
     var flip = dv.type == "left";
-    var top = dv.orig.heightAtLine(topOrig, "local") - sTopOrig;
+    var top = dv.orig.heightAtLine(chunk.origFrom, "local") - sTopOrig;
     if (dv.svg) {
       var topLpx = top;
-      var topRpx = dv.edit.heightAtLine(topEdit, "local") - sTopEdit;
+      var topRpx = dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit;
       if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; }
-      var botLpx = dv.orig.heightAtLine(botOrig, "local") - sTopOrig;
-      var botRpx = dv.edit.heightAtLine(botEdit, "local") - sTopEdit;
+      var botLpx = dv.orig.heightAtLine(chunk.origTo, "local") - sTopOrig;
+      var botRpx = dv.edit.heightAtLine(chunk.editTo, "local") - sTopEdit;
       if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; }
       var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx;
       var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx;
@@ -321,34 +324,35 @@
                                                 "CodeMirror-merge-copy"));
       var editOriginals = dv.mv.options.allowEditingOriginals;
       copy.title = editOriginals ? "Push to left" : "Revert chunk";
-      copy.chunk = {topEdit: topEdit, botEdit: botEdit, topOrig: topOrig, botOrig: botOrig};
+      copy.chunk = chunk;
       copy.style.top = top + "px";
 
       if (editOriginals) {
-        var topReverse = dv.orig.heightAtLine(topEdit, "local") - sTopEdit;
+        var topReverse = dv.orig.heightAtLine(chunk.editFrom, "local") - sTopEdit;
         var copyReverse = dv.copyButtons.appendChild(elt("div", dv.type == "right" ? "\u21dd" : "\u21dc",
                                                          "CodeMirror-merge-copy-reverse"));
         copyReverse.title = "Push to right";
-        copyReverse.chunk = {topEdit: topOrig, botEdit: botOrig, topOrig: topEdit, botOrig: botEdit};
+        copyReverse.chunk = {editFrom: chunk.origFrom, editTo: chunk.origTo,
+                             origFrom: chunk.editFrom, origTo: chunk.editTo};
         copyReverse.style.top = topReverse + "px";
         dv.type == "right" ? copyReverse.style.left = "2px" : copyReverse.style.right = "2px";
       }
     }
   }
 
-  function alignChunks(dv, topOrig, botOrig, topEdit, botEdit, aboveViewport) {
-    var topOrigPx = dv.orig.heightAtLine(topOrig, "local");
-    var botOrigPx = dv.orig.heightAtLine(botOrig, "local");
-    var topEditPx = dv.edit.heightAtLine(topEdit, "local");
-    var botEditPx = dv.edit.heightAtLine(botEdit, "local");
-    var origH = botOrigPx -topOrigPx, editH = botEditPx - topEditPx;
+  function alignChunks(dv, chunk, aboveViewport) {
+    var topOrigPx = dv.orig.heightAtLine(chunk.origFrom, "local");
+    var botOrigPx = dv.orig.heightAtLine(chunk.origTo, "local");
+    var topEditPx = dv.edit.heightAtLine(chunk.editFrom, "local");
+    var botEditPx = dv.edit.heightAtLine(chunk.editTo, "local");
+    var origH = botOrigPx - topOrigPx, editH = botEditPx - topEditPx;
     var diff = editH - origH;
     if (diff > 1) {
       if (aboveViewport) aboveViewport.orig += diff;
-      else dv.aligners.push(padBelow(dv.orig, botOrig - 1, diff));
+      else dv.aligners.push(padBelow(dv.orig, chunk.origTo - 1, diff));
     } else if (diff < -1) {
       if (aboveViewport) aboveViewport.edit -= diff;
-      else dv.aligners.push(padBelow(dv.edit, botEdit - 1, -diff));
+      else dv.aligners.push(padBelow(dv.edit, chunk.editTo - 1, -diff));
     }
     return 0;
   }
@@ -362,8 +366,8 @@
 
   function copyChunk(dv, to, from, chunk) {
     if (dv.diffOutOfDate) return;
-    to.replaceRange(from.getRange(Pos(chunk.topOrig, 0), Pos(chunk.botOrig, 0)),
-                         Pos(chunk.topEdit, 0), Pos(chunk.botEdit, 0));
+    to.replaceRange(from.getRange(Pos(chunk.origFrom, 0), Pos(chunk.origTo, 0)),
+                         Pos(chunk.editFrom, 0), Pos(chunk.editTo, 0));
   }
 
   // Merge view, containing 0, 1, or 2 diff views.
@@ -464,10 +468,10 @@
       if (this.left) this.left.setShowDifferences(val);
     },
     rightChunks: function() {
-      return this.right && getChunks(this.right);
+      if (this.right) { ensureDiff(this.right); return this.right.chunks; }
     },
     leftChunks: function() {
-      return this.left && getChunks(this.left);
+      if (this.left) { ensureDiff(this.left); return this.left.chunks; }
     }
   };
 
@@ -495,7 +499,8 @@
     return diff;
   }
 
-  function iterateChunks(diff, f) {
+  function getChunks(diff) {
+    var chunks = [];
     var startEdit = 0, startOrig = 0;
     var edit = Pos(0, 0), orig = Pos(0, 0);
     for (var i = 0; i < diff.length; ++i) {
@@ -507,7 +512,8 @@
         var endOff = endOfLineClean(diff, i) ? 1 : 0;
         var cleanToEdit = edit.line + endOff, cleanToOrig = orig.line + endOff;
         if (cleanToEdit > cleanFromEdit) {
-          if (i) f(startOrig, cleanFromOrig, startEdit, cleanFromEdit);
+          if (i) chunks.push({origFrom: startOrig, origTo: cleanFromOrig,
+                              editFrom: startEdit, editTo: cleanFromEdit});
           startEdit = cleanToEdit; startOrig = cleanToOrig;
         }
       } else {
@@ -515,17 +521,9 @@
       }
     }
     if (startEdit <= edit.line || startOrig <= orig.line)
-      f(startOrig, orig.line + 1, startEdit, edit.line + 1);
-  }
-
-  function getChunks(dv) {
-    ensureDiff(dv);
-    var collect = [];
-    iterateChunks(dv.diff, function(topOrig, botOrig, topEdit, botEdit) {
-      collect.push({origFrom: topOrig, origTo: botOrig,
-                    editFrom: topEdit, editTo: botEdit});
-    });
-    return collect;
+      chunks.push({origFrom: startOrig, origTo: orig.line + 1,
+                   editFrom: startEdit, editTo: edit.line + 1});
+    return chunks;
   }
 
   function endOfLineClean(diff, i) {
@@ -546,18 +544,19 @@
     return last.charCodeAt(last.length - 1) == 10;
   }
 
-  function chunkBoundariesAround(diff, n, nInEdit) {
+  function chunkBoundariesAround(chunks, n, nInEdit) {
     var beforeE, afterE, beforeO, afterO;
-    iterateChunks(diff, function(fromOrig, toOrig, fromEdit, toEdit) {
-      var fromLocal = nInEdit ? fromEdit : fromOrig;
-      var toLocal = nInEdit ? toEdit : toOrig;
+    for (var i = 0; i < chunks.length; i++) {
+      var chunk = chunks[i];
+      var fromLocal = nInEdit ? chunk.editFrom : chunk.origFrom;
+      var toLocal = nInEdit ? chunk.editTo : chunk.origTo;
       if (afterE == null) {
-        if (fromLocal > n) { afterE = fromEdit; afterO = fromOrig; }
-        else if (toLocal > n) { afterE = toEdit; afterO = toOrig; }
+        if (fromLocal > n) { afterE = chunk.editFrom; afterO = chunk.origFrom; }
+        else if (toLocal > n) { afterE = chunk.editTo; afterO = chunk.origTo; }
       }
-      if (toLocal <= n) { beforeE = toEdit; beforeO = toOrig; }
-      else if (fromLocal <= n) { beforeE = fromEdit; beforeO = fromOrig; }
-    });
+      if (toLocal <= n) { beforeE = chunk.editTo; beforeO = chunk.origTo; }
+      else if (fromLocal <= n) { beforeE = chunk.editFrom; beforeO = chunk.origFrom; }
+    }
     return {edit: {before: beforeE, after: afterE}, orig: {before: beforeO, after: afterO}};
   }
 
@@ -590,12 +589,13 @@
   function collapseIdenticalStretches(dv, margin) {
     if (typeof margin != "number") margin = 2;
     var lastOrig = dv.orig.firstLine(), lastEdit = dv.edit.firstLine();
-    iterateChunks(dv.diff, function(topOrig, botOrig, _topEdit, botEdit) {
-      var identicalSize = topOrig - margin - lastOrig;
+    for (var i = 0; i < dv.chunks.length; i++) {
+      var chunk = dv.chunks[i];
+      var identicalSize = chunk.origFrom - margin - lastOrig;
       if (identicalSize > margin)
         collapseStretch(dv, lastOrig, lastEdit, identicalSize);
-      lastOrig = botOrig + margin; lastEdit = botEdit + margin;
-    });
+      lastOrig = chunk.origTo + margin; lastEdit = chunk.editTo + margin;
+    }
     var bottomSize = dv.orig.lastLine() + 1 - lastOrig;
     if (bottomSize > margin)
       collapseStretch(dv, lastOrig, lastEdit, bottomSize);