diff --git a/addon/merge/merge.css b/addon/merge/merge.css index 5d24b9bb7fc68d62df4bdae65b07468d03a8f4dd..a6a80e43e075a5e98d4171350e1b3973c53b0a65 100644 --- a/addon/merge/merge.css +++ b/addon/merge/merge.css @@ -96,3 +96,17 @@ .CodeMirror-merge-l-chunk.CodeMirror-merge-r-chunk { background: #dfd; } .CodeMirror-merge-l-chunk-start.CodeMirror-merge-r-chunk-start { border-top: 1px solid #4e4; } .CodeMirror-merge-l-chunk-end.CodeMirror-merge-r-chunk-end { border-bottom: 1px solid #4e4; } + +.CodeMirror-merge-collapsed-widget:before { + content: "(...)"; +} +.CodeMirror-merge-collapsed-widget { + cursor: pointer; + color: #88b; + background: #eef; + border: 1px solid #ddf; + font-size: 90%; + padding: 0 3px; + border-radius: 4px; +} +.CodeMirror-merge-collapsed-line .CodeMirror-gutter-elt { display: none; } diff --git a/addon/merge/merge.js b/addon/merge/merge.js index 14d06903fe61164f017ec39ecd72837c5cdbde8f..ed22b60024a6988125cab5761a2353d7b5a0fbc6 100644 --- a/addon/merge/merge.js +++ b/addon/merge/merge.js @@ -368,8 +368,12 @@ this.options = options; var origLeft = options.origLeft, origRight = options.origRight == null ? options.orig : options.origRight; - if (origLeft && origRight && options.connect == "align") - throw new Error("connect: \"align\" is not supported for three-way merge views"); + if (origLeft && origRight) { + if (options.connect == "align") + throw new Error("connect: \"align\" is not supported for three-way merge views"); + if (options.collapseIdentical) + throw new Error("collapseIdentical option is not supported for three-way merge views"); + } var hasLeft = origLeft != null, hasRight = origRight != null; var panes = 1 + (hasLeft ? 1 : 0) + (hasRight ? 1 : 0); @@ -402,6 +406,9 @@ if (left) left.init(leftPane, origLeft, options); if (right) right.init(rightPane, origRight, options); + if (options.collapseIdentical) + collapseIdenticalStretches(left || right, options.collapseIdentical); + var onResize = function() { if (left) makeConnections(left); if (right) makeConnections(right); @@ -549,6 +556,46 @@ return {edit: {before: beforeE, after: afterE}, orig: {before: beforeO, after: afterO}}; } + function collapseSingle(cm, from, to) { + cm.addLineClass(from, "wrap", "CodeMirror-merge-collapsed-line"); + var widget = document.createElement("span"); + widget.className = "CodeMirror-merge-collapsed-widget"; + widget.title = "Identical text collapsed. Click to expand."; + var mark = cm.markText(Pos(from, 0), Pos(to - 1), { + inclusiveLeft: true, + inclusiveRight: true, + replacedWith: widget, + clearOnEnter: true + }); + function clear() { + mark.clear(); + cm.removeLineClass(from, "wrap", "CodeMirror-merge-collapsed-line"); + } + widget.addEventListener("click", clear); + return {mark: mark, clear: clear}; + } + + function collapseStretch(dv, origStart, editStart, size) { + var mOrig = collapseSingle(dv.orig, origStart, origStart + size); + var mEdit = collapseSingle(dv.edit, editStart, editStart + size); + mOrig.mark.on("clear", function() { mEdit.clear(); }); + mEdit.mark.on("clear", function() { mOrig.clear(); }); + } + + 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; + if (identicalSize > margin) + collapseStretch(dv, lastOrig, lastEdit, identicalSize); + lastOrig = botOrig + margin; lastEdit = botEdit + margin; + }); + var bottomSize = dv.orig.lastLine() + 1 - lastOrig; + if (bottomSize > margin) + collapseStretch(dv, lastOrig, lastEdit, bottomSize); + } + // General utilities function elt(tag, content, className, style) {