codemirror-rails 3.21 → 3.22

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/codemirror/rails/version.rb +2 -2
  3. data/vendor/assets/javascripts/codemirror.js +89 -43
  4. data/vendor/assets/javascripts/codemirror/addons/comment/continuecomment.js +16 -3
  5. data/vendor/assets/javascripts/codemirror/addons/dialog/dialog.js +1 -0
  6. data/vendor/assets/javascripts/codemirror/addons/display/rulers.js +47 -0
  7. data/vendor/assets/javascripts/codemirror/addons/edit/closetag.js +1 -0
  8. data/vendor/assets/javascripts/codemirror/addons/fold/foldcode.js +6 -0
  9. data/vendor/assets/javascripts/codemirror/addons/fold/markdown-fold.js +34 -0
  10. data/vendor/assets/javascripts/codemirror/addons/hint/anyword-hint.js +2 -2
  11. data/vendor/assets/javascripts/codemirror/addons/hint/html-hint.js +0 -0
  12. data/vendor/assets/javascripts/codemirror/addons/hint/show-hint.js +25 -18
  13. data/vendor/assets/javascripts/codemirror/addons/lint/yaml-lint.js +14 -0
  14. data/vendor/assets/javascripts/codemirror/addons/runmode/runmode.node.js +2 -2
  15. data/vendor/assets/javascripts/codemirror/addons/scroll/scrollpastend.js +2 -0
  16. data/vendor/assets/javascripts/codemirror/addons/search/search.js +9 -3
  17. data/vendor/assets/javascripts/codemirror/keymaps/vim.js +126 -54
  18. data/vendor/assets/javascripts/codemirror/modes/clojure.js +6 -5
  19. data/vendor/assets/javascripts/codemirror/modes/css.js +19 -9
  20. data/vendor/assets/javascripts/codemirror/modes/gas.js +1 -1
  21. data/vendor/assets/javascripts/codemirror/modes/htmlmixed.js +4 -1
  22. data/vendor/assets/javascripts/codemirror/modes/javascript.js +11 -3
  23. data/vendor/assets/javascripts/codemirror/modes/julia.js +47 -23
  24. data/vendor/assets/javascripts/codemirror/modes/markdown.js +5 -3
  25. data/vendor/assets/javascripts/codemirror/modes/octave.js +6 -4
  26. data/vendor/assets/javascripts/codemirror/modes/puppet.js +204 -0
  27. data/vendor/assets/javascripts/codemirror/modes/python.js +4 -0
  28. data/vendor/assets/javascripts/codemirror/modes/rst.js +520 -517
  29. data/vendor/assets/javascripts/codemirror/modes/ruby.js +1 -0
  30. data/vendor/assets/javascripts/codemirror/modes/solr.js +89 -0
  31. data/vendor/assets/javascripts/codemirror/modes/sql.js +2 -2
  32. data/vendor/assets/javascripts/codemirror/modes/xml.js +2 -1
  33. data/vendor/assets/stylesheets/codemirror.css +12 -11
  34. data/vendor/assets/stylesheets/codemirror/themes/mdn-like.css +44 -0
  35. data/vendor/assets/stylesheets/codemirror/themes/solarized.css +1 -0
  36. metadata +8 -2
