codemirror-rails 2.35 → 2.36

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  module Codemirror
2
2
  module Rails
3
- VERSION = '2.35'
4
- CODEMIRROR_VERSION = '2.35'
3
+ VERSION = '2.36'
4
+ CODEMIRROR_VERSION = '2.36'
5
5
  end
6
6
  end
@@ -1,4 +1,4 @@
1
- // CodeMirror version 2.35
1
+ // CodeMirror version 2.36
2
2
  //
3
3
  // All functions that need access to the editor's state live inside
4
4
  // the CodeMirror function. Below that, at the bottom of the file,
@@ -232,6 +232,7 @@ window.CodeMirror = (function() {
232
232
  var off = eltOffset(lineSpace);
233
233
  return coordsChar(coords.x - off.left, coords.y - off.top);
234
234
  },
235
+ defaultTextHeight: function() { return textHeight(); },
235
236
  markText: operation(markText),
236
237
  setBookmark: setBookmark,
237
238
  findMarksAt: findMarksAt,
@@ -346,6 +347,11 @@ window.CodeMirror = (function() {
346
347
  return {x: scroller.scrollLeft, y: scrollbar.scrollTop,
347
348
  height: scrollbar.scrollHeight, width: scroller.scrollWidth};
348
349
  },
350
+ scrollIntoView: function(pos) {
351
+ var coords = localCoords(pos ? clipPos(pos) : sel.inverted ? sel.from : sel.to);
352
+ scrollIntoView(coords.x, coords.y, coords.x, coords.yBot);
353
+ },
354
+
349
355
  setSize: function(width, height) {
350
356
  function interpret(val) {
351
357
  val = String(val);
@@ -395,7 +401,7 @@ window.CodeMirror = (function() {
395
401
  }
396
402
 
397
403
  function onScrollBar(e) {
398
- if (scrollbar.scrollTop != lastScrollTop) {
404
+ if (Math.abs(scrollbar.scrollTop - lastScrollTop) > 1) {
399
405
  lastScrollTop = scroller.scrollTop = scrollbar.scrollTop;
400
406
  updateDisplay([]);
401
407
  }
@@ -404,7 +410,7 @@ window.CodeMirror = (function() {
404
410
  function onScrollMain(e) {
405
411
  if (options.fixedGutter && gutter.style.left != scroller.scrollLeft + "px")
406
412
  gutter.style.left = scroller.scrollLeft + "px";
407
- if (scroller.scrollTop != lastScrollTop) {
413
+ if (Math.abs(scroller.scrollTop - lastScrollTop) > 1) {
408
414
  lastScrollTop = scroller.scrollTop;
409
415
  if (scrollbar.scrollTop != lastScrollTop)
410
416
  scrollbar.scrollTop = lastScrollTop;
@@ -637,7 +643,7 @@ window.CodeMirror = (function() {
637
643
  if (handled) {
638
644
  e_preventDefault(e);
639
645
  restartBlink();
640
- if (ie) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
646
+ if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
641
647
  }
642
648
  return handled;
643
649
  }
@@ -2174,7 +2180,7 @@ window.CodeMirror = (function() {
2174
2180
  keyMap.emacsy = {
2175
2181
  "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
2176
2182
  "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
2177
- "Ctrl-V": "goPageUp", "Shift-Ctrl-V": "goPageDown", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft",
2183
+ "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft",
2178
2184
  "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
2179
2185
  };
2180
2186
 
@@ -2232,15 +2238,13 @@ window.CodeMirror = (function() {
2232
2238
  if (textarea.form) {
2233
2239
  // Deplorable hack to make the submit method do the right thing.
2234
2240
  var rmSubmit = connect(textarea.form, "submit", save, true);
2235
- if (typeof textarea.form.submit == "function") {
2236
- var realSubmit = textarea.form.submit;
2237
- textarea.form.submit = function wrappedSubmit() {
2238
- save();
2239
- textarea.form.submit = realSubmit;
2240
- textarea.form.submit();
2241
- textarea.form.submit = wrappedSubmit;
2242
- };
2243
- }
2241
+ var realSubmit = textarea.form.submit;
2242
+ textarea.form.submit = function wrappedSubmit() {
2243
+ save();
2244
+ textarea.form.submit = realSubmit;
2245
+ textarea.form.submit();
2246
+ textarea.form.submit = wrappedSubmit;
2247
+ };
2244
2248
  }
2245
2249
 
2246
2250
  textarea.style.display = "none";
@@ -3102,8 +3106,10 @@ window.CodeMirror = (function() {
3102
3106
  if (collection[i] == elt) return i;
3103
3107
  return -1;
3104
3108
  }
3109
+ var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc]/;
3105
3110
  function isWordChar(ch) {
3106
- return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase() || /[\u4E00-\u9FA5]/.test(ch);
3111
+ return /\w/.test(ch) || ch > "\x80" &&
3112
+ (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch));
3107
3113
  }
3108
3114
 
3109
3115
  // See if "".split is the broken IE version, if so, provide an
@@ -3159,7 +3165,7 @@ window.CodeMirror = (function() {
3159
3165
  for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
3160
3166
  })();
3161
3167
 
3162
- CodeMirror.version = "2.35";
3168
+ CodeMirror.version = "2.36";
3163
3169
 
3164
3170
  return CodeMirror;
3165
3171
  })();
@@ -18,7 +18,8 @@
18
18
  "Alt-Y": function(cm) {cm.replaceSelection(popFromRing());},
19
19
  "Ctrl-/": "undo", "Shift-Ctrl--": "undo", "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd",
20
20
  "Ctrl-S": "findNext", "Ctrl-R": "findPrev", "Ctrl-G": "clearSearch", "Shift-Alt-5": "replace",
21
- "Ctrl-Z": "undo", "Cmd-Z": "undo", "Alt-/": "autocomplete",
21
+ "Ctrl-Z": "undo", "Cmd-Z": "undo", "Alt-/": "autocomplete", "Alt-V": "goPageUp",
22
+ "Ctrl-J": "newlineAndIndent", "Enter": false, "Tab": "indentAuto",
22
23
  fallthrough: ["basic", "emacsy"]
23
24
  };
24
25
 
@@ -17,7 +17,7 @@
17
17
  // Entering insert mode:
18
18
  // i, I, a, A, o, O
19
19
  // s
20
- // ce, cb (without support for number of actions like c3e - TODO)
20
+ // ce, cb
21
21
  // cc
22
22
  // S, C TODO
23
23
  // cf<char>, cF<char>, ct<char>, cT<char>
@@ -26,7 +26,7 @@
26
26
  // x, X
27
27
  // J
28
28
  // dd, D
29
- // de, db (without support for number of actions like d3e - TODO)
29
+ // de, db
30
30
  // df<char>, dF<char>, dt<char>, dT<char>
31
31
  //
32
32
  // Yanking and pasting:
@@ -48,22 +48,28 @@
48
48
  //
49
49
 
50
50
  (function() {
51
- var count = "";
52
51
  var sdir = "f";
53
52
  var buf = "";
54
- var yank = 0;
55
- var mark = [];
56
- var reptTimes = 0;
53
+ var mark = {};
54
+ var repeatCount = 0;
55
+ function isLine(cm, line) { return line >= 0 && line < cm.lineCount(); }
57
56
  function emptyBuffer() { buf = ""; }
58
57
  function pushInBuffer(str) { buf += str; }
59
- function pushCountDigit(digit) { return function(cm) {count += digit;}; }
60
- function popCount() { var i = parseInt(count, 10); count = ""; return i || 1; }
58
+ function pushRepeatCountDigit(digit) {return function(cm) {repeatCount = (repeatCount * 10) + digit}; }
59
+ function getCountOrOne() {
60
+ var i = repeatCount;
61
+ return i || 1;
62
+ }
63
+ function clearCount() {
64
+ repeatCount = 0;
65
+ }
61
66
  function iterTimes(func) {
62
- for (var i = 0, c = popCount(); i < c; ++i) func(i, i == c - 1);
67
+ for (var i = 0, c = getCountOrOne(); i < c; ++i) func(i, i == c - 1);
68
+ clearCount();
63
69
  }
64
70
  function countTimes(func) {
65
71
  if (typeof func == "string") func = CodeMirror.commands[func];
66
- return function(cm) { iterTimes(function () { func(cm); }); };
72
+ return function(cm) { iterTimes(function (i, last) { func(cm, i, last); }); };
67
73
  }
68
74
 
69
75
  function iterObj(o, f) {
@@ -93,50 +99,110 @@
93
99
  }
94
100
 
95
101
  var word = [/\w/, /[^\w\s]/], bigWord = [/\S/];
96
- function findWord(line, pos, dir, regexps) {
97
- var stop = 0, next = -1;
98
- if (dir > 0) { stop = line.length; next = 0; }
99
- var start = stop, end = stop;
100
- // Find bounds of next one.
101
- outer: for (; pos != stop; pos += dir) {
102
- for (var i = 0; i < regexps.length; ++i) {
103
- if (regexps[i].test(line.charAt(pos + next))) {
104
- start = pos;
105
- for (; pos != stop; pos += dir) {
106
- if (!regexps[i].test(line.charAt(pos + next))) break;
102
+ // Finds a word on the given line, and continue searching the next line if it can't find one.
103
+ function findWord(cm, lineNum, pos, dir, regexps) {
104
+ var line = cm.getLine(lineNum);
105
+ while (true) {
106
+ var stop = (dir > 0) ? line.length : -1;
107
+ var wordStart = stop, wordEnd = stop;
108
+ // Find bounds of next word.
109
+ for (; pos != stop; pos += dir) {
110
+ for (var i = 0; i < regexps.length; ++i) {
111
+ if (regexps[i].test(line.charAt(pos))) {
112
+ wordStart = pos;
113
+ // Advance to end of word.
114
+ for (; pos != stop && regexps[i].test(line.charAt(pos)); pos += dir) {}
115
+ wordEnd = (dir > 0) ? pos : pos + 1;
116
+ return {
117
+ from: Math.min(wordStart, wordEnd),
118
+ to: Math.max(wordStart, wordEnd),
119
+ line: lineNum};
107
120
  }
108
- end = pos;
109
- break outer;
110
121
  }
111
122
  }
123
+ // Advance to next/prev line.
124
+ lineNum += dir;
125
+ if (!isLine(cm, lineNum)) return null;
126
+ line = cm.getLine(lineNum);
127
+ pos = (dir > 0) ? 0 : line.length;
112
128
  }
113
- return {from: Math.min(start, end), to: Math.max(start, end)};
114
129
  }
115
- function moveToWord(cm, regexps, dir, times, where) {
130
+ /**
131
+ * @param {boolean} cm CodeMirror object.
132
+ * @param {regexp} regexps Regular expressions for word characters.
133
+ * @param {number} dir Direction, +/- 1.
134
+ * @param {number} times Number of times to advance word.
135
+ * @param {string} where Go to "start" or "end" of word, 'e' vs 'w'.
136
+ * @param {boolean} yank Whether we are finding words to yank. If true,
137
+ * do not go to the next line to look for the last word. This is to
138
+ * prevent deleting new line on 'dw' at the end of a line.
139
+ */
140
+ function moveToWord(cm, regexps, dir, times, where, yank) {
116
141
  var cur = cm.getCursor();
117
-
142
+ if (yank) {
143
+ where = 'start';
144
+ }
118
145
  for (var i = 0; i < times; i++) {
119
- var line = cm.getLine(cur.line), startCh = cur.ch, word;
146
+ var startCh = cur.ch, startLine = cur.line, word;
120
147
  while (true) {
121
- // If we're at start/end of line, start on prev/next respectivly
122
- if (cur.ch == line.length && dir > 0) {
123
- cur.line++;
124
- cur.ch = 0;
125
- line = cm.getLine(cur.line);
126
- } else if (cur.ch == 0 && dir < 0) {
127
- cur.line--;
128
- cur.ch = line.length;
129
- line = cm.getLine(cur.line);
148
+ // Search and advance.
149
+ word = findWord(cm, cur.line, cur.ch, dir, regexps);
150
+ if (word) {
151
+ if (yank && times == 1 && dir == 1 && cur.line != word.line) {
152
+ // Stop at end of line of last word. Don't want to delete line return
153
+ // for dw if the last deleted word is at the end of a line.
154
+ cur.ch = cm.getLine(cur.line).length;
155
+ break;
156
+ } else {
157
+ // Move to the word we just found. If by moving to the word we end up
158
+ // in the same spot, then move an extra character and search again.
159
+ cur.line = word.line;
160
+ if (dir > 0 && where == 'end') {
161
+ // 'e'
162
+ if (startCh != word.to - 1 || startLine != word.line) {
163
+ cur.ch = word.to - 1;
164
+ break;
165
+ } else {
166
+ cur.ch = word.to;
167
+ }
168
+ } else if (dir > 0 && where == 'start') {
169
+ // 'w'
170
+ if (startCh != word.from || startLine != word.line) {
171
+ cur.ch = word.from;
172
+ break;
173
+ } else {
174
+ cur.ch = word.to;
175
+ }
176
+ } else if (dir < 0 && where == 'end') {
177
+ // 'ge'
178
+ if (startCh != word.to || startLine != word.line) {
179
+ cur.ch = word.to;
180
+ break;
181
+ } else {
182
+ cur.ch = word.from - 1;
183
+ }
184
+ } else if (dir < 0 && where == 'start') {
185
+ // 'b'
186
+ if (startCh != word.from || startLine != word.line) {
187
+ cur.ch = word.from;
188
+ break;
189
+ } else {
190
+ cur.ch = word.from - 1;
191
+ }
192
+ }
193
+ }
194
+ } else {
195
+ // No more words to be found. Move to end of document.
196
+ for (; isLine(cm, cur.line + dir); cur.line += dir) {}
197
+ cur.ch = (dir > 0) ? cm.getLine(cur.line).length : 0;
198
+ break;
130
199
  }
131
- if (!line) break;
132
-
133
- // On to the actual searching
134
- word = findWord(line, cur.ch, dir, regexps);
135
- cur.ch = word[where == "end" ? "to" : "from"];
136
- if (startCh == cur.ch && word.from != word.to) cur.ch = word[dir < 0 ? "from" : "to"];
137
- else break;
138
200
  }
139
201
  }
202
+ if (where == 'end' && yank) {
203
+ // Include the last character of the word for actions.
204
+ cur.ch++;
205
+ }
140
206
  return cur;
141
207
  }
142
208
  function joinLineNext(cm) {
@@ -157,7 +223,7 @@
157
223
  var l = cm.getCursor().line, start = i > l ? l : i, end = i > l ? i : l;
158
224
  cm.setCursor(start);
159
225
  for (var c = start; c <= end; c++) {
160
- pushInBuffer("\n"+cm.getLine(start));
226
+ pushInBuffer("\n" + cm.getLine(start));
161
227
  cm.removeLine(start);
162
228
  }
163
229
  }
@@ -169,7 +235,7 @@
169
235
  }
170
236
  var l = cm.getCursor().line, start = i > l ? l : i, end = i > l ? i : l;
171
237
  for (var c = start; c <= end; c++) {
172
- pushInBuffer("\n"+cm.getLine(c));
238
+ pushInBuffer("\n" + cm.getLine(c));
173
239
  }
174
240
  cm.setCursor(start);
175
241
  }
@@ -220,7 +286,7 @@
220
286
 
221
287
  function enterInsertMode(cm) {
222
288
  // enter insert mode: switch mode and cursor
223
- popCount();
289
+ clearCount();
224
290
  cm.setOption("keyMap", "vim-insert");
225
291
  }
226
292
 
@@ -238,7 +304,8 @@
238
304
  var map = CodeMirror.keyMap.vim = {
239
305
  // Pipe (|); TODO: should be *screen* chars, so need a util function to turn tabs into spaces?
240
306
  "'|'": function(cm) {
241
- cm.setCursor(cm.getCursor().line, popCount() - 1, true);
307
+ cm.setCursor(cm.getCursor().line, getCountOrOne() - 1, true);
308
+ clearCount();
242
309
  },
243
310
  "A": function(cm) {
244
311
  cm.setCursor(cm.getCursor().line, cm.getCursor().ch+1, true);
@@ -260,16 +327,26 @@
260
327
  },
261
328
  "G": function(cm) { cm.setOption("keyMap", "vim-prefix-g");},
262
329
  "Shift-D": function(cm) {
263
- // commented out verions works, but I left original, cause maybe
264
- // I don't know vim enouth to see what it does
265
- /* var cur = cm.getCursor();
266
- var f = {line: cur.line, ch: cur.ch}, t = {line: cur.line};
267
- pushInBuffer(cm.getRange(f, t));
268
- */
330
+ var cursor = cm.getCursor();
331
+ var lineN = cursor.line;
332
+ var line = cm.getLine(lineN);
333
+ cm.setLine(lineN, line.slice(0, cursor.ch));
334
+
269
335
  emptyBuffer();
270
- mark["Shift-D"] = cm.getCursor(false).line;
271
- cm.setCursor(cm.getCursor(true).line);
272
- delTillMark(cm,"Shift-D"); mark = [];
336
+ pushInBuffer(line.slice(cursor.ch));
337
+
338
+ if (repeatCount > 1) {
339
+ // we've already done it once
340
+ --repeatCount;
341
+ // the lines dissapear (ie, cursor stays on the same lineN),
342
+ // so only incremenet once
343
+ ++lineN;
344
+
345
+ iterTimes(function() {
346
+ pushInBuffer(cm.getLine(lineN));
347
+ cm.removeLine(lineN);
348
+ });
349
+ }
273
350
  },
274
351
 
275
352
  "S": function (cm) {
@@ -278,13 +355,11 @@
278
355
  })(cm);
279
356
  enterInsertMode(cm);
280
357
  },
281
- "M": function(cm) {cm.setOption("keyMap", "vim-prefix-m"); mark = [];},
282
- "Y": function(cm) {cm.setOption("keyMap", "vim-prefix-y"); emptyBuffer(); yank = 0;},
358
+ "M": function(cm) {cm.setOption("keyMap", "vim-prefix-m"); mark = {};},
359
+ "Y": function(cm) {cm.setOption("keyMap", "vim-prefix-y"); emptyBuffer();},
283
360
  "Shift-Y": function(cm) {
284
361
  emptyBuffer();
285
- mark["Shift-D"] = cm.getCursor(false).line;
286
- cm.setCursor(cm.getCursor(true).line);
287
- yankTillMark(cm,"Shift-D"); mark = [];
362
+ iterTimes(function(i) { pushInBuffer("\n" + cm.getLine(cm.getCursor().line + i)); });
288
363
  },
289
364
  "/": function(cm) {var f = CodeMirror.commands.find; f && f(cm); sdir = "f";},
290
365
  "'?'": function(cm) {
@@ -300,8 +375,8 @@
300
375
  if (fn) sdir != "r" ? CodeMirror.commands.findPrev(cm) : fn.findNext(cm);
301
376
  },
302
377
  "Shift-G": function(cm) {
303
- count == "" ? cm.setCursor(cm.lineCount()) : cm.setCursor(parseInt(count, 10)-1);
304
- popCount();
378
+ (repeatCount == 0) ? cm.setCursor(cm.lineCount()) : cm.setCursor(repeatCount - 1);
379
+ clearCount();
305
380
  CodeMirror.commands.goLineStart(cm);
306
381
  },
307
382
  "':'": function(cm) {
@@ -325,21 +400,9 @@
325
400
  };
326
401
  });
327
402
 
328
- function addCountBindings(keyMap) {
329
- // Add bindings for number keys
330
- keyMap["0"] = function(cm) {
331
- count.length > 0 ? pushCountDigit("0")(cm) : CodeMirror.commands.goLineStart(cm);
332
- };
333
- for (var i = 1; i < 10; ++i) keyMap[i] = pushCountDigit(i);
334
- }
335
- addCountBindings(CodeMirror.keyMap.vim);
336
-
337
403
  // main num keymap
338
404
  // Add bindings that are influenced by number keys
339
405
  iterObj({
340
- "Left": "goColumnLeft", "Right": "goColumnRight",
341
- "Down": "goLineDown", "Up": "goLineUp", "Backspace": "goCharLeft",
342
- "Space": "goCharRight",
343
406
  "X": function(cm) {CodeMirror.commands.delCharRight(cm);},
344
407
  "P": function(cm) {
345
408
  var cur = cm.getCursor().line;
@@ -401,15 +464,18 @@
401
464
  });
402
465
 
403
466
  CodeMirror.keyMap["vim-prefix-g"] = {
404
- "E": countTimes(function(cm) { cm.setCursor(moveToWord(cm, word, -1, 1, "start"));}),
405
- "Shift-E": countTimes(function(cm) { cm.setCursor(moveToWord(cm, bigWord, -1, 1, "start"));}),
406
- "G": function (cm) { cm.setCursor({line: 0, ch: cm.getCursor().ch});},
467
+ "E": countTimes(function(cm) { cm.setCursor(moveToWord(cm, word, -1, 1, "end"));}),
468
+ "Shift-E": countTimes(function(cm) { cm.setCursor(moveToWord(cm, bigWord, -1, 1, "end"));}),
469
+ "G": function (cm) {
470
+ cm.setCursor({line: repeatCount - 1, ch: cm.getCursor().ch});
471
+ clearCount();
472
+ },
407
473
  auto: "vim", nofallthrough: true, style: "fat-cursor"
408
474
  };
409
475
 
410
476
  CodeMirror.keyMap["vim-prefix-d"] = {
411
477
  "D": countTimes(function(cm) {
412
- pushInBuffer("\n"+cm.getLine(cm.getCursor().line));
478
+ pushInBuffer("\n" + cm.getLine(cm.getCursor().line));
413
479
  cm.removeLine(cm.getCursor().line);
414
480
  cm.setOption("keyMap", "vim");
415
481
  }),
@@ -428,8 +494,6 @@
428
494
  },
429
495
  nofallthrough: true, style: "fat-cursor"
430
496
  };
431
- // FIXME - does not work for bindings like "d3e"
432
- addCountBindings(CodeMirror.keyMap["vim-prefix-d"]);
433
497
 
434
498
  CodeMirror.keyMap["vim-prefix-c"] = {
435
499
  "B": function (cm) {
@@ -471,10 +535,10 @@
471
535
  mark[m] = cm.getCursor().line;
472
536
  };
473
537
  CodeMirror.keyMap["vim-prefix-d'"][m] = function(cm) {
474
- delTillMark(cm,m);
538
+ delTillMark(cm, m);
475
539
  };
476
540
  CodeMirror.keyMap["vim-prefix-y'"][m] = function(cm) {
477
- yankTillMark(cm,m);
541
+ yankTillMark(cm, m);
478
542
  };
479
543
  CodeMirror.keyMap["vim-prefix-r"][m] = function (cm) {
480
544
  var cur = cm.getCursor();
@@ -508,8 +572,8 @@
508
572
  setupPrefixBindingForKey("Space");
509
573
 
510
574
  CodeMirror.keyMap["vim-prefix-y"] = {
511
- "Y": countTimes(function(cm) {
512
- pushInBuffer("\n"+cm.getLine(cm.getCursor().line+yank)); yank++;
575
+ "Y": countTimes(function(cm, i, last) {
576
+ pushInBuffer("\n" + cm.getLine(cm.getCursor().line + i));
513
577
  cm.setOption("keyMap", "vim");
514
578
  }),
515
579
  "'": function(cm) {cm.setOption("keyMap", "vim-prefix-y'"); emptyBuffer();},
@@ -588,67 +652,107 @@
588
652
  return {start: start, end: end};
589
653
  }
590
654
 
591
- // These are our motion commands to be used for navigation and selection with
592
- // certian other commands. All should return a cursor object.
593
- var motionList = ['B', 'E', 'J', 'K', 'H', 'L', 'W', 'Shift-W', "'^'", "'$'", "'%'", 'Esc'];
655
+ // takes in a symbol and a cursor and tries to simulate text objects that have
656
+ // identical opening and closing symbols
657
+ // TODO support across multiple lines
658
+ function findBeginningAndEnd(cm, symb, inclusive) {
659
+ var cur = cm.getCursor();
660
+ var line = cm.getLine(cur.line);
661
+ var chars = line.split('');
662
+ var start = undefined;
663
+ var end = undefined;
664
+ var firstIndex = chars.indexOf(symb);
665
+
666
+ // the decision tree is to always look backwards for the beginning first,
667
+ // but if the cursor is in front of the first instance of the symb,
668
+ // then move the cursor forward
669
+ if (cur.ch < firstIndex) {
670
+ cur.ch = firstIndex;
671
+ cm.setCursor(cur.line, firstIndex+1);
672
+ }
673
+ // otherwise if the cursor is currently on the closing symbol
674
+ else if (firstIndex < cur.ch && chars[cur.ch] == symb) {
675
+ end = cur.ch; // assign end to the current cursor
676
+ --cur.ch; // make sure to look backwards
677
+ }
594
678
 
595
- motions = {
596
- 'B': function(cm, times) { return moveToWord(cm, word, -1, times); },
597
- 'Shift-B': function(cm, times) { return moveToWord(cm, bigWord, -1, times); },
598
- 'E': function(cm, times) { return moveToWord(cm, word, 1, times, 'end'); },
599
- 'Shift-E': function(cm, times) { return moveToWord(cm, bigWord, 1, times, 'end'); },
600
- 'J': function(cm, times) {
601
- var cur = cm.getCursor();
602
- return {line: cur.line+times, ch : cur.ch};
603
- },
679
+ // if we're currently on the symbol, we've got a start
680
+ if (chars[cur.ch] == symb && end == null)
681
+ start = cur.ch + 1; // assign start to ahead of the cursor
682
+ else {
683
+ // go backwards to find the start
684
+ for (var i = cur.ch; i > -1 && start == null; i--)
685
+ if (chars[i] == symb) start = i + 1;
686
+ }
604
687
 
605
- 'K': function(cm, times) {
606
- var cur = cm.getCursor();
607
- return {line: cur.line-times, ch: cur.ch};
608
- },
688
+ // look forwards for the end symbol
689
+ if (start != null && end == null) {
690
+ for (var i = start, len = chars.length; i < len && end == null; i++) {
691
+ if (chars[i] == symb) end = i;
692
+ }
693
+ }
609
694
 
610
- 'H': function(cm, times) {
611
- var cur = cm.getCursor();
612
- return {line: cur.line, ch: cur.ch-times};
613
- },
695
+ // nothing found
696
+ // FIXME still enters insert mode
697
+ if (start == null || end == null) return {
698
+ start: cur, end: cur
699
+ };
614
700
 
615
- 'L': function(cm, times) {
616
- var cur = cm.getCursor();
617
- return {line: cur.line, ch: cur.ch+times};
618
- },
619
- 'W': function(cm, times) { return moveToWord(cm, word, 1, times); },
620
- 'Shift-W': function(cm, times) { return moveToWord(cm, bigWord, 1, times); },
621
- "'^'": function(cm, times) {
622
- var cur = cm.getCursor();
623
- var line = cm.getLine(cur.line).split('');
701
+ // include the symbols
702
+ if (inclusive) {
703
+ --start; ++end;
704
+ }
624
705
 
625
- // Empty line :o
626
- if (line.length == 0) return cur;
706
+ return {
707
+ start: {line: cur.line, ch: start},
708
+ end: {line: cur.line, ch: end}
709
+ };
710
+ }
627
711
 
628
- for (var index = 0; index < line.length; index++) {
629
- if (line[index].match(/[^\s]/)) return {line: cur.line, ch: index};
712
+ function offsetCursor(cm, line, ch) {
713
+ var cur = cm.getCursor(); return {line: cur.line + line, ch: cur.ch + ch};
714
+ }
715
+
716
+ // These are the motion commands we use for navigation and selection with
717
+ // certain other commands. All should return a cursor object.
718
+ var motions = {
719
+ "J": function(cm, times) { return offsetCursor(cm, times, 0); },
720
+ "Down": function(cm, times) { return offsetCursor(cm, times, 0); },
721
+ "K": function(cm, times) { return offsetCursor(cm, -times, 0); },
722
+ "Up": function(cm, times) { return offsetCursor(cm, -times, 0); },
723
+ "L": function(cm, times) { return offsetCursor(cm, 0, times); },
724
+ "Right": function(cm, times) { return offsetCursor(cm, 0, times); },
725
+ "Space": function(cm, times) { return offsetCursor(cm, 0, times); },
726
+ "H": function(cm, times) { return offsetCursor(cm, 0, -times); },
727
+ "Left": function(cm, times) { return offsetCursor(cm, 0, -times); },
728
+ "Backspace": function(cm, times) { return offsetCursor(cm, 0, -times); },
729
+ "B": function(cm, times, yank) { return moveToWord(cm, word, -1, times, 'start', yank); },
730
+ "Shift-B": function(cm, times, yank) { return moveToWord(cm, bigWord, -1, times, 'start', yank); },
731
+ "E": function(cm, times, yank) { return moveToWord(cm, word, 1, times, 'end', yank); },
732
+ "Shift-E": function(cm, times, yank) { return moveToWord(cm, bigWord, 1, times, 'end', yank); },
733
+ "W": function(cm, times, yank) { return moveToWord(cm, word, 1, times, 'start', yank); },
734
+ "Shift-W": function(cm, times, yank) { return moveToWord(cm, bigWord, 1, times, 'start', yank); },
735
+ "'^'": function(cm, times) {
736
+ var cur = cm.getCursor(), line = cm.getLine(cur.line).split('');
737
+ for (var i = 0; i < line.length; i++) {
738
+ if (line[i].match(/[^\s]/)) return {line: cur.line, ch: index};
630
739
  }
740
+ return cur;
631
741
  },
632
742
  "'$'": function(cm) {
633
- var cur = cm.getCursor();
634
- var line = cm.getLine(cur.line);
635
- return {line: cur.line, ch: line.length};
743
+ var cur = cm.getCursor(), ch = cm.getLine(cur.line).length;
744
+ return {line: cur.line, ch: ch};
636
745
  },
637
746
  "'%'": function(cm) { return findMatchedSymbol(cm, cm.getCursor()); },
638
- "Esc" : function(cm) {
639
- cm.setOption('vim');
640
- reptTimes = 0;
641
-
642
- return cm.getCursor();
643
- }
747
+ "Esc" : function(cm) { cm.setOption("keyMap", "vim"); repeatCount = 0; return cm.getCursor(); }
644
748
  };
645
749
 
646
750
  // Map our movement actions each operator and non-operational movement
647
- iterList(motionList, function(key, index, array) {
751
+ iterObj(motions, function(key, motion) {
648
752
  CodeMirror.keyMap['vim-prefix-d'][key] = function(cm) {
649
753
  // Get our selected range
650
754
  var start = cm.getCursor();
651
- var end = motions[key](cm, reptTimes ? reptTimes : 1);
755
+ var end = motion(cm, repeatCount ? repeatCount : 1, true);
652
756
 
653
757
  // Set swap var if range is of negative length
654
758
  if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true;
@@ -658,56 +762,59 @@
658
762
  cm.replaceRange("", swap ? end : start, swap ? start : end);
659
763
 
660
764
  // And clean up
661
- reptTimes = 0;
765
+ repeatCount = 0;
662
766
  cm.setOption("keyMap", "vim");
663
767
  };
664
768
 
665
769
  CodeMirror.keyMap['vim-prefix-c'][key] = function(cm) {
666
770
  var start = cm.getCursor();
667
- var end = motions[key](cm, reptTimes ? reptTimes : 1);
771
+ var end = motion(cm, repeatCount ? repeatCount : 1, true);
668
772
 
669
773
  if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true;
670
774
  pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end));
671
775
  cm.replaceRange("", swap ? end : start, swap ? start : end);
672
776
 
673
- reptTimes = 0;
777
+ repeatCount = 0;
674
778
  cm.setOption('keyMap', 'vim-insert');
675
779
  };
676
780
 
677
781
  CodeMirror.keyMap['vim-prefix-y'][key] = function(cm) {
678
782
  var start = cm.getCursor();
679
- var end = motions[key](cm, reptTimes ? reptTimes : 1);
783
+ var end = motion(cm, repeatCount ? repeatCount : 1, true);
680
784
 
681
785
  if ((start.line > end.line) || (start.line == end.line && start.ch > end.ch)) var swap = true;
682
786
  pushInBuffer(cm.getRange(swap ? end : start, swap ? start : end));
683
787
 
684
- reptTimes = 0;
788
+ repeatCount = 0;
685
789
  cm.setOption("keyMap", "vim");
686
790
  };
687
791
 
688
792
  CodeMirror.keyMap['vim'][key] = function(cm) {
689
- var cur = motions[key](cm, reptTimes ? reptTimes : 1);
793
+ var cur = motion(cm, repeatCount ? repeatCount : 1);
690
794
  cm.setCursor(cur.line, cur.ch);
691
795
 
692
- reptTimes = 0;
796
+ repeatCount = 0;
693
797
  };
694
798
  });
695
799
 
696
- var nums = [1,2,3,4,5,6,7,8,9];
697
- iterList(nums, function(key, index, array) {
698
- CodeMirror.keyMap['vim'][key] = function (cm) {
699
- reptTimes = (reptTimes * 10) + key;
700
- };
701
- CodeMirror.keyMap['vim-prefix-d'][key] = function (cm) {
702
- reptTimes = (reptTimes * 10) + key;
703
- };
704
- CodeMirror.keyMap['vim-prefix-y'][key] = function (cm) {
705
- reptTimes = (reptTimes * 10) + key;
706
- };
707
- CodeMirror.keyMap['vim-prefix-c'][key] = function (cm) {
708
- reptTimes = (reptTimes * 10) + key;
800
+ function addCountBindings(keyMapName) {
801
+ // Add bindings for number keys
802
+ keyMap = CodeMirror.keyMap[keyMapName];
803
+ keyMap["0"] = function(cm) {
804
+ if (repeatCount > 0) {
805
+ pushRepeatCountDigit(0)(cm);
806
+ } else {
807
+ CodeMirror.commands.goLineStart(cm);
808
+ }
709
809
  };
710
- });
810
+ for (var i = 1; i < 10; ++i) {
811
+ keyMap[i] = pushRepeatCountDigit(i);
812
+ }
813
+ }
814
+ addCountBindings('vim');
815
+ addCountBindings('vim-prefix-d');
816
+ addCountBindings('vim-prefix-y');
817
+ addCountBindings('vim-prefix-c');
711
818
 
712
819
  // Create our keymaps for each operator and make xa and xi where x is an operator
713
820
  // change to the corrosponding keymap
@@ -721,12 +828,12 @@
721
828
  };
722
829
 
723
830
  CodeMirror.keyMap['vim-prefix-'+key]['A'] = function(cm) {
724
- reptTimes = 0;
831
+ repeatCount = 0;
725
832
  cm.setOption('keyMap', 'vim-prefix-' + key + 'a');
726
833
  };
727
834
 
728
835
  CodeMirror.keyMap['vim-prefix-'+key]['I'] = function(cm) {
729
- reptTimes = 0;
836
+ repeatCount = 0;
730
837
  cm.setOption('keyMap', 'vim-prefix-' + key + 'i');
731
838
  };
732
839
  });
@@ -739,7 +846,7 @@
739
846
 
740
847
  // Create our text object functions. They work similar to motions but they
741
848
  // return a start cursor as well
742
- var textObjectList = ['W', 'Shift-[', 'Shift-9', '['];
849
+ var textObjectList = ['W', 'Shift-[', 'Shift-9', '[', "'", "Shift-'"];
743
850
  var textObjects = {
744
851
  'W': function(cm, inclusive) {
745
852
  var cur = cm.getCursor();
@@ -754,7 +861,9 @@
754
861
  },
755
862
  'Shift-[': function(cm, inclusive) { return selectCompanionObject(cm, '}', inclusive); },
756
863
  'Shift-9': function(cm, inclusive) { return selectCompanionObject(cm, ')', inclusive); },
757
- '[': function(cm, inclusive) { return selectCompanionObject(cm, ']', inclusive); }
864
+ '[': function(cm, inclusive) { return selectCompanionObject(cm, ']', inclusive); },
865
+ "'": function(cm, inclusive) { return findBeginningAndEnd(cm, "'", inclusive); },
866
+ "Shift-'": function(cm, inclusive) { return findBeginningAndEnd(cm, '"', inclusive); }
758
867
  };
759
868
 
760
869
  // One function to handle all operation upon text objects. Kinda funky but it works