PluginProbe ʕ •ᴥ•ʔ
SiteOrigin CSS / 1.0
SiteOrigin CSS v1.0
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 11 years ago merge.min.js 11 years ago
merge.js
776 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"), require("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, diff_match_patch);
13 })(function(CodeMirror, diff_match_patch) {
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 updating = true;
476 this.editor().operation(function() {
477 collapseIdenticalStretches(self, options.collapseIdentical);
478 });
479 updating = false;
480 }
481 if (options.connect == "align") {
482 this.aligners = [];
483 alignChunks(this.left || this.right, true);
484 }
485
486 var onResize = function() {
487 if (left) makeConnections(left);
488 if (right) makeConnections(right);
489 };
490 CodeMirror.on(window, "resize", onResize);
491 var resizeInterval = setInterval(function() {
492 for (var p = wrapElt.parentNode; p && p != document.body; p = p.parentNode) {}
493 if (!p) { clearInterval(resizeInterval); CodeMirror.off(window, "resize", onResize); }
494 }, 5000);
495 };
496
497 function buildGap(dv) {
498 var lock = dv.lockButton = elt("div", null, "CodeMirror-merge-scrolllock");
499 lock.title = "Toggle locked scrolling";
500 var lockWrap = elt("div", [lock], "CodeMirror-merge-scrolllock-wrap");
501 CodeMirror.on(lock, "click", function() { setScrollLock(dv, !dv.lockScroll); });
502 var gapElts = [lockWrap];
503 if (dv.mv.options.revertButtons !== false) {
504 dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type);
505 CodeMirror.on(dv.copyButtons, "click", function(e) {
506 var node = e.target || e.srcElement;
507 if (!node.chunk) return;
508 if (node.className == "CodeMirror-merge-copy-reverse") {
509 copyChunk(dv, dv.orig, dv.edit, node.chunk);
510 return;
511 }
512 copyChunk(dv, dv.edit, dv.orig, node.chunk);
513 });
514 gapElts.unshift(dv.copyButtons);
515 }
516 if (dv.mv.options.connect != "align") {
517 var svg = document.createElementNS && document.createElementNS(svgNS, "svg");
518 if (svg && !svg.createSVGRect) svg = null;
519 dv.svg = svg;
520 if (svg) gapElts.push(svg);
521 }
522
523 return dv.gap = elt("div", gapElts, "CodeMirror-merge-gap");
524 }
525
526 MergeView.prototype = {
527 constuctor: MergeView,
528 editor: function() { return this.edit; },
529 rightOriginal: function() { return this.right && this.right.orig; },
530 leftOriginal: function() { return this.left && this.left.orig; },
531 setShowDifferences: function(val) {
532 if (this.right) this.right.setShowDifferences(val);
533 if (this.left) this.left.setShowDifferences(val);
534 },
535 rightChunks: function() {
536 if (this.right) { ensureDiff(this.right); return this.right.chunks; }
537 },
538 leftChunks: function() {
539 if (this.left) { ensureDiff(this.left); return this.left.chunks; }
540 }
541 };
542
543 function asString(obj) {
544 if (typeof obj == "string") return obj;
545 else return obj.getValue();
546 }
547
548 // Operations on diffs
549
550 var dmp = new diff_match_patch();
551 function getDiff(a, b) {
552 var diff = dmp.diff_main(a, b);
553 dmp.diff_cleanupSemantic(diff);
554 // The library sometimes leaves in empty parts, which confuse the algorithm
555 for (var i = 0; i < diff.length; ++i) {
556 var part = diff[i];
557 if (!part[1]) {
558 diff.splice(i--, 1);
559 } else if (i && diff[i - 1][0] == part[0]) {
560 diff.splice(i--, 1);
561 diff[i][1] += part[1];
562 }
563 }
564 return diff;
565 }
566
567 function getChunks(diff) {
568 var chunks = [];
569 var startEdit = 0, startOrig = 0;
570 var edit = Pos(0, 0), orig = Pos(0, 0);
571 for (var i = 0; i < diff.length; ++i) {
572 var part = diff[i], tp = part[0];
573 if (tp == DIFF_EQUAL) {
574 var startOff = startOfLineClean(diff, i) ? 0 : 1;
575 var cleanFromEdit = edit.line + startOff, cleanFromOrig = orig.line + startOff;
576 moveOver(edit, part[1], null, orig);
577 var endOff = endOfLineClean(diff, i) ? 1 : 0;
578 var cleanToEdit = edit.line + endOff, cleanToOrig = orig.line + endOff;
579 if (cleanToEdit > cleanFromEdit) {
580 if (i) chunks.push({origFrom: startOrig, origTo: cleanFromOrig,
581 editFrom: startEdit, editTo: cleanFromEdit});
582 startEdit = cleanToEdit; startOrig = cleanToOrig;
583 }
584 } else {
585 moveOver(tp == DIFF_INSERT ? edit : orig, part[1]);
586 }
587 }
588 if (startEdit <= edit.line || startOrig <= orig.line)
589 chunks.push({origFrom: startOrig, origTo: orig.line + 1,
590 editFrom: startEdit, editTo: edit.line + 1});
591 return chunks;
592 }
593
594 function endOfLineClean(diff, i) {
595 if (i == diff.length - 1) return true;
596 var next = diff[i + 1][1];
597 if (next.length == 1 || next.charCodeAt(0) != 10) return false;
598 if (i == diff.length - 2) return true;
599 next = diff[i + 2][1];
600 return next.length > 1 && next.charCodeAt(0) == 10;
601 }
602
603 function startOfLineClean(diff, i) {
604 if (i == 0) return true;
605 var last = diff[i - 1][1];
606 if (last.charCodeAt(last.length - 1) != 10) return false;
607 if (i == 1) return true;
608 last = diff[i - 2][1];
609 return last.charCodeAt(last.length - 1) == 10;
610 }
611
612 function chunkBoundariesAround(chunks, n, nInEdit) {
613 var beforeE, afterE, beforeO, afterO;
614 for (var i = 0; i < chunks.length; i++) {
615 var chunk = chunks[i];
616 var fromLocal = nInEdit ? chunk.editFrom : chunk.origFrom;
617 var toLocal = nInEdit ? chunk.editTo : chunk.origTo;
618 if (afterE == null) {
619 if (fromLocal > n) { afterE = chunk.editFrom; afterO = chunk.origFrom; }
620 else if (toLocal > n) { afterE = chunk.editTo; afterO = chunk.origTo; }
621 }
622 if (toLocal <= n) { beforeE = chunk.editTo; beforeO = chunk.origTo; }
623 else if (fromLocal <= n) { beforeE = chunk.editFrom; beforeO = chunk.origFrom; }
624 }
625 return {edit: {before: beforeE, after: afterE}, orig: {before: beforeO, after: afterO}};
626 }
627
628 function collapseSingle(cm, from, to) {
629 cm.addLineClass(from, "wrap", "CodeMirror-merge-collapsed-line");
630 var widget = document.createElement("span");
631 widget.className = "CodeMirror-merge-collapsed-widget";
632 widget.title = "Identical text collapsed. Click to expand.";
633 var mark = cm.markText(Pos(from, 0), Pos(to - 1), {
634 inclusiveLeft: true,
635 inclusiveRight: true,
636 replacedWith: widget,
637 clearOnEnter: true
638 });
639 function clear() {
640 mark.clear();
641 cm.removeLineClass(from, "wrap", "CodeMirror-merge-collapsed-line");
642 }
643 widget.addEventListener("click", clear);
644 return {mark: mark, clear: clear};
645 }
646
647 function collapseStretch(size, editors) {
648 var marks = [];
649 function clear() {
650 for (var i = 0; i < marks.length; i++) marks[i].clear();
651 }
652 for (var i = 0; i < editors.length; i++) {
653 var editor = editors[i];
654 var mark = collapseSingle(editor.cm, editor.line, editor.line + size);
655 marks.push(mark);
656 mark.mark.on("clear", clear);
657 }
658 return marks[0].mark;
659 }
660
661 function unclearNearChunks(dv, margin, off, clear) {
662 for (var i = 0; i < dv.chunks.length; i++) {
663 var chunk = dv.chunks[i];
664 for (var l = chunk.editFrom - margin; l < chunk.editTo + margin; l++) {
665 var pos = l + off;
666 if (pos >= 0 && pos < clear.length) clear[pos] = false;
667 }
668 }
669 }
670
671 function collapseIdenticalStretches(mv, margin) {
672 if (typeof margin != "number") margin = 2;
673 var clear = [], edit = mv.editor(), off = edit.firstLine();
674 for (var l = off, e = edit.lastLine(); l <= e; l++) clear.push(true);
675 if (mv.left) unclearNearChunks(mv.left, margin, off, clear);
676 if (mv.right) unclearNearChunks(mv.right, margin, off, clear);
677
678 for (var i = 0; i < clear.length; i++) {
679 if (clear[i]) {
680 var line = i + off;
681 for (var size = 1; i < clear.length - 1 && clear[i + 1]; i++, size++) {}
682 if (size > margin) {
683 var editors = [{line: line, cm: edit}];
684 if (mv.left) editors.push({line: getMatchingOrigLine(line, mv.left.chunks), cm: mv.left.orig});
685 if (mv.right) editors.push({line: getMatchingOrigLine(line, mv.right.chunks), cm: mv.right.orig});
686 var mark = collapseStretch(size, editors);
687 if (mv.options.onCollapse) mv.options.onCollapse(mv, line, size, mark);
688 }
689 }
690 }
691 }
692
693 // General utilities
694
695 function elt(tag, content, className, style) {
696 var e = document.createElement(tag);
697 if (className) e.className = className;
698 if (style) e.style.cssText = style;
699 if (typeof content == "string") e.appendChild(document.createTextNode(content));
700 else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]);
701 return e;
702 }
703
704 function clear(node) {
705 for (var count = node.childNodes.length; count > 0; --count)
706 node.removeChild(node.firstChild);
707 }
708
709 function attrs(elt) {
710 for (var i = 1; i < arguments.length; i += 2)
711 elt.setAttribute(arguments[i], arguments[i+1]);
712 }
713
714 function copyObj(obj, target) {
715 if (!target) target = {};
716 for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop];
717 return target;
718 }
719
720 function moveOver(pos, str, copy, other) {
721 var out = copy ? Pos(pos.line, pos.ch) : pos, at = 0;
722 for (;;) {
723 var nl = str.indexOf("\n", at);
724 if (nl == -1) break;
725 ++out.line;
726 if (other) ++other.line;
727 at = nl + 1;
728 }
729 out.ch = (at ? 0 : out.ch) + (str.length - at);
730 if (other) other.ch = (at ? 0 : other.ch) + (str.length - at);
731 return out;
732 }
733
734 function posMin(a, b) { return (a.line - b.line || a.ch - b.ch) < 0 ? a : b; }
735 function posMax(a, b) { return (a.line - b.line || a.ch - b.ch) > 0 ? a : b; }
736 function posEq(a, b) { return a.line == b.line && a.ch == b.ch; }
737
738 function findPrevDiff(chunks, start, isOrig) {
739 for (var i = chunks.length - 1; i >= 0; i--) {
740 var chunk = chunks[i];
741 var to = (isOrig ? chunk.origTo : chunk.editTo) - 1;
742 if (to < start) return to;
743 }
744 }
745
746 function findNextDiff(chunks, start, isOrig) {
747 for (var i = 0; i < chunks.length; i++) {
748 var chunk = chunks[i];
749 var from = (isOrig ? chunk.origFrom : chunk.editFrom);
750 if (from > start) return from;
751 }
752 }
753
754 function goNearbyDiff(cm, dir) {
755 var found = null, views = cm.state.diffViews, line = cm.getCursor().line;
756 if (views) for (var i = 0; i < views.length; i++) {
757 var dv = views[i], isOrig = cm == dv.orig;
758 ensureDiff(dv);
759 var pos = dir < 0 ? findPrevDiff(dv.chunks, line, isOrig) : findNextDiff(dv.chunks, line, isOrig);
760 if (pos != null && (found == null || (dir < 0 ? pos > found : pos < found)))
761 found = pos;
762 }
763 if (found != null)
764 cm.setCursor(found, 0);
765 else
766 return CodeMirror.Pass;
767 }
768
769 CodeMirror.commands.goNextDiff = function(cm) {
770 return goNearbyDiff(cm, 1);
771 };
772 CodeMirror.commands.goPrevDiff = function(cm) {
773 return goNearbyDiff(cm, -1);
774 };
775 });
776