@@ -39,6 +39,7 @@
39
39
  CodeMirror.on(inp, "keydown", function(e) {
40
40
  if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }
41
41
  if (e.keyCode == 13 || e.keyCode == 27) {
42
+ inp.blur();
42
43
  CodeMirror.e_stop(e);
43
44
  close();
44
45
  me.focus();
@@ -0,0 +1,47 @@
1
+ (function() {
2
+ "use strict";
3
+
4
+ CodeMirror.defineOption("rulers", false, function(cm, val, old) {
5
+ if (old && old != CodeMirror.Init) {
6
+ clearRulers(cm);
7
+ cm.off("refresh", refreshRulers);
8
+ }
9
+ if (val && val.length) {
10
+ setRulers(cm);
11
+ cm.on("refresh", refreshRulers);
12
+ }
13
+ });
14
+
15
+ function clearRulers(cm) {
16
+ for (var i = cm.display.lineSpace.childNodes.length - 1; i >= 0; i--) {
17
+ var node = cm.display.lineSpace.childNodes[i];
18
+ if (/(^|\s)CodeMirror-ruler($|\s)/.test(node.className))
19
+ node.parentNode.removeChild(node);
20
+ }
21
+ }
22
+
23
+ function setRulers(cm) {
24
+ var val = cm.getOption("rulers");
25
+ var cw = cm.defaultCharWidth();
26
+ var left = cm.charCoords(CodeMirror.Pos(cm.firstLine(), 0), "div").left;
27
+ var bot = -cm.display.scroller.offsetHeight;
28
+ for (var i = 0; i < val.length; i++) {
29
+ var elt = document.createElement("div");
30
+ var col, cls = null;
31
+ if (typeof val[i] == "number") {
32
+ col = val[i];
33
+ } else {
34
+ col = val[i].column;
35
+ cls = val[i].className;
36
+ }
37
+ elt.className = "CodeMirror-ruler" + (cls ? " " + cls : "");
38
+ elt.style.cssText = "left: " + (left + col * cw) + "px; top: -50px; bottom: " + bot + "px";
39
+ cm.display.lineSpace.insertBefore(elt, cm.display.cursorDiv);
40
+ }
41
+ }
42
+
43
+ function refreshRulers(cm) {
44
+ clearRulers(cm);
45
+ setRulers(cm);
46
+ }
47
+ })();
@@ -82,6 +82,7 @@
82
82
 
83
83
  var tagName = state.context && state.context.tagName;
84
84
  if (tagName) cm.replaceSelection("/" + tagName + ">", "end");
85
+ else return CodeMirror.Pass;
85
86
  }
86
87
 
87
88
  function indexOf(collection, elt) {
@@ -62,6 +62,12 @@
62
62
  doFold(this, pos, options, force);
63
63
  });
64
64
 
65
+ CodeMirror.defineExtension("isFolded", function(pos) {
66
+ var marks = this.findMarksAt(pos);
67
+ for (var i = 0; i < marks.length; ++i)
68
+ if (marks[i].__isFold) return true;
69
+ });
70
+
65
71
  CodeMirror.commands.fold = function(cm) {
66
72
  cm.foldCode(cm.getCursor());
67
73
  };
