github.com/jancarloviray/community@v0.41.1-0.20170124221257-33a66c87cf2f/app/public/codemirror/addon/merge/merge.js (about) 1 // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 // Distributed under an MIT license: http://codemirror.net/LICENSE 3 4 // declare global: diff_match_patch, DIFF_INSERT, DIFF_DELETE, DIFF_EQUAL 5 6 (function(mod) { 7 if (typeof exports == "object" && typeof module == "object") // CommonJS 8 mod(require("../../lib/codemirror")); // Note non-packaged dependency diff_match_patch 9 else if (typeof define == "function" && define.amd) // AMD 10 define(["../../lib/codemirror", "diff_match_patch"], mod); 11 else // Plain browser env 12 mod(CodeMirror); 13 })(function(CodeMirror) { 14 "use strict"; 15 var Pos = CodeMirror.Pos; 16 var svgNS = "http://www.w3.org/2000/svg"; 17 18 function DiffView(mv, type) { 19 this.mv = mv; 20 this.type = type; 21 this.classes = type == "left" 22 ? {chunk: "CodeMirror-merge-l-chunk", 23 start: "CodeMirror-merge-l-chunk-start", 24 end: "CodeMirror-merge-l-chunk-end", 25 insert: "CodeMirror-merge-l-inserted", 26 del: "CodeMirror-merge-l-deleted", 27 connect: "CodeMirror-merge-l-connect"} 28 : {chunk: "CodeMirror-merge-r-chunk", 29 start: "CodeMirror-merge-r-chunk-start", 30 end: "CodeMirror-merge-r-chunk-end", 31 insert: "CodeMirror-merge-r-inserted", 32 del: "CodeMirror-merge-r-deleted", 33 connect: "CodeMirror-merge-r-connect"}; 34 } 35 36 DiffView.prototype = { 37 constructor: DiffView, 38 init: function(pane, orig, options) { 39 this.edit = this.mv.edit; 40 (this.edit.state.diffViews || (this.edit.state.diffViews = [])).push(this); 41 this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: !this.mv.options.allowEditingOriginals}, copyObj(options))); 42 this.orig.state.diffViews = [this]; 43 44 this.diff = getDiff(asString(orig), asString(options.value)); 45 this.chunks = getChunks(this.diff); 46 this.diffOutOfDate = this.dealigned = false; 47 48 this.showDifferences = options.showDifferences !== false; 49 this.forceUpdate = registerUpdate(this); 50 setScrollLock(this, true, false); 51 registerScroll(this); 52 }, 53 setShowDifferences: function(val) { 54 val = val !== false; 55 if (val != this.showDifferences) { 56 this.showDifferences = val; 57 this.forceUpdate("full"); 58 } 59 } 60 }; 61 62 function ensureDiff(dv) { 63 if (dv.diffOutOfDate) { 64 dv.diff = getDiff(dv.orig.getValue(), dv.edit.getValue()); 65 dv.chunks = getChunks(dv.diff); 66 dv.diffOutOfDate = false; 67 CodeMirror.signal(dv.edit, "updateDiff", dv.diff); 68 } 69 } 70 71 var updating = false; 72 function registerUpdate(dv) { 73 var edit = {from: 0, to: 0, marked: []}; 74 var orig = {from: 0, to: 0, marked: []}; 75 var debounceChange, updatingFast = false; 76 function update(mode) { 77 updating = true; 78 updatingFast = false; 79 if (mode == "full") { 80 if (dv.svg) clear(dv.svg); 81 if (dv.copyButtons) clear(dv.copyButtons); 82 clearMarks(dv.edit, edit.marked, dv.classes); 83 clearMarks(dv.orig, orig.marked, dv.classes); 84 edit.from = edit.to = orig.from = orig.to = 0; 85 } 86 ensureDiff(dv); 87 if (dv.showDifferences) { 88 updateMarks(dv.edit, dv.diff, edit, DIFF_INSERT, dv.classes); 89 updateMarks(dv.orig, dv.diff, orig, DIFF_DELETE, dv.classes); 90 } 91 makeConnections(dv); 92 93 if (dv.mv.options.connect == "align") 94 alignChunks(dv); 95 updating = false; 96 } 97 function setDealign(fast) { 98 if (updating) return; 99 dv.dealigned = true; 100 set(fast); 101 } 102 function set(fast) { 103 if (updating || updatingFast) return; 104 clearTimeout(debounceChange); 105 if (fast === true) updatingFast = true; 106 debounceChange = setTimeout(update, fast === true ? 20 : 250); 107 } 108 function change(_cm, change) { 109 if (!dv.diffOutOfDate) { 110 dv.diffOutOfDate = true; 111 edit.from = edit.to = orig.from = orig.to = 0; 112 } 113 // Update faster when a line was added/removed 114 setDealign(change.text.length - 1 != change.to.line - change.from.line); 115 } 116 dv.edit.on("change", change); 117 dv.orig.on("change", change); 118 dv.edit.on("markerAdded", setDealign); 119 dv.edit.on("markerCleared", setDealign); 120 dv.orig.on("markerAdded", setDealign); 121 dv.orig.on("markerCleared", setDealign); 122 dv.edit.on("viewportChange", function() { set(false); }); 123 dv.orig.on("viewportChange", function() { set(false); }); 124 update(); 125 return update; 126 } 127 128 function registerScroll(dv) { 129 dv.edit.on("scroll", function() { 130 syncScroll(dv, DIFF_INSERT) && makeConnections(dv); 131 }); 132 dv.orig.on("scroll", function() { 133 syncScroll(dv, DIFF_DELETE) && makeConnections(dv); 134 }); 135 } 136 137 function syncScroll(dv, type) { 138 // Change handler will do a refresh after a timeout when diff is out of date 139 if (dv.diffOutOfDate) return false; 140 if (!dv.lockScroll) return true; 141 var editor, other, now = +new Date; 142 if (type == DIFF_INSERT) { editor = dv.edit; other = dv.orig; } 143 else { editor = dv.orig; other = dv.edit; } 144 // Don't take action if the position of this editor was recently set 145 // (to prevent feedback loops) 146 if (editor.state.scrollSetBy == dv && (editor.state.scrollSetAt || 0) + 50 > now) return false; 147 148 var sInfo = editor.getScrollInfo(); 149 if (dv.mv.options.connect == "align") { 150 targetPos = sInfo.top; 151 } else { 152 var halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen; 153 var mid = editor.lineAtHeight(midY, "local"); 154 var around = chunkBoundariesAround(dv.chunks, mid, type == DIFF_INSERT); 155 var off = getOffsets(editor, type == DIFF_INSERT ? around.edit : around.orig); 156 var offOther = getOffsets(other, type == DIFF_INSERT ? around.orig : around.edit); 157 var ratio = (midY - off.top) / (off.bot - off.top); 158 var targetPos = (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top); 159 160 var botDist, mix; 161 // Some careful tweaking to make sure no space is left out of view 162 // when scrolling to top or bottom. 163 if (targetPos > sInfo.top && (mix = sInfo.top / halfScreen) < 1) { 164 targetPos = targetPos * mix + sInfo.top * (1 - mix); 165 } else if ((botDist = sInfo.height - sInfo.clientHeight - sInfo.top) < halfScreen) { 166 var otherInfo = other.getScrollInfo(); 167 var botDistOther = otherInfo.height - otherInfo.clientHeight - targetPos; 168 if (botDistOther > botDist && (mix = botDist / halfScreen) < 1) 169 targetPos = targetPos * mix + (otherInfo.height - otherInfo.clientHeight - botDist) * (1 - mix); 170 } 171 } 172 173 other.scrollTo(sInfo.left, targetPos); 174 other.state.scrollSetAt = now; 175 other.state.scrollSetBy = dv; 176 return true; 177 } 178 179 function getOffsets(editor, around) { 180 var bot = around.after; 181 if (bot == null) bot = editor.lastLine() + 1; 182 return {top: editor.heightAtLine(around.before || 0, "local"), 183 bot: editor.heightAtLine(bot, "local")}; 184 } 185 186 function setScrollLock(dv, val, action) { 187 dv.lockScroll = val; 188 if (val && action != false) syncScroll(dv, DIFF_INSERT) && makeConnections(dv); 189 dv.lockButton.innerHTML = val ? "\u21db\u21da" : "\u21db \u21da"; 190 } 191 192 // Updating the marks for editor content 193 194 function clearMarks(editor, arr, classes) { 195 for (var i = 0; i < arr.length; ++i) { 196 var mark = arr[i]; 197 if (mark instanceof CodeMirror.TextMarker) { 198 mark.clear(); 199 } else if (mark.parent) { 200 editor.removeLineClass(mark, "background", classes.chunk); 201 editor.removeLineClass(mark, "background", classes.start); 202 editor.removeLineClass(mark, "background", classes.end); 203 } 204 } 205 arr.length = 0; 206 } 207 208 // FIXME maybe add a margin around viewport to prevent too many updates 209 function updateMarks(editor, diff, state, type, classes) { 210 var vp = editor.getViewport(); 211 editor.operation(function() { 212 if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) { 213 clearMarks(editor, state.marked, classes); 214 markChanges(editor, diff, type, state.marked, vp.from, vp.to, classes); 215 state.from = vp.from; state.to = vp.to; 216 } else { 217 if (vp.from < state.from) { 218 markChanges(editor, diff, type, state.marked, vp.from, state.from, classes); 219 state.from = vp.from; 220 } 221 if (vp.to > state.to) { 222 markChanges(editor, diff, type, state.marked, state.to, vp.to, classes); 223 state.to = vp.to; 224 } 225 } 226 }); 227 } 228 229 function markChanges(editor, diff, type, marks, from, to, classes) { 230 var pos = Pos(0, 0); 231 var top = Pos(from, 0), bot = editor.clipPos(Pos(to - 1)); 232 var cls = type == DIFF_DELETE ? classes.del : classes.insert; 233 function markChunk(start, end) { 234 var bfrom = Math.max(from, start), bto = Math.min(to, end); 235 for (var i = bfrom; i < bto; ++i) { 236 var line = editor.addLineClass(i, "background", classes.chunk); 237 if (i == start) editor.addLineClass(line, "background", classes.start); 238 if (i == end - 1) editor.addLineClass(line, "background", classes.end); 239 marks.push(line); 240 } 241 // When the chunk is empty, make sure a horizontal line shows up 242 if (start == end && bfrom == end && bto == end) { 243 if (bfrom) 244 marks.push(editor.addLineClass(bfrom - 1, "background", classes.end)); 245 else 246 marks.push(editor.addLineClass(bfrom, "background", classes.start)); 247 } 248 } 249 250 var chunkStart = 0; 251 for (var i = 0; i < diff.length; ++i) { 252 var part = diff[i], tp = part[0], str = part[1]; 253 if (tp == DIFF_EQUAL) { 254 var cleanFrom = pos.line + (startOfLineClean(diff, i) ? 0 : 1); 255 moveOver(pos, str); 256 var cleanTo = pos.line + (endOfLineClean(diff, i) ? 1 : 0); 257 if (cleanTo > cleanFrom) { 258 if (i) markChunk(chunkStart, cleanFrom); 259 chunkStart = cleanTo; 260 } 261 } else { 262 if (tp == type) { 263 var end = moveOver(pos, str, true); 264 var a = posMax(top, pos), b = posMin(bot, end); 265 if (!posEq(a, b)) 266 marks.push(editor.markText(a, b, {className: cls})); 267 pos = end; 268 } 269 } 270 } 271 if (chunkStart <= pos.line) markChunk(chunkStart, pos.line + 1); 272 } 273 274 // Updating the gap between editor and original 275 276 function makeConnections(dv) { 277 if (!dv.showDifferences) return; 278 279 if (dv.svg) { 280 clear(dv.svg); 281 var w = dv.gap.offsetWidth; 282 attrs(dv.svg, "width", w, "height", dv.gap.offsetHeight); 283 } 284 if (dv.copyButtons) clear(dv.copyButtons); 285 286 var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport(); 287 var sTopEdit = dv.edit.getScrollInfo().top, sTopOrig = dv.orig.getScrollInfo().top; 288 for (var i = 0; i < dv.chunks.length; i++) { 289 var ch = dv.chunks[i]; 290 if (ch.editFrom <= vpEdit.to && ch.editTo >= vpEdit.from && 291 ch.origFrom <= vpOrig.to && ch.origTo >= vpOrig.from) 292 drawConnectorsForChunk(dv, ch, sTopOrig, sTopEdit, w); 293 } 294 } 295 296 function getMatchingOrigLine(editLine, chunks) { 297 var editStart = 0, origStart = 0; 298 for (var i = 0; i < chunks.length; i++) { 299 var chunk = chunks[i]; 300 if (chunk.editTo > editLine && chunk.editFrom <= editLine) return null; 301 if (chunk.editFrom > editLine) break; 302 editStart = chunk.editTo; 303 origStart = chunk.origTo; 304 } 305 return origStart + (editLine - editStart); 306 } 307 308 function findAlignedLines(dv, other) { 309 var linesToAlign = []; 310 for (var i = 0; i < dv.chunks.length; i++) { 311 var chunk = dv.chunks[i]; 312 linesToAlign.push([chunk.origTo, chunk.editTo, other ? getMatchingOrigLine(chunk.editTo, other.chunks) : null]); 313 } 314 if (other) { 315 for (var i = 0; i < other.chunks.length; i++) { 316 var chunk = other.chunks[i]; 317 for (var j = 0; j < linesToAlign.length; j++) { 318 var align = linesToAlign[j]; 319 if (align[1] == chunk.editTo) { 320 j = -1; 321 break; 322 } else if (align[1] > chunk.editTo) { 323 break; 324 } 325 } 326 if (j > -1) 327 linesToAlign.splice(j - 1, 0, [getMatchingOrigLine(chunk.editTo, dv.chunks), chunk.editTo, chunk.origTo]); 328 } 329 } 330 return linesToAlign; 331 } 332 333 function alignChunks(dv, force) { 334 if (!dv.dealigned && !force) return; 335 if (!dv.orig.curOp) return dv.orig.operation(function() { 336 alignChunks(dv, force); 337 }); 338 339 dv.dealigned = false; 340 var other = dv.mv.left == dv ? dv.mv.right : dv.mv.left; 341 if (other) { 342 ensureDiff(other); 343 other.dealigned = false; 344 } 345 var linesToAlign = findAlignedLines(dv, other); 346 347 // Clear old aligners 348 var aligners = dv.mv.aligners; 349 for (var i = 0; i < aligners.length; i++) 350 aligners[i].clear(); 351 aligners.length = 0; 352 353 var cm = [dv.orig, dv.edit], scroll = []; 354 if (other) cm.push(other.orig); 355 for (var i = 0; i < cm.length; i++) 356 scroll.push(cm[i].getScrollInfo().top); 357 358 for (var ln = 0; ln < linesToAlign.length; ln++) 359 alignLines(cm, linesToAlign[ln], aligners); 360 361 for (var i = 0; i < cm.length; i++) 362 cm[i].scrollTo(null, scroll[i]); 363 } 364 365 function alignLines(cm, lines, aligners) { 366 var maxOffset = 0, offset = []; 367 for (var i = 0; i < cm.length; i++) if (lines[i] != null) { 368 var off = cm[i].heightAtLine(lines[i], "local"); 369 offset[i] = off; 370 maxOffset = Math.max(maxOffset, off); 371 } 372 for (var i = 0; i < cm.length; i++) if (lines[i] != null) { 373 var diff = maxOffset - offset[i]; 374 if (diff > 1) 375 aligners.push(padAbove(cm[i], lines[i], diff)); 376 } 377 } 378 379 function padAbove(cm, line, size) { 380 var above = true; 381 if (line > cm.lastLine()) { 382 line--; 383 above = false; 384 } 385 var elt = document.createElement("div"); 386 elt.className = "CodeMirror-merge-spacer"; 387 elt.style.height = size + "px"; elt.style.minWidth = "1px"; 388 return cm.addLineWidget(line, elt, {height: size, above: above}); 389 } 390 391 function drawConnectorsForChunk(dv, chunk, sTopOrig, sTopEdit, w) { 392 var flip = dv.type == "left"; 393 var top = dv.orig.heightAtLine(chunk.origFrom, "local") - sTopOrig; 394 if (dv.svg) { 395 var topLpx = top; 396 var topRpx = dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit; 397 if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; } 398 var botLpx = dv.orig.heightAtLine(chunk.origTo, "local") - sTopOrig; 399 var botRpx = dv.edit.heightAtLine(chunk.editTo, "local") - sTopEdit; 400 if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; } 401 var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx; 402 var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx; 403 attrs(dv.svg.appendChild(document.createElementNS(svgNS, "path")), 404 "d", "M -1 " + topRpx + curveTop + " L " + (w + 2) + " " + botLpx + curveBot + " z", 405 "class", dv.classes.connect); 406 } 407 if (dv.copyButtons) { 408 var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc", 409 "CodeMirror-merge-copy")); 410 var editOriginals = dv.mv.options.allowEditingOriginals; 411 copy.title = editOriginals ? "Push to left" : "Revert chunk"; 412 copy.chunk = chunk; 413 copy.style.top = top + "px"; 414 415 if (editOriginals) { 416 var topReverse = dv.orig.heightAtLine(chunk.editFrom, "local") - sTopEdit; 417 var copyReverse = dv.copyButtons.appendChild(elt("div", dv.type == "right" ? "\u21dd" : "\u21dc", 418 "CodeMirror-merge-copy-reverse")); 419 copyReverse.title = "Push to right"; 420 copyReverse.chunk = {editFrom: chunk.origFrom, editTo: chunk.origTo, 421 origFrom: chunk.editFrom, origTo: chunk.editTo}; 422 copyReverse.style.top = topReverse + "px"; 423 dv.type == "right" ? copyReverse.style.left = "2px" : copyReverse.style.right = "2px"; 424 } 425 } 426 } 427 428 function copyChunk(dv, to, from, chunk) { 429 if (dv.diffOutOfDate) return; 430 to.replaceRange(from.getRange(Pos(chunk.origFrom, 0), Pos(chunk.origTo, 0)), 431 Pos(chunk.editFrom, 0), Pos(chunk.editTo, 0)); 432 } 433 434 // Merge view, containing 0, 1, or 2 diff views. 435 436 var MergeView = CodeMirror.MergeView = function(node, options) { 437 if (!(this instanceof MergeView)) return new MergeView(node, options); 438 439 this.options = options; 440 var origLeft = options.origLeft, origRight = options.origRight == null ? options.orig : options.origRight; 441 442 var hasLeft = origLeft != null, hasRight = origRight != null; 443 var panes = 1 + (hasLeft ? 1 : 0) + (hasRight ? 1 : 0); 444 var wrap = [], left = this.left = null, right = this.right = null; 445 var self = this; 446 447 if (hasLeft) { 448 left = this.left = new DiffView(this, "left"); 449 var leftPane = elt("div", null, "CodeMirror-merge-pane"); 450 wrap.push(leftPane); 451 wrap.push(buildGap(left)); 452 } 453 454 var editPane = elt("div", null, "CodeMirror-merge-pane"); 455 wrap.push(editPane); 456 457 if (hasRight) { 458 right = this.right = new DiffView(this, "right"); 459 wrap.push(buildGap(right)); 460 var rightPane = elt("div", null, "CodeMirror-merge-pane"); 461 wrap.push(rightPane); 462 } 463 464 (hasRight ? rightPane : editPane).className += " CodeMirror-merge-pane-rightmost"; 465 466 wrap.push(elt("div", null, null, "height: 0; clear: both;")); 467 468 var wrapElt = this.wrap = node.appendChild(elt("div", wrap, "CodeMirror-merge CodeMirror-merge-" + panes + "pane")); 469 this.edit = CodeMirror(editPane, copyObj(options)); 470 471 if (left) left.init(leftPane, origLeft, options); 472 if (right) right.init(rightPane, origRight, options); 473 474 if (options.collapseIdentical) 475 this.editor().operation(function() { 476 collapseIdenticalStretches(self, options.collapseIdentical); 477 }); 478 if (options.connect == "align") { 479 this.aligners = []; 480 alignChunks(this.left || this.right, true); 481 } 482 483 var onResize = function() { 484 if (left) makeConnections(left); 485 if (right) makeConnections(right); 486 }; 487 CodeMirror.on(window, "resize", onResize); 488 var resizeInterval = setInterval(function() { 489 for (var p = wrapElt.parentNode; p && p != document.body; p = p.parentNode) {} 490 if (!p) { clearInterval(resizeInterval); CodeMirror.off(window, "resize", onResize); } 491 }, 5000); 492 }; 493 494 function buildGap(dv) { 495 var lock = dv.lockButton = elt("div", null, "CodeMirror-merge-scrolllock"); 496 lock.title = "Toggle locked scrolling"; 497 var lockWrap = elt("div", [lock], "CodeMirror-merge-scrolllock-wrap"); 498 CodeMirror.on(lock, "click", function() { setScrollLock(dv, !dv.lockScroll); }); 499 var gapElts = [lockWrap]; 500 if (dv.mv.options.revertButtons !== false) { 501 dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type); 502 CodeMirror.on(dv.copyButtons, "click", function(e) { 503 var node = e.target || e.srcElement; 504 if (!node.chunk) return; 505 if (node.className == "CodeMirror-merge-copy-reverse") { 506 copyChunk(dv, dv.orig, dv.edit, node.chunk); 507 return; 508 } 509 copyChunk(dv, dv.edit, dv.orig, node.chunk); 510 }); 511 gapElts.unshift(dv.copyButtons); 512 } 513 if (dv.mv.options.connect != "align") { 514 var svg = document.createElementNS && document.createElementNS(svgNS, "svg"); 515 if (svg && !svg.createSVGRect) svg = null; 516 dv.svg = svg; 517 if (svg) gapElts.push(svg); 518 } 519 520 return dv.gap = elt("div", gapElts, "CodeMirror-merge-gap"); 521 } 522 523 MergeView.prototype = { 524 constuctor: MergeView, 525 editor: function() { return this.edit; }, 526 rightOriginal: function() { return this.right && this.right.orig; }, 527 leftOriginal: function() { return this.left && this.left.orig; }, 528 setShowDifferences: function(val) { 529 if (this.right) this.right.setShowDifferences(val); 530 if (this.left) this.left.setShowDifferences(val); 531 }, 532 rightChunks: function() { 533 if (this.right) { ensureDiff(this.right); return this.right.chunks; } 534 }, 535 leftChunks: function() { 536 if (this.left) { ensureDiff(this.left); return this.left.chunks; } 537 } 538 }; 539 540 function asString(obj) { 541 if (typeof obj == "string") return obj; 542 else return obj.getValue(); 543 } 544 545 // Operations on diffs 546 547 var dmp = new diff_match_patch(); 548 function getDiff(a, b) { 549 var diff = dmp.diff_main(a, b); 550 dmp.diff_cleanupSemantic(diff); 551 // The library sometimes leaves in empty parts, which confuse the algorithm 552 for (var i = 0; i < diff.length; ++i) { 553 var part = diff[i]; 554 if (!part[1]) { 555 diff.splice(i--, 1); 556 } else if (i && diff[i - 1][0] == part[0]) { 557 diff.splice(i--, 1); 558 diff[i][1] += part[1]; 559 } 560 } 561 return diff; 562 } 563 564 function getChunks(diff) { 565 var chunks = []; 566 var startEdit = 0, startOrig = 0; 567 var edit = Pos(0, 0), orig = Pos(0, 0); 568 for (var i = 0; i < diff.length; ++i) { 569 var part = diff[i], tp = part[0]; 570 if (tp == DIFF_EQUAL) { 571 var startOff = startOfLineClean(diff, i) ? 0 : 1; 572 var cleanFromEdit = edit.line + startOff, cleanFromOrig = orig.line + startOff; 573 moveOver(edit, part[1], null, orig); 574 var endOff = endOfLineClean(diff, i) ? 1 : 0; 575 var cleanToEdit = edit.line + endOff, cleanToOrig = orig.line + endOff; 576 if (cleanToEdit > cleanFromEdit) { 577 if (i) chunks.push({origFrom: startOrig, origTo: cleanFromOrig, 578 editFrom: startEdit, editTo: cleanFromEdit}); 579 startEdit = cleanToEdit; startOrig = cleanToOrig; 580 } 581 } else { 582 moveOver(tp == DIFF_INSERT ? edit : orig, part[1]); 583 } 584 } 585 if (startEdit <= edit.line || startOrig <= orig.line) 586 chunks.push({origFrom: startOrig, origTo: orig.line + 1, 587 editFrom: startEdit, editTo: edit.line + 1}); 588 return chunks; 589 } 590 591 function endOfLineClean(diff, i) { 592 if (i == diff.length - 1) return true; 593 var next = diff[i + 1][1]; 594 if (next.length == 1 || next.charCodeAt(0) != 10) return false; 595 if (i == diff.length - 2) return true; 596 next = diff[i + 2][1]; 597 return next.length > 1 && next.charCodeAt(0) == 10; 598 } 599 600 function startOfLineClean(diff, i) { 601 if (i == 0) return true; 602 var last = diff[i - 1][1]; 603 if (last.charCodeAt(last.length - 1) != 10) return false; 604 if (i == 1) return true; 605 last = diff[i - 2][1]; 606 return last.charCodeAt(last.length - 1) == 10; 607 } 608 609 function chunkBoundariesAround(chunks, n, nInEdit) { 610 var beforeE, afterE, beforeO, afterO; 611 for (var i = 0; i < chunks.length; i++) { 612 var chunk = chunks[i]; 613 var fromLocal = nInEdit ? chunk.editFrom : chunk.origFrom; 614 var toLocal = nInEdit ? chunk.editTo : chunk.origTo; 615 if (afterE == null) { 616 if (fromLocal > n) { afterE = chunk.editFrom; afterO = chunk.origFrom; } 617 else if (toLocal > n) { afterE = chunk.editTo; afterO = chunk.origTo; } 618 } 619 if (toLocal <= n) { beforeE = chunk.editTo; beforeO = chunk.origTo; } 620 else if (fromLocal <= n) { beforeE = chunk.editFrom; beforeO = chunk.origFrom; } 621 } 622 return {edit: {before: beforeE, after: afterE}, orig: {before: beforeO, after: afterO}}; 623 } 624 625 function collapseSingle(cm, from, to) { 626 cm.addLineClass(from, "wrap", "CodeMirror-merge-collapsed-line"); 627 var widget = document.createElement("span"); 628 widget.className = "CodeMirror-merge-collapsed-widget"; 629 widget.title = "Identical text collapsed. Click to expand."; 630 var mark = cm.markText(Pos(from, 0), Pos(to - 1), { 631 inclusiveLeft: true, 632 inclusiveRight: true, 633 replacedWith: widget, 634 clearOnEnter: true 635 }); 636 function clear() { 637 mark.clear(); 638 cm.removeLineClass(from, "wrap", "CodeMirror-merge-collapsed-line"); 639 } 640 CodeMirror.on(widget, "click", clear); 641 return {mark: mark, clear: clear}; 642 } 643 644 function collapseStretch(size, editors) { 645 var marks = []; 646 function clear() { 647 for (var i = 0; i < marks.length; i++) marks[i].clear(); 648 } 649 for (var i = 0; i < editors.length; i++) { 650 var editor = editors[i]; 651 var mark = collapseSingle(editor.cm, editor.line, editor.line + size); 652 marks.push(mark); 653 mark.mark.on("clear", clear); 654 } 655 return marks[0].mark; 656 } 657 658 function unclearNearChunks(dv, margin, off, clear) { 659 for (var i = 0; i < dv.chunks.length; i++) { 660 var chunk = dv.chunks[i]; 661 for (var l = chunk.editFrom - margin; l < chunk.editTo + margin; l++) { 662 var pos = l + off; 663 if (pos >= 0 && pos < clear.length) clear[pos] = false; 664 } 665 } 666 } 667 668 function collapseIdenticalStretches(mv, margin) { 669 if (typeof margin != "number") margin = 2; 670 var clear = [], edit = mv.editor(), off = edit.firstLine(); 671 for (var l = off, e = edit.lastLine(); l <= e; l++) clear.push(true); 672 if (mv.left) unclearNearChunks(mv.left, margin, off, clear); 673 if (mv.right) unclearNearChunks(mv.right, margin, off, clear); 674 675 for (var i = 0; i < clear.length; i++) { 676 if (clear[i]) { 677 var line = i + off; 678 for (var size = 1; i < clear.length - 1 && clear[i + 1]; i++, size++) {} 679 if (size > margin) { 680 var editors = [{line: line, cm: edit}]; 681 if (mv.left) editors.push({line: getMatchingOrigLine(line, mv.left.chunks), cm: mv.left.orig}); 682 if (mv.right) editors.push({line: getMatchingOrigLine(line, mv.right.chunks), cm: mv.right.orig}); 683 var mark = collapseStretch(size, editors); 684 if (mv.options.onCollapse) mv.options.onCollapse(mv, line, size, mark); 685 } 686 } 687 } 688 } 689 690 // General utilities 691 692 function elt(tag, content, className, style) { 693 var e = document.createElement(tag); 694 if (className) e.className = className; 695 if (style) e.style.cssText = style; 696 if (typeof content == "string") e.appendChild(document.createTextNode(content)); 697 else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); 698 return e; 699 } 700 701 function clear(node) { 702 for (var count = node.childNodes.length; count > 0; --count) 703 node.removeChild(node.firstChild); 704 } 705 706 function attrs(elt) { 707 for (var i = 1; i < arguments.length; i += 2) 708 elt.setAttribute(arguments[i], arguments[i+1]); 709 } 710 711 function copyObj(obj, target) { 712 if (!target) target = {}; 713 for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop]; 714 return target; 715 } 716 717 function moveOver(pos, str, copy, other) { 718 var out = copy ? Pos(pos.line, pos.ch) : pos, at = 0; 719 for (;;) { 720 var nl = str.indexOf("\n", at); 721 if (nl == -1) break; 722 ++out.line; 723 if (other) ++other.line; 724 at = nl + 1; 725 } 726 out.ch = (at ? 0 : out.ch) + (str.length - at); 727 if (other) other.ch = (at ? 0 : other.ch) + (str.length - at); 728 return out; 729 } 730 731 function posMin(a, b) { return (a.line - b.line || a.ch - b.ch) < 0 ? a : b; } 732 function posMax(a, b) { return (a.line - b.line || a.ch - b.ch) > 0 ? a : b; } 733 function posEq(a, b) { return a.line == b.line && a.ch == b.ch; } 734 735 function findPrevDiff(chunks, start, isOrig) { 736 for (var i = chunks.length - 1; i >= 0; i--) { 737 var chunk = chunks[i]; 738 var to = (isOrig ? chunk.origTo : chunk.editTo) - 1; 739 if (to < start) return to; 740 } 741 } 742 743 function findNextDiff(chunks, start, isOrig) { 744 for (var i = 0; i < chunks.length; i++) { 745 var chunk = chunks[i]; 746 var from = (isOrig ? chunk.origFrom : chunk.editFrom); 747 if (from > start) return from; 748 } 749 } 750 751 function goNearbyDiff(cm, dir) { 752 var found = null, views = cm.state.diffViews, line = cm.getCursor().line; 753 if (views) for (var i = 0; i < views.length; i++) { 754 var dv = views[i], isOrig = cm == dv.orig; 755 ensureDiff(dv); 756 var pos = dir < 0 ? findPrevDiff(dv.chunks, line, isOrig) : findNextDiff(dv.chunks, line, isOrig); 757 if (pos != null && (found == null || (dir < 0 ? pos > found : pos < found))) 758 found = pos; 759 } 760 if (found != null) 761 cm.setCursor(found, 0); 762 else 763 return CodeMirror.Pass; 764 } 765 766 CodeMirror.commands.goNextDiff = function(cm) { 767 return goNearbyDiff(cm, 1); 768 }; 769 CodeMirror.commands.goPrevDiff = function(cm) { 770 return goNearbyDiff(cm, -1); 771 }; 772 });