codemirror-rails 3.22 → 3.23

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4503a996155c96877d36e19f5e461db92383dbf4
4
- data.tar.gz: 5abe2e7b9dc2f39f4ec1185d220e3dc8ab9a8eb9
3
+ metadata.gz: a43600e7cc5c0826859fd737519ab1a93720bfd5
4
+ data.tar.gz: 7bb9599e53f755a14682493259634e7bf3ef485e
5
5
  SHA512:
6
- metadata.gz: 22aef71a6f3827e6f71a38213649a0ad7d081d2b5fc8831ccfadf726246f2d5d59435823dbc6b2119685c98e4d859381a7429debbae6622f3cf655cbc404e4d4
7
- data.tar.gz: 12642596e377573cdcad5b977aa648310069efd0c2278e192aff0758bce6ce9e2d2f81f295a9d942148028eb392c1594f684f685aeb151bb15b52ee1d54c3dc2
6
+ metadata.gz: 62bdf9e6c06fc7e21e468c8f1d346eb0b8bcf6ef4bca06795ec60c61ce0bbec3e503be8443a5e5a9f3a124ec7c96bbbbfe39c986307b335d8c0e0d4e3e3dcde2
7
+ data.tar.gz: 1bc95b49a2b8c34126bed096693b0a7b231d78d0ca361522ab1dc6be1b9f55200ea76a5ab2327ec86b4d8ee5bb1adb162c8315b33e8e19a98f87f55a358a2355
@@ -1,6 +1,6 @@
1
1
  module Codemirror
2
2
  module Rails
3
- VERSION = '3.22'
4
- CODEMIRROR_VERSION = '3.22'
3
+ VERSION = '3.23'
4
+ CODEMIRROR_VERSION = '3.23'
5
5
  end
6
6
  end
@@ -1,5 +1,3 @@
1
- // CodeMirror version 3.22
2
- //
3
1
  // CodeMirror is the only global var we claim