@@ -0,0 +1,34 @@
1
+ CodeMirror.registerHelper("fold", "markdown", function(cm, start) {
2
+ var maxDepth = 100;
3
+
4
+ function isHeader(lineNo) {
5
+ var tokentype = cm.getTokenTypeAt(CodeMirror.Pos(lineNo, 0));
6
+ return tokentype && /\bheader\b/.test(tokentype);
7
+ }
8
+
9
+ function headerLevel(lineNo, line, nextLine) {
10
+ var match = line && line.match(/^#+/);
11
+ if (match && isHeader(lineNo)) return match[0].length;
12
+ match = nextLine && nextLine.match(/^[=\-]+\s*$/);
13
+ if (match && isHeader(lineNo + 1)) return nextLine[0] == "=" ? 1 : 2;
14
+ return maxDepth;
15
+ }
16
+
17
+ var firstLine = cm.getLine(start.line), nextLine = cm.getLine(start.line + 1);
18
+ var level = headerLevel(start.line, firstLine, nextLine);
19
+ if (level === maxDepth) return undefined;
20
+
21
+ var lastLineNo = cm.lastLine();
22
+ var end = start.line, nextNextLine = cm.getLine(end + 2);
23
+ while (end < lastLineNo) {
24
+ if (headerLevel(end + 1, nextLine, nextNextLine) <= level) break;
25
+ ++end;
26
+ nextLine = nextNextLine;
27
+ nextNextLine = cm.getLine(end + 2);
28
+ }
29
+
30
+ return {
31
+ from: CodeMirror.Pos(start.line, firstLine.length),
32
+ to: CodeMirror.Pos(end, cm.getLine(end).length)
33
+ };
34
+ });
@@ -15,8 +15,8 @@
15
15
  var list = [], seen = {};
16
16
  var re = new RegExp(word.source, "g");
17
17
  for (var dir = -1; dir <= 1; dir += 2) {
18
- var line = cur.line, end = Math.min(Math.max(line + dir * range, editor.firstLine()), editor.lastLine()) + dir;
19
- for (; line != end; line += dir) {
18
+ var line = cur.line, endLine = Math.min(Math.max(line + dir * range, editor.firstLine()), editor.lastLine()) + dir;
19
+ for (; line != endLine; line += dir) {
20
20
  var text = editor.getLine(line), m;
21
21
  while (m = re.exec(text)) {
22
22
  if (line == cur.line && m[0] === curWord) continue;
@@ -46,7 +46,7 @@
46
46
  pick: function(data, i) {
47
47
  var completion = data.list[i];
48
48
  if (completion.hint) completion.hint(this.cm, data, completion);
49
- else this.cm.replaceRange(getText(completion), data.from, data.to);
49
+ else this.cm.replaceRange(getText(completion), completion.from||data.from, completion.to||data.to);
50
50
  CodeMirror.signal(data, "pick", completion);
51
51
  this.close();
52
52
  },
@@ -193,8 +193,24 @@
193
193
  var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
194
194
  var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
195
195
  (options.container || document.body).appendChild(hints);
196
- var box = hints.getBoundingClientRect();
197
- var overlapX = box.right - winW, overlapY = box.bottom - winH;
196
+ var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
197
+ if (overlapY > 0) {
198
+ var height = box.bottom - box.top, curTop = box.top - (pos.bottom - pos.top);
199
+ if (curTop - height > 0) { // Fits above cursor
200
+ hints.style.top = (top = curTop - height) + "px";
201
+ below = false;
202
+ } else if (height > winH) {
203
+ hints.style.height = (winH - 5) + "px";
204
+ hints.style.top = (top = pos.bottom - box.top) + "px";
205
+ var cursor = cm.getCursor();
206
+ if (data.from.ch != cursor.ch) {
207
+ pos = cm.cursorCoords(cursor);
208
+ hints.style.left = (left = pos.left) + "px";
209
+ box = hints.getBoundingClientRect();
210
+ }
211
+ }
212
+ }
213
+ var overlapX = box.left - winW;
198
214
  if (overlapX > 0) {
199
215
  if (box.right - box.left > winW) {
200
216
  hints.style.width = (winW - 5) + "px";
@@ -202,17 +218,6 @@
202
218
  }
203
219
  hints.style.left = (left = pos.left - overlapX) + "px";
204
220
  }
205
- if (overlapY > 0) {
206
- var height = box.bottom - box.top;
207
- if (box.top - (pos.bottom - pos.top) - height > 0) {
208
- overlapY = height + (pos.bottom - pos.top);
209
- below = false;
210
- } else if (height > winH) {
211
- hints.style.height = (winH - 5) + "px";
212
- overlapY -= height - winH;
213
- }
214
- hints.style.top = (top = pos.bottom - overlapY) + "px";
215
- }
216
221
 
217
222
  cm.addKeyMap(this.keyMap = buildKeyMap(options, {
218
223
  moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
@@ -220,7 +225,8 @@
220
225
  menuSize: function() { return widget.screenAmount(); },
221
226
  length: completions.length,
222
227
  close: function() { completion.close(); },
223
- pick: function() { widget.pick(); }
228
+ pick: function() { widget.pick(); },
229
+ data: data
224
230
  }));
225
231
 
226
232
  if (options.closeOnUnfocus !== false) {
@@ -303,15 +309,16 @@
303
309
  };
304
310
 
305
311
  CodeMirror.registerHelper("hint", "auto", function(cm, options) {
306
- var helpers = cm.getHelpers(cm.getCursor(), "hint");
312
+ var helpers = cm.getHelpers(cm.getCursor(), "hint"), words;
307
313
  if (helpers.length) {
308
314
  for (var i = 0; i < helpers.length; i++) {
309
315
  var cur = helpers[i](cm, options);
310
316
  if (cur && cur.list.length) return cur;
311
317
  }
312
- } else {
313
- var words = cm.getHelper(cm.getCursor(), "hintWords");
318
+ } else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
314
319
  if (words) return CodeMirror.hint.fromList(cm, {words: words});
320
+ } else if (CodeMirror.hint.anyword) {
321
+ return CodeMirror.hint.anyword(cm, options);
315
322
  }
316
323
  });
317
324
 
@@ -0,0 +1,14 @@
1
+ // Depends on js-yaml.js from https://github.com/nodeca/js-yaml
2
+
3
+ // declare global: jsyaml
4
+
5
+ CodeMirror.registerHelper("lint", "yaml", function(text) {
6
+ var found = [];
7
+ try { jsyaml.load(text); }
8
+ catch(e) {
9
+ var loc = e.mark;
10
+ found.push({ from: CodeMirror.Pos(loc.line, loc.column), to: CodeMirror.Pos(loc.line, loc.column), message: e.message });
11
+ }
12
+ return found;
13
+ });
14
+ CodeMirror.yamlValidator = CodeMirror.lint.yaml; // deprecated
@@ -92,14 +92,14 @@ exports.resolveMode = function(spec) {
92
92
  else return spec || {name: "null"};
93
93
  };
94
94
  exports.getMode = function(options, spec) {
95
- spec = exports.resolveMode(mimeModes[spec]);
95
+ spec = exports.resolveMode(spec);
96
96
  var mfactory = modes[spec.name];
97
97
  if (!mfactory) throw new Error("Unknown mode: " + spec);
98
98
  return mfactory(options, spec);
99
99
  };
100
100
  exports.registerHelper = exports.registerGlobalHelper = Math.min;
101
101
 
102
- exports.runMode = function(string, modespec, callback) {
102
+ exports.runMode = function(string, modespec, callback, options) {
103
103
  var mode = exports.getMode({indentUnit: 2}, modespec);
104
104
  var lines = splitLines(string), state = (options && options.state) || exports.startState(mode);
105
105
  for (var i = 0, e = lines.length; i < e; ++i) {
@@ -4,11 +4,13 @@
4
4
  CodeMirror.defineOption("scrollPastEnd", false, function(cm, val, old) {
5
5
  if (old && old != CodeMirror.Init) {
6
6
  cm.off("change", onChange);
7
+ cm.off("refresh", updateBottomMargin);
7
8
  cm.display.lineSpace.parentNode.style.paddingBottom = "";
8
9
  cm.state.scrollPastEndPadding = null;
9
10
  }
10
11
  if (val) {
11
12
  cm.on("change", onChange);
13
+ cm.on("refresh", updateBottomMargin);
12
14
  updateBottomMargin(cm);
13
15
  }
14
16
  });
@@ -56,7 +56,13 @@
56
56
  }
57
57
  function parseQuery(query) {
58
58
  var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
59
- return isRE ? new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i") : query;
59
+ if (isRE) {
60
+ query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i");
61
+ if (query.test("")) query = /x^/;
62
+ } else if (query == "") {
63
+ query = /x^/;
64
+ }
65
+ return query;
60
66
  }
61
67
  var queryDialog =
62
68
  'Search: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
@@ -107,7 +113,7 @@
107
113
  for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
108
114
  if (typeof query != "string") {
109
115
  var match = cm.getRange(cursor.from(), cursor.to()).match(query);
110
- cursor.replace(text.replace(/\$(\d)/, function(_, i) {return match[i];}));
116
+ cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
111
117
  } else cursor.replace(text);
112
118
  }
113
119
  });
@@ -128,7 +134,7 @@
128
134
  };
129
135
  var doReplace = function(match) {
130
136
  cursor.replace(typeof query == "string" ? text :
131
- text.replace(/\$(\d)/, function(_, i) {return match[i];}));
137
+ text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
132
138
  advance();
133
139
  };
134
140
  advance();
@@ -74,7 +74,7 @@
74
74
  { keys: ['<S-BS>'], type: 'keyToKey', toKeys: ['b'] },
75
75
  { keys: ['<C-n>'], type: 'keyToKey', toKeys: ['j'] },
76
76
  { keys: ['<C-p>'], type: 'keyToKey', toKeys: ['k'] },
77
- { keys: ['C-['], type: 'keyToKey', toKeys: ['<Esc>'] },
77
+ { keys: ['<C-[>'], type: 'keyToKey', toKeys: ['<Esc>'] },
78
78
  { keys: ['<C-c>'], type: 'keyToKey', toKeys: ['<Esc>'] },
79
79
  { keys: ['s'], type: 'keyToKey', toKeys: ['c', 'l'], context: 'normal' },
80
80
  { keys: ['s'], type: 'keyToKey', toKeys: ['x', 'i'], context: 'visual'},
@@ -1457,7 +1457,7 @@
1457
1457
  motionArgs.selectedCharacter);
1458
1458
  var increment = motionArgs.forward ? -1 : 1;
1459
1459
  recordLastCharacterSearch(increment, motionArgs);
1460
- if(!curEnd)return cm.getCursor();
1460
+ if (!curEnd) return null;
1461
1461
  curEnd.ch += increment;
1462
1462
  return curEnd;
1463
1463
  },
