codemirror-rails 4.6 → 4.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/codemirror/rails/version.rb +2 -2
  3. data/vendor/assets/javascripts/codemirror.js +24 -25
  4. data/vendor/assets/javascripts/codemirror/addons/comment/comment.js +1 -1
  5. data/vendor/assets/javascripts/codemirror/addons/dialog/dialog.js +4 -1
  6. data/vendor/assets/javascripts/codemirror/addons/edit/closebrackets.js +7 -6
  7. data/vendor/assets/javascripts/codemirror/addons/edit/continuelist.js +21 -8
  8. data/vendor/assets/javascripts/codemirror/addons/hint/sql-hint.js +54 -20
  9. data/vendor/assets/javascripts/codemirror/addons/lint/lint.js +10 -15
  10. data/vendor/assets/javascripts/codemirror/addons/mode/simple.js +210 -0
  11. data/vendor/assets/javascripts/codemirror/addons/runmode/runmode-standalone.js +5 -1
  12. data/vendor/assets/javascripts/codemirror/addons/runmode/runmode.node.js +2 -4
  13. data/vendor/assets/javascripts/codemirror/keymaps/vim.js +425 -554
  14. data/vendor/assets/javascripts/codemirror/modes/clike.js +28 -5
  15. data/vendor/assets/javascripts/codemirror/modes/clojure.js +5 -3
  16. data/vendor/assets/javascripts/codemirror/modes/coffeescript.js +8 -7
  17. data/vendor/assets/javascripts/codemirror/modes/d.js +1 -1
  18. data/vendor/assets/javascripts/codemirror/modes/dtd.js +1 -1
  19. data/vendor/assets/javascripts/codemirror/modes/gherkin.js +1 -1
  20. data/vendor/assets/javascripts/codemirror/modes/go.js +1 -1
  21. data/vendor/assets/javascripts/codemirror/modes/haskell.js +1 -1
  22. data/vendor/assets/javascripts/codemirror/modes/javascript.js +3 -3
  23. data/vendor/assets/javascripts/codemirror/modes/julia.js +1 -1
  24. data/vendor/assets/javascripts/codemirror/modes/markdown.js +4 -3
  25. data/vendor/assets/javascripts/codemirror/modes/octave.js +1 -1
  26. data/vendor/assets/javascripts/codemirror/modes/perl.js +2 -2
  27. data/vendor/assets/javascripts/codemirror/modes/php.js +37 -44
  28. data/vendor/assets/javascripts/codemirror/modes/python.js +17 -4
  29. data/vendor/assets/javascripts/codemirror/modes/ruby.js +12 -11
  30. data/vendor/assets/javascripts/codemirror/modes/rust.js +2 -2
  31. data/vendor/assets/javascripts/codemirror/modes/smartymixed.js +5 -1
  32. data/vendor/assets/javascripts/codemirror/modes/sql.js +2 -1
  33. data/vendor/assets/javascripts/codemirror/modes/stex.js +2 -1
  34. data/vendor/assets/javascripts/codemirror/modes/tcl.js +1 -1
  35. data/vendor/assets/javascripts/codemirror/modes/textile.js +553 -0
  36. data/vendor/assets/javascripts/codemirror/modes/tornado.js +68 -0
  37. data/vendor/assets/javascripts/codemirror/modes/verilog.js +14 -9
  38. data/vendor/assets/stylesheets/codemirror.css +1 -1
  39. data/vendor/assets/stylesheets/codemirror/themes/base16-dark.css +2 -2
  40. data/vendor/assets/stylesheets/codemirror/themes/mdn-like.css +1 -1
  41. metadata +4 -1
@@ -74,7 +74,11 @@ CodeMirror.startState = function (mode, a1, a2) {
74
74
  };
75
75
 
76
76
  var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
77
- CodeMirror.defineMode = function (name, mode) { modes[name] = mode; };
77
+ CodeMirror.defineMode = function (name, mode) {
78
+ if (arguments.length > 2)
79
+ mode.dependencies = Array.prototype.slice.call(arguments, 2);
80
+ modes[name] = mode;
81
+ };
78
82
  CodeMirror.defineMIME = function (mime, spec) { mimeModes[mime] = spec; };
79
83
  CodeMirror.resolveMode = function(spec) {
80
84
  if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) {
@@ -74,10 +74,8 @@ exports.startState = function(mode, a1, a2) {
74
74
 
75
75
  var modes = exports.modes = {}, mimeModes = exports.mimeModes = {};
76
76
  exports.defineMode = function(name, mode) {
77
- if (arguments.length > 2) {
78
- mode.dependencies = [];
79
- for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]);
80
- }
77
+ if (arguments.length > 2)
78
+ mode.dependencies = Array.prototype.slice.call(arguments, 2);
81
79
  modes[name] = mode;
82
80
  };
83
81
  exports.defineMIME = function(mime, spec) { mimeModes[mime] = spec; };