4
2
  window.CodeMirror = (function() {
5
3
  "use strict";
@@ -112,8 +110,8 @@ window.CodeMirror = (function() {
112
110
  // Wraps and hides input textarea
113
111
  d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
114
112
  // The actual fake scrollbars.
115
- d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar");
116
- d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar");
113
+ d.scrollbarH = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar");
114
+ d.scrollbarV = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar");
117
115
  d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
118
116
  d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler");
119
117
  // DIVs containing the selection and the actual code
@@ -232,12 +230,17 @@ window.CodeMirror = (function() {
232
230
  var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;
233
231
  var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3);
234
232
  return function(line) {
235
- if (lineIsHidden(cm.doc, line))
236
- return 0;
237
- else if (wrapping)
238
- return (Math.ceil(line.text.length / perLine) || 1) * th;
233
+ if (lineIsHidden(cm.doc, line)) return 0;
234
+
235
+ var widgetsHeight = 0;
236
+ if (line.widgets) for (var i = 0; i < line.widgets.length; i++) {
237
+ if (line.widgets[i].height) widgetsHeight += line.widgets[i].height;
238
+ }
239
+
240
+ if (wrapping)
241
+ return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th;
239
242
  else
240
- return th;
243
+ return widgetsHeight + th;
241
244
  };
242
245
  }
243
246
 
@@ -335,8 +338,8 @@ window.CodeMirror = (function() {
335
338
  d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px";
336
339
  d.gutters.style.height = Math.max(totalHeight, d.scroller.clientHeight - scrollerCutOff) + "px";
337
340
  var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight);
338
- var needsH = d.scroller.scrollWidth > (d.scroller.clientWidth + 1);
339
- var needsV = scrollHeight > (d.scroller.clientHeight + 1);
341
+ var needsH = d.scroller.scrollWidth > d.scroller.clientWidth;
342
+ var needsV = scrollHeight > d.scroller.clientHeight;
340
343
  if (needsV) {
341
344
  d.scrollbarV.style.display = "block";
342
345
  d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0";
@@ -368,7 +371,12 @@ window.CodeMirror = (function() {
368
371
 
369
372
  if (mac_geLion && scrollbarWidth(d.measure) === 0) {
370
373
  d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px";
371
- d.scrollbarV.style.pointerEvents = d.scrollbarH.style.pointerEvents = "none";
374
+ var barMouseDown = function(e) {
375
+ if (e_target(e) != d.scrollbarV && e_target(e) != d.scrollbarH)
376
+ operation(cm, onMouseDown)(e);
377
+ };
378
+ on(d.scrollbarV, "mousedown", barMouseDown);
379
+ on(d.scrollbarH, "mousedown", barMouseDown);
372
380
  }
373
381
  }
374
382
 
@@ -1160,7 +1168,12 @@ window.CodeMirror = (function() {
1160
1168
  var pre = buildLineContent(cm, line, null, true).pre;
1161
1169
  var end = pre.appendChild(zeroWidthElement(cm.display.measure));
1162
1170
  removeChildrenAndAdd(cm.display.measure, pre);
1163
- return getRect(end).right - getRect(cm.display.lineDiv).left;
1171
+ var rect = getRect(end);
1172
+ if (rect.right == 0 && rect.bottom == 0) {
1173
+ end = pre.appendChild(elt("span", "\u00a0"));
1174
+ rect = getRect(end);
1175
+ }
1176
+ return rect.left - getRect(cm.display.lineDiv).left;
1164
1177
  }
1165
1178
 
1166
1179
  function clearCaches(cm) {
@@ -1495,10 +1508,6 @@ window.CodeMirror = (function() {
1495
1508
  function readInput(cm) {
1496
1509
  var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel;
1497
1510
  if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.options.disableInput) return false;
1498
- if (cm.state.pasteIncoming && cm.state.fakedLastChar) {
1499
- input.value = input.value.substring(0, input.value.length - 1);
1500
- cm.state.fakedLastChar = false;
1501
- }
1502
1511
  var text = input.value;
1503
1512
  if (text == prevInput && posEq(sel.from, sel.to)) return false;
1504
1513
  if (ie && !ie_lt9 && cm.display.inputHasSelection === text) {
@@ -1665,16 +1674,6 @@ window.CodeMirror = (function() {
1665
1674
  fastPoll(cm);
1666
1675
  });
1667
1676
  on(d.input, "paste", function() {
1668
- // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206
1669
- // Add a char to the end of textarea before paste occur so that
1670
- // selection doesn't span to the end of textarea.
1671
- if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) {
1672
- var start = d.input.selectionStart, end = d.input.selectionEnd;
1673
- d.input.value += "$";
1674
- d.input.selectionStart = start;
1675
- d.input.selectionEnd = end;
1676
- cm.state.fakedLastChar = true;
1677
- }
1678
1677
  cm.state.pasteIncoming = true;
1679
1678
  fastPoll(cm);
1680
1679
  });
@@ -1708,8 +1707,7 @@ window.CodeMirror = (function() {
1708
1707
  var display = cm.display;
1709
1708
  if (!liberal) {
1710
1709
  var target = e_target(e);
1711
- if (target == display.scrollbarH || target == display.scrollbarH.firstChild ||
1712
- target == display.scrollbarV || target == display.scrollbarV.firstChild ||
1710
+ if (target == display.scrollbarH || target == display.scrollbarV ||
1713
1711
  target == display.scrollbarFiller || target == display.gutterFiller) return null;
1714
1712
  }
1715
1713
  var x, y, space = getRect(display.lineSpace);
@@ -2230,8 +2228,9 @@ window.CodeMirror = (function() {
2230
2228
  var oldCSS = display.input.style.cssText;
2231
2229
  display.inputDiv.style.position = "absolute";
2232
2230
  display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
2233
- "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: transparent; outline: none;" +
2234
- "border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);";
2231
+ "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " +
2232
+ (ie ? "rgba(255, 255, 255, .05)" : "transparent") +
2233
+ "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
2235
2234
  focusInput(cm);
2236
2235
  resetInput(cm, true);
2237
2236
  // Adds "Select all" to context menu in FF
@@ -3064,7 +3063,7 @@ window.CodeMirror = (function() {
3064
3063
  else if (line > last) { line = last; end = true; }
3065
3064
  var lineObj = getLine(this.doc, line);
3066
3065
  return intoCoordSystem(this, getLine(this.doc, line), {top: 0, left: 0}, mode || "page").top +
3067
- (end ? lineObj.height : 0);
3066
+ (end ? this.doc.height - heightAtLine(this, lineObj) : 0);
3068
3067
  },
3069
3068
 
3070
3069
  defaultTextHeight: function() { return textHeight(this.display); },
@@ -3905,6 +3904,7 @@ window.CodeMirror = (function() {
3905
3904
  this.doc.cantEdit = false;
3906
3905
  if (cm) reCheckSelection(cm);
3907
3906
  }
3907
+ if (cm) signalLater(cm, "markerCleared", cm, this);
3908
3908
  if (withOp) endOperation(cm);
3909
3909
  };
3910
3910
 
@@ -4013,6 +4013,7 @@ window.CodeMirror = (function() {
4013
4013
  if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.collapsed)
4014
4014
  regChange(cm, from.line, to.line + 1);
4015
4015
  if (marker.atomic) reCheckSelection(cm);
4016
+ signalLater(cm, "markerAdded", cm, marker);
4016
4017
  }
4017
4018
  return marker;
4018
4019
  }
@@ -5024,6 +5025,7 @@ window.CodeMirror = (function() {
5024
5025
  redo: docOperation(function() {makeChangeFromHistory(this, "redo");}),
5025
5026
 
5026
5027
  setExtending: function(val) {this.sel.extend = val;},
5028
+ getExtending: function() {return this.sel.extend;},
5027
5029
 
5028
5030
  historySize: function() {
5029
5031
  var hist = this.history;
@@ -6087,7 +6089,7 @@ window.CodeMirror = (function() {
6087
6089
 
6088
6090
  // THE END
6089
6091
 
6090
- CodeMirror.version = "3.22.0";
6092
+ CodeMirror.version = "3.23.0";
6091
6093
 
6092
6094
  return CodeMirror;
6093
6095
  })();
@@ -93,6 +93,7 @@
93
93
  data = data_;
94
94
  if (finished) return;
95
95
  if (!data || !data.list.length) return done();
96
+ if (completion.widget) completion.widget.close();
96
97
  completion.widget = new Widget(completion, data);
97
98
  }
98
99
 
@@ -7,6 +7,7 @@
7
7
  QUERY_DIV: ";",
8
8
  ALIAS_KEYWORD: "AS"
9
9
  };
10
+ var Pos = CodeMirror.Pos;
10
11
 
11
12
  function getKeywords(editor) {
12
13
  var mode = editor.doc.modeOption;
@@ -36,7 +37,7 @@
36
37
  var cur = editor.getCursor();
37
38
  var token = editor.getTokenAt(cur);
38
39
  var string = token.string.substr(1);
39
- var prevCur = CodeMirror.Pos(cur.line, token.start);
40
+ var prevCur = Pos(cur.line, token.start);
40
41
  var table = editor.getTokenAt(prevCur).string;
41
42
  if( !tables.hasOwnProperty( table ) ){
42
43
  table = findTableByAlias(table, editor);
@@ -64,7 +65,7 @@
64
65
  }
65
66
 
66
67
  function convertNumberToCur( num ){
67
- return CodeMirror.Pos(Math.floor( num ), +num.toString().split( '.' ).pop());
68
+ return Pos(Math.floor( num ), +num.toString().split( '.' ).pop());
68
69
  }
69
70
 
70
71
  function findTableByAlias(alias, editor) {
@@ -75,8 +76,8 @@
75
76
  var table = "";
76
77
  var separator = [];
77
78
  var validRange = {
78
- start: CodeMirror.Pos( 0, 0 ),
79
- end: CodeMirror.Pos( editor.lastLine(), editor.getLineHandle( editor.lastLine() ).length )
79
+ start: Pos( 0, 0 ),
80
+ end: Pos( editor.lastLine(), editor.getLineHandle( editor.lastLine() ).length )
80
81
  };
81
82
 
82
83
  //add separator
@@ -85,8 +86,8 @@
85
86
  separator.push( doc.posFromIndex(indexOfSeparator));
86
87
  indexOfSeparator = fullQuery.indexOf( CONS.QUERY_DIV, indexOfSeparator+1);
87
88
  }
88
- separator.unshift( CodeMirror.Pos( 0, 0 ) );
89
- separator.push( CodeMirror.Pos( editor.lastLine(), editor.getLineHandle( editor.lastLine() ).text.length ) );
89
+ separator.unshift( Pos( 0, 0 ) );
90
+ separator.push( Pos( editor.lastLine(), editor.getLineHandle( editor.lastLine() ).text.length ) );
90
91
 
91
92
  //find valieRange
92
93
  var prevItem = 0;
@@ -122,24 +123,31 @@
122
123
  tables = (options && options.tables) || {};
123
124
  keywords = keywords || getKeywords(editor);
124
125
  var cur = editor.getCursor();
125
- var token = editor.getTokenAt(cur);
126
+ var token = editor.getTokenAt(cur), end = token.end;
126
127
  var result = [];
127
128
  var search = token.string.trim();
128
129
 
129
- addMatches(result, search, keywords,
130
- function(w) {return w.toUpperCase();});
131
-
132
- addMatches(result, search, tables,
133
- function(w) {return w;});
134
-
135
- if(search.lastIndexOf('.') === 0) {
130
+ if (search.charAt(0) == ".") {
136
131
  columnCompletion(result, editor);
132
+ if (!result.length) {
133
+ while (token.start && search.charAt(0) == ".") {
134
+ token = editor.getTokenAt(Pos(cur.line, token.start - 1));
135
+ search = token.string + search;
136
+ }
137
+ addMatches(result, search, tables,
138
+ function(w) {return w;});
139
+ }
140
+ } else {
141
+ addMatches(result, search, keywords,
142
+ function(w) {return w.toUpperCase();});
143
+ addMatches(result, search, tables,
144
+ function(w) {return w;});
137
145
  }
138
146
 
139
147
  return {
140
148
  list: result,
141
- from: CodeMirror.Pos(cur.line, token.start),
142
- to: CodeMirror.Pos(cur.line, token.end)
149
+ from: Pos(cur.line, token.start),
150
+ to: Pos(cur.line, end)
143
151
  };
144
152
  }
145
153
  CodeMirror.registerHelper("hint", "sql", sqlHint);
@@ -11,22 +11,30 @@
11
11
  var inner = CodeMirror.innerMode(cm.getMode(), token.state);
12
12
  if (inner.mode.name != "xml") return;
13
13
  var result = [], replaceToken = false, prefix;
14
- var isTag = token.string.charAt(0) == "<";
15
- if (!inner.state.tagName || isTag) { // Tag completion
16
- if (isTag) {
17
- prefix = token.string.slice(1);
18
- replaceToken = true;
19
- }
14
+ var tag = /\btag\b/.test(token.type), tagName = tag && /^\w/.test(token.string), tagStart;
15
+ if (tagName) {
16
+ var before = cm.getLine(cur.line).slice(Math.max(0, token.start - 2), token.start);
17
+ var tagType = /<\/$/.test(before) ? "close" : /<$/.test(before) ? "open" : null;
18
+ if (tagType) tagStart = token.start - (tagType == "close" ? 2 : 1);
19
+ } else if (tag && token.string == "<") {
20
+ tagType = "open";
21
+ } else if (tag && token.string == "</") {
22
+ tagType = "close";
23
+ }
24
+ if (!tag && !inner.state.tagName || tagType) {
25
+ if (tagName)
26
+ prefix = token.string;
27
+ replaceToken = tagType;
20
28
  var cx = inner.state.context, curTag = cx && tags[cx.tagName];
21
29
  var childList = cx ? curTag && curTag.children : tags["!top"];
22
- if (childList) {
30
+ if (childList && tagType != "close") {
23
31
  for (var i = 0; i < childList.length; ++i) if (!prefix || childList[i].lastIndexOf(prefix, 0) == 0)
24
32
  result.push("<" + childList[i]);
25
- } else {
33
+ } else if (tagType != "close") {
26
34
  for (var name in tags) if (tags.hasOwnProperty(name) && name != "!top" && (!prefix || name.lastIndexOf(prefix, 0) == 0))
27
35
  result.push("<" + name);
28
36
  }
29
- if (cx && (!prefix || ("/" + cx.tagName).lastIndexOf(prefix, 0) == 0))
37
+ if (cx && (!prefix || tagType == "close" && cx.tagName.lastIndexOf(prefix, 0) == 0))
30
38
  result.push("</" + cx.tagName + ">");
31
39
  } else {
32
40
  // Attribute completion
@@ -59,7 +67,7 @@
59
67
  }
60
68
  return {
61
69
  list: result,
62
- from: replaceToken ? Pos(cur.line, token.start) : cur,
70
+ from: replaceToken ? Pos(cur.line, tagStart == null ? token.start : tagStart) : cur,
63
71
  to: replaceToken ? Pos(cur.line, token.end) : cur
64
72
  };
65
73
  }
@@ -4,6 +4,7 @@
4
4
 
5
5
  CodeMirror.registerHelper("lint", "css", function(text) {
6
6
  var found = [];
7
+ if (!window.CSSLint) return found;
7
8
  var results = CSSLint.verify(text), messages = results.messages, message = null;
8
9
  for ( var i = 0; i < messages.length; i++) {
9
10
  message = messages[i];
@@ -12,6 +12,7 @@
12
12
  "Unclosed string", "Stopping, unable to continue" ];
13
13
 
14
14
  function validator(text, options) {
15
+ if (!window.JSHINT) return [];
15
16
  JSHINT(text, options);
16
17
  var errors = JSHINT.data().errors, result = [];
17
18
  if (errors) parseErrors(errors, result);
@@ -82,6 +82,10 @@
82
82
  }
83
83
  dv.edit.on("change", change);
84
84
  dv.orig.on("change", change);
85
+ dv.edit.on("markerAdded", set);
86
+ dv.edit.on("markerCleared", set);
87
+ dv.orig.on("markerAdded", set);
88
+ dv.orig.on("markerCleared", set);
85
89
  dv.edit.on("viewportChange", set);
86
90
  dv.orig.on("viewportChange", set);
87
91
  update();
@@ -349,6 +353,12 @@
349
353
  setShowDifferences: function(val) {
350
354
  if (this.right) this.right.setShowDifferences(val);
351
355
  if (this.left) this.left.setShowDifferences(val);
356
+ },
357
+ rightChunks: function() {
358
+ return this.right && getChunks(this.right.diff);
359
+ },
360
+ leftChunks: function() {
361
+ return this.left && getChunks(this.left.diff);
352
362
  }
353
363
  };
354
364
 
@@ -399,6 +409,15 @@
399
409
  f(startOrig, orig.line + 1, startEdit, edit.line + 1);
400
410
  }
401
411
 
412
+ function getChunks(diff) {
413
+ var collect = [];
414
+ iterateChunks(diff, function(topOrig, botOrig, topEdit, botEdit) {
415
+ collect.push({origFrom: topOrig, origTo: botOrig,
416
+ editFrom: topEdit, editTo: botEdit});
417
+ });
418
+ return collect;
419
+ }
420
+
402
421
  function endOfLineClean(diff, i) {
403
422
  if (i == diff.length - 1) return true;
404
423
  var next = diff[i + 1][1];
@@ -16,16 +16,11 @@
16
16
  } else {
17
17
  query = new RegExp("^(?:" + query.source + ")", query.ignoreCase ? "i" : "");
18
18
  }
19
- if (typeof query == "string") return {token: function(stream) {
20
- if (stream.match(query)) return "searching";
21
- stream.next();
22
- stream.skipTo(query.charAt(0)) || stream.skipToEnd();
23
- }};
24
19
  return {token: function(stream) {
25
20
  if (stream.match(query)) return "searching";
26
21
  while (!stream.eol()) {
27
22
  stream.next();
28
- if (startChar)
23
+ if (startChar && !caseInsensitive)
29
24
  stream.skipTo(startChar) || stream.skipToEnd();
30
25
  if (stream.match(query, false)) break;
31
26
  }
@@ -74,7 +69,7 @@
74
69
  if (!query || state.query) return;
75
70
  state.query = parseQuery(query);
76
71
  cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
77
- state.overlay = searchOverlay(state.query);
72
+ state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
78
73
  cm.addOverlay(state.overlay);
79
74
  state.posFrom = state.posTo = cm.getCursor();
80
75
  findNext(cm, rev);
@@ -197,7 +197,7 @@
197
197
 
198
198
  function setMark(cm) {
199
199
  cm.setCursor(cm.getCursor());
200
- cm.setExtending(true);
200
+ cm.setExtending(!cm.getExtending());
201
201
  cm.on("change", function() { cm.setExtending(false); });
202
202
  }
203
203
 
@@ -32,7 +32,7 @@
32
32
  * ESC - leave insert mode, visual mode, and clear input state.
33
33
  * Ctrl-[, Ctrl-c - same as ESC.
34
34
  *
35
- * Registers: unamed, -, a-z, A-Z, 0-9
35
+ * Registers: unnamed, -, a-z, A-Z, 0-9
36
36
  * (Does not respect the special case for number registers when delete
37
37
  * operator is made with these commands: %, (, ), , /, ?, n, N, {, } )
38
38
  * TODO: Implement the remaining registers.
@@ -194,7 +194,7 @@
194
194
  { keys: [','], type: 'motion', motion: 'repeatLastCharacterSearch',
195
195
  motionArgs: { forward: false }},
196
196
  { keys: ['\'', 'character'], type: 'motion', motion: 'goToMark',
197
- motionArgs: {toJumplist: true}},
197
+ motionArgs: {toJumplist: true, linewise: true}},
198
198
  { keys: ['`', 'character'], type: 'motion', motion: 'goToMark',
199
199
  motionArgs: {toJumplist: true}},
200
200
  { keys: [']', '`'], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } },
@@ -210,6 +210,7 @@
210
210
  { keys: ['|'], type: 'motion',
211
211
  motion: 'moveToColumn',
212
212
  motionArgs: { }},
213
+ { keys: ['o'], type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: { },context:'visual'},
213
214
  // Operators
214
215
  { keys: ['d'], type: 'operator', operator: 'delete' },
215
216
  { keys: ['y'], type: 'operator', operator: 'yank' },
@@ -271,6 +272,7 @@
271
272
  { keys: ['v'], type: 'action', action: 'toggleVisualMode' },
272
273
  { keys: ['V'], type: 'action', action: 'toggleVisualMode',
273
274
  actionArgs: { linewise: true }},
275
+ { keys: ['g', 'v'], type: 'action', action: 'reselectLastSelection' },
274
276
  { keys: ['J'], type: 'action', action: 'joinLines', isEdit: true },
275
277
  { keys: ['p'], type: 'action', action: 'paste', isEdit: true,
276
278
  actionArgs: { after: true, isEdit: true }},
@@ -320,9 +322,11 @@
320
322
  { keys: ['?'], type: 'search',
321
323
  searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }},
322
324
  { keys: ['*'], type: 'search',
323
- searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }},
325
+ searchArgs: { forward: true, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }},
324
326
  { keys: ['#'], type: 'search',
325
- searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }},
327
+ searchArgs: { forward: false, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }},
328
+ { keys: ['g', '*'], type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }},
329
+ { keys: ['g', '#'], type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }},
326
330
  // Ex command
327
331
  { keys: [':'], type: 'ex' }
328
332
  ];
@@ -382,7 +386,7 @@
382
386
  var specialKeys = ['Left', 'Right', 'Up', 'Down', 'Space', 'Backspace',
383
387
  'Esc', 'Home', 'End', 'PageUp', 'PageDown', 'Enter'];
384
388
  var validMarks = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['<', '>']);
385
- var validRegisters = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['-', '"']);
389
+ var validRegisters = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['-', '"', '.', ':']);
386
390
 
387
391
  function isLine(cm, line) {
388
392
  return line >= cm.firstLine() && line <= cm.lastLine();
@@ -411,6 +415,41 @@
411
415
  return false;
412
416
  }
413
417
 
418
+ var options = {};
419
+ function defineOption(name, defaultValue, type) {
420
+ if (defaultValue === undefined) { throw Error('defaultValue is required'); }
421
+ if (!type) { type = 'string'; }
422
+ options[name] = {
423
+ type: type,
424
+ defaultValue: defaultValue
425
+ };
426
+ setOption(name, defaultValue);
427
+ }
428
+
429
+ function setOption(name, value) {
430
+ var option = options[name];
431
+ if (!option) {
432
+ throw Error('Unknown option: ' + name);
433
+ }
434
+ if (option.type == 'boolean') {
435
+ if (value && value !== true) {
436
+ throw Error('Invalid argument: ' + name + '=' + value);
437
+ } else if (value !== false) {
438
+ // Boolean options are set to true if value is not defined.
439
+ value = true;
440
+ }
441
+ }
442
+ option.value = option.type == 'boolean' ? !!value : value;
443
+ }
444
+
445
+ function getOption(name) {
446
+ var option = options[name];
447
+ if (!option) {
448
+ throw Error('Unknown option: ' + name);
449
+ }
450
+ return option.value;
451
+ }
452
+
414
453
  var createCircularJumpList = function() {
415
454
  var size = 100;
416
455
  var pointer = -1;
@@ -477,30 +516,52 @@
477
516
  };
478
517
  };
479
518
 
480
- var createMacroState = function() {
519
+ // Returns an object to track the changes associated insert mode. It
520
+ // clones the object that is passed in, or creates an empty object one if
521
+ // none is provided.
522
+ var createInsertModeChanges = function(c) {
523
+ if (c) {
524
+ // Copy construction
525
+ return {
526
+ changes: c.changes,
527
+ expectCursorActivityForChange: c.expectCursorActivityForChange
528
+ };
529
+ }
481
530
  return {
482
- macroKeyBuffer: [],
483
- latestRegister: undefined,
484
- inReplay: false,
485
- lastInsertModeChanges: {
486
- changes: [], // Change list
487
- expectCursorActivityForChange: false // Set to true on change, false on cursorActivity.
488
- },
489
- enteredMacroMode: undefined,
490
- isMacroPlaying: false,
491
- toggle: function(cm, registerName) {
492
- if (this.enteredMacroMode) { //onExit
493
- this.enteredMacroMode(); // close dialog
494
- this.enteredMacroMode = undefined;
495
- } else { //onEnter
496
- this.latestRegister = registerName;
497
- this.enteredMacroMode = cm.openDialog(
498
- '(recording)['+registerName+']', null, {bottom:true});
499
- }
500
- }
531
+ // Change list
532
+ changes: [],
533
+ // Set to true on change, false on cursorActivity.
534
+ expectCursorActivityForChange: false
501
535
  };
502
536
  };
503
537
 
538
+ function MacroModeState() {
539
+ this.latestRegister = undefined;
540
+ this.isPlaying = false;
541
+ this.isRecording = false;
542
+ this.replaySearchQueries = [];
543
+ this.onRecordingDone = undefined;
544
+ this.lastInsertModeChanges = createInsertModeChanges();
545
+ }
546
+ MacroModeState.prototype = {
547
+ exitMacroRecordMode: function() {
548
+ var macroModeState = vimGlobalState.macroModeState;
549
+ macroModeState.onRecordingDone(); // close dialog
550
+ macroModeState.onRecordingDone = undefined;
551
+ macroModeState.isRecording = false;
552
+ },
553
+ enterMacroRecordMode: function(cm, registerName) {
554
+ var register =
555
+ vimGlobalState.registerController.getRegister(registerName);
556
+ if (register) {
557
+ register.clear();
558
+ this.latestRegister = registerName;
559
+ this.onRecordingDone = cm.openDialog(
560
+ '(recording)['+registerName+']', null, {bottom:true});
561
+ this.isRecording = true;
562
+ }
563
+ }
564
+ };
504
565
 
505
566
  function maybeInitVimState(cm) {
506
567
  if (!cm.state.vim) {
@@ -531,7 +592,8 @@
531
592
  insertModeRepeat: undefined,
532
593
  visualMode: false,
533
594
  // If we are in visual line mode. No effect if visualMode is false.
534
- visualLine: false
595
+ visualLine: false,
596
+ lastSelection: null
535
597
  };
536
598
  }
537
599
  return cm.state.vim;
@@ -543,12 +605,18 @@
543
605
  searchQuery: null,
544
606
  // Whether we are searching backwards.
545
607
  searchIsReversed: false,
608
+ // Replace part of the last substituted pattern
609
+ lastSubstituteReplacePart: undefined,
546
610
  jumpList: createCircularJumpList(),
547
- macroModeState: createMacroState(),
611
+ macroModeState: new MacroModeState,
548
612
  // Recording latest f, t, F or T motion command.
549
613
  lastChararacterSearch: {increment:0, forward:true, selectedCharacter:''},
550
614
  registerController: new RegisterController({})
551
615
  };
616
+ for (var optionName in options) {
617
+ var option = options[optionName];
618
+ option.value = option.defaultValue;
619
+ }
552
620
  }
553
621
 
554
622
  var vimApi= {
@@ -576,6 +644,9 @@
576
644
  // Add user defined key bindings.
577
645
  exCommandDispatcher.map(lhs, rhs, ctx);
578
646
  },
647
+ setOption: setOption,
648
+ getOption: getOption,
649
+ defineOption: defineOption,
579
650
  defineEx: function(name, prefix, func){
580
651
  if (name.indexOf(prefix) !== 0) {
581
652
  throw new Error('(Vim.defineEx) "'+prefix+'" is not a prefix of "'+name+'", command not registered');
@@ -589,9 +660,9 @@
589
660
  var command;
590
661
  var vim = maybeInitVimState(cm);
591
662
  var macroModeState = vimGlobalState.macroModeState;
592
- if (macroModeState.enteredMacroMode) {
663
+ if (macroModeState.isRecording) {
593
664
  if (key == 'q') {
594
- actions.exitMacroRecordMode();
665
+ macroModeState.exitMacroRecordMode();
595
666
  vim.inputState = new InputState();
596
667
  return;
597
668
  }
@@ -621,6 +692,9 @@
621
692
  // Increment count unless count is 0 and key is 0.
622
693
  vim.inputState.pushRepeatDigit(key);
623
694
  }
695
+ if (macroModeState.isRecording) {
696
+ logKey(macroModeState, key);
697
+ }
624
698
  return;
625
699
  }
626
700
  if (command.type == 'keyToKey') {
@@ -629,7 +703,7 @@
629
703
  this.handleKey(cm, command.toKeys[i]);
630
704
  }
631
705
  } else {
632
- if (macroModeState.enteredMacroMode) {
706
+ if (macroModeState.isRecording) {
633
707
  logKey(macroModeState, key);
634
708
  }
635
709
  commandDispatcher.processCommand(cm, vim, command);
@@ -650,7 +724,7 @@
650
724
  this.motion = null;
651
725
  this.motionArgs = null;
652
726
  this.keyBuffer = []; // For matching multi-key commands.
653
- this.registerName = null; // Defaults to the unamed register.
727
+ this.registerName = null; // Defaults to the unnamed register.
654
728
  }
655
729
  InputState.prototype.pushRepeatDigit = function(n) {
656
730
  if (!this.operator) {
@@ -681,29 +755,39 @@
681
755
  */
682
756
  function Register(text, linewise) {
683
757
  this.clear();
684
- if (text) {
685
- this.set(text, linewise);
686
- }
758
+ this.keyBuffer = [text || ''];
759
+ this.insertModeChanges = [];
760
+ this.searchQueries = [];
761
+ this.linewise = !!linewise;
687
762
  }
688
763
  Register.prototype = {
689
- set: function(text, linewise) {
690
- this.text = text;
764
+ setText: function(text, linewise) {
765
+ this.keyBuffer = [text || ''];
691
766
  this.linewise = !!linewise;
692
767
  },
693
- append: function(text, linewise) {
768
+ pushText: function(text, linewise) {
694
769
  // if this register has ever been set to linewise, use linewise.
695
770
  if (linewise || this.linewise) {
696
- this.text += '\n' + text;
771
+ this.keyBuffer.push('\n');
697
772
  this.linewise = true;
698
- } else {
699
- this.text += text;
700
773
  }
774
+ this.keyBuffer.push(text);
775
+ },
776
+ pushInsertModeChanges: function(changes) {
777
+ this.insertModeChanges.push(createInsertModeChanges(changes));
778
+ },
779
+ pushSearchQuery: function(query) {
780
+ this.searchQueries.push(query);
701
781
  },
702
782
  clear: function() {
703
- this.text = '';
783
+ this.keyBuffer = [];
784
+ this.insertModeChanges = [];
785
+ this.searchQueries = [];
704
786
  this.linewise = false;
705
787
  },
706
- toString: function() { return this.text; }
788
+ toString: function() {
789
+ return this.keyBuffer.join('');
790
+ }
707
791
  };
708
792
 
709
793
  /*
@@ -716,14 +800,16 @@
716
800
  */
717
801
  function RegisterController(registers) {
718
802
  this.registers = registers;
719
- this.unamedRegister = registers['"'] = new Register();
803
+ this.unnamedRegister = registers['"'] = new Register();
804
+ registers['.'] = new Register();
805
+ registers[':'] = new Register();
720
806
  }
721
807
  RegisterController.prototype = {
722
808
  pushText: function(registerName, operator, text, linewise) {
723
809
  if (linewise && text.charAt(0) == '\n') {
724
810
  text = text.slice(1) + '\n';
725
811
  }
726
- if(linewise && text.charAt(text.length - 1) !== '\n'){
812
+ if (linewise && text.charAt(text.length - 1) !== '\n'){
727
813
  text += '\n';
728
814
  }
729
815
  // Lowercase and uppercase registers refer to the same register.
@@ -752,7 +838,7 @@
752
838
  break;
753
839
  }
754
840
  // Make sure the unnamed register is set to what just happened
755
- this.unamedRegister.set(text, linewise);
841
+ this.unnamedRegister.setText(text, linewise);
756
842
  return;
757
843
  }
758
844
 
@@ -760,22 +846,19 @@
760
846
  var append = isUpperCase(registerName);
761
847
  if (append) {
762
848
  register.append(text, linewise);
763
- // The unamed register always has the same value as the last used
849
+ // The unnamed register always has the same value as the last used
764
850
  // register.
765
- this.unamedRegister.append(text, linewise);
851
+ this.unnamedRegister.append(text, linewise);
766
852
  } else {
767
- register.set(text, linewise);
768
- this.unamedRegister.set(text, linewise);
853
+ register.setText(text, linewise);
854
+ this.unnamedRegister.setText(text, linewise);
769
855
  }
770
856
  },
771
- setRegisterText: function(name, text, linewise) {
772
- this.getRegister(name).set(text, linewise);
773
- },
774
857
  // Gets the register named @name. If one of @name doesn't already exist,
775
- // create it. If @name is invalid, return the unamedRegister.
858
+ // create it. If @name is invalid, return the unnamedRegister.
776
859
  getRegister: function(name) {
777
860
  if (!this.isValidRegister(name)) {
778
- return this.unamedRegister;
861
+ return this.unnamedRegister;
779
862
  }
780
863
  name = name.toLowerCase();
781
864
  if (!this.registers[name]) {
@@ -811,7 +894,7 @@
811
894
  // Match commands that take <character> as an argument.
812
895
  if (command.keys[keys.length - 1] == 'character') {
813
896
  selectedCharacter = keys[keys.length - 1];
814
- if(selectedCharacter.length>1){
897
+ if (selectedCharacter.length>1){
815
898
  switch(selectedCharacter){
816
899
  case '<CR>':
817
900
  selectedCharacter='\n';
@@ -973,6 +1056,7 @@
973
1056
  return;
974
1057
  }
975
1058
  var forward = command.searchArgs.forward;
1059
+ var wholeWordOnly = command.searchArgs.wholeWordOnly;
976
1060
  getSearchState(cm).setReversed(!forward);
977
1061
  var promptPrefix = (forward) ? '/' : '?';
978
1062
  var originalQuery = getSearchState(cm).getQuery();
@@ -993,6 +1077,10 @@
993
1077
  function onPromptClose(query) {
994
1078
  cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
995
1079
  handleQuery(query, true /** ignoreCase */, true /** smartCase */);
1080
+ var macroModeState = vimGlobalState.macroModeState;
1081
+ if (macroModeState.isRecording) {
1082
+ logSearchQuery(macroModeState, query);
1083
+ }
996
1084
  }
997
1085
  function onPromptKeyUp(_e, query) {
998
1086
  var parsedQuery;
@@ -1023,13 +1111,19 @@
1023
1111
  }
1024
1112
  switch (command.searchArgs.querySrc) {
1025
1113
  case 'prompt':
1026
- showPrompt(cm, {
1027
- onClose: onPromptClose,
1028
- prefix: promptPrefix,
1029
- desc: searchPromptDesc,
1030
- onKeyUp: onPromptKeyUp,
1031
- onKeyDown: onPromptKeyDown
1032
- });
1114
+ var macroModeState = vimGlobalState.macroModeState;
1115
+ if (macroModeState.isPlaying) {
1116
+ var query = macroModeState.replaySearchQueries.shift();
1117
+ handleQuery(query, true /** ignoreCase */, false /** smartCase */);
1118
+ } else {
1119
+ showPrompt(cm, {
1120
+ onClose: onPromptClose,
1121
+ prefix: promptPrefix,
1122
+ desc: searchPromptDesc,
1123
+ onKeyUp: onPromptKeyUp,
1124
+ onKeyDown: onPromptKeyDown
1125
+ });
1126
+ }
1033
1127
  break;
1034
1128
  case 'wordUnderCursor':
1035
1129
  var word = expandWordUnderCursor(cm, false /** inclusive */,
@@ -1047,8 +1141,8 @@
1047
1141
  }
1048
1142
  var query = cm.getLine(word.start.line).substring(word.start.ch,
1049
1143
  word.end.ch);
1050
- if (isKeyword) {
1051
- query = '\\b' + query + '\\b';
1144
+ if (isKeyword && wholeWordOnly) {
1145
+ query = '\\b' + query + '\\b';
1052
1146
  } else {
1053
1147
  query = escapeRegex(query);
1054
1148
  }
@@ -1174,6 +1268,7 @@
1174
1268
  selectionStart.ch -= 1;
1175
1269
  }
1176
1270
  selectionEnd = curEnd;
1271
+ selectionStart = (motionResult instanceof Array) ? curStart : selectionStart;
1177
1272
  if (vim.visualLine) {
1178
1273
  if (cursorIsBefore(selectionStart, selectionEnd)) {
1179
1274
  selectionStart.ch = 0;
@@ -1243,7 +1338,7 @@
1243
1338
  },
1244
1339
  recordLastEdit: function(vim, inputState, actionCommand) {
1245
1340
  var macroModeState = vimGlobalState.macroModeState;
1246
- if (macroModeState.inReplay) { return; }
1341
+ if (macroModeState.isPlaying) { return; }
1247
1342
  vim.lastEditInputState = inputState;
1248
1343
  vim.lastEditActionCommand = actionCommand;
1249
1344
  macroModeState.lastInsertModeChanges.changes = [];
@@ -1288,13 +1383,24 @@
1288
1383
  highlightSearchMatches(cm, query);
1289
1384
  return findNext(cm, prev/** prev */, query, motionArgs.repeat);
1290
1385
  },
1291
- goToMark: function(_cm, motionArgs, vim) {
1386
+ goToMark: function(cm, motionArgs, vim) {
1292
1387
  var mark = vim.marks[motionArgs.selectedCharacter];
1293
1388
  if (mark) {
1294
- return mark.find();
1389
+ var pos = mark.find();
1390
+ return motionArgs.linewise ? { line: pos.line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(pos.line)) } : pos;
1295
1391
  }
1296
1392
  return null;
1297
1393
  },
1394
+ moveToOtherHighlightedEnd: function(cm) {
1395
+ var curEnd = copyCursor(cm.getCursor('head'));
1396
+ var curStart = copyCursor(cm.getCursor('anchor'));
1397
+ if (cursorIsBefore(curStart, curEnd)) {
1398
+ curEnd.ch += 1;
1399
+ } else if (cursorIsBefore(curEnd, curStart)) {
1400
+ curStart.ch -= 1;
1401
+ }
1402
+ return ([curEnd,curStart]);
1403
+ },
1298
1404
  jumpToMark: function(cm, motionArgs, vim) {
1299
1405
  var best = cm.getCursor();
1300
1406
  for (var i = 0; i < motionArgs.repeat; i++) {
@@ -1368,7 +1474,7 @@
1368
1474
  (line > last && cur.line == last)) {
1369
1475
  return;
1370
1476
  }
1371
- if(motionArgs.toFirstChar){
1477
+ if (motionArgs.toFirstChar){
1372
1478
  endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line));
1373
1479
  vim.lastHPos = endCh;
1374
1480
  }
@@ -1740,29 +1846,21 @@
1740
1846
  }
1741
1847
  cm.scrollTo(null, y);
1742
1848
  },
1743
- replayMacro: function(cm, actionArgs) {
1849
+ replayMacro: function(cm, actionArgs, vim) {
1744
1850
  var registerName = actionArgs.selectedCharacter;
1745
1851
  var repeat = actionArgs.repeat;
1746
1852
  var macroModeState = vimGlobalState.macroModeState;
1747
1853
  if (registerName == '@') {
1748
1854
  registerName = macroModeState.latestRegister;
1749
1855
  }
1750
- var keyBuffer = parseRegisterToKeyBuffer(macroModeState, registerName);
1751
1856
  while(repeat--){
1752
- executeMacroKeyBuffer(cm, macroModeState, keyBuffer);
1857
+ executeMacroRegister(cm, vim, macroModeState, registerName);
1753
1858
  }
1754
1859
  },
1755
- exitMacroRecordMode: function() {
1756
- var macroModeState = vimGlobalState.macroModeState;
1757
- macroModeState.toggle();
1758
- parseKeyBufferToRegister(macroModeState.latestRegister,
1759
- macroModeState.macroKeyBuffer);
1760
- },
1761
1860
  enterMacroRecordMode: function(cm, actionArgs) {
1762
1861
  var macroModeState = vimGlobalState.macroModeState;
1763
1862
  var registerName = actionArgs.selectedCharacter;
1764
- macroModeState.toggle(cm, registerName);
1765
- emptyMacroKeyBuffer(macroModeState);
1863
+ macroModeState.enterMacroRecordMode(cm, registerName);
1766
1864
  },
1767
1865
  enterInsertMode: function(cm, actionArgs, vim) {
1768
1866
  if (cm.getOption('readOnly')) { return; }
@@ -1789,7 +1887,7 @@
1789
1887
  cm.setOption('keyMap', 'vim-insert');
1790
1888
  CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"});
1791
1889
  }
1792
- if (!vimGlobalState.macroModeState.inReplay) {
1890
+ if (!vimGlobalState.macroModeState.isPlaying) {
1793
1891
  // Only record if not replaying.
1794
1892
  cm.on('change', onChange);
1795
1893
  cm.on('cursorActivity', onCursorActivity);
@@ -1857,6 +1955,21 @@
1857
1955
  updateMark(cm, vim, '>', cursorIsBefore(curStart, curEnd) ? curEnd
1858
1956
  : curStart);
1859
1957
  },
1958
+ reselectLastSelection: function(cm, _actionArgs, vim) {
1959
+ if (vim.lastSelection) {
1960
+ var lastSelection = vim.lastSelection;
1961
+ cm.setSelection(lastSelection.curStart, lastSelection.curEnd);
1962
+ if (lastSelection.visualLine) {
1963
+ vim.visualMode = true;
1964
+ vim.visualLine = true;
1965
+ }
1966
+ else {
1967
+ vim.visualMode = true;
1968
+ vim.visualLine = false;
1969
+ }
1970
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : ""});
1971
+ }
1972
+ },
1860
1973
  joinLines: function(cm, actionArgs, vim) {
1861
1974
  var curStart, curEnd;
1862
1975
  if (vim.visualMode) {
@@ -1906,11 +2019,12 @@
1906
2019
  var cur = cm.getCursor();
1907
2020
  var register = vimGlobalState.registerController.getRegister(
1908
2021
  actionArgs.registerName);
1909
- if (!register.text) {
2022
+ var text = register.toString();
2023
+ if (!text) {
1910
2024
  return;
1911
2025
  }
1912
- for (var text = '', i = 0; i < actionArgs.repeat; i++) {
1913
- text += register.text;
2026
+ if (actionArgs.repeat > 1) {
2027
+ var text = Array(actionArgs.repeat + 1).join(text);
1914
2028
  }
1915
2029
  var linewise = register.linewise;
1916
2030
  if (linewise) {
@@ -1965,7 +2079,7 @@
1965
2079
  var curStart = cm.getCursor();
1966
2080
  var replaceTo;
1967
2081
  var curEnd;
1968
- if(vim.visualMode){
2082
+ if (vim.visualMode){
1969
2083
  curStart=cm.getCursor('start');
1970
2084
  curEnd=cm.getCursor('end');
1971
2085
  // workaround to catch the character under the cursor
@@ -1979,8 +2093,8 @@
1979
2093
  }
1980
2094
  curEnd = { line: curStart.line, ch: replaceTo };
1981
2095
  }
1982
- if(replaceWith=='\n'){
1983
- if(!vim.visualMode) cm.replaceRange('', curStart, curEnd);
2096
+ if (replaceWith=='\n'){
2097
+ if (!vim.visualMode) cm.replaceRange('', curStart, curEnd);
1984
2098
  // special case, where vim help says to replace by just one line-break
1985
2099
  (CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm);
1986
2100
  }else {
@@ -1988,7 +2102,7 @@
1988
2102
  //replace all characters in range by selected, but keep linebreaks
1989
2103
  replaceWithStr=replaceWithStr.replace(/[^\n]/g,replaceWith);
1990
2104
  cm.replaceRange(replaceWithStr, curStart, curEnd);
1991
- if(vim.visualMode){
2105
+ if (vim.visualMode){
1992
2106
  cm.setCursor(curStart);
1993
2107
  exitVisualMode(cm);
1994
2108
  }else{
@@ -2009,9 +2123,9 @@
2009
2123
  token = match[0];
2010
2124
  start = match.index;
2011
2125
  end = start + token.length;
2012
- if(cur.ch < end)break;
2126
+ if (cur.ch < end)break;
2013
2127
  }
2014
- if(!actionArgs.backtrack && (end <= cur.ch))return;
2128
+ if (!actionArgs.backtrack && (end <= cur.ch))return;
2015
2129
  if (token) {
2016
2130
  var increment = actionArgs.increase ? 1 : -1;
2017
2131
  var number = parseInt(token) + (increment * actionArgs.repeat);
@@ -2120,6 +2234,10 @@
2120
2234
  function exitVisualMode(cm) {
2121
2235
  cm.off('mousedown', exitVisualMode);
2122
2236
  var vim = cm.state.vim;
2237
+ // can't use selection state here because yank has already reset its cursor
2238
+ vim.lastSelection = {'curStart': vim.marks['<'].find(),
2239
+ 'curEnd': vim.marks['>'].find(), 'visualMode': vim.visualMode,
2240
+ 'visualLine': vim.visualLine};
2123
2241
  vim.visualMode = false;
2124
2242
  vim.visualLine = false;
2125
2243
  var selectionStart = cm.getCursor('anchor');
@@ -2243,7 +2361,7 @@
2243
2361
  }
2244
2362
 
2245
2363
  function recordJumpPosition(cm, oldCur, newCur) {
2246
- if(!cursorEqual(oldCur, newCur)) {
2364
+ if (!cursorEqual(oldCur, newCur)) {
2247
2365
  vimGlobalState.jumpList.add(cm, oldCur, newCur);
2248
2366
  }
2249
2367
  }
@@ -2266,7 +2384,7 @@
2266
2384
  isComplete: function(state) {
2267
2385
  if (state.nextCh === state.symb) {
2268
2386
  state.depth++;
2269
- if(state.depth >= 1)return true;
2387
+ if (state.depth >= 1)return true;
2270
2388
  } else if (state.nextCh === state.reverseSymb) {
2271
2389
  state.depth--;
2272
2390
  }
@@ -2298,7 +2416,7 @@
2298
2416
  state.reverseSymb = state.symb === '{' ? '}' : '{';
2299
2417
  },
2300
2418
  isComplete: function(state) {
2301
- if(state.nextCh === state.symb)return true;
2419
+ if (state.nextCh === state.symb)return true;
2302
2420
  return false;
2303
2421
  }
2304
2422
  },
@@ -2320,7 +2438,7 @@
2320
2438
  }
2321
2439
  state.depth--;
2322
2440
  }
2323
- if(token === 'else' && state.depth === 0)return true;
2441
+ if (token === 'else' && state.depth === 0)return true;
2324
2442
  }
2325
2443
  return false;
2326
2444
  }
@@ -2345,10 +2463,10 @@
2345
2463
  curMoveThrough: false
2346
2464
  };
2347
2465
  var mode = symbolToMode[symb];
2348
- if(!mode)return cur;
2466
+ if (!mode)return cur;
2349
2467
  var init = findSymbolModes[mode].init;
2350
2468
  var isComplete = findSymbolModes[mode].isComplete;
2351
- if(init)init(state);
2469
+ if (init) { init(state); }
2352
2470
  while (line !== endLine && repeat) {
2353
2471
  state.index += increment;
2354
2472
  state.nextCh = state.lineText.charAt(state.index);
@@ -2633,14 +2751,14 @@
2633
2751
  var end = findMatchedSymbol(cm, cur, revSymb);
2634
2752
  var start = findMatchedSymbol(cm, end);
2635
2753
 
2636
- if((start.line == end.line && start.ch > end.ch)
2754
+ if ((start.line == end.line && start.ch > end.ch)
2637
2755
  || (start.line > end.line)) {
2638
2756
  var tmp = start;
2639
2757
  start = end;
2640
2758
  end = tmp;
2641
2759
  }
2642
2760
 
2643
- if(inclusive) {
2761
+ if (inclusive) {
2644
2762
  end.ch += 1;
2645
2763
  } else {
2646
2764
  start.ch += 1;
@@ -2711,6 +2829,7 @@
2711
2829
  }
2712
2830
 
2713
2831
  // Search functions
2832
+ defineOption('pcre', true, 'boolean');
2714
2833
  function SearchState() {}
2715
2834
  SearchState.prototype = {
2716
2835
  getQuery: function() {
@@ -2760,7 +2879,7 @@
2760
2879
  }
2761
2880
 
2762
2881
  // Translates a search string from ex (vim) syntax into javascript form.
2763
- function fixRegex(str) {
2882
+ function translateRegex(str) {
2764
2883
  // When these match, add a '\' if unescaped or remove one if escaped.
2765
2884
  var specials = ['|', '(', ')', '{'];
2766
2885
  // Remove, but never add, a '\' for these.
@@ -2799,15 +2918,17 @@
2799
2918
  }
2800
2919
 
2801
2920
  // 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
2921
+ // javascript form. Similar to translateRegex, but additionally fixes back references
2803
2922
  // (translates '\[0..9]' to '$[0..9]') and follows different rules for escaping '$'.
2804
- function fixRegexReplace(str) {
2923
+ function translateRegexReplace(str) {
2805
2924
  var escapeNextChar = false;
2806
2925
  var out = [];
2807
2926
  for (var i = -1; i < str.length; i++) {
2808
2927
  var c = str.charAt(i) || '';
2809
2928
  var n = str.charAt(i+1) || '';
2810
2929
  if (escapeNextChar) {
2930
+ // At any point in the loop, escapeNextChar is true if the previous
2931
+ // character was a '\' and was not escaped.
2811
2932
  out.push(c);
2812
2933
  escapeNextChar = false;
2813
2934
  } else {
@@ -2832,6 +2953,29 @@
2832
2953
  return out.join('');
2833
2954
  }
2834
2955
 
2956
+ // Unescape \ and / in the replace part, for PCRE mode.
2957
+ function unescapeRegexReplace(str) {
2958
+ var stream = new CodeMirror.StringStream(str);
2959
+ var output = [];
2960
+ while (!stream.eol()) {
2961
+ // Search for \.
2962
+ while (stream.peek() && stream.peek() != '\\') {
2963
+ output.push(stream.next());
2964
+ }
2965
+ if (stream.match('\\/', true)) {
2966
+ // \/ => /
2967
+ output.push('/');
2968
+ } else if (stream.match('\\\\', true)) {
2969
+ // \\ => \
2970
+ output.push('\\');
2971
+ } else {
2972
+ // Don't change anything
2973
+ output.push(stream.next());
2974
+ }
2975
+ }
2976
+ return output.join('');
2977
+ }
2978
+
2835
2979
  /**
2836
2980
  * Extract the regular expression from the query and return a Regexp object.
2837
2981
  * Returns null if the query is blank.
@@ -2863,7 +3007,9 @@
2863
3007
  if (!regexPart) {
2864
3008
  return null;
2865
3009
  }
2866
- regexPart = fixRegex(regexPart);
3010
+ if (!getOption('pcre')) {
3011
+ regexPart = translateRegex(regexPart);
3012
+ }
2867
3013
  if (smartCase) {
2868
3014
  ignoreCase = (/^[^A-Z]*$/).test(regexPart);
2869
3015
  }
@@ -3045,13 +3191,16 @@
3045
3191
  { name: 'map' },
3046
3192
  { name: 'nmap', shortName: 'nm' },
3047
3193
  { name: 'vmap', shortName: 'vm' },
3194
+ { name: 'unmap' },
3048
3195
  { name: 'write', shortName: 'w' },
3049
3196
  { name: 'undo', shortName: 'u' },
3050
3197
  { name: 'redo', shortName: 'red' },
3198
+ { name: 'set', shortName: 'set' },
3051
3199
  { name: 'sort', shortName: 'sor' },
3052
3200
  { name: 'substitute', shortName: 's' },
3053
3201
  { name: 'nohlsearch', shortName: 'noh' },
3054
- { name: 'delmarks', shortName: 'delm' }
3202
+ { name: 'delmarks', shortName: 'delm' },
3203
+ { name: 'registers', shortName: 'reg', excludeFromCommandHistory: true }
3055
3204
  ];
3056
3205
  Vim.ExCommandDispatcher = function() {
3057
3206
  this.buildCommandMap_();
@@ -3059,17 +3208,21 @@
3059
3208
  Vim.ExCommandDispatcher.prototype = {
3060
3209
  processCommand: function(cm, input) {
3061
3210
  var vim = cm.state.vim;
3211
+ var commandHistoryRegister = vimGlobalState.registerController.getRegister(':');
3212
+ var previousCommand = commandHistoryRegister.toString();
3062
3213
  if (vim.visualMode) {
3063
3214
  exitVisualMode(cm);
3064
3215
  }
3065
3216
  var inputStream = new CodeMirror.StringStream(input);
3217
+ // update ": with the latest command whether valid or invalid
3218
+ commandHistoryRegister.setText(input);
3066
3219
  var params = {};
3067
3220
  params.input = input;
3068
3221
  try {
3069
3222
  this.parseInput_(cm, inputStream, params);
3070
3223
  } catch(e) {
3071
3224
  showConfirm(cm, e);
3072
- return;
3225
+ throw e;
3073
3226
  }
3074
3227
  var commandName;
3075
3228
  if (!params.commandName) {
@@ -3081,6 +3234,9 @@
3081
3234
  var command = this.matchCommand_(params.commandName);
3082
3235
  if (command) {
3083
3236
  commandName = command.name;
3237
+ if (command.excludeFromCommandHistory) {
3238
+ commandHistoryRegister.setText(previousCommand);
3239
+ }
3084
3240
  this.parseCommandArgs_(inputStream, params, command);
3085
3241
  if (command.type == 'exToKey') {
3086
3242
  // Handle Ex to Key mapping.
@@ -3195,14 +3351,16 @@
3195
3351
  this.commandMap_[commandName] = {
3196
3352
  name: commandName,
3197
3353
  type: 'exToEx',
3198
- toInput: rhs.substring(1)
3354
+ toInput: rhs.substring(1),
3355
+ user: true
3199
3356
  };
3200
3357
  } else {
3201
3358
  // Ex to key mapping
3202
3359
  this.commandMap_[commandName] = {
3203
3360
  name: commandName,
3204
3361
  type: 'exToKey',
3205
- toKeys: parseKeyString(rhs)
3362
+ toKeys: parseKeyString(rhs),
3363
+ user: true
3206
3364
  };
3207
3365
  }
3208
3366
  } else {
@@ -3211,7 +3369,8 @@
3211
3369
  var mapping = {
3212
3370
  keys: parseKeyString(lhs),
3213
3371
  type: 'keyToEx',
3214
- exArgs: { input: rhs.substring(1) }};
3372
+ exArgs: { input: rhs.substring(1) },
3373
+ user: true};
3215
3374
  if (ctx) { mapping.context = ctx; }
3216
3375
  defaultKeymap.unshift(mapping);
3217
3376
  } else {
@@ -3219,12 +3378,45 @@
3219
3378
  var mapping = {
3220
3379
  keys: parseKeyString(lhs),
3221
3380
  type: 'keyToKey',
3222
- toKeys: parseKeyString(rhs)
3381
+ toKeys: parseKeyString(rhs),
3382
+ user: true
3223
3383
  };
3224
3384
  if (ctx) { mapping.context = ctx; }
3225
3385
  defaultKeymap.unshift(mapping);
3226
3386
  }
3227
3387
  }
3388
+ },
3389
+ unmap: function(lhs, ctx) {
3390
+ var arrayEquals = function(a, b) {
3391
+ if (a === b) return true;
3392
+ if (a == null || b == null) return true;
3393
+ if (a.length != b.length) return false;
3394
+ for (var i = 0; i < a.length; i++) {
3395
+ if (a[i] !== b[i]) return false;
3396
+ }
3397
+ return true;
3398
+ };
3399
+ if (lhs != ':' && lhs.charAt(0) == ':') {
3400
+ // Ex to Ex or Ex to key mapping
3401
+ if (ctx) { throw Error('Mode not supported for ex mappings'); }
3402
+ var commandName = lhs.substring(1);
3403
+ if (this.commandMap_[commandName] && this.commandMap_[commandName].user) {
3404
+ delete this.commandMap_[commandName];
3405
+ return;
3406
+ }
3407
+ } else {
3408
+ // Key to Ex or key to key mapping
3409
+ var keys = parseKeyString(lhs);
3410
+ for (var i = 0; i < defaultKeymap.length; i++) {
3411
+ if (arrayEquals(keys, defaultKeymap[i].keys)
3412
+ && defaultKeymap[i].context === ctx
3413
+ && defaultKeymap[i].user) {
3414
+ defaultKeymap.splice(i, 1);
3415
+ return;
3416
+ }
3417
+ }
3418
+ }
3419
+ throw Error('No such mapping.');
3228
3420
  }
3229
3421
  };
3230
3422
 
@@ -3235,7 +3427,7 @@
3235
3427
  var keys = [];
3236
3428
  while (str) {
3237
3429
  match = (/<\w+-.+?>|<\w+>|./).exec(str);
3238
- if(match === null)break;
3430
+ if (match === null)break;
3239
3431
  key = match[0];
3240
3432
  str = str.substring(match.index + key.length);
3241
3433
  keys.push(key);
@@ -3256,6 +3448,16 @@
3256
3448
  },
3257
3449
  nmap: function(cm, params) { this.map(cm, params, 'normal'); },
3258
3450
  vmap: function(cm, params) { this.map(cm, params, 'visual'); },
3451
+ unmap: function(cm, params, ctx) {
3452
+ var mapArgs = params.args;
3453
+ if (!mapArgs || mapArgs.length < 1) {
3454
+ if (cm) {
3455
+ showConfirm(cm, 'No such mapping: ' + params.input);
3456
+ }
3457
+ return;
3458
+ }
3459
+ exCommandDispatcher.unmap(mapArgs[0], ctx);
3460
+ },
3259
3461
  move: function(cm, params) {
3260
3462
  commandDispatcher.processCommand(cm, cm.state.vim, {
3261
3463
  type: 'motion',
@@ -3264,6 +3466,73 @@
3264
3466
  linewise: true },
3265
3467
  repeatOverride: params.line+1});
3266
3468
  },
3469
+ set: function(cm, params) {
3470
+ var setArgs = params.args;
3471
+ if (!setArgs || setArgs.length < 1) {
3472
+ if (cm) {
3473
+ showConfirm(cm, 'Invalid mapping: ' + params.input);
3474
+ }
3475
+ return;
3476
+ }
3477
+ var expr = setArgs[0].split('=');
3478
+ var optionName = expr[0];
3479
+ var value = expr[1];
3480
+ var forceGet = false;
3481
+
3482
+ if (optionName.charAt(optionName.length - 1) == '?') {
3483
+ // If post-fixed with ?, then the set is actually a get.
3484
+ if (value) { throw Error('Trailing characters: ' + params.argString); }
3485
+ optionName = optionName.substring(0, optionName.length - 1);
3486
+ forceGet = true;
3487
+ }
3488
+ if (value === undefined && optionName.substring(0, 2) == 'no') {
3489
+ // To set boolean options to false, the option name is prefixed with
3490
+ // 'no'.
3491
+ optionName = optionName.substring(2);
3492
+ value = false;
3493
+ }
3494
+ var optionIsBoolean = options[optionName] && options[optionName].type == 'boolean';
3495
+ if (optionIsBoolean && value == undefined) {
3496
+ // Calling set with a boolean option sets it to true.
3497
+ value = true;
3498
+ }
3499
+ if (!optionIsBoolean && !value || forceGet) {
3500
+ var oldValue = getOption(optionName);
3501
+ // If no value is provided, then we assume this is a get.
3502
+ if (oldValue === true || oldValue === false) {
3503
+ showConfirm(cm, ' ' + (oldValue ? '' : 'no') + optionName);
3504
+ } else {
3505
+ showConfirm(cm, ' ' + optionName + '=' + oldValue);
3506
+ }
3507
+ } else {
3508
+ setOption(optionName, value);
3509
+ }
3510
+ },
3511
+ registers: function(cm,params) {
3512
+ var regArgs = params.args;
3513
+ var registers = vimGlobalState.registerController.registers;
3514
+ var regInfo = '----------Registers----------<br><br>';
3515
+ if (!regArgs) {
3516
+ for (var registerName in registers) {
3517
+ var text = registers[registerName].toString();
3518
+ if (text.length) {
3519
+ regInfo += '"' + registerName + ' ' + text + '<br>';
3520
+ }
3521
+ }
3522
+ } else {
3523
+ var registerName;
3524
+ regArgs = regArgs.join('');
3525
+ for (var i = 0; i < regArgs.length; i++) {
3526
+ registerName = regArgs.charAt(i);
3527
+ if (!vimGlobalState.registerController.isValidRegister(registerName)) {
3528
+ continue;
3529
+ }
3530
+ var register = registers[registerName] || new Register();
3531
+ regInfo += '"' + registerName + ' ' + register.toString() + '<br>';
3532
+ }
3533
+ }
3534
+ showConfirm(cm, regInfo);
3535
+ },
3267
3536
  sort: function(cm, params) {
3268
3537
  var reverse, ignoreCase, unique, number;
3269
3538
  function parseArgs() {
@@ -3345,34 +3614,41 @@
3345
3614
  'any other getSearchCursor implementation.');
3346
3615
  }
3347
3616
  var argString = params.argString;
3348
- var slashes = findUnescapedSlashes(argString);
3349
- if (slashes[0] !== 0) {
3350
- showConfirm(cm, 'Substitutions should be of the form ' +
3351
- ':s/pattern/replace/');
3352
- return;
3353
- }
3354
- var regexPart = argString.substring(slashes[0] + 1, slashes[1]);
3617
+ var slashes = argString ? findUnescapedSlashes(argString) : [];
3355
3618
  var replacePart = '';
3356
- var flagsPart;
3357
- var count;
3358
- var confirm = false; // Whether to confirm each replace.
3359
- if (slashes[1]) {
3360
- replacePart = argString.substring(slashes[1] + 1, slashes[2]);
3361
- replacePart = fixRegexReplace(replacePart);
3362
- }
3363
- if (slashes[2]) {
3364
- // After the 3rd slash, we can have flags followed by a space followed
3365
- // by count.
3366
- var trailing = argString.substring(slashes[2] + 1).split(' ');
3367
- flagsPart = trailing[0];
3368
- count = parseInt(trailing[1]);
3369
- }
3370
- if (flagsPart) {
3371
- if (flagsPart.indexOf('c') != -1) {
3372
- confirm = true;
3373
- flagsPart.replace('c', '');
3374
- }
3375
- regexPart = regexPart + '/' + flagsPart;
3619
+ if (slashes.length) {
3620
+ if (slashes[0] !== 0) {
3621
+ showConfirm(cm, 'Substitutions should be of the form ' +
3622
+ ':s/pattern/replace/');
3623
+ return;
3624
+ }
3625
+ var regexPart = argString.substring(slashes[0] + 1, slashes[1]);
3626
+ var flagsPart;
3627
+ var count;
3628
+ var confirm = false; // Whether to confirm each replace.
3629
+ if (slashes[1]) {
3630
+ replacePart = argString.substring(slashes[1] + 1, slashes[2]);
3631
+ if (getOption('pcre')) {
3632
+ replacePart = unescapeRegexReplace(replacePart);
3633
+ } else {
3634
+ replacePart = translateRegexReplace(replacePart);
3635
+ }
3636
+ vimGlobalState.lastSubstituteReplacePart = replacePart;
3637
+ }
3638
+ if (slashes[2]) {
3639
+ // After the 3rd slash, we can have flags followed by a space followed
3640
+ // by count.
3641
+ var trailing = argString.substring(slashes[2] + 1).split(' ');
3642
+ flagsPart = trailing[0];
3643
+ count = parseInt(trailing[1]);
3644
+ }
3645
+ if (flagsPart) {
3646
+ if (flagsPart.indexOf('c') != -1) {
3647
+ confirm = true;
3648
+ flagsPart.replace('c', '');
3649
+ }
3650
+ regexPart = regexPart + '/' + flagsPart;
3651
+ }
3376
3652
  }
3377
3653
  if (regexPart) {
3378
3654
  // If regex part is empty, then use the previous query. Otherwise use
@@ -3385,6 +3661,11 @@
3385
3661
  return;
3386
3662
  }
3387
3663
  }
3664
+ replacePart = replacePart || vimGlobalState.lastSubstituteReplacePart;
3665
+ if (replacePart === undefined) {
3666
+ showConfirm(cm, 'No previous substitute regular expression');
3667
+ return;
3668
+ }
3388
3669
  var state = getSearchState(cm);
3389
3670
  var query = state.getQuery();
3390
3671
  var lineStart = (params.line !== undefined) ? params.line : cm.getCursor().line;
@@ -3629,13 +3910,15 @@
3629
3910
 
3630
3911
  function exitInsertMode(cm) {
3631
3912
  var vim = cm.state.vim;
3632
- var inReplay = vimGlobalState.macroModeState.inReplay;
3633
- if (!inReplay) {
3913
+ var macroModeState = vimGlobalState.macroModeState;
3914
+ var insertModeChangeRegister = vimGlobalState.registerController.getRegister('.');
3915
+ var isPlaying = macroModeState.isPlaying;
3916
+ if (!isPlaying) {
3634
3917
  cm.off('change', onChange);
3635
3918
  cm.off('cursorActivity', onCursorActivity);
3636
3919
  CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
3637
3920
  }
3638
- if (!inReplay && vim.insertModeRepeat > 1) {
3921
+ if (!isPlaying && vim.insertModeRepeat > 1) {
3639
3922
  // Perform insert mode repeat for commands like 3,a and 3,o.
3640
3923
  repeatLastEdit(cm, vim, vim.insertModeRepeat - 1,
3641
3924
  true /** repeatForInsert */);
@@ -3647,7 +3930,12 @@
3647
3930
  cm.setOption('keyMap', 'vim');
3648
3931
  cm.setOption('disableInput', true);
3649
3932
  cm.toggleOverwrite(false); // exit replace mode if we were in it.
3933
+ // update the ". register before exiting insert mode
3934
+ insertModeChangeRegister.setText(macroModeState.lastInsertModeChanges.changes.join(''));
3650
3935
  CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
3936
+ if (macroModeState.isRecording) {
3937
+ logInsertModeChange(macroModeState);
3938
+ }
3651
3939
  }
3652
3940
 
3653
3941
  CodeMirror.keyMap['vim-insert'] = {
@@ -3671,45 +3959,57 @@
3671
3959
  fallthrough: ['vim-insert']
3672
3960
  };
3673
3961
 
3674
- function parseRegisterToKeyBuffer(macroModeState, registerName) {
3675
- var match, key;
3962
+ function executeMacroRegister(cm, vim, macroModeState, registerName) {
3676
3963
  var register = vimGlobalState.registerController.getRegister(registerName);
3677
- var text = register.toString();
3678
- var macroKeyBuffer = macroModeState.macroKeyBuffer;
3679
- emptyMacroKeyBuffer(macroModeState);
3680
- do {
3681
- match = (/<\w+-.+?>|<\w+>|./).exec(text);
3682
- if(match === null)break;
3683
- key = match[0];
3684
- text = text.substring(match.index + key.length);
3685
- macroKeyBuffer.push(key);
3686
- } while (text);
3687
- return macroKeyBuffer;
3688
- }
3689
-
3690
- function parseKeyBufferToRegister(registerName, keyBuffer) {
3691
- var text = keyBuffer.join('');
3692
- vimGlobalState.registerController.setRegisterText(registerName, text);
3964
+ var keyBuffer = register.keyBuffer;
3965
+ var imc = 0;
3966
+ macroModeState.isPlaying = true;
3967
+ macroModeState.replaySearchQueries = register.searchQueries.slice(0);
3968
+ for (var i = 0; i < keyBuffer.length; i++) {
3969
+ var text = keyBuffer[i];
3970
+ var match, key;
3971
+ while (text) {
3972
+ // Pull off one command key, which is either a single character
3973
+ // or a special sequence wrapped in '<' and '>', e.g. '<Space>'.
3974
+ match = (/<\w+-.+?>|<\w+>|./).exec(text);
3975
+ key = match[0];
3976
+ text = text.substring(match.index + key.length);
3977
+ CodeMirror.Vim.handleKey(cm, key);
3978
+ if (vim.insertMode) {
3979
+ repeatInsertModeChanges(
3980
+ cm, register.insertModeChanges[imc++].changes, 1);
3981
+ exitInsertMode(cm);
3982
+ }
3983
+ }
3984
+ };
3985
+ macroModeState.isPlaying = false;
3693
3986
  }
3694
3987
 
3695
- function emptyMacroKeyBuffer(macroModeState) {
3696
- if(macroModeState.isMacroPlaying)return;
3697
- var macroKeyBuffer = macroModeState.macroKeyBuffer;
3698
- macroKeyBuffer.length = 0;
3988
+ function logKey(macroModeState, key) {
3989
+ if (macroModeState.isPlaying) { return; }
3990
+ var registerName = macroModeState.latestRegister;
3991
+ var register = vimGlobalState.registerController.getRegister(registerName);
3992
+ if (register) {
3993
+ register.pushText(key);
3994
+ }
3699
3995
  }
3700
3996
 
3701
- function executeMacroKeyBuffer(cm, macroModeState, keyBuffer) {
3702
- macroModeState.isMacroPlaying = true;
3703
- for (var i = 0, len = keyBuffer.length; i < len; i++) {
3704
- CodeMirror.Vim.handleKey(cm, keyBuffer[i]);
3705
- };
3706
- macroModeState.isMacroPlaying = false;
3997
+ function logInsertModeChange(macroModeState) {
3998
+ if (macroModeState.isPlaying) { return; }
3999
+ var registerName = macroModeState.latestRegister;
4000
+ var register = vimGlobalState.registerController.getRegister(registerName);
4001
+ if (register) {
4002
+ register.pushInsertModeChanges(macroModeState.lastInsertModeChanges);
4003
+ }
3707
4004
  }
3708
4005
 
3709
- function logKey(macroModeState, key) {
3710
- if(macroModeState.isMacroPlaying)return;
3711
- var macroKeyBuffer = macroModeState.macroKeyBuffer;
3712
- macroKeyBuffer.push(key);
4006
+ function logSearchQuery(macroModeState, query) {
4007
+ if (macroModeState.isPlaying) { return; }
4008
+ var registerName = macroModeState.latestRegister;
4009
+ var register = vimGlobalState.registerController.getRegister(registerName);
4010
+ if (register) {
4011
+ register.pushSearchQuery(query);
4012
+ }
3713
4013
  }
3714
4014
 
3715
4015
  /**
@@ -3719,15 +4019,17 @@
3719
4019
  function onChange(_cm, changeObj) {
3720
4020
  var macroModeState = vimGlobalState.macroModeState;
3721
4021
  var lastChange = macroModeState.lastInsertModeChanges;
3722
- while (changeObj) {
3723
- lastChange.expectCursorActivityForChange = true;
3724
- if (changeObj.origin == '+input' || changeObj.origin == 'paste'
3725
- || changeObj.origin === undefined /* only in testing */) {
3726
- var text = changeObj.text.join('\n');
3727
- lastChange.changes.push(text);
4022
+ if (!macroModeState.isPlaying) {
4023
+ while(changeObj) {
4024
+ lastChange.expectCursorActivityForChange = true;
4025
+ if (changeObj.origin == '+input' || changeObj.origin == 'paste'
4026
+ || changeObj.origin === undefined /* only in testing */) {
4027
+ var text = changeObj.text.join('\n');
4028
+ lastChange.changes.push(text);
4029
+ }
4030
+ // Change objects may be chained with next.
4031
+ changeObj = changeObj.next;
3728
4032
  }
3729
- // Change objects may be chained with next.
3730
- changeObj = changeObj.next;
3731
4033
  }
3732
4034
  }
3733
4035
 
@@ -3738,6 +4040,7 @@
3738
4040
  */
3739
4041
  function onCursorActivity() {
3740
4042
  var macroModeState = vimGlobalState.macroModeState;
4043
+ if (macroModeState.isPlaying) { return; }
3741
4044
  var lastChange = macroModeState.lastInsertModeChanges;
3742
4045
  if (lastChange.expectCursorActivityForChange) {
3743
4046
  lastChange.expectCursorActivityForChange = false;
@@ -3781,7 +4084,7 @@
3781
4084
  */
3782
4085
  function repeatLastEdit(cm, vim, repeat, repeatForInsert) {
3783
4086
  var macroModeState = vimGlobalState.macroModeState;
3784
- macroModeState.inReplay = true;
4087
+ macroModeState.isPlaying = true;
3785
4088
  var isAction = !!vim.lastEditActionCommand;
3786
4089
  var cachedInputState = vim.inputState;
3787
4090
  function repeatCommand() {
@@ -3793,10 +4096,15 @@
3793
4096
  }
3794
4097
  function repeatInsert(repeat) {
3795
4098
  if (macroModeState.lastInsertModeChanges.changes.length > 0) {
3796
- // For some reason, repeat cw in desktop VIM will does not repeat
4099
+ // For some reason, repeat cw in desktop VIM does not repeat
3797
4100
  // insert mode changes. Will conform to that behavior.
3798
4101
  repeat = !vim.lastEditActionCommand ? 1 : repeat;
3799
- repeatLastInsertModeChanges(cm, repeat, macroModeState);
4102
+ var changeObject = macroModeState.lastInsertModeChanges;
4103
+ // This isn't strictly necessary, but since lastInsertModeChanges is
4104
+ // supposed to be immutable during replay, this helps catch bugs.
4105
+ macroModeState.lastInsertModeChanges = {};
4106
+ repeatInsertModeChanges(cm, changeObject.changes, repeat);
4107
+ macroModeState.lastInsertModeChanges = changeObject;
3800
4108
  }
3801
4109
  }
3802
4110
  vim.inputState = vim.lastEditInputState;
@@ -3822,11 +4130,10 @@
3822
4130
  // were called by an exitInsertMode call lower on the stack.
3823
4131
  exitInsertMode(cm);
3824
4132
  }
3825
- macroModeState.inReplay = false;
4133
+ macroModeState.isPlaying = false;
3826
4134
  };
3827
4135
 
3828
- function repeatLastInsertModeChanges(cm, repeat, macroModeState) {
3829
- var lastChange = macroModeState.lastInsertModeChanges;
4136
+ function repeatInsertModeChanges(cm, changes, repeat) {
3830
4137
  function keyHandler(binding) {
3831
4138
  if (typeof binding == 'string') {
3832
4139
  CodeMirror.commands[binding](cm);
@@ -3836,8 +4143,8 @@
3836
4143
  return true;
3837
4144
  }
3838
4145
  for (var i = 0; i < repeat; i++) {
3839
- for (var j = 0; j < lastChange.changes.length; j++) {
3840
- var change = lastChange.changes[j];
4146
+ for (var j = 0; j < changes.length; j++) {
4147
+ var change = changes[j];
3841
4148
  if (change instanceof InsertModeKey) {
3842
4149
  CodeMirror.lookupKey(change.keyName, ['vim-insert'], keyHandler);
3843
4150
  } else {