@@ -1532,22 +1532,44 @@
1532
1532
  ch: findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum)) };
1533
1533
  },
1534
1534
  textObjectManipulation: function(cm, motionArgs) {
1535
+ // TODO: lots of possible exceptions that can be thrown here. Try da(
1536
+ // outside of a () block.
1537
+
1538
+ // TODO: adding <> >< to this map doesn't work, presumably because
1539
+ // they're operators
1540
+ var mirroredPairs = {'(': ')', ')': '(',
1541
+ '{': '}', '}': '{',
1542
+ '[': ']', ']': '['};
1543
+ var selfPaired = {'\'': true, '"': true};
1544
+
1535
1545
  var character = motionArgs.selectedCharacter;
1546
+
1536
1547
  // Inclusive is the difference between a and i
1537
1548
  // TODO: Instead of using the additional text object map to perform text
1538
1549
  // object operations, merge the map into the defaultKeyMap and use
1539
1550
  // motionArgs to define behavior. Define separate entries for 'aw',
1540
1551
  // 'iw', 'a[', 'i[', etc.
1541
1552
  var inclusive = !motionArgs.textObjectInner;
1542
- if (!textObjects[character]) {
1553
+
1554
+ var tmp;
1555
+ if (mirroredPairs[character]) {
1556
+ tmp = selectCompanionObject(cm, mirroredPairs[character], inclusive);
1557
+ } else if (selfPaired[character]) {
1558
+ tmp = findBeginningAndEnd(cm, character, inclusive);
1559
+ } else if (character === 'W') {
1560
+ tmp = expandWordUnderCursor(cm, inclusive, true /** forward */,
1561
+ true /** bigWord */);
1562
+ } else if (character === 'w') {
1563
+ tmp = expandWordUnderCursor(cm, inclusive, true /** forward */,
1564
+ false /** bigWord */);
1565
+ } else {
1543
1566
  // No text object defined for this, don't move.
1544
1567
  return null;
1545
1568
  }
1546
- var tmp = textObjects[character](cm, inclusive);
1547
- var start = tmp.start;
1548
- var end = tmp.end;
1549
- return [start, end];
1569
+
1570
+ return [tmp.start, tmp.end];
1550
1571
  },
1572
+
1551
1573
  repeatLastCharacterSearch: function(cm, motionArgs) {
1552
1574
  var lastSearch = vimGlobalState.lastChararacterSearch;
1553
1575
  var repeat = motionArgs.repeat;
@@ -2015,36 +2037,6 @@
2015
2037
  }
2016
2038
  };
