codemirror-rails 4.8 → 4.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +24 -6
  3. data/lib/codemirror/rails/version.rb +2 -2
  4. data/vendor/assets/javascripts/codemirror.js +289 -184
  5. data/vendor/assets/javascripts/codemirror/addons/dialog/dialog.js +1 -1
  6. data/vendor/assets/javascripts/codemirror/addons/display/panel.js +94 -0
  7. data/vendor/assets/javascripts/codemirror/addons/edit/continuelist.js +4 -4
  8. data/vendor/assets/javascripts/codemirror/addons/hint/anyword-hint.js +1 -2
  9. data/vendor/assets/javascripts/codemirror/addons/hint/css-hint.js +1 -1
  10. data/vendor/assets/javascripts/codemirror/addons/hint/html-hint.js +1 -1
  11. data/vendor/assets/javascripts/codemirror/addons/hint/javascript-hint.js +8 -3
  12. data/vendor/assets/javascripts/codemirror/addons/hint/show-hint.js +1 -1
  13. data/vendor/assets/javascripts/codemirror/addons/hint/sql-hint.js +7 -4
  14. data/vendor/assets/javascripts/codemirror/addons/hint/xml-hint.js +3 -4
  15. data/vendor/assets/javascripts/codemirror/addons/merge/merge.js +170 -63
  16. data/vendor/assets/javascripts/codemirror/addons/mode/simple.js +11 -8
  17. data/vendor/assets/javascripts/codemirror/addons/scroll/annotatescrollbar.js +76 -0
  18. data/vendor/assets/javascripts/codemirror/addons/scroll/simplescrollbars.js +139 -0
  19. data/vendor/assets/javascripts/codemirror/addons/search/matchesonscrollbar.js +90 -0
  20. data/vendor/assets/javascripts/codemirror/addons/search/search.js +9 -4
  21. data/vendor/assets/javascripts/codemirror/addons/tern/tern.js +5 -3
  22. data/vendor/assets/javascripts/codemirror/keymaps/emacs.js +25 -7
  23. data/vendor/assets/javascripts/codemirror/keymaps/vim.js +181 -109
  24. data/vendor/assets/javascripts/codemirror/modes/coffeescript.js +2 -2
  25. data/vendor/assets/javascripts/codemirror/modes/commonlisp.js +5 -3
  26. data/vendor/assets/javascripts/codemirror/modes/cypher.js +1 -1
  27. data/vendor/assets/javascripts/codemirror/modes/dart.js +50 -0
  28. data/vendor/assets/javascripts/codemirror/modes/dockerfile.js +5 -9
  29. data/vendor/assets/javascripts/codemirror/modes/ebnf.js +195 -0
  30. data/vendor/assets/javascripts/codemirror/modes/javascript.js +2 -0
  31. data/vendor/assets/javascripts/codemirror/modes/markdown.js +3 -3
  32. data/vendor/assets/javascripts/codemirror/modes/puppet.js +2 -2
  33. data/vendor/assets/javascripts/codemirror/modes/shell.js +2 -1
  34. data/vendor/assets/javascripts/codemirror/modes/soy.js +198 -0
  35. data/vendor/assets/javascripts/codemirror/modes/spreadsheet.js +109 -0
  36. data/vendor/assets/javascripts/codemirror/modes/stex.js +4 -6
  37. data/vendor/assets/javascripts/codemirror/modes/textile.js +392 -476
  38. data/vendor/assets/javascripts/codemirror/modes/turtle.js +3 -1
  39. data/vendor/assets/stylesheets/codemirror.css +2 -11
  40. data/vendor/assets/stylesheets/codemirror/addons/merge/merge.css +14 -0
  41. data/vendor/assets/stylesheets/codemirror/addons/scroll/simplescrollbars.css +66 -0
  42. data/vendor/assets/stylesheets/codemirror/addons/search/matchesonscrollbar.css +8 -0
  43. data/vendor/assets/stylesheets/codemirror/themes/tomorrow-night-bright.css +35 -0
  44. data/vendor/assets/stylesheets/codemirror/themes/zenburn.css +37 -0
  45. metadata +14 -3
  46. data/vendor/assets/javascripts/codemirror/addons/hint/python-hint.js +0 -102
