File modules/editor/codemirror/addon/merge/merge.min.js

Last commit: Tue May 22 22:39:53 2018 +0200	Jan Dankert	Fix für PHP 7.2: 'Object' darf nun nicht mehr als Klassennamen verwendet werden. AUCH NICHT IN EINEM NAMESPACE! WTF, wozu habe ich das in einen verfickten Namespace gepackt? Wozu soll der sonst da sein??? Amateure. Daher nun notgedrungen unbenannt in 'BaseObject'.
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 if (this.mv.options.connect == "align") { 43 if (!this.edit.state.trackAlignable) this.edit.state.trackAlignable = new TrackAlignable(this.edit) 44 this.orig.state.trackAlignable = new TrackAlignable(this.orig) 45 } 46 47 this.orig.state.diffViews = [this]; 48 var classLocation = options.chunkClassLocation || "background"; 49 if (Object.prototype.toString.call(classLocation) != "[object Array]") classLocation = [classLocation] 50 this.classes.classLocation = classLocation 51 52 this.diff = getDiff(asString(orig), asString(options.value), this.mv.options.ignoreWhitespace); 53 this.chunks = getChunks(this.diff); 54 this.diffOutOfDate = this.dealigned = false; 55 this.needsScrollSync = null 56 57 this.showDifferences = options.showDifferences !== false; 58 }, 59 registerEvents: function(otherDv) { 60 this.forceUpdate = registerUpdate(this); 61 setScrollLock(this, true, false); 62 registerScroll(this, otherDv); 63 }, 64 setShowDifferences: function(val) { 65 val = val !== false; 66 if (val != this.showDifferences) { 67 this.showDifferences = val; 68 this.forceUpdate("full"); 69 } 70 } 71 }; 72 73 function ensureDiff(dv) { 74 if (dv.diffOutOfDate) { 75 dv.diff = getDiff(dv.orig.getValue(), dv.edit.getValue(), dv.mv.options.ignoreWhitespace); 76 dv.chunks = getChunks(dv.diff); 77 dv.diffOutOfDate = false; 78 CodeMirror.signal(dv.edit, "updateDiff", dv.diff); 79 } 80 } 81 82 var updating = false; 83 function registerUpdate(dv) { 84 var edit = {from: 0, to: 0, marked: []}; 85 var orig = {from: 0, to: 0, marked: []}; 86 var debounceChange, updatingFast = false; 87 function update(mode) { 88 updating = true; 89 updatingFast = false; 90 if (mode == "full") { 91 if (dv.svg) clear(dv.svg); 92 if (dv.copyButtons) clear(dv.copyButtons); 93 clearMarks(dv.edit, edit.marked, dv.classes); 94 clearMarks(dv.orig, orig.marked, dv.classes); 95 edit.from = edit.to = orig.from = orig.to = 0; 96 } 97 ensureDiff(dv); 98 if (dv.showDifferences) { 99 updateMarks(dv.edit, dv.diff, edit, DIFF_INSERT, dv.classes); 100 updateMarks(dv.orig, dv.diff, orig, DIFF_DELETE, dv.classes); 101 } 102 103 if (dv.mv.options.connect == "align") 104 alignChunks(dv); 105 makeConnections(dv); 106 if (dv.needsScrollSync != null) syncScroll(dv, dv.needsScrollSync) 107 108 updating = false; 109 } 110 function setDealign(fast) { 111 if (updating) return; 112 dv.dealigned = true; 113 set(fast); 114 } 115 function set(fast) { 116 if (updating || updatingFast) return; 117 clearTimeout(debounceChange); 118 if (fast === true) updatingFast = true; 119 debounceChange = setTimeout(update, fast === true ? 20 : 250); 120 } 121 function change(_cm, change) { 122 if (!dv.diffOutOfDate) { 123 dv.diffOutOfDate = true; 124 edit.from = edit.to = orig.from = orig.to = 0; 125 } 126 // Update faster when a line was added/removed 127 setDealign(change.text.length - 1 != change.to.line - change.from.line); 128 } 129 function swapDoc() { 130 dv.diffOutOfDate = true; 131 dv.dealigned = true; 132 update("full"); 133 } 134 dv.edit.on("change", change); 135 dv.orig.on("change", change); 136 dv.edit.on("swapDoc", swapDoc); 137 dv.orig.on("swapDoc", swapDoc); 138 if (dv.mv.options.connect == "align") { 139 CodeMirror.on(dv.edit.state.trackAlignable, "realign", setDealign) 140 CodeMirror.on(dv.orig.state.trackAlignable, "realign", setDealign) 141 } 142 dv.edit.on("viewportChange", function() { set(false); }); 143 dv.orig.on("viewportChange", function() { set(false); }); 144 update(); 145 return update; 146 } 147 148 function registerScroll(dv, otherDv) { 149 dv.edit.on("scroll", function() { 150 syncScroll(dv, true) && makeConnections(dv); 151 }); 152 dv.orig.on("scroll", function() { 153 syncScroll(dv, false) && makeConnections(dv); 154 if (otherDv) syncScroll(otherDv, true) && makeConnections(otherDv); 155 }); 156 } 157 158 function syncScroll(dv, toOrig) { 159 // Change handler will do a refresh after a timeout when diff is out of date 160 if (dv.diffOutOfDate) { 161 if (dv.lockScroll && dv.needsScrollSync == null) dv.needsScrollSync = toOrig 162 return false 163 } 164 dv.needsScrollSync = null 165 if (!dv.lockScroll) return true; 166 var editor, other, now = +new Date; 167 if (toOrig) { editor = dv.edit; other = dv.orig; } 168 else { editor = dv.orig; other = dv.edit; } 169 // Don't take action if the position of this editor was recently set 170 // (to prevent feedback loops) 171 if (editor.state.scrollSetBy == dv && (editor.state.scrollSetAt || 0) + 250 > now) return false; 172 173 var sInfo = editor.getScrollInfo(); 174 if (dv.mv.options.connect == "align") { 175 targetPos = sInfo.top; 176 } else { 177 var halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen; 178 var mid = editor.lineAtHeight(midY, "local"); 179 var around = chunkBoundariesAround(dv.chunks, mid, toOrig); 180 var off = getOffsets(editor, toOrig ? around.edit : around.orig); 181 var offOther = getOffsets(other, toOrig ? around.orig : around.edit); 182 var ratio = (midY - off.top) / (off.bot - off.top); 183 var targetPos = (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top); 184 185 var botDist, mix; 186 // Some careful tweaking to make sure no space is left out of view 187 // when scrolling to top or bottom. 188 if (targetPos > sInfo.top && (mix = sInfo.top / halfScreen) < 1) { 189 targetPos = targetPos * mix + sInfo.top * (1 - mix); 190 } else if ((botDist = sInfo.height - sInfo.clientHeight - sInfo.top) < halfScreen) { 191 var otherInfo = other.getScrollInfo(); 192 var botDistOther = otherInfo.height - otherInfo.clientHeight - targetPos; 193 if (botDistOther > botDist && (mix = botDist / halfScreen) < 1) 194 targetPos = targetPos * mix + (otherInfo.height - otherInfo.clientHeight - botDist) * (1 - mix); 195 } 196 } 197 198 other.scrollTo(sInfo.left, targetPos); 199 other.state.scrollSetAt = now; 200 other.state.scrollSetBy = dv; 201 return true; 202 } 203 204 function getOffsets(editor, around) { 205 var bot = around.after; 206 if (bot == null) bot = editor.lastLine() + 1; 207 return {top: editor.heightAtLine(around.before || 0, "local"), 208 bot: editor.heightAtLine(bot, "local")}; 209 } 210 211 function setScrollLock(dv, val, action) { 212 dv.lockScroll = val; 213 if (val && action != false) syncScroll(dv, DIFF_INSERT) && makeConnections(dv); 214 dv.lockButton.innerHTML = val ? "\u21db\u21da" : "\u21db&nbsp;&nbsp;\u21da"; 215 } 216 217 // Updating the marks for editor content 218 219 function removeClass(editor, line, classes) { 220 var locs = classes.classLocation 221 for (var i = 0; i < locs.length; i++) { 222 editor.removeLineClass(line, locs[i], classes.chunk); 223 editor.removeLineClass(line, locs[i], classes.start); 224 editor.removeLineClass(line, locs[i], classes.end); 225 } 226 } 227 228 function clearMarks(editor, arr, classes) { 229 for (var i = 0; i < arr.length; ++i) { 230 var mark = arr[i]; 231 if (mark instanceof CodeMirror.TextMarker) 232 mark.clear(); 233 else if (mark.parent) 234 removeClass(editor, mark, classes); 235 } 236 arr.length = 0; 237 } 238 239 // FIXME maybe add a margin around viewport to prevent too many updates 240 function updateMarks(editor, diff, state, type, classes) { 241 var vp = editor.getViewport(); 242 editor.operation(function() { 243 if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) { 244 clearMarks(editor, state.marked, classes); 245 markChanges(editor, diff, type, state.marked, vp.from, vp.to, classes); 246 state.from = vp.from; state.to = vp.to; 247 } else { 248 if (vp.from < state.from) { 249 markChanges(editor, diff, type, state.marked, vp.from, state.from, classes); 250 state.from = vp.from; 251 } 252 if (vp.to > state.to) { 253 markChanges(editor, diff, type, state.marked, state.to, vp.to, classes); 254 state.to = vp.to; 255 } 256 } 257 }); 258 } 259 260 function addClass(editor, lineNr, classes, main, start, end) { 261 var locs = classes.classLocation, line = editor.getLineHandle(lineNr); 262 for (var i = 0; i < locs.length; i++) { 263 if (main) editor.addLineClass(line, locs[i], classes.chunk); 264 if (start) editor.addLineClass(line, locs[i], classes.start); 265 if (end) editor.addLineClass(line, locs[i], classes.end); 266 } 267 return line; 268 } 269 270 function markChanges(editor, diff, type, marks, from, to, classes) { 271 var pos = Pos(0, 0); 272 var top = Pos(from, 0), bot = editor.clipPos(Pos(to - 1)); 273 var cls = type == DIFF_DELETE ? classes.del : classes.insert; 274 function markChunk(start, end) { 275 var bfrom = Math.max(from, start), bto = Math.min(to, end); 276 for (var i = bfrom; i < bto; ++i) 277 marks.push(addClass(editor, i, classes, true, i == start, i == end - 1)); 278 // When the chunk is empty, make sure a horizontal line shows up 279 if (start == end && bfrom == end && bto == end) { 280 if (bfrom) 281 marks.push(addClass(editor, bfrom - 1, classes, false, false, true)); 282 else 283 marks.push(addClass(editor, bfrom, classes, false, true, false)); 284 } 285 } 286 287 var chunkStart = 0, pending = false; 288 for (var i = 0; i < diff.length; ++i) { 289 var part = diff[i], tp = part[0], str = part[1]; 290 if (tp == DIFF_EQUAL) { 291 var cleanFrom = pos.line + (startOfLineClean(diff, i) ? 0 : 1); 292 moveOver(pos, str); 293 var cleanTo = pos.line + (endOfLineClean(diff, i) ? 1 : 0); 294 if (cleanTo > cleanFrom) { 295 if (pending) { markChunk(chunkStart, cleanFrom); pending = false } 296 chunkStart = cleanTo; 297 } 298 } else { 299 pending = true 300 if (tp == type) { 301 var end = moveOver(pos, str, true); 302 var a = posMax(top, pos), b = posMin(bot, end); 303 if (!posEq(a, b)) 304 marks.push(editor.markText(a, b, {className: cls})); 305 pos = end; 306 } 307 } 308 } 309 if (pending) markChunk(chunkStart, pos.line + 1); 310 } 311 312 // Updating the gap between editor and original 313 314 function makeConnections(dv) { 315 if (!dv.showDifferences) return; 316 317 if (dv.svg) { 318 clear(dv.svg); 319 var w = dv.gap.offsetWidth; 320 attrs(dv.svg, "width", w, "height", dv.gap.offsetHeight); 321 } 322 if (dv.copyButtons) clear(dv.copyButtons); 323 324 var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport(); 325 var outerTop = dv.mv.wrap.getBoundingClientRect().top 326 var sTopEdit = outerTop - dv.edit.getScrollerElement().getBoundingClientRect().top + dv.edit.getScrollInfo().top 327 var sTopOrig = outerTop - dv.orig.getScrollerElement().getBoundingClientRect().top + dv.orig.getScrollInfo().top; 328 for (var i = 0; i < dv.chunks.length; i++) { 329 var ch = dv.chunks[i]; 330 if (ch.editFrom <= vpEdit.to && ch.editTo >= vpEdit.from && 331 ch.origFrom <= vpOrig.to && ch.origTo >= vpOrig.from) 332 drawConnectorsForChunk(dv, ch, sTopOrig, sTopEdit, w); 333 } 334 } 335 336 function getMatchingOrigLine(editLine, chunks) { 337 var editStart = 0, origStart = 0; 338 for (var i = 0; i < chunks.length; i++) { 339 var chunk = chunks[i]; 340 if (chunk.editTo > editLine && chunk.editFrom <= editLine) return null; 341 if (chunk.editFrom > editLine) break; 342 editStart = chunk.editTo; 343 origStart = chunk.origTo; 344 } 345 return origStart + (editLine - editStart); 346 } 347 348 // Combines information about chunks and widgets/markers to return 349 // an array of lines, in a single editor, that probably need to be 350 // aligned with their counterparts in the editor next to it. 351 function alignableFor(cm, chunks, isOrig) { 352 var tracker = cm.state.trackAlignable 353 var start = cm.firstLine(), trackI = 0 354 var result = [] 355 for (var i = 0;; i++) { 356 var chunk = chunks[i] 357 var chunkStart = !chunk ? 1e9 : isOrig ? chunk.origFrom : chunk.editFrom 358 for (; trackI < tracker.alignable.length; trackI += 2) { 359 var n = tracker.alignable[trackI] + 1 360 if (n <= start) continue 361 if (n <= chunkStart) result.push(n) 362 else break 363 } 364 if (!chunk) break 365 result.push(start = isOrig ? chunk.origTo : chunk.editTo) 366 } 367 return result 368 } 369 370 // Given information about alignable lines in two editors, fill in 371 // the result (an array of three-element arrays) to reflect the 372 // lines that need to be aligned with each other. 373 function mergeAlignable(result, origAlignable, chunks, setIndex) { 374 var rI = 0, origI = 0, chunkI = 0, diff = 0 375 outer: for (;; rI++) { 376 var nextR = result[rI], nextO = origAlignable[origI] 377 if (!nextR && nextO == null) break 378 379 var rLine = nextR ? nextR[0] : 1e9, oLine = nextO == null ? 1e9 : nextO 380 while (chunkI < chunks.length) { 381 var chunk = chunks[chunkI] 382 if (chunk.origFrom <= oLine && chunk.origTo > oLine) { 383 origI++ 384 rI-- 385 continue outer; 386 } 387 if (chunk.editTo > rLine) { 388 if (chunk.editFrom <= rLine) continue outer; 389 break 390 } 391 diff += (chunk.origTo - chunk.origFrom) - (chunk.editTo - chunk.editFrom) 392 chunkI++ 393 } 394 if (rLine == oLine - diff) { 395 nextR[setIndex] = oLine 396 origI++ 397 } else if (rLine < oLine - diff) { 398 nextR[setIndex] = rLine + diff 399 } else { 400 var record = [oLine - diff, null, null] 401 record[setIndex] = oLine 402 result.splice(rI, 0, record) 403 origI++ 404 } 405 } 406 } 407 408 function findAlignedLines(dv, other) { 409 var alignable = alignableFor(dv.edit, dv.chunks, false), result = [] 410 if (other) for (var i = 0, j = 0; i < other.chunks.length; i++) { 411 var n = other.chunks[i].editTo 412 while (j < alignable.length && alignable[j] < n) j++ 413 if (j == alignable.length || alignable[j] != n) alignable.splice(j++, 0, n) 414 } 415 for (var i = 0; i < alignable.length; i++) 416 result.push([alignable[i], null, null]) 417 418 mergeAlignable(result, alignableFor(dv.orig, dv.chunks, true), dv.chunks, 1) 419 if (other) 420 mergeAlignable(result, alignableFor(other.orig, other.chunks, true), other.chunks, 2) 421 422 return result 423 } 424 425 function alignChunks(dv, force) { 426 if (!dv.dealigned && !force) return; 427 if (!dv.orig.curOp) return dv.orig.operation(function() { 428 alignChunks(dv, force); 429 }); 430 431 dv.dealigned = false; 432 var other = dv.mv.left == dv ? dv.mv.right : dv.mv.left; 433 if (other) { 434 ensureDiff(other); 435 other.dealigned = false; 436 } 437 var linesToAlign = findAlignedLines(dv, other); 438 439 // Clear old aligners 440 var aligners = dv.mv.aligners; 441 for (var i = 0; i < aligners.length; i++) 442 aligners[i].clear(); 443 aligners.length = 0; 444 445 var cm = [dv.edit, dv.orig], scroll = []; 446 if (other) cm.push(other.orig); 447 for (var i = 0; i < cm.length; i++) 448 scroll.push(cm[i].getScrollInfo().top); 449 450 for (var ln = 0; ln < linesToAlign.length; ln++) 451 alignLines(cm, linesToAlign[ln], aligners); 452 453 for (var i = 0; i < cm.length; i++) 454 cm[i].scrollTo(null, scroll[i]); 455 } 456 457 function alignLines(cm, lines, aligners) { 458 var maxOffset = 0, offset = []; 459 for (var i = 0; i < cm.length; i++) if (lines[i] != null) { 460 var off = cm[i].heightAtLine(lines[i], "local"); 461 offset[i] = off; 462 maxOffset = Math.max(maxOffset, off); 463 } 464 for (var i = 0; i < cm.length; i++) if (lines[i] != null) { 465 var diff = maxOffset - offset[i]; 466 if (diff > 1) 467 aligners.push(padAbove(cm[i], lines[i], diff)); 468 } 469 } 470 471 function padAbove(cm, line, size) { 472 var above = true; 473 if (line > cm.lastLine()) { 474 line--; 475 above = false; 476 } 477 var elt = document.createElement("div"); 478 elt.className = "CodeMirror-merge-spacer"; 479 elt.style.height = size + "px"; elt.style.minWidth = "1px"; 480 return cm.addLineWidget(line, elt, {height: size, above: above, mergeSpacer: true, handleMouseEvents: true}); 481 } 482 483 function drawConnectorsForChunk(dv, chunk, sTopOrig, sTopEdit, w) { 484 var flip = dv.type == "left"; 485 var top = dv.orig.heightAtLine(chunk.origFrom, "local", true) - sTopOrig; 486 if (dv.svg) { 487 var topLpx = top; 488 var topRpx = dv.edit.heightAtLine(chunk.editFrom, "local", true) - sTopEdit; 489 if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; } 490 var botLpx = dv.orig.heightAtLine(chunk.origTo, "local", true) - sTopOrig; 491 var botRpx = dv.edit.heightAtLine(chunk.editTo, "local", true) - sTopEdit; 492 if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; } 493 var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx; 494 var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx; 495 attrs(dv.svg.appendChild(document.createElementNS(svgNS, "path")), 496 "d", "M -1 " + topRpx + curveTop + " L " + (w + 2) + " " + botLpx + curveBot + " z", 497 "class", dv.classes.connect); 498 } 499 if (dv.copyButtons) { 500 var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc", 501 "CodeMirror-merge-copy")); 502 var editOriginals = dv.mv.options.allowEditingOriginals; 503 copy.title = editOriginals ? "Push to left" : "Revert chunk"; 504 copy.chunk = chunk; 505 copy.style.top = (chunk.origTo > chunk.origFrom ? top : dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit) + "px"; 506 507 if (editOriginals) { 508 var topReverse = dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit; 509 var copyReverse = dv.copyButtons.appendChild(elt("div", dv.type == "right" ? "\u21dd" : "\u21dc", 510 "CodeMirror-merge-copy-reverse")); 511 copyReverse.title = "Push to right"; 512 copyReverse.chunk = {editFrom: chunk.origFrom, editTo: chunk.origTo, 513 origFrom: chunk.editFrom, origTo: chunk.editTo}; 514 copyReverse.style.top = topReverse + "px"; 515 dv.type == "right" ? copyReverse.style.left = "2px" : copyReverse.style.right = "2px"; 516 } 517 } 518 } 519 520 function copyChunk(dv, to, from, chunk) { 521 if (dv.diffOutOfDate) return; 522 var origStart = chunk.origTo > from.lastLine() ? Pos(chunk.origFrom - 1) : Pos(chunk.origFrom, 0) 523 var origEnd = Pos(chunk.origTo, 0) 524 var editStart = chunk.editTo > to.lastLine() ? Pos(chunk.editFrom - 1) : Pos(chunk.editFrom, 0) 525 var editEnd = Pos(chunk.editTo, 0) 526 var handler = dv.mv.options.revertChunk 527 if (handler) 528 handler(dv.mv, from, origStart, origEnd, to, editStart, editEnd) 529 else 530 to.replaceRange(from.getRange(origStart, origEnd), editStart, editEnd) 531 } 532 533 // Merge view, containing 0, 1, or 2 diff views. 534 535 var MergeView = CodeMirror.MergeView = function(node, options) { 536 if (!(this instanceof MergeView)) return new MergeView(node, options); 537 538 this.options = options; 539 var origLeft = options.origLeft, origRight = options.origRight == null ? options.orig : options.origRight; 540 541 var hasLeft = origLeft != null, hasRight = origRight != null; 542 var panes = 1 + (hasLeft ? 1 : 0) + (hasRight ? 1 : 0); 543 var wrap = [], left = this.left = null, right = this.right = null; 544 var self = this; 545 546 if (hasLeft) { 547 left = this.left = new DiffView(this, "left"); 548 var leftPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-left"); 549 wrap.push(leftPane); 550 wrap.push(buildGap(left)); 551 } 552 553 var editPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-editor"); 554 wrap.push(editPane); 555 556 if (hasRight) { 557 right = this.right = new DiffView(this, "right"); 558 wrap.push(buildGap(right)); 559 var rightPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-right"); 560 wrap.push(rightPane); 561 } 562 563 (hasRight ? rightPane : editPane).className += " CodeMirror-merge-pane-rightmost"; 564 565 wrap.push(elt("div", null, null, "height: 0; clear: both;")); 566 567 var wrapElt = this.wrap = node.appendChild(elt("div", wrap, "CodeMirror-merge CodeMirror-merge-" + panes + "pane")); 568 this.edit = CodeMirror(editPane, copyObj(options)); 569 570 if (left) left.init(leftPane, origLeft, options); 571 if (right) right.init(rightPane, origRight, options); 572 if (options.collapseIdentical) 573 this.editor().operation(function() { 574 collapseIdenticalStretches(self, options.collapseIdentical); 575 }); 576 if (options.connect == "align") { 577 this.aligners = []; 578 alignChunks(this.left || this.right, true); 579 } 580 if (left) left.registerEvents(right) 581 if (right) right.registerEvents(left) 582 583 584 var onResize = function() { 585 if (left) makeConnections(left); 586 if (right) makeConnections(right); 587 }; 588 CodeMirror.on(window, "resize", onResize); 589 var resizeInterval = setInterval(function() { 590 for (var p = wrapElt.parentNode; p && p != document.body; p = p.parentNode) {} 591 if (!p) { clearInterval(resizeInterval); CodeMirror.off(window, "resize", onResize); } 592 }, 5000); 593 }; 594 595 function buildGap(dv) { 596 var lock = dv.lockButton = elt("div", null, "CodeMirror-merge-scrolllock"); 597 lock.title = "Toggle locked scrolling"; 598 var lockWrap = elt("div", [lock], "CodeMirror-merge-scrolllock-wrap"); 599 CodeMirror.on(lock, "click", function() { setScrollLock(dv, !dv.lockScroll); }); 600 var gapElts = [lockWrap]; 601 if (dv.mv.options.revertButtons !== false) { 602 dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type); 603 CodeMirror.on(dv.copyButtons, "click", function(e) { 604 var node = e.target || e.srcElement; 605 if (!node.chunk) return; 606 if (node.className == "CodeMirror-merge-copy-reverse") { 607 copyChunk(dv, dv.orig, dv.edit, node.chunk); 608 return; 609 } 610 copyChunk(dv, dv.edit, dv.orig, node.chunk); 611 }); 612 gapElts.unshift(dv.copyButtons); 613 } 614 if (dv.mv.options.connect != "align") { 615 var svg = document.createElementNS && document.createElementNS(svgNS, "svg"); 616 if (svg && !svg.createSVGRect) svg = null; 617 dv.svg = svg; 618 if (svg) gapElts.push(svg); 619 } 620 621 return dv.gap = elt("div", gapElts, "CodeMirror-merge-gap"); 622 } 623 624 MergeView.prototype = { 625 constructor: MergeView, 626 editor: function() { return this.edit; }, 627 rightOriginal: function() { return this.right && this.right.orig; }, 628 leftOriginal: function() { return this.left && this.left.orig; }, 629 setShowDifferences: function(val) { 630 if (this.right) this.right.setShowDifferences(val); 631 if (this.left) this.left.setShowDifferences(val); 632 }, 633 rightChunks: function() { 634 if (this.right) { ensureDiff(this.right); return this.right.chunks; } 635 }, 636 leftChunks: function() { 637 if (this.left) { ensureDiff(this.left); return this.left.chunks; } 638 } 639 }; 640 641 function asString(obj) { 642 if (typeof obj == "string") return obj; 643 else return obj.getValue(); 644 } 645 646 // Operations on diffs 647 var dmp; 648 function getDiff(a, b, ignoreWhitespace) { 649 if (!dmp) dmp = new diff_match_patch(); 650 651 var diff = dmp.diff_main(a, b); 652 // The library sometimes leaves in empty parts, which confuse the algorithm 653 for (var i = 0; i < diff.length; ++i) { 654 var part = diff[i]; 655 if (ignoreWhitespace ? !/[^ \t]/.test(part[1]) : !part[1]) { 656 diff.splice(i--, 1); 657 } else if (i && diff[i - 1][0] == part[0]) { 658 diff.splice(i--, 1); 659 diff[i][1] += part[1]; 660 } 661 } 662 return diff; 663 } 664 665 function getChunks(diff) { 666 var chunks = []; 667 var startEdit = 0, startOrig = 0; 668 var edit = Pos(0, 0), orig = Pos(0, 0); 669 for (var i = 0; i < diff.length; ++i) { 670 var part = diff[i], tp = part[0]; 671 if (tp == DIFF_EQUAL) { 672 var startOff = !startOfLineClean(diff, i) || edit.line < startEdit || orig.line < startOrig ? 1 : 0; 673 var cleanFromEdit = edit.line + startOff, cleanFromOrig = orig.line + startOff; 674 moveOver(edit, part[1], null, orig); 675 var endOff = endOfLineClean(diff, i) ? 1 : 0; 676 var cleanToEdit = edit.line + endOff, cleanToOrig = orig.line + endOff; 677 if (cleanToEdit > cleanFromEdit) { 678 if (i) chunks.push({origFrom: startOrig, origTo: cleanFromOrig, 679 editFrom: startEdit, editTo: cleanFromEdit}); 680 startEdit = cleanToEdit; startOrig = cleanToOrig; 681 } 682 } else { 683 moveOver(tp == DIFF_INSERT ? edit : orig, part[1]); 684 } 685 } 686 if (startEdit <= edit.line || startOrig <= orig.line) 687 chunks.push({origFrom: startOrig, origTo: orig.line + 1, 688 editFrom: startEdit, editTo: edit.line + 1}); 689 return chunks; 690 } 691 692 function endOfLineClean(diff, i) { 693 if (i == diff.length - 1) return true; 694 var next = diff[i + 1][1]; 695 if ((next.length == 1 && i < diff.length - 2) || next.charCodeAt(0) != 10) return false; 696 if (i == diff.length - 2) return true; 697 next = diff[i + 2][1]; 698 return (next.length > 1 || i == diff.length - 3) && next.charCodeAt(0) == 10; 699 } 700 701 function startOfLineClean(diff, i) { 702 if (i == 0) return true; 703 var last = diff[i - 1][1]; 704 if (last.charCodeAt(last.length - 1) != 10) return false; 705 if (i == 1) return true; 706 last = diff[i - 2][1]; 707 return last.charCodeAt(last.length - 1) == 10; 708 } 709 710 function chunkBoundariesAround(chunks, n, nInEdit) { 711 var beforeE, afterE, beforeO, afterO; 712 for (var i = 0; i < chunks.length; i++) { 713 var chunk = chunks[i]; 714 var fromLocal = nInEdit ? chunk.editFrom : chunk.origFrom; 715 var toLocal = nInEdit ? chunk.editTo : chunk.origTo; 716 if (afterE == null) { 717 if (fromLocal > n) { afterE = chunk.editFrom; afterO = chunk.origFrom; } 718 else if (toLocal > n) { afterE = chunk.editTo; afterO = chunk.origTo; } 719 } 720 if (toLocal <= n) { beforeE = chunk.editTo; beforeO = chunk.origTo; } 721 else if (fromLocal <= n) { beforeE = chunk.editFrom; beforeO = chunk.origFrom; } 722 } 723 return {edit: {before: beforeE, after: afterE}, orig: {before: beforeO, after: afterO}}; 724 } 725 726 function collapseSingle(cm, from, to) { 727 cm.addLineClass(from, "wrap", "CodeMirror-merge-collapsed-line"); 728 var widget = document.createElement("span"); 729 widget.className = "CodeMirror-merge-collapsed-widget"; 730 widget.title = "Identical text collapsed. Click to expand."; 731 var mark = cm.markText(Pos(from, 0), Pos(to - 1), { 732 inclusiveLeft: true, 733 inclusiveRight: true, 734 replacedWith: widget, 735 clearOnEnter: true 736 }); 737 function clear() { 738 mark.clear(); 739 cm.removeLineClass(from, "wrap", "CodeMirror-merge-collapsed-line"); 740 } 741 if (mark.explicitlyCleared) clear(); 742 CodeMirror.on(widget, "click", clear); 743 mark.on("clear", clear); 744 CodeMirror.on(widget, "click", clear); 745 return {mark: mark, clear: clear}; 746 } 747 748 function collapseStretch(size, editors) { 749 var marks = []; 750 function clear() { 751 for (var i = 0; i < marks.length; i++) marks[i].clear(); 752 } 753 for (var i = 0; i < editors.length; i++) { 754 var editor = editors[i]; 755 var mark = collapseSingle(editor.cm, editor.line, editor.line + size); 756 marks.push(mark); 757 mark.mark.on("clear", clear); 758 } 759 return marks[0].mark; 760 } 761 762 function unclearNearChunks(dv, margin, off, clear) { 763 for (var i = 0; i < dv.chunks.length; i++) { 764 var chunk = dv.chunks[i]; 765 for (var l = chunk.editFrom - margin; l < chunk.editTo + margin; l++) { 766 var pos = l + off; 767 if (pos >= 0 && pos < clear.length) clear[pos] = false; 768 } 769 } 770 } 771 772 function collapseIdenticalStretches(mv, margin) { 773 if (typeof margin != "number") margin = 2; 774 var clear = [], edit = mv.editor(), off = edit.firstLine(); 775 for (var l = off, e = edit.lastLine(); l <= e; l++) clear.push(true); 776 if (mv.left) unclearNearChunks(mv.left, margin, off, clear); 777 if (mv.right) unclearNearChunks(mv.right, margin, off, clear); 778 779 for (var i = 0; i < clear.length; i++) { 780 if (clear[i]) { 781 var line = i + off; 782 for (var size = 1; i < clear.length - 1 && clear[i + 1]; i++, size++) {} 783 if (size > margin) { 784 var editors = [{line: line, cm: edit}]; 785 if (mv.left) editors.push({line: getMatchingOrigLine(line, mv.left.chunks), cm: mv.left.orig}); 786 if (mv.right) editors.push({line: getMatchingOrigLine(line, mv.right.chunks), cm: mv.right.orig}); 787 var mark = collapseStretch(size, editors); 788 if (mv.options.onCollapse) mv.options.onCollapse(mv, line, size, mark); 789 } 790 } 791 } 792 } 793 794 // General utilities 795 796 function elt(tag, content, className, style) { 797 var e = document.createElement(tag); 798 if (className) e.className = className; 799 if (style) e.style.cssText = style; 800 if (typeof content == "string") e.appendChild(document.createTextNode(content)); 801 else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); 802 return e; 803 } 804 805 function clear(node) { 806 for (var count = node.childNodes.length; count > 0; --count) 807 node.removeChild(node.firstChild); 808 } 809 810 function attrs(elt) { 811 for (var i = 1; i < arguments.length; i += 2) 812 elt.setAttribute(arguments[i], arguments[i+1]); 813 } 814 815 function copyObj(obj, target) { 816 if (!target) target = {}; 817 for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop]; 818 return target; 819 } 820 821 function moveOver(pos, str, copy, other) { 822 var out = copy ? Pos(pos.line, pos.ch) : pos, at = 0; 823 for (;;) { 824 var nl = str.indexOf("\n", at); 825 if (nl == -1) break; 826 ++out.line; 827 if (other) ++other.line; 828 at = nl + 1; 829 } 830 out.ch = (at ? 0 : out.ch) + (str.length - at); 831 if (other) other.ch = (at ? 0 : other.ch) + (str.length - at); 832 return out; 833 } 834 835 // Tracks collapsed markers and line widgets, in order to be able to 836 // accurately align the content of two editors. 837 838 var F_WIDGET = 1, F_WIDGET_BELOW = 2, F_MARKER = 4 839 840 function TrackAlignable(cm) { 841 this.cm = cm 842 this.alignable = [] 843 this.height = cm.doc.height 844 var self = this 845 cm.on("markerAdded", function(_, marker) { 846 if (!marker.collapsed) return 847 var found = marker.find(1) 848 if (found != null) self.set(found.line, F_MARKER) 849 }) 850 cm.on("markerCleared", function(_, marker, _min, max) { 851 if (max != null && marker.collapsed) 852 self.check(max, F_MARKER, self.hasMarker) 853 }) 854 cm.on("markerChanged", this.signal.bind(this)) 855 cm.on("lineWidgetAdded", function(_, widget, lineNo) { 856 if (widget.mergeSpacer) return 857 if (widget.above) self.set(lineNo - 1, F_WIDGET_BELOW) 858 else self.set(lineNo, F_WIDGET) 859 }) 860 cm.on("lineWidgetCleared", function(_, widget, lineNo) { 861 if (widget.mergeSpacer) return 862 if (widget.above) self.check(lineNo - 1, F_WIDGET_BELOW, self.hasWidgetBelow) 863 else self.check(lineNo, F_WIDGET, self.hasWidget) 864 }) 865 cm.on("lineWidgetChanged", this.signal.bind(this)) 866 cm.on("change", function(_, change) { 867 var start = change.from.line, nBefore = change.to.line - change.from.line 868 var nAfter = change.text.length - 1, end = start + nAfter 869 if (nBefore || nAfter) self.map(start, nBefore, nAfter) 870 self.check(end, F_MARKER, self.hasMarker) 871 if (nBefore || nAfter) self.check(change.from.line, F_MARKER, self.hasMarker) 872 }) 873 cm.on("viewportChange", function() { 874 if (self.cm.doc.height != self.height) self.signal() 875 }) 876 } 877 878 TrackAlignable.prototype = { 879 signal: function() { 880 CodeMirror.signal(this, "realign") 881 this.height = this.cm.doc.height 882 }, 883 884 set: function(n, flags) { 885 var pos = -1 886 for (; pos < this.alignable.length; pos += 2) { 887 var diff = this.alignable[pos] - n 888 if (diff == 0) { 889 if ((this.alignable[pos + 1] & flags) == flags) return 890 this.alignable[pos + 1] |= flags 891 this.signal() 892 return 893 } 894 if (diff > 0) break 895 } 896 this.signal() 897 this.alignable.splice(pos, 0, n, flags) 898 }, 899 900 find: function(n) { 901 for (var i = 0; i < this.alignable.length; i += 2) 902 if (this.alignable[i] == n) return i 903 return -1 904 }, 905 906 check: function(n, flag, pred) { 907 var found = this.find(n) 908 if (found == -1 || !(this.alignable[found + 1] & flag)) return 909 if (!pred.call(this, n)) { 910 this.signal() 911 var flags = this.alignable[found + 1] & ~flag 912 if (flags) this.alignable[found + 1] = flags 913 else this.alignable.splice(found, 2) 914 } 915 }, 916 917 hasMarker: function(n) { 918 var handle = this.cm.getLineHandle(n) 919 if (handle.markedSpans) for (var i = 0; i < handle.markedSpans.length; i++) 920 if (handle.markedSpans[i].mark.collapsed && handle.markedSpans[i].to != null) 921 return true 922 return false 923 }, 924 925 hasWidget: function(n) { 926 var handle = this.cm.getLineHandle(n) 927 if (handle.widgets) for (var i = 0; i < handle.widgets.length; i++) 928 if (!handle.widgets[i].above && !handle.widgets[i].mergeSpacer) return true 929 return false 930 }, 931 932 hasWidgetBelow: function(n) { 933 if (n == this.cm.lastLine()) return false 934 var handle = this.cm.getLineHandle(n + 1) 935 if (handle.widgets) for (var i = 0; i < handle.widgets.length; i++) 936 if (handle.widgets[i].above && !handle.widgets[i].mergeSpacer) return true 937 return false 938 }, 939 940 map: function(from, nBefore, nAfter) { 941 var diff = nAfter - nBefore, to = from + nBefore, widgetFrom = -1, widgetTo = -1 942 for (var i = 0; i < this.alignable.length; i += 2) { 943 var n = this.alignable[i] 944 if (n == from && (this.alignable[i + 1] & F_WIDGET_BELOW)) widgetFrom = i 945 if (n == to && (this.alignable[i + 1] & F_WIDGET_BELOW)) widgetTo = i 946 if (n <= from) continue 947 else if (n < to) this.alignable.splice(i--, 2) 948 else this.alignable[i] += diff 949 } 950 if (widgetFrom > -1) { 951 var flags = this.alignable[widgetFrom + 1] 952 if (flags == F_WIDGET_BELOW) this.alignable.splice(widgetFrom, 2) 953 else this.alignable[widgetFrom + 1] = flags & ~F_WIDGET_BELOW 954 } 955 if (widgetTo > -1 && nAfter) 956 this.set(from + nAfter, F_WIDGET_BELOW) 957 } 958 } 959 960 function posMin(a, b) { return (a.line - b.line || a.ch - b.ch) < 0 ? a : b; } 961 function posMax(a, b) { return (a.line - b.line || a.ch - b.ch) > 0 ? a : b; } 962 function posEq(a, b) { return a.line == b.line && a.ch == b.ch; } 963 964 function findPrevDiff(chunks, start, isOrig) { 965 for (var i = chunks.length - 1; i >= 0; i--) { 966 var chunk = chunks[i]; 967 var to = (isOrig ? chunk.origTo : chunk.editTo) - 1; 968 if (to < start) return to; 969 } 970 } 971 972 function findNextDiff(chunks, start, isOrig) { 973 for (var i = 0; i < chunks.length; i++) { 974 var chunk = chunks[i]; 975 var from = (isOrig ? chunk.origFrom : chunk.editFrom); 976 if (from > start) return from; 977 } 978 } 979 980 function goNearbyDiff(cm, dir) { 981 var found = null, views = cm.state.diffViews, line = cm.getCursor().line; 982 if (views) for (var i = 0; i < views.length; i++) { 983 var dv = views[i], isOrig = cm == dv.orig; 984 ensureDiff(dv); 985 var pos = dir < 0 ? findPrevDiff(dv.chunks, line, isOrig) : findNextDiff(dv.chunks, line, isOrig); 986 if (pos != null && (found == null || (dir < 0 ? pos > found : pos < found))) 987 found = pos; 988 } 989 if (found != null) 990 cm.setCursor(found, 0); 991 else 992 return CodeMirror.Pass; 993 } 994 995 CodeMirror.commands.goNextDiff = function(cm) { 996 return goNearbyDiff(cm, 1); 997 }; 998 CodeMirror.commands.goPrevDiff = function(cm) { 999 return goNearbyDiff(cm, -1); 1000 }; 1001 });
Download modules/editor/codemirror/addon/merge/merge.min.js
History Tue, 22 May 2018 22:39:53 +0200 Jan Dankert Fix für PHP 7.2: 'Object' darf nun nicht mehr als Klassennamen verwendet werden. AUCH NICHT IN EINEM NAMESPACE! WTF, wozu habe ich das in einen verfickten Namespace gepackt? Wozu soll der sonst da sein??? Amateure. Daher nun notgedrungen unbenannt in 'BaseObject'.