2017
2039
 
2018
- var textObjects = {
2019
- // TODO: lots of possible exceptions that can be thrown here. Try da(
2020
- // outside of a () block.
2021
- // TODO: implement text objects for the reverse like }. Should just be
2022
- // an additional mapping after moving to the defaultKeyMap.
2023
- 'w': function(cm, inclusive) {
2024
- return expandWordUnderCursor(cm, inclusive, true /** forward */,
2025
- false /** bigWord */);
2026
- },
2027
- 'W': function(cm, inclusive) {
2028
- return expandWordUnderCursor(cm, inclusive,
2029
- true /** forward */, true /** bigWord */);
2030
- },
2031
- '{': function(cm, inclusive) {
2032
- return selectCompanionObject(cm, '}', inclusive);
2033
- },
2034
- '(': function(cm, inclusive) {
2035
- return selectCompanionObject(cm, ')', inclusive);
2036
- },
2037
- '[': function(cm, inclusive) {
2038
- return selectCompanionObject(cm, ']', inclusive);
2039
- },
2040
- '\'': function(cm, inclusive) {
2041
- return findBeginningAndEnd(cm, "'", inclusive);
2042
- },
2043
- '"': function(cm, inclusive) {
2044
- return findBeginningAndEnd(cm, '"', inclusive);
2045
- }
2046
- };
2047
-
2048
2040
  /*
2049
2041
  * Below are miscellaneous utility functions used by vim.js
2050
2042
  */