@@ -72,290 +72,205 @@
72
72
  var defaultKeymap = [
73
73
  // Key to key mapping. This goes first to make it possible to override
74
74
  // existing mappings.
75
- { keys: ['<Left>'], type: 'keyToKey', toKeys: ['h'] },
76
- { keys: ['<Right>'], type: 'keyToKey', toKeys: ['l'] },
77
- { keys: ['<Up>'], type: 'keyToKey', toKeys: ['k'] },
78
- { keys: ['<Down>'], type: 'keyToKey', toKeys: ['j'] },
79
- { keys: ['<Space>'], type: 'keyToKey', toKeys: ['l'] },
80
- { keys: ['<BS>'], type: 'keyToKey', toKeys: ['h'] },
81
- { keys: ['<C-Space>'], type: 'keyToKey', toKeys: ['W'] },
82
- { keys: ['<C-BS>'], type: 'keyToKey', toKeys: ['B'] },
83
- { keys: ['<S-Space>'], type: 'keyToKey', toKeys: ['w'] },
84
- { keys: ['<S-BS>'], type: 'keyToKey', toKeys: ['b'] },
85
- { keys: ['<C-n>'], type: 'keyToKey', toKeys: ['j'] },
86
- { keys: ['<C-p>'], type: 'keyToKey', toKeys: ['k'] },
87
- { keys: ['<C-[>'], type: 'keyToKey', toKeys: ['<Esc>'] },
88
- { keys: ['<C-c>'], type: 'keyToKey', toKeys: ['<Esc>'] },
89
- { keys: ['s'], type: 'keyToKey', toKeys: ['c', 'l'], context: 'normal' },
90
- { keys: ['s'], type: 'keyToKey', toKeys: ['x', 'i'], context: 'visual'},
91
- { keys: ['S'], type: 'keyToKey', toKeys: ['c', 'c'], context: 'normal' },
92
- { keys: ['S'], type: 'keyToKey', toKeys: ['d', 'c', 'c'], context: 'visual' },
93
- { keys: ['<Home>'], type: 'keyToKey', toKeys: ['0'] },
94
- { keys: ['<End>'], type: 'keyToKey', toKeys: ['$'] },
95
- { keys: ['<PageUp>'], type: 'keyToKey', toKeys: ['<C-b>'] },
96
- { keys: ['<PageDown>'], type: 'keyToKey', toKeys: ['<C-f>'] },
97
- { keys: ['<CR>'], type: 'keyToKey', toKeys: ['j', '^'], context: 'normal' },
75
+ { keys: '<Left>', type: 'keyToKey', toKeys: 'h' },
76
+ { keys: '<Right>', type: 'keyToKey', toKeys: 'l' },
77
+ { keys: '<Up>', type: 'keyToKey', toKeys: 'k' },
78
+ { keys: '<Down>', type: 'keyToKey', toKeys: 'j' },
79
+ { keys: '<Space>', type: 'keyToKey', toKeys: 'l' },
80
+ { keys: '<BS>', type: 'keyToKey', toKeys: 'h' },
81
+ { keys: '<C-Space>', type: 'keyToKey', toKeys: 'W' },
82
+ { keys: '<C-BS>', type: 'keyToKey', toKeys: 'B' },
83
+ { keys: '<S-Space>', type: 'keyToKey', toKeys: 'w' },
84
+ { keys: '<S-BS>', type: 'keyToKey', toKeys: 'b' },
85
+ { keys: '<C-n>', type: 'keyToKey', toKeys: 'j' },
86
+ { keys: '<C-p>', type: 'keyToKey', toKeys: 'k' },
87
+ { keys: '<C-[>', type: 'keyToKey', toKeys: '<Esc>' },
88
+ { keys: '<C-c>', type: 'keyToKey', toKeys: '<Esc>' },
89
+ { keys: '<C-[>', type: 'keyToKey', toKeys: '<Esc>', context: 'insert' },
90
+ { keys: '<C-c>', type: 'keyToKey', toKeys: '<Esc>', context: 'insert' },
91
+ { keys: 's', type: 'keyToKey', toKeys: 'cl', context: 'normal' },
92
+ { keys: 's', type: 'keyToKey', toKeys: 'xi', context: 'visual'},
93
+ { keys: 'S', type: 'keyToKey', toKeys: 'cc', context: 'normal' },
94
+ { keys: 'S', type: 'keyToKey', toKeys: 'dcc', context: 'visual' },
95
+ { keys: '<Home>', type: 'keyToKey', toKeys: '0' },
96
+ { keys: '<End>', type: 'keyToKey', toKeys: '$' },
97
+ { keys: '<PageUp>', type: 'keyToKey', toKeys: '<C-b>' },
98
+ { keys: '<PageDown>', type: 'keyToKey', toKeys: '<C-f>' },
99
+ { keys: '<CR>', type: 'keyToKey', toKeys: 'j^', context: 'normal' },
98
100
  // Motions
99
- { keys: ['H'], type: 'motion',
100
- motion: 'moveToTopLine',
101
- motionArgs: { linewise: true, toJumplist: true }},
102
- { keys: ['M'], type: 'motion',
103
- motion: 'moveToMiddleLine',
104
- motionArgs: { linewise: true, toJumplist: true }},
105
- { keys: ['L'], type: 'motion',
106
- motion: 'moveToBottomLine',
107
- motionArgs: { linewise: true, toJumplist: true }},
108
- { keys: ['h'], type: 'motion',
109
- motion: 'moveByCharacters',
110
- motionArgs: { forward: false }},
111
- { keys: ['l'], type: 'motion',
112
- motion: 'moveByCharacters',
113
- motionArgs: { forward: true }},
114
- { keys: ['j'], type: 'motion',
115
- motion: 'moveByLines',
116
- motionArgs: { forward: true, linewise: true }},
117
- { keys: ['k'], type: 'motion',
118
- motion: 'moveByLines',
119
- motionArgs: { forward: false, linewise: true }},
120
- { keys: ['g','j'], type: 'motion',
121
- motion: 'moveByDisplayLines',
122
- motionArgs: { forward: true }},
123
- { keys: ['g','k'], type: 'motion',
124
- motion: 'moveByDisplayLines',
125
- motionArgs: { forward: false }},
126
- { keys: ['w'], type: 'motion',
127
- motion: 'moveByWords',
128
- motionArgs: { forward: true, wordEnd: false }},
129
- { keys: ['W'], type: 'motion',
130
- motion: 'moveByWords',
131
- motionArgs: { forward: true, wordEnd: false, bigWord: true }},
132
- { keys: ['e'], type: 'motion',
133
- motion: 'moveByWords',
134
- motionArgs: { forward: true, wordEnd: true, inclusive: true }},
135
- { keys: ['E'], type: 'motion',
136
- motion: 'moveByWords',
137
- motionArgs: { forward: true, wordEnd: true, bigWord: true,
138
- inclusive: true }},
139
- { keys: ['b'], type: 'motion',
140
- motion: 'moveByWords',
141
- motionArgs: { forward: false, wordEnd: false }},
142
- { keys: ['B'], type: 'motion',
143
- motion: 'moveByWords',
144
- motionArgs: { forward: false, wordEnd: false, bigWord: true }},
145
- { keys: ['g', 'e'], type: 'motion',
146
- motion: 'moveByWords',
147
- motionArgs: { forward: false, wordEnd: true, inclusive: true }},
148
- { keys: ['g', 'E'], type: 'motion',
149
- motion: 'moveByWords',
150
- motionArgs: { forward: false, wordEnd: true, bigWord: true,
151
- inclusive: true }},
152
- { keys: ['{'], type: 'motion', motion: 'moveByParagraph',
153
- motionArgs: { forward: false, toJumplist: true }},
154
- { keys: ['}'], type: 'motion', motion: 'moveByParagraph',
155
- motionArgs: { forward: true, toJumplist: true }},
156
- { keys: ['<C-f>'], type: 'motion',
157
- motion: 'moveByPage', motionArgs: { forward: true }},
158
- { keys: ['<C-b>'], type: 'motion',
159
- motion: 'moveByPage', motionArgs: { forward: false }},
160
- { keys: ['<C-d>'], type: 'motion',
161
- motion: 'moveByScroll',
162
- motionArgs: { forward: true, explicitRepeat: true }},
163
- { keys: ['<C-u>'], type: 'motion',
164
- motion: 'moveByScroll',
165
- motionArgs: { forward: false, explicitRepeat: true }},
166
- { keys: ['g', 'g'], type: 'motion',
167
- motion: 'moveToLineOrEdgeOfDocument',
168
- motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }},
169
- { keys: ['G'], type: 'motion',
170
- motion: 'moveToLineOrEdgeOfDocument',
171
- motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }},
172
- { keys: ['0'], type: 'motion', motion: 'moveToStartOfLine' },
173
- { keys: ['^'], type: 'motion',
174
- motion: 'moveToFirstNonWhiteSpaceCharacter' },
175
- { keys: ['+'], type: 'motion',
176
- motion: 'moveByLines',
177
- motionArgs: { forward: true, toFirstChar:true }},
178
- { keys: ['-'], type: 'motion',
179
- motion: 'moveByLines',
180
- motionArgs: { forward: false, toFirstChar:true }},
181
- { keys: ['_'], type: 'motion',
182
- motion: 'moveByLines',
183
- motionArgs: { forward: true, toFirstChar:true, repeatOffset:-1 }},
184
- { keys: ['$'], type: 'motion',
185
- motion: 'moveToEol',
186
- motionArgs: { inclusive: true }},
187
- { keys: ['%'], type: 'motion',
188
- motion: 'moveToMatchedSymbol',
189
- motionArgs: { inclusive: true, toJumplist: true }},
190
- { keys: ['f', 'character'], type: 'motion',
191
- motion: 'moveToCharacter',
192
- motionArgs: { forward: true , inclusive: true }},
193
- { keys: ['F', 'character'], type: 'motion',
194
- motion: 'moveToCharacter',
195
- motionArgs: { forward: false }},
196
- { keys: ['t', 'character'], type: 'motion',
197
- motion: 'moveTillCharacter',
198
- motionArgs: { forward: true, inclusive: true }},
199
- { keys: ['T', 'character'], type: 'motion',
200
- motion: 'moveTillCharacter',
201
- motionArgs: { forward: false }},
202
- { keys: [';'], type: 'motion', motion: 'repeatLastCharacterSearch',
203
- motionArgs: { forward: true }},
204
- { keys: [','], type: 'motion', motion: 'repeatLastCharacterSearch',
205
- motionArgs: { forward: false }},
206
- { keys: ['\'', 'character'], type: 'motion', motion: 'goToMark',
207
- motionArgs: {toJumplist: true, linewise: true}},
208
- { keys: ['`', 'character'], type: 'motion', motion: 'goToMark',
209
- motionArgs: {toJumplist: true}},
210
- { keys: [']', '`'], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } },
211
- { keys: ['[', '`'], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } },
212
- { keys: [']', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } },
213
- { keys: ['[', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } },
101
+ { keys: 'H', type: 'motion', motion: 'moveToTopLine', motionArgs: { linewise: true, toJumplist: true }},
102
+ { keys: 'M', type: 'motion', motion: 'moveToMiddleLine', motionArgs: { linewise: true, toJumplist: true }},
103
+ { keys: 'L', type: 'motion', motion: 'moveToBottomLine', motionArgs: { linewise: true, toJumplist: true }},
104
+ { keys: 'h', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: false }},
105
+ { keys: 'l', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: true }},
106
+ { keys: 'j', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, linewise: true }},
107
+ { keys: 'k', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, linewise: true }},
108
+ { keys: 'gj', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: true }},
109
+ { keys: 'gk', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: false }},
110
+ { keys: 'w', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false }},
111
+ { keys: 'W', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false, bigWord: true }},
112
+ { keys: 'e', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, inclusive: true }},
113
+ { keys: 'E', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, bigWord: true, inclusive: true }},
114
+ { keys: 'b', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }},
115
+ { keys: 'B', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false, bigWord: true }},
116
+ { keys: 'ge', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, inclusive: true }},
117
+ { keys: 'gE', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, bigWord: true, inclusive: true }},
118
+ { keys: '{', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: false, toJumplist: true }},
119
+ { keys: '}', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: true, toJumplist: true }},
120
+ { keys: '<C-f>', type: 'motion', motion: 'moveByPage', motionArgs: { forward: true }},
121
+ { keys: '<C-b>', type: 'motion', motion: 'moveByPage', motionArgs: { forward: false }},
122
+ { keys: '<C-d>', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: true, explicitRepeat: true }},
123
+ { keys: '<C-u>', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: false, explicitRepeat: true }},
124
+ { keys: 'gg', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }},
125
+ { keys: 'G', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }},
126
+ { keys: '0', type: 'motion', motion: 'moveToStartOfLine' },
127
+ { keys: '^', type: 'motion', motion: 'moveToFirstNonWhiteSpaceCharacter' },
128
+ { keys: '+', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true }},
129
+ { keys: '-', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, toFirstChar:true }},
130
+ { keys: '_', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true, repeatOffset:-1 }},
131
+ { keys: '$', type: 'motion', motion: 'moveToEol', motionArgs: { inclusive: true }},
132
+ { keys: '%', type: 'motion', motion: 'moveToMatchedSymbol', motionArgs: { inclusive: true, toJumplist: true }},
133
+ { keys: 'f<character>', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: true , inclusive: true }},
134
+ { keys: 'F<character>', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: false }},
135
+ { keys: 't<character>', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: true, inclusive: true }},
136
+ { keys: 'T<character>', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: false }},
137
+ { keys: ';', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: true }},
138
+ { keys: ',', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: false }},
139
+ { keys: '\'<character>', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true, linewise: true}},
140
+ { keys: '`<character>', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true}},
141
+ { keys: ']`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } },
142
+ { keys: '[`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } },
143
+ { keys: ']\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } },
144
+ { keys: '[\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } },
214
145
  // the next two aren't motions but must come before more general motion declarations
215
- { keys: [']', 'p'], type: 'action', action: 'paste', isEdit: true,
216
- actionArgs: { after: true, isEdit: true, matchIndent: true}},
217
- { keys: ['[', 'p'], type: 'action', action: 'paste', isEdit: true,
218
- actionArgs: { after: false, isEdit: true, matchIndent: true}},
219
- { keys: [']', 'character'], type: 'motion',
220
- motion: 'moveToSymbol',
221
- motionArgs: { forward: true, toJumplist: true}},
222
- { keys: ['[', 'character'], type: 'motion',
223
- motion: 'moveToSymbol',
224
- motionArgs: { forward: false, toJumplist: true}},
225
- { keys: ['|'], type: 'motion',
226
- motion: 'moveToColumn',
227
- motionArgs: { }},
228
- { keys: ['o'], type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: { }, context:'visual'},
229
- { keys: ['O'], type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: {sameLine: true}, context:'visual'},
146
+ { keys: ']p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true, matchIndent: true}},
147
+ { keys: '[p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true, matchIndent: true}},
148
+ { keys: ']<character>', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: true, toJumplist: true}},
149
+ { keys: '[<character>', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: false, toJumplist: true}},
150
+ { keys: '|', type: 'motion', motion: 'moveToColumn'},
151
+ { keys: 'o', type: 'motion', motion: 'moveToOtherHighlightedEnd', context:'visual'},
152
+ { keys: 'O', type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: {sameLine: true}, context:'visual'},
230
153
  // Operators
231
- { keys: ['d'], type: 'operator', operator: 'delete' },
232
- { keys: ['y'], type: 'operator', operator: 'yank' },
233
- { keys: ['c'], type: 'operator', operator: 'change' },
234
- { keys: ['>'], type: 'operator', operator: 'indent',
235
- operatorArgs: { indentRight: true }},
236
- { keys: ['<'], type: 'operator', operator: 'indent',
237
- operatorArgs: { indentRight: false }},
238
- { keys: ['g', '~'], type: 'operator', operator: 'swapcase' },
239
- { keys: ['n'], type: 'motion', motion: 'findNext',
240
- motionArgs: { forward: true, toJumplist: true }},
241
- { keys: ['N'], type: 'motion', motion: 'findNext',
242
- motionArgs: { forward: false, toJumplist: true }},
154
+ { keys: 'd', type: 'operator', operator: 'delete' },
155
+ { keys: 'y', type: 'operator', operator: 'yank' },
156
+ { keys: 'c', type: 'operator', operator: 'change' },
157
+ { keys: '>', type: 'operator', operator: 'indent', operatorArgs: { indentRight: true }},
158
+ { keys: '<', type: 'operator', operator: 'indent', operatorArgs: { indentRight: false }},
159
+ { keys: 'g~', type: 'operator', operator: 'swapcase' },
160
+ { keys: 'n', type: 'motion', motion: 'findNext', motionArgs: { forward: true, toJumplist: true }},
161
+ { keys: 'N', type: 'motion', motion: 'findNext', motionArgs: { forward: false, toJumplist: true }},
243
162
  // Operator-Motion dual commands
244
- { keys: ['x'], type: 'operatorMotion', operator: 'delete',
245
- motion: 'moveByCharacters', motionArgs: { forward: true },
246
- operatorMotionArgs: { visualLine: false }},
247
- { keys: ['X'], type: 'operatorMotion', operator: 'delete',
248
- motion: 'moveByCharacters', motionArgs: { forward: false },
249
- operatorMotionArgs: { visualLine: true }},
250
- { keys: ['D'], type: 'operatorMotion', operator: 'delete',
251
- motion: 'moveToEol', motionArgs: { inclusive: true },
252
- operatorMotionArgs: { visualLine: true }},
253
- { keys: ['Y'], type: 'operatorMotion', operator: 'yank',
254
- motion: 'moveToEol', motionArgs: { inclusive: true },
255
- operatorMotionArgs: { visualLine: true }},
256
- { keys: ['C'], type: 'operatorMotion',
257
- operator: 'change',
258
- motion: 'moveToEol', motionArgs: { inclusive: true },
259
- operatorMotionArgs: { visualLine: true }},
260
- { keys: ['~'], type: 'operatorMotion',
261
- operator: 'swapcase', operatorArgs: { shouldMoveCursor: true },
262
- motion: 'moveByCharacters', motionArgs: { forward: true }},
163
+ { keys: 'x', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorMotionArgs: { visualLine: false }},
164
+ { keys: 'X', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: false }, operatorMotionArgs: { visualLine: true }},
165
+ { keys: 'D', type: 'operatorMotion', operator: 'delete', motion: 'moveToEol', motionArgs: { inclusive: true }, operatorMotionArgs: { visualLine: true }},
166
+ { keys: 'Y', type: 'operatorMotion', operator: 'yank', motion: 'moveToEol', motionArgs: { inclusive: true }, operatorMotionArgs: { visualLine: true }},
167
+ { keys: 'C', type: 'operatorMotion', operator: 'change', motion: 'moveToEol', motionArgs: { inclusive: true }, operatorMotionArgs: { visualLine: true }},
168
+ { keys: '~', type: 'operatorMotion', operator: 'swapcase', operatorArgs: { shouldMoveCursor: true }, motion: 'moveByCharacters', motionArgs: { forward: true }},
169
+ { keys: '<C-w>', type: 'operatorMotion', operator: 'delete', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }, context: 'insert' },
263
170
  // Actions
264
- { keys: ['<C-i>'], type: 'action', action: 'jumpListWalk',
265
- actionArgs: { forward: true }},
266
- { keys: ['<C-o>'], type: 'action', action: 'jumpListWalk',
267
- actionArgs: { forward: false }},
268
- { keys: ['<C-e>'], type: 'action',
269
- action: 'scroll',
270
- actionArgs: { forward: true, linewise: true }},
271
- { keys: ['<C-y>'], type: 'action',
272
- action: 'scroll',
273
- actionArgs: { forward: false, linewise: true }},
274
- { keys: ['a'], type: 'action', action: 'enterInsertMode', isEdit: true,
275
- actionArgs: { insertAt: 'charAfter' }},
276
- { keys: ['A'], type: 'action', action: 'enterInsertMode', isEdit: true,
277
- actionArgs: { insertAt: 'eol' }},
278
- { keys: ['A'], type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'endOfSelectedArea' }, context: 'visual' },
279
- { keys: ['i'], type: 'action', action: 'enterInsertMode', isEdit: true,
280
- actionArgs: { insertAt: 'inplace' }},
281
- { keys: ['I'], type: 'action', action: 'enterInsertMode', isEdit: true,
282
- actionArgs: { insertAt: 'firstNonBlank' }},
283
- { keys: ['o'], type: 'action', action: 'newLineAndEnterInsertMode',
284
- isEdit: true, interlaceInsertRepeat: true,
285
- actionArgs: { after: true }},
286
- { keys: ['O'], type: 'action', action: 'newLineAndEnterInsertMode',
287
- isEdit: true, interlaceInsertRepeat: true,
288
- actionArgs: { after: false }},
289
- { keys: ['v'], type: 'action', action: 'toggleVisualMode' },
290
- { keys: ['V'], type: 'action', action: 'toggleVisualMode',
291
- actionArgs: { linewise: true }},
292
- { keys: ['<C-v>'], type: 'action', action: 'toggleVisualMode',
293
- actionArgs: { blockwise: true }},
294
- { keys: ['g', 'v'], type: 'action', action: 'reselectLastSelection' },
295
- { keys: ['J'], type: 'action', action: 'joinLines', isEdit: true },
296
- { keys: ['p'], type: 'action', action: 'paste', isEdit: true,
297
- actionArgs: { after: true, isEdit: true }},
298
- { keys: ['P'], type: 'action', action: 'paste', isEdit: true,
299
- actionArgs: { after: false, isEdit: true }},
300
- { keys: ['r', 'character'], type: 'action', action: 'replace', isEdit: true },
301
- { keys: ['@', 'character'], type: 'action', action: 'replayMacro' },
302
- { keys: ['q', 'character'], type: 'action', action: 'enterMacroRecordMode' },
171
+ { keys: '<C-i>', type: 'action', action: 'jumpListWalk', actionArgs: { forward: true }},
172
+ { keys: '<C-o>', type: 'action', action: 'jumpListWalk', actionArgs: { forward: false }},
173
+ { keys: '<C-e>', type: 'action', action: 'scroll', actionArgs: { forward: true, linewise: true }},
174
+ { keys: '<C-y>', type: 'action', action: 'scroll', actionArgs: { forward: false, linewise: true }},
175
+ { keys: 'a', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'charAfter' }, context: 'normal' },
176
+ { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'eol' }, context: 'normal' },
177
+ { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'endOfSelectedArea' }, context: 'visual' },
178
+ { keys: 'i', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'inplace' }, context: 'normal' },
179
+ { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'firstNonBlank' }},
180
+ { keys: 'o', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: true }, context: 'normal' },
181
+ { keys: 'O', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: false }, context: 'normal' },
182
+ { keys: 'v', type: 'action', action: 'toggleVisualMode' },
183
+ { keys: 'V', type: 'action', action: 'toggleVisualMode', actionArgs: { linewise: true }},
184
+ { keys: '<C-v>', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }},
185
+ { keys: 'gv', type: 'action', action: 'reselectLastSelection' },
186
+ { keys: 'J', type: 'action', action: 'joinLines', isEdit: true },
187
+ { keys: 'p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true }},
188
+ { keys: 'P', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true }},
189
+ { keys: 'r<character>', type: 'action', action: 'replace', isEdit: true },
190
+ { keys: '@<character>', type: 'action', action: 'replayMacro' },
191
+ { keys: 'q<character>', type: 'action', action: 'enterMacroRecordMode' },
303
192
  // Handle Replace-mode as a special case of insert mode.