@@ -73,7 +73,7 @@
73
73
  CodeMirror.e_stop(e);
74
74
  close();
75
75
  }
76
- if (e.keyCode == 13) callback(inp.value);
76
+ if (e.keyCode == 13) callback(inp.value, e);
77
77
  });
78
78
 
79
79
  if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
@@ -0,0 +1,94 @@
1
+ // CodeMirror, copyright (c) by Marijn Haverbeke and others
2
+ // Distributed under an MIT license: http://codemirror.net/LICENSE
3
+
4
+ (function(mod) {
5
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
6
+ mod(require("../../lib/codemirror"));
7
+ else if (typeof define == "function" && define.amd) // AMD
8
+ define(["../../lib/codemirror"], mod);
9
+ else // Plain browser env
10
+ mod(CodeMirror);
11
+ })(function(CodeMirror) {
12
+ CodeMirror.defineExtension("addPanel", function(node, options) {
13
+ if (!this.state.panels) initPanels(this);
14
+
15
+ var info = this.state.panels;
16
+ if (options && options.position == "bottom")
17
+ info.wrapper.appendChild(node);
18
+ else
19
+ info.wrapper.insertBefore(node, info.wrapper.firstChild);
20
+ var height = (options && options.height) || node.offsetHeight;
21
+ this._setSize(null, info.heightLeft -= height);
22
+ info.panels++;
23
+ return new Panel(this, node, options, height);
24
+ });
25
+
26
+ function Panel(cm, node, options, height) {
27
+ this.cm = cm;
28
+ this.node = node;
29
+ this.options = options;
30
+ this.height = height;
31
+ this.cleared = false;
32
+ }
33
+
34
+ Panel.prototype.clear = function() {
35
+ if (this.cleared) return;
36
+ this.cleared = true;
37
+ var info = this.cm.state.panels;
38
+ this.cm._setSize(null, info.heightLeft += this.height);
39
+ info.wrapper.removeChild(this.node);
40
+ if (--info.panels == 0) removePanels(this.cm);
41
+ };
42
+
43
+ Panel.prototype.changed = function(height) {
44
+ var newHeight = height == null ? this.node.offsetHeight : height;
45
+ var info = this.cm.state.panels;
46
+ this.cm._setSize(null, info.height += (newHeight - this.height));
47
+ this.height = newHeight;
48
+ };
49
+
50
+ function initPanels(cm) {
51
+ var wrap = cm.getWrapperElement();
52
+ var style = window.getComputedStyle ? window.getComputedStyle(wrap) : wrap.currentStyle;
53
+ var height = parseInt(style.height);
54
+ var info = cm.state.panels = {
55
+ setHeight: wrap.style.height,
56
+ heightLeft: height,
57
+ panels: 0,
58
+ wrapper: document.createElement("div")
59
+ };
60
+ wrap.parentNode.insertBefore(info.wrapper, wrap);
61
+ var hasFocus = cm.hasFocus();
62
+ info.wrapper.appendChild(wrap);
63
+ if (hasFocus) cm.focus();
64
+
65
+ cm._setSize = cm.setSize;
66
+ if (height != null) cm.setSize = function(width, newHeight) {
67
+ if (newHeight == null) return this._setSize(width, newHeight);
68
+ info.setHeight = newHeight;
69
+ if (typeof newHeight != "number") {
70
+ var px = /^(\d+\.?\d*)px$/.exec(newHeight);
71
+ if (px) {
72
+ newHeight = Number(px[1]);
73
+ } else {
74
+ info.wrapper.style.height = newHeight;
75
+ newHeight = info.wrapper.offsetHeight;
76
+ info.wrapper.style.height = "";
77
+ }
78
+ }
79
+ cm._setSize(width, info.heightLeft += (newHeight - height));
80
+ height = newHeight;
81
+ };
82
+ }
83
+
84
+ function removePanels(cm) {
85
+ var info = cm.state.panels;
86
+ cm.state.panels = null;
87
+
88
+ var wrap = cm.getWrapperElement();
89
+ info.wrapper.parentNode.replaceChild(wrap, info.wrapper);
90
+ wrap.style.height = info.setHeight;
91
+ cm.setSize = cm._setSize;
92
+ cm.setSize();
93
+ }
94
+ });
@@ -11,9 +11,9 @@
11
11
  })(function(CodeMirror) {
12
12
  "use strict";
13
13
 
14
- var listRE = /^(\s*)([> ]+|[*+-]|(\d+)\.)(\s+)/,
15
- emptyListRE = /^(\s*)([> ]+|[*+-]|(\d+)\.)(\s*)$/,
16
- unorderedBullets = "*+-";
14
+ var listRE = /^(\s*)(>[> ]*|[*+-]\s|(\d+)\.)(\s*)/,
15
+ emptyListRE = /^(\s*)(>[> ]*|[*+-]|(\d+)\.)(\s*)$/,
16
+ unorderedListRE = /[*+-]\s/;
17
17
 
18
18
  CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) {
19
19
  if (cm.getOption("disableInput")) return CodeMirror.Pass;
@@ -38,7 +38,7 @@
38
38
 
39
39
  } else {
40
40
  var indent = match[1], after = match[4];
41
- var bullet = unorderedBullets.indexOf(match[2]) >= 0 || match[2].indexOf(">") >= 0
41
+ var bullet = unorderedListRE.test(match[2]) || match[2].indexOf(">") >= 0
42
42
  ? match[2]
43
43
  : (parseInt(match[3], 10) + 1) + ".";
44
44
 
@@ -17,8 +17,7 @@
17
17
  var word = options && options.word || WORD;
18
18
  var range = options && options.range || RANGE;
19
19
  var cur = editor.getCursor(), curLine = editor.getLine(cur.line);
20
- var start = cur.ch, end = start;
21
- while (end < curLine.length && word.test(curLine.charAt(end))) ++end;
20
+ var end = cur.ch, start = end;
22
21
  while (start && word.test(curLine.charAt(start - 1))) --start;
23
22
  var curWord = start != end && curLine.slice(start, end);
24
23
 
@@ -20,7 +20,7 @@
20
20
  var inner = CodeMirror.innerMode(cm.getMode(), token.state);
21
21
  if (inner.mode.name != "css") return;
22
22
 
23
- var word = token.string, start = token.start, end = token.end;
23
+ var start = token.start, end = cur.ch, word = token.string.slice(0, end - start);
24
24
  if (/[^\w$_-]/.test(word)) {
25
25
  word = ""; start = end = cur.ch;
26
26
  }
@@ -3,7 +3,7 @@
3
3
 
4
4
  (function(mod) {
5
5
  if (typeof exports == "object" && typeof module == "object") // CommonJS
6
- mod(require("../../lib/codemirror", "./xml-hint"));
6
+ mod(require("../../lib/codemirror"), require("./xml-hint"));
7
7
  else if (typeof define == "function" && define.amd) // AMD
8
8
  define(["../../lib/codemirror", "./xml-hint"], mod);
9
9
  else // Plain browser env
@@ -30,15 +30,20 @@
30
30
 
31
31
  function scriptHint(editor, keywords, getToken, options) {
32
32
  // Find the token at the cursor
33
- var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token;
33
+ var cur = editor.getCursor(), token = getToken(editor, cur);
34
34
  if (/\b(?:string|comment)\b/.test(token.type)) return;
35
35
  token.state = CodeMirror.innerMode(editor.getMode(), token.state).state;
36
36
 
37
37
  // If it's not a 'word-style' token, ignore the token.
38
38
  if (!/^[\w$_]*$/.test(token.string)) {
39
- token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state,
40
- type: token.string == "." ? "property" : null};
39
+ token = {start: cur.ch, end: cur.ch, string: "", state: token.state,
40
+ type: token.string == "." ? "property" : null};
41
+ } else if (token.end > cur.ch) {
42
+ token.end = cur.ch;
43
+ token.string = token.string.slice(0, cur.ch - token.start);
41
44
  }
45
+
46
+ var tprop = token;
42
47
  // If it is a property, find out what it is a property of.
43
48
  while (tprop.type == "property") {
44
49
  tprop = getToken(editor, Pos(cur.line, tprop.start));
@@ -243,7 +243,7 @@
243
243
  }
244
244
  }
245
245
  }
246
- var overlapX = box.left - winW;
246
+ var overlapX = box.right - winW;
247
247
  if (overlapX > 0) {
248
248
  if (box.right - box.left > winW) {
249
249
  hints.style.width = (winW - 5) + "px";
@@ -44,9 +44,7 @@
44
44
  }
45
45
  }
46
46
 
47
- function nameCompletion(result, editor) {
48
- var cur = editor.getCursor();
49
- var token = editor.getTokenAt(cur);
47
+ function nameCompletion(cur, token, result, editor) {
50
48
  var useBacktick = (token.string.charAt(0) == "`");
51
49
  var string = token.string.substr(1);
52
50
  var prevToken = editor.getTokenAt(Pos(cur.line, token.start));
@@ -173,6 +171,11 @@
173
171
  var cur = editor.getCursor();
174
172
  var result = [];
175
173
  var token = editor.getTokenAt(cur), start, end, search;
174
+ if (token.end > cur.ch) {
175
+ token.end = cur.ch;
176
+ token.string = token.string.slice(0, cur.ch - token.start);
177
+ }
178
+
176
179
  if (token.string.match(/^[.`\w@]\w*$/)) {
177
180
  search = token.string;
178
181
  start = token.start;
@@ -182,7 +185,7 @@
182
185
  search = "";
183
186
  }
184
187
  if (search.charAt(0) == "." || search.charAt(0) == "`") {
185
- nameCompletion(result, editor);
188
+ nameCompletion(cur, token, result, editor);
186
189
  } else {
187
190
  addMatches(result, search, tables, function(w) {return w;});
188
191
  addMatches(result, search, defaultTable, function(w) {return w;});
@@ -18,10 +18,9 @@
18
18
  var quote = (options && options.quoteChar) || '"';
19
19
  if (!tags) return;
20
20
  var cur = cm.getCursor(), token = cm.getTokenAt(cur);
21
- if (/^<\/?$/.test(token.string) && token.end == cur.ch) {
22
- var nextToken = cm.getTokenAt(Pos(cur.line, cur.ch + 1));
23
- if (nextToken.start == cur.ch && /\btag\b/.test(nextToken.type))
24
- token = nextToken;
21
+ if (token.end > cur.ch) {
22
+ token.end = cur.ch;
23
+ token.string = token.string.slice(0, cur.ch - token.start);
25
24
  }
26
25
  var inner = CodeMirror.innerMode(cm.getMode(), token.state);
27
26
  if (inner.mode.name != "xml") return;
@@ -31,6 +31,8 @@
31
31
  insert: "CodeMirror-merge-r-inserted",
32
32
  del: "CodeMirror-merge-r-deleted",
33
33
  connect: "CodeMirror-merge-r-connect"};
34
+ if (mv.options.connect == "align")
35
+ this.aligners = [];
34
36
  }
35
37
 
36
38
  DiffView.prototype = {
@@ -81,7 +83,7 @@
81
83
  updateMarks(dv.edit, dv.diff, edit, DIFF_INSERT, dv.classes);
82
84
  updateMarks(dv.orig, dv.diff, orig, DIFF_DELETE, dv.classes);
83
85
  }
84
- drawConnectors(dv);
86
+ makeConnections(dv);
85
87
  }
86
88
  function set(slow) {
87
89
  clearTimeout(debounceChange);
@@ -108,10 +110,10 @@
108
110
 
109
111
  function registerScroll(dv) {
110
112
  dv.edit.on("scroll", function() {
111
- syncScroll(dv, DIFF_INSERT) && drawConnectors(dv);
113
+ syncScroll(dv, DIFF_INSERT) && makeConnections(dv);
112
114
  });
113
115
  dv.orig.on("scroll", function() {
114
- syncScroll(dv, DIFF_DELETE) && drawConnectors(dv);
116
+ syncScroll(dv, DIFF_DELETE) && makeConnections(dv);
115
117
  });
116
118
  }
117
119
 
@@ -126,24 +128,29 @@
126
128
  // (to prevent feedback loops)
127
129
  if (editor.state.scrollSetBy == dv && (editor.state.scrollSetAt || 0) + 50 > now) return false;
128
130
 
129
- var sInfo = editor.getScrollInfo(), halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen;
130
- var mid = editor.lineAtHeight(midY, "local");
131
- var around = chunkBoundariesAround(dv.diff, mid, type == DIFF_INSERT);
132
- var off = getOffsets(editor, type == DIFF_INSERT ? around.edit : around.orig);
133
- var offOther = getOffsets(other, type == DIFF_INSERT ? around.orig : around.edit);
134
- var ratio = (midY - off.top) / (off.bot - off.top);
135
- var targetPos = (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top);
136
-
137
- var botDist, mix;
138
- // Some careful tweaking to make sure no space is left out of view
139
- // when scrolling to top or bottom.
140
- if (targetPos > sInfo.top && (mix = sInfo.top / halfScreen) < 1) {
141
- targetPos = targetPos * mix + sInfo.top * (1 - mix);
142
- } else if ((botDist = sInfo.height - sInfo.clientHeight - sInfo.top) < halfScreen) {
143
- var otherInfo = other.getScrollInfo();
144
- var botDistOther = otherInfo.height - otherInfo.clientHeight - targetPos;
145
- if (botDistOther > botDist && (mix = botDist / halfScreen) < 1)
146
- targetPos = targetPos * mix + (otherInfo.height - otherInfo.clientHeight - botDist) * (1 - mix);
131
+ var sInfo = editor.getScrollInfo();
132
+ if (dv.mv.options.connect == "align") {
133
+ targetPos = sInfo.top;
134
+ } else {
135
+ var halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen;
136
+ var mid = editor.lineAtHeight(midY, "local");
137
+ var around = chunkBoundariesAround(dv.diff, mid, type == DIFF_INSERT);
138
+ var off = getOffsets(editor, type == DIFF_INSERT ? around.edit : around.orig);
139
+ var offOther = getOffsets(other, type == DIFF_INSERT ? around.orig : around.edit);
140
+ var ratio = (midY - off.top) / (off.bot - off.top);
141
+ var targetPos = (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top);
142
+
143
+ var botDist, mix;
144
+ // Some careful tweaking to make sure no space is left out of view
145
+ // when scrolling to top or bottom.
146
+ if (targetPos > sInfo.top && (mix = sInfo.top / halfScreen) < 1) {
147
+ targetPos = targetPos * mix + sInfo.top * (1 - mix);
148
+ } else if ((botDist = sInfo.height - sInfo.clientHeight - sInfo.top) < halfScreen) {
149
+ var otherInfo = other.getScrollInfo();
150
+ var botDistOther = otherInfo.height - otherInfo.clientHeight - targetPos;
151
+ if (botDistOther > botDist && (mix = botDist / halfScreen) < 1)
152
+ targetPos = targetPos * mix + (otherInfo.height - otherInfo.clientHeight - botDist) * (1 - mix);
153
+ }
147
154
  }
148
155
 
149
156
  other.scrollTo(sInfo.left, targetPos);
@@ -161,7 +168,7 @@
161
168
 
162
169
  function setScrollLock(dv, val, action) {
163
170
  dv.lockScroll = val;
164
- if (val && action != false) syncScroll(dv, DIFF_INSERT) && drawConnectors(dv);
171
+ if (val && action != false) syncScroll(dv, DIFF_INSERT) && makeConnections(dv);
165
172
  dv.lockButton.innerHTML = val ? "\u21db\u21da" : "\u21db&nbsp;&nbsp;\u21da";
166
173
  }
167
174
 
@@ -249,9 +256,20 @@
249
256
 
250
257
  // Updating the gap between editor and original
251
258
 
252
- function drawConnectors(dv) {
259
+ function makeConnections(dv) {
253
260
  if (!dv.showDifferences) return;
254
261
 
262
+ var align = dv.mv.options.connect == "align";
263
+ if (align) {
264
+ if (!dv.orig.curOp) return dv.orig.operation(function() {
265
+ makeConnections(dv);
266
+ });
267
+ for (var i = 0; i < dv.aligners.length; i++)
268
+ dv.aligners[i].clear();
269
+ dv.aligners.length = 0;
270
+ var extraSpaceAbove = {edit: 0, orig: 0};
271
+ }
272
+
255
273
  if (dv.svg) {
256
274
  clear(dv.svg);
257
275
  var w = dv.gap.offsetWidth;
@@ -259,45 +277,82 @@
259
277
  }
260
278
  if (dv.copyButtons) clear(dv.copyButtons);
261
279
 
262
- var flip = dv.type == "left";
263
280
  var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport();
264
281
  var sTopEdit = dv.edit.getScrollInfo().top, sTopOrig = dv.orig.getScrollInfo().top;
265
282
  iterateChunks(dv.diff, function(topOrig, botOrig, topEdit, botEdit) {
266
- if (topEdit > vpEdit.to || botEdit < vpEdit.from ||
267
- topOrig > vpOrig.to || botOrig < vpOrig.from)
268
- return;
269
- var topLpx = dv.orig.heightAtLine(topOrig, "local") - sTopOrig, top = topLpx;
270
- if (dv.svg) {
271
- var topRpx = dv.edit.heightAtLine(topEdit, "local") - sTopEdit;
272
- if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; }
273
- var botLpx = dv.orig.heightAtLine(botOrig, "local") - sTopOrig;
274
- var botRpx = dv.edit.heightAtLine(botEdit, "local") - sTopEdit;
275
- if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; }
276
- var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx;
277
- var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx;
278
- attrs(dv.svg.appendChild(document.createElementNS(svgNS, "path")),
279
- "d", "M -1 " + topRpx + curveTop + " L " + (w + 2) + " " + botLpx + curveBot + " z",
280
- "class", dv.classes.connect);
281
- }
282
- if (dv.copyButtons) {
283
- var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc",
284
- "CodeMirror-merge-copy"));
285
- var editOriginals = dv.mv.options.allowEditingOriginals;
286
- copy.title = editOriginals ? "Push to left" : "Revert chunk";
287
- copy.chunk = {topEdit: topEdit, botEdit: botEdit, topOrig: topOrig, botOrig: botOrig};
288
- copy.style.top = top + "px";
289
-
290
- if (editOriginals) {
291
- var topReverse = dv.orig.heightAtLine(topEdit, "local") - sTopEdit;
292
- var copyReverse = dv.copyButtons.appendChild(elt("div", dv.type == "right" ? "\u21dd" : "\u21dc",
293
- "CodeMirror-merge-copy-reverse"));
294
- copyReverse.title = "Push to right";
295
- copyReverse.chunk = {topEdit: topOrig, botEdit: botOrig, topOrig: topEdit, botOrig: botEdit};
296
- copyReverse.style.top = topReverse + "px";
297
- dv.type == "right" ? copyReverse.style.left = "2px" : copyReverse.style.right = "2px";
298
- }
283
+ if (topEdit <= vpEdit.to && botEdit >= vpEdit.from &&
284
+ topOrig <= vpOrig.to && botOrig >= vpOrig.from)
285
+ drawConnectorsForChunk(dv, topOrig, botOrig, topEdit, botEdit, sTopOrig, sTopEdit, w);
286
+ if (align && (topEdit <= vpEdit.to || topOrig <= vpOrig.to)) {
287
+ var above = (botEdit < vpEdit.from && botOrig < vpOrig.from);
288
+ alignChunks(dv, topOrig, botOrig, topEdit, botEdit, above && extraSpaceAbove);
299
289
  }
300
290
  });
291
+ if (align) {
292
+ if (extraSpaceAbove.edit)
293
+ dv.aligners.push(padBelow(dv.edit, 0, extraSpaceAbove.edit));
294
+ if (extraSpaceAbove.orig)
295
+ dv.aligners.push(padBelow(dv.orig, 0, extraSpaceAbove.orig));
296
+ }
297
+ }
298
+
299
+ function drawConnectorsForChunk(dv, topOrig, botOrig, topEdit, botEdit, sTopOrig, sTopEdit, w) {
300
+ var flip = dv.type == "left";
301
+ var top = dv.orig.heightAtLine(topOrig, "local") - sTopOrig;
302
+ if (dv.svg) {
303
+ var topLpx = top;
304
+ var topRpx = dv.edit.heightAtLine(topEdit, "local") - sTopEdit;
305
+ if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; }
306
+ var botLpx = dv.orig.heightAtLine(botOrig, "local") - sTopOrig;
307
+ var botRpx = dv.edit.heightAtLine(botEdit, "local") - sTopEdit;
308
+ if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; }
309
+ var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx;
310
+ var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx;
311
+ attrs(dv.svg.appendChild(document.createElementNS(svgNS, "path")),
312
+ "d", "M -1 " + topRpx + curveTop + " L " + (w + 2) + " " + botLpx + curveBot + " z",
313
+ "class", dv.classes.connect);
314
+ }
315
+ if (dv.copyButtons) {
316
+ var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc",
317
+ "CodeMirror-merge-copy"));
318
+ var editOriginals = dv.mv.options.allowEditingOriginals;
319
+ copy.title = editOriginals ? "Push to left" : "Revert chunk";
320
+ copy.chunk = {topEdit: topEdit, botEdit: botEdit, topOrig: topOrig, botOrig: botOrig};
321
+ copy.style.top = top + "px";
322
+
323
+ if (editOriginals) {
324
+ var topReverse = dv.orig.heightAtLine(topEdit, "local") - sTopEdit;
325
+ var copyReverse = dv.copyButtons.appendChild(elt("div", dv.type == "right" ? "\u21dd" : "\u21dc",
326
+ "CodeMirror-merge-copy-reverse"));
327
+ copyReverse.title = "Push to right";
328
+ copyReverse.chunk = {topEdit: topOrig, botEdit: botOrig, topOrig: topEdit, botOrig: botEdit};
329
+ copyReverse.style.top = topReverse + "px";
330
+ dv.type == "right" ? copyReverse.style.left = "2px" : copyReverse.style.right = "2px";
331
+ }
332
+ }
333
+ }
334
+
335
+ function alignChunks(dv, topOrig, botOrig, topEdit, botEdit, aboveViewport) {
336
+ var topOrigPx = dv.orig.heightAtLine(topOrig, "local");
337
+ var botOrigPx = dv.orig.heightAtLine(botOrig, "local");
338
+ var topEditPx = dv.edit.heightAtLine(topEdit, "local");
339
+ var botEditPx = dv.edit.heightAtLine(botEdit, "local");
340
+ var origH = botOrigPx -topOrigPx, editH = botEditPx - topEditPx;
341
+ var diff = editH - origH;
342
+ if (diff > 1) {
343
+ if (aboveViewport) aboveViewport.orig += diff;
344
+ else dv.aligners.push(padBelow(dv.orig, botOrig - 1, diff));
345
+ } else if (diff < -1) {
346
+ if (aboveViewport) aboveViewport.edit -= diff;
347
+ else dv.aligners.push(padBelow(dv.edit, botEdit - 1, -diff));
348
+ }
349
+ return 0;
350
+ }
351
+
352
+ function padBelow(cm, line, size) {
353
+ var elt = document.createElement("div");
354
+ elt.style.height = size + "px"; elt.style.minWidth = "1px";
355
+ return cm.addLineWidget(line, elt, {height: size});
301
356
  }
302
357
 
303
358
  function copyChunk(dv, to, from, chunk) {
@@ -313,6 +368,13 @@
313
368
 
314
369
  this.options = options;
315
370
  var origLeft = options.origLeft, origRight = options.origRight == null ? options.orig : options.origRight;
371
+ if (origLeft && origRight) {
372
+ if (options.connect == "align")
373
+ throw new Error("connect: \"align\" is not supported for three-way merge views");
374
+ if (options.collapseIdentical)
375
+ throw new Error("collapseIdentical option is not supported for three-way merge views");
376
+ }
377
+
316
378
  var hasLeft = origLeft != null, hasRight = origRight != null;
317
379
  var panes = 1 + (hasLeft ? 1 : 0) + (hasRight ? 1 : 0);
318
380
  var wrap = [], left = this.left = null, right = this.right = null;
@@ -344,9 +406,12 @@
344
406
  if (left) left.init(leftPane, origLeft, options);
345
407
  if (right) right.init(rightPane, origRight, options);
346
408
 
409
+ if (options.collapseIdentical)
410
+ collapseIdenticalStretches(left || right, options.collapseIdentical);
411
+
347
412
  var onResize = function() {
348
- if (left) drawConnectors(left);
349
- if (right) drawConnectors(right);
413
+ if (left) makeConnections(left);
414
+ if (right) makeConnections(right);
350
415
  };
351
416
  CodeMirror.on(window, "resize", onResize);
352
417
  var resizeInterval = setInterval(function() {
@@ -374,10 +439,12 @@
374
439
  });
375
440
  gapElts.unshift(dv.copyButtons);
376
441
  }
377
- var svg = document.createElementNS && document.createElementNS(svgNS, "svg");
378
- if (svg && !svg.createSVGRect) svg = null;
379
- dv.svg = svg;
380
- if (svg) gapElts.push(svg);
442
+ if (dv.mv.options.connect != "align") {
443
+ var svg = document.createElementNS && document.createElementNS(svgNS, "svg");
444
+ if (svg && !svg.createSVGRect) svg = null;
445
+ dv.svg = svg;
446
+ if (svg) gapElts.push(svg);
447
+ }
381
448
 
382
449
  return dv.gap = elt("div", gapElts, "CodeMirror-merge-gap");
383
450
  }
