vim.js 82 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298
  1. /**
  2. * Supported keybindings:
  3. *
  4. * Motion:
  5. * h, j, k, l
  6. * e, E, w, W, b, B, ge, gE
  7. * f<character>, F<character>, t<character>, T<character>
  8. * $, ^, 0
  9. * gg, G
  10. * %
  11. * '<character>, `<character>
  12. *
  13. * Operator:
  14. * d, y, c
  15. * dd, yy, cc
  16. * g~, g~g~
  17. * >, <, >>, <<
  18. *
  19. * Operator-Motion:
  20. * x, X, D, Y, C, ~
  21. *
  22. * Action:
  23. * a, i, s, A, I, S, o, O
  24. * J
  25. * u, Ctrl-r
  26. * m<character>
  27. * r<character>
  28. *
  29. * Modes:
  30. * ESC - leave insert mode, visual mode, and clear input state.
  31. * Ctrl-[, Ctrl-c - same as ESC.
  32. *
  33. * Registers: unamed, -, a-z, A-Z, 0-9
  34. * (Does not respect the special case for number registers when delete
  35. * operator is made with these commands: %, (, ), , /, ?, n, N, {, } )
  36. * TODO: Implement the remaining registers.
  37. * Marks: a-z, A-Z, and 0-9
  38. * TODO: Implement the remaining special marks. They have more complex
  39. * behavior.
  40. *
  41. * Code structure:
  42. * 1. Default keymap
  43. * 2. Variable declarations and short basic helpers
  44. * 3. Instance (External API) implementation
  45. * 4. Internal state tracking objects (input state, counter) implementation
  46. * and instanstiation
  47. * 5. Key handler (the main command dispatcher) implementation
  48. * 6. Motion, operator, and action implementations
  49. * 7. Helper functions for the key handler, motions, operators, and actions
  50. * 8. Set up Vim to work as a keymap for CodeMirror.
  51. */
  52. (function() {
  53. 'use strict';
  54. var defaultKeymap = [
  55. // Key to key mapping. This goes first to make it possible to override
  56. // existing mappings.
  57. { keys: ['Left'], type: 'keyToKey', toKeys: ['h'] },
  58. { keys: ['Right'], type: 'keyToKey', toKeys: ['l'] },
  59. { keys: ['Up'], type: 'keyToKey', toKeys: ['k'] },
  60. { keys: ['Down'], type: 'keyToKey', toKeys: ['j'] },
  61. { keys: ['Space'], type: 'keyToKey', toKeys: ['l'] },
  62. { keys: ['Backspace'], type: 'keyToKey', toKeys: ['h'] },
  63. { keys: ['Ctrl-Space'], type: 'keyToKey', toKeys: ['W'] },
  64. { keys: ['Ctrl-Backspace'], type: 'keyToKey', toKeys: ['B'] },
  65. { keys: ['Shift-Space'], type: 'keyToKey', toKeys: ['w'] },
  66. { keys: ['Shift-Backspace'], type: 'keyToKey', toKeys: ['b'] },
  67. { keys: ['Ctrl-n'], type: 'keyToKey', toKeys: ['j'] },
  68. { keys: ['Ctrl-p'], type: 'keyToKey', toKeys: ['k'] },
  69. { keys: ['Ctrl-['], type: 'keyToKey', toKeys: ['Esc'] },
  70. { keys: ['Ctrl-c'], type: 'keyToKey', toKeys: ['Esc'] },
  71. { keys: ['s'], type: 'keyToKey', toKeys: ['c', 'l'] },
  72. { keys: ['S'], type: 'keyToKey', toKeys: ['c', 'c'] },
  73. { keys: ['Home'], type: 'keyToKey', toKeys: ['0'] },
  74. { keys: ['End'], type: 'keyToKey', toKeys: ['$'] },
  75. { keys: ['PageUp'], type: 'keyToKey', toKeys: ['Ctrl-b'] },
  76. { keys: ['PageDown'], type: 'keyToKey', toKeys: ['Ctrl-f'] },
  77. // Motions
  78. { keys: ['h'], type: 'motion',
  79. motion: 'moveByCharacters',
  80. motionArgs: { forward: false }},
  81. { keys: ['l'], type: 'motion',
  82. motion: 'moveByCharacters',
  83. motionArgs: { forward: true }},
  84. { keys: ['j'], type: 'motion',
  85. motion: 'moveByLines',
  86. motionArgs: { forward: true, linewise: true }},
  87. { keys: ['k'], type: 'motion',
  88. motion: 'moveByLines',
  89. motionArgs: { forward: false, linewise: true }},
  90. { keys: ['w'], type: 'motion',
  91. motion: 'moveByWords',
  92. motionArgs: { forward: true, wordEnd: false }},
  93. { keys: ['W'], type: 'motion',
  94. motion: 'moveByWords',
  95. motionArgs: { forward: true, wordEnd: false, bigWord: true }},
  96. { keys: ['e'], type: 'motion',
  97. motion: 'moveByWords',
  98. motionArgs: { forward: true, wordEnd: true, inclusive: true }},
  99. { keys: ['E'], type: 'motion',
  100. motion: 'moveByWords',
  101. motionArgs: { forward: true, wordEnd: true, bigWord: true,
  102. inclusive: true }},
  103. { keys: ['b'], type: 'motion',
  104. motion: 'moveByWords',
  105. motionArgs: { forward: false, wordEnd: false }},
  106. { keys: ['B'], type: 'motion',
  107. motion: 'moveByWords',
  108. motionArgs: { forward: false, wordEnd: false, bigWord: true }},
  109. { keys: ['g', 'e'], type: 'motion',
  110. motion: 'moveByWords',
  111. motionArgs: { forward: false, wordEnd: true, inclusive: true }},
  112. { keys: ['g', 'E'], type: 'motion',
  113. motion: 'moveByWords',
  114. motionArgs: { forward: false, wordEnd: true, bigWord: true,
  115. inclusive: true }},
  116. { keys: ['Ctrl-f'], type: 'motion',
  117. motion: 'moveByPage', motionArgs: { forward: true }},
  118. { keys: ['Ctrl-b'], type: 'motion',
  119. motion: 'moveByPage', motionArgs: { forward: false }},
  120. { keys: ['g', 'g'], type: 'motion',
  121. motion: 'moveToLineOrEdgeOfDocument',
  122. motionArgs: { forward: false, explicitRepeat: true, linewise: true }},
  123. { keys: ['G'], type: 'motion',
  124. motion: 'moveToLineOrEdgeOfDocument',
  125. motionArgs: { forward: true, explicitRepeat: true, linewise: true }},
  126. { keys: ['0'], type: 'motion', motion: 'moveToStartOfLine' },
  127. { keys: ['^'], type: 'motion',
  128. motion: 'moveToFirstNonWhiteSpaceCharacter' },
  129. { keys: ['$'], type: 'motion',
  130. motion: 'moveToEol',
  131. motionArgs: { inclusive: true }},
  132. { keys: ['%'], type: 'motion',
  133. motion: 'moveToMatchedSymbol',
  134. motionArgs: { inclusive: true }},
  135. { keys: ['f', 'character'], type: 'motion',
  136. motion: 'moveToCharacter',
  137. motionArgs: { forward: true , inclusive: true }},
  138. { keys: ['F', 'character'], type: 'motion',
  139. motion: 'moveToCharacter',
  140. motionArgs: { forward: false }},
  141. { keys: ['t', 'character'], type: 'motion',
  142. motion: 'moveTillCharacter',
  143. motionArgs: { forward: true, inclusive: true }},
  144. { keys: ['T', 'character'], type: 'motion',
  145. motion: 'moveTillCharacter',
  146. motionArgs: { forward: false }},
  147. { keys: ['\'', 'character'], type: 'motion', motion: 'goToMark' },
  148. { keys: ['`', 'character'], type: 'motion', motion: 'goToMark' },
  149. { keys: ['|'], type: 'motion',
  150. motion: 'moveToColumn',
  151. motionArgs: { }},
  152. // Operators
  153. { keys: ['d'], type: 'operator', operator: 'delete' },
  154. { keys: ['y'], type: 'operator', operator: 'yank' },
  155. { keys: ['c'], type: 'operator', operator: 'change',
  156. operatorArgs: { enterInsertMode: true } },
  157. { keys: ['>'], type: 'operator', operator: 'indent',
  158. operatorArgs: { indentRight: true }},
  159. { keys: ['<'], type: 'operator', operator: 'indent',
  160. operatorArgs: { indentRight: false }},
  161. { keys: ['g', '~'], type: 'operator', operator: 'swapcase' },
  162. { keys: ['n'], type: 'motion', motion: 'findNext' },
  163. { keys: ['N'], type: 'motion', motion: 'findPrev' },
  164. // Operator-Motion dual commands
  165. { keys: ['x'], type: 'operatorMotion', operator: 'delete',
  166. motion: 'moveByCharacters', motionArgs: { forward: true },
  167. operatorMotionArgs: { visualLine: false }},
  168. { keys: ['X'], type: 'operatorMotion', operator: 'delete',
  169. motion: 'moveByCharacters', motionArgs: { forward: false },
  170. operatorMotionArgs: { visualLine: true }},
  171. { keys: ['D'], type: 'operatorMotion', operator: 'delete',
  172. motion: 'moveToEol', motionArgs: { inclusive: true },
  173. operatorMotionArgs: { visualLine: true }},
  174. { keys: ['Y'], type: 'operatorMotion', operator: 'yank',
  175. motion: 'moveToEol', motionArgs: { inclusive: true },
  176. operatorMotionArgs: { visualLine: true }},
  177. { keys: ['C'], type: 'operatorMotion',
  178. operator: 'change', operatorArgs: { enterInsertMode: true },
  179. motion: 'moveToEol', motionArgs: { inclusive: true },
  180. operatorMotionArgs: { visualLine: true }},
  181. { keys: ['~'], type: 'operatorMotion', operator: 'swapcase',
  182. motion: 'moveByCharacters', motionArgs: { forward: true }},
  183. // Actions
  184. { keys: ['a'], type: 'action', action: 'enterInsertMode',
  185. actionArgs: { insertAt: 'charAfter' }},
  186. { keys: ['A'], type: 'action', action: 'enterInsertMode',
  187. actionArgs: { insertAt: 'eol' }},
  188. { keys: ['i'], type: 'action', action: 'enterInsertMode' },
  189. { keys: ['I'], type: 'action', action: 'enterInsertMode',
  190. motion: 'moveToFirstNonWhiteSpaceCharacter' },
  191. { keys: ['o'], type: 'action', action: 'newLineAndEnterInsertMode',
  192. actionArgs: { after: true }},
  193. { keys: ['O'], type: 'action', action: 'newLineAndEnterInsertMode',
  194. actionArgs: { after: false }},
  195. { keys: ['v'], type: 'action', action: 'toggleVisualMode' },
  196. { keys: ['V'], type: 'action', action: 'toggleVisualMode',
  197. actionArgs: { linewise: true }},
  198. { keys: ['J'], type: 'action', action: 'joinLines' },
  199. { keys: ['p'], type: 'action', action: 'paste',
  200. actionArgs: { after: true }},
  201. { keys: ['P'], type: 'action', action: 'paste',
  202. actionArgs: { after: false }},
  203. { keys: ['r', 'character'], type: 'action', action: 'replace' },
  204. { keys: ['u'], type: 'action', action: 'undo' },
  205. { keys: ['Ctrl-r'], type: 'action', action: 'redo' },
  206. { keys: ['m', 'character'], type: 'action', action: 'setMark' },
  207. { keys: ['\"', 'character'], type: 'action', action: 'setRegister' },
  208. { keys: [',', '/'], type: 'action', action: 'clearSearchHighlight' },
  209. // Text object motions
  210. { keys: ['a', 'character'], type: 'motion',
  211. motion: 'textObjectManipulation' },
  212. { keys: ['i', 'character'], type: 'motion',
  213. motion: 'textObjectManipulation',
  214. motionArgs: { textObjectInner: true }},
  215. // Search
  216. { keys: ['/'], type: 'search',
  217. searchArgs: { forward: true, querySrc: 'prompt' }},
  218. { keys: ['?'], type: 'search',
  219. searchArgs: { forward: false, querySrc: 'prompt' }},
  220. { keys: ['*'], type: 'search',
  221. searchArgs: { forward: true, querySrc: 'wordUnderCursor' }},
  222. { keys: ['#'], type: 'search',
  223. searchArgs: { forward: false, querySrc: 'wordUnderCursor' }},
  224. // Ex command
  225. { keys: [':'], type: 'ex' }
  226. ];
  227. var Vim = function() {
  228. var alphabetRegex = /[A-Za-z]/;
  229. var numberRegex = /[\d]/;
  230. var whiteSpaceRegex = /\s/;
  231. var wordRegexp = [(/\w/), (/[^\w\s]/)], bigWordRegexp = [(/\S/)];
  232. function makeKeyRange(start, size) {
  233. var keys = [];
  234. for (var i = start; i < start + size; i++) {
  235. keys.push(String.fromCharCode(i));
  236. }
  237. return keys;
  238. }
  239. var upperCaseAlphabet = makeKeyRange(65, 26);
  240. var lowerCaseAlphabet = makeKeyRange(97, 26);
  241. var numbers = makeKeyRange(48, 10);
  242. var SPECIAL_SYMBOLS = '~`!@#$%^&*()_-+=[{}]\\|/?.,<>:;\"\'';
  243. var specialSymbols = SPECIAL_SYMBOLS.split('');
  244. var specialKeys = ['Left', 'Right', 'Up', 'Down', 'Space', 'Backspace',
  245. 'Esc', 'Home', 'End', 'PageUp', 'PageDown'];
  246. var validMarks = upperCaseAlphabet.concat(lowerCaseAlphabet).concat(
  247. numbers);
  248. var validRegisters = upperCaseAlphabet.concat(lowerCaseAlphabet).concat(
  249. numbers).concat('-\"'.split(''));
  250. function isAlphabet(k) {
  251. return alphabetRegex.test(k);
  252. }
  253. function isLine(cm, line) {
  254. return line >= 0 && line < cm.lineCount();
  255. }
  256. function isLowerCase(k) {
  257. return (/^[a-z]$/).test(k);
  258. }
  259. function isMatchableSymbol(k) {
  260. return '()[]{}'.indexOf(k) != -1;
  261. }
  262. function isNumber(k) {
  263. return numberRegex.test(k);
  264. }
  265. function isUpperCase(k) {
  266. return (/^[A-Z]$/).test(k);
  267. }
  268. function isAlphanumeric(k) {
  269. return (/^[\w]$/).test(k);
  270. }
  271. function isWhiteSpace(k) {
  272. return whiteSpaceRegex.test(k);
  273. }
  274. function isWhiteSpaceString(k) {
  275. return (/^\s*$/).test(k);
  276. }
  277. function inRangeInclusive(x, start, end) {
  278. return x >= start && x <= end;
  279. }
  280. function inArray(val, arr) {
  281. for (var i = 0; i < arr.length; i++) {
  282. if (arr[i] == val) {
  283. return true;
  284. }
  285. }
  286. return false;
  287. }
  288. // Global Vim state. Call getVimGlobalState to get and initialize.
  289. var vimGlobalState;
  290. function getVimGlobalState() {
  291. if (!vimGlobalState) {
  292. vimGlobalState = {
  293. // The current search query.
  294. searchQuery: null,
  295. // Whether we are searching backwards.
  296. searchIsReversed: false,
  297. registerController: new RegisterController({})
  298. };
  299. }
  300. return vimGlobalState;
  301. }
  302. function getVimState(cm) {
  303. if (!cm.vimState) {
  304. // Store instance state in the CodeMirror object.
  305. cm.vimState = {
  306. inputState: new InputState(),
  307. // When using jk for navigation, if you move from a longer line to a
  308. // shorter line, the cursor may clip to the end of the shorter line.
  309. // If j is pressed again and cursor goes to the next line, the
  310. // cursor should go back to its horizontal position on the longer
  311. // line if it can. This is to keep track of the horizontal position.
  312. lastHPos: -1,
  313. // The last motion command run. Cleared if a non-motion command gets
  314. // executed in between.
  315. lastMotion: null,
  316. marks: {},
  317. visualMode: false,
  318. // If we are in visual line mode. No effect if visualMode is false.
  319. visualLine: false
  320. };
  321. }
  322. return cm.vimState;
  323. }
  324. var vimApi= {
  325. buildKeyMap: function() {
  326. // TODO: Convert keymap into dictionary format for fast lookup.
  327. },
  328. // Testing hook, though it might be useful to expose the register
  329. // controller anyways.
  330. getRegisterController: function() {
  331. return getVimGlobalState().registerController;
  332. },
  333. // Testing hook.
  334. clearVimGlobalState_: function() {
  335. vimGlobalState = null;
  336. },
  337. map: function(lhs, rhs) {
  338. // Add user defined key bindings.
  339. exCommandDispatcher.map(lhs, rhs);
  340. },
  341. // Initializes vim state variable on the CodeMirror object. Should only be
  342. // called lazily by handleKey or for testing.
  343. maybeInitState: function(cm) {
  344. getVimState(cm);
  345. },
  346. // This is the outermost function called by CodeMirror, after keys have
  347. // been mapped to their Vim equivalents.
  348. handleKey: function(cm, key) {
  349. var command;
  350. var vim = getVimState(cm);
  351. if (key == 'Esc') {
  352. // Clear input state and get back to normal mode.
  353. vim.inputState.reset();
  354. if (vim.visualMode) {
  355. exitVisualMode(cm, vim);
  356. }
  357. return;
  358. }
  359. if (vim.visualMode &&
  360. cursorEqual(cm.getCursor('head'), cm.getCursor('anchor'))) {
  361. // The selection was cleared. Exit visual mode.
  362. exitVisualMode(cm, vim);
  363. }
  364. if (!vim.visualMode &&
  365. !cursorEqual(cm.getCursor('head'), cm.getCursor('anchor'))) {
  366. vim.visualMode = true;
  367. vim.visualLine = false;
  368. }
  369. if (key != '0' || (key == '0' && vim.inputState.getRepeat() === 0)) {
  370. // Have to special case 0 since it's both a motion and a number.
  371. command = commandDispatcher.matchCommand(key, defaultKeymap, vim);
  372. }
  373. if (!command) {
  374. if (isNumber(key)) {
  375. // Increment count unless count is 0 and key is 0.
  376. vim.inputState.pushRepeatDigit(key);
  377. }
  378. return;
  379. }
  380. if (command.type == 'keyToKey') {
  381. // TODO: prevent infinite recursion.
  382. for (var i = 0; i < command.toKeys.length; i++) {
  383. this.handleKey(cm, command.toKeys[i]);
  384. }
  385. } else {
  386. commandDispatcher.processCommand(cm, vim, command);
  387. }
  388. }
  389. };
  390. // Represents the current input state.
  391. function InputState() {
  392. this.reset();
  393. }
  394. InputState.prototype.reset = function() {
  395. this.prefixRepeat = [];
  396. this.motionRepeat = [];
  397. this.operator = null;
  398. this.operatorArgs = null;
  399. this.motion = null;
  400. this.motionArgs = null;
  401. this.keyBuffer = []; // For matching multi-key commands.
  402. this.registerName = null; // Defaults to the unamed register.
  403. };
  404. InputState.prototype.pushRepeatDigit = function(n) {
  405. if (!this.operator) {
  406. this.prefixRepeat = this.prefixRepeat.concat(n);
  407. } else {
  408. this.motionRepeat = this.motionRepeat.concat(n);
  409. }
  410. };
  411. InputState.prototype.getRepeat = function() {
  412. var repeat = 0;
  413. if (this.prefixRepeat.length > 0 || this.motionRepeat.length > 0) {
  414. repeat = 1;
  415. if (this.prefixRepeat.length > 0) {
  416. repeat *= parseInt(this.prefixRepeat.join(''), 10);
  417. }
  418. if (this.motionRepeat.length > 0) {
  419. repeat *= parseInt(this.motionRepeat.join(''), 10);
  420. }
  421. }
  422. return repeat;
  423. };
  424. /*
  425. * Register stores information about copy and paste registers. Besides
  426. * text, a register must store whether it is linewise (i.e., when it is
  427. * pasted, should it insert itself into a new line, or should the text be
  428. * inserted at the cursor position.)
  429. */
  430. function Register(text, linewise) {
  431. this.clear();
  432. if (text) {
  433. this.set(text, linewise);
  434. }
  435. }
  436. Register.prototype = {
  437. set: function(text, linewise) {
  438. this.text = text;
  439. this.linewise = !!linewise;
  440. },
  441. append: function(text, linewise) {
  442. // if this register has ever been set to linewise, use linewise.
  443. if (linewise || this.linewise) {
  444. this.text += '\n' + text;
  445. this.linewise = true;
  446. } else {
  447. this.text += text;
  448. }
  449. },
  450. clear: function() {
  451. this.text = '';
  452. this.linewise = false;
  453. },
  454. toString: function() { return this.text; }
  455. };
  456. /*
  457. * vim registers allow you to keep many independent copy and paste buffers.
  458. * See http://usevim.com/2012/04/13/registers/ for an introduction.
  459. *
  460. * RegisterController keeps the state of all the registers. An initial
  461. * state may be passed in. The unnamed register '"' will always be
  462. * overridden.
  463. */
  464. function RegisterController(registers) {
  465. this.registers = registers;
  466. this.unamedRegister = registers['\"'] = new Register();
  467. }
  468. RegisterController.prototype = {
  469. pushText: function(registerName, operator, text, linewise) {
  470. // Lowercase and uppercase registers refer to the same register.
  471. // Uppercase just means append.
  472. var register = this.isValidRegister(registerName) ?
  473. this.getRegister(registerName) : null;
  474. // if no register/an invalid register was specified, things go to the
  475. // default registers
  476. if (!register) {
  477. switch (operator) {
  478. case 'yank':
  479. // The 0 register contains the text from the most recent yank.
  480. this.registers['0'] = new Register(text, linewise);
  481. break;
  482. case 'delete':
  483. case 'change':
  484. if (text.indexOf('\n') == -1) {
  485. // Delete less than 1 line. Update the small delete register.
  486. this.registers['-'] = new Register(text, linewise);
  487. } else {
  488. // Shift down the contents of the numbered registers and put the
  489. // deleted text into register 1.
  490. this.shiftNumericRegisters_();
  491. this.registers['1'] = new Register(text, linewise);
  492. }
  493. break;
  494. }
  495. // Make sure the unnamed register is set to what just happened
  496. this.unamedRegister.set(text, linewise);
  497. return;
  498. }
  499. // If we've gotten to this point, we've actually specified a register
  500. var append = isUpperCase(registerName);
  501. if (append) {
  502. register.append(text, linewise);
  503. // The unamed register always has the same value as the last used
  504. // register.
  505. this.unamedRegister.append(text, linewise);
  506. } else {
  507. register.set(text, linewise);
  508. this.unamedRegister.set(text, linewise);
  509. }
  510. },
  511. // Gets the register named @name. If one of @name doesn't already exist,
  512. // create it. If @name is invalid, return the unamedRegister.
  513. getRegister: function(name) {
  514. if (!this.isValidRegister(name)) {
  515. return this.unamedRegister;
  516. }
  517. name = name.toLowerCase();
  518. if (!this.registers[name]) {
  519. this.registers[name] = new Register();
  520. }
  521. return this.registers[name];
  522. },
  523. isValidRegister: function(name) {
  524. return name && inArray(name, validRegisters);
  525. },
  526. shiftNumericRegisters_: function() {
  527. for (var i = 9; i >= 2; i--) {
  528. this.registers[i] = this.getRegister('' + (i - 1));
  529. }
  530. }
  531. };
  532. var commandDispatcher = {
  533. matchCommand: function(key, keyMap, vim) {
  534. var inputState = vim.inputState;
  535. var keys = inputState.keyBuffer.concat(key);
  536. for (var i = 0; i < keyMap.length; i++) {
  537. var command = keyMap[i];
  538. if (matchKeysPartial(keys, command.keys)) {
  539. if (keys.length < command.keys.length) {
  540. // Matches part of a multi-key command. Buffer and wait for next
  541. // stroke.
  542. inputState.keyBuffer.push(key);
  543. return null;
  544. } else {
  545. if (inputState.operator && command.type == 'action') {
  546. // Ignore matched action commands after an operator. Operators
  547. // only operate on motions. This check is really for text
  548. // objects since aW, a[ etcs conflicts with a.
  549. continue;
  550. }
  551. // Matches whole comand. Return the command.
  552. if (command.keys[keys.length - 1] == 'character') {
  553. inputState.selectedCharacter = keys[keys.length - 1];
  554. }
  555. inputState.keyBuffer = [];
  556. return command;
  557. }
  558. }
  559. }
  560. // Clear the buffer since there are no partial matches.
  561. inputState.keyBuffer = [];
  562. return null;
  563. },
  564. processCommand: function(cm, vim, command) {
  565. switch (command.type) {
  566. case 'motion':
  567. this.processMotion(cm, vim, command);
  568. break;
  569. case 'operator':
  570. this.processOperator(cm, vim, command);
  571. break;
  572. case 'operatorMotion':
  573. this.processOperatorMotion(cm, vim, command);
  574. break;
  575. case 'action':
  576. this.processAction(cm, vim, command);
  577. break;
  578. case 'search':
  579. this.processSearch(cm, vim, command);
  580. break;
  581. case 'ex':
  582. case 'keyToEx':
  583. this.processEx(cm, vim, command);
  584. break;
  585. default:
  586. break;
  587. }
  588. },
  589. processMotion: function(cm, vim, command) {
  590. vim.inputState.motion = command.motion;
  591. vim.inputState.motionArgs = copyArgs(command.motionArgs);
  592. this.evalInput(cm, vim);
  593. },
  594. processOperator: function(cm, vim, command) {
  595. var inputState = vim.inputState;
  596. if (inputState.operator) {
  597. if (inputState.operator == command.operator) {
  598. // Typing an operator twice like 'dd' makes the operator operate
  599. // linewise
  600. inputState.motion = 'expandToLine';
  601. inputState.motionArgs = { linewise: true };
  602. this.evalInput(cm, vim);
  603. return;
  604. } else {
  605. // 2 different operators in a row doesn't make sense.
  606. inputState.reset();
  607. }
  608. }
  609. inputState.operator = command.operator;
  610. inputState.operatorArgs = copyArgs(command.operatorArgs);
  611. if (vim.visualMode) {
  612. // Operating on a selection in visual mode. We don't need a motion.
  613. this.evalInput(cm, vim);
  614. }
  615. },
  616. processOperatorMotion: function(cm, vim, command) {
  617. var visualMode = vim.visualMode;
  618. var operatorMotionArgs = copyArgs(command.operatorMotionArgs);
  619. if (operatorMotionArgs) {
  620. // Operator motions may have special behavior in visual mode.
  621. if (visualMode && operatorMotionArgs.visualLine) {
  622. vim.visualLine = true;
  623. }
  624. }
  625. this.processOperator(cm, vim, command);
  626. if (!visualMode) {
  627. this.processMotion(cm, vim, command);
  628. }
  629. },
  630. processAction: function(cm, vim, command) {
  631. var inputState = vim.inputState;
  632. var repeat = inputState.getRepeat();
  633. var repeatIsExplicit = !!repeat;
  634. var actionArgs = copyArgs(command.actionArgs) || {};
  635. if (inputState.selectedCharacter) {
  636. actionArgs.selectedCharacter = inputState.selectedCharacter;
  637. }
  638. // Actions may or may not have motions and operators. Do these first.
  639. if (command.operator) {
  640. this.processOperator(cm, vim, command);
  641. }
  642. if (command.motion) {
  643. this.processMotion(cm, vim, command);
  644. }
  645. if (command.motion || command.operator) {
  646. this.evalInput(cm, vim);
  647. }
  648. actionArgs.repeat = repeat || 1;
  649. actionArgs.repeatIsExplicit = repeatIsExplicit;
  650. actionArgs.registerName = inputState.registerName;
  651. inputState.reset();
  652. vim.lastMotion = null,
  653. actions[command.action](cm, actionArgs, vim);
  654. },
  655. processSearch: function(cm, vim, command) {
  656. if (!cm.getSearchCursor) {
  657. // Search depends on SearchCursor.
  658. return;
  659. }
  660. var forward = command.searchArgs.forward;
  661. getSearchState(cm).setReversed(!forward);
  662. var promptPrefix = (forward) ? '/' : '?';
  663. function handleQuery(query, ignoreCase, smartCase) {
  664. updateSearchQuery(cm, query, ignoreCase, smartCase);
  665. commandDispatcher.processMotion(cm, vim, {
  666. type: 'motion',
  667. motion: 'findNext'
  668. });
  669. }
  670. function onPromptClose(query) {
  671. handleQuery(query, true /** ignoreCase */, true /** smartCase */);
  672. }
  673. switch (command.searchArgs.querySrc) {
  674. case 'prompt':
  675. showPrompt(cm, onPromptClose, promptPrefix, searchPromptDesc);
  676. break;
  677. case 'wordUnderCursor':
  678. var word = expandWordUnderCursor(cm, false /** inclusive */,
  679. true /** forward */, false /** bigWord */,
  680. true /** noSymbol */);
  681. var isKeyword = true;
  682. if (!word) {
  683. word = expandWordUnderCursor(cm, false /** inclusive */,
  684. true /** forward */, false /** bigWord */,
  685. false /** noSymbol */);
  686. isKeyword = false;
  687. }
  688. if (!word) {
  689. return;
  690. }
  691. var query = cm.getLine(word.start.line).substring(word.start.ch,
  692. word.end.ch + 1);
  693. if (isKeyword) {
  694. query = '\\b' + query + '\\b';
  695. } else {
  696. query = escapeRegex(query);
  697. }
  698. cm.setCursor(word.start);
  699. handleQuery(query, true /** ignoreCase */, false /** smartCase */);
  700. break;
  701. }
  702. },
  703. processEx: function(cm, vim, command) {
  704. function onPromptClose(input) {
  705. exCommandDispatcher.processCommand(cm, input);
  706. }
  707. if (command.type == 'keyToEx') {
  708. // Handle user defined Ex to Ex mappings
  709. exCommandDispatcher.processCommand(cm, command.exArgs.input);
  710. } else {
  711. showPrompt(cm, onPromptClose, ':');
  712. }
  713. },
  714. evalInput: function(cm, vim) {
  715. // If the motion comand is set, execute both the operator and motion.
  716. // Otherwise return.
  717. var inputState = vim.inputState;
  718. var motion = inputState.motion;
  719. var motionArgs = inputState.motionArgs || {};
  720. var operator = inputState.operator;
  721. var operatorArgs = inputState.operatorArgs || {};
  722. var registerName = inputState.registerName;
  723. var selectionEnd = cm.getCursor('head');
  724. var selectionStart = cm.getCursor('anchor');
  725. // The difference between cur and selection cursors are that cur is
  726. // being operated on and ignores that there is a selection.
  727. var curStart = copyCursor(selectionEnd);
  728. var curOriginal = copyCursor(curStart);
  729. var curEnd;
  730. var repeat;
  731. if (motionArgs.repeat !== undefined) {
  732. // If motionArgs specifies a repeat, that takes precedence over the
  733. // input state's repeat. Used by Ex mode and can be user defined.
  734. repeat = inputState.motionArgs.repeat;
  735. } else {
  736. repeat = inputState.getRepeat();
  737. }
  738. if (repeat > 0 && motionArgs.explicitRepeat) {
  739. motionArgs.repeatIsExplicit = true;
  740. } else if (motionArgs.noRepeat ||
  741. (!motionArgs.explicitRepeat && repeat === 0)) {
  742. repeat = 1;
  743. motionArgs.repeatIsExplicit = false;
  744. }
  745. if (inputState.selectedCharacter) {
  746. // If there is a character input, stick it in all of the arg arrays.
  747. motionArgs.selectedCharacter = operatorArgs.selectedCharacter =
  748. inputState.selectedCharacter;
  749. }
  750. motionArgs.repeat = repeat;
  751. inputState.reset();
  752. if (motion) {
  753. var motionResult = motions[motion](cm, motionArgs, vim);
  754. vim.lastMotion = motions[motion];
  755. if (!motionResult) {
  756. return;
  757. }
  758. if (motionResult instanceof Array) {
  759. curStart = motionResult[0];
  760. curEnd = motionResult[1];
  761. } else {
  762. curEnd = motionResult;
  763. }
  764. // TODO: Handle null returns from motion commands better.
  765. if (!curEnd) {
  766. curEnd = { ch: curStart.ch, line: curStart.line };
  767. }
  768. if (vim.visualMode) {
  769. // Check if the selection crossed over itself. Will need to shift
  770. // the start point if that happened.
  771. if (cursorIsBefore(selectionStart, selectionEnd) &&
  772. (cursorEqual(selectionStart, curEnd) ||
  773. cursorIsBefore(curEnd, selectionStart))) {
  774. // The end of the selection has moved from after the start to
  775. // before the start. We will shift the start right by 1.
  776. selectionStart.ch += 1;
  777. } else if (cursorIsBefore(selectionEnd, selectionStart) &&
  778. (cursorEqual(selectionStart, curEnd) ||
  779. cursorIsBefore(selectionStart, curEnd))) {
  780. // The opposite happened. We will shift the start left by 1.
  781. selectionStart.ch -= 1;
  782. }
  783. selectionEnd = curEnd;
  784. if (vim.visualLine) {
  785. if (cursorIsBefore(selectionStart, selectionEnd)) {
  786. selectionStart.ch = 0;
  787. selectionEnd.ch = lineLength(cm, selectionEnd.line);
  788. } else {
  789. selectionEnd.ch = 0;
  790. selectionStart.ch = lineLength(cm, selectionStart.line);
  791. }
  792. }
  793. // Need to set the cursor to clear the selection. Otherwise,
  794. // CodeMirror can't figure out that we changed directions...
  795. cm.setCursor(selectionStart);
  796. cm.setSelection(selectionStart, selectionEnd);
  797. } else if (!operator) {
  798. curEnd = clipCursorToContent(cm, curEnd);
  799. cm.setCursor(curEnd.line, curEnd.ch);
  800. }
  801. }
  802. if (operator) {
  803. var inverted = false;
  804. vim.lastMotion = null;
  805. operatorArgs.repeat = repeat; // Indent in visual mode needs this.
  806. if (vim.visualMode) {
  807. curStart = selectionStart;
  808. curEnd = selectionEnd;
  809. motionArgs.inclusive = true;
  810. }
  811. // Swap start and end if motion was backward.
  812. if (cursorIsBefore(curEnd, curStart)) {
  813. var tmp = curStart;
  814. curStart = curEnd;
  815. curEnd = tmp;
  816. inverted = true;
  817. }
  818. if (motionArgs.inclusive && !(vim.visualMode && inverted)) {
  819. // Move the selection end one to the right to include the last
  820. // character.
  821. curEnd.ch++;
  822. }
  823. var linewise = motionArgs.linewise ||
  824. (vim.visualMode && vim.visualLine);
  825. if (linewise) {
  826. // Expand selection to entire line.
  827. expandSelectionToLine(cm, curStart, curEnd);
  828. } else if (motionArgs.forward) {
  829. // Clip to trailing newlines only if we the motion goes forward.
  830. clipToLine(cm, curStart, curEnd);
  831. }
  832. operatorArgs.registerName = registerName;
  833. // Keep track of linewise as it affects how paste and change behave.
  834. operatorArgs.linewise = linewise;
  835. operators[operator](cm, operatorArgs, vim, curStart,
  836. curEnd, curOriginal);
  837. if (vim.visualMode) {
  838. exitVisualMode(cm, vim);
  839. }
  840. if (operatorArgs.enterInsertMode) {
  841. actions.enterInsertMode(cm);
  842. }
  843. }
  844. }
  845. };
  846. /**
  847. * typedef {Object{line:number,ch:number}} Cursor An object containing the
  848. * position of the cursor.
  849. */
  850. // All of the functions below return Cursor objects.
  851. var motions = {
  852. expandToLine: function(cm, motionArgs) {
  853. // Expands forward to end of line, and then to next line if repeat is
  854. // >1. Does not handle backward motion!
  855. var cur = cm.getCursor();
  856. return { line: cur.line + motionArgs.repeat - 1, ch: Infinity };
  857. },
  858. findNext: function(cm, motionArgs, vim) {
  859. return findNext(cm, false /** prev */, motionArgs.repeat);
  860. },
  861. findPrev: function(cm, motionArgs, vim) {
  862. return findNext(cm, true /** prev */, motionArgs.repeat);
  863. },
  864. goToMark: function(cm, motionArgs, vim) {
  865. var mark = vim.marks[motionArgs.selectedCharacter];
  866. if (mark) {
  867. return mark.find();
  868. }
  869. return null;
  870. },
  871. moveByCharacters: function(cm, motionArgs) {
  872. var cur = cm.getCursor();
  873. var repeat = motionArgs.repeat;
  874. var ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat;
  875. return { line: cur.line, ch: ch };
  876. },
  877. moveByLines: function(cm, motionArgs, vim) {
  878. var endCh = cm.getCursor().ch;
  879. // Depending what our last motion was, we may want to do different
  880. // things. If our last motion was moving vertically, we want to
  881. // preserve the HPos from our last horizontal move. If our last motion
  882. // was going to the end of a line, moving vertically we should go to
  883. // the end of the line, etc.
  884. switch (vim.lastMotion) {
  885. case this.moveByLines:
  886. case this.moveToColumn:
  887. case this.moveToEol:
  888. endCh = vim.lastHPos;
  889. break;
  890. default:
  891. vim.lastHPos = endCh;
  892. }
  893. var cur = cm.getCursor();
  894. var repeat = motionArgs.repeat;
  895. var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat;
  896. if (line < 0 || line > cm.lineCount() - 1) {
  897. return null;
  898. }
  899. return { line: line, ch: endCh };
  900. },
  901. moveByPage: function(cm, motionArgs) {
  902. // CodeMirror only exposes functions that move the cursor page down, so
  903. // doing this bad hack to move the cursor and move it back. evalInput
  904. // will move the cursor to where it should be in the end.
  905. var curStart = cm.getCursor();
  906. var repeat = motionArgs.repeat;
  907. cm.moveV((motionArgs.forward ? repeat : -repeat), 'page');
  908. var curEnd = cm.getCursor();
  909. cm.setCursor(curStart);
  910. return curEnd;
  911. },
  912. moveByWords: function(cm, motionArgs) {
  913. return moveToWord(cm, motionArgs.repeat, !!motionArgs.forward,
  914. !!motionArgs.wordEnd, !!motionArgs.bigWord);
  915. },
  916. moveTillCharacter: function(cm, motionArgs) {
  917. var repeat = motionArgs.repeat;
  918. var curEnd = moveToCharacter(cm, repeat, motionArgs.forward,
  919. motionArgs.selectedCharacter);
  920. var increment = motionArgs.forward ? -1 : 1;
  921. curEnd.ch += increment;
  922. return curEnd;
  923. },
  924. moveToCharacter: function(cm, motionArgs) {
  925. var repeat = motionArgs.repeat;
  926. return moveToCharacter(cm, repeat, motionArgs.forward,
  927. motionArgs.selectedCharacter);
  928. },
  929. moveToColumn: function(cm, motionArgs, vim) {
  930. var repeat = motionArgs.repeat;
  931. // repeat is equivalent to which column we want to move to!
  932. vim.lastHPos = repeat - 1;
  933. return moveToColumn(cm, repeat);
  934. },
  935. moveToEol: function(cm, motionArgs, vim) {
  936. var cur = cm.getCursor();
  937. vim.lastHPos = Infinity;
  938. return { line: cur.line + motionArgs.repeat - 1, ch: Infinity };
  939. },
  940. moveToFirstNonWhiteSpaceCharacter: function(cm) {
  941. // Go to the start of the line where the text begins, or the end for
  942. // whitespace-only lines
  943. var cursor = cm.getCursor();
  944. var line = cm.getLine(cursor.line);
  945. return { line: cursor.line,
  946. ch: findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)) };
  947. },
  948. moveToMatchedSymbol: function(cm, motionArgs) {
  949. var cursor = cm.getCursor();
  950. var symbol = cm.getLine(cursor.line).charAt(cursor.ch);
  951. if (isMatchableSymbol(symbol)) {
  952. return findMatchedSymbol(cm, cm.getCursor(), motionArgs.symbol);
  953. } else {
  954. return cursor;
  955. }
  956. },
  957. moveToStartOfLine: function(cm) {
  958. var cursor = cm.getCursor();
  959. return { line: cursor.line, ch: 0 };
  960. },
  961. moveToLineOrEdgeOfDocument: function(cm, motionArgs) {
  962. var lineNum = motionArgs.forward ? cm.lineCount() - 1 : 0;
  963. if (motionArgs.repeatIsExplicit) {
  964. lineNum = motionArgs.repeat - 1;
  965. }
  966. return { line: lineNum,
  967. ch: findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum)) };
  968. },
  969. textObjectManipulation: function(cm, motionArgs) {
  970. var character = motionArgs.selectedCharacter;
  971. // Inclusive is the difference between a and i
  972. // TODO: Instead of using the additional text object map to perform text
  973. // object operations, merge the map into the defaultKeyMap and use
  974. // motionArgs to define behavior. Define separate entries for 'aw',
  975. // 'iw', 'a[', 'i[', etc.
  976. var inclusive = !motionArgs.textObjectInner;
  977. if (!textObjects[character]) {
  978. // No text object defined for this, don't move.
  979. return null;
  980. }
  981. var tmp = textObjects[character](cm, inclusive);
  982. var start = tmp.start;
  983. var end = tmp.end;
  984. return [start, end];
  985. }
  986. };
  987. var operators = {
  988. change: function(cm, operatorArgs, vim, curStart, curEnd) {
  989. getVimGlobalState().registerController.pushText(
  990. operatorArgs.registerName, 'change', cm.getRange(curStart, curEnd),
  991. operatorArgs.linewise);
  992. if (operatorArgs.linewise) {
  993. // Delete starting at the first nonwhitespace character of the first
  994. // line, instead of from the start of the first line. This way we get
  995. // an indent when we get into insert mode. This behavior isn't quite
  996. // correct because we should treat this as a completely new line, and
  997. // indent should be whatever codemirror thinks is the right indent.
  998. // But cm.indentLine doesn't seem work on empty lines.
  999. // TODO: Fix the above.
  1000. curStart.ch =
  1001. findFirstNonWhiteSpaceCharacter(cm.getLine(curStart.line));
  1002. // Insert an additional newline so that insert mode can start there.
  1003. // curEnd should be on the first character of the new line.
  1004. cm.replaceRange('\n', curStart, curEnd);
  1005. } else {
  1006. cm.replaceRange('', curStart, curEnd);
  1007. }
  1008. cm.setCursor(curStart);
  1009. },
  1010. // delete is a javascript keyword.
  1011. 'delete': function(cm, operatorArgs, vim, curStart, curEnd) {
  1012. getVimGlobalState().registerController.pushText(
  1013. operatorArgs.registerName, 'delete', cm.getRange(curStart, curEnd),
  1014. operatorArgs.linewise);
  1015. cm.replaceRange('', curStart, curEnd);
  1016. if (operatorArgs.linewise) {
  1017. cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
  1018. } else {
  1019. cm.setCursor(curStart);
  1020. }
  1021. },
  1022. indent: function(cm, operatorArgs, vim, curStart, curEnd) {
  1023. var startLine = curStart.line;
  1024. var endLine = curEnd.line;
  1025. // In visual mode, n> shifts the selection right n times, instead of
  1026. // shifting n lines right once.
  1027. var repeat = (vim.visualMode) ? operatorArgs.repeat : 1;
  1028. if (operatorArgs.linewise) {
  1029. // The only way to delete a newline is to delete until the start of
  1030. // the next line, so in linewise mode evalInput will include the next
  1031. // line. We don't want this in indent, so we go back a line.
  1032. endLine--;
  1033. }
  1034. for (var i = startLine; i <= endLine; i++) {
  1035. for (var j = 0; j < repeat; j++) {
  1036. cm.indentLine(i, operatorArgs.indentRight);
  1037. }
  1038. }
  1039. cm.setCursor(curStart);
  1040. cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
  1041. },
  1042. swapcase: function(cm, operatorArgs, vim, curStart, curEnd, curOriginal) {
  1043. var toSwap = cm.getRange(curStart, curEnd);
  1044. var swapped = '';
  1045. for (var i = 0; i < toSwap.length; i++) {
  1046. var character = toSwap.charAt(i);
  1047. swapped += isUpperCase(character) ? character.toLowerCase() :
  1048. character.toUpperCase();
  1049. }
  1050. cm.replaceRange(swapped, curStart, curEnd);
  1051. cm.setCursor(curOriginal);
  1052. },
  1053. yank: function(cm, operatorArgs, vim, curStart, curEnd, curOriginal) {
  1054. getVimGlobalState().registerController.pushText(
  1055. operatorArgs.registerName, 'yank',
  1056. cm.getRange(curStart, curEnd), operatorArgs.linewise);
  1057. cm.setCursor(curOriginal);
  1058. }
  1059. };
  1060. var actions = {
  1061. clearSearchHighlight: clearSearchHighlight,
  1062. enterInsertMode: function(cm, actionArgs) {
  1063. var insertAt = (actionArgs) ? actionArgs.insertAt : null;
  1064. if (insertAt == 'eol') {
  1065. var cursor = cm.getCursor();
  1066. cursor = { line: cursor.line, ch: lineLength(cm, cursor.line) };
  1067. cm.setCursor(cursor);
  1068. } else if (insertAt == 'charAfter') {
  1069. cm.setCursor(offsetCursor(cm.getCursor(), 0, 1));
  1070. }
  1071. cm.setOption('keyMap', 'vim-insert');
  1072. },
  1073. toggleVisualMode: function(cm, actionArgs, vim) {
  1074. var repeat = actionArgs.repeat;
  1075. var curStart = cm.getCursor();
  1076. var curEnd;
  1077. // TODO: The repeat should actually select number of characters/lines
  1078. // equal to the repeat times the size of the previous visual
  1079. // operation.
  1080. if (!vim.visualMode) {
  1081. vim.visualMode = true;
  1082. vim.visualLine = !!actionArgs.linewise;
  1083. if (vim.visualLine) {
  1084. curStart.ch = 0;
  1085. curEnd = clipCursorToContent(cm, {
  1086. line: curStart.line + repeat - 1,
  1087. ch: lineLength(cm, curStart.line)
  1088. }, true /** includeLineBreak */);
  1089. } else {
  1090. curEnd = clipCursorToContent(cm, {
  1091. line: curStart.line,
  1092. ch: curStart.ch + repeat
  1093. }, true /** includeLineBreak */);
  1094. }
  1095. // Make the initial selection.
  1096. if (!actionArgs.repeatIsExplicit && !vim.visualLine) {
  1097. // This is a strange case. Here the implicit repeat is 1. The
  1098. // following commands lets the cursor hover over the 1 character
  1099. // selection.
  1100. cm.setCursor(curEnd);
  1101. cm.setSelection(curEnd, curStart);
  1102. } else {
  1103. cm.setSelection(curStart, curEnd);
  1104. }
  1105. } else {
  1106. if (!vim.visualLine && actionArgs.linewise) {
  1107. // Shift-V pressed in characterwise visual mode. Switch to linewise
  1108. // visual mode instead of exiting visual mode.
  1109. vim.visualLine = true;
  1110. curStart = cm.getCursor('anchor');
  1111. curEnd = cm.getCursor('head');
  1112. curStart.ch = cursorIsBefore(curStart, curEnd) ? 0 :
  1113. lineLength(cm, curStart.line);
  1114. curEnd.ch = cursorIsBefore(curStart, curEnd) ?
  1115. lineLength(cm, curEnd.line) : 0;
  1116. cm.setSelection(curStart, curEnd);
  1117. } else {
  1118. exitVisualMode(cm, vim);
  1119. }
  1120. }
  1121. },
  1122. joinLines: function(cm, actionArgs, vim) {
  1123. var curStart, curEnd;
  1124. if (vim.visualMode) {
  1125. curStart = cm.getCursor('anchor');
  1126. curEnd = cm.getCursor('head');
  1127. curEnd.ch = lineLength(cm, curEnd.line) - 1;
  1128. } else {
  1129. // Repeat is the number of lines to join. Minimum 2 lines.
  1130. var repeat = Math.max(actionArgs.repeat, 2);
  1131. curStart = cm.getCursor();
  1132. curEnd = clipCursorToContent(cm, { line: curStart.line + repeat - 1,
  1133. ch: Infinity });
  1134. }
  1135. var finalCh = 0;
  1136. cm.operation(function() {
  1137. for (var i = curStart.line; i < curEnd.line; i++) {
  1138. finalCh = lineLength(cm, curStart.line);
  1139. var tmp = { line: curStart.line + 1,
  1140. ch: lineLength(cm, curStart.line + 1) };
  1141. var text = cm.getRange(curStart, tmp);
  1142. text = text.replace(/\n\s*/g, ' ');
  1143. cm.replaceRange(text, curStart, tmp);
  1144. }
  1145. var curFinalPos = { line: curStart.line, ch: finalCh };
  1146. cm.setCursor(curFinalPos);
  1147. });
  1148. },
  1149. newLineAndEnterInsertMode: function(cm, actionArgs) {
  1150. var insertAt = cm.getCursor();
  1151. if (insertAt.line === 0 && !actionArgs.after) {
  1152. // Special case for inserting newline before start of document.
  1153. cm.replaceRange('\n', { line: 0, ch: 0 });
  1154. cm.setCursor(0, 0);
  1155. } else {
  1156. insertAt.line = (actionArgs.after) ? insertAt.line :
  1157. insertAt.line - 1;
  1158. insertAt.ch = lineLength(cm, insertAt.line);
  1159. cm.setCursor(insertAt);
  1160. var newlineFn = CodeMirror.commands.newlineAndIndentContinueComment ||
  1161. CodeMirror.commands.newlineAndIndent;
  1162. newlineFn(cm);
  1163. }
  1164. this.enterInsertMode(cm);
  1165. },
  1166. paste: function(cm, actionArgs, vim) {
  1167. var cur = cm.getCursor();
  1168. var register = getVimGlobalState().registerController.getRegister(
  1169. actionArgs.registerName);
  1170. if (!register.text) {
  1171. return;
  1172. }
  1173. for (var text = '', i = 0; i < actionArgs.repeat; i++) {
  1174. text += register.text;
  1175. }
  1176. var linewise = register.linewise;
  1177. if (linewise) {
  1178. if (actionArgs.after) {
  1179. // Move the newline at the end to the start instead, and paste just
  1180. // before the newline character of the line we are on right now.
  1181. text = '\n' + text.slice(0, text.length - 1);
  1182. cur.ch = lineLength(cm, cur.line);
  1183. } else {
  1184. cur.ch = 0;
  1185. }
  1186. } else {
  1187. cur.ch += actionArgs.after ? 1 : 0;
  1188. }
  1189. cm.replaceRange(text, cur);
  1190. // Now fine tune the cursor to where we want it.
  1191. var curPosFinal;
  1192. var idx;
  1193. if (linewise && actionArgs.after) {
  1194. curPosFinal = { line: cur.line + 1,
  1195. ch: findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1)) };
  1196. } else if (linewise && !actionArgs.after) {
  1197. curPosFinal = { line: cur.line,
  1198. ch: findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line)) };
  1199. } else if (!linewise && actionArgs.after) {
  1200. idx = cm.indexFromPos(cur);
  1201. curPosFinal = cm.posFromIndex(idx + text.length - 1);
  1202. } else {
  1203. idx = cm.indexFromPos(cur);
  1204. curPosFinal = cm.posFromIndex(idx + text.length);
  1205. }
  1206. cm.setCursor(curPosFinal);
  1207. },
  1208. undo: function(cm, actionArgs) {
  1209. repeatFn(cm, CodeMirror.commands.undo, actionArgs.repeat)();
  1210. },
  1211. redo: function(cm, actionArgs) {
  1212. repeatFn(cm, CodeMirror.commands.redo, actionArgs.repeat)();
  1213. },
  1214. setRegister: function(cm, actionArgs, vim) {
  1215. vim.inputState.registerName = actionArgs.selectedCharacter;
  1216. },
  1217. setMark: function(cm, actionArgs, vim) {
  1218. var markName = actionArgs.selectedCharacter;
  1219. if (!inArray(markName, validMarks)) {
  1220. return;
  1221. }
  1222. if (vim.marks[markName]) {
  1223. vim.marks[markName].clear();
  1224. }
  1225. vim.marks[markName] = cm.setBookmark(cm.getCursor());
  1226. },
  1227. replace: function(cm, actionArgs) {
  1228. var replaceWith = actionArgs.selectedCharacter;
  1229. var curStart = cm.getCursor();
  1230. var line = cm.getLine(curStart.line);
  1231. var replaceTo = curStart.ch + actionArgs.repeat;
  1232. if (replaceTo > line.length) {
  1233. return;
  1234. }
  1235. var curEnd = { line: curStart.line, ch: replaceTo };
  1236. var replaceWithStr = '';
  1237. for (var i = 0; i < curEnd.ch - curStart.ch; i++) {
  1238. replaceWithStr += replaceWith;
  1239. }
  1240. cm.replaceRange(replaceWithStr, curStart, curEnd);
  1241. cm.setCursor(offsetCursor(curEnd, 0, -1));
  1242. }
  1243. };
  1244. var textObjects = {
  1245. // TODO: lots of possible exceptions that can be thrown here. Try da(
  1246. // outside of a () block.
  1247. // TODO: implement text objects for the reverse like }. Should just be
  1248. // an additional mapping after moving to the defaultKeyMap.
  1249. 'w': function(cm, inclusive) {
  1250. return expandWordUnderCursor(cm, inclusive, true /** forward */,
  1251. false /** bigWord */);
  1252. },
  1253. 'W': function(cm, inclusive) {
  1254. return expandWordUnderCursor(cm, inclusive,
  1255. true /** forward */, true /** bigWord */);
  1256. },
  1257. '{': function(cm, inclusive) {
  1258. return selectCompanionObject(cm, '}', inclusive);
  1259. },
  1260. '(': function(cm, inclusive) {
  1261. return selectCompanionObject(cm, ')', inclusive);
  1262. },
  1263. '[': function(cm, inclusive) {
  1264. return selectCompanionObject(cm, ']', inclusive);
  1265. },
  1266. '\'': function(cm, inclusive) {
  1267. return findBeginningAndEnd(cm, "'", inclusive);
  1268. },
  1269. '\"': function(cm, inclusive) {
  1270. return findBeginningAndEnd(cm, '"', inclusive);
  1271. }
  1272. };
  1273. /*
  1274. * Below are miscellaneous utility functions used by vim.js
  1275. */
  1276. /**
  1277. * Clips cursor to ensure that:
  1278. * 0 <= cur.ch < lineLength
  1279. * AND
  1280. * 0 <= cur.line < lineCount
  1281. * If includeLineBreak is true, then allow cur.ch == lineLength.
  1282. */
  1283. function clipCursorToContent(cm, cur, includeLineBreak) {
  1284. var line = Math.min(Math.max(0, cur.line), cm.lineCount() - 1);
  1285. var maxCh = lineLength(cm, line) - 1;
  1286. maxCh = (includeLineBreak) ? maxCh + 1 : maxCh;
  1287. var ch = Math.min(Math.max(0, cur.ch), maxCh);
  1288. return { line: line, ch: ch };
  1289. }
  1290. // Merge arguments in place, for overriding arguments.
  1291. function mergeArgs(to, from) {
  1292. for (var prop in from) {
  1293. if (from.hasOwnProperty(prop)) {
  1294. to[prop] = from[prop];
  1295. }
  1296. }
  1297. }
  1298. function copyArgs(args) {
  1299. var ret = {};
  1300. for (var prop in args) {
  1301. if (args.hasOwnProperty(prop)) {
  1302. ret[prop] = args[prop];
  1303. }
  1304. }
  1305. return ret;
  1306. }
  1307. function offsetCursor(cur, offsetLine, offsetCh) {
  1308. return { line: cur.line + offsetLine, ch: cur.ch + offsetCh };
  1309. }
  1310. function arrayEq(a1, a2) {
  1311. if (a1.length != a2.length) {
  1312. return false;
  1313. }
  1314. for (var i = 0; i < a1.length; i++) {
  1315. if (a1[i] != a2[i]) {
  1316. return false;
  1317. }
  1318. }
  1319. return true;
  1320. }
  1321. function matchKeysPartial(pressed, mapped) {
  1322. for (var i = 0; i < pressed.length; i++) {
  1323. // 'character' means any character. For mark, register commads, etc.
  1324. if (pressed[i] != mapped[i] && mapped[i] != 'character') {
  1325. return false;
  1326. }
  1327. }
  1328. return true;
  1329. }
  1330. function arrayIsSubsetFromBeginning(small, big) {
  1331. for (var i = 0; i < small.length; i++) {
  1332. if (small[i] != big[i]) {
  1333. return false;
  1334. }
  1335. }
  1336. return true;
  1337. }
  1338. function repeatFn(cm, fn, repeat) {
  1339. return function() {
  1340. for (var i = 0; i < repeat; i++) {
  1341. fn(cm);
  1342. }
  1343. };
  1344. }
  1345. function copyCursor(cur) {
  1346. return { line: cur.line, ch: cur.ch };
  1347. }
  1348. function cursorEqual(cur1, cur2) {
  1349. return cur1.ch == cur2.ch && cur1.line == cur2.line;
  1350. }
  1351. function cursorIsBefore(cur1, cur2) {
  1352. if (cur1.line < cur2.line) {
  1353. return true;
  1354. } else if (cur1.line == cur2.line && cur1.ch < cur2.ch) {
  1355. return true;
  1356. }
  1357. return false;
  1358. }
  1359. function lineLength(cm, lineNum) {
  1360. return cm.getLine(lineNum).length;
  1361. }
  1362. function reverse(s){
  1363. return s.split("").reverse().join("");
  1364. }
  1365. function trim(s) {
  1366. if (s.trim) {
  1367. return s.trim();
  1368. } else {
  1369. return s.replace(/^\s+|\s+$/g, '');
  1370. }
  1371. }
  1372. function escapeRegex(s) {
  1373. return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, "\\$1");
  1374. }
  1375. function exitVisualMode(cm, vim) {
  1376. vim.visualMode = false;
  1377. vim.visualLine = false;
  1378. var selectionStart = cm.getCursor('anchor');
  1379. var selectionEnd = cm.getCursor('head');
  1380. if (!cursorEqual(selectionStart, selectionEnd)) {
  1381. // Clear the selection and set the cursor only if the selection has not
  1382. // already been cleared. Otherwise we risk moving the cursor somewhere
  1383. // it's not supposed to be.
  1384. cm.setCursor(clipCursorToContent(cm, selectionEnd));
  1385. }
  1386. }
  1387. // Remove any trailing newlines from the selection. For
  1388. // example, with the caret at the start of the last word on the line,
  1389. // 'dw' should word, but not the newline, while 'w' should advance the
  1390. // caret to the first character of the next line.
  1391. function clipToLine(cm, curStart, curEnd) {
  1392. var selection = cm.getRange(curStart, curEnd);
  1393. var lines = selection.split('\n');
  1394. if (lines.length > 1 && isWhiteSpaceString(lines.pop())) {
  1395. curEnd.line--;
  1396. curEnd.ch = lineLength(cm, curEnd.line);
  1397. }
  1398. }
  1399. // Expand the selection to line ends.
  1400. function expandSelectionToLine(cm, curStart, curEnd) {
  1401. curStart.ch = 0;
  1402. curEnd.ch = 0;
  1403. curEnd.line++;
  1404. }
  1405. function findFirstNonWhiteSpaceCharacter(text) {
  1406. if (!text) {
  1407. return 0;
  1408. }
  1409. var firstNonWS = text.search(/\S/);
  1410. return firstNonWS == -1 ? text.length : firstNonWS;
  1411. }
  1412. function expandWordUnderCursor(cm, inclusive, forward, bigWord, noSymbol) {
  1413. var cur = cm.getCursor();
  1414. var line = cm.getLine(cur.line);
  1415. var idx = cur.ch;
  1416. // Seek to first word or non-whitespace character, depending on if
  1417. // noSymbol is true.
  1418. var textAfterIdx = line.substring(idx);
  1419. var firstMatchedChar;
  1420. if (noSymbol) {
  1421. firstMatchedChar = textAfterIdx.search(/\w/);
  1422. } else {
  1423. firstMatchedChar = textAfterIdx.search(/\S/);
  1424. }
  1425. if (firstMatchedChar == -1) {
  1426. return null;
  1427. }
  1428. idx += firstMatchedChar;
  1429. textAfterIdx = line.substring(idx);
  1430. var textBeforeIdx = line.substring(0, idx);
  1431. var matchRegex;
  1432. // Greedy matchers for the "word" we are trying to expand.
  1433. if (bigWord) {
  1434. matchRegex = /^\S+/;
  1435. } else {
  1436. if ((/\w/).test(line.charAt(idx))) {
  1437. matchRegex = /^\w+/;
  1438. } else {
  1439. matchRegex = /^[^\w\s]+/;
  1440. }
  1441. }
  1442. var wordAfterRegex = matchRegex.exec(textAfterIdx);
  1443. var wordStart = idx;
  1444. var wordEnd = idx + wordAfterRegex[0].length - 1;
  1445. // TODO: Find a better way to do this. It will be slow on very long lines.
  1446. var wordBeforeRegex = matchRegex.exec(reverse(textBeforeIdx));
  1447. if (wordBeforeRegex) {
  1448. wordStart -= wordBeforeRegex[0].length;
  1449. }
  1450. if (inclusive) {
  1451. wordEnd++;
  1452. }
  1453. return { start: { line: cur.line, ch: wordStart },
  1454. end: { line: cur.line, ch: wordEnd }};
  1455. }
  1456. /*
  1457. * Returns the boundaries of the next word. If the cursor in the middle of
  1458. * the word, then returns the boundaries of the current word, starting at
  1459. * the cursor. If the cursor is at the start/end of a word, and we are going
  1460. * forward/backward, respectively, find the boundaries of the next word.
  1461. *
  1462. * @param {CodeMirror} cm CodeMirror object.
  1463. * @param {Cursor} cur The cursor position.
  1464. * @param {boolean} forward True to search forward. False to search
  1465. * backward.
  1466. * @param {boolean} bigWord True if punctuation count as part of the word.
  1467. * False if only [a-zA-Z0-9] characters count as part of the word.
  1468. * @return {Object{from:number, to:number, line: number}} The boundaries of
  1469. * the word, or null if there are no more words.
  1470. */
  1471. // TODO: Treat empty lines (with no whitespace) as words.
  1472. function findWord(cm, cur, forward, bigWord) {
  1473. var lineNum = cur.line;
  1474. var pos = cur.ch;
  1475. var line = cm.getLine(lineNum);
  1476. var dir = forward ? 1 : -1;
  1477. var regexps = bigWord ? bigWordRegexp : wordRegexp;
  1478. while (true) {
  1479. var stop = (dir > 0) ? line.length : -1;
  1480. var wordStart = stop, wordEnd = stop;
  1481. // Find bounds of next word.
  1482. while (pos != stop) {
  1483. var foundWord = false;
  1484. for (var i = 0; i < regexps.length && !foundWord; ++i) {
  1485. if (regexps[i].test(line.charAt(pos))) {
  1486. wordStart = pos;
  1487. // Advance to end of word.
  1488. while (pos != stop && regexps[i].test(line.charAt(pos))) {
  1489. pos += dir;
  1490. }
  1491. wordEnd = pos;
  1492. foundWord = wordStart != wordEnd;
  1493. if (wordStart == cur.ch && lineNum == cur.line &&
  1494. wordEnd == wordStart + dir) {
  1495. // We started at the end of a word. Find the next one.
  1496. continue;
  1497. } else {
  1498. return {
  1499. from: Math.min(wordStart, wordEnd + 1),
  1500. to: Math.max(wordStart, wordEnd),
  1501. line: lineNum };
  1502. }
  1503. }
  1504. }
  1505. if (!foundWord) {
  1506. pos += dir;
  1507. }
  1508. }
  1509. // Advance to next/prev line.
  1510. lineNum += dir;
  1511. if (!isLine(cm, lineNum)) {
  1512. return null;
  1513. }
  1514. line = cm.getLine(lineNum);
  1515. pos = (dir > 0) ? 0 : line.length;
  1516. }
  1517. // Should never get here.
  1518. throw 'The impossible happened.';
  1519. }
  1520. /**
  1521. * @param {CodeMirror} cm CodeMirror object.
  1522. * @param {int} repeat Number of words to move past.
  1523. * @param {boolean} forward True to search forward. False to search
  1524. * backward.
  1525. * @param {boolean} wordEnd True to move to end of word. False to move to
  1526. * beginning of word.
  1527. * @param {boolean} bigWord True if punctuation count as part of the word.
  1528. * False if only alphabet characters count as part of the word.
  1529. * @return {Cursor} The position the cursor should move to.
  1530. */
  1531. function moveToWord(cm, repeat, forward, wordEnd, bigWord) {
  1532. var cur = cm.getCursor();
  1533. for (var i = 0; i < repeat; i++) {
  1534. var startCh = cur.ch, startLine = cur.line, word;
  1535. var movedToNextWord = false;
  1536. while (!movedToNextWord) {
  1537. // Search and advance.
  1538. word = findWord(cm, cur, forward, bigWord);
  1539. movedToNextWord = true;
  1540. if (word) {
  1541. // Move to the word we just found. If by moving to the word we end
  1542. // up in the same spot, then move an extra character and search
  1543. // again.
  1544. cur.line = word.line;
  1545. if (forward && wordEnd) {
  1546. // 'e'
  1547. cur.ch = word.to - 1;
  1548. } else if (forward && !wordEnd) {
  1549. // 'w'
  1550. if (inRangeInclusive(cur.ch, word.from, word.to) &&
  1551. word.line == startLine) {
  1552. // Still on the same word. Go to the next one.
  1553. movedToNextWord = false;
  1554. cur.ch = word.to - 1;
  1555. } else {
  1556. cur.ch = word.from;
  1557. }
  1558. } else if (!forward && wordEnd) {
  1559. // 'ge'
  1560. if (inRangeInclusive(cur.ch, word.from, word.to) &&
  1561. word.line == startLine) {
  1562. // still on the same word. Go to the next one.
  1563. movedToNextWord = false;
  1564. cur.ch = word.from;
  1565. } else {
  1566. cur.ch = word.to;
  1567. }
  1568. } else if (!forward && !wordEnd) {
  1569. // 'b'
  1570. cur.ch = word.from;
  1571. }
  1572. } else {
  1573. // No more words to be found. Move to the end.
  1574. if (forward) {
  1575. return { line: cur.line, ch: lineLength(cm, cur.line) };
  1576. } else {
  1577. return { line: cur.line, ch: 0 };
  1578. }
  1579. }
  1580. }
  1581. }
  1582. return cur;
  1583. }
  1584. function moveToCharacter(cm, repeat, forward, character) {
  1585. var cur = cm.getCursor();
  1586. var start = cur.ch;
  1587. var idx;
  1588. for (var i = 0; i < repeat; i ++) {
  1589. var line = cm.getLine(cur.line);
  1590. idx = charIdxInLine(start, line, character, forward, true);
  1591. if (idx == -1) {
  1592. return cur;
  1593. }
  1594. start = idx;
  1595. }
  1596. return { line: cm.getCursor().line, ch: idx };
  1597. }
  1598. function moveToColumn(cm, repeat) {
  1599. // repeat is always >= 1, so repeat - 1 always corresponds
  1600. // to the column we want to go to.
  1601. var line = cm.getCursor().line;
  1602. return clipCursorToContent(cm, { line: line, ch: repeat - 1 });
  1603. }
  1604. function charIdxInLine(start, line, character, forward, includeChar) {
  1605. // Search for char in line.
  1606. // motion_options: {forward, includeChar}
  1607. // If includeChar = true, include it too.
  1608. // If forward = true, search forward, else search backwards.
  1609. // If char is not found on this line, do nothing
  1610. var idx;
  1611. if (forward) {
  1612. idx = line.indexOf(character, start + 1);
  1613. if (idx != -1 && !includeChar) {
  1614. idx -= 1;
  1615. }
  1616. } else {
  1617. idx = line.lastIndexOf(character, start - 1);
  1618. if (idx != -1 && !includeChar) {
  1619. idx += 1;
  1620. }
  1621. }
  1622. return idx;
  1623. }
  1624. function findMatchedSymbol(cm, cur, symb) {
  1625. var line = cur.line;
  1626. symb = symb ? symb : cm.getLine(line).charAt(cur.ch);
  1627. // Are we at the opening or closing char
  1628. var forwards = inArray(symb, ['(', '[', '{']);
  1629. var reverseSymb = ({
  1630. '(': ')', ')': '(',
  1631. '[': ']', ']': '[',
  1632. '{': '}', '}': '{'})[symb];
  1633. // Couldn't find a matching symbol, abort
  1634. if (!reverseSymb) {
  1635. return cur;
  1636. }
  1637. // set our increment to move forward (+1) or backwards (-1)
  1638. // depending on which bracket we're matching
  1639. var increment = ({'(': 1, '{': 1, '[': 1})[symb] || -1;
  1640. var depth = 1, nextCh = symb, index = cur.ch, lineText = cm.getLine(line);
  1641. // Simple search for closing paren--just count openings and closings till
  1642. // we find our match
  1643. // TODO: use info from CodeMirror to ignore closing brackets in comments
  1644. // and quotes, etc.
  1645. while (nextCh && depth > 0) {
  1646. index += increment;
  1647. nextCh = lineText.charAt(index);
  1648. if (!nextCh) {
  1649. line += increment;
  1650. index = 0;
  1651. lineText = cm.getLine(line) || '';
  1652. nextCh = lineText.charAt(index);
  1653. }
  1654. if (nextCh === symb) {
  1655. depth++;
  1656. } else if (nextCh === reverseSymb) {
  1657. depth--;
  1658. }
  1659. }
  1660. if (nextCh) {
  1661. return { line: line, ch: index };
  1662. }
  1663. return cur;
  1664. }
  1665. function selectCompanionObject(cm, revSymb, inclusive) {
  1666. var cur = cm.getCursor();
  1667. var end = findMatchedSymbol(cm, cur, revSymb);
  1668. var start = findMatchedSymbol(cm, end);
  1669. start.ch += inclusive ? 1 : 0;
  1670. end.ch += inclusive ? 0 : 1;
  1671. return { start: start, end: end };
  1672. }
  1673. function regexLastIndexOf(string, pattern, startIndex) {
  1674. for (var i = !startIndex ? string.length : startIndex;
  1675. i >= 0; --i) {
  1676. if (pattern.test(string.charAt(i))) {
  1677. return i;
  1678. }
  1679. }
  1680. return -1;
  1681. }
  1682. // Takes in a symbol and a cursor and tries to simulate text objects that
  1683. // have identical opening and closing symbols
  1684. // TODO support across multiple lines
  1685. function findBeginningAndEnd(cm, symb, inclusive) {
  1686. var cur = cm.getCursor();
  1687. var line = cm.getLine(cur.line);
  1688. var chars = line.split('');
  1689. var start, end, i, len;
  1690. var firstIndex = chars.indexOf(symb);
  1691. // the decision tree is to always look backwards for the beginning first,
  1692. // but if the cursor is in front of the first instance of the symb,
  1693. // then move the cursor forward
  1694. if (cur.ch < firstIndex) {
  1695. cur.ch = firstIndex;
  1696. // Why is this line even here???
  1697. // cm.setCursor(cur.line, firstIndex+1);
  1698. }
  1699. // otherwise if the cursor is currently on the closing symbol
  1700. else if (firstIndex < cur.ch && chars[cur.ch] == symb) {
  1701. end = cur.ch; // assign end to the current cursor
  1702. --cur.ch; // make sure to look backwards
  1703. }
  1704. // if we're currently on the symbol, we've got a start
  1705. if (chars[cur.ch] == symb && !end) {
  1706. start = cur.ch + 1; // assign start to ahead of the cursor
  1707. } else {
  1708. // go backwards to find the start
  1709. for (i = cur.ch; i > -1 && !start; i--) {
  1710. if (chars[i] == symb) {
  1711. start = i + 1;
  1712. }
  1713. }
  1714. }
  1715. // look forwards for the end symbol
  1716. if (start && !end) {
  1717. for (i = start, len = chars.length; i < len && !end; i++) {
  1718. if (chars[i] == symb) {
  1719. end = i;
  1720. }
  1721. }
  1722. }
  1723. // nothing found
  1724. if (!start || !end) {
  1725. return { start: cur, end: cur };
  1726. }
  1727. // include the symbols
  1728. if (inclusive) {
  1729. --start; ++end;
  1730. }
  1731. return {
  1732. start: { line: cur.line, ch: start },
  1733. end: { line: cur.line, ch: end }
  1734. };
  1735. }
  1736. // Search functions
  1737. function SearchState() {
  1738. // Highlighted text that match the query.
  1739. this.marked = null;
  1740. }
  1741. SearchState.prototype = {
  1742. getQuery: function() {
  1743. return getVimGlobalState().query;
  1744. },
  1745. setQuery: function(query) {
  1746. getVimGlobalState().query = query;
  1747. },
  1748. getMarked: function() {
  1749. return this.marked;
  1750. },
  1751. setMarked: function(marked) {
  1752. this.marked = marked;
  1753. },
  1754. isReversed: function() {
  1755. return getVimGlobalState().isReversed;
  1756. },
  1757. setReversed: function(reversed) {
  1758. getVimGlobalState().isReversed = reversed;
  1759. }
  1760. };
  1761. function getSearchState(cm) {
  1762. var vim = getVimState(cm);
  1763. return vim.searchState_ || (vim.searchState_ = new SearchState());
  1764. }
  1765. function dialog(cm, text, shortText, callback) {
  1766. if (cm.openDialog) {
  1767. cm.openDialog(text, callback, {bottom: true});
  1768. }
  1769. else {
  1770. callback(prompt(shortText, ""));
  1771. }
  1772. }
  1773. function findUnescapedSlashes(str) {
  1774. var escapeNextChar = false;
  1775. var slashes = [];
  1776. for (var i = 0; i < str.length; i++) {
  1777. var c = str.charAt(i);
  1778. if (!escapeNextChar && c == '/') {
  1779. slashes.push(i);
  1780. }
  1781. escapeNextChar = (c == '\\');
  1782. }
  1783. return slashes;
  1784. }
  1785. /**
  1786. * Extract the regular expression from the query and return a Regexp object.
  1787. * Returns null if the query is blank.
  1788. * If ignoreCase is passed in, the Regexp object will have the 'i' flag set.
  1789. * If smartCase is passed in, and the query contains upper case letters,
  1790. * then ignoreCase is overridden, and the 'i' flag will not be set.
  1791. * If the query contains the /i in the flag part of the regular expression,
  1792. * then both ignoreCase and smartCase are ignored, and 'i' will be passed
  1793. * through to the Regex object.
  1794. */
  1795. function parseQuery(cm, query, ignoreCase, smartCase) {
  1796. // First try to extract regex + flags from the input. If no flags found,
  1797. // extract just the regex. IE does not accept flags directly defined in
  1798. // the regex string in the form /regex/flags
  1799. var slashes = findUnescapedSlashes(query);
  1800. var regexPart;
  1801. var forceIgnoreCase;
  1802. if (!slashes.length) {
  1803. // Query looks like 'regexp'
  1804. regexPart = query;
  1805. } else {
  1806. // Query looks like 'regexp/...'
  1807. regexPart = query.substring(0, slashes[0]);
  1808. var flagsPart = query.substring(slashes[0]);
  1809. forceIgnoreCase = (flagsPart.indexOf('i') != -1);
  1810. }
  1811. if (!regexPart) {
  1812. return null;
  1813. }
  1814. if (smartCase) {
  1815. ignoreCase = (/^[^A-Z]*$/).test(regexPart);
  1816. }
  1817. try {
  1818. var regexp = new RegExp(regexPart,
  1819. (ignoreCase || forceIgnoreCase) ? 'i' : undefined);
  1820. return regexp;
  1821. } catch (e) {
  1822. showConfirm(cm, 'Invalid regex: ' + regexPart);
  1823. }
  1824. }
  1825. function showConfirm(cm, text) {
  1826. if (cm.openConfirm) {
  1827. cm.openConfirm('<span style="color: red">' + text +
  1828. '</span> <button type="button">OK</button>', function() {},
  1829. {bottom: true});
  1830. } else {
  1831. alert(text);
  1832. }
  1833. }
  1834. function makePrompt(prefix, desc) {
  1835. var raw = '';
  1836. if (prefix) {
  1837. raw += '<span style="font-family: monospace">' + prefix + '</span>';
  1838. }
  1839. raw += '<input type="text"/> ' +
  1840. '<span style="color: #888">';
  1841. if (desc) {
  1842. raw += '<span style="color: #888">';
  1843. raw += desc;
  1844. raw += '</span>';
  1845. }
  1846. return raw;
  1847. }
  1848. var searchPromptDesc = '(Javascript regexp)';
  1849. function showPrompt(cm, onPromptClose, prefix, desc) {
  1850. var shortText = (prefix || '') + ' ' + (desc || '');
  1851. dialog(cm, makePrompt(prefix, desc), shortText, onPromptClose);
  1852. }
  1853. function regexEqual(r1, r2) {
  1854. if (r1 instanceof RegExp && r2 instanceof RegExp) {
  1855. var props = ["global", "multiline", "ignoreCase", "source"];
  1856. for (var i = 0; i < props.length; i++) {
  1857. var prop = props[i];
  1858. if (r1[prop] !== r2[prop]) {
  1859. return(false);
  1860. }
  1861. }
  1862. return(true);
  1863. }
  1864. return(false);
  1865. }
  1866. function updateSearchQuery(cm, rawQuery, ignoreCase, smartCase) {
  1867. cm.operation(function() {
  1868. var state = getSearchState(cm);
  1869. if (!rawQuery) {
  1870. return;
  1871. }
  1872. var query = parseQuery(cm, rawQuery, !!ignoreCase, !!smartCase);
  1873. if (!query) {
  1874. return;
  1875. }
  1876. if (regexEqual(query, state.getQuery())) {
  1877. return;
  1878. }
  1879. clearSearchHighlight(cm);
  1880. highlightSearchMatches(cm, query);
  1881. state.setQuery(query);
  1882. });
  1883. }
  1884. function highlightSearchMatches(cm, query) {
  1885. // TODO: Highlight only text inside the viewport. Highlighting everything
  1886. // is inefficient and expensive.
  1887. if (cm.lineCount() < 2000) { // This is too expensive on big documents.
  1888. var marked = [];
  1889. for (var cursor = cm.getSearchCursor(query);
  1890. cursor.findNext();) {
  1891. marked.push(cm.markText(cursor.from(), cursor.to(),
  1892. { className: 'CodeMirror-searching' }));
  1893. }
  1894. getSearchState(cm).setMarked(marked);
  1895. }
  1896. }
  1897. function findNext(cm, prev, repeat) {
  1898. return cm.operation(function() {
  1899. var state = getSearchState(cm);
  1900. var query = state.getQuery();
  1901. if (!query) {
  1902. return;
  1903. }
  1904. if (!state.getMarked()) {
  1905. highlightSearchMatches(cm, query);
  1906. }
  1907. var pos = cm.getCursor();
  1908. // If search is initiated with ? instead of /, negate direction.
  1909. prev = (state.isReversed()) ? !prev : prev;
  1910. if (!prev) {
  1911. pos.ch += 1;
  1912. }
  1913. var cursor = cm.getSearchCursor(query, pos);
  1914. for (var i = 0; i < repeat; i++) {
  1915. if (!cursor.find(prev)) {
  1916. // SearchCursor may have returned null because it hit EOF, wrap
  1917. // around and try again.
  1918. cursor = cm.getSearchCursor(query,
  1919. (prev) ? { line: cm.lineCount() - 1} : {line: 0, ch: 0} );
  1920. if (!cursor.find(prev)) {
  1921. return;
  1922. }
  1923. }
  1924. }
  1925. return cursor.from();
  1926. });}
  1927. function clearSearchHighlight(cm) {
  1928. cm.operation(function() {
  1929. var state = getSearchState(cm);
  1930. if (!state.getQuery()) {
  1931. return;
  1932. }
  1933. var marked = state.getMarked();
  1934. if (!marked) {
  1935. return;
  1936. }
  1937. for (var i = 0; i < marked.length; ++i) {
  1938. marked[i].clear();
  1939. }
  1940. state.setMarked(null);
  1941. });}
  1942. // Ex command handling
  1943. // Care must be taken when adding to the default Ex command map. For any
  1944. // pair of commands that have a shared prefix, at least one of their
  1945. // shortNames must not match the prefix of the other command.
  1946. var defaultExCommandMap = [
  1947. { name: 'map', type: 'builtIn' },
  1948. { name: 'write', shortName: 'w', type: 'builtIn' },
  1949. { name: 'undo', shortName: 'u', type: 'builtIn' },
  1950. { name: 'redo', shortName: 'red', type: 'builtIn' }
  1951. ];
  1952. var ExCommandDispatcher = function() {
  1953. this.buildCommandMap_();
  1954. };
  1955. ExCommandDispatcher.prototype = {
  1956. processCommand: function(cm, input) {
  1957. var params = this.parseInput_(input);
  1958. var commandName;
  1959. if (!params.commandName) {
  1960. // If only a line range is defined, move to the line.
  1961. if (params.line !== undefined) {
  1962. commandName = 'move';
  1963. }
  1964. } else {
  1965. var command = this.matchCommand_(params.commandName);
  1966. if (command) {
  1967. commandName = command.name;
  1968. if (command.type == 'exToKey') {
  1969. // Handle Ex to Key mapping.
  1970. for (var i = 0; i < command.toKeys.length; i++) {
  1971. vim.handleKey(cm, command.toKeys[i]);
  1972. }
  1973. return;
  1974. } else if (command.type == 'exToEx') {
  1975. // Handle Ex to Ex mapping.
  1976. this.processCommand(cm, command.toInput);
  1977. return;
  1978. }
  1979. }
  1980. }
  1981. if (!commandName) {
  1982. showConfirm(cm, 'Not an editor command ":' + input + '"');
  1983. return;
  1984. }
  1985. exCommands[commandName](cm, params);
  1986. },
  1987. parseInput_: function(input) {
  1988. var result = {};
  1989. result.input = input;
  1990. var idx = 0;
  1991. // Trim preceding ':'.
  1992. var colons = (/^:+/).exec(input);
  1993. if (colons) {
  1994. idx += colons[0].length;
  1995. }
  1996. // Parse range.
  1997. var numberMatch = (/^(\d+)/).exec(input.substring(idx));
  1998. if (numberMatch) {
  1999. result.line = parseInt(numberMatch[1], 10);
  2000. idx += numberMatch[0].length;
  2001. }
  2002. // Parse command name.
  2003. var commandMatch = (/^(\w+)/).exec(input.substring(idx));
  2004. if (commandMatch) {
  2005. result.commandName = commandMatch[1];
  2006. idx += commandMatch[1].length;
  2007. }
  2008. // Parse command-line arguments
  2009. var args = trim(input.substring(idx)).split(/\s+/);
  2010. if (args.length && args[0]) {
  2011. result.commandArgs = args;
  2012. }
  2013. return result;
  2014. },
  2015. matchCommand_: function(commandName) {
  2016. // Return the command in the command map that matches the shortest
  2017. // prefix of the passed in command name. The match is guaranteed to be
  2018. // unambiguous if the defaultExCommandMap's shortNames are set up
  2019. // correctly. (see @code{defaultExCommandMap}).
  2020. for (var i = commandName.length; i > 0; i--) {
  2021. var prefix = commandName.substring(0, i);
  2022. if (this.commandMap_[prefix]) {
  2023. var command = this.commandMap_[prefix];
  2024. if (command.name.indexOf(commandName) === 0) {
  2025. return command;
  2026. }
  2027. }
  2028. }
  2029. return null;
  2030. },
  2031. buildCommandMap_: function() {
  2032. this.commandMap_ = {};
  2033. for (var i = 0; i < defaultExCommandMap.length; i++) {
  2034. var command = defaultExCommandMap[i];
  2035. var key = command.shortName || command.name;
  2036. this.commandMap_[key] = command;
  2037. }
  2038. },
  2039. map: function(lhs, rhs) {
  2040. if (lhs.charAt(0) == ':') {
  2041. var commandName = lhs.substring(1);
  2042. if (rhs.charAt(0) == ':') {
  2043. // Ex to Ex mapping
  2044. this.commandMap_[commandName] = {
  2045. name: commandName,
  2046. type: 'exToEx',
  2047. toInput: rhs.substring(1)
  2048. };
  2049. } else {
  2050. // Ex to key mapping
  2051. this.commandMap_[commandName] = {
  2052. name: commandName,
  2053. type: 'exToKey',
  2054. toKeys: parseKeyString(rhs)
  2055. };
  2056. }
  2057. } else {
  2058. if (rhs.charAt(0) == ':') {
  2059. // Key to Ex mapping.
  2060. defaultKeymap.unshift({
  2061. keys: parseKeyString(lhs),
  2062. type: 'keyToEx',
  2063. exArgs: { input: rhs.substring(1) }});
  2064. } else {
  2065. // Key to key mapping
  2066. defaultKeymap.unshift({
  2067. keys: parseKeyString(lhs),
  2068. type: 'keyToKey',
  2069. toKeys: parseKeyString(rhs)
  2070. });
  2071. }
  2072. }
  2073. }
  2074. };
  2075. // Converts a key string sequence of the form a<C-w>bd<Left> into Vim's
  2076. // keymap representation.
  2077. function parseKeyString(str) {
  2078. var idx = 0;
  2079. var keys = [];
  2080. while (idx < str.length) {
  2081. if (str.charAt(idx) != '<') {
  2082. keys.push(str.charAt(idx));
  2083. idx++;
  2084. continue;
  2085. }
  2086. // Vim key notation here means desktop Vim key-notation.
  2087. // See :help key-notation in desktop Vim.
  2088. var vimKeyNotationStart = ++idx;
  2089. while (str.charAt(idx++) != '>') {}
  2090. var vimKeyNotation = str.substring(vimKeyNotationStart, idx - 1);
  2091. var match = (/^C-(.+)$/).exec(vimKeyNotation);
  2092. if (match) {
  2093. var key;
  2094. switch (match[1]) {
  2095. case 'BS':
  2096. key = 'Backspace';
  2097. break;
  2098. case 'CR':
  2099. key = 'Enter';
  2100. break;
  2101. case 'Del':
  2102. key = 'Delete';
  2103. break;
  2104. default:
  2105. key = match[1];
  2106. break;
  2107. }
  2108. keys.push('Ctrl-' + key);
  2109. }
  2110. }
  2111. return keys;
  2112. }
  2113. var exCommands = {
  2114. map: function(cm, params) {
  2115. var mapArgs = params.commandArgs;
  2116. if (!mapArgs || mapArgs.length < 2) {
  2117. if (cm) {
  2118. showConfirm(cm, 'Invalid mapping: ' + params.input);
  2119. }
  2120. return;
  2121. }
  2122. exCommandDispatcher.map(mapArgs[0], mapArgs[1], cm);
  2123. },
  2124. move: function(cm, params) {
  2125. commandDispatcher.processMotion(cm, getVimState(cm), {
  2126. motion: 'moveToLineOrEdgeOfDocument',
  2127. motionArgs: { forward: false, explicitRepeat: true,
  2128. linewise: true, repeat: params.line }});
  2129. },
  2130. redo: CodeMirror.commands.redo,
  2131. undo: CodeMirror.commands.undo,
  2132. write: function(cm) {
  2133. if (CodeMirror.commands.save) {
  2134. // If a save command is defined, call it.
  2135. CodeMirror.commands.save(cm);
  2136. } else {
  2137. // Saves to text area if no save command is defined.
  2138. cm.save();
  2139. }
  2140. }
  2141. };
  2142. var exCommandDispatcher = new ExCommandDispatcher();
  2143. // Register Vim with CodeMirror
  2144. function buildVimKeyMap() {
  2145. /**
  2146. * Handle the raw key event from CodeMirror. Translate the
  2147. * Shift + key modifier to the resulting letter, while preserving other
  2148. * modifers.
  2149. */
  2150. // TODO: Figure out a way to catch capslock.
  2151. function handleKeyEvent_(cm, key, modifier) {
  2152. if (isUpperCase(key)) {
  2153. // Convert to lower case if shift is not the modifier since the key
  2154. // we get from CodeMirror is always upper case.
  2155. if (modifier == 'Shift') {
  2156. modifier = null;
  2157. }
  2158. else {
  2159. key = key.toLowerCase();
  2160. }
  2161. }
  2162. if (modifier) {
  2163. // Vim will parse modifier+key combination as a single key.
  2164. key = modifier + '-' + key;
  2165. }
  2166. vim.handleKey(cm, key);
  2167. }
  2168. // Closure to bind CodeMirror, key, modifier.
  2169. function keyMapper(key, modifier) {
  2170. return function(cm) {
  2171. handleKeyEvent_(cm, key, modifier);
  2172. };
  2173. }
  2174. var modifiers = ['Shift', 'Ctrl'];
  2175. var keyMap = {
  2176. 'nofallthrough': true,
  2177. 'style': 'fat-cursor'
  2178. };
  2179. function bindKeys(keys, modifier) {
  2180. for (var i = 0; i < keys.length; i++) {
  2181. var key = keys[i];
  2182. if (!modifier && inArray(key, specialSymbols)) {
  2183. // Wrap special symbols with '' because that's how CodeMirror binds
  2184. // them.
  2185. key = "'" + key + "'";
  2186. }
  2187. if (modifier) {
  2188. keyMap[modifier + '-' + key] = keyMapper(keys[i], modifier);
  2189. } else {
  2190. keyMap[key] = keyMapper(keys[i]);
  2191. }
  2192. }
  2193. }
  2194. bindKeys(upperCaseAlphabet);
  2195. bindKeys(upperCaseAlphabet, 'Shift');
  2196. bindKeys(upperCaseAlphabet, 'Ctrl');
  2197. bindKeys(specialSymbols);
  2198. bindKeys(specialSymbols, 'Ctrl');
  2199. bindKeys(numbers);
  2200. bindKeys(numbers, 'Ctrl');
  2201. bindKeys(specialKeys);
  2202. bindKeys(specialKeys, 'Ctrl');
  2203. return keyMap;
  2204. }
  2205. CodeMirror.keyMap.vim = buildVimKeyMap();
  2206. function exitInsertMode(cm) {
  2207. cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1, true);
  2208. cm.setOption('keyMap', 'vim');
  2209. }
  2210. CodeMirror.keyMap['vim-insert'] = {
  2211. // TODO: override navigation keys so that Esc will cancel automatic
  2212. // indentation from o, O, i_<CR>
  2213. 'Esc': exitInsertMode,
  2214. 'Ctrl-[': exitInsertMode,
  2215. 'Ctrl-C': exitInsertMode,
  2216. 'Ctrl-N': 'autocomplete',
  2217. 'Ctrl-P': 'autocomplete',
  2218. 'Enter': function(cm) {
  2219. var fn = CodeMirror.commands.newlineAndIndentContinueComment ||
  2220. CodeMirror.commands.newlineAndIndent;
  2221. fn(cm);
  2222. },
  2223. fallthrough: ['default']
  2224. };
  2225. return vimApi;
  2226. };
  2227. // Initialize Vim and make it available as an API.
  2228. var vim = Vim();
  2229. CodeMirror.Vim = vim;
  2230. }
  2231. )();