304
- { keys: ['R'], type: 'action', action: 'enterInsertMode', isEdit: true,
305
- actionArgs: { replace: true }},
306
- { keys: ['u'], type: 'action', action: 'undo' },
307
- { keys: ['u'], type: 'action', action: 'changeCase', actionArgs: {toLower: true}, context: 'visual', isEdit: true },
308
- { keys: ['U'],type: 'action', action: 'changeCase', actionArgs: {toLower: false}, context: 'visual', isEdit: true },
309
- { keys: ['<C-r>'], type: 'action', action: 'redo' },
310
- { keys: ['m', 'character'], type: 'action', action: 'setMark' },
311
- { keys: ['"', 'character'], type: 'action', action: 'setRegister' },
312
- { keys: ['z', 'z'], type: 'action', action: 'scrollToCursor',
313
- actionArgs: { position: 'center' }},
314
- { keys: ['z', '.'], type: 'action', action: 'scrollToCursor',
315
- actionArgs: { position: 'center' },
316
- motion: 'moveToFirstNonWhiteSpaceCharacter' },
317
- { keys: ['z', 't'], type: 'action', action: 'scrollToCursor',
318
- actionArgs: { position: 'top' }},
319
- { keys: ['z', '<CR>'], type: 'action', action: 'scrollToCursor',
320
- actionArgs: { position: 'top' },
321
- motion: 'moveToFirstNonWhiteSpaceCharacter' },
322
- { keys: ['z', '-'], type: 'action', action: 'scrollToCursor',
323
- actionArgs: { position: 'bottom' }},
324
- { keys: ['z', 'b'], type: 'action', action: 'scrollToCursor',
325
- actionArgs: { position: 'bottom' },
326
- motion: 'moveToFirstNonWhiteSpaceCharacter' },
327
- { keys: ['.'], type: 'action', action: 'repeatLastEdit' },
328
- { keys: ['<C-a>'], type: 'action', action: 'incrementNumberToken',
329
- isEdit: true,
330
- actionArgs: {increase: true, backtrack: false}},
331
- { keys: ['<C-x>'], type: 'action', action: 'incrementNumberToken',
332
- isEdit: true,
333
- actionArgs: {increase: false, backtrack: false}},
193
+ { keys: 'R', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { replace: true }},
194
+ { keys: 'u', type: 'action', action: 'undo', context: 'normal' },
195
+ { keys: 'u', type: 'action', action: 'changeCase', actionArgs: {toLower: true}, context: 'visual', isEdit: true },
196
+ { keys: 'U',type: 'action', action: 'changeCase', actionArgs: {toLower: false}, context: 'visual', isEdit: true },
197
+ { keys: '<C-r>', type: 'action', action: 'redo' },
198
+ { keys: 'm<character>', type: 'action', action: 'setMark' },
199
+ { keys: '"<character>', type: 'action', action: 'setRegister' },
200
+ { keys: 'zz', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }},
201
+ { keys: 'z.', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },
202
+ { keys: 'zt', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }},
203
+ { keys: 'z<CR>', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },
204
+ { keys: 'z-', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }},
205
+ { keys: 'zb', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }, motion: 'moveToFirstNonWhiteSpaceCharacter' },
206
+ { keys: '.', type: 'action', action: 'repeatLastEdit' },
207
+ { keys: '<C-a>', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: true, backtrack: false}},
208
+ { keys: '<C-x>', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: false, backtrack: false}},
334
209
  // Text object motions
