PluginProbe ʕ •ᴥ•ʔ
SiteOrigin CSS / 1.4.1
SiteOrigin CSS v1.4.1
1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.10 1.5.11 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 trunk 1.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.1 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.2.0
so-css / lib / codemirror / addon / merge / merge.js
so-css / lib / codemirror / addon / merge Last commit date
merge.css 9 years ago merge.js 9 years ago merge.min.js 6 years ago
merge.js
998 lines
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
648 var dmp = new diff_match_patch();
649 function getDiff(a, b, ignoreWhitespace) {
650 var diff = dmp.diff_main(a, b);
651 // The library sometimes leaves in empty parts, which confuse the algorithm
652 for (var i = 0; i < diff.length; ++i) {
653 var part = diff[i];
654 if (ignoreWhitespace ? !/[^ \t]/.test(part[1]) : !part[1]) {
655 diff.splice(i--, 1);
656 } else if (i && diff[i - 1][0] == part[0]) {
657 diff.splice(i--, 1);
658 diff[i][1] += part[1];
659 }
660 }
661 return diff;
662 }
663
664 function getChunks(diff) {
665 var chunks = [];
666 var startEdit = 0, startOrig = 0;
667 var edit = Pos(0, 0), orig = Pos(0, 0);
668 for (var i = 0; i < diff.length; ++i) {
669 var part = diff[i], tp = part[0];
670 if (tp == DIFF_EQUAL) {
671 var startOff = !startOfLineClean(diff, i) || edit.line < startEdit || orig.line < startOrig ? 1 : 0;
672 var cleanFromEdit = edit.line + startOff, cleanFromOrig = orig.line + startOff;
673 moveOver(edit, part[1], null, orig);
674 var endOff = endOfLineClean(diff, i) ? 1 : 0;
675 var cleanToEdit = edit.line + endOff, cleanToOrig = orig.line + endOff;
676 if (cleanToEdit > cleanFromEdit) {
677 if (i) chunks.push({origFrom: startOrig, origTo: cleanFromOrig,
678 editFrom: startEdit, editTo: cleanFromEdit});
679 startEdit = cleanToEdit; startOrig = cleanToOrig;
680 }
681 } else {
682 moveOver(tp == DIFF_INSERT ? edit : orig, part[1]);
683 }
684 }
685 if (startEdit <= edit.line || startOrig <= orig.line)
686 chunks.push({origFrom: startOrig, origTo: orig.line + 1,
687 editFrom: startEdit, editTo: edit.line + 1});
688 return chunks;
689 }
690
691 function endOfLineClean(diff, i) {
692 if (i == diff.length - 1) return true;
693 var next = diff[i + 1][1];
694 if ((next.length == 1 && i < diff.length - 2) || next.charCodeAt(0) != 10) return false;
695 if (i == diff.length - 2) return true;
696 next = diff[i + 2][1];
697 return (next.length > 1 || i == diff.length - 3) && next.charCodeAt(0) == 10;
698 }
699
700 function startOfLineClean(diff, i) {
701 if (i == 0) return true;
702 var last = diff[i - 1][1];
703 if (last.charCodeAt(last.length - 1) != 10) return false;
704 if (i == 1) return true;
705 last = diff[i - 2][1];
706 return last.charCodeAt(last.length - 1) == 10;
707 }
708
709 function chunkBoundariesAround(chunks, n, nInEdit) {
710 var beforeE, afterE, beforeO, afterO;
711 for (var i = 0; i < chunks.length; i++) {
712 var chunk = chunks[i];
713 var fromLocal = nInEdit ? chunk.editFrom : chunk.origFrom;
714 var toLocal = nInEdit ? chunk.editTo : chunk.origTo;
715 if (afterE == null) {
716 if (fromLocal > n) { afterE = chunk.editFrom; afterO = chunk.origFrom; }
717 else if (toLocal > n) { afterE = chunk.editTo; afterO = chunk.origTo; }
718 }
719 if (toLocal <= n) { beforeE = chunk.editTo; beforeO = chunk.origTo; }
720 else if (fromLocal <= n) { beforeE = chunk.editFrom; beforeO = chunk.origFrom; }
721 }
722 return {edit: {before: beforeE, after: afterE}, orig: {before: beforeO, after: afterO}};
723 }
724
725 function collapseSingle(cm, from, to) {
726 cm.addLineClass(from, "wrap", "CodeMirror-merge-collapsed-line");
727 var widget = document.createElement("span");
728 widget.className = "CodeMirror-merge-collapsed-widget";
729 widget.title = "Identical text collapsed. Click to expand.";
730 var mark = cm.markText(Pos(from, 0), Pos(to - 1), {
731 inclusiveLeft: true,
732 inclusiveRight: true,
733 replacedWith: widget,
734 clearOnEnter: true
735 });
736 function clear() {
737 mark.clear();
738 cm.removeLineClass(from, "wrap", "CodeMirror-merge-collapsed-line");
739 }
740 CodeMirror.on(widget, "click", clear);
741 return {mark: mark, clear: clear};
742 }
743
744 function collapseStretch(size, editors) {
745 var marks = [];
746 function clear() {
747 for (var i = 0; i < marks.length; i++) marks[i].clear();
748 }
749 for (var i = 0; i < editors.length; i++) {
750 var editor = editors[i];
751 var mark = collapseSingle(editor.cm, editor.line, editor.line + size);
752 marks.push(mark);
753 mark.mark.on("clear", clear);
754 }
755 return marks[0].mark;
756 }
757
758 function unclearNearChunks(dv, margin, off, clear) {
759 for (var i = 0; i < dv.chunks.length; i++) {
760 var chunk = dv.chunks[i];
761 for (var l = chunk.editFrom - margin; l < chunk.editTo + margin; l++) {
762 var pos = l + off;
763 if (pos >= 0 && pos < clear.length) clear[pos] = false;
764 }
765 }
766 }
767
768 function collapseIdenticalStretches(mv, margin) {
769 if (typeof margin != "number") margin = 2;
770 var clear = [], edit = mv.editor(), off = edit.firstLine();
771 for (var l = off, e = edit.lastLine(); l <= e; l++) clear.push(true);
772 if (mv.left) unclearNearChunks(mv.left, margin, off, clear);
773 if (mv.right) unclearNearChunks(mv.right, margin, off, clear);
774
775 for (var i = 0; i < clear.length; i++) {
776 if (clear[i]) {
777 var line = i + off;
778 for (var size = 1; i < clear.length - 1 && clear[i + 1]; i++, size++) {}
779 if (size > margin) {
780 var editors = [{line: line, cm: edit}];
781 if (mv.left) editors.push({line: getMatchingOrigLine(line, mv.left.chunks), cm: mv.left.orig});
782 if (mv.right) editors.push({line: getMatchingOrigLine(line, mv.right.chunks), cm: mv.right.orig});
783 var mark = collapseStretch(size, editors);
784 if (mv.options.onCollapse) mv.options.onCollapse(mv, line, size, mark);
785 }
786 }
787 }
788 }
789
790 // General utilities
791
792 function elt(tag, content, className, style) {
793 var e = document.createElement(tag);
794 if (className) e.className = className;
795 if (style) e.style.cssText = style;
796 if (typeof content == "string") e.appendChild(document.createTextNode(content));
797 else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]);
798 return e;
799 }
800
801 function clear(node) {
802 for (var count = node.childNodes.length; count > 0; --count)
803 node.removeChild(node.firstChild);
804 }
805
806 function attrs(elt) {
807 for (var i = 1; i < arguments.length; i += 2)
808 elt.setAttribute(arguments[i], arguments[i+1]);
809 }
810
811 function copyObj(obj, target) {
812 if (!target) target = {};
813 for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop];
814 return target;
815 }
816
817 function moveOver(pos, str, copy, other) {
818 var out = copy ? Pos(pos.line, pos.ch) : pos, at = 0;
819 for (;;) {
820 var nl = str.indexOf("\n", at);
821 if (nl == -1) break;
822 ++out.line;
823 if (other) ++other.line;
824 at = nl + 1;
825 }
826 out.ch = (at ? 0 : out.ch) + (str.length - at);
827 if (other) other.ch = (at ? 0 : other.ch) + (str.length - at);
828 return out;
829 }
830
831 // Tracks collapsed markers and line widgets, in order to be able to
832 // accurately align the content of two editors.
833
834 var F_WIDGET = 1, F_WIDGET_BELOW = 2, F_MARKER = 4
835
836 function TrackAlignable(cm) {
837 this.cm = cm
838 this.alignable = []
839 this.height = cm.doc.height
840 var self = this
841 cm.on("markerAdded", function(_, marker) {
842 if (!marker.collapsed) return
843 var found = marker.find(1)
844 if (found != null) self.set(found.line, F_MARKER)
845 })
846 cm.on("markerCleared", function(_, marker, _min, max) {
847 if (max != null && marker.collapsed)
848 self.check(max, F_MARKER, self.hasMarker)
849 })
850 cm.on("markerChanged", this.signal.bind(this))
851 cm.on("lineWidgetAdded", function(_, widget, lineNo) {
852 if (widget.mergeSpacer) return
853 if (widget.above) self.set(lineNo - 1, F_WIDGET_BELOW)
854 else self.set(lineNo, F_WIDGET)
855 })
856 cm.on("lineWidgetCleared", function(_, widget, lineNo) {
857 if (widget.mergeSpacer) return
858 if (widget.above) self.check(lineNo - 1, F_WIDGET_BELOW, self.hasWidgetBelow)
859 else self.check(lineNo, F_WIDGET, self.hasWidget)
860 })
861 cm.on("lineWidgetChanged", this.signal.bind(this))
862 cm.on("change", function(_, change) {
863 var start = change.from.line, nBefore = change.to.line - change.from.line
864 var nAfter = change.text.length - 1, end = start + nAfter
865 if (nBefore || nAfter) self.map(start, nBefore, nAfter)
866 self.check(end, F_MARKER, self.hasMarker)
867 if (nBefore || nAfter) self.check(change.from.line, F_MARKER, self.hasMarker)
868 })
869 cm.on("viewportChange", function() {
870 if (self.cm.doc.height != self.height) self.signal()
871 })
872 }
873
874 TrackAlignable.prototype = {
875 signal: function() {
876 CodeMirror.signal(this, "realign")
877 this.height = this.cm.doc.height
878 },
879
880 set: function(n, flags) {
881 var pos = -1
882 for (; pos < this.alignable.length; pos += 2) {
883 var diff = this.alignable[pos] - n
884 if (diff == 0) {
885 if ((this.alignable[pos + 1] & flags) == flags) return
886 this.alignable[pos + 1] |= flags
887 this.signal()
888 return
889 }
890 if (diff > 0) break
891 }
892 this.signal()
893 this.alignable.splice(pos, 0, n, flags)
894 },
895
896 find: function(n) {
897 for (var i = 0; i < this.alignable.length; i += 2)
898 if (this.alignable[i] == n) return i
899 return -1
900 },
901
902 check: function(n, flag, pred) {
903 var found = this.find(n)
904 if (found == -1 || !(this.alignable[found + 1] & flag)) return
905 if (!pred.call(this, n)) {
906 this.signal()
907 var flags = this.alignable[found + 1] & ~flag
908 if (flags) this.alignable[found + 1] = flags
909 else this.alignable.splice(found, 2)
910 }
911 },
912
913 hasMarker: function(n) {
914 var handle = this.cm.getLineHandle(n)
915 if (handle.markedSpans) for (var i = 0; i < handle.markedSpans.length; i++)
916 if (handle.markedSpans[i].mark.collapsed && handle.markedSpans[i].to != null)
917 return true
918 return false
919 },
920
921 hasWidget: function(n) {
922 var handle = this.cm.getLineHandle(n)
923 if (handle.widgets) for (var i = 0; i < handle.widgets.length; i++)
924 if (!handle.widgets[i].above && !handle.widgets[i].mergeSpacer) return true
925 return false
926 },
927
928 hasWidgetBelow: function(n) {
929 if (n == this.cm.lastLine()) return false
930 var handle = this.cm.getLineHandle(n + 1)
931 if (handle.widgets) for (var i = 0; i < handle.widgets.length; i++)
932 if (handle.widgets[i].above && !handle.widgets[i].mergeSpacer) return true
933 return false
934 },
935
936 map: function(from, nBefore, nAfter) {
937 var diff = nAfter - nBefore, to = from + nBefore, widgetFrom = -1, widgetTo = -1
938 for (var i = 0; i < this.alignable.length; i += 2) {
939 var n = this.alignable[i]
940 if (n == from && (this.alignable[i + 1] & F_WIDGET_BELOW)) widgetFrom = i
941 if (n == to && (this.alignable[i + 1] & F_WIDGET_BELOW)) widgetTo = i
942 if (n <= from) continue
943 else if (n < to) this.alignable.splice(i--, 2)
944 else this.alignable[i] += diff
945 }
946 if (widgetFrom > -1) {
947 var flags = this.alignable[widgetFrom + 1]
948 if (flags == F_WIDGET_BELOW) this.alignable.splice(widgetFrom, 2)
949 else this.alignable[widgetFrom + 1] = flags & ~F_WIDGET_BELOW
950 }
951 if (widgetTo > -1 && nAfter)
952 this.set(from + nAfter, F_WIDGET_BELOW)
953 }
954 }
955
956 function posMin(a, b) { return (a.line - b.line || a.ch - b.ch) < 0 ? a : b; }
957 function posMax(a, b) { return (a.line - b.line || a.ch - b.ch) > 0 ? a : b; }
958 function posEq(a, b) { return a.line == b.line && a.ch == b.ch; }
959
960 function findPrevDiff(chunks, start, isOrig) {
961 for (var i = chunks.length - 1; i >= 0; i--) {
962 var chunk = chunks[i];
963 var to = (isOrig ? chunk.origTo : chunk.editTo) - 1;
964 if (to < start) return to;
965 }
966 }
967
968 function findNextDiff(chunks, start, isOrig) {
969 for (var i = 0; i < chunks.length; i++) {
970 var chunk = chunks[i];
971 var from = (isOrig ? chunk.origFrom : chunk.editFrom);
972 if (from > start) return from;
973 }
974 }
975
976 function goNearbyDiff(cm, dir) {
977 var found = null, views = cm.state.diffViews, line = cm.getCursor().line;
978 if (views) for (var i = 0; i < views.length; i++) {
979 var dv = views[i], isOrig = cm == dv.orig;
980 ensureDiff(dv);
981 var pos = dir < 0 ? findPrevDiff(dv.chunks, line, isOrig) : findNextDiff(dv.chunks, line, isOrig);
982 if (pos != null && (found == null || (dir < 0 ? pos > found : pos < found)))
983 found = pos;
984 }
985 if (found != null)
986 cm.setCursor(found, 0);
987 else
988 return CodeMirror.Pass;
989 }
990
991 CodeMirror.commands.goNextDiff = function(cm) {
992 return goNearbyDiff(cm, 1);
993 };
994 CodeMirror.commands.goPrevDiff = function(cm) {
995 return goNearbyDiff(cm, -1);
996 };
997 });
998