PluginProbe ʕ •ᴥ•ʔ
SiteOrigin CSS / 1.0.7
SiteOrigin CSS v1.0.7
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 11 years ago merge.js 10 years ago merge.min.js 10 years ago
merge.js
773 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 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&nbsp;&nbsp;\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 });
773