335
- { keys: ['a', 'character'], type: 'motion',
336
- motion: 'textObjectManipulation' },
337
- { keys: ['i', 'character'], type: 'motion',
338
- motion: 'textObjectManipulation',
339
- motionArgs: { textObjectInner: true }},
210
+ { keys: 'a<character>', type: 'motion', motion: 'textObjectManipulation' },
211
+ { keys: 'i<character>', type: 'motion', motion: 'textObjectManipulation', motionArgs: { textObjectInner: true }},
340
212
  // Search
341
- { keys: ['/'], type: 'search',
342
- searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }},
343
- { keys: ['?'], type: 'search',
344
- searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }},
345
- { keys: ['*'], type: 'search',
346
- searchArgs: { forward: true, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }},
347
- { keys: ['#'], type: 'search',
348
- searchArgs: { forward: false, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }},
349
- { keys: ['g', '*'], type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }},
350
- { keys: ['g', '#'], type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }},
213
+ { keys: '/', type: 'search', searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }},
214
+ { keys: '?', type: 'search', searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }},
215
+ { keys: '*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }},
216
+ { keys: '#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }},
217
+ { keys: 'g*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }},
218
+ { keys: 'g#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }},
351
219
  // Ex command
352
- { keys: [':'], type: 'ex' }
220
+ { keys: ':', type: 'ex' }
353
221
  ];
354
222
 
355
223
  var Pos = CodeMirror.Pos;
356
224
 
225
+ var modifierCodes = [16, 17, 18, 91];
226
+ var specialKey = {Enter:'CR',Backspace:'BS',Delete:'Del'};
227
+ var mac = /Mac/.test(navigator.platform);
357
228
  var Vim = function() {
358
229
  CodeMirror.defineOption('vimMode', false, function(cm, val) {
230
+ function lookupKey(e) {
231
+ var keyCode = e.keyCode;
232
+ if (modifierCodes.indexOf(keyCode) != -1) { return; }
233
+ var hasModifier = e.ctrlKey || e.metaKey;
234
+ var key = CodeMirror.keyNames[keyCode];
235
+ key = specialKey[key] || key;
236
+ var name = '';
237
+ if (e.ctrlKey) { name += 'C-'; }
238
+ if (e.altKey) { name += 'A-'; }
239
+ if (mac && e.metaKey || (!hasModifier && e.shiftKey) && key.length < 2) {
240
+ // Shift key bindings can only specified for special characters.
241
+ return;
242
+ } else if (e.shiftKey && !/^[A-Za-z]$/.test(key)) {
243
+ name += 'S-';
244
+ }
245
+ if (key.length == 1) { key = key.toLowerCase(); }
246
+ name += key;
247
+ if (name.length > 1) { name = '<' + name + '>'; }
248
+ return name;
249
+ }
250
+ // Keys with modifiers are handled using keydown due to limitations of
251
+ // keypress event.
252
+ function handleKeyDown(cm, e) {
253
+ var name = lookupKey(e);
254
+ if (!name) { return; }
255
+
256
+ CodeMirror.signal(cm, 'vim-keypress', name);
257
+ if (CodeMirror.Vim.handleKey(cm, name, 'user')) {
258
+ CodeMirror.e_stop(e);
259
+ }
260
+ }
261
+ // Keys without modifiers are handled using keypress to work best with
262
+ // non-standard keyboard layouts.
263
+ function handleKeyPress(cm, e) {
264
+ var code = e.charCode || e.keyCode;
265
+ if (e.ctrlKey || e.metaKey || e.altKey ||
266
+ e.shiftKey && code < 32) { return; }
267
+ var name = String.fromCharCode(code);
268
+
269
+ CodeMirror.signal(cm, 'vim-keypress', name);
270
+ if (CodeMirror.Vim.handleKey(cm, name, 'user')) {
271
+ CodeMirror.e_stop(e);
272
+ }
273
+ }
359
274
  if (val) {
360
275
  cm.setOption('keyMap', 'vim');
361
276
  cm.setOption('disableInput', true);
@@ -364,12 +279,16 @@
364
279
  cm.on('cursorActivity', onCursorActivity);
365
280
  maybeInitVimState(cm);
366
281
  CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm));
282
+ cm.on('keypress', handleKeyPress);
283
+ cm.on('keydown', handleKeyDown);
367
284
  } else if (cm.state.vim) {
368
285
  cm.setOption('keyMap', 'default');
369
286
  cm.setOption('disableInput', false);
370
287
  cm.off('cursorActivity', onCursorActivity);
371
288
  CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm));
372
289
  cm.state.vim = null;
290
+ cm.off('keypress', handleKeyPress);
291
+ cm.off('keydown', handleKeyDown);
373
292
  }
374
293
  });
