codemirror-rails 4.8 → 4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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) {