@@ -2634,13 +2626,25 @@
2634
2626
  return cur;
2635
2627
  }
2636
2628
 
2629
+ // TODO: perhaps this finagling of start and end positions belonds
2630
+ // in codmirror/replaceRange?
2637
2631
  function selectCompanionObject(cm, revSymb, inclusive) {
2638
2632
  var cur = cm.getCursor();
2639
-
2640
2633
  var end = findMatchedSymbol(cm, cur, revSymb);
2641
2634
  var start = findMatchedSymbol(cm, end);
2642
- start.ch += inclusive ? 1 : 0;
2643
- end.ch += inclusive ? 0 : 1;
2635
+
2636
+ if((start.line == end.line && start.ch > end.ch)
2637
+ || (start.line > end.line)) {
2638
+ var tmp = start;
2639
+ start = end;
2640
+ end = tmp;
2641
+ }
2642
+
2643
+ if(inclusive) {
2644
+ end.ch += 1;
2645
+ } else {
2646
+ start.ch += 1;
2647
+ }
2644
2648
 
2645
2649
  return { start: start, end: end };
2646
2650
  }
@@ -2750,10 +2754,84 @@
2750
2754
  if (!escapeNextChar && c == '/') {
2751
2755
  slashes.push(i);
2752
2756
  }
2753
- escapeNextChar = (c == '\\');
2757
+ escapeNextChar = !escapeNextChar && (c == '\\');
2754
2758
  }
2755
2759
  return slashes;
2756
2760
  }