375
294
  function getOnPasteFn(cm) {
@@ -397,9 +316,6 @@
397
316
  var upperCaseAlphabet = makeKeyRange(65, 26);
398
317
  var lowerCaseAlphabet = makeKeyRange(97, 26);
399
318
  var numbers = makeKeyRange(48, 10);
400
- var specialSymbols = '~`!@#$%^&*()_-+=[{}]\\|/?.,<>:;"\''.split('');
401
- var specialKeys = ['Left', 'Right', 'Up', 'Down', 'Space', 'Backspace',
402
- 'Esc', 'Home', 'End', 'PageUp', 'PageDown', 'Enter'];
403
319
  var validMarks = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['<', '>']);
404
320
  var validRegisters = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['-', '"', '.', ':', '/']);
405
321
 
@@ -648,6 +564,7 @@
648
564
  }
649
565
  }
650
566
 
567
+ var lastInsertModeKeyTimer;
651
568
  var vimApi= {
652
569
  buildKeyMap: function() {
653
570
  // TODO: Convert keymap into dictionary format for fast lookup.
@@ -685,58 +602,111 @@
685
602
  },
686
603
  // This is the outermost function called by CodeMirror, after keys have
687
604
  // been mapped to their Vim equivalents.
688
- handleKey: function(cm, key) {
689
- var command;
605
+ handleKey: function(cm, key, origin) {
690
606
  var vim = maybeInitVimState(cm);
691
- var macroModeState = vimGlobalState.macroModeState;
692
- if (macroModeState.isRecording) {
693
- if (key == 'q') {
694
- macroModeState.exitMacroRecordMode();
695
- clearInputState(cm);
696
- return;
607
+ function handleMacroRecording() {
608
+ var macroModeState = vimGlobalState.macroModeState;
609
+ if (macroModeState.isRecording) {
610
+ if (key == 'q') {
611
+ macroModeState.exitMacroRecordMode();
612
+ clearInputState(cm);
613
+ return true;
614
+ }
615
+ if (origin != 'mapping') {
616
+ logKey(macroModeState, key);
617
+ }
697
618
  }
698
619
  }
699
- if (key == '<Esc>') {
700
- // Clear input state and get back to normal mode.
701
- clearInputState(cm);
702
- if (vim.visualMode) {
703
- exitVisualMode(cm);
620
+ function handleEsc() {
621
+ if (key == '<Esc>') {
622
+ // Clear input state and get back to normal mode.
623
+ clearInputState(cm);
624
+ if (vim.visualMode) {
625
+ exitVisualMode(cm);
626
+ } else if (vim.insertMode) {
627
+ exitInsertMode(cm);
628
+ }
629
+ return true;
704
630
  }
705
- return;
706
631
  }
707
- // Enter visual mode when the mouse selects text.
708
- if (!vim.visualMode &&
709
- !cursorEqual(cm.getCursor('head'), cm.getCursor('anchor'))) {
710
- vim.visualMode = true;
711
- vim.visualLine = false;
712
- CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"});
713
- cm.on('mousedown', exitVisualMode);
714
- }
715
- if (key != '0' || (key == '0' && vim.inputState.getRepeat() === 0)) {
716
- // Have to special case 0 since it's both a motion and a number.
717
- command = commandDispatcher.matchCommand(key, defaultKeymap, vim);
632
+ function doKeyToKey(keys) {
633
+ // TODO: prevent infinite recursion.
634
+ var match;
635
+ while (keys) {
636
+ // Pull off one command key, which is either a single character
637
+ // or a special sequence wrapped in '<' and '>', e.g. '<Space>'.
638
+ match = (/<\w+-.+?>|<\w+>|./).exec(keys);
639
+ key = match[0];
640
+ keys = keys.substring(match.index + key.length);
641
+ CodeMirror.Vim.handleKey(cm, key, 'mapping');
642
+ }
718
643
  }
719
- if (!command) {
720
- if (isNumber(key)) {
721
- // Increment count unless count is 0 and key is 0.
722
- vim.inputState.pushRepeatDigit(key);
644
+
645
+ function handleKeyInsertMode() {
646
+ if (handleEsc()) { return true; }
647
+ var keys = vim.inputState.keyBuffer = vim.inputState.keyBuffer + key;
648
+ var keysAreChars = key.length == 1;
649
+ var match = commandDispatcher.matchCommand(keys, defaultKeymap, vim.inputState, 'insert');
650
+ // Need to check all key substrings in insert mode.
651
+ while (keys.length > 1 && match.type != 'full') {
652
+ var keys = vim.inputState.keyBuffer = keys.slice(1);
653
+ var thisMatch = commandDispatcher.matchCommand(keys, defaultKeymap, vim.inputState, 'insert');
654
+ if (thisMatch.type != 'none') { match = thisMatch; }
723
655
  }
724
- if (macroModeState.isRecording) {
725
- logKey(macroModeState, key);
656
+ if (match.type == 'none') { clearInputState(cm); return false; }
657
+ else if (match.type == 'partial') {
658
+ if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); }
659
+ lastInsertModeKeyTimer = window.setTimeout(
660
+ function() { if (vim.insertMode && vim.inputState.keyBuffer) { clearInputState(cm); } },
661
+ getOption('insertModeEscKeysTimeout'));
662
+ return !keysAreChars;
726
663
  }
727
- return;
664
+
665
+ if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); }
666
+ if (keysAreChars) {
667
+ var here = cm.getCursor();
668
+ cm.replaceRange('', offsetCursor(here, 0, -(keys.length - 1)), here, '+input');
669
+ }
670
+ clearInputState(cm);
671
+ var command = match.command;
672
+ if (command.type == 'keyToKey') {
673
+ doKeyToKey(command.toKeys);
674
+ } else {
675
+ commandDispatcher.processCommand(cm, vim, command);
676
+ }
677
+ return !keysAreChars;
728
678
  }
729
- if (command.type == 'keyToKey') {
730
- // TODO: prevent infinite recursion.
731
- for (var i = 0; i < command.toKeys.length; i++) {
732
- this.handleKey(cm, command.toKeys[i]);
679
+
680
+ function handleKeyNonInsertMode() {
681
+ if (handleMacroRecording() || handleEsc()) { return true; };
682
+
683
+ var keys = vim.inputState.keyBuffer = vim.inputState.keyBuffer + key;
684
+ if (/^[1-9]\d*$/.test(keys)) { return true; }
685
+
686
+ var keysMatcher = /^(\d*)(.*)$/.exec(keys);
687
+ if (!keysMatcher) { clearInputState(cm); return false; }
688
+ var context = vim.visualMode ? 'visual' :
689
+ 'normal';
690
+ var match = commandDispatcher.matchCommand(keysMatcher[2] || keysMatcher[1], defaultKeymap, vim.inputState, context);
691
+ if (match.type == 'none') { clearInputState(cm); return false; }
692
+ else if (match.type == 'partial') { return true; }
693
+
694
+ vim.inputState.keyBuffer = '';
695
+ var command = match.command;
696
+ var keysMatcher = /^(\d*)(.*)$/.exec(keys);
697
+ if (keysMatcher[1] && keysMatcher[1] != '0') {
698
+ vim.inputState.pushRepeatDigit(keysMatcher[1]);
733
699
  }
734
- } else {
735
- if (macroModeState.isRecording) {
736
- logKey(macroModeState, key);
700
+ if (command.type == 'keyToKey') {
701
+ doKeyToKey(command.toKeys);
702
+ } else {
703
+ commandDispatcher.processCommand(cm, vim, command);
737
704
  }
738
- commandDispatcher.processCommand(cm, vim, command);
705
+ return true;
739
706
  }
707
+
708
+ if (vim.insertMode) { return handleKeyInsertMode(); }
709
+ else { return handleKeyNonInsertMode(); }
740
710
  },
741
711
  handleEx: function(cm, input) {
742
712
  exCommandDispatcher.processCommand(cm, input);
@@ -953,83 +923,25 @@
953
923
  }
954
924
  };
955
925
  var commandDispatcher = {
956
- matchCommand: function(key, keyMap, vim) {
957
- var inputState = vim.inputState;
958
- var keys = inputState.keyBuffer.concat(key);
959
- var matchedCommands = [];
960
- var selectedCharacter;
961
- for (var i = 0; i < keyMap.length; i++) {
962
- var command = keyMap[i];
963
- if (matchKeysPartial(keys, command.keys)) {
964
- if (inputState.operator && command.type == 'action') {
965
- // Ignore matched action commands after an operator. Operators
966
- // only operate on motions. This check is really for text
967
- // objects since aW, a[ etcs conflicts with a.
968
- continue;
969
- }
970
- // Match commands that take <character> as an argument.
971
- if (command.keys[keys.length - 1] == 'character') {
972
- selectedCharacter = keys[keys.length - 1];
973
- if (selectedCharacter.length>1){
974
- switch(selectedCharacter){
975
- case '<CR>':
976
- selectedCharacter='\n';
977
- break;
978
- case '<Space>':
979
- selectedCharacter=' ';
980
- break;
981
- default:
982
- continue;
983
- }
984
- }
985
- }
986
- // Add the command to the list of matched commands. Choose the best
987
- // command later.
988
- matchedCommands.push(command);
989
- }
926
+ matchCommand: function(keys, keyMap, inputState, context) {
927
+ var matches = commandMatches(keys, keyMap, context, inputState);
928
+ if (!matches.full && !matches.partial) {
929
+ return {type: 'none'};
930
+ } else if (!matches.full && matches.partial) {
931
+ return {type: 'partial'};
990
932
  }
991
933
 
992
- // Returns the command if it is a full match, or null if not.
993
- function getFullyMatchedCommandOrNull(command) {
994
- if (keys.length < command.keys.length) {
995
- // Matches part of a multi-key command. Buffer and wait for next
996
- // stroke.
997
- inputState.keyBuffer.push(key);
998
- return null;
999
- } else {
1000
- if (command.keys[keys.length - 1] == 'character') {
1001
- inputState.selectedCharacter = selectedCharacter;
1002
- }
1003
- // Clear the buffer since a full match was found.
1004
- inputState.keyBuffer = [];
1005
- return command;
934
+ var bestMatch;
935
+ for (var i = 0; i < matches.full.length; i++) {
936
+ var match = matches.full[i];
937
+ if (!bestMatch) {
938
+ bestMatch = match;
1006
939
  }
1007
940
  }
1008
-
1009
- if (!matchedCommands.length) {
1010
- // Clear the buffer since there were no matches.
1011
- inputState.keyBuffer = [];
1012
- return null;
1013
- } else if (matchedCommands.length == 1) {
1014
- return getFullyMatchedCommandOrNull(matchedCommands[0]);
1015
- } else {
1016
- // Find the best match in the list of matchedCommands.
1017
- var context = vim.visualMode ? 'visual' : 'normal';
1018
- var bestMatch; // Default to first in the list.
1019
- for (var i = 0; i < matchedCommands.length; i++) {
1020
- var current = matchedCommands[i];
1021
- if (current.context == context) {
1022
- bestMatch = current;
1023
- break;
1024
- } else if (!bestMatch && !current.context) {
1025
- // Only set an imperfect match to best match if no best match is
1026
- // set and the imperfect match is not restricted to another
1027
- // context.
1028
- bestMatch = current;
1029
- }
1030
- }
1031
- return getFullyMatchedCommandOrNull(bestMatch);
941
+ if (bestMatch.keys.slice(-11) == '<character>') {
942
+ inputState.selectedCharacter = lastChar(keys);
1032
943
  }
944
+ return {type: 'full', command: bestMatch};
1033
945
  },
1034
946
  processCommand: function(cm, vim, command) {
1035
947
  vim.inputState.repeatOverride = command.repeatOverride;
@@ -1584,8 +1496,8 @@
1584
1496
 
1585
1497
  var equal = cursorEqual(cursor, best);
1586
1498
  var between = (motionArgs.forward) ?
1587
- cusrorIsBetween(cursor, mark, best) :
1588
- cusrorIsBetween(best, mark, cursor);
1499
+ cursorIsBetween(cursor, mark, best) :
1500
+ cursorIsBetween(best, mark, cursor);
1589
1501
 
1590
1502
  if (equal || between) {
1591
1503
  best = mark;
@@ -1837,7 +1749,11 @@
1837
1749
  return null;
1838
1750
  }
1839
1751
 
1840
- return [tmp.start, tmp.end];
1752
+ if (!cm.state.vim.visualMode) {
1753
+ return [tmp.start, tmp.end];
1754
+ } else {
1755
+ return expandSelection(cm, tmp.start, tmp.end);
1756
+ }
1841
1757
  },
1842
1758
 
1843
1759
  repeatLastCharacterSearch: function(cm, motionArgs) {
@@ -1944,10 +1860,15 @@
1944
1860
  // including the trailing \n, include the \n before the starting line
1945
1861
  if (operatorArgs.linewise &&
1946
1862
  curEnd.line == cm.lastLine() && curStart.line == curEnd.line) {
1947
- var tmp = copyCursor(curEnd);
1948
- curStart.line--;
1949
- curStart.ch = lineLength(cm, curStart.line);
1950
- curEnd = tmp;
1863
+ if (curEnd.line == 0) {
1864
+ curStart.ch = 0;
1865
+ }
1866
+ else {
1867
+ var tmp = copyCursor(curEnd);
1868
+ curStart.line--;
1869
+ curStart.ch = lineLength(cm, curStart.line);
1870
+ curEnd = tmp;
1871
+ }
1951
1872
  cm.replaceRange('', curStart, curEnd);
1952
1873
  } else {
1953
1874
  cm.replaceSelections(replacement);
@@ -2604,7 +2525,8 @@
2604
2525
  if (vim.visualMode) {
2605
2526
  exitVisualMode(cm);
2606
2527
  }
2607
- }
2528
+ },
2529
+ exitInsertMode: exitInsertMode
2608
2530
  };
2609
2531
 
2610
2532
  /*
@@ -2634,14 +2556,54 @@
2634
2556
  function offsetCursor(cur, offsetLine, offsetCh) {
2635
2557
  return Pos(cur.line + offsetLine, cur.ch + offsetCh);
2636
2558
  }
2637
- function matchKeysPartial(pressed, mapped) {
2638
- for (var i = 0; i < pressed.length; i++) {
2639
- // 'character' means any character. For mark, register commads, etc.
2640
- if (pressed[i] != mapped[i] && mapped[i] != 'character') {
2641
- return false;
2559
+ function commandMatches(keys, keyMap, context, inputState) {
2560
+ // Partial matches are not applied. They inform the key handler
2561
+ // that the current key sequence is a subsequence of a valid key
2562
+ // sequence, so that the key buffer is not cleared.
2563
+ var match, partial = [], full = [];
2564
+ for (var i = 0; i < keyMap.length; i++) {
2565
+ var command = keyMap[i];
2566
+ if (context == 'insert' && command.context != 'insert' ||
2567
+ command.context && command.context != context ||
2568
+ inputState.operator && command.type == 'action' ||
2569
+ !(match = commandMatch(keys, command.keys))) { continue; }
2570
+ if (match == 'partial') { partial.push(command); }
2571
+ if (match == 'full') { full.push(command); }
2572
+ }
2573
+ return {
2574
+ partial: partial.length && partial,
2575
+ full: full.length && full
2576
+ };
2577
+ }
2578
+ function commandMatch(pressed, mapped) {
2579
+ if (mapped.slice(-11) == '<character>') {
2580
+ // Last character matches anything.
2581
+ var prefixLen = mapped.length - 11;
2582
+ var pressedPrefix = pressed.slice(0, prefixLen);
2583
+ var mappedPrefix = mapped.slice(0, prefixLen);
2584
+ return pressedPrefix == mappedPrefix && pressed.length > prefixLen ? 'full' :
2585
+ mappedPrefix.indexOf(pressedPrefix) == 0 ? 'partial' : false;
2586
+ } else {
2587
+ return pressed == mapped ? 'full' :
2588
+ mapped.indexOf(pressed) == 0 ? 'partial' : false;
2589
+ }
2590
+ }
2591
+ function lastChar(keys) {
2592
+ var match = /^.*(<[\w\-]+>)$/.exec(keys);
2593
+ var selectedCharacter = match ? match[1] : keys.slice(-1);
2594
+ if (selectedCharacter.length > 1){
2595
+ switch(selectedCharacter){
2596
+ case '<CR>':
2597
+ selectedCharacter='\n';
2598
+ break;
2599
+ case '<Space>':
2600
+ selectedCharacter=' ';
2601
+ break;
2602
+ default:
2603
+ break;
2642
2604
  }
2643
2605
  }
2644
- return true;
2606
+ return selectedCharacter;
2645
2607
  }
2646
2608
  function repeatFn(cm, fn, repeat) {
2647
2609
  return function() {
@@ -2665,7 +2627,13 @@
2665
2627
  }
2666
2628
  return false;
2667
2629
  }
2668
- function cusrorIsBetween(cur1, cur2, cur3) {
2630
+ function cursorMin(cur1, cur2) {
2631
+ return cursorIsBefore(cur1, cur2) ? cur1 : cur2;
2632
+ }
2633
+ function cursorMax(cur1, cur2) {
2634
+ return cursorIsBefore(cur1, cur2) ? cur2 : cur1;
2635
+ }
2636
+ function cursorIsBetween(cur1, cur2, cur3) {
2669
2637
  // returns true if cur2 is between cur1 and cur3.
2670
2638
  var cur1before2 = cursorIsBefore(cur1, cur2);
2671
2639
  var cur2before3 = cursorIsBefore(cur2, cur3);
@@ -2892,6 +2860,33 @@
2892
2860
  'visualLine': vim.visualLine,
2893
2861
  'visualBlock': block};
2894
2862
  }
2863
+ function expandSelection(cm, start, end) {
2864
+ var head = cm.getCursor('head');
2865
+ var anchor = cm.getCursor('anchor');
2866
+ var tmp;
2867
+ if (cursorIsBefore(end, start)) {
2868
+ tmp = end;
2869
+ end = start;
2870
+ start = tmp;
2871
+ }
2872
+ if (cursorIsBefore(head, anchor)) {
2873
+ head = cursorMin(start, head);
2874
+ anchor = cursorMax(anchor, end);
2875
+ } else {
2876
+ anchor = cursorMin(start, anchor);
2877
+ head = cursorMax(head, end);
2878
+ }
2879
+ return [anchor, head];
2880
+ }
2881
+ function getHead(cm) {
2882
+ var cur = cm.getCursor('head');
2883
+ if (cm.getSelection().length == 1) {
2884
+ // Small corner case when only 1 character is selected. The "real"
2885
+ // head is the left of head and anchor.
2886
+ cur = cursorMin(cur, cm.getCursor('anchor'));
2887
+ }
2888
+ return cur;
2889
+ }
2895
2890
 
2896
2891
  function exitVisualMode(cm) {
2897
2892
  cm.off('mousedown', exitVisualMode);
@@ -2964,7 +2959,7 @@
2964
2959
  }
2965
2960
 
2966
2961
  function expandWordUnderCursor(cm, inclusive, _forward, bigWord, noSymbol) {
2967
- var cur = cm.getCursor();
2962
+ var cur = getHead(cm);
2968
2963
  var line = cm.getLine(cur.line);
2969
2964
  var idx = cur.ch;
2970
2965
 
@@ -3350,7 +3345,7 @@
3350
3345
  // TODO: perhaps this finagling of start and end positions belonds
3351
3346
  // in codmirror/replaceRange?
3352
3347
  function selectCompanionObject(cm, symb, inclusive) {
3353
- var cur = cm.getCursor(), start, end;
3348
+ var cur = getHead(cm), start, end;
3354
3349
 
3355
3350
  var bracketRegexp = ({
3356
3351
  '(': /[()]/, ')': /[()]/,
@@ -3395,7 +3390,7 @@
3395
3390
  // have identical opening and closing symbols
3396
3391
  // TODO support across multiple lines
3397
3392
  function findBeginningAndEnd(cm, symb, inclusive) {
3398
- var cur = copyCursor(cm.getCursor());
3393
+ var cur = copyCursor(getHead(cm));
3399
3394
  var line = cm.getLine(cur.line);
3400
3395
  var chars = line.split('');
3401
3396
  var start, end, i, len;
@@ -3828,6 +3823,7 @@
3828
3823
  // shortNames must not match the prefix of the other command.
3829
3824
  var defaultExCommandMap = [
3830
3825
  { name: 'map' },
3826
+ { name: 'imap', shortName: 'im' },
3831
3827
  { name: 'nmap', shortName: 'nm' },
3832
3828
  { name: 'vmap', shortName: 'vm' },
3833
3829
  { name: 'unmap' },
@@ -3882,7 +3878,7 @@
3882
3878
  if (command.type == 'exToKey') {
3883
3879
  // Handle Ex to Key mapping.
3884
3880
  for (var i = 0; i < command.toKeys.length; i++) {
3885
- CodeMirror.Vim.handleKey(cm, command.toKeys[i]);
3881
+ CodeMirror.Vim.handleKey(cm, command.toKeys[i], 'mapping');
3886
3882
  }
3887
3883
  return;
3888
3884
  } else if (command.type == 'exToEx') {
@@ -4006,7 +4002,7 @@
4006
4002
  this.commandMap_[commandName] = {
4007
4003
  name: commandName,
4008
4004
  type: 'exToKey',
4009
- toKeys: parseKeyString(rhs),
4005
+ toKeys: rhs,
4010
4006
  user: true
4011
4007
  };
4012
4008
  }
@@ -4014,7 +4010,7 @@
4014
4010
  if (rhs != ':' && rhs.charAt(0) == ':') {
4015
4011
  // Key to Ex mapping.
4016
4012
  var mapping = {
4017
- keys: parseKeyString(lhs),
4013
+ keys: lhs,
4018
4014
  type: 'keyToEx',
4019
4015
  exArgs: { input: rhs.substring(1) },
4020
4016
  user: true};
@@ -4023,9 +4019,9 @@
4023
4019
  } else {
4024
4020
  // Key to key mapping
4025
4021
  var mapping = {
4026
- keys: parseKeyString(lhs),
4022
+ keys: lhs,
4027
4023
  type: 'keyToKey',
4028
- toKeys: parseKeyString(rhs),
4024
+ toKeys: rhs,
4029
4025
  user: true
4030
4026
  };
4031
4027
  if (ctx) { mapping.context = ctx; }
@@ -4034,15 +4030,6 @@
4034
4030
  }
4035
4031
  },
4036
4032
  unmap: function(lhs, ctx) {
4037
- var arrayEquals = function(a, b) {
4038
- if (a === b) return true;
4039
- if (a == null || b == null) return true;
4040
- if (a.length != b.length) return false;
4041
- for (var i = 0; i < a.length; i++) {
4042
- if (a[i] !== b[i]) return false;
4043
- }
4044
- return true;
4045
- };
4046
4033
  if (lhs != ':' && lhs.charAt(0) == ':') {
4047
4034
  // Ex to Ex or Ex to key mapping
4048
4035
  if (ctx) { throw Error('Mode not supported for ex mappings'); }
@@ -4053,9 +4040,9 @@
4053
4040
  }
4054
4041
  } else {
4055
4042
  // Key to Ex or key to key mapping
4056
- var keys = parseKeyString(lhs);
4043
+ var keys = lhs;
4057
4044
  for (var i = 0; i < defaultKeymap.length; i++) {
4058
- if (arrayEquals(keys, defaultKeymap[i].keys)
4045
+ if (keys == defaultKeymap[i].keys
4059
4046
  && defaultKeymap[i].context === ctx
4060
4047
  && defaultKeymap[i].user) {
4061
4048
  defaultKeymap.splice(i, 1);
@@ -4067,21 +4054,6 @@
4067
4054
  }
4068
4055
  };
4069
4056
 
4070
- // Converts a key string sequence of the form a<C-w>bd<Left> into Vim's
4071
- // keymap representation.
4072
- function parseKeyString(str) {
4073
- var key, match;
4074
- var keys = [];
4075
- while (str) {
4076
- match = (/<\w+-.+?>|<\w+>|./).exec(str);
4077
- if (match === null)break;
4078
- key = match[0];
4079
- str = str.substring(match.index + key.length);
4080
- keys.push(key);
4081
- }
4082
- return keys;
4083
- }
4084
-
4085
4057
  var exCommands = {
4086
4058
  map: function(cm, params, ctx) {
4087
4059
  var mapArgs = params.args;
@@ -4093,6 +4065,7 @@
4093
4065
  }
4094
4066
  exCommandDispatcher.map(mapArgs[0], mapArgs[1], ctx);
4095
4067
  },
4068
+ imap: function(cm, params) { this.map(cm, params, 'insert'); },
4096
4069
  nmap: function(cm, params) { this.map(cm, params, 'normal'); },
4097
4070
  vmap: function(cm, params) { this.map(cm, params, 'visual'); },
4098
4071
  unmap: function(cm, params, ctx) {
@@ -4574,65 +4547,10 @@
4574
4547
  });
4575
4548
  }
4576
4549
 
4577
- // Register Vim with CodeMirror
4578
- function buildVimKeyMap() {
4579
- /**
4580
- * Handle the raw key event from CodeMirror. Translate the
4581
- * Shift + key modifier to the resulting letter, while preserving other
4582
- * modifers.
4583
- */
4584
- function cmKeyToVimKey(key, modifier) {
4585
- var vimKey = key;
4586
- if (isUpperCase(vimKey) && modifier == 'Ctrl') {
4587
- vimKey = vimKey.toLowerCase();
4588
- }
4589
- if (modifier) {
4590
- // Vim will parse modifier+key combination as a single key.
4591
- vimKey = modifier.charAt(0) + '-' + vimKey;
4592
- }
4593
- var specialKey = ({Enter:'CR',Backspace:'BS',Delete:'Del'})[vimKey];
4594
- vimKey = specialKey ? specialKey : vimKey;
4595
- vimKey = vimKey.length > 1 ? '<'+ vimKey + '>' : vimKey;
4596
- return vimKey;
4597
- }
4598
-
4599
- // Closure to bind CodeMirror, key, modifier.
4600
- function keyMapper(vimKey) {
4601
- return function(cm) {
4602
- CodeMirror.signal(cm, 'vim-keypress', vimKey);
4603
- CodeMirror.Vim.handleKey(cm, vimKey);
4604
- };
4605
- }
4606
-
4607
- var cmToVimKeymap = {
4550
+ CodeMirror.keyMap.vim = {
4608
4551
  'nofallthrough': true,
4609
4552
  'style': 'fat-cursor'
4610
4553
  };
4611
- function bindKeys(keys, modifier) {
4612
- for (var i = 0; i < keys.length; i++) {
4613
- var key = keys[i];
4614
- if (!modifier && key.length == 1) {
4615
- // Wrap all keys without modifiers with '' to identify them by their
4616
- // key characters instead of key identifiers.
4617
- key = "'" + key + "'";
4618
- }
4619
- var vimKey = cmKeyToVimKey(keys[i], modifier);
4620
- var cmKey = modifier ? modifier + '-' + key : key;
4621
- cmToVimKeymap[cmKey] = keyMapper(vimKey);
4622
- }
4623
- }
4624
- bindKeys(upperCaseAlphabet);
4625
- bindKeys(lowerCaseAlphabet);
4626
- bindKeys(upperCaseAlphabet, 'Ctrl');
4627
- bindKeys(specialSymbols);
4628
- bindKeys(specialSymbols, 'Ctrl');
4629
- bindKeys(numbers);
4630
- bindKeys(numbers, 'Ctrl');
4631
- bindKeys(specialKeys);
4632
- bindKeys(specialKeys, 'Ctrl');
4633
- return cmToVimKeymap;
4634
- }
4635
- CodeMirror.keyMap.vim = buildVimKeyMap();
4636
4554
 
4637
4555
  function exitInsertMode(cm) {
4638
4556
  var vim = cm.state.vim;
@@ -4688,63 +4606,13 @@
4688
4606
  }
4689
4607
  }
4690
4608
 
4691
- defineOption('enableInsertModeEscKeys', false, 'boolean');
4692
- // Use this option to customize the two-character ESC keymap.
4693
- // If you want to use characters other than i j or k you'll have to add
4694
- // lines to the vim-insert and await-second keymaps later in this file.
4695
- defineOption('insertModeEscKeys', 'kj', 'string');
4696
4609
  // The timeout in milliseconds for the two-character ESC keymap should be
4697
4610
  // adjusted according to your typing speed to prevent false positives.
4698
4611
  defineOption('insertModeEscKeysTimeout', 200, 'number');
4699
- function firstEscCharacterHandler(ch) {
4700
- return function(cm){
4701
- var keys = getOption('insertModeEscKeys');
4702
- var firstEscCharacter = keys && keys.length > 1 && keys.charAt(0);
4703
- if (!getOption('enableInsertModeEscKeys')|| firstEscCharacter !== ch) {
4704
- return CodeMirror.Pass;
4705
- } else {
4706
- cm.replaceRange(ch, cm.getCursor(), cm.getCursor(), "+input");
4707
- cm.setOption('keyMap', 'await-second');
4708
- cm.state.vim.awaitingEscapeSecondCharacter = true;
4709
- setTimeout(
4710
- function(){
4711
- if(cm.state.vim.awaitingEscapeSecondCharacter) {
4712
- cm.state.vim.awaitingEscapeSecondCharacter = false;
4713
- cm.setOption('keyMap', 'vim-insert');
4714
- }
4715
- },
4716
- getOption('insertModeEscKeysTimeout'));
4717
- }
4718
- };
4719
- }
4720
- function secondEscCharacterHandler(ch){
4721
- return function(cm) {
4722
- var keys = getOption('insertModeEscKeys');
4723
- var secondEscCharacter = keys && keys.length > 1 && keys.charAt(1);
4724
- if (!getOption('enableInsertModeEscKeys')|| secondEscCharacter !== ch) {
4725
- return CodeMirror.Pass;
4726
- // This is not the handler you're looking for. Just insert as usual.
4727
- } else {
4728
- if (cm.state.vim.insertMode) {
4729
- var lastChange = vimGlobalState.macroModeState.lastInsertModeChanges;
4730
- if (lastChange && lastChange.changes.length) {
4731
- lastChange.changes.pop();
4732
- }
4733
- }
4734
- cm.state.vim.awaitingEscapeSecondCharacter = false;
4735
- cm.replaceRange('', {ch: cm.getCursor().ch - 1, line: cm.getCursor().line},
4736
- cm.getCursor(), "+input");
4737
- exitInsertMode(cm);
4738
- }
4739
- };
4740
- }
4741
4612
 
4742
4613
  CodeMirror.keyMap['vim-insert'] = {
4743
4614
  // TODO: override navigation keys so that Esc will cancel automatic
4744
4615
  // indentation from o, O, i_<CR>
4745
- 'Esc': exitInsertMode,
4746
- 'Ctrl-[': exitInsertMode,
4747
- 'Ctrl-C': exitInsertMode,
4748
4616
  'Ctrl-N': 'autocomplete',
4749
4617
  'Ctrl-P': 'autocomplete',
4750
4618
  'Enter': function(cm) {
@@ -4752,20 +4620,10 @@
4752
4620
  CodeMirror.commands.newlineAndIndent;
4753
4621
  fn(cm);
4754
4622
  },
4755
- // The next few lines are where you'd add additional handlers if
4756
- // you wanted to use keys other than i j and k for two-character
4757
- // escape sequences. Don't forget to add them in the await-second
4758
- // section as well.
4759
- "'i'": firstEscCharacterHandler('i'),
4760
- "'j'": firstEscCharacterHandler('j'),
4761
- "'k'": firstEscCharacterHandler('k'),
4762
4623
  fallthrough: ['default']
4763
4624
  };
4764
4625
 
4765
4626
  CodeMirror.keyMap['await-second'] = {
4766
- "'i'": secondEscCharacterHandler('i'),
4767
- "'j'": secondEscCharacterHandler('j'),
4768
- "'k'": secondEscCharacterHandler('k'),
4769
4627
  fallthrough: ['vim-insert']
4770
4628
  };
4771
4629
 
@@ -4789,7 +4647,7 @@
4789
4647
  match = (/<\w+-.+?>|<\w+>|./).exec(text);
4790
4648
  key = match[0];
4791
4649
  text = text.substring(match.index + key.length);
4792
- CodeMirror.Vim.handleKey(cm, key);
4650
+ CodeMirror.Vim.handleKey(cm, key, 'macro');
4793
4651
  if (vim.insertMode) {
4794
4652
  var changes = register.insertModeChanges[imc++].changes;
4795
4653
  vimGlobalState.macroModeState.lastInsertModeChanges.changes =
@@ -4869,10 +4727,7 @@
4869
4727
  } else if (cm.doc.history.lastSelOrigin == '*mouse') {
4870
4728
  // Reset lastHPos if mouse click was done in normal mode.
4871
4729
  vim.lastHPos = cm.doc.getCursor().ch;
4872
- if (cm.somethingSelected()) {
4873
- // If something is still selected, enter visual mode.
4874
- vim.visualMode = true;
4875
- }
4730
+ handleExternalSelection(cm, vim);
4876
4731
  }
4877
4732
  if (vim.visualMode) {
4878
4733
  var from, head;
@@ -4891,6 +4746,22 @@
4891
4746
  }
4892
4747
  }
4893
4748
 
4749
+ function handleExternalSelection(cm, vim) {
4750
+ var anchor = cm.getCursor('anchor');
4751
+ var head = cm.getCursor('head');
4752
+ // Enter visual mode when the mouse selects text.
4753
+ if (!vim.visualMode && !vim.insertMode && cm.somethingSelected()) {
4754
+ vim.visualMode = true;
4755
+ vim.visualLine = false;
4756
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"});
4757
+ cm.on('mousedown', exitVisualMode);
4758
+ }
4759
+ if (vim.visualMode) {
4760
+ updateMark(cm, vim, '<', cursorMin(head, anchor));
4761
+ updateMark(cm, vim, '>', cursorMax(head, anchor));
4762
+ }
4763
+ }
4764
+
4894
4765
  /** Wrapper for special keys pressed in insert mode */
4895
4766
  function InsertModeKey(keyName) {
4896
4767
  this.keyName = keyName;