@@ -489,6 +556,46 @@
489
556
  return {edit: {before: beforeE, after: afterE}, orig: {before: beforeO, after: afterO}};
490
557
  }
491
558
 
559
+ function collapseSingle(cm, from, to) {
560
+ cm.addLineClass(from, "wrap", "CodeMirror-merge-collapsed-line");
561
+ var widget = document.createElement("span");
562
+ widget.className = "CodeMirror-merge-collapsed-widget";
563
+ widget.title = "Identical text collapsed. Click to expand.";
564
+ var mark = cm.markText(Pos(from, 0), Pos(to - 1), {
565
+ inclusiveLeft: true,
566
+ inclusiveRight: true,
567
+ replacedWith: widget,
568
+ clearOnEnter: true
569
+ });
570
+ function clear() {
571
+ mark.clear();
572
+ cm.removeLineClass(from, "wrap", "CodeMirror-merge-collapsed-line");
573
+ }
574
+ widget.addEventListener("click", clear);
575
+ return {mark: mark, clear: clear};
576
+ }
577
+
578
+ function collapseStretch(dv, origStart, editStart, size) {
579
+ var mOrig = collapseSingle(dv.orig, origStart, origStart + size);
580
+ var mEdit = collapseSingle(dv.edit, editStart, editStart + size);
581
+ mOrig.mark.on("clear", function() { mEdit.clear(); });
582
+ mEdit.mark.on("clear", function() { mOrig.clear(); });
583
+ }
584
+
585
+ function collapseIdenticalStretches(dv, margin) {
586
+ if (typeof margin != "number") margin = 2;
587
+ var lastOrig = dv.orig.firstLine(), lastEdit = dv.edit.firstLine();
588
+ iterateChunks(dv.diff, function(topOrig, botOrig, _topEdit, botEdit) {
589
+ var identicalSize = topOrig - margin - lastOrig;
590
+ if (identicalSize > margin)
591
+ collapseStretch(dv, lastOrig, lastEdit, identicalSize);
592
+ lastOrig = botOrig + margin; lastEdit = botEdit + margin;
593
+ });
594
+ var bottomSize = dv.orig.lastLine() + 1 - lastOrig;
595
+ if (bottomSize > margin)
596
+ collapseStretch(dv, lastOrig, lastEdit, bottomSize);
597
+ }
598
+
492
599
  // General utilities
493
600
 
494
601
  function elt(tag, content, className, style) {