1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/sourceeditor/codemirror/keymap/vim.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,4123 @@ 1.4 +/** 1.5 + * Supported keybindings: 1.6 + * 1.7 + * Motion: 1.8 + * h, j, k, l 1.9 + * gj, gk 1.10 + * e, E, w, W, b, B, ge, gE 1.11 + * f<character>, F<character>, t<character>, T<character> 1.12 + * $, ^, 0, -, +, _ 1.13 + * gg, G 1.14 + * % 1.15 + * '<character>, `<character> 1.16 + * 1.17 + * Operator: 1.18 + * d, y, c 1.19 + * dd, yy, cc 1.20 + * g~, g~g~ 1.21 + * >, <, >>, << 1.22 + * 1.23 + * Operator-Motion: 1.24 + * x, X, D, Y, C, ~ 1.25 + * 1.26 + * Action: 1.27 + * a, i, s, A, I, S, o, O 1.28 + * zz, z., z<CR>, zt, zb, z- 1.29 + * J 1.30 + * u, Ctrl-r 1.31 + * m<character> 1.32 + * r<character> 1.33 + * 1.34 + * Modes: 1.35 + * ESC - leave insert mode, visual mode, and clear input state. 1.36 + * Ctrl-[, Ctrl-c - same as ESC. 1.37 + * 1.38 + * Registers: unnamed, -, a-z, A-Z, 0-9 1.39 + * (Does not respect the special case for number registers when delete 1.40 + * operator is made with these commands: %, (, ), , /, ?, n, N, {, } ) 1.41 + * TODO: Implement the remaining registers. 1.42 + * Marks: a-z, A-Z, and 0-9 1.43 + * TODO: Implement the remaining special marks. They have more complex 1.44 + * behavior. 1.45 + * 1.46 + * Events: 1.47 + * 'vim-mode-change' - raised on the editor anytime the current mode changes, 1.48 + * Event object: {mode: "visual", subMode: "linewise"} 1.49 + * 1.50 + * Code structure: 1.51 + * 1. Default keymap 1.52 + * 2. Variable declarations and short basic helpers 1.53 + * 3. Instance (External API) implementation 1.54 + * 4. Internal state tracking objects (input state, counter) implementation 1.55 + * and instanstiation 1.56 + * 5. Key handler (the main command dispatcher) implementation 1.57 + * 6. Motion, operator, and action implementations 1.58 + * 7. Helper functions for the key handler, motions, operators, and actions 1.59 + * 8. Set up Vim to work as a keymap for CodeMirror. 1.60 + */ 1.61 + 1.62 +(function(mod) { 1.63 + if (typeof exports == "object" && typeof module == "object") // CommonJS 1.64 + mod(require("../lib/codemirror"), require("../addon/search/searchcursor"), require("../addon/dialog/dialog")); 1.65 + else if (typeof define == "function" && define.amd) // AMD 1.66 + define(["../lib/codemirror", "../addon/search/searchcursor", "../addon/dialog/dialog"], mod); 1.67 + else // Plain browser env 1.68 + mod(CodeMirror); 1.69 +})(function(CodeMirror) { 1.70 + 'use strict'; 1.71 + 1.72 + var defaultKeymap = [ 1.73 + // Key to key mapping. This goes first to make it possible to override 1.74 + // existing mappings. 1.75 + { keys: ['<Left>'], type: 'keyToKey', toKeys: ['h'] }, 1.76 + { keys: ['<Right>'], type: 'keyToKey', toKeys: ['l'] }, 1.77 + { keys: ['<Up>'], type: 'keyToKey', toKeys: ['k'] }, 1.78 + { keys: ['<Down>'], type: 'keyToKey', toKeys: ['j'] }, 1.79 + { keys: ['<Space>'], type: 'keyToKey', toKeys: ['l'] }, 1.80 + { keys: ['<BS>'], type: 'keyToKey', toKeys: ['h'] }, 1.81 + { keys: ['<C-Space>'], type: 'keyToKey', toKeys: ['W'] }, 1.82 + { keys: ['<C-BS>'], type: 'keyToKey', toKeys: ['B'] }, 1.83 + { keys: ['<S-Space>'], type: 'keyToKey', toKeys: ['w'] }, 1.84 + { keys: ['<S-BS>'], type: 'keyToKey', toKeys: ['b'] }, 1.85 + { keys: ['<C-n>'], type: 'keyToKey', toKeys: ['j'] }, 1.86 + { keys: ['<C-p>'], type: 'keyToKey', toKeys: ['k'] }, 1.87 + { keys: ['<C-[>'], type: 'keyToKey', toKeys: ['<Esc>'] }, 1.88 + { keys: ['<C-c>'], type: 'keyToKey', toKeys: ['<Esc>'] }, 1.89 + { keys: ['s'], type: 'keyToKey', toKeys: ['c', 'l'], context: 'normal' }, 1.90 + { keys: ['s'], type: 'keyToKey', toKeys: ['x', 'i'], context: 'visual'}, 1.91 + { keys: ['S'], type: 'keyToKey', toKeys: ['c', 'c'], context: 'normal' }, 1.92 + { keys: ['S'], type: 'keyToKey', toKeys: ['d', 'c', 'c'], context: 'visual' }, 1.93 + { keys: ['<Home>'], type: 'keyToKey', toKeys: ['0'] }, 1.94 + { keys: ['<End>'], type: 'keyToKey', toKeys: ['$'] }, 1.95 + { keys: ['<PageUp>'], type: 'keyToKey', toKeys: ['<C-b>'] }, 1.96 + { keys: ['<PageDown>'], type: 'keyToKey', toKeys: ['<C-f>'] }, 1.97 + { keys: ['<CR>'], type: 'keyToKey', toKeys: ['j', '^'], context: 'normal' }, 1.98 + // Motions 1.99 + { keys: ['H'], type: 'motion', 1.100 + motion: 'moveToTopLine', 1.101 + motionArgs: { linewise: true, toJumplist: true }}, 1.102 + { keys: ['M'], type: 'motion', 1.103 + motion: 'moveToMiddleLine', 1.104 + motionArgs: { linewise: true, toJumplist: true }}, 1.105 + { keys: ['L'], type: 'motion', 1.106 + motion: 'moveToBottomLine', 1.107 + motionArgs: { linewise: true, toJumplist: true }}, 1.108 + { keys: ['h'], type: 'motion', 1.109 + motion: 'moveByCharacters', 1.110 + motionArgs: { forward: false }}, 1.111 + { keys: ['l'], type: 'motion', 1.112 + motion: 'moveByCharacters', 1.113 + motionArgs: { forward: true }}, 1.114 + { keys: ['j'], type: 'motion', 1.115 + motion: 'moveByLines', 1.116 + motionArgs: { forward: true, linewise: true }}, 1.117 + { keys: ['k'], type: 'motion', 1.118 + motion: 'moveByLines', 1.119 + motionArgs: { forward: false, linewise: true }}, 1.120 + { keys: ['g','j'], type: 'motion', 1.121 + motion: 'moveByDisplayLines', 1.122 + motionArgs: { forward: true }}, 1.123 + { keys: ['g','k'], type: 'motion', 1.124 + motion: 'moveByDisplayLines', 1.125 + motionArgs: { forward: false }}, 1.126 + { keys: ['w'], type: 'motion', 1.127 + motion: 'moveByWords', 1.128 + motionArgs: { forward: true, wordEnd: false }}, 1.129 + { keys: ['W'], type: 'motion', 1.130 + motion: 'moveByWords', 1.131 + motionArgs: { forward: true, wordEnd: false, bigWord: true }}, 1.132 + { keys: ['e'], type: 'motion', 1.133 + motion: 'moveByWords', 1.134 + motionArgs: { forward: true, wordEnd: true, inclusive: true }}, 1.135 + { keys: ['E'], type: 'motion', 1.136 + motion: 'moveByWords', 1.137 + motionArgs: { forward: true, wordEnd: true, bigWord: true, 1.138 + inclusive: true }}, 1.139 + { keys: ['b'], type: 'motion', 1.140 + motion: 'moveByWords', 1.141 + motionArgs: { forward: false, wordEnd: false }}, 1.142 + { keys: ['B'], type: 'motion', 1.143 + motion: 'moveByWords', 1.144 + motionArgs: { forward: false, wordEnd: false, bigWord: true }}, 1.145 + { keys: ['g', 'e'], type: 'motion', 1.146 + motion: 'moveByWords', 1.147 + motionArgs: { forward: false, wordEnd: true, inclusive: true }}, 1.148 + { keys: ['g', 'E'], type: 'motion', 1.149 + motion: 'moveByWords', 1.150 + motionArgs: { forward: false, wordEnd: true, bigWord: true, 1.151 + inclusive: true }}, 1.152 + { keys: ['{'], type: 'motion', motion: 'moveByParagraph', 1.153 + motionArgs: { forward: false, toJumplist: true }}, 1.154 + { keys: ['}'], type: 'motion', motion: 'moveByParagraph', 1.155 + motionArgs: { forward: true, toJumplist: true }}, 1.156 + { keys: ['<C-f>'], type: 'motion', 1.157 + motion: 'moveByPage', motionArgs: { forward: true }}, 1.158 + { keys: ['<C-b>'], type: 'motion', 1.159 + motion: 'moveByPage', motionArgs: { forward: false }}, 1.160 + { keys: ['<C-d>'], type: 'motion', 1.161 + motion: 'moveByScroll', 1.162 + motionArgs: { forward: true, explicitRepeat: true }}, 1.163 + { keys: ['<C-u>'], type: 'motion', 1.164 + motion: 'moveByScroll', 1.165 + motionArgs: { forward: false, explicitRepeat: true }}, 1.166 + { keys: ['g', 'g'], type: 'motion', 1.167 + motion: 'moveToLineOrEdgeOfDocument', 1.168 + motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }}, 1.169 + { keys: ['G'], type: 'motion', 1.170 + motion: 'moveToLineOrEdgeOfDocument', 1.171 + motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }}, 1.172 + { keys: ['0'], type: 'motion', motion: 'moveToStartOfLine' }, 1.173 + { keys: ['^'], type: 'motion', 1.174 + motion: 'moveToFirstNonWhiteSpaceCharacter' }, 1.175 + { keys: ['+'], type: 'motion', 1.176 + motion: 'moveByLines', 1.177 + motionArgs: { forward: true, toFirstChar:true }}, 1.178 + { keys: ['-'], type: 'motion', 1.179 + motion: 'moveByLines', 1.180 + motionArgs: { forward: false, toFirstChar:true }}, 1.181 + { keys: ['_'], type: 'motion', 1.182 + motion: 'moveByLines', 1.183 + motionArgs: { forward: true, toFirstChar:true, repeatOffset:-1 }}, 1.184 + { keys: ['$'], type: 'motion', 1.185 + motion: 'moveToEol', 1.186 + motionArgs: { inclusive: true }}, 1.187 + { keys: ['%'], type: 'motion', 1.188 + motion: 'moveToMatchedSymbol', 1.189 + motionArgs: { inclusive: true, toJumplist: true }}, 1.190 + { keys: ['f', 'character'], type: 'motion', 1.191 + motion: 'moveToCharacter', 1.192 + motionArgs: { forward: true , inclusive: true }}, 1.193 + { keys: ['F', 'character'], type: 'motion', 1.194 + motion: 'moveToCharacter', 1.195 + motionArgs: { forward: false }}, 1.196 + { keys: ['t', 'character'], type: 'motion', 1.197 + motion: 'moveTillCharacter', 1.198 + motionArgs: { forward: true, inclusive: true }}, 1.199 + { keys: ['T', 'character'], type: 'motion', 1.200 + motion: 'moveTillCharacter', 1.201 + motionArgs: { forward: false }}, 1.202 + { keys: [';'], type: 'motion', motion: 'repeatLastCharacterSearch', 1.203 + motionArgs: { forward: true }}, 1.204 + { keys: [','], type: 'motion', motion: 'repeatLastCharacterSearch', 1.205 + motionArgs: { forward: false }}, 1.206 + { keys: ['\'', 'character'], type: 'motion', motion: 'goToMark', 1.207 + motionArgs: {toJumplist: true}}, 1.208 + { keys: ['`', 'character'], type: 'motion', motion: 'goToMark', 1.209 + motionArgs: {toJumplist: true}}, 1.210 + { keys: [']', '`'], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } }, 1.211 + { keys: ['[', '`'], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } }, 1.212 + { keys: [']', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } }, 1.213 + { keys: ['[', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } }, 1.214 + { keys: [']', 'character'], type: 'motion', 1.215 + motion: 'moveToSymbol', 1.216 + motionArgs: { forward: true, toJumplist: true}}, 1.217 + { keys: ['[', 'character'], type: 'motion', 1.218 + motion: 'moveToSymbol', 1.219 + motionArgs: { forward: false, toJumplist: true}}, 1.220 + { keys: ['|'], type: 'motion', 1.221 + motion: 'moveToColumn', 1.222 + motionArgs: { }}, 1.223 + { keys: ['o'], type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: { },context:'visual'}, 1.224 + // Operators 1.225 + { keys: ['d'], type: 'operator', operator: 'delete' }, 1.226 + { keys: ['y'], type: 'operator', operator: 'yank' }, 1.227 + { keys: ['c'], type: 'operator', operator: 'change' }, 1.228 + { keys: ['>'], type: 'operator', operator: 'indent', 1.229 + operatorArgs: { indentRight: true }}, 1.230 + { keys: ['<'], type: 'operator', operator: 'indent', 1.231 + operatorArgs: { indentRight: false }}, 1.232 + { keys: ['g', '~'], type: 'operator', operator: 'swapcase' }, 1.233 + { keys: ['n'], type: 'motion', motion: 'findNext', 1.234 + motionArgs: { forward: true, toJumplist: true }}, 1.235 + { keys: ['N'], type: 'motion', motion: 'findNext', 1.236 + motionArgs: { forward: false, toJumplist: true }}, 1.237 + // Operator-Motion dual commands 1.238 + { keys: ['x'], type: 'operatorMotion', operator: 'delete', 1.239 + motion: 'moveByCharacters', motionArgs: { forward: true }, 1.240 + operatorMotionArgs: { visualLine: false }}, 1.241 + { keys: ['X'], type: 'operatorMotion', operator: 'delete', 1.242 + motion: 'moveByCharacters', motionArgs: { forward: false }, 1.243 + operatorMotionArgs: { visualLine: true }}, 1.244 + { keys: ['D'], type: 'operatorMotion', operator: 'delete', 1.245 + motion: 'moveToEol', motionArgs: { inclusive: true }, 1.246 + operatorMotionArgs: { visualLine: true }}, 1.247 + { keys: ['Y'], type: 'operatorMotion', operator: 'yank', 1.248 + motion: 'moveToEol', motionArgs: { inclusive: true }, 1.249 + operatorMotionArgs: { visualLine: true }}, 1.250 + { keys: ['C'], type: 'operatorMotion', 1.251 + operator: 'change', 1.252 + motion: 'moveToEol', motionArgs: { inclusive: true }, 1.253 + operatorMotionArgs: { visualLine: true }}, 1.254 + { keys: ['~'], type: 'operatorMotion', 1.255 + operator: 'swapcase', operatorArgs: { shouldMoveCursor: true }, 1.256 + motion: 'moveByCharacters', motionArgs: { forward: true }}, 1.257 + // Actions 1.258 + { keys: ['<C-i>'], type: 'action', action: 'jumpListWalk', 1.259 + actionArgs: { forward: true }}, 1.260 + { keys: ['<C-o>'], type: 'action', action: 'jumpListWalk', 1.261 + actionArgs: { forward: false }}, 1.262 + { keys: ['<C-e>'], type: 'action', 1.263 + action: 'scroll', 1.264 + actionArgs: { forward: true, linewise: true }}, 1.265 + { keys: ['<C-y>'], type: 'action', 1.266 + action: 'scroll', 1.267 + actionArgs: { forward: false, linewise: true }}, 1.268 + { keys: ['a'], type: 'action', action: 'enterInsertMode', isEdit: true, 1.269 + actionArgs: { insertAt: 'charAfter' }}, 1.270 + { keys: ['A'], type: 'action', action: 'enterInsertMode', isEdit: true, 1.271 + actionArgs: { insertAt: 'eol' }}, 1.272 + { keys: ['i'], type: 'action', action: 'enterInsertMode', isEdit: true, 1.273 + actionArgs: { insertAt: 'inplace' }}, 1.274 + { keys: ['I'], type: 'action', action: 'enterInsertMode', isEdit: true, 1.275 + actionArgs: { insertAt: 'firstNonBlank' }}, 1.276 + { keys: ['o'], type: 'action', action: 'newLineAndEnterInsertMode', 1.277 + isEdit: true, interlaceInsertRepeat: true, 1.278 + actionArgs: { after: true }}, 1.279 + { keys: ['O'], type: 'action', action: 'newLineAndEnterInsertMode', 1.280 + isEdit: true, interlaceInsertRepeat: true, 1.281 + actionArgs: { after: false }}, 1.282 + { keys: ['v'], type: 'action', action: 'toggleVisualMode' }, 1.283 + { keys: ['V'], type: 'action', action: 'toggleVisualMode', 1.284 + actionArgs: { linewise: true }}, 1.285 + { keys: ['g', 'v'], type: 'action', action: 'reselectLastSelection' }, 1.286 + { keys: ['J'], type: 'action', action: 'joinLines', isEdit: true }, 1.287 + { keys: ['p'], type: 'action', action: 'paste', isEdit: true, 1.288 + actionArgs: { after: true, isEdit: true }}, 1.289 + { keys: ['P'], type: 'action', action: 'paste', isEdit: true, 1.290 + actionArgs: { after: false, isEdit: true }}, 1.291 + { keys: ['r', 'character'], type: 'action', action: 'replace', isEdit: true }, 1.292 + { keys: ['@', 'character'], type: 'action', action: 'replayMacro' }, 1.293 + { keys: ['q', 'character'], type: 'action', action: 'enterMacroRecordMode' }, 1.294 + // Handle Replace-mode as a special case of insert mode. 1.295 + { keys: ['R'], type: 'action', action: 'enterInsertMode', isEdit: true, 1.296 + actionArgs: { replace: true }}, 1.297 + { keys: ['u'], type: 'action', action: 'undo' }, 1.298 + { keys: ['<C-r>'], type: 'action', action: 'redo' }, 1.299 + { keys: ['m', 'character'], type: 'action', action: 'setMark' }, 1.300 + { keys: ['"', 'character'], type: 'action', action: 'setRegister' }, 1.301 + { keys: ['z', 'z'], type: 'action', action: 'scrollToCursor', 1.302 + actionArgs: { position: 'center' }}, 1.303 + { keys: ['z', '.'], type: 'action', action: 'scrollToCursor', 1.304 + actionArgs: { position: 'center' }, 1.305 + motion: 'moveToFirstNonWhiteSpaceCharacter' }, 1.306 + { keys: ['z', 't'], type: 'action', action: 'scrollToCursor', 1.307 + actionArgs: { position: 'top' }}, 1.308 + { keys: ['z', '<CR>'], type: 'action', action: 'scrollToCursor', 1.309 + actionArgs: { position: 'top' }, 1.310 + motion: 'moveToFirstNonWhiteSpaceCharacter' }, 1.311 + { keys: ['z', '-'], type: 'action', action: 'scrollToCursor', 1.312 + actionArgs: { position: 'bottom' }}, 1.313 + { keys: ['z', 'b'], type: 'action', action: 'scrollToCursor', 1.314 + actionArgs: { position: 'bottom' }, 1.315 + motion: 'moveToFirstNonWhiteSpaceCharacter' }, 1.316 + { keys: ['.'], type: 'action', action: 'repeatLastEdit' }, 1.317 + { keys: ['<C-a>'], type: 'action', action: 'incrementNumberToken', 1.318 + isEdit: true, 1.319 + actionArgs: {increase: true, backtrack: false}}, 1.320 + { keys: ['<C-x>'], type: 'action', action: 'incrementNumberToken', 1.321 + isEdit: true, 1.322 + actionArgs: {increase: false, backtrack: false}}, 1.323 + // Text object motions 1.324 + { keys: ['a', 'character'], type: 'motion', 1.325 + motion: 'textObjectManipulation' }, 1.326 + { keys: ['i', 'character'], type: 'motion', 1.327 + motion: 'textObjectManipulation', 1.328 + motionArgs: { textObjectInner: true }}, 1.329 + // Search 1.330 + { keys: ['/'], type: 'search', 1.331 + searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }}, 1.332 + { keys: ['?'], type: 'search', 1.333 + searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }}, 1.334 + { keys: ['*'], type: 'search', 1.335 + searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }}, 1.336 + { keys: ['#'], type: 'search', 1.337 + searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }}, 1.338 + // Ex command 1.339 + { keys: [':'], type: 'ex' } 1.340 + ]; 1.341 + 1.342 + var Pos = CodeMirror.Pos; 1.343 + 1.344 + var Vim = function() { 1.345 + CodeMirror.defineOption('vimMode', false, function(cm, val) { 1.346 + if (val) { 1.347 + cm.setOption('keyMap', 'vim'); 1.348 + cm.setOption('disableInput', true); 1.349 + CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); 1.350 + cm.on('beforeSelectionChange', beforeSelectionChange); 1.351 + maybeInitVimState(cm); 1.352 + CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm)); 1.353 + } else if (cm.state.vim) { 1.354 + cm.setOption('keyMap', 'default'); 1.355 + cm.setOption('disableInput', false); 1.356 + cm.off('beforeSelectionChange', beforeSelectionChange); 1.357 + CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm)); 1.358 + cm.state.vim = null; 1.359 + } 1.360 + }); 1.361 + function beforeSelectionChange(cm, obj) { 1.362 + var vim = cm.state.vim; 1.363 + if (vim.insertMode || vim.exMode) return; 1.364 + 1.365 + var head = obj.ranges[0].head; 1.366 + var anchor = obj.ranges[0].anchor; 1.367 + if (head.ch && head.ch == cm.doc.getLine(head.line).length) { 1.368 + var pos = Pos(head.line, head.ch - 1); 1.369 + obj.update([{anchor: cursorEqual(head, anchor) ? pos : anchor, 1.370 + head: pos}]); 1.371 + } 1.372 + } 1.373 + function getOnPasteFn(cm) { 1.374 + var vim = cm.state.vim; 1.375 + if (!vim.onPasteFn) { 1.376 + vim.onPasteFn = function() { 1.377 + if (!vim.insertMode) { 1.378 + cm.setCursor(offsetCursor(cm.getCursor(), 0, 1)); 1.379 + actions.enterInsertMode(cm, {}, vim); 1.380 + } 1.381 + }; 1.382 + } 1.383 + return vim.onPasteFn; 1.384 + } 1.385 + 1.386 + var numberRegex = /[\d]/; 1.387 + var wordRegexp = [(/\w/), (/[^\w\s]/)], bigWordRegexp = [(/\S/)]; 1.388 + function makeKeyRange(start, size) { 1.389 + var keys = []; 1.390 + for (var i = start; i < start + size; i++) { 1.391 + keys.push(String.fromCharCode(i)); 1.392 + } 1.393 + return keys; 1.394 + } 1.395 + var upperCaseAlphabet = makeKeyRange(65, 26); 1.396 + var lowerCaseAlphabet = makeKeyRange(97, 26); 1.397 + var numbers = makeKeyRange(48, 10); 1.398 + var specialSymbols = '~`!@#$%^&*()_-+=[{}]\\|/?.,<>:;"\''.split(''); 1.399 + var specialKeys = ['Left', 'Right', 'Up', 'Down', 'Space', 'Backspace', 1.400 + 'Esc', 'Home', 'End', 'PageUp', 'PageDown', 'Enter']; 1.401 + var validMarks = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['<', '>']); 1.402 + var validRegisters = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['-', '"']); 1.403 + 1.404 + function isLine(cm, line) { 1.405 + return line >= cm.firstLine() && line <= cm.lastLine(); 1.406 + } 1.407 + function isLowerCase(k) { 1.408 + return (/^[a-z]$/).test(k); 1.409 + } 1.410 + function isMatchableSymbol(k) { 1.411 + return '()[]{}'.indexOf(k) != -1; 1.412 + } 1.413 + function isNumber(k) { 1.414 + return numberRegex.test(k); 1.415 + } 1.416 + function isUpperCase(k) { 1.417 + return (/^[A-Z]$/).test(k); 1.418 + } 1.419 + function isWhiteSpaceString(k) { 1.420 + return (/^\s*$/).test(k); 1.421 + } 1.422 + function inArray(val, arr) { 1.423 + for (var i = 0; i < arr.length; i++) { 1.424 + if (arr[i] == val) { 1.425 + return true; 1.426 + } 1.427 + } 1.428 + return false; 1.429 + } 1.430 + 1.431 + var options = {}; 1.432 + function defineOption(name, defaultValue, type) { 1.433 + if (defaultValue === undefined) { throw Error('defaultValue is required'); } 1.434 + if (!type) { type = 'string'; } 1.435 + options[name] = { 1.436 + type: type, 1.437 + defaultValue: defaultValue 1.438 + }; 1.439 + setOption(name, defaultValue); 1.440 + } 1.441 + 1.442 + function setOption(name, value) { 1.443 + var option = options[name]; 1.444 + if (!option) { 1.445 + throw Error('Unknown option: ' + name); 1.446 + } 1.447 + if (option.type == 'boolean') { 1.448 + if (value && value !== true) { 1.449 + throw Error('Invalid argument: ' + name + '=' + value); 1.450 + } else if (value !== false) { 1.451 + // Boolean options are set to true if value is not defined. 1.452 + value = true; 1.453 + } 1.454 + } 1.455 + option.value = option.type == 'boolean' ? !!value : value; 1.456 + } 1.457 + 1.458 + function getOption(name) { 1.459 + var option = options[name]; 1.460 + if (!option) { 1.461 + throw Error('Unknown option: ' + name); 1.462 + } 1.463 + return option.value; 1.464 + } 1.465 + 1.466 + var createCircularJumpList = function() { 1.467 + var size = 100; 1.468 + var pointer = -1; 1.469 + var head = 0; 1.470 + var tail = 0; 1.471 + var buffer = new Array(size); 1.472 + function add(cm, oldCur, newCur) { 1.473 + var current = pointer % size; 1.474 + var curMark = buffer[current]; 1.475 + function useNextSlot(cursor) { 1.476 + var next = ++pointer % size; 1.477 + var trashMark = buffer[next]; 1.478 + if (trashMark) { 1.479 + trashMark.clear(); 1.480 + } 1.481 + buffer[next] = cm.setBookmark(cursor); 1.482 + } 1.483 + if (curMark) { 1.484 + var markPos = curMark.find(); 1.485 + // avoid recording redundant cursor position 1.486 + if (markPos && !cursorEqual(markPos, oldCur)) { 1.487 + useNextSlot(oldCur); 1.488 + } 1.489 + } else { 1.490 + useNextSlot(oldCur); 1.491 + } 1.492 + useNextSlot(newCur); 1.493 + head = pointer; 1.494 + tail = pointer - size + 1; 1.495 + if (tail < 0) { 1.496 + tail = 0; 1.497 + } 1.498 + } 1.499 + function move(cm, offset) { 1.500 + pointer += offset; 1.501 + if (pointer > head) { 1.502 + pointer = head; 1.503 + } else if (pointer < tail) { 1.504 + pointer = tail; 1.505 + } 1.506 + var mark = buffer[(size + pointer) % size]; 1.507 + // skip marks that are temporarily removed from text buffer 1.508 + if (mark && !mark.find()) { 1.509 + var inc = offset > 0 ? 1 : -1; 1.510 + var newCur; 1.511 + var oldCur = cm.getCursor(); 1.512 + do { 1.513 + pointer += inc; 1.514 + mark = buffer[(size + pointer) % size]; 1.515 + // skip marks that are the same as current position 1.516 + if (mark && 1.517 + (newCur = mark.find()) && 1.518 + !cursorEqual(oldCur, newCur)) { 1.519 + break; 1.520 + } 1.521 + } while (pointer < head && pointer > tail); 1.522 + } 1.523 + return mark; 1.524 + } 1.525 + return { 1.526 + cachedCursor: undefined, //used for # and * jumps 1.527 + add: add, 1.528 + move: move 1.529 + }; 1.530 + }; 1.531 + 1.532 + // Returns an object to track the changes associated insert mode. It 1.533 + // clones the object that is passed in, or creates an empty object one if 1.534 + // none is provided. 1.535 + var createInsertModeChanges = function(c) { 1.536 + if (c) { 1.537 + // Copy construction 1.538 + return { 1.539 + changes: c.changes, 1.540 + expectCursorActivityForChange: c.expectCursorActivityForChange 1.541 + }; 1.542 + } 1.543 + return { 1.544 + // Change list 1.545 + changes: [], 1.546 + // Set to true on change, false on cursorActivity. 1.547 + expectCursorActivityForChange: false 1.548 + }; 1.549 + }; 1.550 + 1.551 + function MacroModeState() { 1.552 + this.latestRegister = undefined; 1.553 + this.isPlaying = false; 1.554 + this.isRecording = false; 1.555 + this.onRecordingDone = undefined; 1.556 + this.lastInsertModeChanges = createInsertModeChanges(); 1.557 + } 1.558 + MacroModeState.prototype = { 1.559 + exitMacroRecordMode: function() { 1.560 + var macroModeState = vimGlobalState.macroModeState; 1.561 + macroModeState.onRecordingDone(); // close dialog 1.562 + macroModeState.onRecordingDone = undefined; 1.563 + macroModeState.isRecording = false; 1.564 + }, 1.565 + enterMacroRecordMode: function(cm, registerName) { 1.566 + var register = 1.567 + vimGlobalState.registerController.getRegister(registerName); 1.568 + if (register) { 1.569 + register.clear(); 1.570 + this.latestRegister = registerName; 1.571 + this.onRecordingDone = cm.openDialog( 1.572 + '(recording)['+registerName+']', null, {bottom:true}); 1.573 + this.isRecording = true; 1.574 + } 1.575 + } 1.576 + }; 1.577 + 1.578 + function maybeInitVimState(cm) { 1.579 + if (!cm.state.vim) { 1.580 + // Store instance state in the CodeMirror object. 1.581 + cm.state.vim = { 1.582 + inputState: new InputState(), 1.583 + // Vim's input state that triggered the last edit, used to repeat 1.584 + // motions and operators with '.'. 1.585 + lastEditInputState: undefined, 1.586 + // Vim's action command before the last edit, used to repeat actions 1.587 + // with '.' and insert mode repeat. 1.588 + lastEditActionCommand: undefined, 1.589 + // When using jk for navigation, if you move from a longer line to a 1.590 + // shorter line, the cursor may clip to the end of the shorter line. 1.591 + // If j is pressed again and cursor goes to the next line, the 1.592 + // cursor should go back to its horizontal position on the longer 1.593 + // line if it can. This is to keep track of the horizontal position. 1.594 + lastHPos: -1, 1.595 + // Doing the same with screen-position for gj/gk 1.596 + lastHSPos: -1, 1.597 + // The last motion command run. Cleared if a non-motion command gets 1.598 + // executed in between. 1.599 + lastMotion: null, 1.600 + marks: {}, 1.601 + insertMode: false, 1.602 + // Repeat count for changes made in insert mode, triggered by key 1.603 + // sequences like 3,i. Only exists when insertMode is true. 1.604 + insertModeRepeat: undefined, 1.605 + visualMode: false, 1.606 + // If we are in visual line mode. No effect if visualMode is false. 1.607 + visualLine: false, 1.608 + lastSelection: null 1.609 + }; 1.610 + } 1.611 + return cm.state.vim; 1.612 + } 1.613 + var vimGlobalState; 1.614 + function resetVimGlobalState() { 1.615 + vimGlobalState = { 1.616 + // The current search query. 1.617 + searchQuery: null, 1.618 + // Whether we are searching backwards. 1.619 + searchIsReversed: false, 1.620 + jumpList: createCircularJumpList(), 1.621 + macroModeState: new MacroModeState, 1.622 + // Recording latest f, t, F or T motion command. 1.623 + lastChararacterSearch: {increment:0, forward:true, selectedCharacter:''}, 1.624 + registerController: new RegisterController({}) 1.625 + }; 1.626 + for (var optionName in options) { 1.627 + var option = options[optionName]; 1.628 + option.value = option.defaultValue; 1.629 + } 1.630 + } 1.631 + 1.632 + var vimApi= { 1.633 + buildKeyMap: function() { 1.634 + // TODO: Convert keymap into dictionary format for fast lookup. 1.635 + }, 1.636 + // Testing hook, though it might be useful to expose the register 1.637 + // controller anyways. 1.638 + getRegisterController: function() { 1.639 + return vimGlobalState.registerController; 1.640 + }, 1.641 + // Testing hook. 1.642 + resetVimGlobalState_: resetVimGlobalState, 1.643 + 1.644 + // Testing hook. 1.645 + getVimGlobalState_: function() { 1.646 + return vimGlobalState; 1.647 + }, 1.648 + 1.649 + // Testing hook. 1.650 + maybeInitVimState_: maybeInitVimState, 1.651 + 1.652 + InsertModeKey: InsertModeKey, 1.653 + map: function(lhs, rhs, ctx) { 1.654 + // Add user defined key bindings. 1.655 + exCommandDispatcher.map(lhs, rhs, ctx); 1.656 + }, 1.657 + setOption: setOption, 1.658 + getOption: getOption, 1.659 + defineOption: defineOption, 1.660 + defineEx: function(name, prefix, func){ 1.661 + if (name.indexOf(prefix) !== 0) { 1.662 + throw new Error('(Vim.defineEx) "'+prefix+'" is not a prefix of "'+name+'", command not registered'); 1.663 + } 1.664 + exCommands[name]=func; 1.665 + exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'}; 1.666 + }, 1.667 + // This is the outermost function called by CodeMirror, after keys have 1.668 + // been mapped to their Vim equivalents. 1.669 + handleKey: function(cm, key) { 1.670 + var command; 1.671 + var vim = maybeInitVimState(cm); 1.672 + var macroModeState = vimGlobalState.macroModeState; 1.673 + if (macroModeState.isRecording) { 1.674 + if (key == 'q') { 1.675 + macroModeState.exitMacroRecordMode(); 1.676 + vim.inputState = new InputState(); 1.677 + return; 1.678 + } 1.679 + } 1.680 + if (key == '<Esc>') { 1.681 + // Clear input state and get back to normal mode. 1.682 + vim.inputState = new InputState(); 1.683 + if (vim.visualMode) { 1.684 + exitVisualMode(cm); 1.685 + } 1.686 + return; 1.687 + } 1.688 + // Enter visual mode when the mouse selects text. 1.689 + if (!vim.visualMode && 1.690 + !cursorEqual(cm.getCursor('head'), cm.getCursor('anchor'))) { 1.691 + vim.visualMode = true; 1.692 + vim.visualLine = false; 1.693 + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"}); 1.694 + cm.on('mousedown', exitVisualMode); 1.695 + } 1.696 + if (key != '0' || (key == '0' && vim.inputState.getRepeat() === 0)) { 1.697 + // Have to special case 0 since it's both a motion and a number. 1.698 + command = commandDispatcher.matchCommand(key, defaultKeymap, vim); 1.699 + } 1.700 + if (!command) { 1.701 + if (isNumber(key)) { 1.702 + // Increment count unless count is 0 and key is 0. 1.703 + vim.inputState.pushRepeatDigit(key); 1.704 + } 1.705 + if (macroModeState.isRecording) { 1.706 + logKey(macroModeState, key); 1.707 + } 1.708 + return; 1.709 + } 1.710 + if (command.type == 'keyToKey') { 1.711 + // TODO: prevent infinite recursion. 1.712 + for (var i = 0; i < command.toKeys.length; i++) { 1.713 + this.handleKey(cm, command.toKeys[i]); 1.714 + } 1.715 + } else { 1.716 + if (macroModeState.isRecording) { 1.717 + logKey(macroModeState, key); 1.718 + } 1.719 + commandDispatcher.processCommand(cm, vim, command); 1.720 + } 1.721 + }, 1.722 + handleEx: function(cm, input) { 1.723 + exCommandDispatcher.processCommand(cm, input); 1.724 + } 1.725 + }; 1.726 + 1.727 + // Represents the current input state. 1.728 + function InputState() { 1.729 + this.prefixRepeat = []; 1.730 + this.motionRepeat = []; 1.731 + 1.732 + this.operator = null; 1.733 + this.operatorArgs = null; 1.734 + this.motion = null; 1.735 + this.motionArgs = null; 1.736 + this.keyBuffer = []; // For matching multi-key commands. 1.737 + this.registerName = null; // Defaults to the unnamed register. 1.738 + } 1.739 + InputState.prototype.pushRepeatDigit = function(n) { 1.740 + if (!this.operator) { 1.741 + this.prefixRepeat = this.prefixRepeat.concat(n); 1.742 + } else { 1.743 + this.motionRepeat = this.motionRepeat.concat(n); 1.744 + } 1.745 + }; 1.746 + InputState.prototype.getRepeat = function() { 1.747 + var repeat = 0; 1.748 + if (this.prefixRepeat.length > 0 || this.motionRepeat.length > 0) { 1.749 + repeat = 1; 1.750 + if (this.prefixRepeat.length > 0) { 1.751 + repeat *= parseInt(this.prefixRepeat.join(''), 10); 1.752 + } 1.753 + if (this.motionRepeat.length > 0) { 1.754 + repeat *= parseInt(this.motionRepeat.join(''), 10); 1.755 + } 1.756 + } 1.757 + return repeat; 1.758 + }; 1.759 + 1.760 + /* 1.761 + * Register stores information about copy and paste registers. Besides 1.762 + * text, a register must store whether it is linewise (i.e., when it is 1.763 + * pasted, should it insert itself into a new line, or should the text be 1.764 + * inserted at the cursor position.) 1.765 + */ 1.766 + function Register(text, linewise) { 1.767 + this.clear(); 1.768 + this.keyBuffer = [text || '']; 1.769 + this.insertModeChanges = []; 1.770 + this.linewise = !!linewise; 1.771 + } 1.772 + Register.prototype = { 1.773 + setText: function(text, linewise) { 1.774 + this.keyBuffer = [text || '']; 1.775 + this.linewise = !!linewise; 1.776 + }, 1.777 + pushText: function(text, linewise) { 1.778 + // if this register has ever been set to linewise, use linewise. 1.779 + if (linewise || this.linewise) { 1.780 + this.keyBuffer.push('\n'); 1.781 + this.linewise = true; 1.782 + } 1.783 + this.keyBuffer.push(text); 1.784 + }, 1.785 + pushInsertModeChanges: function(changes) { 1.786 + this.insertModeChanges.push(createInsertModeChanges(changes)); 1.787 + }, 1.788 + clear: function() { 1.789 + this.keyBuffer = []; 1.790 + this.insertModeChanges = []; 1.791 + this.linewise = false; 1.792 + }, 1.793 + toString: function() { 1.794 + return this.keyBuffer.join(''); 1.795 + } 1.796 + }; 1.797 + 1.798 + /* 1.799 + * vim registers allow you to keep many independent copy and paste buffers. 1.800 + * See http://usevim.com/2012/04/13/registers/ for an introduction. 1.801 + * 1.802 + * RegisterController keeps the state of all the registers. An initial 1.803 + * state may be passed in. The unnamed register '"' will always be 1.804 + * overridden. 1.805 + */ 1.806 + function RegisterController(registers) { 1.807 + this.registers = registers; 1.808 + this.unnamedRegister = registers['"'] = new Register(); 1.809 + } 1.810 + RegisterController.prototype = { 1.811 + pushText: function(registerName, operator, text, linewise) { 1.812 + if (linewise && text.charAt(0) == '\n') { 1.813 + text = text.slice(1) + '\n'; 1.814 + } 1.815 + if (linewise && text.charAt(text.length - 1) !== '\n'){ 1.816 + text += '\n'; 1.817 + } 1.818 + // Lowercase and uppercase registers refer to the same register. 1.819 + // Uppercase just means append. 1.820 + var register = this.isValidRegister(registerName) ? 1.821 + this.getRegister(registerName) : null; 1.822 + // if no register/an invalid register was specified, things go to the 1.823 + // default registers 1.824 + if (!register) { 1.825 + switch (operator) { 1.826 + case 'yank': 1.827 + // The 0 register contains the text from the most recent yank. 1.828 + this.registers['0'] = new Register(text, linewise); 1.829 + break; 1.830 + case 'delete': 1.831 + case 'change': 1.832 + if (text.indexOf('\n') == -1) { 1.833 + // Delete less than 1 line. Update the small delete register. 1.834 + this.registers['-'] = new Register(text, linewise); 1.835 + } else { 1.836 + // Shift down the contents of the numbered registers and put the 1.837 + // deleted text into register 1. 1.838 + this.shiftNumericRegisters_(); 1.839 + this.registers['1'] = new Register(text, linewise); 1.840 + } 1.841 + break; 1.842 + } 1.843 + // Make sure the unnamed register is set to what just happened 1.844 + this.unnamedRegister.setText(text, linewise); 1.845 + return; 1.846 + } 1.847 + 1.848 + // If we've gotten to this point, we've actually specified a register 1.849 + var append = isUpperCase(registerName); 1.850 + if (append) { 1.851 + register.append(text, linewise); 1.852 + // The unnamed register always has the same value as the last used 1.853 + // register. 1.854 + this.unnamedRegister.append(text, linewise); 1.855 + } else { 1.856 + register.setText(text, linewise); 1.857 + this.unnamedRegister.setText(text, linewise); 1.858 + } 1.859 + }, 1.860 + // Gets the register named @name. If one of @name doesn't already exist, 1.861 + // create it. If @name is invalid, return the unnamedRegister. 1.862 + getRegister: function(name) { 1.863 + if (!this.isValidRegister(name)) { 1.864 + return this.unnamedRegister; 1.865 + } 1.866 + name = name.toLowerCase(); 1.867 + if (!this.registers[name]) { 1.868 + this.registers[name] = new Register(); 1.869 + } 1.870 + return this.registers[name]; 1.871 + }, 1.872 + isValidRegister: function(name) { 1.873 + return name && inArray(name, validRegisters); 1.874 + }, 1.875 + shiftNumericRegisters_: function() { 1.876 + for (var i = 9; i >= 2; i--) { 1.877 + this.registers[i] = this.getRegister('' + (i - 1)); 1.878 + } 1.879 + } 1.880 + }; 1.881 + 1.882 + var commandDispatcher = { 1.883 + matchCommand: function(key, keyMap, vim) { 1.884 + var inputState = vim.inputState; 1.885 + var keys = inputState.keyBuffer.concat(key); 1.886 + var matchedCommands = []; 1.887 + var selectedCharacter; 1.888 + for (var i = 0; i < keyMap.length; i++) { 1.889 + var command = keyMap[i]; 1.890 + if (matchKeysPartial(keys, command.keys)) { 1.891 + if (inputState.operator && command.type == 'action') { 1.892 + // Ignore matched action commands after an operator. Operators 1.893 + // only operate on motions. This check is really for text 1.894 + // objects since aW, a[ etcs conflicts with a. 1.895 + continue; 1.896 + } 1.897 + // Match commands that take <character> as an argument. 1.898 + if (command.keys[keys.length - 1] == 'character') { 1.899 + selectedCharacter = keys[keys.length - 1]; 1.900 + if (selectedCharacter.length>1){ 1.901 + switch(selectedCharacter){ 1.902 + case '<CR>': 1.903 + selectedCharacter='\n'; 1.904 + break; 1.905 + case '<Space>': 1.906 + selectedCharacter=' '; 1.907 + break; 1.908 + default: 1.909 + continue; 1.910 + } 1.911 + } 1.912 + } 1.913 + // Add the command to the list of matched commands. Choose the best 1.914 + // command later. 1.915 + matchedCommands.push(command); 1.916 + } 1.917 + } 1.918 + 1.919 + // Returns the command if it is a full match, or null if not. 1.920 + function getFullyMatchedCommandOrNull(command) { 1.921 + if (keys.length < command.keys.length) { 1.922 + // Matches part of a multi-key command. Buffer and wait for next 1.923 + // stroke. 1.924 + inputState.keyBuffer.push(key); 1.925 + return null; 1.926 + } else { 1.927 + if (command.keys[keys.length - 1] == 'character') { 1.928 + inputState.selectedCharacter = selectedCharacter; 1.929 + } 1.930 + // Clear the buffer since a full match was found. 1.931 + inputState.keyBuffer = []; 1.932 + return command; 1.933 + } 1.934 + } 1.935 + 1.936 + if (!matchedCommands.length) { 1.937 + // Clear the buffer since there were no matches. 1.938 + inputState.keyBuffer = []; 1.939 + return null; 1.940 + } else if (matchedCommands.length == 1) { 1.941 + return getFullyMatchedCommandOrNull(matchedCommands[0]); 1.942 + } else { 1.943 + // Find the best match in the list of matchedCommands. 1.944 + var context = vim.visualMode ? 'visual' : 'normal'; 1.945 + var bestMatch; // Default to first in the list. 1.946 + for (var i = 0; i < matchedCommands.length; i++) { 1.947 + var current = matchedCommands[i]; 1.948 + if (current.context == context) { 1.949 + bestMatch = current; 1.950 + break; 1.951 + } else if (!bestMatch && !current.context) { 1.952 + // Only set an imperfect match to best match if no best match is 1.953 + // set and the imperfect match is not restricted to another 1.954 + // context. 1.955 + bestMatch = current; 1.956 + } 1.957 + } 1.958 + return getFullyMatchedCommandOrNull(bestMatch); 1.959 + } 1.960 + }, 1.961 + processCommand: function(cm, vim, command) { 1.962 + vim.inputState.repeatOverride = command.repeatOverride; 1.963 + switch (command.type) { 1.964 + case 'motion': 1.965 + this.processMotion(cm, vim, command); 1.966 + break; 1.967 + case 'operator': 1.968 + this.processOperator(cm, vim, command); 1.969 + break; 1.970 + case 'operatorMotion': 1.971 + this.processOperatorMotion(cm, vim, command); 1.972 + break; 1.973 + case 'action': 1.974 + this.processAction(cm, vim, command); 1.975 + break; 1.976 + case 'search': 1.977 + this.processSearch(cm, vim, command); 1.978 + break; 1.979 + case 'ex': 1.980 + case 'keyToEx': 1.981 + this.processEx(cm, vim, command); 1.982 + break; 1.983 + default: 1.984 + break; 1.985 + } 1.986 + }, 1.987 + processMotion: function(cm, vim, command) { 1.988 + vim.inputState.motion = command.motion; 1.989 + vim.inputState.motionArgs = copyArgs(command.motionArgs); 1.990 + this.evalInput(cm, vim); 1.991 + }, 1.992 + processOperator: function(cm, vim, command) { 1.993 + var inputState = vim.inputState; 1.994 + if (inputState.operator) { 1.995 + if (inputState.operator == command.operator) { 1.996 + // Typing an operator twice like 'dd' makes the operator operate 1.997 + // linewise 1.998 + inputState.motion = 'expandToLine'; 1.999 + inputState.motionArgs = { linewise: true }; 1.1000 + this.evalInput(cm, vim); 1.1001 + return; 1.1002 + } else { 1.1003 + // 2 different operators in a row doesn't make sense. 1.1004 + vim.inputState = new InputState(); 1.1005 + } 1.1006 + } 1.1007 + inputState.operator = command.operator; 1.1008 + inputState.operatorArgs = copyArgs(command.operatorArgs); 1.1009 + if (vim.visualMode) { 1.1010 + // Operating on a selection in visual mode. We don't need a motion. 1.1011 + this.evalInput(cm, vim); 1.1012 + } 1.1013 + }, 1.1014 + processOperatorMotion: function(cm, vim, command) { 1.1015 + var visualMode = vim.visualMode; 1.1016 + var operatorMotionArgs = copyArgs(command.operatorMotionArgs); 1.1017 + if (operatorMotionArgs) { 1.1018 + // Operator motions may have special behavior in visual mode. 1.1019 + if (visualMode && operatorMotionArgs.visualLine) { 1.1020 + vim.visualLine = true; 1.1021 + } 1.1022 + } 1.1023 + this.processOperator(cm, vim, command); 1.1024 + if (!visualMode) { 1.1025 + this.processMotion(cm, vim, command); 1.1026 + } 1.1027 + }, 1.1028 + processAction: function(cm, vim, command) { 1.1029 + var inputState = vim.inputState; 1.1030 + var repeat = inputState.getRepeat(); 1.1031 + var repeatIsExplicit = !!repeat; 1.1032 + var actionArgs = copyArgs(command.actionArgs) || {}; 1.1033 + if (inputState.selectedCharacter) { 1.1034 + actionArgs.selectedCharacter = inputState.selectedCharacter; 1.1035 + } 1.1036 + // Actions may or may not have motions and operators. Do these first. 1.1037 + if (command.operator) { 1.1038 + this.processOperator(cm, vim, command); 1.1039 + } 1.1040 + if (command.motion) { 1.1041 + this.processMotion(cm, vim, command); 1.1042 + } 1.1043 + if (command.motion || command.operator) { 1.1044 + this.evalInput(cm, vim); 1.1045 + } 1.1046 + actionArgs.repeat = repeat || 1; 1.1047 + actionArgs.repeatIsExplicit = repeatIsExplicit; 1.1048 + actionArgs.registerName = inputState.registerName; 1.1049 + vim.inputState = new InputState(); 1.1050 + vim.lastMotion = null; 1.1051 + if (command.isEdit) { 1.1052 + this.recordLastEdit(vim, inputState, command); 1.1053 + } 1.1054 + actions[command.action](cm, actionArgs, vim); 1.1055 + }, 1.1056 + processSearch: function(cm, vim, command) { 1.1057 + if (!cm.getSearchCursor) { 1.1058 + // Search depends on SearchCursor. 1.1059 + return; 1.1060 + } 1.1061 + var forward = command.searchArgs.forward; 1.1062 + getSearchState(cm).setReversed(!forward); 1.1063 + var promptPrefix = (forward) ? '/' : '?'; 1.1064 + var originalQuery = getSearchState(cm).getQuery(); 1.1065 + var originalScrollPos = cm.getScrollInfo(); 1.1066 + function handleQuery(query, ignoreCase, smartCase) { 1.1067 + try { 1.1068 + updateSearchQuery(cm, query, ignoreCase, smartCase); 1.1069 + } catch (e) { 1.1070 + showConfirm(cm, 'Invalid regex: ' + query); 1.1071 + return; 1.1072 + } 1.1073 + commandDispatcher.processMotion(cm, vim, { 1.1074 + type: 'motion', 1.1075 + motion: 'findNext', 1.1076 + motionArgs: { forward: true, toJumplist: command.searchArgs.toJumplist } 1.1077 + }); 1.1078 + } 1.1079 + function onPromptClose(query) { 1.1080 + cm.scrollTo(originalScrollPos.left, originalScrollPos.top); 1.1081 + handleQuery(query, true /** ignoreCase */, true /** smartCase */); 1.1082 + } 1.1083 + function onPromptKeyUp(_e, query) { 1.1084 + var parsedQuery; 1.1085 + try { 1.1086 + parsedQuery = updateSearchQuery(cm, query, 1.1087 + true /** ignoreCase */, true /** smartCase */); 1.1088 + } catch (e) { 1.1089 + // Swallow bad regexes for incremental search. 1.1090 + } 1.1091 + if (parsedQuery) { 1.1092 + cm.scrollIntoView(findNext(cm, !forward, parsedQuery), 30); 1.1093 + } else { 1.1094 + clearSearchHighlight(cm); 1.1095 + cm.scrollTo(originalScrollPos.left, originalScrollPos.top); 1.1096 + } 1.1097 + } 1.1098 + function onPromptKeyDown(e, _query, close) { 1.1099 + var keyName = CodeMirror.keyName(e); 1.1100 + if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') { 1.1101 + updateSearchQuery(cm, originalQuery); 1.1102 + clearSearchHighlight(cm); 1.1103 + cm.scrollTo(originalScrollPos.left, originalScrollPos.top); 1.1104 + 1.1105 + CodeMirror.e_stop(e); 1.1106 + close(); 1.1107 + cm.focus(); 1.1108 + } 1.1109 + } 1.1110 + switch (command.searchArgs.querySrc) { 1.1111 + case 'prompt': 1.1112 + showPrompt(cm, { 1.1113 + onClose: onPromptClose, 1.1114 + prefix: promptPrefix, 1.1115 + desc: searchPromptDesc, 1.1116 + onKeyUp: onPromptKeyUp, 1.1117 + onKeyDown: onPromptKeyDown 1.1118 + }); 1.1119 + break; 1.1120 + case 'wordUnderCursor': 1.1121 + var word = expandWordUnderCursor(cm, false /** inclusive */, 1.1122 + true /** forward */, false /** bigWord */, 1.1123 + true /** noSymbol */); 1.1124 + var isKeyword = true; 1.1125 + if (!word) { 1.1126 + word = expandWordUnderCursor(cm, false /** inclusive */, 1.1127 + true /** forward */, false /** bigWord */, 1.1128 + false /** noSymbol */); 1.1129 + isKeyword = false; 1.1130 + } 1.1131 + if (!word) { 1.1132 + return; 1.1133 + } 1.1134 + var query = cm.getLine(word.start.line).substring(word.start.ch, 1.1135 + word.end.ch); 1.1136 + if (isKeyword) { 1.1137 + query = '\\b' + query + '\\b'; 1.1138 + } else { 1.1139 + query = escapeRegex(query); 1.1140 + } 1.1141 + 1.1142 + // cachedCursor is used to save the old position of the cursor 1.1143 + // when * or # causes vim to seek for the nearest word and shift 1.1144 + // the cursor before entering the motion. 1.1145 + vimGlobalState.jumpList.cachedCursor = cm.getCursor(); 1.1146 + cm.setCursor(word.start); 1.1147 + 1.1148 + handleQuery(query, true /** ignoreCase */, false /** smartCase */); 1.1149 + break; 1.1150 + } 1.1151 + }, 1.1152 + processEx: function(cm, vim, command) { 1.1153 + function onPromptClose(input) { 1.1154 + // Give the prompt some time to close so that if processCommand shows 1.1155 + // an error, the elements don't overlap. 1.1156 + exCommandDispatcher.processCommand(cm, input); 1.1157 + } 1.1158 + function onPromptKeyDown(e, _input, close) { 1.1159 + var keyName = CodeMirror.keyName(e); 1.1160 + if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') { 1.1161 + CodeMirror.e_stop(e); 1.1162 + close(); 1.1163 + cm.focus(); 1.1164 + } 1.1165 + } 1.1166 + if (command.type == 'keyToEx') { 1.1167 + // Handle user defined Ex to Ex mappings 1.1168 + exCommandDispatcher.processCommand(cm, command.exArgs.input); 1.1169 + } else { 1.1170 + if (vim.visualMode) { 1.1171 + showPrompt(cm, { onClose: onPromptClose, prefix: ':', value: '\'<,\'>', 1.1172 + onKeyDown: onPromptKeyDown}); 1.1173 + } else { 1.1174 + showPrompt(cm, { onClose: onPromptClose, prefix: ':', 1.1175 + onKeyDown: onPromptKeyDown}); 1.1176 + } 1.1177 + } 1.1178 + }, 1.1179 + evalInput: function(cm, vim) { 1.1180 + // If the motion comand is set, execute both the operator and motion. 1.1181 + // Otherwise return. 1.1182 + var inputState = vim.inputState; 1.1183 + var motion = inputState.motion; 1.1184 + var motionArgs = inputState.motionArgs || {}; 1.1185 + var operator = inputState.operator; 1.1186 + var operatorArgs = inputState.operatorArgs || {}; 1.1187 + var registerName = inputState.registerName; 1.1188 + var selectionEnd = copyCursor(cm.getCursor('head')); 1.1189 + var selectionStart = copyCursor(cm.getCursor('anchor')); 1.1190 + // The difference between cur and selection cursors are that cur is 1.1191 + // being operated on and ignores that there is a selection. 1.1192 + var curStart = copyCursor(selectionEnd); 1.1193 + var curOriginal = copyCursor(curStart); 1.1194 + var curEnd; 1.1195 + var repeat; 1.1196 + if (operator) { 1.1197 + this.recordLastEdit(vim, inputState); 1.1198 + } 1.1199 + if (inputState.repeatOverride !== undefined) { 1.1200 + // If repeatOverride is specified, that takes precedence over the 1.1201 + // input state's repeat. Used by Ex mode and can be user defined. 1.1202 + repeat = inputState.repeatOverride; 1.1203 + } else { 1.1204 + repeat = inputState.getRepeat(); 1.1205 + } 1.1206 + if (repeat > 0 && motionArgs.explicitRepeat) { 1.1207 + motionArgs.repeatIsExplicit = true; 1.1208 + } else if (motionArgs.noRepeat || 1.1209 + (!motionArgs.explicitRepeat && repeat === 0)) { 1.1210 + repeat = 1; 1.1211 + motionArgs.repeatIsExplicit = false; 1.1212 + } 1.1213 + if (inputState.selectedCharacter) { 1.1214 + // If there is a character input, stick it in all of the arg arrays. 1.1215 + motionArgs.selectedCharacter = operatorArgs.selectedCharacter = 1.1216 + inputState.selectedCharacter; 1.1217 + } 1.1218 + motionArgs.repeat = repeat; 1.1219 + vim.inputState = new InputState(); 1.1220 + if (motion) { 1.1221 + var motionResult = motions[motion](cm, motionArgs, vim); 1.1222 + vim.lastMotion = motions[motion]; 1.1223 + if (!motionResult) { 1.1224 + return; 1.1225 + } 1.1226 + if (motionArgs.toJumplist) { 1.1227 + var jumpList = vimGlobalState.jumpList; 1.1228 + // if the current motion is # or *, use cachedCursor 1.1229 + var cachedCursor = jumpList.cachedCursor; 1.1230 + if (cachedCursor) { 1.1231 + recordJumpPosition(cm, cachedCursor, motionResult); 1.1232 + delete jumpList.cachedCursor; 1.1233 + } else { 1.1234 + recordJumpPosition(cm, curOriginal, motionResult); 1.1235 + } 1.1236 + } 1.1237 + if (motionResult instanceof Array) { 1.1238 + curStart = motionResult[0]; 1.1239 + curEnd = motionResult[1]; 1.1240 + } else { 1.1241 + curEnd = motionResult; 1.1242 + } 1.1243 + // TODO: Handle null returns from motion commands better. 1.1244 + if (!curEnd) { 1.1245 + curEnd = Pos(curStart.line, curStart.ch); 1.1246 + } 1.1247 + if (vim.visualMode) { 1.1248 + // Check if the selection crossed over itself. Will need to shift 1.1249 + // the start point if that happened. 1.1250 + if (cursorIsBefore(selectionStart, selectionEnd) && 1.1251 + (cursorEqual(selectionStart, curEnd) || 1.1252 + cursorIsBefore(curEnd, selectionStart))) { 1.1253 + // The end of the selection has moved from after the start to 1.1254 + // before the start. We will shift the start right by 1. 1.1255 + selectionStart.ch += 1; 1.1256 + } else if (cursorIsBefore(selectionEnd, selectionStart) && 1.1257 + (cursorEqual(selectionStart, curEnd) || 1.1258 + cursorIsBefore(selectionStart, curEnd))) { 1.1259 + // The opposite happened. We will shift the start left by 1. 1.1260 + selectionStart.ch -= 1; 1.1261 + } 1.1262 + selectionEnd = curEnd; 1.1263 + selectionStart = (motionResult instanceof Array) ? curStart : selectionStart; 1.1264 + if (vim.visualLine) { 1.1265 + if (cursorIsBefore(selectionStart, selectionEnd)) { 1.1266 + selectionStart.ch = 0; 1.1267 + 1.1268 + var lastLine = cm.lastLine(); 1.1269 + if (selectionEnd.line > lastLine) { 1.1270 + selectionEnd.line = lastLine; 1.1271 + } 1.1272 + selectionEnd.ch = lineLength(cm, selectionEnd.line); 1.1273 + } else { 1.1274 + selectionEnd.ch = 0; 1.1275 + selectionStart.ch = lineLength(cm, selectionStart.line); 1.1276 + } 1.1277 + } 1.1278 + cm.setSelection(selectionStart, selectionEnd); 1.1279 + updateMark(cm, vim, '<', 1.1280 + cursorIsBefore(selectionStart, selectionEnd) ? selectionStart 1.1281 + : selectionEnd); 1.1282 + updateMark(cm, vim, '>', 1.1283 + cursorIsBefore(selectionStart, selectionEnd) ? selectionEnd 1.1284 + : selectionStart); 1.1285 + } else if (!operator) { 1.1286 + curEnd = clipCursorToContent(cm, curEnd); 1.1287 + cm.setCursor(curEnd.line, curEnd.ch); 1.1288 + } 1.1289 + } 1.1290 + 1.1291 + if (operator) { 1.1292 + var inverted = false; 1.1293 + vim.lastMotion = null; 1.1294 + operatorArgs.repeat = repeat; // Indent in visual mode needs this. 1.1295 + if (vim.visualMode) { 1.1296 + curStart = selectionStart; 1.1297 + curEnd = selectionEnd; 1.1298 + motionArgs.inclusive = true; 1.1299 + } 1.1300 + // Swap start and end if motion was backward. 1.1301 + if (cursorIsBefore(curEnd, curStart)) { 1.1302 + var tmp = curStart; 1.1303 + curStart = curEnd; 1.1304 + curEnd = tmp; 1.1305 + inverted = true; 1.1306 + } 1.1307 + if (motionArgs.inclusive && !(vim.visualMode && inverted)) { 1.1308 + // Move the selection end one to the right to include the last 1.1309 + // character. 1.1310 + curEnd.ch++; 1.1311 + } 1.1312 + var linewise = motionArgs.linewise || 1.1313 + (vim.visualMode && vim.visualLine); 1.1314 + if (linewise) { 1.1315 + // Expand selection to entire line. 1.1316 + expandSelectionToLine(cm, curStart, curEnd); 1.1317 + } else if (motionArgs.forward) { 1.1318 + // Clip to trailing newlines only if the motion goes forward. 1.1319 + clipToLine(cm, curStart, curEnd); 1.1320 + } 1.1321 + operatorArgs.registerName = registerName; 1.1322 + // Keep track of linewise as it affects how paste and change behave. 1.1323 + operatorArgs.linewise = linewise; 1.1324 + operators[operator](cm, operatorArgs, vim, curStart, 1.1325 + curEnd, curOriginal); 1.1326 + if (vim.visualMode) { 1.1327 + exitVisualMode(cm); 1.1328 + } 1.1329 + } 1.1330 + }, 1.1331 + recordLastEdit: function(vim, inputState, actionCommand) { 1.1332 + var macroModeState = vimGlobalState.macroModeState; 1.1333 + if (macroModeState.isPlaying) { return; } 1.1334 + vim.lastEditInputState = inputState; 1.1335 + vim.lastEditActionCommand = actionCommand; 1.1336 + macroModeState.lastInsertModeChanges.changes = []; 1.1337 + macroModeState.lastInsertModeChanges.expectCursorActivityForChange = false; 1.1338 + } 1.1339 + }; 1.1340 + 1.1341 + /** 1.1342 + * typedef {Object{line:number,ch:number}} Cursor An object containing the 1.1343 + * position of the cursor. 1.1344 + */ 1.1345 + // All of the functions below return Cursor objects. 1.1346 + var motions = { 1.1347 + moveToTopLine: function(cm, motionArgs) { 1.1348 + var line = getUserVisibleLines(cm).top + motionArgs.repeat -1; 1.1349 + return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); 1.1350 + }, 1.1351 + moveToMiddleLine: function(cm) { 1.1352 + var range = getUserVisibleLines(cm); 1.1353 + var line = Math.floor((range.top + range.bottom) * 0.5); 1.1354 + return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); 1.1355 + }, 1.1356 + moveToBottomLine: function(cm, motionArgs) { 1.1357 + var line = getUserVisibleLines(cm).bottom - motionArgs.repeat +1; 1.1358 + return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); 1.1359 + }, 1.1360 + expandToLine: function(cm, motionArgs) { 1.1361 + // Expands forward to end of line, and then to next line if repeat is 1.1362 + // >1. Does not handle backward motion! 1.1363 + var cur = cm.getCursor(); 1.1364 + return Pos(cur.line + motionArgs.repeat - 1, Infinity); 1.1365 + }, 1.1366 + findNext: function(cm, motionArgs) { 1.1367 + var state = getSearchState(cm); 1.1368 + var query = state.getQuery(); 1.1369 + if (!query) { 1.1370 + return; 1.1371 + } 1.1372 + var prev = !motionArgs.forward; 1.1373 + // If search is initiated with ? instead of /, negate direction. 1.1374 + prev = (state.isReversed()) ? !prev : prev; 1.1375 + highlightSearchMatches(cm, query); 1.1376 + return findNext(cm, prev/** prev */, query, motionArgs.repeat); 1.1377 + }, 1.1378 + goToMark: function(_cm, motionArgs, vim) { 1.1379 + var mark = vim.marks[motionArgs.selectedCharacter]; 1.1380 + if (mark) { 1.1381 + return mark.find(); 1.1382 + } 1.1383 + return null; 1.1384 + }, 1.1385 + moveToOtherHighlightedEnd: function(cm) { 1.1386 + var curEnd = copyCursor(cm.getCursor('head')); 1.1387 + var curStart = copyCursor(cm.getCursor('anchor')); 1.1388 + if (cursorIsBefore(curStart, curEnd)) { 1.1389 + curEnd.ch += 1; 1.1390 + } else if (cursorIsBefore(curEnd, curStart)) { 1.1391 + curStart.ch -= 1; 1.1392 + } 1.1393 + return ([curEnd,curStart]); 1.1394 + }, 1.1395 + jumpToMark: function(cm, motionArgs, vim) { 1.1396 + var best = cm.getCursor(); 1.1397 + for (var i = 0; i < motionArgs.repeat; i++) { 1.1398 + var cursor = best; 1.1399 + for (var key in vim.marks) { 1.1400 + if (!isLowerCase(key)) { 1.1401 + continue; 1.1402 + } 1.1403 + var mark = vim.marks[key].find(); 1.1404 + var isWrongDirection = (motionArgs.forward) ? 1.1405 + cursorIsBefore(mark, cursor) : cursorIsBefore(cursor, mark); 1.1406 + 1.1407 + if (isWrongDirection) { 1.1408 + continue; 1.1409 + } 1.1410 + if (motionArgs.linewise && (mark.line == cursor.line)) { 1.1411 + continue; 1.1412 + } 1.1413 + 1.1414 + var equal = cursorEqual(cursor, best); 1.1415 + var between = (motionArgs.forward) ? 1.1416 + cusrorIsBetween(cursor, mark, best) : 1.1417 + cusrorIsBetween(best, mark, cursor); 1.1418 + 1.1419 + if (equal || between) { 1.1420 + best = mark; 1.1421 + } 1.1422 + } 1.1423 + } 1.1424 + 1.1425 + if (motionArgs.linewise) { 1.1426 + // Vim places the cursor on the first non-whitespace character of 1.1427 + // the line if there is one, else it places the cursor at the end 1.1428 + // of the line, regardless of whether a mark was found. 1.1429 + best = Pos(best.line, findFirstNonWhiteSpaceCharacter(cm.getLine(best.line))); 1.1430 + } 1.1431 + return best; 1.1432 + }, 1.1433 + moveByCharacters: function(cm, motionArgs) { 1.1434 + var cur = cm.getCursor(); 1.1435 + var repeat = motionArgs.repeat; 1.1436 + var ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat; 1.1437 + return Pos(cur.line, ch); 1.1438 + }, 1.1439 + moveByLines: function(cm, motionArgs, vim) { 1.1440 + var cur = cm.getCursor(); 1.1441 + var endCh = cur.ch; 1.1442 + // Depending what our last motion was, we may want to do different 1.1443 + // things. If our last motion was moving vertically, we want to 1.1444 + // preserve the HPos from our last horizontal move. If our last motion 1.1445 + // was going to the end of a line, moving vertically we should go to 1.1446 + // the end of the line, etc. 1.1447 + switch (vim.lastMotion) { 1.1448 + case this.moveByLines: 1.1449 + case this.moveByDisplayLines: 1.1450 + case this.moveByScroll: 1.1451 + case this.moveToColumn: 1.1452 + case this.moveToEol: 1.1453 + endCh = vim.lastHPos; 1.1454 + break; 1.1455 + default: 1.1456 + vim.lastHPos = endCh; 1.1457 + } 1.1458 + var repeat = motionArgs.repeat+(motionArgs.repeatOffset||0); 1.1459 + var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat; 1.1460 + var first = cm.firstLine(); 1.1461 + var last = cm.lastLine(); 1.1462 + // Vim cancels linewise motions that start on an edge and move beyond 1.1463 + // that edge. It does not cancel motions that do not start on an edge. 1.1464 + if ((line < first && cur.line == first) || 1.1465 + (line > last && cur.line == last)) { 1.1466 + return; 1.1467 + } 1.1468 + if (motionArgs.toFirstChar){ 1.1469 + endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line)); 1.1470 + vim.lastHPos = endCh; 1.1471 + } 1.1472 + vim.lastHSPos = cm.charCoords(Pos(line, endCh),'div').left; 1.1473 + return Pos(line, endCh); 1.1474 + }, 1.1475 + moveByDisplayLines: function(cm, motionArgs, vim) { 1.1476 + var cur = cm.getCursor(); 1.1477 + switch (vim.lastMotion) { 1.1478 + case this.moveByDisplayLines: 1.1479 + case this.moveByScroll: 1.1480 + case this.moveByLines: 1.1481 + case this.moveToColumn: 1.1482 + case this.moveToEol: 1.1483 + break; 1.1484 + default: 1.1485 + vim.lastHSPos = cm.charCoords(cur,'div').left; 1.1486 + } 1.1487 + var repeat = motionArgs.repeat; 1.1488 + var res=cm.findPosV(cur,(motionArgs.forward ? repeat : -repeat),'line',vim.lastHSPos); 1.1489 + if (res.hitSide) { 1.1490 + if (motionArgs.forward) { 1.1491 + var lastCharCoords = cm.charCoords(res, 'div'); 1.1492 + var goalCoords = { top: lastCharCoords.top + 8, left: vim.lastHSPos }; 1.1493 + var res = cm.coordsChar(goalCoords, 'div'); 1.1494 + } else { 1.1495 + var resCoords = cm.charCoords(Pos(cm.firstLine(), 0), 'div'); 1.1496 + resCoords.left = vim.lastHSPos; 1.1497 + res = cm.coordsChar(resCoords, 'div'); 1.1498 + } 1.1499 + } 1.1500 + vim.lastHPos = res.ch; 1.1501 + return res; 1.1502 + }, 1.1503 + moveByPage: function(cm, motionArgs) { 1.1504 + // CodeMirror only exposes functions that move the cursor page down, so 1.1505 + // doing this bad hack to move the cursor and move it back. evalInput 1.1506 + // will move the cursor to where it should be in the end. 1.1507 + var curStart = cm.getCursor(); 1.1508 + var repeat = motionArgs.repeat; 1.1509 + cm.moveV((motionArgs.forward ? repeat : -repeat), 'page'); 1.1510 + var curEnd = cm.getCursor(); 1.1511 + cm.setCursor(curStart); 1.1512 + return curEnd; 1.1513 + }, 1.1514 + moveByParagraph: function(cm, motionArgs) { 1.1515 + var line = cm.getCursor().line; 1.1516 + var repeat = motionArgs.repeat; 1.1517 + var inc = motionArgs.forward ? 1 : -1; 1.1518 + for (var i = 0; i < repeat; i++) { 1.1519 + if ((!motionArgs.forward && line === cm.firstLine() ) || 1.1520 + (motionArgs.forward && line == cm.lastLine())) { 1.1521 + break; 1.1522 + } 1.1523 + line += inc; 1.1524 + while (line !== cm.firstLine() && line != cm.lastLine() && cm.getLine(line)) { 1.1525 + line += inc; 1.1526 + } 1.1527 + } 1.1528 + return Pos(line, 0); 1.1529 + }, 1.1530 + moveByScroll: function(cm, motionArgs, vim) { 1.1531 + var scrollbox = cm.getScrollInfo(); 1.1532 + var curEnd = null; 1.1533 + var repeat = motionArgs.repeat; 1.1534 + if (!repeat) { 1.1535 + repeat = scrollbox.clientHeight / (2 * cm.defaultTextHeight()); 1.1536 + } 1.1537 + var orig = cm.charCoords(cm.getCursor(), 'local'); 1.1538 + motionArgs.repeat = repeat; 1.1539 + var curEnd = motions.moveByDisplayLines(cm, motionArgs, vim); 1.1540 + if (!curEnd) { 1.1541 + return null; 1.1542 + } 1.1543 + var dest = cm.charCoords(curEnd, 'local'); 1.1544 + cm.scrollTo(null, scrollbox.top + dest.top - orig.top); 1.1545 + return curEnd; 1.1546 + }, 1.1547 + moveByWords: function(cm, motionArgs) { 1.1548 + return moveToWord(cm, motionArgs.repeat, !!motionArgs.forward, 1.1549 + !!motionArgs.wordEnd, !!motionArgs.bigWord); 1.1550 + }, 1.1551 + moveTillCharacter: function(cm, motionArgs) { 1.1552 + var repeat = motionArgs.repeat; 1.1553 + var curEnd = moveToCharacter(cm, repeat, motionArgs.forward, 1.1554 + motionArgs.selectedCharacter); 1.1555 + var increment = motionArgs.forward ? -1 : 1; 1.1556 + recordLastCharacterSearch(increment, motionArgs); 1.1557 + if (!curEnd) return null; 1.1558 + curEnd.ch += increment; 1.1559 + return curEnd; 1.1560 + }, 1.1561 + moveToCharacter: function(cm, motionArgs) { 1.1562 + var repeat = motionArgs.repeat; 1.1563 + recordLastCharacterSearch(0, motionArgs); 1.1564 + return moveToCharacter(cm, repeat, motionArgs.forward, 1.1565 + motionArgs.selectedCharacter) || cm.getCursor(); 1.1566 + }, 1.1567 + moveToSymbol: function(cm, motionArgs) { 1.1568 + var repeat = motionArgs.repeat; 1.1569 + return findSymbol(cm, repeat, motionArgs.forward, 1.1570 + motionArgs.selectedCharacter) || cm.getCursor(); 1.1571 + }, 1.1572 + moveToColumn: function(cm, motionArgs, vim) { 1.1573 + var repeat = motionArgs.repeat; 1.1574 + // repeat is equivalent to which column we want to move to! 1.1575 + vim.lastHPos = repeat - 1; 1.1576 + vim.lastHSPos = cm.charCoords(cm.getCursor(),'div').left; 1.1577 + return moveToColumn(cm, repeat); 1.1578 + }, 1.1579 + moveToEol: function(cm, motionArgs, vim) { 1.1580 + var cur = cm.getCursor(); 1.1581 + vim.lastHPos = Infinity; 1.1582 + var retval= Pos(cur.line + motionArgs.repeat - 1, Infinity); 1.1583 + var end=cm.clipPos(retval); 1.1584 + end.ch--; 1.1585 + vim.lastHSPos = cm.charCoords(end,'div').left; 1.1586 + return retval; 1.1587 + }, 1.1588 + moveToFirstNonWhiteSpaceCharacter: function(cm) { 1.1589 + // Go to the start of the line where the text begins, or the end for 1.1590 + // whitespace-only lines 1.1591 + var cursor = cm.getCursor(); 1.1592 + return Pos(cursor.line, 1.1593 + findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line))); 1.1594 + }, 1.1595 + moveToMatchedSymbol: function(cm) { 1.1596 + var cursor = cm.getCursor(); 1.1597 + var line = cursor.line; 1.1598 + var ch = cursor.ch; 1.1599 + var lineText = cm.getLine(line); 1.1600 + var symbol; 1.1601 + var startContext = cm.getTokenAt(cursor).type; 1.1602 + var startCtxLevel = getContextLevel(startContext); 1.1603 + do { 1.1604 + symbol = lineText.charAt(ch++); 1.1605 + if (symbol && isMatchableSymbol(symbol)) { 1.1606 + var endContext = cm.getTokenAt(Pos(line, ch)).type; 1.1607 + var endCtxLevel = getContextLevel(endContext); 1.1608 + if (startCtxLevel >= endCtxLevel) { 1.1609 + break; 1.1610 + } 1.1611 + } 1.1612 + } while (symbol); 1.1613 + if (symbol) { 1.1614 + return findMatchedSymbol(cm, Pos(line, ch-1), symbol); 1.1615 + } else { 1.1616 + return cursor; 1.1617 + } 1.1618 + }, 1.1619 + moveToStartOfLine: function(cm) { 1.1620 + var cursor = cm.getCursor(); 1.1621 + return Pos(cursor.line, 0); 1.1622 + }, 1.1623 + moveToLineOrEdgeOfDocument: function(cm, motionArgs) { 1.1624 + var lineNum = motionArgs.forward ? cm.lastLine() : cm.firstLine(); 1.1625 + if (motionArgs.repeatIsExplicit) { 1.1626 + lineNum = motionArgs.repeat - cm.getOption('firstLineNumber'); 1.1627 + } 1.1628 + return Pos(lineNum, 1.1629 + findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum))); 1.1630 + }, 1.1631 + textObjectManipulation: function(cm, motionArgs) { 1.1632 + // TODO: lots of possible exceptions that can be thrown here. Try da( 1.1633 + // outside of a () block. 1.1634 + 1.1635 + // TODO: adding <> >< to this map doesn't work, presumably because 1.1636 + // they're operators 1.1637 + var mirroredPairs = {'(': ')', ')': '(', 1.1638 + '{': '}', '}': '{', 1.1639 + '[': ']', ']': '['}; 1.1640 + var selfPaired = {'\'': true, '"': true}; 1.1641 + 1.1642 + var character = motionArgs.selectedCharacter; 1.1643 + 1.1644 + // Inclusive is the difference between a and i 1.1645 + // TODO: Instead of using the additional text object map to perform text 1.1646 + // object operations, merge the map into the defaultKeyMap and use 1.1647 + // motionArgs to define behavior. Define separate entries for 'aw', 1.1648 + // 'iw', 'a[', 'i[', etc. 1.1649 + var inclusive = !motionArgs.textObjectInner; 1.1650 + 1.1651 + var tmp; 1.1652 + if (mirroredPairs[character]) { 1.1653 + tmp = selectCompanionObject(cm, mirroredPairs[character], inclusive); 1.1654 + } else if (selfPaired[character]) { 1.1655 + tmp = findBeginningAndEnd(cm, character, inclusive); 1.1656 + } else if (character === 'W') { 1.1657 + tmp = expandWordUnderCursor(cm, inclusive, true /** forward */, 1.1658 + true /** bigWord */); 1.1659 + } else if (character === 'w') { 1.1660 + tmp = expandWordUnderCursor(cm, inclusive, true /** forward */, 1.1661 + false /** bigWord */); 1.1662 + } else { 1.1663 + // No text object defined for this, don't move. 1.1664 + return null; 1.1665 + } 1.1666 + 1.1667 + return [tmp.start, tmp.end]; 1.1668 + }, 1.1669 + 1.1670 + repeatLastCharacterSearch: function(cm, motionArgs) { 1.1671 + var lastSearch = vimGlobalState.lastChararacterSearch; 1.1672 + var repeat = motionArgs.repeat; 1.1673 + var forward = motionArgs.forward === lastSearch.forward; 1.1674 + var increment = (lastSearch.increment ? 1 : 0) * (forward ? -1 : 1); 1.1675 + cm.moveH(-increment, 'char'); 1.1676 + motionArgs.inclusive = forward ? true : false; 1.1677 + var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter); 1.1678 + if (!curEnd) { 1.1679 + cm.moveH(increment, 'char'); 1.1680 + return cm.getCursor(); 1.1681 + } 1.1682 + curEnd.ch += increment; 1.1683 + return curEnd; 1.1684 + } 1.1685 + }; 1.1686 + 1.1687 + var operators = { 1.1688 + change: function(cm, operatorArgs, _vim, curStart, curEnd) { 1.1689 + vimGlobalState.registerController.pushText( 1.1690 + operatorArgs.registerName, 'change', cm.getRange(curStart, curEnd), 1.1691 + operatorArgs.linewise); 1.1692 + if (operatorArgs.linewise) { 1.1693 + // Push the next line back down, if there is a next line. 1.1694 + var replacement = curEnd.line > cm.lastLine() ? '' : '\n'; 1.1695 + cm.replaceRange(replacement, curStart, curEnd); 1.1696 + cm.indentLine(curStart.line, 'smart'); 1.1697 + // null ch so setCursor moves to end of line. 1.1698 + curStart.ch = null; 1.1699 + } else { 1.1700 + // Exclude trailing whitespace if the range is not all whitespace. 1.1701 + var text = cm.getRange(curStart, curEnd); 1.1702 + if (!isWhiteSpaceString(text)) { 1.1703 + var match = (/\s+$/).exec(text); 1.1704 + if (match) { 1.1705 + curEnd = offsetCursor(curEnd, 0, - match[0].length); 1.1706 + } 1.1707 + } 1.1708 + cm.replaceRange('', curStart, curEnd); 1.1709 + } 1.1710 + actions.enterInsertMode(cm, {}, cm.state.vim); 1.1711 + cm.setCursor(curStart); 1.1712 + }, 1.1713 + // delete is a javascript keyword. 1.1714 + 'delete': function(cm, operatorArgs, _vim, curStart, curEnd) { 1.1715 + // If the ending line is past the last line, inclusive, instead of 1.1716 + // including the trailing \n, include the \n before the starting line 1.1717 + if (operatorArgs.linewise && 1.1718 + curEnd.line > cm.lastLine() && curStart.line > cm.firstLine()) { 1.1719 + curStart.line--; 1.1720 + curStart.ch = lineLength(cm, curStart.line); 1.1721 + } 1.1722 + vimGlobalState.registerController.pushText( 1.1723 + operatorArgs.registerName, 'delete', cm.getRange(curStart, curEnd), 1.1724 + operatorArgs.linewise); 1.1725 + cm.replaceRange('', curStart, curEnd); 1.1726 + if (operatorArgs.linewise) { 1.1727 + cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm)); 1.1728 + } else { 1.1729 + cm.setCursor(curStart); 1.1730 + } 1.1731 + }, 1.1732 + indent: function(cm, operatorArgs, vim, curStart, curEnd) { 1.1733 + var startLine = curStart.line; 1.1734 + var endLine = curEnd.line; 1.1735 + // In visual mode, n> shifts the selection right n times, instead of 1.1736 + // shifting n lines right once. 1.1737 + var repeat = (vim.visualMode) ? operatorArgs.repeat : 1; 1.1738 + if (operatorArgs.linewise) { 1.1739 + // The only way to delete a newline is to delete until the start of 1.1740 + // the next line, so in linewise mode evalInput will include the next 1.1741 + // line. We don't want this in indent, so we go back a line. 1.1742 + endLine--; 1.1743 + } 1.1744 + for (var i = startLine; i <= endLine; i++) { 1.1745 + for (var j = 0; j < repeat; j++) { 1.1746 + cm.indentLine(i, operatorArgs.indentRight); 1.1747 + } 1.1748 + } 1.1749 + cm.setCursor(curStart); 1.1750 + cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm)); 1.1751 + }, 1.1752 + swapcase: function(cm, operatorArgs, _vim, curStart, curEnd, curOriginal) { 1.1753 + var toSwap = cm.getRange(curStart, curEnd); 1.1754 + var swapped = ''; 1.1755 + for (var i = 0; i < toSwap.length; i++) { 1.1756 + var character = toSwap.charAt(i); 1.1757 + swapped += isUpperCase(character) ? character.toLowerCase() : 1.1758 + character.toUpperCase(); 1.1759 + } 1.1760 + cm.replaceRange(swapped, curStart, curEnd); 1.1761 + if (!operatorArgs.shouldMoveCursor) { 1.1762 + cm.setCursor(curOriginal); 1.1763 + } 1.1764 + }, 1.1765 + yank: function(cm, operatorArgs, _vim, curStart, curEnd, curOriginal) { 1.1766 + vimGlobalState.registerController.pushText( 1.1767 + operatorArgs.registerName, 'yank', 1.1768 + cm.getRange(curStart, curEnd), operatorArgs.linewise); 1.1769 + cm.setCursor(curOriginal); 1.1770 + } 1.1771 + }; 1.1772 + 1.1773 + var actions = { 1.1774 + jumpListWalk: function(cm, actionArgs, vim) { 1.1775 + if (vim.visualMode) { 1.1776 + return; 1.1777 + } 1.1778 + var repeat = actionArgs.repeat; 1.1779 + var forward = actionArgs.forward; 1.1780 + var jumpList = vimGlobalState.jumpList; 1.1781 + 1.1782 + var mark = jumpList.move(cm, forward ? repeat : -repeat); 1.1783 + var markPos = mark ? mark.find() : undefined; 1.1784 + markPos = markPos ? markPos : cm.getCursor(); 1.1785 + cm.setCursor(markPos); 1.1786 + }, 1.1787 + scroll: function(cm, actionArgs, vim) { 1.1788 + if (vim.visualMode) { 1.1789 + return; 1.1790 + } 1.1791 + var repeat = actionArgs.repeat || 1; 1.1792 + var lineHeight = cm.defaultTextHeight(); 1.1793 + var top = cm.getScrollInfo().top; 1.1794 + var delta = lineHeight * repeat; 1.1795 + var newPos = actionArgs.forward ? top + delta : top - delta; 1.1796 + var cursor = copyCursor(cm.getCursor()); 1.1797 + var cursorCoords = cm.charCoords(cursor, 'local'); 1.1798 + if (actionArgs.forward) { 1.1799 + if (newPos > cursorCoords.top) { 1.1800 + cursor.line += (newPos - cursorCoords.top) / lineHeight; 1.1801 + cursor.line = Math.ceil(cursor.line); 1.1802 + cm.setCursor(cursor); 1.1803 + cursorCoords = cm.charCoords(cursor, 'local'); 1.1804 + cm.scrollTo(null, cursorCoords.top); 1.1805 + } else { 1.1806 + // Cursor stays within bounds. Just reposition the scroll window. 1.1807 + cm.scrollTo(null, newPos); 1.1808 + } 1.1809 + } else { 1.1810 + var newBottom = newPos + cm.getScrollInfo().clientHeight; 1.1811 + if (newBottom < cursorCoords.bottom) { 1.1812 + cursor.line -= (cursorCoords.bottom - newBottom) / lineHeight; 1.1813 + cursor.line = Math.floor(cursor.line); 1.1814 + cm.setCursor(cursor); 1.1815 + cursorCoords = cm.charCoords(cursor, 'local'); 1.1816 + cm.scrollTo( 1.1817 + null, cursorCoords.bottom - cm.getScrollInfo().clientHeight); 1.1818 + } else { 1.1819 + // Cursor stays within bounds. Just reposition the scroll window. 1.1820 + cm.scrollTo(null, newPos); 1.1821 + } 1.1822 + } 1.1823 + }, 1.1824 + scrollToCursor: function(cm, actionArgs) { 1.1825 + var lineNum = cm.getCursor().line; 1.1826 + var charCoords = cm.charCoords(Pos(lineNum, 0), 'local'); 1.1827 + var height = cm.getScrollInfo().clientHeight; 1.1828 + var y = charCoords.top; 1.1829 + var lineHeight = charCoords.bottom - y; 1.1830 + switch (actionArgs.position) { 1.1831 + case 'center': y = y - (height / 2) + lineHeight; 1.1832 + break; 1.1833 + case 'bottom': y = y - height + lineHeight*1.4; 1.1834 + break; 1.1835 + case 'top': y = y + lineHeight*0.4; 1.1836 + break; 1.1837 + } 1.1838 + cm.scrollTo(null, y); 1.1839 + }, 1.1840 + replayMacro: function(cm, actionArgs, vim) { 1.1841 + var registerName = actionArgs.selectedCharacter; 1.1842 + var repeat = actionArgs.repeat; 1.1843 + var macroModeState = vimGlobalState.macroModeState; 1.1844 + if (registerName == '@') { 1.1845 + registerName = macroModeState.latestRegister; 1.1846 + } 1.1847 + while(repeat--){ 1.1848 + executeMacroRegister(cm, vim, macroModeState, registerName); 1.1849 + } 1.1850 + }, 1.1851 + enterMacroRecordMode: function(cm, actionArgs) { 1.1852 + var macroModeState = vimGlobalState.macroModeState; 1.1853 + var registerName = actionArgs.selectedCharacter; 1.1854 + macroModeState.enterMacroRecordMode(cm, registerName); 1.1855 + }, 1.1856 + enterInsertMode: function(cm, actionArgs, vim) { 1.1857 + if (cm.getOption('readOnly')) { return; } 1.1858 + vim.insertMode = true; 1.1859 + vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1; 1.1860 + var insertAt = (actionArgs) ? actionArgs.insertAt : null; 1.1861 + if (insertAt == 'eol') { 1.1862 + var cursor = cm.getCursor(); 1.1863 + cursor = Pos(cursor.line, lineLength(cm, cursor.line)); 1.1864 + cm.setCursor(cursor); 1.1865 + } else if (insertAt == 'charAfter') { 1.1866 + cm.setCursor(offsetCursor(cm.getCursor(), 0, 1)); 1.1867 + } else if (insertAt == 'firstNonBlank') { 1.1868 + cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm)); 1.1869 + } 1.1870 + cm.setOption('keyMap', 'vim-insert'); 1.1871 + cm.setOption('disableInput', false); 1.1872 + if (actionArgs && actionArgs.replace) { 1.1873 + // Handle Replace-mode as a special case of insert mode. 1.1874 + cm.toggleOverwrite(true); 1.1875 + cm.setOption('keyMap', 'vim-replace'); 1.1876 + CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"}); 1.1877 + } else { 1.1878 + cm.setOption('keyMap', 'vim-insert'); 1.1879 + CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"}); 1.1880 + } 1.1881 + if (!vimGlobalState.macroModeState.isPlaying) { 1.1882 + // Only record if not replaying. 1.1883 + cm.on('change', onChange); 1.1884 + cm.on('cursorActivity', onCursorActivity); 1.1885 + CodeMirror.on(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown); 1.1886 + } 1.1887 + }, 1.1888 + toggleVisualMode: function(cm, actionArgs, vim) { 1.1889 + var repeat = actionArgs.repeat; 1.1890 + var curStart = cm.getCursor(); 1.1891 + var curEnd; 1.1892 + // TODO: The repeat should actually select number of characters/lines 1.1893 + // equal to the repeat times the size of the previous visual 1.1894 + // operation. 1.1895 + if (!vim.visualMode) { 1.1896 + cm.on('mousedown', exitVisualMode); 1.1897 + vim.visualMode = true; 1.1898 + vim.visualLine = !!actionArgs.linewise; 1.1899 + if (vim.visualLine) { 1.1900 + curStart.ch = 0; 1.1901 + curEnd = clipCursorToContent( 1.1902 + cm, Pos(curStart.line + repeat - 1, lineLength(cm, curStart.line)), 1.1903 + true /** includeLineBreak */); 1.1904 + } else { 1.1905 + curEnd = clipCursorToContent( 1.1906 + cm, Pos(curStart.line, curStart.ch + repeat), 1.1907 + true /** includeLineBreak */); 1.1908 + } 1.1909 + // Make the initial selection. 1.1910 + if (!actionArgs.repeatIsExplicit && !vim.visualLine) { 1.1911 + // This is a strange case. Here the implicit repeat is 1. The 1.1912 + // following commands lets the cursor hover over the 1 character 1.1913 + // selection. 1.1914 + cm.setCursor(curEnd); 1.1915 + cm.setSelection(curEnd, curStart); 1.1916 + } else { 1.1917 + cm.setSelection(curStart, curEnd); 1.1918 + } 1.1919 + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : ""}); 1.1920 + } else { 1.1921 + curStart = cm.getCursor('anchor'); 1.1922 + curEnd = cm.getCursor('head'); 1.1923 + if (!vim.visualLine && actionArgs.linewise) { 1.1924 + // Shift-V pressed in characterwise visual mode. Switch to linewise 1.1925 + // visual mode instead of exiting visual mode. 1.1926 + vim.visualLine = true; 1.1927 + curStart.ch = cursorIsBefore(curStart, curEnd) ? 0 : 1.1928 + lineLength(cm, curStart.line); 1.1929 + curEnd.ch = cursorIsBefore(curStart, curEnd) ? 1.1930 + lineLength(cm, curEnd.line) : 0; 1.1931 + cm.setSelection(curStart, curEnd); 1.1932 + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: "linewise"}); 1.1933 + } else if (vim.visualLine && !actionArgs.linewise) { 1.1934 + // v pressed in linewise visual mode. Switch to characterwise visual 1.1935 + // mode instead of exiting visual mode. 1.1936 + vim.visualLine = false; 1.1937 + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"}); 1.1938 + } else { 1.1939 + exitVisualMode(cm); 1.1940 + } 1.1941 + } 1.1942 + updateMark(cm, vim, '<', cursorIsBefore(curStart, curEnd) ? curStart 1.1943 + : curEnd); 1.1944 + updateMark(cm, vim, '>', cursorIsBefore(curStart, curEnd) ? curEnd 1.1945 + : curStart); 1.1946 + }, 1.1947 + reselectLastSelection: function(cm, _actionArgs, vim) { 1.1948 + if (vim.lastSelection) { 1.1949 + var lastSelection = vim.lastSelection; 1.1950 + cm.setSelection(lastSelection.curStart, lastSelection.curEnd); 1.1951 + if (lastSelection.visualLine) { 1.1952 + vim.visualMode = true; 1.1953 + vim.visualLine = true; 1.1954 + } 1.1955 + else { 1.1956 + vim.visualMode = true; 1.1957 + vim.visualLine = false; 1.1958 + } 1.1959 + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : ""}); 1.1960 + } 1.1961 + }, 1.1962 + joinLines: function(cm, actionArgs, vim) { 1.1963 + var curStart, curEnd; 1.1964 + if (vim.visualMode) { 1.1965 + curStart = cm.getCursor('anchor'); 1.1966 + curEnd = cm.getCursor('head'); 1.1967 + curEnd.ch = lineLength(cm, curEnd.line) - 1; 1.1968 + } else { 1.1969 + // Repeat is the number of lines to join. Minimum 2 lines. 1.1970 + var repeat = Math.max(actionArgs.repeat, 2); 1.1971 + curStart = cm.getCursor(); 1.1972 + curEnd = clipCursorToContent(cm, Pos(curStart.line + repeat - 1, 1.1973 + Infinity)); 1.1974 + } 1.1975 + var finalCh = 0; 1.1976 + cm.operation(function() { 1.1977 + for (var i = curStart.line; i < curEnd.line; i++) { 1.1978 + finalCh = lineLength(cm, curStart.line); 1.1979 + var tmp = Pos(curStart.line + 1, 1.1980 + lineLength(cm, curStart.line + 1)); 1.1981 + var text = cm.getRange(curStart, tmp); 1.1982 + text = text.replace(/\n\s*/g, ' '); 1.1983 + cm.replaceRange(text, curStart, tmp); 1.1984 + } 1.1985 + var curFinalPos = Pos(curStart.line, finalCh); 1.1986 + cm.setCursor(curFinalPos); 1.1987 + }); 1.1988 + }, 1.1989 + newLineAndEnterInsertMode: function(cm, actionArgs, vim) { 1.1990 + vim.insertMode = true; 1.1991 + var insertAt = copyCursor(cm.getCursor()); 1.1992 + if (insertAt.line === cm.firstLine() && !actionArgs.after) { 1.1993 + // Special case for inserting newline before start of document. 1.1994 + cm.replaceRange('\n', Pos(cm.firstLine(), 0)); 1.1995 + cm.setCursor(cm.firstLine(), 0); 1.1996 + } else { 1.1997 + insertAt.line = (actionArgs.after) ? insertAt.line : 1.1998 + insertAt.line - 1; 1.1999 + insertAt.ch = lineLength(cm, insertAt.line); 1.2000 + cm.setCursor(insertAt); 1.2001 + var newlineFn = CodeMirror.commands.newlineAndIndentContinueComment || 1.2002 + CodeMirror.commands.newlineAndIndent; 1.2003 + newlineFn(cm); 1.2004 + } 1.2005 + this.enterInsertMode(cm, { repeat: actionArgs.repeat }, vim); 1.2006 + }, 1.2007 + paste: function(cm, actionArgs) { 1.2008 + var cur = copyCursor(cm.getCursor()); 1.2009 + var register = vimGlobalState.registerController.getRegister( 1.2010 + actionArgs.registerName); 1.2011 + var text = register.toString(); 1.2012 + if (!text) { 1.2013 + return; 1.2014 + } 1.2015 + if (actionArgs.repeat > 1) { 1.2016 + var text = Array(actionArgs.repeat + 1).join(text); 1.2017 + } 1.2018 + var linewise = register.linewise; 1.2019 + if (linewise) { 1.2020 + if (actionArgs.after) { 1.2021 + // Move the newline at the end to the start instead, and paste just 1.2022 + // before the newline character of the line we are on right now. 1.2023 + text = '\n' + text.slice(0, text.length - 1); 1.2024 + cur.ch = lineLength(cm, cur.line); 1.2025 + } else { 1.2026 + cur.ch = 0; 1.2027 + } 1.2028 + } else { 1.2029 + cur.ch += actionArgs.after ? 1 : 0; 1.2030 + } 1.2031 + cm.replaceRange(text, cur); 1.2032 + // Now fine tune the cursor to where we want it. 1.2033 + var curPosFinal; 1.2034 + var idx; 1.2035 + if (linewise && actionArgs.after) { 1.2036 + curPosFinal = Pos( 1.2037 + cur.line + 1, 1.2038 + findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1))); 1.2039 + } else if (linewise && !actionArgs.after) { 1.2040 + curPosFinal = Pos( 1.2041 + cur.line, 1.2042 + findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line))); 1.2043 + } else if (!linewise && actionArgs.after) { 1.2044 + idx = cm.indexFromPos(cur); 1.2045 + curPosFinal = cm.posFromIndex(idx + text.length - 1); 1.2046 + } else { 1.2047 + idx = cm.indexFromPos(cur); 1.2048 + curPosFinal = cm.posFromIndex(idx + text.length); 1.2049 + } 1.2050 + cm.setCursor(curPosFinal); 1.2051 + }, 1.2052 + undo: function(cm, actionArgs) { 1.2053 + cm.operation(function() { 1.2054 + repeatFn(cm, CodeMirror.commands.undo, actionArgs.repeat)(); 1.2055 + cm.setCursor(cm.getCursor('anchor')); 1.2056 + }); 1.2057 + }, 1.2058 + redo: function(cm, actionArgs) { 1.2059 + repeatFn(cm, CodeMirror.commands.redo, actionArgs.repeat)(); 1.2060 + }, 1.2061 + setRegister: function(_cm, actionArgs, vim) { 1.2062 + vim.inputState.registerName = actionArgs.selectedCharacter; 1.2063 + }, 1.2064 + setMark: function(cm, actionArgs, vim) { 1.2065 + var markName = actionArgs.selectedCharacter; 1.2066 + updateMark(cm, vim, markName, cm.getCursor()); 1.2067 + }, 1.2068 + replace: function(cm, actionArgs, vim) { 1.2069 + var replaceWith = actionArgs.selectedCharacter; 1.2070 + var curStart = cm.getCursor(); 1.2071 + var replaceTo; 1.2072 + var curEnd; 1.2073 + if (vim.visualMode){ 1.2074 + curStart=cm.getCursor('start'); 1.2075 + curEnd=cm.getCursor('end'); 1.2076 + // workaround to catch the character under the cursor 1.2077 + // existing workaround doesn't cover actions 1.2078 + curEnd=cm.clipPos(Pos(curEnd.line, curEnd.ch+1)); 1.2079 + }else{ 1.2080 + var line = cm.getLine(curStart.line); 1.2081 + replaceTo = curStart.ch + actionArgs.repeat; 1.2082 + if (replaceTo > line.length) { 1.2083 + replaceTo=line.length; 1.2084 + } 1.2085 + curEnd = Pos(curStart.line, replaceTo); 1.2086 + } 1.2087 + if (replaceWith=='\n'){ 1.2088 + if (!vim.visualMode) cm.replaceRange('', curStart, curEnd); 1.2089 + // special case, where vim help says to replace by just one line-break 1.2090 + (CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm); 1.2091 + }else { 1.2092 + var replaceWithStr=cm.getRange(curStart, curEnd); 1.2093 + //replace all characters in range by selected, but keep linebreaks 1.2094 + replaceWithStr=replaceWithStr.replace(/[^\n]/g,replaceWith); 1.2095 + cm.replaceRange(replaceWithStr, curStart, curEnd); 1.2096 + if (vim.visualMode){ 1.2097 + cm.setCursor(curStart); 1.2098 + exitVisualMode(cm); 1.2099 + }else{ 1.2100 + cm.setCursor(offsetCursor(curEnd, 0, -1)); 1.2101 + } 1.2102 + } 1.2103 + }, 1.2104 + incrementNumberToken: function(cm, actionArgs) { 1.2105 + var cur = cm.getCursor(); 1.2106 + var lineStr = cm.getLine(cur.line); 1.2107 + var re = /-?\d+/g; 1.2108 + var match; 1.2109 + var start; 1.2110 + var end; 1.2111 + var numberStr; 1.2112 + var token; 1.2113 + while ((match = re.exec(lineStr)) !== null) { 1.2114 + token = match[0]; 1.2115 + start = match.index; 1.2116 + end = start + token.length; 1.2117 + if (cur.ch < end)break; 1.2118 + } 1.2119 + if (!actionArgs.backtrack && (end <= cur.ch))return; 1.2120 + if (token) { 1.2121 + var increment = actionArgs.increase ? 1 : -1; 1.2122 + var number = parseInt(token) + (increment * actionArgs.repeat); 1.2123 + var from = Pos(cur.line, start); 1.2124 + var to = Pos(cur.line, end); 1.2125 + numberStr = number.toString(); 1.2126 + cm.replaceRange(numberStr, from, to); 1.2127 + } else { 1.2128 + return; 1.2129 + } 1.2130 + cm.setCursor(Pos(cur.line, start + numberStr.length - 1)); 1.2131 + }, 1.2132 + repeatLastEdit: function(cm, actionArgs, vim) { 1.2133 + var lastEditInputState = vim.lastEditInputState; 1.2134 + if (!lastEditInputState) { return; } 1.2135 + var repeat = actionArgs.repeat; 1.2136 + if (repeat && actionArgs.repeatIsExplicit) { 1.2137 + vim.lastEditInputState.repeatOverride = repeat; 1.2138 + } else { 1.2139 + repeat = vim.lastEditInputState.repeatOverride || repeat; 1.2140 + } 1.2141 + repeatLastEdit(cm, vim, repeat, false /** repeatForInsert */); 1.2142 + } 1.2143 + }; 1.2144 + 1.2145 + /* 1.2146 + * Below are miscellaneous utility functions used by vim.js 1.2147 + */ 1.2148 + 1.2149 + /** 1.2150 + * Clips cursor to ensure that line is within the buffer's range 1.2151 + * If includeLineBreak is true, then allow cur.ch == lineLength. 1.2152 + */ 1.2153 + function clipCursorToContent(cm, cur, includeLineBreak) { 1.2154 + var line = Math.min(Math.max(cm.firstLine(), cur.line), cm.lastLine() ); 1.2155 + var maxCh = lineLength(cm, line) - 1; 1.2156 + maxCh = (includeLineBreak) ? maxCh + 1 : maxCh; 1.2157 + var ch = Math.min(Math.max(0, cur.ch), maxCh); 1.2158 + return Pos(line, ch); 1.2159 + } 1.2160 + function copyArgs(args) { 1.2161 + var ret = {}; 1.2162 + for (var prop in args) { 1.2163 + if (args.hasOwnProperty(prop)) { 1.2164 + ret[prop] = args[prop]; 1.2165 + } 1.2166 + } 1.2167 + return ret; 1.2168 + } 1.2169 + function offsetCursor(cur, offsetLine, offsetCh) { 1.2170 + return Pos(cur.line + offsetLine, cur.ch + offsetCh); 1.2171 + } 1.2172 + function matchKeysPartial(pressed, mapped) { 1.2173 + for (var i = 0; i < pressed.length; i++) { 1.2174 + // 'character' means any character. For mark, register commads, etc. 1.2175 + if (pressed[i] != mapped[i] && mapped[i] != 'character') { 1.2176 + return false; 1.2177 + } 1.2178 + } 1.2179 + return true; 1.2180 + } 1.2181 + function repeatFn(cm, fn, repeat) { 1.2182 + return function() { 1.2183 + for (var i = 0; i < repeat; i++) { 1.2184 + fn(cm); 1.2185 + } 1.2186 + }; 1.2187 + } 1.2188 + function copyCursor(cur) { 1.2189 + return Pos(cur.line, cur.ch); 1.2190 + } 1.2191 + function cursorEqual(cur1, cur2) { 1.2192 + return cur1.ch == cur2.ch && cur1.line == cur2.line; 1.2193 + } 1.2194 + function cursorIsBefore(cur1, cur2) { 1.2195 + if (cur1.line < cur2.line) { 1.2196 + return true; 1.2197 + } 1.2198 + if (cur1.line == cur2.line && cur1.ch < cur2.ch) { 1.2199 + return true; 1.2200 + } 1.2201 + return false; 1.2202 + } 1.2203 + function cusrorIsBetween(cur1, cur2, cur3) { 1.2204 + // returns true if cur2 is between cur1 and cur3. 1.2205 + var cur1before2 = cursorIsBefore(cur1, cur2); 1.2206 + var cur2before3 = cursorIsBefore(cur2, cur3); 1.2207 + return cur1before2 && cur2before3; 1.2208 + } 1.2209 + function lineLength(cm, lineNum) { 1.2210 + return cm.getLine(lineNum).length; 1.2211 + } 1.2212 + function reverse(s){ 1.2213 + return s.split('').reverse().join(''); 1.2214 + } 1.2215 + function trim(s) { 1.2216 + if (s.trim) { 1.2217 + return s.trim(); 1.2218 + } 1.2219 + return s.replace(/^\s+|\s+$/g, ''); 1.2220 + } 1.2221 + function escapeRegex(s) { 1.2222 + return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1'); 1.2223 + } 1.2224 + 1.2225 + function exitVisualMode(cm) { 1.2226 + cm.off('mousedown', exitVisualMode); 1.2227 + var vim = cm.state.vim; 1.2228 + // can't use selection state here because yank has already reset its cursor 1.2229 + vim.lastSelection = {'curStart': vim.marks['<'].find(), 1.2230 + 'curEnd': vim.marks['>'].find(), 'visualMode': vim.visualMode, 1.2231 + 'visualLine': vim.visualLine}; 1.2232 + vim.visualMode = false; 1.2233 + vim.visualLine = false; 1.2234 + var selectionStart = cm.getCursor('anchor'); 1.2235 + var selectionEnd = cm.getCursor('head'); 1.2236 + if (!cursorEqual(selectionStart, selectionEnd)) { 1.2237 + // Clear the selection and set the cursor only if the selection has not 1.2238 + // already been cleared. Otherwise we risk moving the cursor somewhere 1.2239 + // it's not supposed to be. 1.2240 + cm.setCursor(clipCursorToContent(cm, selectionEnd)); 1.2241 + } 1.2242 + CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); 1.2243 + } 1.2244 + 1.2245 + // Remove any trailing newlines from the selection. For 1.2246 + // example, with the caret at the start of the last word on the line, 1.2247 + // 'dw' should word, but not the newline, while 'w' should advance the 1.2248 + // caret to the first character of the next line. 1.2249 + function clipToLine(cm, curStart, curEnd) { 1.2250 + var selection = cm.getRange(curStart, curEnd); 1.2251 + // Only clip if the selection ends with trailing newline + whitespace 1.2252 + if (/\n\s*$/.test(selection)) { 1.2253 + var lines = selection.split('\n'); 1.2254 + // We know this is all whitepsace. 1.2255 + lines.pop(); 1.2256 + 1.2257 + // Cases: 1.2258 + // 1. Last word is an empty line - do not clip the trailing '\n' 1.2259 + // 2. Last word is not an empty line - clip the trailing '\n' 1.2260 + var line; 1.2261 + // Find the line containing the last word, and clip all whitespace up 1.2262 + // to it. 1.2263 + for (var line = lines.pop(); lines.length > 0 && line && isWhiteSpaceString(line); line = lines.pop()) { 1.2264 + curEnd.line--; 1.2265 + curEnd.ch = 0; 1.2266 + } 1.2267 + // If the last word is not an empty line, clip an additional newline 1.2268 + if (line) { 1.2269 + curEnd.line--; 1.2270 + curEnd.ch = lineLength(cm, curEnd.line); 1.2271 + } else { 1.2272 + curEnd.ch = 0; 1.2273 + } 1.2274 + } 1.2275 + } 1.2276 + 1.2277 + // Expand the selection to line ends. 1.2278 + function expandSelectionToLine(_cm, curStart, curEnd) { 1.2279 + curStart.ch = 0; 1.2280 + curEnd.ch = 0; 1.2281 + curEnd.line++; 1.2282 + } 1.2283 + 1.2284 + function findFirstNonWhiteSpaceCharacter(text) { 1.2285 + if (!text) { 1.2286 + return 0; 1.2287 + } 1.2288 + var firstNonWS = text.search(/\S/); 1.2289 + return firstNonWS == -1 ? text.length : firstNonWS; 1.2290 + } 1.2291 + 1.2292 + function expandWordUnderCursor(cm, inclusive, _forward, bigWord, noSymbol) { 1.2293 + var cur = cm.getCursor(); 1.2294 + var line = cm.getLine(cur.line); 1.2295 + var idx = cur.ch; 1.2296 + 1.2297 + // Seek to first word or non-whitespace character, depending on if 1.2298 + // noSymbol is true. 1.2299 + var textAfterIdx = line.substring(idx); 1.2300 + var firstMatchedChar; 1.2301 + if (noSymbol) { 1.2302 + firstMatchedChar = textAfterIdx.search(/\w/); 1.2303 + } else { 1.2304 + firstMatchedChar = textAfterIdx.search(/\S/); 1.2305 + } 1.2306 + if (firstMatchedChar == -1) { 1.2307 + return null; 1.2308 + } 1.2309 + idx += firstMatchedChar; 1.2310 + textAfterIdx = line.substring(idx); 1.2311 + var textBeforeIdx = line.substring(0, idx); 1.2312 + 1.2313 + var matchRegex; 1.2314 + // Greedy matchers for the "word" we are trying to expand. 1.2315 + if (bigWord) { 1.2316 + matchRegex = /^\S+/; 1.2317 + } else { 1.2318 + if ((/\w/).test(line.charAt(idx))) { 1.2319 + matchRegex = /^\w+/; 1.2320 + } else { 1.2321 + matchRegex = /^[^\w\s]+/; 1.2322 + } 1.2323 + } 1.2324 + 1.2325 + var wordAfterRegex = matchRegex.exec(textAfterIdx); 1.2326 + var wordStart = idx; 1.2327 + var wordEnd = idx + wordAfterRegex[0].length; 1.2328 + // TODO: Find a better way to do this. It will be slow on very long lines. 1.2329 + var revTextBeforeIdx = reverse(textBeforeIdx); 1.2330 + var wordBeforeRegex = matchRegex.exec(revTextBeforeIdx); 1.2331 + if (wordBeforeRegex) { 1.2332 + wordStart -= wordBeforeRegex[0].length; 1.2333 + } 1.2334 + 1.2335 + if (inclusive) { 1.2336 + // If present, trim all whitespace after word. 1.2337 + // Otherwise, trim all whitespace before word. 1.2338 + var textAfterWordEnd = line.substring(wordEnd); 1.2339 + var whitespacesAfterWord = textAfterWordEnd.match(/^\s*/)[0].length; 1.2340 + if (whitespacesAfterWord > 0) { 1.2341 + wordEnd += whitespacesAfterWord; 1.2342 + } else { 1.2343 + var revTrim = revTextBeforeIdx.length - wordStart; 1.2344 + var textBeforeWordStart = revTextBeforeIdx.substring(revTrim); 1.2345 + var whitespacesBeforeWord = textBeforeWordStart.match(/^\s*/)[0].length; 1.2346 + wordStart -= whitespacesBeforeWord; 1.2347 + } 1.2348 + } 1.2349 + 1.2350 + return { start: Pos(cur.line, wordStart), 1.2351 + end: Pos(cur.line, wordEnd) }; 1.2352 + } 1.2353 + 1.2354 + function recordJumpPosition(cm, oldCur, newCur) { 1.2355 + if (!cursorEqual(oldCur, newCur)) { 1.2356 + vimGlobalState.jumpList.add(cm, oldCur, newCur); 1.2357 + } 1.2358 + } 1.2359 + 1.2360 + function recordLastCharacterSearch(increment, args) { 1.2361 + vimGlobalState.lastChararacterSearch.increment = increment; 1.2362 + vimGlobalState.lastChararacterSearch.forward = args.forward; 1.2363 + vimGlobalState.lastChararacterSearch.selectedCharacter = args.selectedCharacter; 1.2364 + } 1.2365 + 1.2366 + var symbolToMode = { 1.2367 + '(': 'bracket', ')': 'bracket', '{': 'bracket', '}': 'bracket', 1.2368 + '[': 'section', ']': 'section', 1.2369 + '*': 'comment', '/': 'comment', 1.2370 + 'm': 'method', 'M': 'method', 1.2371 + '#': 'preprocess' 1.2372 + }; 1.2373 + var findSymbolModes = { 1.2374 + bracket: { 1.2375 + isComplete: function(state) { 1.2376 + if (state.nextCh === state.symb) { 1.2377 + state.depth++; 1.2378 + if (state.depth >= 1)return true; 1.2379 + } else if (state.nextCh === state.reverseSymb) { 1.2380 + state.depth--; 1.2381 + } 1.2382 + return false; 1.2383 + } 1.2384 + }, 1.2385 + section: { 1.2386 + init: function(state) { 1.2387 + state.curMoveThrough = true; 1.2388 + state.symb = (state.forward ? ']' : '[') === state.symb ? '{' : '}'; 1.2389 + }, 1.2390 + isComplete: function(state) { 1.2391 + return state.index === 0 && state.nextCh === state.symb; 1.2392 + } 1.2393 + }, 1.2394 + comment: { 1.2395 + isComplete: function(state) { 1.2396 + var found = state.lastCh === '*' && state.nextCh === '/'; 1.2397 + state.lastCh = state.nextCh; 1.2398 + return found; 1.2399 + } 1.2400 + }, 1.2401 + // TODO: The original Vim implementation only operates on level 1 and 2. 1.2402 + // The current implementation doesn't check for code block level and 1.2403 + // therefore it operates on any levels. 1.2404 + method: { 1.2405 + init: function(state) { 1.2406 + state.symb = (state.symb === 'm' ? '{' : '}'); 1.2407 + state.reverseSymb = state.symb === '{' ? '}' : '{'; 1.2408 + }, 1.2409 + isComplete: function(state) { 1.2410 + if (state.nextCh === state.symb)return true; 1.2411 + return false; 1.2412 + } 1.2413 + }, 1.2414 + preprocess: { 1.2415 + init: function(state) { 1.2416 + state.index = 0; 1.2417 + }, 1.2418 + isComplete: function(state) { 1.2419 + if (state.nextCh === '#') { 1.2420 + var token = state.lineText.match(/#(\w+)/)[1]; 1.2421 + if (token === 'endif') { 1.2422 + if (state.forward && state.depth === 0) { 1.2423 + return true; 1.2424 + } 1.2425 + state.depth++; 1.2426 + } else if (token === 'if') { 1.2427 + if (!state.forward && state.depth === 0) { 1.2428 + return true; 1.2429 + } 1.2430 + state.depth--; 1.2431 + } 1.2432 + if (token === 'else' && state.depth === 0)return true; 1.2433 + } 1.2434 + return false; 1.2435 + } 1.2436 + } 1.2437 + }; 1.2438 + function findSymbol(cm, repeat, forward, symb) { 1.2439 + var cur = copyCursor(cm.getCursor()); 1.2440 + var increment = forward ? 1 : -1; 1.2441 + var endLine = forward ? cm.lineCount() : -1; 1.2442 + var curCh = cur.ch; 1.2443 + var line = cur.line; 1.2444 + var lineText = cm.getLine(line); 1.2445 + var state = { 1.2446 + lineText: lineText, 1.2447 + nextCh: lineText.charAt(curCh), 1.2448 + lastCh: null, 1.2449 + index: curCh, 1.2450 + symb: symb, 1.2451 + reverseSymb: (forward ? { ')': '(', '}': '{' } : { '(': ')', '{': '}' })[symb], 1.2452 + forward: forward, 1.2453 + depth: 0, 1.2454 + curMoveThrough: false 1.2455 + }; 1.2456 + var mode = symbolToMode[symb]; 1.2457 + if (!mode)return cur; 1.2458 + var init = findSymbolModes[mode].init; 1.2459 + var isComplete = findSymbolModes[mode].isComplete; 1.2460 + if (init) { init(state); } 1.2461 + while (line !== endLine && repeat) { 1.2462 + state.index += increment; 1.2463 + state.nextCh = state.lineText.charAt(state.index); 1.2464 + if (!state.nextCh) { 1.2465 + line += increment; 1.2466 + state.lineText = cm.getLine(line) || ''; 1.2467 + if (increment > 0) { 1.2468 + state.index = 0; 1.2469 + } else { 1.2470 + var lineLen = state.lineText.length; 1.2471 + state.index = (lineLen > 0) ? (lineLen-1) : 0; 1.2472 + } 1.2473 + state.nextCh = state.lineText.charAt(state.index); 1.2474 + } 1.2475 + if (isComplete(state)) { 1.2476 + cur.line = line; 1.2477 + cur.ch = state.index; 1.2478 + repeat--; 1.2479 + } 1.2480 + } 1.2481 + if (state.nextCh || state.curMoveThrough) { 1.2482 + return Pos(line, state.index); 1.2483 + } 1.2484 + return cur; 1.2485 + } 1.2486 + 1.2487 + /* 1.2488 + * Returns the boundaries of the next word. If the cursor in the middle of 1.2489 + * the word, then returns the boundaries of the current word, starting at 1.2490 + * the cursor. If the cursor is at the start/end of a word, and we are going 1.2491 + * forward/backward, respectively, find the boundaries of the next word. 1.2492 + * 1.2493 + * @param {CodeMirror} cm CodeMirror object. 1.2494 + * @param {Cursor} cur The cursor position. 1.2495 + * @param {boolean} forward True to search forward. False to search 1.2496 + * backward. 1.2497 + * @param {boolean} bigWord True if punctuation count as part of the word. 1.2498 + * False if only [a-zA-Z0-9] characters count as part of the word. 1.2499 + * @param {boolean} emptyLineIsWord True if empty lines should be treated 1.2500 + * as words. 1.2501 + * @return {Object{from:number, to:number, line: number}} The boundaries of 1.2502 + * the word, or null if there are no more words. 1.2503 + */ 1.2504 + function findWord(cm, cur, forward, bigWord, emptyLineIsWord) { 1.2505 + var lineNum = cur.line; 1.2506 + var pos = cur.ch; 1.2507 + var line = cm.getLine(lineNum); 1.2508 + var dir = forward ? 1 : -1; 1.2509 + var regexps = bigWord ? bigWordRegexp : wordRegexp; 1.2510 + 1.2511 + if (emptyLineIsWord && line == '') { 1.2512 + lineNum += dir; 1.2513 + line = cm.getLine(lineNum); 1.2514 + if (!isLine(cm, lineNum)) { 1.2515 + return null; 1.2516 + } 1.2517 + pos = (forward) ? 0 : line.length; 1.2518 + } 1.2519 + 1.2520 + while (true) { 1.2521 + if (emptyLineIsWord && line == '') { 1.2522 + return { from: 0, to: 0, line: lineNum }; 1.2523 + } 1.2524 + var stop = (dir > 0) ? line.length : -1; 1.2525 + var wordStart = stop, wordEnd = stop; 1.2526 + // Find bounds of next word. 1.2527 + while (pos != stop) { 1.2528 + var foundWord = false; 1.2529 + for (var i = 0; i < regexps.length && !foundWord; ++i) { 1.2530 + if (regexps[i].test(line.charAt(pos))) { 1.2531 + wordStart = pos; 1.2532 + // Advance to end of word. 1.2533 + while (pos != stop && regexps[i].test(line.charAt(pos))) { 1.2534 + pos += dir; 1.2535 + } 1.2536 + wordEnd = pos; 1.2537 + foundWord = wordStart != wordEnd; 1.2538 + if (wordStart == cur.ch && lineNum == cur.line && 1.2539 + wordEnd == wordStart + dir) { 1.2540 + // We started at the end of a word. Find the next one. 1.2541 + continue; 1.2542 + } else { 1.2543 + return { 1.2544 + from: Math.min(wordStart, wordEnd + 1), 1.2545 + to: Math.max(wordStart, wordEnd), 1.2546 + line: lineNum }; 1.2547 + } 1.2548 + } 1.2549 + } 1.2550 + if (!foundWord) { 1.2551 + pos += dir; 1.2552 + } 1.2553 + } 1.2554 + // Advance to next/prev line. 1.2555 + lineNum += dir; 1.2556 + if (!isLine(cm, lineNum)) { 1.2557 + return null; 1.2558 + } 1.2559 + line = cm.getLine(lineNum); 1.2560 + pos = (dir > 0) ? 0 : line.length; 1.2561 + } 1.2562 + // Should never get here. 1.2563 + throw new Error('The impossible happened.'); 1.2564 + } 1.2565 + 1.2566 + /** 1.2567 + * @param {CodeMirror} cm CodeMirror object. 1.2568 + * @param {int} repeat Number of words to move past. 1.2569 + * @param {boolean} forward True to search forward. False to search 1.2570 + * backward. 1.2571 + * @param {boolean} wordEnd True to move to end of word. False to move to 1.2572 + * beginning of word. 1.2573 + * @param {boolean} bigWord True if punctuation count as part of the word. 1.2574 + * False if only alphabet characters count as part of the word. 1.2575 + * @return {Cursor} The position the cursor should move to. 1.2576 + */ 1.2577 + function moveToWord(cm, repeat, forward, wordEnd, bigWord) { 1.2578 + var cur = cm.getCursor(); 1.2579 + var curStart = copyCursor(cur); 1.2580 + var words = []; 1.2581 + if (forward && !wordEnd || !forward && wordEnd) { 1.2582 + repeat++; 1.2583 + } 1.2584 + // For 'e', empty lines are not considered words, go figure. 1.2585 + var emptyLineIsWord = !(forward && wordEnd); 1.2586 + for (var i = 0; i < repeat; i++) { 1.2587 + var word = findWord(cm, cur, forward, bigWord, emptyLineIsWord); 1.2588 + if (!word) { 1.2589 + var eodCh = lineLength(cm, cm.lastLine()); 1.2590 + words.push(forward 1.2591 + ? {line: cm.lastLine(), from: eodCh, to: eodCh} 1.2592 + : {line: 0, from: 0, to: 0}); 1.2593 + break; 1.2594 + } 1.2595 + words.push(word); 1.2596 + cur = Pos(word.line, forward ? (word.to - 1) : word.from); 1.2597 + } 1.2598 + var shortCircuit = words.length != repeat; 1.2599 + var firstWord = words[0]; 1.2600 + var lastWord = words.pop(); 1.2601 + if (forward && !wordEnd) { 1.2602 + // w 1.2603 + if (!shortCircuit && (firstWord.from != curStart.ch || firstWord.line != curStart.line)) { 1.2604 + // We did not start in the middle of a word. Discard the extra word at the end. 1.2605 + lastWord = words.pop(); 1.2606 + } 1.2607 + return Pos(lastWord.line, lastWord.from); 1.2608 + } else if (forward && wordEnd) { 1.2609 + return Pos(lastWord.line, lastWord.to - 1); 1.2610 + } else if (!forward && wordEnd) { 1.2611 + // ge 1.2612 + if (!shortCircuit && (firstWord.to != curStart.ch || firstWord.line != curStart.line)) { 1.2613 + // We did not start in the middle of a word. Discard the extra word at the end. 1.2614 + lastWord = words.pop(); 1.2615 + } 1.2616 + return Pos(lastWord.line, lastWord.to); 1.2617 + } else { 1.2618 + // b 1.2619 + return Pos(lastWord.line, lastWord.from); 1.2620 + } 1.2621 + } 1.2622 + 1.2623 + function moveToCharacter(cm, repeat, forward, character) { 1.2624 + var cur = cm.getCursor(); 1.2625 + var start = cur.ch; 1.2626 + var idx; 1.2627 + for (var i = 0; i < repeat; i ++) { 1.2628 + var line = cm.getLine(cur.line); 1.2629 + idx = charIdxInLine(start, line, character, forward, true); 1.2630 + if (idx == -1) { 1.2631 + return null; 1.2632 + } 1.2633 + start = idx; 1.2634 + } 1.2635 + return Pos(cm.getCursor().line, idx); 1.2636 + } 1.2637 + 1.2638 + function moveToColumn(cm, repeat) { 1.2639 + // repeat is always >= 1, so repeat - 1 always corresponds 1.2640 + // to the column we want to go to. 1.2641 + var line = cm.getCursor().line; 1.2642 + return clipCursorToContent(cm, Pos(line, repeat - 1)); 1.2643 + } 1.2644 + 1.2645 + function updateMark(cm, vim, markName, pos) { 1.2646 + if (!inArray(markName, validMarks)) { 1.2647 + return; 1.2648 + } 1.2649 + if (vim.marks[markName]) { 1.2650 + vim.marks[markName].clear(); 1.2651 + } 1.2652 + vim.marks[markName] = cm.setBookmark(pos); 1.2653 + } 1.2654 + 1.2655 + function charIdxInLine(start, line, character, forward, includeChar) { 1.2656 + // Search for char in line. 1.2657 + // motion_options: {forward, includeChar} 1.2658 + // If includeChar = true, include it too. 1.2659 + // If forward = true, search forward, else search backwards. 1.2660 + // If char is not found on this line, do nothing 1.2661 + var idx; 1.2662 + if (forward) { 1.2663 + idx = line.indexOf(character, start + 1); 1.2664 + if (idx != -1 && !includeChar) { 1.2665 + idx -= 1; 1.2666 + } 1.2667 + } else { 1.2668 + idx = line.lastIndexOf(character, start - 1); 1.2669 + if (idx != -1 && !includeChar) { 1.2670 + idx += 1; 1.2671 + } 1.2672 + } 1.2673 + return idx; 1.2674 + } 1.2675 + 1.2676 + function getContextLevel(ctx) { 1.2677 + return (ctx === 'string' || ctx === 'comment') ? 1 : 0; 1.2678 + } 1.2679 + 1.2680 + function findMatchedSymbol(cm, cur, symb) { 1.2681 + var line = cur.line; 1.2682 + var ch = cur.ch; 1.2683 + symb = symb ? symb : cm.getLine(line).charAt(ch); 1.2684 + 1.2685 + var symbContext = cm.getTokenAt(Pos(line, ch + 1)).type; 1.2686 + var symbCtxLevel = getContextLevel(symbContext); 1.2687 + 1.2688 + var reverseSymb = ({ 1.2689 + '(': ')', ')': '(', 1.2690 + '[': ']', ']': '[', 1.2691 + '{': '}', '}': '{'})[symb]; 1.2692 + 1.2693 + // Couldn't find a matching symbol, abort 1.2694 + if (!reverseSymb) { 1.2695 + return cur; 1.2696 + } 1.2697 + 1.2698 + // set our increment to move forward (+1) or backwards (-1) 1.2699 + // depending on which bracket we're matching 1.2700 + var increment = ({'(': 1, '{': 1, '[': 1})[symb] || -1; 1.2701 + var endLine = increment === 1 ? cm.lineCount() : -1; 1.2702 + var depth = 1, nextCh = symb, index = ch, lineText = cm.getLine(line); 1.2703 + // Simple search for closing paren--just count openings and closings till 1.2704 + // we find our match 1.2705 + // TODO: use info from CodeMirror to ignore closing brackets in comments 1.2706 + // and quotes, etc. 1.2707 + while (line !== endLine && depth > 0) { 1.2708 + index += increment; 1.2709 + nextCh = lineText.charAt(index); 1.2710 + if (!nextCh) { 1.2711 + line += increment; 1.2712 + lineText = cm.getLine(line) || ''; 1.2713 + if (increment > 0) { 1.2714 + index = 0; 1.2715 + } else { 1.2716 + var lineLen = lineText.length; 1.2717 + index = (lineLen > 0) ? (lineLen-1) : 0; 1.2718 + } 1.2719 + nextCh = lineText.charAt(index); 1.2720 + } 1.2721 + var revSymbContext = cm.getTokenAt(Pos(line, index + 1)).type; 1.2722 + var revSymbCtxLevel = getContextLevel(revSymbContext); 1.2723 + if (symbCtxLevel >= revSymbCtxLevel) { 1.2724 + if (nextCh === symb) { 1.2725 + depth++; 1.2726 + } else if (nextCh === reverseSymb) { 1.2727 + depth--; 1.2728 + } 1.2729 + } 1.2730 + } 1.2731 + 1.2732 + if (nextCh) { 1.2733 + return Pos(line, index); 1.2734 + } 1.2735 + return cur; 1.2736 + } 1.2737 + 1.2738 + // TODO: perhaps this finagling of start and end positions belonds 1.2739 + // in codmirror/replaceRange? 1.2740 + function selectCompanionObject(cm, revSymb, inclusive) { 1.2741 + var cur = copyCursor(cm.getCursor()); 1.2742 + var end = findMatchedSymbol(cm, cur, revSymb); 1.2743 + var start = findMatchedSymbol(cm, end); 1.2744 + 1.2745 + if ((start.line == end.line && start.ch > end.ch) 1.2746 + || (start.line > end.line)) { 1.2747 + var tmp = start; 1.2748 + start = end; 1.2749 + end = tmp; 1.2750 + } 1.2751 + 1.2752 + if (inclusive) { 1.2753 + end.ch += 1; 1.2754 + } else { 1.2755 + start.ch += 1; 1.2756 + } 1.2757 + 1.2758 + return { start: start, end: end }; 1.2759 + } 1.2760 + 1.2761 + // Takes in a symbol and a cursor and tries to simulate text objects that 1.2762 + // have identical opening and closing symbols 1.2763 + // TODO support across multiple lines 1.2764 + function findBeginningAndEnd(cm, symb, inclusive) { 1.2765 + var cur = copyCursor(cm.getCursor()); 1.2766 + var line = cm.getLine(cur.line); 1.2767 + var chars = line.split(''); 1.2768 + var start, end, i, len; 1.2769 + var firstIndex = chars.indexOf(symb); 1.2770 + 1.2771 + // the decision tree is to always look backwards for the beginning first, 1.2772 + // but if the cursor is in front of the first instance of the symb, 1.2773 + // then move the cursor forward 1.2774 + if (cur.ch < firstIndex) { 1.2775 + cur.ch = firstIndex; 1.2776 + // Why is this line even here??? 1.2777 + // cm.setCursor(cur.line, firstIndex+1); 1.2778 + } 1.2779 + // otherwise if the cursor is currently on the closing symbol 1.2780 + else if (firstIndex < cur.ch && chars[cur.ch] == symb) { 1.2781 + end = cur.ch; // assign end to the current cursor 1.2782 + --cur.ch; // make sure to look backwards 1.2783 + } 1.2784 + 1.2785 + // if we're currently on the symbol, we've got a start 1.2786 + if (chars[cur.ch] == symb && !end) { 1.2787 + start = cur.ch + 1; // assign start to ahead of the cursor 1.2788 + } else { 1.2789 + // go backwards to find the start 1.2790 + for (i = cur.ch; i > -1 && !start; i--) { 1.2791 + if (chars[i] == symb) { 1.2792 + start = i + 1; 1.2793 + } 1.2794 + } 1.2795 + } 1.2796 + 1.2797 + // look forwards for the end symbol 1.2798 + if (start && !end) { 1.2799 + for (i = start, len = chars.length; i < len && !end; i++) { 1.2800 + if (chars[i] == symb) { 1.2801 + end = i; 1.2802 + } 1.2803 + } 1.2804 + } 1.2805 + 1.2806 + // nothing found 1.2807 + if (!start || !end) { 1.2808 + return { start: cur, end: cur }; 1.2809 + } 1.2810 + 1.2811 + // include the symbols 1.2812 + if (inclusive) { 1.2813 + --start; ++end; 1.2814 + } 1.2815 + 1.2816 + return { 1.2817 + start: Pos(cur.line, start), 1.2818 + end: Pos(cur.line, end) 1.2819 + }; 1.2820 + } 1.2821 + 1.2822 + // Search functions 1.2823 + defineOption('pcre', true, 'boolean'); 1.2824 + function SearchState() {} 1.2825 + SearchState.prototype = { 1.2826 + getQuery: function() { 1.2827 + return vimGlobalState.query; 1.2828 + }, 1.2829 + setQuery: function(query) { 1.2830 + vimGlobalState.query = query; 1.2831 + }, 1.2832 + getOverlay: function() { 1.2833 + return this.searchOverlay; 1.2834 + }, 1.2835 + setOverlay: function(overlay) { 1.2836 + this.searchOverlay = overlay; 1.2837 + }, 1.2838 + isReversed: function() { 1.2839 + return vimGlobalState.isReversed; 1.2840 + }, 1.2841 + setReversed: function(reversed) { 1.2842 + vimGlobalState.isReversed = reversed; 1.2843 + } 1.2844 + }; 1.2845 + function getSearchState(cm) { 1.2846 + var vim = cm.state.vim; 1.2847 + return vim.searchState_ || (vim.searchState_ = new SearchState()); 1.2848 + } 1.2849 + function dialog(cm, template, shortText, onClose, options) { 1.2850 + if (cm.openDialog) { 1.2851 + cm.openDialog(template, onClose, { bottom: true, value: options.value, 1.2852 + onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp }); 1.2853 + } 1.2854 + else { 1.2855 + onClose(prompt(shortText, '')); 1.2856 + } 1.2857 + } 1.2858 + 1.2859 + function findUnescapedSlashes(str) { 1.2860 + var escapeNextChar = false; 1.2861 + var slashes = []; 1.2862 + for (var i = 0; i < str.length; i++) { 1.2863 + var c = str.charAt(i); 1.2864 + if (!escapeNextChar && c == '/') { 1.2865 + slashes.push(i); 1.2866 + } 1.2867 + escapeNextChar = !escapeNextChar && (c == '\\'); 1.2868 + } 1.2869 + return slashes; 1.2870 + } 1.2871 + 1.2872 + // Translates a search string from ex (vim) syntax into javascript form. 1.2873 + function translateRegex(str) { 1.2874 + // When these match, add a '\' if unescaped or remove one if escaped. 1.2875 + var specials = ['|', '(', ')', '{']; 1.2876 + // Remove, but never add, a '\' for these. 1.2877 + var unescape = ['}']; 1.2878 + var escapeNextChar = false; 1.2879 + var out = []; 1.2880 + for (var i = -1; i < str.length; i++) { 1.2881 + var c = str.charAt(i) || ''; 1.2882 + var n = str.charAt(i+1) || ''; 1.2883 + var specialComesNext = (specials.indexOf(n) != -1); 1.2884 + if (escapeNextChar) { 1.2885 + if (c !== '\\' || !specialComesNext) { 1.2886 + out.push(c); 1.2887 + } 1.2888 + escapeNextChar = false; 1.2889 + } else { 1.2890 + if (c === '\\') { 1.2891 + escapeNextChar = true; 1.2892 + // Treat the unescape list as special for removing, but not adding '\'. 1.2893 + if (unescape.indexOf(n) != -1) { 1.2894 + specialComesNext = true; 1.2895 + } 1.2896 + // Not passing this test means removing a '\'. 1.2897 + if (!specialComesNext || n === '\\') { 1.2898 + out.push(c); 1.2899 + } 1.2900 + } else { 1.2901 + out.push(c); 1.2902 + if (specialComesNext && n !== '\\') { 1.2903 + out.push('\\'); 1.2904 + } 1.2905 + } 1.2906 + } 1.2907 + } 1.2908 + return out.join(''); 1.2909 + } 1.2910 + 1.2911 + // Translates the replace part of a search and replace from ex (vim) syntax into 1.2912 + // javascript form. Similar to translateRegex, but additionally fixes back references 1.2913 + // (translates '\[0..9]' to '$[0..9]') and follows different rules for escaping '$'. 1.2914 + function translateRegexReplace(str) { 1.2915 + var escapeNextChar = false; 1.2916 + var out = []; 1.2917 + for (var i = -1; i < str.length; i++) { 1.2918 + var c = str.charAt(i) || ''; 1.2919 + var n = str.charAt(i+1) || ''; 1.2920 + if (escapeNextChar) { 1.2921 + // At any point in the loop, escapeNextChar is true if the previous 1.2922 + // character was a '\' and was not escaped. 1.2923 + out.push(c); 1.2924 + escapeNextChar = false; 1.2925 + } else { 1.2926 + if (c === '\\') { 1.2927 + escapeNextChar = true; 1.2928 + if ((isNumber(n) || n === '$')) { 1.2929 + out.push('$'); 1.2930 + } else if (n !== '/' && n !== '\\') { 1.2931 + out.push('\\'); 1.2932 + } 1.2933 + } else { 1.2934 + if (c === '$') { 1.2935 + out.push('$'); 1.2936 + } 1.2937 + out.push(c); 1.2938 + if (n === '/') { 1.2939 + out.push('\\'); 1.2940 + } 1.2941 + } 1.2942 + } 1.2943 + } 1.2944 + return out.join(''); 1.2945 + } 1.2946 + 1.2947 + // Unescape \ and / in the replace part, for PCRE mode. 1.2948 + function unescapeRegexReplace(str) { 1.2949 + var stream = new CodeMirror.StringStream(str); 1.2950 + var output = []; 1.2951 + while (!stream.eol()) { 1.2952 + // Search for \. 1.2953 + while (stream.peek() && stream.peek() != '\\') { 1.2954 + output.push(stream.next()); 1.2955 + } 1.2956 + if (stream.match('\\/', true)) { 1.2957 + // \/ => / 1.2958 + output.push('/'); 1.2959 + } else if (stream.match('\\\\', true)) { 1.2960 + // \\ => \ 1.2961 + output.push('\\'); 1.2962 + } else { 1.2963 + // Don't change anything 1.2964 + output.push(stream.next()); 1.2965 + } 1.2966 + } 1.2967 + return output.join(''); 1.2968 + } 1.2969 + 1.2970 + /** 1.2971 + * Extract the regular expression from the query and return a Regexp object. 1.2972 + * Returns null if the query is blank. 1.2973 + * If ignoreCase is passed in, the Regexp object will have the 'i' flag set. 1.2974 + * If smartCase is passed in, and the query contains upper case letters, 1.2975 + * then ignoreCase is overridden, and the 'i' flag will not be set. 1.2976 + * If the query contains the /i in the flag part of the regular expression, 1.2977 + * then both ignoreCase and smartCase are ignored, and 'i' will be passed 1.2978 + * through to the Regex object. 1.2979 + */ 1.2980 + function parseQuery(query, ignoreCase, smartCase) { 1.2981 + // Check if the query is already a regex. 1.2982 + if (query instanceof RegExp) { return query; } 1.2983 + // First try to extract regex + flags from the input. If no flags found, 1.2984 + // extract just the regex. IE does not accept flags directly defined in 1.2985 + // the regex string in the form /regex/flags 1.2986 + var slashes = findUnescapedSlashes(query); 1.2987 + var regexPart; 1.2988 + var forceIgnoreCase; 1.2989 + if (!slashes.length) { 1.2990 + // Query looks like 'regexp' 1.2991 + regexPart = query; 1.2992 + } else { 1.2993 + // Query looks like 'regexp/...' 1.2994 + regexPart = query.substring(0, slashes[0]); 1.2995 + var flagsPart = query.substring(slashes[0]); 1.2996 + forceIgnoreCase = (flagsPart.indexOf('i') != -1); 1.2997 + } 1.2998 + if (!regexPart) { 1.2999 + return null; 1.3000 + } 1.3001 + if (!getOption('pcre')) { 1.3002 + regexPart = translateRegex(regexPart); 1.3003 + } 1.3004 + if (smartCase) { 1.3005 + ignoreCase = (/^[^A-Z]*$/).test(regexPart); 1.3006 + } 1.3007 + var regexp = new RegExp(regexPart, 1.3008 + (ignoreCase || forceIgnoreCase) ? 'i' : undefined); 1.3009 + return regexp; 1.3010 + } 1.3011 + function showConfirm(cm, text) { 1.3012 + if (cm.openNotification) { 1.3013 + cm.openNotification('<span style="color: red">' + text + '</span>', 1.3014 + {bottom: true, duration: 5000}); 1.3015 + } else { 1.3016 + alert(text); 1.3017 + } 1.3018 + } 1.3019 + function makePrompt(prefix, desc) { 1.3020 + var raw = ''; 1.3021 + if (prefix) { 1.3022 + raw += '<span style="font-family: monospace">' + prefix + '</span>'; 1.3023 + } 1.3024 + raw += '<input type="text"/> ' + 1.3025 + '<span style="color: #888">'; 1.3026 + if (desc) { 1.3027 + raw += '<span style="color: #888">'; 1.3028 + raw += desc; 1.3029 + raw += '</span>'; 1.3030 + } 1.3031 + return raw; 1.3032 + } 1.3033 + var searchPromptDesc = '(Javascript regexp)'; 1.3034 + function showPrompt(cm, options) { 1.3035 + var shortText = (options.prefix || '') + ' ' + (options.desc || ''); 1.3036 + var prompt = makePrompt(options.prefix, options.desc); 1.3037 + dialog(cm, prompt, shortText, options.onClose, options); 1.3038 + } 1.3039 + function regexEqual(r1, r2) { 1.3040 + if (r1 instanceof RegExp && r2 instanceof RegExp) { 1.3041 + var props = ['global', 'multiline', 'ignoreCase', 'source']; 1.3042 + for (var i = 0; i < props.length; i++) { 1.3043 + var prop = props[i]; 1.3044 + if (r1[prop] !== r2[prop]) { 1.3045 + return false; 1.3046 + } 1.3047 + } 1.3048 + return true; 1.3049 + } 1.3050 + return false; 1.3051 + } 1.3052 + // Returns true if the query is valid. 1.3053 + function updateSearchQuery(cm, rawQuery, ignoreCase, smartCase) { 1.3054 + if (!rawQuery) { 1.3055 + return; 1.3056 + } 1.3057 + var state = getSearchState(cm); 1.3058 + var query = parseQuery(rawQuery, !!ignoreCase, !!smartCase); 1.3059 + if (!query) { 1.3060 + return; 1.3061 + } 1.3062 + highlightSearchMatches(cm, query); 1.3063 + if (regexEqual(query, state.getQuery())) { 1.3064 + return query; 1.3065 + } 1.3066 + state.setQuery(query); 1.3067 + return query; 1.3068 + } 1.3069 + function searchOverlay(query) { 1.3070 + if (query.source.charAt(0) == '^') { 1.3071 + var matchSol = true; 1.3072 + } 1.3073 + return { 1.3074 + token: function(stream) { 1.3075 + if (matchSol && !stream.sol()) { 1.3076 + stream.skipToEnd(); 1.3077 + return; 1.3078 + } 1.3079 + var match = stream.match(query, false); 1.3080 + if (match) { 1.3081 + if (match[0].length == 0) { 1.3082 + // Matched empty string, skip to next. 1.3083 + stream.next(); 1.3084 + return 'searching'; 1.3085 + } 1.3086 + if (!stream.sol()) { 1.3087 + // Backtrack 1 to match \b 1.3088 + stream.backUp(1); 1.3089 + if (!query.exec(stream.next() + match[0])) { 1.3090 + stream.next(); 1.3091 + return null; 1.3092 + } 1.3093 + } 1.3094 + stream.match(query); 1.3095 + return 'searching'; 1.3096 + } 1.3097 + while (!stream.eol()) { 1.3098 + stream.next(); 1.3099 + if (stream.match(query, false)) break; 1.3100 + } 1.3101 + }, 1.3102 + query: query 1.3103 + }; 1.3104 + } 1.3105 + function highlightSearchMatches(cm, query) { 1.3106 + var overlay = getSearchState(cm).getOverlay(); 1.3107 + if (!overlay || query != overlay.query) { 1.3108 + if (overlay) { 1.3109 + cm.removeOverlay(overlay); 1.3110 + } 1.3111 + overlay = searchOverlay(query); 1.3112 + cm.addOverlay(overlay); 1.3113 + getSearchState(cm).setOverlay(overlay); 1.3114 + } 1.3115 + } 1.3116 + function findNext(cm, prev, query, repeat) { 1.3117 + if (repeat === undefined) { repeat = 1; } 1.3118 + return cm.operation(function() { 1.3119 + var pos = cm.getCursor(); 1.3120 + var cursor = cm.getSearchCursor(query, pos); 1.3121 + for (var i = 0; i < repeat; i++) { 1.3122 + var found = cursor.find(prev); 1.3123 + if (i == 0 && found && cursorEqual(cursor.from(), pos)) { found = cursor.find(prev); } 1.3124 + if (!found) { 1.3125 + // SearchCursor may have returned null because it hit EOF, wrap 1.3126 + // around and try again. 1.3127 + cursor = cm.getSearchCursor(query, 1.3128 + (prev) ? Pos(cm.lastLine()) : Pos(cm.firstLine(), 0) ); 1.3129 + if (!cursor.find(prev)) { 1.3130 + return; 1.3131 + } 1.3132 + } 1.3133 + } 1.3134 + return cursor.from(); 1.3135 + }); 1.3136 + } 1.3137 + function clearSearchHighlight(cm) { 1.3138 + cm.removeOverlay(getSearchState(cm).getOverlay()); 1.3139 + getSearchState(cm).setOverlay(null); 1.3140 + } 1.3141 + /** 1.3142 + * Check if pos is in the specified range, INCLUSIVE. 1.3143 + * Range can be specified with 1 or 2 arguments. 1.3144 + * If the first range argument is an array, treat it as an array of line 1.3145 + * numbers. Match pos against any of the lines. 1.3146 + * If the first range argument is a number, 1.3147 + * if there is only 1 range argument, check if pos has the same line 1.3148 + * number 1.3149 + * if there are 2 range arguments, then check if pos is in between the two 1.3150 + * range arguments. 1.3151 + */ 1.3152 + function isInRange(pos, start, end) { 1.3153 + if (typeof pos != 'number') { 1.3154 + // Assume it is a cursor position. Get the line number. 1.3155 + pos = pos.line; 1.3156 + } 1.3157 + if (start instanceof Array) { 1.3158 + return inArray(pos, start); 1.3159 + } else { 1.3160 + if (end) { 1.3161 + return (pos >= start && pos <= end); 1.3162 + } else { 1.3163 + return pos == start; 1.3164 + } 1.3165 + } 1.3166 + } 1.3167 + function getUserVisibleLines(cm) { 1.3168 + var scrollInfo = cm.getScrollInfo(); 1.3169 + var occludeToleranceTop = 6; 1.3170 + var occludeToleranceBottom = 10; 1.3171 + var from = cm.coordsChar({left:0, top: occludeToleranceTop + scrollInfo.top}, 'local'); 1.3172 + var bottomY = scrollInfo.clientHeight - occludeToleranceBottom + scrollInfo.top; 1.3173 + var to = cm.coordsChar({left:0, top: bottomY}, 'local'); 1.3174 + return {top: from.line, bottom: to.line}; 1.3175 + } 1.3176 + 1.3177 + // Ex command handling 1.3178 + // Care must be taken when adding to the default Ex command map. For any 1.3179 + // pair of commands that have a shared prefix, at least one of their 1.3180 + // shortNames must not match the prefix of the other command. 1.3181 + var defaultExCommandMap = [ 1.3182 + { name: 'map' }, 1.3183 + { name: 'nmap', shortName: 'nm' }, 1.3184 + { name: 'vmap', shortName: 'vm' }, 1.3185 + { name: 'unmap' }, 1.3186 + { name: 'write', shortName: 'w' }, 1.3187 + { name: 'undo', shortName: 'u' }, 1.3188 + { name: 'redo', shortName: 'red' }, 1.3189 + { name: 'set', shortName: 'set' }, 1.3190 + { name: 'sort', shortName: 'sor' }, 1.3191 + { name: 'substitute', shortName: 's' }, 1.3192 + { name: 'nohlsearch', shortName: 'noh' }, 1.3193 + { name: 'delmarks', shortName: 'delm' }, 1.3194 + { name: 'registers', shortName: 'reg' } 1.3195 + ]; 1.3196 + Vim.ExCommandDispatcher = function() { 1.3197 + this.buildCommandMap_(); 1.3198 + }; 1.3199 + Vim.ExCommandDispatcher.prototype = { 1.3200 + processCommand: function(cm, input) { 1.3201 + var vim = cm.state.vim; 1.3202 + if (vim.visualMode) { 1.3203 + exitVisualMode(cm); 1.3204 + } 1.3205 + var inputStream = new CodeMirror.StringStream(input); 1.3206 + var params = {}; 1.3207 + params.input = input; 1.3208 + try { 1.3209 + this.parseInput_(cm, inputStream, params); 1.3210 + } catch(e) { 1.3211 + showConfirm(cm, e); 1.3212 + throw e; 1.3213 + } 1.3214 + var commandName; 1.3215 + if (!params.commandName) { 1.3216 + // If only a line range is defined, move to the line. 1.3217 + if (params.line !== undefined) { 1.3218 + commandName = 'move'; 1.3219 + } 1.3220 + } else { 1.3221 + var command = this.matchCommand_(params.commandName); 1.3222 + if (command) { 1.3223 + commandName = command.name; 1.3224 + this.parseCommandArgs_(inputStream, params, command); 1.3225 + if (command.type == 'exToKey') { 1.3226 + // Handle Ex to Key mapping. 1.3227 + for (var i = 0; i < command.toKeys.length; i++) { 1.3228 + CodeMirror.Vim.handleKey(cm, command.toKeys[i]); 1.3229 + } 1.3230 + return; 1.3231 + } else if (command.type == 'exToEx') { 1.3232 + // Handle Ex to Ex mapping. 1.3233 + this.processCommand(cm, command.toInput); 1.3234 + return; 1.3235 + } 1.3236 + } 1.3237 + } 1.3238 + if (!commandName) { 1.3239 + showConfirm(cm, 'Not an editor command ":' + input + '"'); 1.3240 + return; 1.3241 + } 1.3242 + try { 1.3243 + exCommands[commandName](cm, params); 1.3244 + } catch(e) { 1.3245 + showConfirm(cm, e); 1.3246 + throw e; 1.3247 + } 1.3248 + }, 1.3249 + parseInput_: function(cm, inputStream, result) { 1.3250 + inputStream.eatWhile(':'); 1.3251 + // Parse range. 1.3252 + if (inputStream.eat('%')) { 1.3253 + result.line = cm.firstLine(); 1.3254 + result.lineEnd = cm.lastLine(); 1.3255 + } else { 1.3256 + result.line = this.parseLineSpec_(cm, inputStream); 1.3257 + if (result.line !== undefined && inputStream.eat(',')) { 1.3258 + result.lineEnd = this.parseLineSpec_(cm, inputStream); 1.3259 + } 1.3260 + } 1.3261 + 1.3262 + // Parse command name. 1.3263 + var commandMatch = inputStream.match(/^(\w+)/); 1.3264 + if (commandMatch) { 1.3265 + result.commandName = commandMatch[1]; 1.3266 + } else { 1.3267 + result.commandName = inputStream.match(/.*/)[0]; 1.3268 + } 1.3269 + 1.3270 + return result; 1.3271 + }, 1.3272 + parseLineSpec_: function(cm, inputStream) { 1.3273 + var numberMatch = inputStream.match(/^(\d+)/); 1.3274 + if (numberMatch) { 1.3275 + return parseInt(numberMatch[1], 10) - 1; 1.3276 + } 1.3277 + switch (inputStream.next()) { 1.3278 + case '.': 1.3279 + return cm.getCursor().line; 1.3280 + case '$': 1.3281 + return cm.lastLine(); 1.3282 + case '\'': 1.3283 + var mark = cm.state.vim.marks[inputStream.next()]; 1.3284 + if (mark && mark.find()) { 1.3285 + return mark.find().line; 1.3286 + } 1.3287 + throw new Error('Mark not set'); 1.3288 + default: 1.3289 + inputStream.backUp(1); 1.3290 + return undefined; 1.3291 + } 1.3292 + }, 1.3293 + parseCommandArgs_: function(inputStream, params, command) { 1.3294 + if (inputStream.eol()) { 1.3295 + return; 1.3296 + } 1.3297 + params.argString = inputStream.match(/.*/)[0]; 1.3298 + // Parse command-line arguments 1.3299 + var delim = command.argDelimiter || /\s+/; 1.3300 + var args = trim(params.argString).split(delim); 1.3301 + if (args.length && args[0]) { 1.3302 + params.args = args; 1.3303 + } 1.3304 + }, 1.3305 + matchCommand_: function(commandName) { 1.3306 + // Return the command in the command map that matches the shortest 1.3307 + // prefix of the passed in command name. The match is guaranteed to be 1.3308 + // unambiguous if the defaultExCommandMap's shortNames are set up 1.3309 + // correctly. (see @code{defaultExCommandMap}). 1.3310 + for (var i = commandName.length; i > 0; i--) { 1.3311 + var prefix = commandName.substring(0, i); 1.3312 + if (this.commandMap_[prefix]) { 1.3313 + var command = this.commandMap_[prefix]; 1.3314 + if (command.name.indexOf(commandName) === 0) { 1.3315 + return command; 1.3316 + } 1.3317 + } 1.3318 + } 1.3319 + return null; 1.3320 + }, 1.3321 + buildCommandMap_: function() { 1.3322 + this.commandMap_ = {}; 1.3323 + for (var i = 0; i < defaultExCommandMap.length; i++) { 1.3324 + var command = defaultExCommandMap[i]; 1.3325 + var key = command.shortName || command.name; 1.3326 + this.commandMap_[key] = command; 1.3327 + } 1.3328 + }, 1.3329 + map: function(lhs, rhs, ctx) { 1.3330 + if (lhs != ':' && lhs.charAt(0) == ':') { 1.3331 + if (ctx) { throw Error('Mode not supported for ex mappings'); } 1.3332 + var commandName = lhs.substring(1); 1.3333 + if (rhs != ':' && rhs.charAt(0) == ':') { 1.3334 + // Ex to Ex mapping 1.3335 + this.commandMap_[commandName] = { 1.3336 + name: commandName, 1.3337 + type: 'exToEx', 1.3338 + toInput: rhs.substring(1), 1.3339 + user: true 1.3340 + }; 1.3341 + } else { 1.3342 + // Ex to key mapping 1.3343 + this.commandMap_[commandName] = { 1.3344 + name: commandName, 1.3345 + type: 'exToKey', 1.3346 + toKeys: parseKeyString(rhs), 1.3347 + user: true 1.3348 + }; 1.3349 + } 1.3350 + } else { 1.3351 + if (rhs != ':' && rhs.charAt(0) == ':') { 1.3352 + // Key to Ex mapping. 1.3353 + var mapping = { 1.3354 + keys: parseKeyString(lhs), 1.3355 + type: 'keyToEx', 1.3356 + exArgs: { input: rhs.substring(1) }, 1.3357 + user: true}; 1.3358 + if (ctx) { mapping.context = ctx; } 1.3359 + defaultKeymap.unshift(mapping); 1.3360 + } else { 1.3361 + // Key to key mapping 1.3362 + var mapping = { 1.3363 + keys: parseKeyString(lhs), 1.3364 + type: 'keyToKey', 1.3365 + toKeys: parseKeyString(rhs), 1.3366 + user: true 1.3367 + }; 1.3368 + if (ctx) { mapping.context = ctx; } 1.3369 + defaultKeymap.unshift(mapping); 1.3370 + } 1.3371 + } 1.3372 + }, 1.3373 + unmap: function(lhs, ctx) { 1.3374 + var arrayEquals = function(a, b) { 1.3375 + if (a === b) return true; 1.3376 + if (a == null || b == null) return true; 1.3377 + if (a.length != b.length) return false; 1.3378 + for (var i = 0; i < a.length; i++) { 1.3379 + if (a[i] !== b[i]) return false; 1.3380 + } 1.3381 + return true; 1.3382 + }; 1.3383 + if (lhs != ':' && lhs.charAt(0) == ':') { 1.3384 + // Ex to Ex or Ex to key mapping 1.3385 + if (ctx) { throw Error('Mode not supported for ex mappings'); } 1.3386 + var commandName = lhs.substring(1); 1.3387 + if (this.commandMap_[commandName] && this.commandMap_[commandName].user) { 1.3388 + delete this.commandMap_[commandName]; 1.3389 + return; 1.3390 + } 1.3391 + } else { 1.3392 + // Key to Ex or key to key mapping 1.3393 + var keys = parseKeyString(lhs); 1.3394 + for (var i = 0; i < defaultKeymap.length; i++) { 1.3395 + if (arrayEquals(keys, defaultKeymap[i].keys) 1.3396 + && defaultKeymap[i].context === ctx 1.3397 + && defaultKeymap[i].user) { 1.3398 + defaultKeymap.splice(i, 1); 1.3399 + return; 1.3400 + } 1.3401 + } 1.3402 + } 1.3403 + throw Error('No such mapping.'); 1.3404 + } 1.3405 + }; 1.3406 + 1.3407 + // Converts a key string sequence of the form a<C-w>bd<Left> into Vim's 1.3408 + // keymap representation. 1.3409 + function parseKeyString(str) { 1.3410 + var key, match; 1.3411 + var keys = []; 1.3412 + while (str) { 1.3413 + match = (/<\w+-.+?>|<\w+>|./).exec(str); 1.3414 + if (match === null)break; 1.3415 + key = match[0]; 1.3416 + str = str.substring(match.index + key.length); 1.3417 + keys.push(key); 1.3418 + } 1.3419 + return keys; 1.3420 + } 1.3421 + 1.3422 + var exCommands = { 1.3423 + map: function(cm, params, ctx) { 1.3424 + var mapArgs = params.args; 1.3425 + if (!mapArgs || mapArgs.length < 2) { 1.3426 + if (cm) { 1.3427 + showConfirm(cm, 'Invalid mapping: ' + params.input); 1.3428 + } 1.3429 + return; 1.3430 + } 1.3431 + exCommandDispatcher.map(mapArgs[0], mapArgs[1], ctx); 1.3432 + }, 1.3433 + nmap: function(cm, params) { this.map(cm, params, 'normal'); }, 1.3434 + vmap: function(cm, params) { this.map(cm, params, 'visual'); }, 1.3435 + unmap: function(cm, params, ctx) { 1.3436 + var mapArgs = params.args; 1.3437 + if (!mapArgs || mapArgs.length < 1) { 1.3438 + if (cm) { 1.3439 + showConfirm(cm, 'No such mapping: ' + params.input); 1.3440 + } 1.3441 + return; 1.3442 + } 1.3443 + exCommandDispatcher.unmap(mapArgs[0], ctx); 1.3444 + }, 1.3445 + move: function(cm, params) { 1.3446 + commandDispatcher.processCommand(cm, cm.state.vim, { 1.3447 + type: 'motion', 1.3448 + motion: 'moveToLineOrEdgeOfDocument', 1.3449 + motionArgs: { forward: false, explicitRepeat: true, 1.3450 + linewise: true }, 1.3451 + repeatOverride: params.line+1}); 1.3452 + }, 1.3453 + set: function(cm, params) { 1.3454 + var setArgs = params.args; 1.3455 + if (!setArgs || setArgs.length < 1) { 1.3456 + if (cm) { 1.3457 + showConfirm(cm, 'Invalid mapping: ' + params.input); 1.3458 + } 1.3459 + return; 1.3460 + } 1.3461 + var expr = setArgs[0].split('='); 1.3462 + var optionName = expr[0]; 1.3463 + var value = expr[1]; 1.3464 + var forceGet = false; 1.3465 + 1.3466 + if (optionName.charAt(optionName.length - 1) == '?') { 1.3467 + // If post-fixed with ?, then the set is actually a get. 1.3468 + if (value) { throw Error('Trailing characters: ' + params.argString); } 1.3469 + optionName = optionName.substring(0, optionName.length - 1); 1.3470 + forceGet = true; 1.3471 + } 1.3472 + if (value === undefined && optionName.substring(0, 2) == 'no') { 1.3473 + // To set boolean options to false, the option name is prefixed with 1.3474 + // 'no'. 1.3475 + optionName = optionName.substring(2); 1.3476 + value = false; 1.3477 + } 1.3478 + var optionIsBoolean = options[optionName] && options[optionName].type == 'boolean'; 1.3479 + if (optionIsBoolean && value == undefined) { 1.3480 + // Calling set with a boolean option sets it to true. 1.3481 + value = true; 1.3482 + } 1.3483 + if (!optionIsBoolean && !value || forceGet) { 1.3484 + var oldValue = getOption(optionName); 1.3485 + // If no value is provided, then we assume this is a get. 1.3486 + if (oldValue === true || oldValue === false) { 1.3487 + showConfirm(cm, ' ' + (oldValue ? '' : 'no') + optionName); 1.3488 + } else { 1.3489 + showConfirm(cm, ' ' + optionName + '=' + oldValue); 1.3490 + } 1.3491 + } else { 1.3492 + setOption(optionName, value); 1.3493 + } 1.3494 + }, 1.3495 + registers: function(cm,params) { 1.3496 + var regArgs = params.args; 1.3497 + var registers = vimGlobalState.registerController.registers; 1.3498 + var regInfo = '----------Registers----------<br><br>'; 1.3499 + if (!regArgs) { 1.3500 + for (var registerName in registers) { 1.3501 + var text = registers[registerName].toString(); 1.3502 + if (text.length) { 1.3503 + regInfo += '"' + registerName + ' ' + text + '<br>'; 1.3504 + } 1.3505 + } 1.3506 + } else { 1.3507 + var registerName; 1.3508 + regArgs = regArgs.join(''); 1.3509 + for (var i = 0; i < regArgs.length; i++) { 1.3510 + registerName = regArgs.charAt(i); 1.3511 + if (!vimGlobalState.registerController.isValidRegister(registerName)) { 1.3512 + continue; 1.3513 + } 1.3514 + var register = registers[registerName] || new Register(); 1.3515 + regInfo += '"' + registerName + ' ' + register.text + '<br>'; 1.3516 + } 1.3517 + } 1.3518 + showConfirm(cm, regInfo); 1.3519 + }, 1.3520 + sort: function(cm, params) { 1.3521 + var reverse, ignoreCase, unique, number; 1.3522 + function parseArgs() { 1.3523 + if (params.argString) { 1.3524 + var args = new CodeMirror.StringStream(params.argString); 1.3525 + if (args.eat('!')) { reverse = true; } 1.3526 + if (args.eol()) { return; } 1.3527 + if (!args.eatSpace()) { return 'Invalid arguments'; } 1.3528 + var opts = args.match(/[a-z]+/); 1.3529 + if (opts) { 1.3530 + opts = opts[0]; 1.3531 + ignoreCase = opts.indexOf('i') != -1; 1.3532 + unique = opts.indexOf('u') != -1; 1.3533 + var decimal = opts.indexOf('d') != -1 && 1; 1.3534 + var hex = opts.indexOf('x') != -1 && 1; 1.3535 + var octal = opts.indexOf('o') != -1 && 1; 1.3536 + if (decimal + hex + octal > 1) { return 'Invalid arguments'; } 1.3537 + number = decimal && 'decimal' || hex && 'hex' || octal && 'octal'; 1.3538 + } 1.3539 + if (args.eatSpace() && args.match(/\/.*\//)) { 'patterns not supported'; } 1.3540 + } 1.3541 + } 1.3542 + var err = parseArgs(); 1.3543 + if (err) { 1.3544 + showConfirm(cm, err + ': ' + params.argString); 1.3545 + return; 1.3546 + } 1.3547 + var lineStart = params.line || cm.firstLine(); 1.3548 + var lineEnd = params.lineEnd || params.line || cm.lastLine(); 1.3549 + if (lineStart == lineEnd) { return; } 1.3550 + var curStart = Pos(lineStart, 0); 1.3551 + var curEnd = Pos(lineEnd, lineLength(cm, lineEnd)); 1.3552 + var text = cm.getRange(curStart, curEnd).split('\n'); 1.3553 + var numberRegex = (number == 'decimal') ? /(-?)([\d]+)/ : 1.3554 + (number == 'hex') ? /(-?)(?:0x)?([0-9a-f]+)/i : 1.3555 + (number == 'octal') ? /([0-7]+)/ : null; 1.3556 + var radix = (number == 'decimal') ? 10 : (number == 'hex') ? 16 : (number == 'octal') ? 8 : null; 1.3557 + var numPart = [], textPart = []; 1.3558 + if (number) { 1.3559 + for (var i = 0; i < text.length; i++) { 1.3560 + if (numberRegex.exec(text[i])) { 1.3561 + numPart.push(text[i]); 1.3562 + } else { 1.3563 + textPart.push(text[i]); 1.3564 + } 1.3565 + } 1.3566 + } else { 1.3567 + textPart = text; 1.3568 + } 1.3569 + function compareFn(a, b) { 1.3570 + if (reverse) { var tmp; tmp = a; a = b; b = tmp; } 1.3571 + if (ignoreCase) { a = a.toLowerCase(); b = b.toLowerCase(); } 1.3572 + var anum = number && numberRegex.exec(a); 1.3573 + var bnum = number && numberRegex.exec(b); 1.3574 + if (!anum) { return a < b ? -1 : 1; } 1.3575 + anum = parseInt((anum[1] + anum[2]).toLowerCase(), radix); 1.3576 + bnum = parseInt((bnum[1] + bnum[2]).toLowerCase(), radix); 1.3577 + return anum - bnum; 1.3578 + } 1.3579 + numPart.sort(compareFn); 1.3580 + textPart.sort(compareFn); 1.3581 + text = (!reverse) ? textPart.concat(numPart) : numPart.concat(textPart); 1.3582 + if (unique) { // Remove duplicate lines 1.3583 + var textOld = text; 1.3584 + var lastLine; 1.3585 + text = []; 1.3586 + for (var i = 0; i < textOld.length; i++) { 1.3587 + if (textOld[i] != lastLine) { 1.3588 + text.push(textOld[i]); 1.3589 + } 1.3590 + lastLine = textOld[i]; 1.3591 + } 1.3592 + } 1.3593 + cm.replaceRange(text.join('\n'), curStart, curEnd); 1.3594 + }, 1.3595 + substitute: function(cm, params) { 1.3596 + if (!cm.getSearchCursor) { 1.3597 + throw new Error('Search feature not available. Requires searchcursor.js or ' + 1.3598 + 'any other getSearchCursor implementation.'); 1.3599 + } 1.3600 + var argString = params.argString; 1.3601 + var slashes = findUnescapedSlashes(argString); 1.3602 + if (slashes[0] !== 0) { 1.3603 + showConfirm(cm, 'Substitutions should be of the form ' + 1.3604 + ':s/pattern/replace/'); 1.3605 + return; 1.3606 + } 1.3607 + var regexPart = argString.substring(slashes[0] + 1, slashes[1]); 1.3608 + var replacePart = ''; 1.3609 + var flagsPart; 1.3610 + var count; 1.3611 + var confirm = false; // Whether to confirm each replace. 1.3612 + if (slashes[1]) { 1.3613 + replacePart = argString.substring(slashes[1] + 1, slashes[2]); 1.3614 + if (getOption('pcre')) { 1.3615 + replacePart = unescapeRegexReplace(replacePart); 1.3616 + } else { 1.3617 + replacePart = translateRegexReplace(replacePart); 1.3618 + } 1.3619 + } 1.3620 + if (slashes[2]) { 1.3621 + // After the 3rd slash, we can have flags followed by a space followed 1.3622 + // by count. 1.3623 + var trailing = argString.substring(slashes[2] + 1).split(' '); 1.3624 + flagsPart = trailing[0]; 1.3625 + count = parseInt(trailing[1]); 1.3626 + } 1.3627 + if (flagsPart) { 1.3628 + if (flagsPart.indexOf('c') != -1) { 1.3629 + confirm = true; 1.3630 + flagsPart.replace('c', ''); 1.3631 + } 1.3632 + regexPart = regexPart + '/' + flagsPart; 1.3633 + } 1.3634 + if (regexPart) { 1.3635 + // If regex part is empty, then use the previous query. Otherwise use 1.3636 + // the regex part as the new query. 1.3637 + try { 1.3638 + updateSearchQuery(cm, regexPart, true /** ignoreCase */, 1.3639 + true /** smartCase */); 1.3640 + } catch (e) { 1.3641 + showConfirm(cm, 'Invalid regex: ' + regexPart); 1.3642 + return; 1.3643 + } 1.3644 + } 1.3645 + var state = getSearchState(cm); 1.3646 + var query = state.getQuery(); 1.3647 + var lineStart = (params.line !== undefined) ? params.line : cm.getCursor().line; 1.3648 + var lineEnd = params.lineEnd || lineStart; 1.3649 + if (count) { 1.3650 + lineStart = lineEnd; 1.3651 + lineEnd = lineStart + count - 1; 1.3652 + } 1.3653 + var startPos = clipCursorToContent(cm, Pos(lineStart, 0)); 1.3654 + var cursor = cm.getSearchCursor(query, startPos); 1.3655 + doReplace(cm, confirm, lineStart, lineEnd, cursor, query, replacePart); 1.3656 + }, 1.3657 + redo: CodeMirror.commands.redo, 1.3658 + undo: CodeMirror.commands.undo, 1.3659 + write: function(cm) { 1.3660 + if (CodeMirror.commands.save) { 1.3661 + // If a save command is defined, call it. 1.3662 + CodeMirror.commands.save(cm); 1.3663 + } else { 1.3664 + // Saves to text area if no save command is defined. 1.3665 + cm.save(); 1.3666 + } 1.3667 + }, 1.3668 + nohlsearch: function(cm) { 1.3669 + clearSearchHighlight(cm); 1.3670 + }, 1.3671 + delmarks: function(cm, params) { 1.3672 + if (!params.argString || !trim(params.argString)) { 1.3673 + showConfirm(cm, 'Argument required'); 1.3674 + return; 1.3675 + } 1.3676 + 1.3677 + var state = cm.state.vim; 1.3678 + var stream = new CodeMirror.StringStream(trim(params.argString)); 1.3679 + while (!stream.eol()) { 1.3680 + stream.eatSpace(); 1.3681 + 1.3682 + // Record the streams position at the beginning of the loop for use 1.3683 + // in error messages. 1.3684 + var count = stream.pos; 1.3685 + 1.3686 + if (!stream.match(/[a-zA-Z]/, false)) { 1.3687 + showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count)); 1.3688 + return; 1.3689 + } 1.3690 + 1.3691 + var sym = stream.next(); 1.3692 + // Check if this symbol is part of a range 1.3693 + if (stream.match('-', true)) { 1.3694 + // This symbol is part of a range. 1.3695 + 1.3696 + // The range must terminate at an alphabetic character. 1.3697 + if (!stream.match(/[a-zA-Z]/, false)) { 1.3698 + showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count)); 1.3699 + return; 1.3700 + } 1.3701 + 1.3702 + var startMark = sym; 1.3703 + var finishMark = stream.next(); 1.3704 + // The range must terminate at an alphabetic character which 1.3705 + // shares the same case as the start of the range. 1.3706 + if (isLowerCase(startMark) && isLowerCase(finishMark) || 1.3707 + isUpperCase(startMark) && isUpperCase(finishMark)) { 1.3708 + var start = startMark.charCodeAt(0); 1.3709 + var finish = finishMark.charCodeAt(0); 1.3710 + if (start >= finish) { 1.3711 + showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count)); 1.3712 + return; 1.3713 + } 1.3714 + 1.3715 + // Because marks are always ASCII values, and we have 1.3716 + // determined that they are the same case, we can use 1.3717 + // their char codes to iterate through the defined range. 1.3718 + for (var j = 0; j <= finish - start; j++) { 1.3719 + var mark = String.fromCharCode(start + j); 1.3720 + delete state.marks[mark]; 1.3721 + } 1.3722 + } else { 1.3723 + showConfirm(cm, 'Invalid argument: ' + startMark + '-'); 1.3724 + return; 1.3725 + } 1.3726 + } else { 1.3727 + // This symbol is a valid mark, and is not part of a range. 1.3728 + delete state.marks[sym]; 1.3729 + } 1.3730 + } 1.3731 + } 1.3732 + }; 1.3733 + 1.3734 + var exCommandDispatcher = new Vim.ExCommandDispatcher(); 1.3735 + 1.3736 + /** 1.3737 + * @param {CodeMirror} cm CodeMirror instance we are in. 1.3738 + * @param {boolean} confirm Whether to confirm each replace. 1.3739 + * @param {Cursor} lineStart Line to start replacing from. 1.3740 + * @param {Cursor} lineEnd Line to stop replacing at. 1.3741 + * @param {RegExp} query Query for performing matches with. 1.3742 + * @param {string} replaceWith Text to replace matches with. May contain $1, 1.3743 + * $2, etc for replacing captured groups using Javascript replace. 1.3744 + */ 1.3745 + function doReplace(cm, confirm, lineStart, lineEnd, searchCursor, query, 1.3746 + replaceWith) { 1.3747 + // Set up all the functions. 1.3748 + cm.state.vim.exMode = true; 1.3749 + var done = false; 1.3750 + var lastPos = searchCursor.from(); 1.3751 + function replaceAll() { 1.3752 + cm.operation(function() { 1.3753 + while (!done) { 1.3754 + replace(); 1.3755 + next(); 1.3756 + } 1.3757 + stop(); 1.3758 + }); 1.3759 + } 1.3760 + function replace() { 1.3761 + var text = cm.getRange(searchCursor.from(), searchCursor.to()); 1.3762 + var newText = text.replace(query, replaceWith); 1.3763 + searchCursor.replace(newText); 1.3764 + } 1.3765 + function next() { 1.3766 + var found = searchCursor.findNext(); 1.3767 + if (!found) { 1.3768 + done = true; 1.3769 + } else if (isInRange(searchCursor.from(), lineStart, lineEnd)) { 1.3770 + cm.scrollIntoView(searchCursor.from(), 30); 1.3771 + cm.setSelection(searchCursor.from(), searchCursor.to()); 1.3772 + lastPos = searchCursor.from(); 1.3773 + done = false; 1.3774 + } else { 1.3775 + done = true; 1.3776 + } 1.3777 + } 1.3778 + function stop(close) { 1.3779 + if (close) { close(); } 1.3780 + cm.focus(); 1.3781 + if (lastPos) { 1.3782 + cm.setCursor(lastPos); 1.3783 + var vim = cm.state.vim; 1.3784 + vim.exMode = false; 1.3785 + vim.lastHPos = vim.lastHSPos = lastPos.ch; 1.3786 + } 1.3787 + } 1.3788 + function onPromptKeyDown(e, _value, close) { 1.3789 + // Swallow all keys. 1.3790 + CodeMirror.e_stop(e); 1.3791 + var keyName = CodeMirror.keyName(e); 1.3792 + switch (keyName) { 1.3793 + case 'Y': 1.3794 + replace(); next(); break; 1.3795 + case 'N': 1.3796 + next(); break; 1.3797 + case 'A': 1.3798 + cm.operation(replaceAll); break; 1.3799 + case 'L': 1.3800 + replace(); 1.3801 + // fall through and exit. 1.3802 + case 'Q': 1.3803 + case 'Esc': 1.3804 + case 'Ctrl-C': 1.3805 + case 'Ctrl-[': 1.3806 + stop(close); 1.3807 + break; 1.3808 + } 1.3809 + if (done) { stop(close); } 1.3810 + } 1.3811 + 1.3812 + // Actually do replace. 1.3813 + next(); 1.3814 + if (done) { 1.3815 + showConfirm(cm, 'No matches for ' + query.source); 1.3816 + return; 1.3817 + } 1.3818 + if (!confirm) { 1.3819 + replaceAll(); 1.3820 + return; 1.3821 + } 1.3822 + showPrompt(cm, { 1.3823 + prefix: 'replace with <strong>' + replaceWith + '</strong> (y/n/a/q/l)', 1.3824 + onKeyDown: onPromptKeyDown 1.3825 + }); 1.3826 + } 1.3827 + 1.3828 + // Register Vim with CodeMirror 1.3829 + function buildVimKeyMap() { 1.3830 + /** 1.3831 + * Handle the raw key event from CodeMirror. Translate the 1.3832 + * Shift + key modifier to the resulting letter, while preserving other 1.3833 + * modifers. 1.3834 + */ 1.3835 + function cmKeyToVimKey(key, modifier) { 1.3836 + var vimKey = key; 1.3837 + if (isUpperCase(vimKey) && modifier == 'Ctrl') { 1.3838 + vimKey = vimKey.toLowerCase(); 1.3839 + } 1.3840 + if (modifier) { 1.3841 + // Vim will parse modifier+key combination as a single key. 1.3842 + vimKey = modifier.charAt(0) + '-' + vimKey; 1.3843 + } 1.3844 + var specialKey = ({Enter:'CR',Backspace:'BS',Delete:'Del'})[vimKey]; 1.3845 + vimKey = specialKey ? specialKey : vimKey; 1.3846 + vimKey = vimKey.length > 1 ? '<'+ vimKey + '>' : vimKey; 1.3847 + return vimKey; 1.3848 + } 1.3849 + 1.3850 + // Closure to bind CodeMirror, key, modifier. 1.3851 + function keyMapper(vimKey) { 1.3852 + return function(cm) { 1.3853 + CodeMirror.Vim.handleKey(cm, vimKey); 1.3854 + }; 1.3855 + } 1.3856 + 1.3857 + var cmToVimKeymap = { 1.3858 + 'nofallthrough': true, 1.3859 + 'style': 'fat-cursor' 1.3860 + }; 1.3861 + function bindKeys(keys, modifier) { 1.3862 + for (var i = 0; i < keys.length; i++) { 1.3863 + var key = keys[i]; 1.3864 + if (!modifier && key.length == 1) { 1.3865 + // Wrap all keys without modifiers with '' to identify them by their 1.3866 + // key characters instead of key identifiers. 1.3867 + key = "'" + key + "'"; 1.3868 + } 1.3869 + var vimKey = cmKeyToVimKey(keys[i], modifier); 1.3870 + var cmKey = modifier ? modifier + '-' + key : key; 1.3871 + cmToVimKeymap[cmKey] = keyMapper(vimKey); 1.3872 + } 1.3873 + } 1.3874 + bindKeys(upperCaseAlphabet); 1.3875 + bindKeys(lowerCaseAlphabet); 1.3876 + bindKeys(upperCaseAlphabet, 'Ctrl'); 1.3877 + bindKeys(specialSymbols); 1.3878 + bindKeys(specialSymbols, 'Ctrl'); 1.3879 + bindKeys(numbers); 1.3880 + bindKeys(numbers, 'Ctrl'); 1.3881 + bindKeys(specialKeys); 1.3882 + bindKeys(specialKeys, 'Ctrl'); 1.3883 + return cmToVimKeymap; 1.3884 + } 1.3885 + CodeMirror.keyMap.vim = buildVimKeyMap(); 1.3886 + 1.3887 + function exitInsertMode(cm) { 1.3888 + var vim = cm.state.vim; 1.3889 + var macroModeState = vimGlobalState.macroModeState; 1.3890 + var isPlaying = macroModeState.isPlaying; 1.3891 + if (!isPlaying) { 1.3892 + cm.off('change', onChange); 1.3893 + cm.off('cursorActivity', onCursorActivity); 1.3894 + CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown); 1.3895 + } 1.3896 + if (!isPlaying && vim.insertModeRepeat > 1) { 1.3897 + // Perform insert mode repeat for commands like 3,a and 3,o. 1.3898 + repeatLastEdit(cm, vim, vim.insertModeRepeat - 1, 1.3899 + true /** repeatForInsert */); 1.3900 + vim.lastEditInputState.repeatOverride = vim.insertModeRepeat; 1.3901 + } 1.3902 + delete vim.insertModeRepeat; 1.3903 + cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1); 1.3904 + vim.insertMode = false; 1.3905 + cm.setOption('keyMap', 'vim'); 1.3906 + cm.setOption('disableInput', true); 1.3907 + cm.toggleOverwrite(false); // exit replace mode if we were in it. 1.3908 + CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); 1.3909 + if (macroModeState.isRecording) { 1.3910 + logInsertModeChange(macroModeState); 1.3911 + } 1.3912 + } 1.3913 + 1.3914 + CodeMirror.keyMap['vim-insert'] = { 1.3915 + // TODO: override navigation keys so that Esc will cancel automatic 1.3916 + // indentation from o, O, i_<CR> 1.3917 + 'Esc': exitInsertMode, 1.3918 + 'Ctrl-[': exitInsertMode, 1.3919 + 'Ctrl-C': exitInsertMode, 1.3920 + 'Ctrl-N': 'autocomplete', 1.3921 + 'Ctrl-P': 'autocomplete', 1.3922 + 'Enter': function(cm) { 1.3923 + var fn = CodeMirror.commands.newlineAndIndentContinueComment || 1.3924 + CodeMirror.commands.newlineAndIndent; 1.3925 + fn(cm); 1.3926 + }, 1.3927 + fallthrough: ['default'] 1.3928 + }; 1.3929 + 1.3930 + CodeMirror.keyMap['vim-replace'] = { 1.3931 + 'Backspace': 'goCharLeft', 1.3932 + fallthrough: ['vim-insert'] 1.3933 + }; 1.3934 + 1.3935 + function executeMacroRegister(cm, vim, macroModeState, registerName) { 1.3936 + var register = vimGlobalState.registerController.getRegister(registerName); 1.3937 + var keyBuffer = register.keyBuffer; 1.3938 + var imc = 0; 1.3939 + macroModeState.isPlaying = true; 1.3940 + for (var i = 0; i < keyBuffer.length; i++) { 1.3941 + var text = keyBuffer[i]; 1.3942 + var match, key; 1.3943 + while (text) { 1.3944 + // Pull off one command key, which is either a single character 1.3945 + // or a special sequence wrapped in '<' and '>', e.g. '<Space>'. 1.3946 + match = (/<\w+-.+?>|<\w+>|./).exec(text); 1.3947 + key = match[0]; 1.3948 + text = text.substring(match.index + key.length); 1.3949 + CodeMirror.Vim.handleKey(cm, key); 1.3950 + if (vim.insertMode) { 1.3951 + repeatInsertModeChanges( 1.3952 + cm, register.insertModeChanges[imc++].changes, 1); 1.3953 + exitInsertMode(cm); 1.3954 + } 1.3955 + } 1.3956 + }; 1.3957 + macroModeState.isPlaying = false; 1.3958 + } 1.3959 + 1.3960 + function logKey(macroModeState, key) { 1.3961 + if (macroModeState.isPlaying) { return; } 1.3962 + var registerName = macroModeState.latestRegister; 1.3963 + var register = vimGlobalState.registerController.getRegister(registerName); 1.3964 + if (register) { 1.3965 + register.pushText(key); 1.3966 + } 1.3967 + } 1.3968 + 1.3969 + function logInsertModeChange(macroModeState) { 1.3970 + if (macroModeState.isPlaying) { return; } 1.3971 + var registerName = macroModeState.latestRegister; 1.3972 + var register = vimGlobalState.registerController.getRegister(registerName); 1.3973 + if (register) { 1.3974 + register.pushInsertModeChanges(macroModeState.lastInsertModeChanges); 1.3975 + } 1.3976 + } 1.3977 + 1.3978 + /** 1.3979 + * Listens for changes made in insert mode. 1.3980 + * Should only be active in insert mode. 1.3981 + */ 1.3982 + function onChange(_cm, changeObj) { 1.3983 + var macroModeState = vimGlobalState.macroModeState; 1.3984 + var lastChange = macroModeState.lastInsertModeChanges; 1.3985 + if (!macroModeState.isPlaying) { 1.3986 + while(changeObj) { 1.3987 + lastChange.expectCursorActivityForChange = true; 1.3988 + if (changeObj.origin == '+input' || changeObj.origin == 'paste' 1.3989 + || changeObj.origin === undefined /* only in testing */) { 1.3990 + var text = changeObj.text.join('\n'); 1.3991 + lastChange.changes.push(text); 1.3992 + } 1.3993 + // Change objects may be chained with next. 1.3994 + changeObj = changeObj.next; 1.3995 + } 1.3996 + } 1.3997 + } 1.3998 + 1.3999 + /** 1.4000 + * Listens for any kind of cursor activity on CodeMirror. 1.4001 + * - For tracking cursor activity in insert mode. 1.4002 + * - Should only be active in insert mode. 1.4003 + */ 1.4004 + function onCursorActivity() { 1.4005 + var macroModeState = vimGlobalState.macroModeState; 1.4006 + if (macroModeState.isPlaying) { return; } 1.4007 + var lastChange = macroModeState.lastInsertModeChanges; 1.4008 + if (lastChange.expectCursorActivityForChange) { 1.4009 + lastChange.expectCursorActivityForChange = false; 1.4010 + } else { 1.4011 + // Cursor moved outside the context of an edit. Reset the change. 1.4012 + lastChange.changes = []; 1.4013 + } 1.4014 + } 1.4015 + 1.4016 + /** Wrapper for special keys pressed in insert mode */ 1.4017 + function InsertModeKey(keyName) { 1.4018 + this.keyName = keyName; 1.4019 + } 1.4020 + 1.4021 + /** 1.4022 + * Handles raw key down events from the text area. 1.4023 + * - Should only be active in insert mode. 1.4024 + * - For recording deletes in insert mode. 1.4025 + */ 1.4026 + function onKeyEventTargetKeyDown(e) { 1.4027 + var macroModeState = vimGlobalState.macroModeState; 1.4028 + var lastChange = macroModeState.lastInsertModeChanges; 1.4029 + var keyName = CodeMirror.keyName(e); 1.4030 + function onKeyFound() { 1.4031 + lastChange.changes.push(new InsertModeKey(keyName)); 1.4032 + return true; 1.4033 + } 1.4034 + if (keyName.indexOf('Delete') != -1 || keyName.indexOf('Backspace') != -1) { 1.4035 + CodeMirror.lookupKey(keyName, ['vim-insert'], onKeyFound); 1.4036 + } 1.4037 + } 1.4038 + 1.4039 + /** 1.4040 + * Repeats the last edit, which includes exactly 1 command and at most 1 1.4041 + * insert. Operator and motion commands are read from lastEditInputState, 1.4042 + * while action commands are read from lastEditActionCommand. 1.4043 + * 1.4044 + * If repeatForInsert is true, then the function was called by 1.4045 + * exitInsertMode to repeat the insert mode changes the user just made. The 1.4046 + * corresponding enterInsertMode call was made with a count. 1.4047 + */ 1.4048 + function repeatLastEdit(cm, vim, repeat, repeatForInsert) { 1.4049 + var macroModeState = vimGlobalState.macroModeState; 1.4050 + macroModeState.isPlaying = true; 1.4051 + var isAction = !!vim.lastEditActionCommand; 1.4052 + var cachedInputState = vim.inputState; 1.4053 + function repeatCommand() { 1.4054 + if (isAction) { 1.4055 + commandDispatcher.processAction(cm, vim, vim.lastEditActionCommand); 1.4056 + } else { 1.4057 + commandDispatcher.evalInput(cm, vim); 1.4058 + } 1.4059 + } 1.4060 + function repeatInsert(repeat) { 1.4061 + if (macroModeState.lastInsertModeChanges.changes.length > 0) { 1.4062 + // For some reason, repeat cw in desktop VIM does not repeat 1.4063 + // insert mode changes. Will conform to that behavior. 1.4064 + repeat = !vim.lastEditActionCommand ? 1 : repeat; 1.4065 + var changeObject = macroModeState.lastInsertModeChanges; 1.4066 + // This isn't strictly necessary, but since lastInsertModeChanges is 1.4067 + // supposed to be immutable during replay, this helps catch bugs. 1.4068 + macroModeState.lastInsertModeChanges = {}; 1.4069 + repeatInsertModeChanges(cm, changeObject.changes, repeat); 1.4070 + macroModeState.lastInsertModeChanges = changeObject; 1.4071 + } 1.4072 + } 1.4073 + vim.inputState = vim.lastEditInputState; 1.4074 + if (isAction && vim.lastEditActionCommand.interlaceInsertRepeat) { 1.4075 + // o and O repeat have to be interlaced with insert repeats so that the 1.4076 + // insertions appear on separate lines instead of the last line. 1.4077 + for (var i = 0; i < repeat; i++) { 1.4078 + repeatCommand(); 1.4079 + repeatInsert(1); 1.4080 + } 1.4081 + } else { 1.4082 + if (!repeatForInsert) { 1.4083 + // Hack to get the cursor to end up at the right place. If I is 1.4084 + // repeated in insert mode repeat, cursor will be 1 insert 1.4085 + // change set left of where it should be. 1.4086 + repeatCommand(); 1.4087 + } 1.4088 + repeatInsert(repeat); 1.4089 + } 1.4090 + vim.inputState = cachedInputState; 1.4091 + if (vim.insertMode && !repeatForInsert) { 1.4092 + // Don't exit insert mode twice. If repeatForInsert is set, then we 1.4093 + // were called by an exitInsertMode call lower on the stack. 1.4094 + exitInsertMode(cm); 1.4095 + } 1.4096 + macroModeState.isPlaying = false; 1.4097 + }; 1.4098 + 1.4099 + function repeatInsertModeChanges(cm, changes, repeat) { 1.4100 + function keyHandler(binding) { 1.4101 + if (typeof binding == 'string') { 1.4102 + CodeMirror.commands[binding](cm); 1.4103 + } else { 1.4104 + binding(cm); 1.4105 + } 1.4106 + return true; 1.4107 + } 1.4108 + for (var i = 0; i < repeat; i++) { 1.4109 + for (var j = 0; j < changes.length; j++) { 1.4110 + var change = changes[j]; 1.4111 + if (change instanceof InsertModeKey) { 1.4112 + CodeMirror.lookupKey(change.keyName, ['vim-insert'], keyHandler); 1.4113 + } else { 1.4114 + var cur = cm.getCursor(); 1.4115 + cm.replaceRange(change, cur, cur); 1.4116 + } 1.4117 + } 1.4118 + } 1.4119 + } 1.4120 + 1.4121 + resetVimGlobalState(); 1.4122 + return vimApi; 1.4123 + }; 1.4124 + // Initialize Vim and make it available as an API. 1.4125 + CodeMirror.Vim = Vim(); 1.4126 +});