2761
+
2762
+ // Translates a search string from ex (vim) syntax into javascript form.
2763
+ function fixRegex(str) {
2764
+ // When these match, add a '\' if unescaped or remove one if escaped.
2765
+ var specials = ['|', '(', ')', '{'];
2766
+ // Remove, but never add, a '\' for these.
2767
+ var unescape = ['}'];
2768
+ var escapeNextChar = false;
2769
+ var out = [];
2770
+ for (var i = -1; i < str.length; i++) {
2771
+ var c = str.charAt(i) || '';
2772
+ var n = str.charAt(i+1) || '';
2773
+ var specialComesNext = (specials.indexOf(n) != -1);
2774
+ if (escapeNextChar) {
2775
+ if (c !== '\\' || !specialComesNext) {
2776
+ out.push(c);
2777
+ }
2778
+ escapeNextChar = false;
2779
+ } else {
2780
+ if (c === '\\') {
2781
+ escapeNextChar = true;
2782
+ // Treat the unescape list as special for removing, but not adding '\'.
2783
+ if (unescape.indexOf(n) != -1) {
2784
+ specialComesNext = true;
2785
+ }
2786
+ // Not passing this test means removing a '\'.
2787
+ if (!specialComesNext || n === '\\') {
2788
+ out.push(c);
2789
+ }
2790
+ } else {
2791
+ out.push(c);
2792
+ if (specialComesNext && n !== '\\') {
2793
+ out.push('\\');
2794
+ }
2795
+ }
2796
+ }
2797
+ }
2798
+ return out.join('');
2799
+ }
2800
+
2801
+ // Translates the replace part of a search and replace from ex (vim) syntax into
2802
+ // javascript form. Similar to fixRegex, but additionally fixes back references
2803
+ // (translates '\[0..9]' to '$[0..9]') and follows different rules for escaping '$'.
2804
+ function fixRegexReplace(str) {
2805
+ var escapeNextChar = false;
2806
+ var out = [];
2807
+ for (var i = -1; i < str.length; i++) {
2808
+ var c = str.charAt(i) || '';
2809
+ var n = str.charAt(i+1) || '';
2810
+ if (escapeNextChar) {
2811
+ out.push(c);
2812
+ escapeNextChar = false;
2813
+ } else {
2814
+ if (c === '\\') {
2815
+ escapeNextChar = true;
2816
+ if ((isNumber(n) || n === '$')) {
2817
+ out.push('$');
2818
+ } else if (n !== '/' && n !== '\\') {
2819
+ out.push('\\');
2820
+ }
2821
+ } else {
2822
+ if (c === '$') {
2823
+ out.push('$');
2824
+ }
2825
+ out.push(c);
2826
+ if (n === '/') {
2827
+ out.push('\\');
2828
+ }
2829
+ }
2830
+ }
2831
+ }
2832
+ return out.join('');
2833
+ }
2834
+
2757
2835
  /**
2758
2836
  * Extract the regular expression from the query and return a Regexp object.
2759
2837
  * Returns null if the query is blank.
@@ -2785,6 +2863,7 @@
2785
2863
  if (!regexPart) {
2786
2864
  return null;
2787
2865
  }
2866
+ regexPart = fixRegex(regexPart);
2788
2867
  if (smartCase) {
2789
2868
  ignoreCase = (/^[^A-Z]*$/).test(regexPart);
2790
2869
  }
@@ -3279,6 +3358,7 @@
3279
3358
  var confirm = false; // Whether to confirm each replace.
3280
3359
  if (slashes[1]) {
3281
3360
  replacePart = argString.substring(slashes[1] + 1, slashes[2]);
3361
+ replacePart = fixRegexReplace(replacePart);
3282
3362
  }
3283
3363
  if (slashes[2]) {
3284
3364
  // After the 3rd slash, we can have flags followed by a space followed
@@ -3495,18 +3575,10 @@
3495
3575
  * Shift + key modifier to the resulting letter, while preserving other
3496
3576
  * modifers.
3497
3577
  */
3498
- // TODO: Figure out a way to catch capslock.
3499
3578
  function cmKeyToVimKey(key, modifier) {
3500
3579
  var vimKey = key;
3501
- if (isUpperCase(vimKey)) {
3502
- // Convert to lower case if shift is not the modifier since the key
3503
- // we get from CodeMirror is always upper case.
3504
- if (modifier == 'Shift') {
3505
- modifier = null;
3506
- }
3507
- else {
3580
+ if (isUpperCase(vimKey) && modifier == 'Ctrl') {
3508
3581
  vimKey = vimKey.toLowerCase();
3509
- }
3510
3582
  }
3511
3583
  if (modifier) {
3512
3584
  // Vim will parse modifier+key combination as a single key.
@@ -3532,9 +3604,9 @@
3532
3604
  function bindKeys(keys, modifier) {
3533
3605
  for (var i = 0; i < keys.length; i++) {
3534
3606
  var key = keys[i];
3535
- if (!modifier && inArray(key, specialSymbols)) {
3536
- // Wrap special symbols with '' because that's how CodeMirror binds
3537
- // them.
3607
+ if (!modifier && key.length == 1) {
3608
+ // Wrap all keys without modifiers with '' to identify them by their
3609
+ // key characters instead of key identifiers.
3538
3610
  key = "'" + key + "'";
3539
3611
  }
3540
3612
  var vimKey = cmKeyToVimKey(keys[i], modifier);
@@ -3543,7 +3615,7 @@
3543
3615
  }
3544
3616
  }
3545
3617
  bindKeys(upperCaseAlphabet);
3546
- bindKeys(upperCaseAlphabet, 'Shift');
3618
+ bindKeys(lowerCaseAlphabet);
3547
3619
  bindKeys(upperCaseAlphabet, 'Ctrl');
3548
3620
  bindKeys(specialSymbols);
3549
3621
  bindKeys(specialSymbols, 'Ctrl');