poly-cms 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (460) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/Gemfile +27 -0
  4. data/Gemfile.lock +95 -0
  5. data/README.md +3 -0
  6. data/Rakefile +45 -0
  7. data/bin/poly +88 -0
  8. data/config.ru +3 -0
  9. data/core/admin/assets/images/icons/cache-clear.svg +11 -0
  10. data/core/admin/assets/images/icons/delete-dark.svg +10 -0
  11. data/core/admin/assets/images/icons/delete.svg +10 -0
  12. data/core/admin/assets/images/icons/handle.svg +12 -0
  13. data/core/admin/assets/images/icons/logout.svg +13 -0
  14. data/core/admin/assets/images/icons/move.svg +10 -0
  15. data/core/admin/assets/images/icons/new.svg +9 -0
  16. data/core/admin/assets/images/icons/prompt.svg +11 -0
  17. data/core/admin/assets/images/icons/refresh.svg +40 -0
  18. data/core/admin/assets/images/icons/save.svg +7 -0
  19. data/core/admin/assets/images/poly-small-boxed.svg +51 -0
  20. data/core/admin/assets/images/poly-small.svg +52 -0
  21. data/core/admin/assets/images/poly.svg +36 -0
  22. data/core/admin/assets/js/app/file-ext.js +15 -0
  23. data/core/admin/assets/js/app/views/editor.js +300 -0
  24. data/core/admin/assets/js/app/views/git.js +65 -0
  25. data/core/admin/assets/js/app/views/main.js +59 -0
  26. data/core/admin/assets/js/main.js +44 -0
  27. data/core/admin/assets/js/routes.js +25 -0
  28. data/core/admin/assets/js/vendor/backbone.js +4 -0
  29. data/core/admin/assets/js/vendor/codemirror/.gitattributes +8 -0
  30. data/core/admin/assets/js/vendor/codemirror/.gitignore +6 -0
  31. data/core/admin/assets/js/vendor/codemirror/.travis.yml +3 -0
  32. data/core/admin/assets/js/vendor/codemirror/addon/comment/comment.js +145 -0
  33. data/core/admin/assets/js/vendor/codemirror/addon/comment/continuecomment.js +54 -0
  34. data/core/admin/assets/js/vendor/codemirror/addon/dialog/dialog.css +32 -0
  35. data/core/admin/assets/js/vendor/codemirror/addon/dialog/dialog.js +80 -0
  36. data/core/admin/assets/js/vendor/codemirror/addon/display/fullscreen.css +6 -0
  37. data/core/admin/assets/js/vendor/codemirror/addon/display/fullscreen.js +30 -0
  38. data/core/admin/assets/js/vendor/codemirror/addon/display/placeholder.js +54 -0
  39. data/core/admin/assets/js/vendor/codemirror/addon/edit/closebrackets.js +82 -0
  40. data/core/admin/assets/js/vendor/codemirror/addon/edit/closetag.js +87 -0
  41. data/core/admin/assets/js/vendor/codemirror/addon/edit/continuelist.js +25 -0
  42. data/core/admin/assets/js/vendor/codemirror/addon/edit/matchbrackets.js +86 -0
  43. data/core/admin/assets/js/vendor/codemirror/addon/edit/matchtags.js +56 -0
  44. data/core/admin/assets/js/vendor/codemirror/addon/edit/trailingspace.js +15 -0
  45. data/core/admin/assets/js/vendor/codemirror/addon/fold/brace-fold.js +93 -0
  46. data/core/admin/assets/js/vendor/codemirror/addon/fold/comment-fold.js +40 -0
  47. data/core/admin/assets/js/vendor/codemirror/addon/fold/foldcode.js +75 -0
  48. data/core/admin/assets/js/vendor/codemirror/addon/fold/foldgutter.css +21 -0
  49. data/core/admin/assets/js/vendor/codemirror/addon/fold/foldgutter.js +124 -0
  50. data/core/admin/assets/js/vendor/codemirror/addon/fold/indent-fold.js +26 -0
  51. data/core/admin/assets/js/vendor/codemirror/addon/fold/xml-fold.js +167 -0
  52. data/core/admin/assets/js/vendor/codemirror/addon/hint/anyword-hint.js +34 -0
  53. data/core/admin/assets/js/vendor/codemirror/addon/hint/css-hint.js +50 -0
  54. data/core/admin/assets/js/vendor/codemirror/addon/hint/html-hint.js +337 -0
  55. data/core/admin/assets/js/vendor/codemirror/addon/hint/javascript-hint.js +130 -0
  56. data/core/admin/assets/js/vendor/codemirror/addon/hint/pig-hint.js +121 -0
  57. data/core/admin/assets/js/vendor/codemirror/addon/hint/python-hint.js +95 -0
  58. data/core/admin/assets/js/vendor/codemirror/addon/hint/show-hint.css +38 -0
  59. data/core/admin/assets/js/vendor/codemirror/addon/hint/show-hint.js +274 -0
  60. data/core/admin/assets/js/vendor/codemirror/addon/hint/sql-hint.js +105 -0
  61. data/core/admin/assets/js/vendor/codemirror/addon/hint/xml-hint.js +69 -0
  62. data/core/admin/assets/js/vendor/codemirror/addon/lint/coffeescript-lint.js +27 -0
  63. data/core/admin/assets/js/vendor/codemirror/addon/lint/css-lint.js +19 -0
  64. data/core/admin/assets/js/vendor/codemirror/addon/lint/javascript-lint.js +126 -0
  65. data/core/admin/assets/js/vendor/codemirror/addon/lint/json-lint.js +17 -0
  66. data/core/admin/assets/js/vendor/codemirror/addon/lint/lint.css +73 -0
  67. data/core/admin/assets/js/vendor/codemirror/addon/lint/lint.js +203 -0
  68. data/core/admin/assets/js/vendor/codemirror/addon/merge/dep/diff_match_patch.js +50 -0
  69. data/core/admin/assets/js/vendor/codemirror/addon/merge/merge.css +92 -0
  70. data/core/admin/assets/js/vendor/codemirror/addon/merge/merge.js +474 -0
  71. data/core/admin/assets/js/vendor/codemirror/addon/mode/loadmode.js +51 -0
  72. data/core/admin/assets/js/vendor/codemirror/addon/mode/multiplex.js +101 -0
  73. data/core/admin/assets/js/vendor/codemirror/addon/mode/multiplex_test.js +30 -0
  74. data/core/admin/assets/js/vendor/codemirror/addon/mode/overlay.js +59 -0
  75. data/core/admin/assets/js/vendor/codemirror/addon/runmode/colorize.js +29 -0
  76. data/core/admin/assets/js/vendor/codemirror/addon/runmode/runmode-standalone.js +136 -0
  77. data/core/admin/assets/js/vendor/codemirror/addon/runmode/runmode.js +56 -0
  78. data/core/admin/assets/js/vendor/codemirror/addon/runmode/runmode.node.js +103 -0
  79. data/core/admin/assets/js/vendor/codemirror/addon/scroll/scrollpastend.js +34 -0
  80. data/core/admin/assets/js/vendor/codemirror/addon/search/match-highlighter.js +91 -0
  81. data/core/admin/assets/js/vendor/codemirror/addon/search/search.js +133 -0
  82. data/core/admin/assets/js/vendor/codemirror/addon/search/searchcursor.js +143 -0
  83. data/core/admin/assets/js/vendor/codemirror/addon/selection/active-line.js +39 -0
  84. data/core/admin/assets/js/vendor/codemirror/addon/selection/mark-selection.js +108 -0
  85. data/core/admin/assets/js/vendor/codemirror/addon/tern/tern.css +85 -0
  86. data/core/admin/assets/js/vendor/codemirror/addon/tern/tern.js +632 -0
  87. data/core/admin/assets/js/vendor/codemirror/addon/tern/worker.js +41 -0
  88. data/core/admin/assets/js/vendor/codemirror/addon/wrap/hardwrap.js +99 -0
  89. data/core/admin/assets/js/vendor/codemirror/bin/authors.sh +6 -0
  90. data/core/admin/assets/js/vendor/codemirror/bin/compress +92 -0
  91. data/core/admin/assets/js/vendor/codemirror/bin/lint +16 -0
  92. data/core/admin/assets/js/vendor/codemirror/bin/source-highlight +61 -0
  93. data/core/admin/assets/js/vendor/codemirror/bower.json +15 -0
  94. data/core/admin/assets/js/vendor/codemirror/demo/activeline.html +78 -0
  95. data/core/admin/assets/js/vendor/codemirror/demo/anywordhint.html +79 -0
  96. data/core/admin/assets/js/vendor/codemirror/demo/bidi.html +74 -0
  97. data/core/admin/assets/js/vendor/codemirror/demo/btree.html +86 -0
  98. data/core/admin/assets/js/vendor/codemirror/demo/buffers.html +109 -0
  99. data/core/admin/assets/js/vendor/codemirror/demo/changemode.html +59 -0
  100. data/core/admin/assets/js/vendor/codemirror/demo/closebrackets.html +63 -0
  101. data/core/admin/assets/js/vendor/codemirror/demo/closetag.html +40 -0
  102. data/core/admin/assets/js/vendor/codemirror/demo/complete.html +80 -0
  103. data/core/admin/assets/js/vendor/codemirror/demo/emacs.html +75 -0
  104. data/core/admin/assets/js/vendor/codemirror/demo/folding.html +75 -0
  105. data/core/admin/assets/js/vendor/codemirror/demo/fullscreen.html +130 -0
  106. data/core/admin/assets/js/vendor/codemirror/demo/hardwrap.html +69 -0
  107. data/core/admin/assets/js/vendor/codemirror/demo/html5complete.html +54 -0
  108. data/core/admin/assets/js/vendor/codemirror/demo/indentwrap.html +58 -0
  109. data/core/admin/assets/js/vendor/codemirror/demo/lint.html +171 -0
  110. data/core/admin/assets/js/vendor/codemirror/demo/loadmode.html +49 -0
  111. data/core/admin/assets/js/vendor/codemirror/demo/marker.html +52 -0
  112. data/core/admin/assets/js/vendor/codemirror/demo/markselection.html +45 -0
  113. data/core/admin/assets/js/vendor/codemirror/demo/matchhighlighter.html +47 -0
  114. data/core/admin/assets/js/vendor/codemirror/demo/matchtags.html +49 -0
  115. data/core/admin/assets/js/vendor/codemirror/demo/merge.html +82 -0
  116. data/core/admin/assets/js/vendor/codemirror/demo/multiplex.html +75 -0
  117. data/core/admin/assets/js/vendor/codemirror/demo/mustache.html +68 -0
  118. data/core/admin/assets/js/vendor/codemirror/demo/placeholder.html +45 -0
  119. data/core/admin/assets/js/vendor/codemirror/demo/preview.html +88 -0
  120. data/core/admin/assets/js/vendor/codemirror/demo/resize.html +58 -0
  121. data/core/admin/assets/js/vendor/codemirror/demo/runmode.html +62 -0
  122. data/core/admin/assets/js/vendor/codemirror/demo/search.html +94 -0
  123. data/core/admin/assets/js/vendor/codemirror/demo/spanaffectswrapping_shim.html +85 -0
  124. data/core/admin/assets/js/vendor/codemirror/demo/tern.html +128 -0
  125. data/core/admin/assets/js/vendor/codemirror/demo/theme.html +121 -0
  126. data/core/admin/assets/js/vendor/codemirror/demo/trailingspace.html +48 -0
  127. data/core/admin/assets/js/vendor/codemirror/demo/variableheight.html +61 -0
  128. data/core/admin/assets/js/vendor/codemirror/demo/vim.html +74 -0
  129. data/core/admin/assets/js/vendor/codemirror/demo/visibletabs.html +62 -0
  130. data/core/admin/assets/js/vendor/codemirror/demo/widget.html +85 -0
  131. data/core/admin/assets/js/vendor/codemirror/demo/xmlcomplete.html +116 -0
  132. data/core/admin/assets/js/vendor/codemirror/doc/activebookmark.js +42 -0
  133. data/core/admin/assets/js/vendor/codemirror/doc/compress.html +231 -0
  134. data/core/admin/assets/js/vendor/codemirror/doc/docs.css +226 -0
  135. data/core/admin/assets/js/vendor/codemirror/doc/internals.html +503 -0
  136. data/core/admin/assets/js/vendor/codemirror/doc/logo.png +0 -0
  137. data/core/admin/assets/js/vendor/codemirror/doc/logo.svg +147 -0
  138. data/core/admin/assets/js/vendor/codemirror/doc/manual.html +2512 -0
  139. data/core/admin/assets/js/vendor/codemirror/doc/realworld.html +134 -0
  140. data/core/admin/assets/js/vendor/codemirror/doc/releases.html +779 -0
  141. data/core/admin/assets/js/vendor/codemirror/doc/reporting.html +61 -0
  142. data/core/admin/assets/js/vendor/codemirror/doc/upgrade_v2.2.html +96 -0
  143. data/core/admin/assets/js/vendor/codemirror/doc/upgrade_v3.html +230 -0
  144. data/core/admin/assets/js/vendor/codemirror/index.html +192 -0
  145. data/core/admin/assets/js/vendor/codemirror/keymap/emacs.js +387 -0
  146. data/core/admin/assets/js/vendor/codemirror/keymap/extra.js +43 -0
  147. data/core/admin/assets/js/vendor/codemirror/keymap/vim.js +3704 -0
  148. data/core/admin/assets/js/vendor/codemirror/lib/codemirror.css +263 -0
  149. data/core/admin/assets/js/vendor/codemirror/lib/codemirror.js +5910 -0
  150. data/core/admin/assets/js/vendor/codemirror/mode/apl/apl.js +160 -0
  151. data/core/admin/assets/js/vendor/codemirror/mode/apl/index.html +72 -0
  152. data/core/admin/assets/js/vendor/codemirror/mode/asterisk/asterisk.js +183 -0
  153. data/core/admin/assets/js/vendor/codemirror/mode/asterisk/index.html +154 -0
  154. data/core/admin/assets/js/vendor/codemirror/mode/clike/clike.js +362 -0
  155. data/core/admin/assets/js/vendor/codemirror/mode/clike/index.html +195 -0
  156. data/core/admin/assets/js/vendor/codemirror/mode/clike/scala.html +767 -0
  157. data/core/admin/assets/js/vendor/codemirror/mode/clojure/clojure.js +224 -0
  158. data/core/admin/assets/js/vendor/codemirror/mode/clojure/index.html +88 -0
  159. data/core/admin/assets/js/vendor/codemirror/mode/cobol/cobol.js +240 -0
  160. data/core/admin/assets/js/vendor/codemirror/mode/cobol/index.html +210 -0
  161. data/core/admin/assets/js/vendor/codemirror/mode/coffeescript/coffeescript.js +349 -0
  162. data/core/admin/assets/js/vendor/codemirror/mode/coffeescript/index.html +740 -0
  163. data/core/admin/assets/js/vendor/codemirror/mode/commonlisp/commonlisp.js +105 -0
  164. data/core/admin/assets/js/vendor/codemirror/mode/commonlisp/index.html +177 -0
  165. data/core/admin/assets/js/vendor/codemirror/mode/css/css.js +639 -0
  166. data/core/admin/assets/js/vendor/codemirror/mode/css/index.html +70 -0
  167. data/core/admin/assets/js/vendor/codemirror/mode/css/scss.html +157 -0
  168. data/core/admin/assets/js/vendor/codemirror/mode/css/scss_test.js +84 -0
  169. data/core/admin/assets/js/vendor/codemirror/mode/css/test.js +130 -0
  170. data/core/admin/assets/js/vendor/codemirror/mode/d/d.js +205 -0
  171. data/core/admin/assets/js/vendor/codemirror/mode/d/index.html +273 -0
  172. data/core/admin/assets/js/vendor/codemirror/mode/diff/diff.js +32 -0
  173. data/core/admin/assets/js/vendor/codemirror/mode/diff/index.html +117 -0
  174. data/core/admin/assets/js/vendor/codemirror/mode/dtd/dtd.js +127 -0
  175. data/core/admin/assets/js/vendor/codemirror/mode/dtd/index.html +89 -0
  176. data/core/admin/assets/js/vendor/codemirror/mode/ecl/ecl.js +192 -0
  177. data/core/admin/assets/js/vendor/codemirror/mode/ecl/index.html +52 -0
  178. data/core/admin/assets/js/vendor/codemirror/mode/eiffel/eiffel.js +147 -0
  179. data/core/admin/assets/js/vendor/codemirror/mode/eiffel/index.html +430 -0
  180. data/core/admin/assets/js/vendor/codemirror/mode/erlang/erlang.js +484 -0
  181. data/core/admin/assets/js/vendor/codemirror/mode/erlang/index.html +75 -0
  182. data/core/admin/assets/js/vendor/codemirror/mode/fortran/fortran.js +173 -0
  183. data/core/admin/assets/js/vendor/codemirror/mode/fortran/index.html +81 -0
  184. data/core/admin/assets/js/vendor/codemirror/mode/gas/gas.js +330 -0
  185. data/core/admin/assets/js/vendor/codemirror/mode/gas/index.html +68 -0
  186. data/core/admin/assets/js/vendor/codemirror/mode/gfm/gfm.js +97 -0
  187. data/core/admin/assets/js/vendor/codemirror/mode/gfm/index.html +82 -0
  188. data/core/admin/assets/js/vendor/codemirror/mode/gfm/test.js +112 -0
  189. data/core/admin/assets/js/vendor/codemirror/mode/gherkin/gherkin.js +168 -0
  190. data/core/admin/assets/js/vendor/codemirror/mode/gherkin/index.html +48 -0
  191. data/core/admin/assets/js/vendor/codemirror/mode/go/go.js +168 -0
  192. data/core/admin/assets/js/vendor/codemirror/mode/go/index.html +85 -0
  193. data/core/admin/assets/js/vendor/codemirror/mode/groovy/groovy.js +211 -0
  194. data/core/admin/assets/js/vendor/codemirror/mode/groovy/index.html +84 -0
  195. data/core/admin/assets/js/vendor/codemirror/mode/haml/haml.js +153 -0
  196. data/core/admin/assets/js/vendor/codemirror/mode/haml/index.html +79 -0
  197. data/core/admin/assets/js/vendor/codemirror/mode/haml/test.js +94 -0
  198. data/core/admin/assets/js/vendor/codemirror/mode/haskell/haskell.js +250 -0
  199. data/core/admin/assets/js/vendor/codemirror/mode/haskell/index.html +73 -0
  200. data/core/admin/assets/js/vendor/codemirror/mode/haxe/haxe.js +429 -0
  201. data/core/admin/assets/js/vendor/codemirror/mode/haxe/index.html +103 -0
  202. data/core/admin/assets/js/vendor/codemirror/mode/htmlembedded/htmlembedded.js +73 -0
  203. data/core/admin/assets/js/vendor/codemirror/mode/htmlembedded/index.html +60 -0
  204. data/core/admin/assets/js/vendor/codemirror/mode/htmlmixed/htmlmixed.js +104 -0
  205. data/core/admin/assets/js/vendor/codemirror/mode/htmlmixed/index.html +85 -0
  206. data/core/admin/assets/js/vendor/codemirror/mode/http/http.js +98 -0
  207. data/core/admin/assets/js/vendor/codemirror/mode/http/index.html +45 -0
  208. data/core/admin/assets/js/vendor/codemirror/mode/index.html +112 -0
  209. data/core/admin/assets/js/vendor/codemirror/mode/jade/index.html +66 -0
  210. data/core/admin/assets/js/vendor/codemirror/mode/jade/jade.js +90 -0
  211. data/core/admin/assets/js/vendor/codemirror/mode/javascript/index.html +107 -0
  212. data/core/admin/assets/js/vendor/codemirror/mode/javascript/javascript.js +480 -0
  213. data/core/admin/assets/js/vendor/codemirror/mode/javascript/test.js +10 -0
  214. data/core/admin/assets/js/vendor/codemirror/mode/javascript/typescript.html +61 -0
  215. data/core/admin/assets/js/vendor/codemirror/mode/jinja2/index.html +50 -0
  216. data/core/admin/assets/js/vendor/codemirror/mode/jinja2/jinja2.js +42 -0
  217. data/core/admin/assets/js/vendor/codemirror/mode/less/index.html +753 -0
  218. data/core/admin/assets/js/vendor/codemirror/mode/less/less.js +344 -0
  219. data/core/admin/assets/js/vendor/codemirror/mode/livescript/index.html +459 -0
  220. data/core/admin/assets/js/vendor/codemirror/mode/livescript/livescript.js +267 -0
  221. data/core/admin/assets/js/vendor/codemirror/mode/livescript/livescript.ls +266 -0
  222. data/core/admin/assets/js/vendor/codemirror/mode/lua/index.html +86 -0
  223. data/core/admin/assets/js/vendor/codemirror/mode/lua/lua.js +144 -0
  224. data/core/admin/assets/js/vendor/codemirror/mode/markdown/index.html +359 -0
  225. data/core/admin/assets/js/vendor/codemirror/mode/markdown/markdown.js +551 -0
  226. data/core/admin/assets/js/vendor/codemirror/mode/markdown/test.js +656 -0
  227. data/core/admin/assets/js/vendor/codemirror/mode/meta.js +89 -0
  228. data/core/admin/assets/js/vendor/codemirror/mode/mirc/index.html +161 -0
  229. data/core/admin/assets/js/vendor/codemirror/mode/mirc/mirc.js +177 -0
  230. data/core/admin/assets/js/vendor/codemirror/mode/nginx/index.html +181 -0
  231. data/core/admin/assets/js/vendor/codemirror/mode/nginx/nginx.js +163 -0
  232. data/core/admin/assets/js/vendor/codemirror/mode/ntriples/index.html +45 -0
  233. data/core/admin/assets/js/vendor/codemirror/mode/ntriples/ntriples.js +170 -0
  234. data/core/admin/assets/js/vendor/codemirror/mode/ocaml/index.html +146 -0
  235. data/core/admin/assets/js/vendor/codemirror/mode/ocaml/ocaml.js +116 -0
  236. data/core/admin/assets/js/vendor/codemirror/mode/octave/index.html +95 -0
  237. data/core/admin/assets/js/vendor/codemirror/mode/octave/octave.js +118 -0
  238. data/core/admin/assets/js/vendor/codemirror/mode/pascal/index.html +61 -0
  239. data/core/admin/assets/js/vendor/codemirror/mode/pascal/pascal.js +94 -0
  240. data/core/admin/assets/js/vendor/codemirror/mode/perl/index.html +75 -0
  241. data/core/admin/assets/js/vendor/codemirror/mode/perl/perl.js +816 -0
  242. data/core/admin/assets/js/vendor/codemirror/mode/php/index.html +62 -0
  243. data/core/admin/assets/js/vendor/codemirror/mode/php/php.js +132 -0
  244. data/core/admin/assets/js/vendor/codemirror/mode/pig/index.html +55 -0
  245. data/core/admin/assets/js/vendor/codemirror/mode/pig/pig.js +171 -0
  246. data/core/admin/assets/js/vendor/codemirror/mode/properties/index.html +53 -0
  247. data/core/admin/assets/js/vendor/codemirror/mode/properties/properties.js +63 -0
  248. data/core/admin/assets/js/vendor/codemirror/mode/python/index.html +187 -0
  249. data/core/admin/assets/js/vendor/codemirror/mode/python/python.js +368 -0
  250. data/core/admin/assets/js/vendor/codemirror/mode/q/index.html +144 -0
  251. data/core/admin/assets/js/vendor/codemirror/mode/q/q.js +124 -0
  252. data/core/admin/assets/js/vendor/codemirror/mode/r/index.html +86 -0
  253. data/core/admin/assets/js/vendor/codemirror/mode/r/r.js +141 -0
  254. data/core/admin/assets/js/vendor/codemirror/mode/rpm/changes/changes.js +19 -0
  255. data/core/admin/assets/js/vendor/codemirror/mode/rpm/changes/index.html +67 -0
  256. data/core/admin/assets/js/vendor/codemirror/mode/rpm/spec/index.html +114 -0
  257. data/core/admin/assets/js/vendor/codemirror/mode/rpm/spec/spec.css +5 -0
  258. data/core/admin/assets/js/vendor/codemirror/mode/rpm/spec/spec.js +66 -0
  259. data/core/admin/assets/js/vendor/codemirror/mode/rst/index.html +534 -0
  260. data/core/admin/assets/js/vendor/codemirror/mode/rst/rst.js +560 -0
  261. data/core/admin/assets/js/vendor/codemirror/mode/ruby/index.html +185 -0
  262. data/core/admin/assets/js/vendor/codemirror/mode/ruby/ruby.js +247 -0
  263. data/core/admin/assets/js/vendor/codemirror/mode/rust/index.html +61 -0
  264. data/core/admin/assets/js/vendor/codemirror/mode/rust/rust.js +436 -0
  265. data/core/admin/assets/js/vendor/codemirror/mode/sass/index.html +66 -0
  266. data/core/admin/assets/js/vendor/codemirror/mode/sass/sass.js +330 -0
  267. data/core/admin/assets/js/vendor/codemirror/mode/scheme/index.html +77 -0
  268. data/core/admin/assets/js/vendor/codemirror/mode/scheme/scheme.js +232 -0
  269. data/core/admin/assets/js/vendor/codemirror/mode/shell/index.html +66 -0
  270. data/core/admin/assets/js/vendor/codemirror/mode/shell/shell.js +118 -0
  271. data/core/admin/assets/js/vendor/codemirror/mode/sieve/index.html +93 -0
  272. data/core/admin/assets/js/vendor/codemirror/mode/sieve/sieve.js +183 -0
  273. data/core/admin/assets/js/vendor/codemirror/mode/smalltalk/index.html +68 -0
  274. data/core/admin/assets/js/vendor/codemirror/mode/smalltalk/smalltalk.js +151 -0
  275. data/core/admin/assets/js/vendor/codemirror/mode/smarty/index.html +136 -0
  276. data/core/admin/assets/js/vendor/codemirror/mode/smarty/smarty.js +205 -0
  277. data/core/admin/assets/js/vendor/codemirror/mode/smartymixed/index.html +114 -0
  278. data/core/admin/assets/js/vendor/codemirror/mode/smartymixed/smartymixed.js +175 -0
  279. data/core/admin/assets/js/vendor/codemirror/mode/sparql/index.html +54 -0
  280. data/core/admin/assets/js/vendor/codemirror/mode/sparql/sparql.js +145 -0
  281. data/core/admin/assets/js/vendor/codemirror/mode/sql/index.html +75 -0
  282. data/core/admin/assets/js/vendor/codemirror/mode/sql/sql.js +365 -0
  283. data/core/admin/assets/js/vendor/codemirror/mode/stex/index.html +110 -0
  284. data/core/admin/assets/js/vendor/codemirror/mode/stex/stex.js +246 -0
  285. data/core/admin/assets/js/vendor/codemirror/mode/stex/test.js +120 -0
  286. data/core/admin/assets/js/vendor/codemirror/mode/tcl/index.html +143 -0
  287. data/core/admin/assets/js/vendor/codemirror/mode/tcl/tcl.js +131 -0
  288. data/core/admin/assets/js/vendor/codemirror/mode/tiddlywiki/index.html +155 -0
  289. data/core/admin/assets/js/vendor/codemirror/mode/tiddlywiki/tiddlywiki.css +14 -0
  290. data/core/admin/assets/js/vendor/codemirror/mode/tiddlywiki/tiddlywiki.js +353 -0
  291. data/core/admin/assets/js/vendor/codemirror/mode/tiki/index.html +95 -0
  292. data/core/admin/assets/js/vendor/codemirror/mode/tiki/tiki.css +26 -0
  293. data/core/admin/assets/js/vendor/codemirror/mode/tiki/tiki.js +308 -0
  294. data/core/admin/assets/js/vendor/codemirror/mode/toml/index.html +73 -0
  295. data/core/admin/assets/js/vendor/codemirror/mode/toml/toml.js +71 -0
  296. data/core/admin/assets/js/vendor/codemirror/mode/turtle/index.html +51 -0
  297. data/core/admin/assets/js/vendor/codemirror/mode/turtle/turtle.js +145 -0
  298. data/core/admin/assets/js/vendor/codemirror/mode/vb/index.html +103 -0
  299. data/core/admin/assets/js/vendor/codemirror/mode/vb/vb.js +259 -0
  300. data/core/admin/assets/js/vendor/codemirror/mode/vbscript/index.html +55 -0
  301. data/core/admin/assets/js/vendor/codemirror/mode/vbscript/vbscript.js +334 -0
  302. data/core/admin/assets/js/vendor/codemirror/mode/velocity/index.html +119 -0
  303. data/core/admin/assets/js/vendor/codemirror/mode/velocity/velocity.js +186 -0
  304. data/core/admin/assets/js/vendor/codemirror/mode/verilog/index.html +132 -0
  305. data/core/admin/assets/js/vendor/codemirror/mode/verilog/verilog.js +182 -0
  306. data/core/admin/assets/js/vendor/codemirror/mode/xml/index.html +57 -0
  307. data/core/admin/assets/js/vendor/codemirror/mode/xml/xml.js +345 -0
  308. data/core/admin/assets/js/vendor/codemirror/mode/xquery/index.html +210 -0
  309. data/core/admin/assets/js/vendor/codemirror/mode/xquery/test.js +64 -0
  310. data/core/admin/assets/js/vendor/codemirror/mode/xquery/xquery.js +432 -0
  311. data/core/admin/assets/js/vendor/codemirror/mode/yaml/index.html +80 -0
  312. data/core/admin/assets/js/vendor/codemirror/mode/yaml/yaml.js +97 -0
  313. data/core/admin/assets/js/vendor/codemirror/mode/z80/index.html +52 -0
  314. data/core/admin/assets/js/vendor/codemirror/mode/z80/z80.js +85 -0
  315. data/core/admin/assets/js/vendor/codemirror/package.json +19 -0
  316. data/core/admin/assets/js/vendor/codemirror/test/comment_test.js +51 -0
  317. data/core/admin/assets/js/vendor/codemirror/test/doc_test.js +329 -0
  318. data/core/admin/assets/js/vendor/codemirror/test/driver.js +139 -0
  319. data/core/admin/assets/js/vendor/codemirror/test/emacs_test.js +135 -0
  320. data/core/admin/assets/js/vendor/codemirror/test/index.html +209 -0
  321. data/core/admin/assets/js/vendor/codemirror/test/lint/acorn.js +1593 -0
  322. data/core/admin/assets/js/vendor/codemirror/test/lint/lint.js +139 -0
  323. data/core/admin/assets/js/vendor/codemirror/test/lint/walk.js +216 -0
  324. data/core/admin/assets/js/vendor/codemirror/test/mode_test.css +10 -0
  325. data/core/admin/assets/js/vendor/codemirror/test/mode_test.js +200 -0
  326. data/core/admin/assets/js/vendor/codemirror/test/phantom_driver.js +31 -0
  327. data/core/admin/assets/js/vendor/codemirror/test/run.js +34 -0
  328. data/core/admin/assets/js/vendor/codemirror/test/test.js +1562 -0
  329. data/core/admin/assets/js/vendor/codemirror/test/vim_test.js +2391 -0
  330. data/core/admin/assets/js/vendor/codemirror/theme/3024-day.css +34 -0
  331. data/core/admin/assets/js/vendor/codemirror/theme/3024-night.css +34 -0
  332. data/core/admin/assets/js/vendor/codemirror/theme/ambiance-mobile.css +5 -0
  333. data/core/admin/assets/js/vendor/codemirror/theme/ambiance.css +75 -0
  334. data/core/admin/assets/js/vendor/codemirror/theme/base16-dark.css +34 -0
  335. data/core/admin/assets/js/vendor/codemirror/theme/base16-light.css +34 -0
  336. data/core/admin/assets/js/vendor/codemirror/theme/blackboard.css +28 -0
  337. data/core/admin/assets/js/vendor/codemirror/theme/cobalt.css +21 -0
  338. data/core/admin/assets/js/vendor/codemirror/theme/eclipse.css +23 -0
  339. data/core/admin/assets/js/vendor/codemirror/theme/elegant.css +13 -0
  340. data/core/admin/assets/js/vendor/codemirror/theme/erlang-dark.css +30 -0
  341. data/core/admin/assets/js/vendor/codemirror/theme/lesser-dark.css +47 -0
  342. data/core/admin/assets/js/vendor/codemirror/theme/mbo.css +35 -0
  343. data/core/admin/assets/js/vendor/codemirror/theme/midnight.css +43 -0
  344. data/core/admin/assets/js/vendor/codemirror/theme/monokai.css +29 -0
  345. data/core/admin/assets/js/vendor/codemirror/theme/neat.css +12 -0
  346. data/core/admin/assets/js/vendor/codemirror/theme/night.css +24 -0
  347. data/core/admin/assets/js/vendor/codemirror/theme/paraiso-dark.css +34 -0
  348. data/core/admin/assets/js/vendor/codemirror/theme/paraiso-light.css +34 -0
  349. data/core/admin/assets/js/vendor/codemirror/theme/rubyblue.css +23 -0
  350. data/core/admin/assets/js/vendor/codemirror/theme/solarized-mod.css +181 -0
  351. data/core/admin/assets/js/vendor/codemirror/theme/solarized.css +180 -0
  352. data/core/admin/assets/js/vendor/codemirror/theme/the-matrix.css +26 -0
  353. data/core/admin/assets/js/vendor/codemirror/theme/tomorrow-night-eighties.css +34 -0
  354. data/core/admin/assets/js/vendor/codemirror/theme/twilight.css +28 -0
  355. data/core/admin/assets/js/vendor/codemirror/theme/vibrant-ink.css +30 -0
  356. data/core/admin/assets/js/vendor/codemirror/theme/xq-dark.css +49 -0
  357. data/core/admin/assets/js/vendor/codemirror/theme/xq-light.css +43 -0
  358. data/core/admin/assets/js/vendor/fastclick.js +777 -0
  359. data/core/admin/assets/js/vendor/jquery.js +6 -0
  360. data/core/admin/assets/js/vendor/lodash.underscore.js +38 -0
  361. data/core/admin/assets/js/vendor/require.js +36 -0
  362. data/core/admin/assets/scss/bootstrap.scss +18 -0
  363. data/core/admin/assets/scss/modules/_all.scss +7 -0
  364. data/core/admin/assets/scss/modules/_colors.scss +7 -0
  365. data/core/admin/assets/scss/modules/_extensions.scss +24 -0
  366. data/core/admin/assets/scss/modules/_mixins.scss +18 -0
  367. data/core/admin/assets/scss/modules/_susy.scss +7 -0
  368. data/core/admin/assets/scss/partials/_base.scss +33 -0
  369. data/core/admin/assets/scss/partials/_buttons.scss +9 -0
  370. data/core/admin/assets/scss/partials/_links.scss +15 -0
  371. data/core/admin/assets/scss/partials/_main.scss +14 -0
  372. data/core/admin/assets/scss/partials/_typography.scss +15 -0
  373. data/core/admin/assets/scss/sections/_editor.scss +274 -0
  374. data/core/admin/assets/scss/sections/_login.scss +34 -0
  375. data/core/admin/assets/scss/vendor/_normalize.scss +406 -0
  376. data/core/admin/layouts/admin.haml +67 -0
  377. data/core/admin/partials/_head.haml +23 -0
  378. data/core/admin/partials/_tree.haml +3 -0
  379. data/core/admin/partials/_treeitem.haml +4 -0
  380. data/core/admin/settings.yml +4 -0
  381. data/core/app.rb +21 -0
  382. data/core/classes/cached.rb +35 -0
  383. data/core/classes/checker.rb +37 -0
  384. data/core/classes/commit.rb +62 -0
  385. data/core/classes/error.rb +9 -0
  386. data/core/classes/page.rb +165 -0
  387. data/core/classes/partial.rb +13 -0
  388. data/core/classes/path.rb +30 -0
  389. data/core/classes/persona.rb +55 -0
  390. data/core/classes/sassfile.rb +95 -0
  391. data/core/classes/settings.rb +19 -0
  392. data/core/classes/theme.rb +55 -0
  393. data/core/helpers/base.rb +46 -0
  394. data/core/helpers/render.rb +26 -0
  395. data/core/helpers/truncate.rb +17 -0
  396. data/core/routes/admin.rb +139 -0
  397. data/core/routes/assetcompiler.rb +23 -0
  398. data/core/routes/base.rb +22 -0
  399. data/core/routes/bootstrap.rb +35 -0
  400. data/core/routes/cache.rb +52 -0
  401. data/core/routes/deploy.rb +16 -0
  402. data/core/routes/git.rb +23 -0
  403. data/poly.gemspec +29 -0
  404. data/sample/.cabi-data +0 -0
  405. data/sample/data/home/index.haml +22 -0
  406. data/sample/data/other/nav/links.yml +4 -0
  407. data/sample/data/page/about/index.haml +12 -0
  408. data/sample/data/page/contact-us/index.haml +11 -0
  409. data/sample/data/page/dynamic-page/index.haml +14 -0
  410. data/sample/data/page/error/index.haml +8 -0
  411. data/sample/data/page/html/index.html +4 -0
  412. data/sample/data/page/no-front-matter/index.haml +7 -0
  413. data/sample/data/page/not-found/index.haml +8 -0
  414. data/sample/data/page/static-page/index.haml +14 -0
  415. data/sample/data/post/about-life/index.md +12 -0
  416. data/sample/data/post/sample-post/index.haml +12 -0
  417. data/sample/data/post/technology/index.haml +12 -0
  418. data/sample/data/post/the-40-hour-workweek/index.haml +9 -0
  419. data/sample/data/post/things-are-changing/index.haml +9 -0
  420. data/sample/data/post/yet-another-post/index.haml +11 -0
  421. data/sample/plugins/my-plugin/plugin.rb +15 -0
  422. data/sample/settings.yml +21 -0
  423. data/sample/themes/base/assets/css/readme.txt +6 -0
  424. data/sample/themes/base/assets/images/poly.svg +36 -0
  425. data/sample/themes/base/assets/js/app/views/main.js +13 -0
  426. data/sample/themes/base/assets/js/main.js +31 -0
  427. data/sample/themes/base/assets/js/vendor/backbone.js +4 -0
  428. data/sample/themes/base/assets/js/vendor/jquery.js +6 -0
  429. data/sample/themes/base/assets/js/vendor/lodash.underscore.js +38 -0
  430. data/sample/themes/base/assets/js/vendor/require.js +36 -0
  431. data/sample/themes/base/assets/scss/bootstrap.scss +20 -0
  432. data/sample/themes/base/assets/scss/modules/_all.scss +7 -0
  433. data/sample/themes/base/assets/scss/modules/_colors.scss +7 -0
  434. data/sample/themes/base/assets/scss/modules/_extensions.scss +13 -0
  435. data/sample/themes/base/assets/scss/modules/_mixins.scss +11 -0
  436. data/sample/themes/base/assets/scss/modules/_susy.scss +7 -0
  437. data/sample/themes/base/assets/scss/partials/_base.scss +33 -0
  438. data/sample/themes/base/assets/scss/partials/_buttons.scss +0 -0
  439. data/sample/themes/base/assets/scss/partials/_links.scss +15 -0
  440. data/sample/themes/base/assets/scss/partials/_main.scss +39 -0
  441. data/sample/themes/base/assets/scss/partials/_typography.scss +15 -0
  442. data/sample/themes/base/assets/scss/sections/_footer.scss +22 -0
  443. data/sample/themes/base/assets/scss/sections/_header.scss +23 -0
  444. data/sample/themes/base/assets/scss/sections/_home.scss +13 -0
  445. data/sample/themes/base/assets/scss/sections/_pages.scss +11 -0
  446. data/sample/themes/base/assets/scss/vendor/_normalize.scss +406 -0
  447. data/sample/themes/base/layouts/default.haml +14 -0
  448. data/sample/themes/base/layouts/post.haml +15 -0
  449. data/sample/themes/base/partials/_footer.haml +9 -0
  450. data/sample/themes/base/partials/_ga.haml +10 -0
  451. data/sample/themes/base/partials/_head.haml +20 -0
  452. data/sample/themes/base/partials/_header.haml +5 -0
  453. data/sample/themes/base/settings.yml +4 -0
  454. data/test/data/post-receive-hook.json +146 -0
  455. data/test/test_basics.rb +50 -0
  456. data/test/test_data.rb +14 -0
  457. data/test/test_deploy.rb +23 -0
  458. data/test/test_includes.rb +43 -0
  459. data/test/test_page.rb +47 -0
  460. metadata +1049 -0
@@ -0,0 +1,387 @@
1
+ (function() {
2
+ "use strict";
3
+
4
+ var Pos = CodeMirror.Pos;
5
+ function posEq(a, b) { return a.line == b.line && a.ch == b.ch; }
6
+
7
+ // Kill 'ring'
8
+
9
+ var killRing = [];
10
+ function addToRing(str) {
11
+ killRing.push(str);
12
+ if (killRing.length > 50) killRing.shift();
13
+ }
14
+ function growRingTop(str) {
15
+ if (!killRing.length) return addToRing(str);
16
+ killRing[killRing.length - 1] += str;
17
+ }
18
+ function getFromRing(n) { return killRing[killRing.length - (n ? Math.min(n, 1) : 1)] || ""; }
19
+ function popFromRing() { if (killRing.length > 1) killRing.pop(); return getFromRing(); }
20
+
21
+ var lastKill = null;
22
+
23
+ function kill(cm, from, to, mayGrow, text) {
24
+ if (text == null) text = cm.getRange(from, to);
25
+
26
+ if (mayGrow && lastKill && lastKill.cm == cm && posEq(from, lastKill.pos) && cm.isClean(lastKill.gen))
27
+ growRingTop(text);
28
+ else
29
+ addToRing(text);
30
+ cm.replaceRange("", from, to, "+delete");
31
+
32
+ if (mayGrow) lastKill = {cm: cm, pos: from, gen: cm.changeGeneration()};
33
+ else lastKill = null;
34
+ }
35
+
36
+ // Boundaries of various units
37
+
38
+ function byChar(cm, pos, dir) {
39
+ return cm.findPosH(pos, dir, "char", true);
40
+ }
41
+
42
+ function byWord(cm, pos, dir) {
43
+ return cm.findPosH(pos, dir, "word", true);
44
+ }
45
+
46
+ function byLine(cm, pos, dir) {
47
+ return cm.findPosV(pos, dir, "line", cm.doc.sel.goalColumn);
48
+ }
49
+
50
+ function byPage(cm, pos, dir) {
51
+ return cm.findPosV(pos, dir, "page", cm.doc.sel.goalColumn);
52
+ }
53
+
54
+ function byParagraph(cm, pos, dir) {
55
+ var no = pos.line, line = cm.getLine(no);
56
+ var sawText = /\S/.test(dir < 0 ? line.slice(0, pos.ch) : line.slice(pos.ch));
57
+ var fst = cm.firstLine(), lst = cm.lastLine();
58
+ for (;;) {
59
+ no += dir;
60
+ if (no < fst || no > lst)
61
+ return cm.clipPos(Pos(no - dir, dir < 0 ? 0 : null));
62
+ line = cm.getLine(no);
63
+ var hasText = /\S/.test(line);
64
+ if (hasText) sawText = true;
65
+ else if (sawText) return Pos(no, 0);
66
+ }
67
+ }
68
+
69
+ function bySentence(cm, pos, dir) {
70
+ var line = pos.line, ch = pos.ch;
71
+ var text = cm.getLine(pos.line), sawWord = false;
72
+ for (;;) {
73
+ var next = text.charAt(ch + (dir < 0 ? -1 : 0));
74
+ if (!next) { // End/beginning of line reached
75
+ if (line == (dir < 0 ? cm.firstLine() : cm.lastLine())) return Pos(line, ch);
76
+ text = cm.getLine(line + dir);
77
+ if (!/\S/.test(text)) return Pos(line, ch);
78
+ line += dir;
79
+ ch = dir < 0 ? text.length : 0;
80
+ continue;
81
+ }
82
+ if (sawWord && /[!?.]/.test(next)) return Pos(line, ch + (dir > 0 ? 1 : 0));
83
+ if (!sawWord) sawWord = /\w/.test(next);
84
+ ch += dir;
85
+ }
86
+ }
87
+
88
+ function byExpr(cm, pos, dir) {
89
+ var wrap;
90
+ if (cm.findMatchingBracket && (wrap = cm.findMatchingBracket(pos, true))
91
+ && wrap.match && (wrap.forward ? 1 : -1) == dir)
92
+ return dir > 0 ? Pos(wrap.to.line, wrap.to.ch + 1) : wrap.to;
93
+
94
+ for (var first = true;; first = false) {
95
+ var token = cm.getTokenAt(pos);
96
+ var after = Pos(pos.line, dir < 0 ? token.start : token.end);
97
+ if (first && dir > 0 && token.end == pos.ch || !/\w/.test(token.string)) {
98
+ var newPos = cm.findPosH(after, dir, "char");
99
+ if (posEq(after, newPos)) return pos;
100
+ else pos = newPos;
101
+ } else {
102
+ return after;
103
+ }
104
+ }
105
+ }
106
+
107
+ // Prefixes (only crudely supported)
108
+
109
+ function getPrefix(cm, precise) {
110
+ var digits = cm.state.emacsPrefix;
111
+ if (!digits) return precise ? null : 1;
112
+ clearPrefix(cm);
113
+ return digits == "-" ? -1 : Number(digits);
114
+ }
115
+
116
+ function repeated(cmd) {
117
+ var f = typeof cmd == "string" ? function(cm) { cm.execCommand(cmd); } : cmd;
118
+ return function(cm) {
119
+ var prefix = getPrefix(cm);
120
+ f(cm);
121
+ for (var i = 1; i < prefix; ++i) f(cm);
122
+ };
123
+ }
124
+
125
+ function findEnd(cm, by, dir) {
126
+ var pos = cm.getCursor(), prefix = getPrefix(cm);
127
+ if (prefix < 0) { dir = -dir; prefix = -prefix; }
128
+ for (var i = 0; i < prefix; ++i) {
129
+ var newPos = by(cm, pos, dir);
130
+ if (posEq(newPos, pos)) break;
131
+ pos = newPos;
132
+ }
133
+ return pos;
134
+ }
135
+
136
+ function move(by, dir) {
137
+ var f = function(cm) {
138
+ cm.extendSelection(findEnd(cm, by, dir));
139
+ };
140
+ f.motion = true;
141
+ return f;
142
+ }
143
+
144
+ function killTo(cm, by, dir) {
145
+ kill(cm, cm.getCursor(), findEnd(cm, by, dir), true);
146
+ }
147
+
148
+ function addPrefix(cm, digit) {
149
+ if (cm.state.emacsPrefix) {
150
+ if (digit != "-") cm.state.emacsPrefix += digit;
151
+ return;
152
+ }
153
+ // Not active yet
154
+ cm.state.emacsPrefix = digit;
155
+ cm.on("keyHandled", maybeClearPrefix);
156
+ cm.on("inputRead", maybeDuplicateInput);
157
+ }
158
+
159
+ var prefixPreservingKeys = {"Alt-G": true, "Ctrl-X": true, "Ctrl-Q": true, "Ctrl-U": true};
160
+
161
+ function maybeClearPrefix(cm, arg) {
162
+ if (!cm.state.emacsPrefixMap && !prefixPreservingKeys.hasOwnProperty(arg))
163
+ clearPrefix(cm);
164
+ }
165
+
166
+ function clearPrefix(cm) {
167
+ cm.state.emacsPrefix = null;
168
+ cm.off("keyHandled", maybeClearPrefix);
169
+ cm.off("inputRead", maybeDuplicateInput);
170
+ }
171
+
172
+ function maybeDuplicateInput(cm, event) {
173
+ var dup = getPrefix(cm);
174
+ if (dup > 1 && event.origin == "+input") {
175
+ var one = event.text.join("\n"), txt = "";
176
+ for (var i = 1; i < dup; ++i) txt += one;
177
+ cm.replaceSelection(txt, "end", "+input");
178
+ }
179
+ }
180
+
181
+ function addPrefixMap(cm) {
182
+ cm.state.emacsPrefixMap = true;
183
+ cm.addKeyMap(prefixMap);
184
+ cm.on("keyHandled", maybeRemovePrefixMap);
185
+ cm.on("inputRead", maybeRemovePrefixMap);
186
+ }
187
+
188
+ function maybeRemovePrefixMap(cm, arg) {
189
+ if (typeof arg == "string" && (/^\d$/.test(arg) || arg == "Ctrl-U")) return;
190
+ cm.removeKeyMap(prefixMap);
191
+ cm.state.emacsPrefixMap = false;
192
+ cm.off("keyHandled", maybeRemovePrefixMap);
193
+ cm.off("inputRead", maybeRemovePrefixMap);
194
+ }
195
+
196
+ // Utilities
197
+
198
+ function setMark(cm) {
199
+ cm.setCursor(cm.getCursor());
200
+ cm.setExtending(true);
201
+ cm.on("change", function() { cm.setExtending(false); });
202
+ }
203
+
204
+ function getInput(cm, msg, f) {
205
+ if (cm.openDialog)
206
+ cm.openDialog(msg + ": <input type=\"text\" style=\"width: 10em\"/>", f, {bottom: true});
207
+ else
208
+ f(prompt(msg, ""));
209
+ }
210
+
211
+ function operateOnWord(cm, op) {
212
+ var start = cm.getCursor(), end = cm.findPosH(start, 1, "word");
213
+ cm.replaceRange(op(cm.getRange(start, end)), start, end);
214
+ cm.setCursor(end);
215
+ }
216
+
217
+ function toEnclosingExpr(cm) {
218
+ var pos = cm.getCursor(), line = pos.line, ch = pos.ch;
219
+ var stack = [];
220
+ while (line >= cm.firstLine()) {
221
+ var text = cm.getLine(line);
222
+ for (var i = ch == null ? text.length : ch; i > 0;) {
223
+ var ch = text.charAt(--i);
224
+ if (ch == ")")
225
+ stack.push("(");
226
+ else if (ch == "]")
227
+ stack.push("[");
228
+ else if (ch == "}")
229
+ stack.push("{");
230
+ else if (/[\(\{\[]/.test(ch) && (!stack.length || stack.pop() != ch))
231
+ return cm.extendSelection(Pos(line, i));
232
+ }
233
+ --line; ch = null;
234
+ }
235
+ }
236
+
237
+ // Actual keymap
238
+
239
+ var keyMap = CodeMirror.keyMap.emacs = {
240
+ "Ctrl-W": function(cm) {kill(cm, cm.getCursor("start"), cm.getCursor("end"));},
241
+ "Ctrl-K": repeated(function(cm) {
242
+ var start = cm.getCursor(), end = cm.clipPos(Pos(start.line));
243
+ var text = cm.getRange(start, end);
244
+ if (!/\S/.test(text)) {
245
+ text += "\n";
246
+ end = Pos(start.line + 1, 0);
247
+ }
248
+ kill(cm, start, end, true, text);
249
+ }),
250
+ "Alt-W": function(cm) {
251
+ addToRing(cm.getSelection());
252
+ },
253
+ "Ctrl-Y": function(cm) {
254
+ var start = cm.getCursor();
255
+ cm.replaceRange(getFromRing(getPrefix(cm)), start, start, "paste");
256
+ cm.setSelection(start, cm.getCursor());
257
+ },
258
+ "Alt-Y": function(cm) {cm.replaceSelection(popFromRing());},
259
+
260
+ "Ctrl-Space": setMark, "Ctrl-Shift-2": setMark,
261
+
262
+ "Ctrl-F": move(byChar, 1), "Ctrl-B": move(byChar, -1),
263
+ "Right": move(byChar, 1), "Left": move(byChar, -1),
264
+ "Ctrl-D": function(cm) { killTo(cm, byChar, 1); },
265
+ "Delete": function(cm) { killTo(cm, byChar, 1); },
266
+ "Ctrl-H": function(cm) { killTo(cm, byChar, -1); },
267
+ "Backspace": function(cm) { killTo(cm, byChar, -1); },
268
+
269
+ "Alt-F": move(byWord, 1), "Alt-B": move(byWord, -1),
270
+ "Alt-D": function(cm) { killTo(cm, byWord, 1); },
271
+ "Alt-Backspace": function(cm) { killTo(cm, byWord, -1); },
272
+
273
+ "Ctrl-N": move(byLine, 1), "Ctrl-P": move(byLine, -1),
274
+ "Down": move(byLine, 1), "Up": move(byLine, -1),
275
+ "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
276
+ "End": "goLineEnd", "Home": "goLineStart",
277
+
278
+ "Alt-V": move(byPage, -1), "Ctrl-V": move(byPage, 1),
279
+ "PageUp": move(byPage, -1), "PageDown": move(byPage, 1),
280
+
281
+ "Ctrl-Up": move(byParagraph, -1), "Ctrl-Down": move(byParagraph, 1),
282
+
283
+ "Alt-A": move(bySentence, -1), "Alt-E": move(bySentence, 1),
284
+ "Alt-K": function(cm) { killTo(cm, bySentence, 1); },
285
+
286
+ "Ctrl-Alt-K": function(cm) { killTo(cm, byExpr, 1); },
287
+ "Ctrl-Alt-Backspace": function(cm) { killTo(cm, byExpr, -1); },
288
+ "Ctrl-Alt-F": move(byExpr, 1), "Ctrl-Alt-B": move(byExpr, -1),
289
+
290
+ "Shift-Ctrl-Alt-2": function(cm) {
291
+ cm.setSelection(findEnd(cm, byExpr, 1), cm.getCursor());
292
+ },
293
+ "Ctrl-Alt-T": function(cm) {
294
+ var leftStart = byExpr(cm, cm.getCursor(), -1), leftEnd = byExpr(cm, leftStart, 1);
295
+ var rightEnd = byExpr(cm, leftEnd, 1), rightStart = byExpr(cm, rightEnd, -1);
296
+ cm.replaceRange(cm.getRange(rightStart, rightEnd) + cm.getRange(leftEnd, rightStart) +
297
+ cm.getRange(leftStart, leftEnd), leftStart, rightEnd);
298
+ },
299
+ "Ctrl-Alt-U": repeated(toEnclosingExpr),
300
+
301
+ "Alt-Space": function(cm) {
302
+ var pos = cm.getCursor(), from = pos.ch, to = pos.ch, text = cm.getLine(pos.line);
303
+ while (from && /\s/.test(text.charAt(from - 1))) --from;
304
+ while (to < text.length && /\s/.test(text.charAt(to))) ++to;
305
+ cm.replaceRange(" ", Pos(pos.line, from), Pos(pos.line, to));
306
+ },
307
+ "Ctrl-O": repeated(function(cm) { cm.replaceSelection("\n", "start"); }),
308
+ "Ctrl-T": repeated(function(cm) {
309
+ var pos = cm.getCursor();
310
+ if (pos.ch < cm.getLine(pos.line).length) pos = Pos(pos.line, pos.ch + 1);
311
+ var from = cm.findPosH(pos, -2, "char");
312
+ var range = cm.getRange(from, pos);
313
+ if (range.length != 2) return;
314
+ cm.setSelection(from, pos);
315
+ cm.replaceSelection(range.charAt(1) + range.charAt(0), "end");
316
+ }),
317
+
318
+ "Alt-C": repeated(function(cm) {
319
+ operateOnWord(cm, function(w) {
320
+ var letter = w.search(/\w/);
321
+ if (letter == -1) return w;
322
+ return w.slice(0, letter) + w.charAt(letter).toUpperCase() + w.slice(letter + 1).toLowerCase();
323
+ });
324
+ }),
325
+ "Alt-U": repeated(function(cm) {
326
+ operateOnWord(cm, function(w) { return w.toUpperCase(); });
327
+ }),
328
+ "Alt-L": repeated(function(cm) {
329
+ operateOnWord(cm, function(w) { return w.toLowerCase(); });
330
+ }),
331
+
332
+ "Alt-;": "toggleComment",
333
+
334
+ "Ctrl-/": repeated("undo"), "Shift-Ctrl--": repeated("undo"),
335
+ "Ctrl-Z": repeated("undo"), "Cmd-Z": repeated("undo"),
336
+ "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd",
337
+ "Ctrl-S": "findNext", "Ctrl-R": "findPrev", "Ctrl-G": "clearSearch", "Shift-Alt-5": "replace",
338
+ "Alt-/": "autocomplete",
339
+ "Ctrl-J": "newlineAndIndent", "Enter": false, "Tab": "indentAuto",
340
+
341
+ "Alt-G": function(cm) {cm.setOption("keyMap", "emacs-Alt-G");},
342
+ "Ctrl-X": function(cm) {cm.setOption("keyMap", "emacs-Ctrl-X");},
343
+ "Ctrl-Q": function(cm) {cm.setOption("keyMap", "emacs-Ctrl-Q");},
344
+ "Ctrl-U": addPrefixMap
345
+ };
346
+
347
+ CodeMirror.keyMap["emacs-Ctrl-X"] = {
348
+ "Tab": function(cm) {
349
+ cm.indentSelection(getPrefix(cm, true) || cm.getOption("indentUnit"));
350
+ },
351
+ "Ctrl-X": function(cm) {
352
+ cm.setSelection(cm.getCursor("head"), cm.getCursor("anchor"));
353
+ },
354
+
355
+ "Ctrl-S": "save", "Ctrl-W": "save", "S": "saveAll", "F": "open", "U": repeated("undo"), "K": "close",
356
+ "Delete": function(cm) { kill(cm, cm.getCursor(), bySentence(cm, cm.getCursor(), 1), true); },
357
+ auto: "emacs", nofallthrough: true, disableInput: true
358
+ };
359
+
360
+ CodeMirror.keyMap["emacs-Alt-G"] = {
361
+ "G": function(cm) {
362
+ var prefix = getPrefix(cm, true);
363
+ if (prefix != null && prefix > 0) return cm.setCursor(prefix - 1);
364
+
365
+ getInput(cm, "Goto line", function(str) {
366
+ var num;
367
+ if (str && !isNaN(num = Number(str)) && num == num|0 && num > 0)
368
+ cm.setCursor(num - 1);
369
+ });
370
+ },
371
+ auto: "emacs", nofallthrough: true, disableInput: true
372
+ };
373
+
374
+ CodeMirror.keyMap["emacs-Ctrl-Q"] = {
375
+ "Tab": repeated("insertTab"),
376
+ auto: "emacs", nofallthrough: true
377
+ };
378
+
379
+ var prefixMap = {"Ctrl-G": clearPrefix};
380
+ function regPrefix(d) {
381
+ prefixMap[d] = function(cm) { addPrefix(cm, d); };
382
+ keyMap["Ctrl-" + d] = function(cm) { addPrefix(cm, d); };
383
+ prefixPreservingKeys["Ctrl-" + d] = true;
384
+ }
385
+ for (var i = 0; i < 10; ++i) regPrefix(String(i));
386
+ regPrefix("-");
387
+ })();
@@ -0,0 +1,43 @@
1
+ // A number of additional default bindings that are too obscure to
2
+ // include in the core codemirror.js file.
3
+
4
+ (function() {
5
+ "use strict";
6
+
7
+ var Pos = CodeMirror.Pos;
8
+
9
+ function moveLines(cm, start, end, dist) {
10
+ if (!dist || start > end) return 0;
11
+
12
+ var from = cm.clipPos(Pos(start, 0)), to = cm.clipPos(Pos(end));
13
+ var text = cm.getRange(from, to);
14
+
15
+ if (start <= cm.firstLine())
16
+ cm.replaceRange("", from, Pos(to.line + 1, 0));
17
+ else
18
+ cm.replaceRange("", Pos(from.line - 1), to);
19
+ var target = from.line + dist;
20
+ if (target <= cm.firstLine()) {
21
+ cm.replaceRange(text + "\n", Pos(target, 0));
22
+ return cm.firstLine() - from.line;
23
+ } else {
24
+ var targetPos = cm.clipPos(Pos(target - 1));
25
+ cm.replaceRange("\n" + text, targetPos);
26
+ return targetPos.line + 1 - from.line;
27
+ }
28
+ }
29
+
30
+ function moveSelectedLines(cm, dist) {
31
+ var head = cm.getCursor("head"), anchor = cm.getCursor("anchor");
32
+ cm.operation(function() {
33
+ var moved = moveLines(cm, Math.min(head.line, anchor.line), Math.max(head.line, anchor.line), dist);
34
+ cm.setSelection(Pos(anchor.line + moved, anchor.ch), Pos(head.line + moved, head.ch));
35
+ });
36
+ }
37
+
38
+ CodeMirror.commands.moveLinesUp = function(cm) { moveSelectedLines(cm, -1); };
39
+ CodeMirror.commands.moveLinesDown = function(cm) { moveSelectedLines(cm, 1); };
40
+
41
+ CodeMirror.keyMap["default"]["Alt-Up"] = "moveLinesUp";
42
+ CodeMirror.keyMap["default"]["Alt-Down"] = "moveLinesDown";
43
+ })();
@@ -0,0 +1,3704 @@
1
+ /**
2
+ * Supported keybindings:
3
+ *
4
+ * Motion:
5
+ * h, j, k, l
6
+ * gj, gk
7
+ * e, E, w, W, b, B, ge, gE
8
+ * f<character>, F<character>, t<character>, T<character>
9
+ * $, ^, 0, -, +, _
10
+ * gg, G
11
+ * %
12
+ * '<character>, `<character>
13
+ *
14
+ * Operator:
15
+ * d, y, c
16
+ * dd, yy, cc
17
+ * g~, g~g~
18
+ * >, <, >>, <<
19
+ *
20
+ * Operator-Motion:
21
+ * x, X, D, Y, C, ~
22
+ *
23
+ * Action:
24
+ * a, i, s, A, I, S, o, O
25
+ * zz, z., z<CR>, zt, zb, z-
26
+ * J
27
+ * u, Ctrl-r
28
+ * m<character>
29
+ * r<character>
30
+ *
31
+ * Modes:
32
+ * ESC - leave insert mode, visual mode, and clear input state.
33
+ * Ctrl-[, Ctrl-c - same as ESC.
34
+ *
35
+ * Registers: unamed, -, a-z, A-Z, 0-9
36
+ * (Does not respect the special case for number registers when delete
37
+ * operator is made with these commands: %, (, ), , /, ?, n, N, {, } )
38
+ * TODO: Implement the remaining registers.
39
+ * Marks: a-z, A-Z, and 0-9
40
+ * TODO: Implement the remaining special marks. They have more complex
41
+ * behavior.
42
+ *
43
+ * Events:
44
+ * 'vim-mode-change' - raised on the editor anytime the current mode changes,
45
+ * Event object: {mode: "visual", subMode: "linewise"}
46
+ *
47
+ * Code structure:
48
+ * 1. Default keymap
49
+ * 2. Variable declarations and short basic helpers
50
+ * 3. Instance (External API) implementation
51
+ * 4. Internal state tracking objects (input state, counter) implementation
52
+ * and instanstiation
53
+ * 5. Key handler (the main command dispatcher) implementation
54
+ * 6. Motion, operator, and action implementations
55
+ * 7. Helper functions for the key handler, motions, operators, and actions
56
+ * 8. Set up Vim to work as a keymap for CodeMirror.
57
+ */
58
+
59
+ (function() {
60
+ 'use strict';
61
+
62
+ var defaultKeymap = [
63
+ // Key to key mapping. This goes first to make it possible to override
64
+ // existing mappings.
65
+ { keys: ['<Left>'], type: 'keyToKey', toKeys: ['h'] },
66
+ { keys: ['<Right>'], type: 'keyToKey', toKeys: ['l'] },
67
+ { keys: ['<Up>'], type: 'keyToKey', toKeys: ['k'] },
68
+ { keys: ['<Down>'], type: 'keyToKey', toKeys: ['j'] },
69
+ { keys: ['<Space>'], type: 'keyToKey', toKeys: ['l'] },
70
+ { keys: ['<BS>'], type: 'keyToKey', toKeys: ['h'] },
71
+ { keys: ['<C-Space>'], type: 'keyToKey', toKeys: ['W'] },
72
+ { keys: ['<C-BS>'], type: 'keyToKey', toKeys: ['B'] },
73
+ { keys: ['<S-Space>'], type: 'keyToKey', toKeys: ['w'] },
74
+ { keys: ['<S-BS>'], type: 'keyToKey', toKeys: ['b'] },
75
+ { keys: ['<C-n>'], type: 'keyToKey', toKeys: ['j'] },
76
+ { keys: ['<C-p>'], type: 'keyToKey', toKeys: ['k'] },
77
+ { keys: ['C-['], type: 'keyToKey', toKeys: ['<Esc>'] },
78
+ { keys: ['<C-c>'], type: 'keyToKey', toKeys: ['<Esc>'] },
79
+ { keys: ['s'], type: 'keyToKey', toKeys: ['c', 'l'], context: 'normal' },
80
+ { keys: ['s'], type: 'keyToKey', toKeys: ['x', 'i'], context: 'visual'},
81
+ { keys: ['S'], type: 'keyToKey', toKeys: ['c', 'c'], context: 'normal' },
82
+ { keys: ['S'], type: 'keyToKey', toKeys: ['d', 'c', 'c'], context: 'visual' },
83
+ { keys: ['<Home>'], type: 'keyToKey', toKeys: ['0'] },
84
+ { keys: ['<End>'], type: 'keyToKey', toKeys: ['$'] },
85
+ { keys: ['<PageUp>'], type: 'keyToKey', toKeys: ['<C-b>'] },
86
+ { keys: ['<PageDown>'], type: 'keyToKey', toKeys: ['<C-f>'] },
87
+ // Motions
88
+ { keys: ['H'], type: 'motion',
89
+ motion: 'moveToTopLine',
90
+ motionArgs: { linewise: true, toJumplist: true }},
91
+ { keys: ['M'], type: 'motion',
92
+ motion: 'moveToMiddleLine',
93
+ motionArgs: { linewise: true, toJumplist: true }},
94
+ { keys: ['L'], type: 'motion',
95
+ motion: 'moveToBottomLine',
96
+ motionArgs: { linewise: true, toJumplist: true }},
97
+ { keys: ['h'], type: 'motion',
98
+ motion: 'moveByCharacters',
99
+ motionArgs: { forward: false }},
100
+ { keys: ['l'], type: 'motion',
101
+ motion: 'moveByCharacters',
102
+ motionArgs: { forward: true }},
103
+ { keys: ['j'], type: 'motion',
104
+ motion: 'moveByLines',
105
+ motionArgs: { forward: true, linewise: true }},
106
+ { keys: ['k'], type: 'motion',
107
+ motion: 'moveByLines',
108
+ motionArgs: { forward: false, linewise: true }},
109
+ { keys: ['g','j'], type: 'motion',
110
+ motion: 'moveByDisplayLines',
111
+ motionArgs: { forward: true }},
112
+ { keys: ['g','k'], type: 'motion',
113
+ motion: 'moveByDisplayLines',
114
+ motionArgs: { forward: false }},
115
+ { keys: ['w'], type: 'motion',
116
+ motion: 'moveByWords',
117
+ motionArgs: { forward: true, wordEnd: false }},
118
+ { keys: ['W'], type: 'motion',
119
+ motion: 'moveByWords',
120
+ motionArgs: { forward: true, wordEnd: false, bigWord: true }},
121
+ { keys: ['e'], type: 'motion',
122
+ motion: 'moveByWords',
123
+ motionArgs: { forward: true, wordEnd: true, inclusive: true }},
124
+ { keys: ['E'], type: 'motion',
125
+ motion: 'moveByWords',
126
+ motionArgs: { forward: true, wordEnd: true, bigWord: true,
127
+ inclusive: true }},
128
+ { keys: ['b'], type: 'motion',
129
+ motion: 'moveByWords',
130
+ motionArgs: { forward: false, wordEnd: false }},
131
+ { keys: ['B'], type: 'motion',
132
+ motion: 'moveByWords',
133
+ motionArgs: { forward: false, wordEnd: false, bigWord: true }},
134
+ { keys: ['g', 'e'], type: 'motion',
135
+ motion: 'moveByWords',
136
+ motionArgs: { forward: false, wordEnd: true, inclusive: true }},
137
+ { keys: ['g', 'E'], type: 'motion',
138
+ motion: 'moveByWords',
139
+ motionArgs: { forward: false, wordEnd: true, bigWord: true,
140
+ inclusive: true }},
141
+ { keys: ['{'], type: 'motion', motion: 'moveByParagraph',
142
+ motionArgs: { forward: false, toJumplist: true }},
143
+ { keys: ['}'], type: 'motion', motion: 'moveByParagraph',
144
+ motionArgs: { forward: true, toJumplist: true }},
145
+ { keys: ['<C-f>'], type: 'motion',
146
+ motion: 'moveByPage', motionArgs: { forward: true }},
147
+ { keys: ['<C-b>'], type: 'motion',
148
+ motion: 'moveByPage', motionArgs: { forward: false }},
149
+ { keys: ['<C-d>'], type: 'motion',
150
+ motion: 'moveByScroll',
151
+ motionArgs: { forward: true, explicitRepeat: true }},
152
+ { keys: ['<C-u>'], type: 'motion',
153
+ motion: 'moveByScroll',
154
+ motionArgs: { forward: false, explicitRepeat: true }},
155
+ { keys: ['g', 'g'], type: 'motion',
156
+ motion: 'moveToLineOrEdgeOfDocument',
157
+ motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }},
158
+ { keys: ['G'], type: 'motion',
159
+ motion: 'moveToLineOrEdgeOfDocument',
160
+ motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }},
161
+ { keys: ['0'], type: 'motion', motion: 'moveToStartOfLine' },
162
+ { keys: ['^'], type: 'motion',
163
+ motion: 'moveToFirstNonWhiteSpaceCharacter' },
164
+ { keys: ['+'], type: 'motion',
165
+ motion: 'moveByLines',
166
+ motionArgs: { forward: true, toFirstChar:true }},
167
+ { keys: ['-'], type: 'motion',
168
+ motion: 'moveByLines',
169
+ motionArgs: { forward: false, toFirstChar:true }},
170
+ { keys: ['_'], type: 'motion',
171
+ motion: 'moveByLines',
172
+ motionArgs: { forward: true, toFirstChar:true, repeatOffset:-1 }},
173
+ { keys: ['$'], type: 'motion',
174
+ motion: 'moveToEol',
175
+ motionArgs: { inclusive: true }},
176
+ { keys: ['%'], type: 'motion',
177
+ motion: 'moveToMatchedSymbol',
178
+ motionArgs: { inclusive: true, toJumplist: true }},
179
+ { keys: ['f', 'character'], type: 'motion',
180
+ motion: 'moveToCharacter',
181
+ motionArgs: { forward: true , inclusive: true }},
182
+ { keys: ['F', 'character'], type: 'motion',
183
+ motion: 'moveToCharacter',
184
+ motionArgs: { forward: false }},
185
+ { keys: ['t', 'character'], type: 'motion',
186
+ motion: 'moveTillCharacter',
187
+ motionArgs: { forward: true, inclusive: true }},
188
+ { keys: ['T', 'character'], type: 'motion',
189
+ motion: 'moveTillCharacter',
190
+ motionArgs: { forward: false }},
191
+ { keys: [';'], type: 'motion', motion: 'repeatLastCharacterSearch',
192
+ motionArgs: { forward: true }},
193
+ { keys: [','], type: 'motion', motion: 'repeatLastCharacterSearch',
194
+ motionArgs: { forward: false }},
195
+ { keys: ['\'', 'character'], type: 'motion', motion: 'goToMark',
196
+ motionArgs: {toJumplist: true}},
197
+ { keys: ['`', 'character'], type: 'motion', motion: 'goToMark',
198
+ motionArgs: {toJumplist: true}},
199
+ { keys: [']', '`'], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } },
200
+ { keys: ['[', '`'], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } },
201
+ { keys: [']', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } },
202
+ { keys: ['[', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } },
203
+ { keys: [']', 'character'], type: 'motion',
204
+ motion: 'moveToSymbol',
205
+ motionArgs: { forward: true, toJumplist: true}},
206
+ { keys: ['[', 'character'], type: 'motion',
207
+ motion: 'moveToSymbol',
208
+ motionArgs: { forward: false, toJumplist: true}},
209
+ { keys: ['|'], type: 'motion',
210
+ motion: 'moveToColumn',
211
+ motionArgs: { }},
212
+ // Operators
213
+ { keys: ['d'], type: 'operator', operator: 'delete' },
214
+ { keys: ['y'], type: 'operator', operator: 'yank' },
215
+ { keys: ['c'], type: 'operator', operator: 'change' },
216
+ { keys: ['>'], type: 'operator', operator: 'indent',
217
+ operatorArgs: { indentRight: true }},
218
+ { keys: ['<'], type: 'operator', operator: 'indent',
219
+ operatorArgs: { indentRight: false }},
220
+ { keys: ['g', '~'], type: 'operator', operator: 'swapcase' },
221
+ { keys: ['n'], type: 'motion', motion: 'findNext',
222
+ motionArgs: { forward: true, toJumplist: true }},
223
+ { keys: ['N'], type: 'motion', motion: 'findNext',
224
+ motionArgs: { forward: false, toJumplist: true }},
225
+ // Operator-Motion dual commands
226
+ { keys: ['x'], type: 'operatorMotion', operator: 'delete',
227
+ motion: 'moveByCharacters', motionArgs: { forward: true },
228
+ operatorMotionArgs: { visualLine: false }},
229
+ { keys: ['X'], type: 'operatorMotion', operator: 'delete',
230
+ motion: 'moveByCharacters', motionArgs: { forward: false },
231
+ operatorMotionArgs: { visualLine: true }},
232
+ { keys: ['D'], type: 'operatorMotion', operator: 'delete',
233
+ motion: 'moveToEol', motionArgs: { inclusive: true },
234
+ operatorMotionArgs: { visualLine: true }},
235
+ { keys: ['Y'], type: 'operatorMotion', operator: 'yank',
236
+ motion: 'moveToEol', motionArgs: { inclusive: true },
237
+ operatorMotionArgs: { visualLine: true }},
238
+ { keys: ['C'], type: 'operatorMotion',
239
+ operator: 'change',
240
+ motion: 'moveToEol', motionArgs: { inclusive: true },
241
+ operatorMotionArgs: { visualLine: true }},
242
+ { keys: ['~'], type: 'operatorMotion',
243
+ operator: 'swapcase', operatorArgs: { shouldMoveCursor: true },
244
+ motion: 'moveByCharacters', motionArgs: { forward: true }},
245
+ // Actions
246
+ { keys: ['<C-i>'], type: 'action', action: 'jumpListWalk',
247
+ actionArgs: { forward: true }},
248
+ { keys: ['<C-o>'], type: 'action', action: 'jumpListWalk',
249
+ actionArgs: { forward: false }},
250
+ { keys: ['a'], type: 'action', action: 'enterInsertMode', isEdit: true,
251
+ actionArgs: { insertAt: 'charAfter' }},
252
+ { keys: ['A'], type: 'action', action: 'enterInsertMode', isEdit: true,
253
+ actionArgs: { insertAt: 'eol' }},
254
+ { keys: ['i'], type: 'action', action: 'enterInsertMode', isEdit: true,
255
+ actionArgs: { insertAt: 'inplace' }},
256
+ { keys: ['I'], type: 'action', action: 'enterInsertMode', isEdit: true,
257
+ actionArgs: { insertAt: 'firstNonBlank' }},
258
+ { keys: ['o'], type: 'action', action: 'newLineAndEnterInsertMode',
259
+ isEdit: true, interlaceInsertRepeat: true,
260
+ actionArgs: { after: true }},
261
+ { keys: ['O'], type: 'action', action: 'newLineAndEnterInsertMode',
262
+ isEdit: true, interlaceInsertRepeat: true,
263
+ actionArgs: { after: false }},
264
+ { keys: ['v'], type: 'action', action: 'toggleVisualMode' },
265
+ { keys: ['V'], type: 'action', action: 'toggleVisualMode',
266
+ actionArgs: { linewise: true }},
267
+ { keys: ['J'], type: 'action', action: 'joinLines', isEdit: true },
268
+ { keys: ['p'], type: 'action', action: 'paste', isEdit: true,
269
+ actionArgs: { after: true, isEdit: true }},
270
+ { keys: ['P'], type: 'action', action: 'paste', isEdit: true,
271
+ actionArgs: { after: false, isEdit: true }},
272
+ { keys: ['r', 'character'], type: 'action', action: 'replace', isEdit: true },
273
+ { keys: ['@', 'character'], type: 'action', action: 'replayMacro' },
274
+ { keys: ['q', 'character'], type: 'action', action: 'enterMacroRecordMode' },
275
+ // Handle Replace-mode as a special case of insert mode.
276
+ { keys: ['R'], type: 'action', action: 'enterInsertMode', isEdit: true,
277
+ actionArgs: { replace: true }},
278
+ { keys: ['u'], type: 'action', action: 'undo' },
279
+ { keys: ['<C-r>'], type: 'action', action: 'redo' },
280
+ { keys: ['m', 'character'], type: 'action', action: 'setMark' },
281
+ { keys: ['"', 'character'], type: 'action', action: 'setRegister' },
282
+ { keys: ['z', 'z'], type: 'action', action: 'scrollToCursor',
283
+ actionArgs: { position: 'center' }},
284
+ { keys: ['z', '.'], type: 'action', action: 'scrollToCursor',
285
+ actionArgs: { position: 'center' },
286
+ motion: 'moveToFirstNonWhiteSpaceCharacter' },
287
+ { keys: ['z', 't'], type: 'action', action: 'scrollToCursor',
288
+ actionArgs: { position: 'top' }},
289
+ { keys: ['z', '<CR>'], type: 'action', action: 'scrollToCursor',
290
+ actionArgs: { position: 'top' },
291
+ motion: 'moveToFirstNonWhiteSpaceCharacter' },
292
+ { keys: ['z', '-'], type: 'action', action: 'scrollToCursor',
293
+ actionArgs: { position: 'bottom' }},
294
+ { keys: ['z', 'b'], type: 'action', action: 'scrollToCursor',
295
+ actionArgs: { position: 'bottom' },
296
+ motion: 'moveToFirstNonWhiteSpaceCharacter' },
297
+ { keys: ['.'], type: 'action', action: 'repeatLastEdit' },
298
+ { keys: ['<C-a>'], type: 'action', action: 'incrementNumberToken',
299
+ isEdit: true,
300
+ actionArgs: {increase: true, backtrack: false}},
301
+ { keys: ['<C-x>'], type: 'action', action: 'incrementNumberToken',
302
+ isEdit: true,
303
+ actionArgs: {increase: false, backtrack: false}},
304
+ // Text object motions
305
+ { keys: ['a', 'character'], type: 'motion',
306
+ motion: 'textObjectManipulation' },
307
+ { keys: ['i', 'character'], type: 'motion',
308
+ motion: 'textObjectManipulation',
309
+ motionArgs: { textObjectInner: true }},
310
+ // Search
311
+ { keys: ['/'], type: 'search',
312
+ searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }},
313
+ { keys: ['?'], type: 'search',
314
+ searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }},
315
+ { keys: ['*'], type: 'search',
316
+ searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }},
317
+ { keys: ['#'], type: 'search',
318
+ searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }},
319
+ // Ex command
320
+ { keys: [':'], type: 'ex' }
321
+ ];
322
+
323
+ var Vim = function() {
324
+ CodeMirror.defineOption('vimMode', false, function(cm, val) {
325
+ if (val) {
326
+ cm.setOption('keyMap', 'vim');
327
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
328
+ cm.on('beforeSelectionChange', beforeSelectionChange);
329
+ maybeInitVimState(cm);
330
+ } else if (cm.state.vim) {
331
+ cm.setOption('keyMap', 'default');
332
+ cm.off('beforeSelectionChange', beforeSelectionChange);
333
+ cm.state.vim = null;
334
+ }
335
+ });
336
+ function beforeSelectionChange(cm, cur) {
337
+ var vim = cm.state.vim;
338
+ if (vim.insertMode || vim.exMode) return;
339
+
340
+ var head = cur.head;
341
+ if (head.ch && head.ch == cm.doc.getLine(head.line).length) {
342
+ head.ch--;
343
+ }
344
+ }
345
+
346
+ var numberRegex = /[\d]/;
347
+ var wordRegexp = [(/\w/), (/[^\w\s]/)], bigWordRegexp = [(/\S/)];
348
+ function makeKeyRange(start, size) {
349
+ var keys = [];
350
+ for (var i = start; i < start + size; i++) {
351
+ keys.push(String.fromCharCode(i));
352
+ }
353
+ return keys;
354
+ }
355
+ var upperCaseAlphabet = makeKeyRange(65, 26);
356
+ var lowerCaseAlphabet = makeKeyRange(97, 26);
357
+ var numbers = makeKeyRange(48, 10);
358
+ var specialSymbols = '~`!@#$%^&*()_-+=[{}]\\|/?.,<>:;"\''.split('');
359
+ var specialKeys = ['Left', 'Right', 'Up', 'Down', 'Space', 'Backspace',
360
+ 'Esc', 'Home', 'End', 'PageUp', 'PageDown', 'Enter'];
361
+ var validMarks = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['<', '>']);
362
+ var validRegisters = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['-', '"']);
363
+
364
+ function isLine(cm, line) {
365
+ return line >= cm.firstLine() && line <= cm.lastLine();
366
+ }
367
+ function isLowerCase(k) {
368
+ return (/^[a-z]$/).test(k);
369
+ }
370
+ function isMatchableSymbol(k) {
371
+ return '()[]{}'.indexOf(k) != -1;
372
+ }
373
+ function isNumber(k) {
374
+ return numberRegex.test(k);
375
+ }
376
+ function isUpperCase(k) {
377
+ return (/^[A-Z]$/).test(k);
378
+ }
379
+ function isWhiteSpaceString(k) {
380
+ return (/^\s*$/).test(k);
381
+ }
382
+ function inArray(val, arr) {
383
+ for (var i = 0; i < arr.length; i++) {
384
+ if (arr[i] == val) {
385
+ return true;
386
+ }
387
+ }
388
+ return false;
389
+ }
390
+
391
+ var createCircularJumpList = function() {
392
+ var size = 100;
393
+ var pointer = -1;
394
+ var head = 0;
395
+ var tail = 0;
396
+ var buffer = new Array(size);
397
+ function add(cm, oldCur, newCur) {
398
+ var current = pointer % size;
399
+ var curMark = buffer[current];
400
+ function useNextSlot(cursor) {
401
+ var next = ++pointer % size;
402
+ var trashMark = buffer[next];
403
+ if (trashMark) {
404
+ trashMark.clear();
405
+ }
406
+ buffer[next] = cm.setBookmark(cursor);
407
+ }
408
+ if (curMark) {
409
+ var markPos = curMark.find();
410
+ // avoid recording redundant cursor position
411
+ if (markPos && !cursorEqual(markPos, oldCur)) {
412
+ useNextSlot(oldCur);
413
+ }
414
+ } else {
415
+ useNextSlot(oldCur);
416
+ }
417
+ useNextSlot(newCur);
418
+ head = pointer;
419
+ tail = pointer - size + 1;
420
+ if (tail < 0) {
421
+ tail = 0;
422
+ }
423
+ }
424
+ function move(cm, offset) {
425
+ pointer += offset;
426
+ if (pointer > head) {
427
+ pointer = head;
428
+ } else if (pointer < tail) {
429
+ pointer = tail;
430
+ }
431
+ var mark = buffer[(size + pointer) % size];
432
+ // skip marks that are temporarily removed from text buffer
433
+ if (mark && !mark.find()) {
434
+ var inc = offset > 0 ? 1 : -1;
435
+ var newCur;
436
+ var oldCur = cm.getCursor();
437
+ do {
438
+ pointer += inc;
439
+ mark = buffer[(size + pointer) % size];
440
+ // skip marks that are the same as current position
441
+ if (mark &&
442
+ (newCur = mark.find()) &&
443
+ !cursorEqual(oldCur, newCur)) {
444
+ break;
445
+ }
446
+ } while (pointer < head && pointer > tail);
447
+ }
448
+ return mark;
449
+ }
450
+ return {
451
+ cachedCursor: undefined, //used for # and * jumps
452
+ add: add,
453
+ move: move
454
+ };
455
+ };
456
+
457
+ var createMacroState = function() {
458
+ return {
459
+ macroKeyBuffer: [],
460
+ latestRegister: undefined,
461
+ inReplay: false,
462
+ lastInsertModeChanges: {
463
+ changes: [], // Change list
464
+ expectCursorActivityForChange: false // Set to true on change, false on cursorActivity.
465
+ },
466
+ enteredMacroMode: undefined,
467
+ isMacroPlaying: false,
468
+ toggle: function(cm, registerName) {
469
+ if (this.enteredMacroMode) { //onExit
470
+ this.enteredMacroMode(); // close dialog
471
+ this.enteredMacroMode = undefined;
472
+ } else { //onEnter
473
+ this.latestRegister = registerName;
474
+ this.enteredMacroMode = cm.openDialog(
475
+ '(recording)['+registerName+']', null, {bottom:true});
476
+ }
477
+ }
478
+ };
479
+ };
480
+
481
+
482
+ function maybeInitVimState(cm) {
483
+ if (!cm.state.vim) {
484
+ // Store instance state in the CodeMirror object.
485
+ cm.state.vim = {
486
+ inputState: new InputState(),
487
+ // Vim's input state that triggered the last edit, used to repeat
488
+ // motions and operators with '.'.
489
+ lastEditInputState: undefined,
490
+ // Vim's action command before the last edit, used to repeat actions
491
+ // with '.' and insert mode repeat.
492
+ lastEditActionCommand: undefined,
493
+ // When using jk for navigation, if you move from a longer line to a
494
+ // shorter line, the cursor may clip to the end of the shorter line.
495
+ // If j is pressed again and cursor goes to the next line, the
496
+ // cursor should go back to its horizontal position on the longer
497
+ // line if it can. This is to keep track of the horizontal position.
498
+ lastHPos: -1,
499
+ // Doing the same with screen-position for gj/gk
500
+ lastHSPos: -1,
501
+ // The last motion command run. Cleared if a non-motion command gets
502
+ // executed in between.
503
+ lastMotion: null,
504
+ marks: {},
505
+ insertMode: false,
506
+ // Repeat count for changes made in insert mode, triggered by key
507
+ // sequences like 3,i. Only exists when insertMode is true.
508
+ insertModeRepeat: undefined,
509
+ visualMode: false,
510
+ // If we are in visual line mode. No effect if visualMode is false.
511
+ visualLine: false
512
+ };
513
+ }
514
+ return cm.state.vim;
515
+ }
516
+ var vimGlobalState;
517
+ function resetVimGlobalState() {
518
+ vimGlobalState = {
519
+ // The current search query.
520
+ searchQuery: null,
521
+ // Whether we are searching backwards.
522
+ searchIsReversed: false,
523
+ jumpList: createCircularJumpList(),
524
+ macroModeState: createMacroState(),
525
+ // Recording latest f, t, F or T motion command.
526
+ lastChararacterSearch: {increment:0, forward:true, selectedCharacter:''},
527
+ registerController: new RegisterController({})
528
+ };
529
+ }
530
+
531
+ var vimApi= {
532
+ buildKeyMap: function() {
533
+ // TODO: Convert keymap into dictionary format for fast lookup.
534
+ },
535
+ // Testing hook, though it might be useful to expose the register
536
+ // controller anyways.
537
+ getRegisterController: function() {
538
+ return vimGlobalState.registerController;
539
+ },
540
+ // Testing hook.
541
+ resetVimGlobalState_: resetVimGlobalState,
542
+
543
+ // Testing hook.
544
+ getVimGlobalState_: function() {
545
+ return vimGlobalState;
546
+ },
547
+
548
+ // Testing hook.
549
+ maybeInitVimState_: maybeInitVimState,
550
+
551
+ InsertModeKey: InsertModeKey,
552
+ map: function(lhs, rhs) {
553
+ // Add user defined key bindings.
554
+ exCommandDispatcher.map(lhs, rhs);
555
+ },
556
+ defineEx: function(name, prefix, func){
557
+ if (name.indexOf(prefix) !== 0) {
558
+ throw new Error('(Vim.defineEx) "'+prefix+'" is not a prefix of "'+name+'", command not registered');
559
+ }
560
+ exCommands[name]=func;
561
+ exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'};
562
+ },
563
+ // This is the outermost function called by CodeMirror, after keys have
564
+ // been mapped to their Vim equivalents.
565
+ handleKey: function(cm, key) {
566
+ var command;
567
+ var vim = maybeInitVimState(cm);
568
+ var macroModeState = vimGlobalState.macroModeState;
569
+ if (macroModeState.enteredMacroMode) {
570
+ if (key == 'q') {
571
+ actions.exitMacroRecordMode();
572
+ vim.inputState = new InputState();
573
+ return;
574
+ }
575
+ }
576
+ if (key == '<Esc>') {
577
+ // Clear input state and get back to normal mode.
578
+ vim.inputState = new InputState();
579
+ if (vim.visualMode) {
580
+ exitVisualMode(cm);
581
+ }
582
+ return;
583
+ }
584
+ // Enter visual mode when the mouse selects text.
585
+ if (!vim.visualMode &&
586
+ !cursorEqual(cm.getCursor('head'), cm.getCursor('anchor'))) {
587
+ vim.visualMode = true;
588
+ vim.visualLine = false;
589
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"});
590
+ cm.on('mousedown', exitVisualMode);
591
+ }
592
+ if (key != '0' || (key == '0' && vim.inputState.getRepeat() === 0)) {
593
+ // Have to special case 0 since it's both a motion and a number.
594
+ command = commandDispatcher.matchCommand(key, defaultKeymap, vim);
595
+ }
596
+ if (!command) {
597
+ if (isNumber(key)) {
598
+ // Increment count unless count is 0 and key is 0.
599
+ vim.inputState.pushRepeatDigit(key);
600
+ }
601
+ return;
602
+ }
603
+ if (command.type == 'keyToKey') {
604
+ // TODO: prevent infinite recursion.
605
+ for (var i = 0; i < command.toKeys.length; i++) {
606
+ this.handleKey(cm, command.toKeys[i]);
607
+ }
608
+ } else {
609
+ if (macroModeState.enteredMacroMode) {
610
+ logKey(macroModeState, key);
611
+ }
612
+ commandDispatcher.processCommand(cm, vim, command);
613
+ }
614
+ },
615
+ handleEx: function(cm, input) {
616
+ exCommandDispatcher.processCommand(cm, input);
617
+ }
618
+ };
619
+
620
+ // Represents the current input state.
621
+ function InputState() {
622
+ this.prefixRepeat = [];
623
+ this.motionRepeat = [];
624
+
625
+ this.operator = null;
626
+ this.operatorArgs = null;
627
+ this.motion = null;
628
+ this.motionArgs = null;
629
+ this.keyBuffer = []; // For matching multi-key commands.
630
+ this.registerName = null; // Defaults to the unamed register.
631
+ }
632
+ InputState.prototype.pushRepeatDigit = function(n) {
633
+ if (!this.operator) {
634
+ this.prefixRepeat = this.prefixRepeat.concat(n);
635
+ } else {
636
+ this.motionRepeat = this.motionRepeat.concat(n);
637
+ }
638
+ };
639
+ InputState.prototype.getRepeat = function() {
640
+ var repeat = 0;
641
+ if (this.prefixRepeat.length > 0 || this.motionRepeat.length > 0) {
642
+ repeat = 1;
643
+ if (this.prefixRepeat.length > 0) {
644
+ repeat *= parseInt(this.prefixRepeat.join(''), 10);
645
+ }
646
+ if (this.motionRepeat.length > 0) {
647
+ repeat *= parseInt(this.motionRepeat.join(''), 10);
648
+ }
649
+ }
650
+ return repeat;
651
+ };
652
+
653
+ /*
654
+ * Register stores information about copy and paste registers. Besides
655
+ * text, a register must store whether it is linewise (i.e., when it is
656
+ * pasted, should it insert itself into a new line, or should the text be
657
+ * inserted at the cursor position.)
658
+ */
659
+ function Register(text, linewise) {
660
+ this.clear();
661
+ if (text) {
662
+ this.set(text, linewise);
663
+ }
664
+ }
665
+ Register.prototype = {
666
+ set: function(text, linewise) {
667
+ this.text = text;
668
+ this.linewise = !!linewise;
669
+ },
670
+ append: function(text, linewise) {
671
+ // if this register has ever been set to linewise, use linewise.
672
+ if (linewise || this.linewise) {
673
+ this.text += '\n' + text;
674
+ this.linewise = true;
675
+ } else {
676
+ this.text += text;
677
+ }
678
+ },
679
+ clear: function() {
680
+ this.text = '';
681
+ this.linewise = false;
682
+ },
683
+ toString: function() { return this.text; }
684
+ };
685
+
686
+ /*
687
+ * vim registers allow you to keep many independent copy and paste buffers.
688
+ * See http://usevim.com/2012/04/13/registers/ for an introduction.
689
+ *
690
+ * RegisterController keeps the state of all the registers. An initial
691
+ * state may be passed in. The unnamed register '"' will always be
692
+ * overridden.
693
+ */
694
+ function RegisterController(registers) {
695
+ this.registers = registers;
696
+ this.unamedRegister = registers['"'] = new Register();
697
+ }
698
+ RegisterController.prototype = {
699
+ pushText: function(registerName, operator, text, linewise) {
700
+ if (linewise && text.charAt(0) == '\n') {
701
+ text = text.slice(1) + '\n';
702
+ }
703
+ if(linewise && text.charAt(text.length - 1) !== '\n'){
704
+ text += '\n';
705
+ }
706
+ // Lowercase and uppercase registers refer to the same register.
707
+ // Uppercase just means append.
708
+ var register = this.isValidRegister(registerName) ?
709
+ this.getRegister(registerName) : null;
710
+ // if no register/an invalid register was specified, things go to the
711
+ // default registers
712
+ if (!register) {
713
+ switch (operator) {
714
+ case 'yank':
715
+ // The 0 register contains the text from the most recent yank.
716
+ this.registers['0'] = new Register(text, linewise);
717
+ break;
718
+ case 'delete':
719
+ case 'change':
720
+ if (text.indexOf('\n') == -1) {
721
+ // Delete less than 1 line. Update the small delete register.
722
+ this.registers['-'] = new Register(text, linewise);
723
+ } else {
724
+ // Shift down the contents of the numbered registers and put the
725
+ // deleted text into register 1.
726
+ this.shiftNumericRegisters_();
727
+ this.registers['1'] = new Register(text, linewise);
728
+ }
729
+ break;
730
+ }
731
+ // Make sure the unnamed register is set to what just happened
732
+ this.unamedRegister.set(text, linewise);
733
+ return;
734
+ }
735
+
736
+ // If we've gotten to this point, we've actually specified a register
737
+ var append = isUpperCase(registerName);
738
+ if (append) {
739
+ register.append(text, linewise);
740
+ // The unamed register always has the same value as the last used
741
+ // register.
742
+ this.unamedRegister.append(text, linewise);
743
+ } else {
744
+ register.set(text, linewise);
745
+ this.unamedRegister.set(text, linewise);
746
+ }
747
+ },
748
+ setRegisterText: function(name, text, linewise) {
749
+ this.getRegister(name).set(text, linewise);
750
+ },
751
+ // Gets the register named @name. If one of @name doesn't already exist,
752
+ // create it. If @name is invalid, return the unamedRegister.
753
+ getRegister: function(name) {
754
+ if (!this.isValidRegister(name)) {
755
+ return this.unamedRegister;
756
+ }
757
+ name = name.toLowerCase();
758
+ if (!this.registers[name]) {
759
+ this.registers[name] = new Register();
760
+ }
761
+ return this.registers[name];
762
+ },
763
+ isValidRegister: function(name) {
764
+ return name && inArray(name, validRegisters);
765
+ },
766
+ shiftNumericRegisters_: function() {
767
+ for (var i = 9; i >= 2; i--) {
768
+ this.registers[i] = this.getRegister('' + (i - 1));
769
+ }
770
+ }
771
+ };
772
+
773
+ var commandDispatcher = {
774
+ matchCommand: function(key, keyMap, vim) {
775
+ var inputState = vim.inputState;
776
+ var keys = inputState.keyBuffer.concat(key);
777
+ var matchedCommands = [];
778
+ var selectedCharacter;
779
+ for (var i = 0; i < keyMap.length; i++) {
780
+ var command = keyMap[i];
781
+ if (matchKeysPartial(keys, command.keys)) {
782
+ if (inputState.operator && command.type == 'action') {
783
+ // Ignore matched action commands after an operator. Operators
784
+ // only operate on motions. This check is really for text
785
+ // objects since aW, a[ etcs conflicts with a.
786
+ continue;
787
+ }
788
+ // Match commands that take <character> as an argument.
789
+ if (command.keys[keys.length - 1] == 'character') {
790
+ selectedCharacter = keys[keys.length - 1];
791
+ if(selectedCharacter.length>1){
792
+ switch(selectedCharacter){
793
+ case '<CR>':
794
+ selectedCharacter='\n';
795
+ break;
796
+ case '<Space>':
797
+ selectedCharacter=' ';
798
+ break;
799
+ default:
800
+ continue;
801
+ }
802
+ }
803
+ }
804
+ // Add the command to the list of matched commands. Choose the best
805
+ // command later.
806
+ matchedCommands.push(command);
807
+ }
808
+ }
809
+
810
+ // Returns the command if it is a full match, or null if not.
811
+ function getFullyMatchedCommandOrNull(command) {
812
+ if (keys.length < command.keys.length) {
813
+ // Matches part of a multi-key command. Buffer and wait for next
814
+ // stroke.
815
+ inputState.keyBuffer.push(key);
816
+ return null;
817
+ } else {
818
+ if (command.keys[keys.length - 1] == 'character') {
819
+ inputState.selectedCharacter = selectedCharacter;
820
+ }
821
+ // Clear the buffer since a full match was found.
822
+ inputState.keyBuffer = [];
823
+ return command;
824
+ }
825
+ }
826
+
827
+ if (!matchedCommands.length) {
828
+ // Clear the buffer since there were no matches.
829
+ inputState.keyBuffer = [];
830
+ return null;
831
+ } else if (matchedCommands.length == 1) {
832
+ return getFullyMatchedCommandOrNull(matchedCommands[0]);
833
+ } else {
834
+ // Find the best match in the list of matchedCommands.
835
+ var context = vim.visualMode ? 'visual' : 'normal';
836
+ var bestMatch = matchedCommands[0]; // Default to first in the list.
837
+ for (var i = 0; i < matchedCommands.length; i++) {
838
+ if (matchedCommands[i].context == context) {
839
+ bestMatch = matchedCommands[i];
840
+ break;
841
+ }
842
+ }
843
+ return getFullyMatchedCommandOrNull(bestMatch);
844
+ }
845
+ },
846
+ processCommand: function(cm, vim, command) {
847
+ vim.inputState.repeatOverride = command.repeatOverride;
848
+ switch (command.type) {
849
+ case 'motion':
850
+ this.processMotion(cm, vim, command);
851
+ break;
852
+ case 'operator':
853
+ this.processOperator(cm, vim, command);
854
+ break;
855
+ case 'operatorMotion':
856
+ this.processOperatorMotion(cm, vim, command);
857
+ break;
858
+ case 'action':
859
+ this.processAction(cm, vim, command);
860
+ break;
861
+ case 'search':
862
+ this.processSearch(cm, vim, command);
863
+ break;
864
+ case 'ex':
865
+ case 'keyToEx':
866
+ this.processEx(cm, vim, command);
867
+ break;
868
+ default:
869
+ break;
870
+ }
871
+ },
872
+ processMotion: function(cm, vim, command) {
873
+ vim.inputState.motion = command.motion;
874
+ vim.inputState.motionArgs = copyArgs(command.motionArgs);
875
+ this.evalInput(cm, vim);
876
+ },
877
+ processOperator: function(cm, vim, command) {
878
+ var inputState = vim.inputState;
879
+ if (inputState.operator) {
880
+ if (inputState.operator == command.operator) {
881
+ // Typing an operator twice like 'dd' makes the operator operate
882
+ // linewise
883
+ inputState.motion = 'expandToLine';
884
+ inputState.motionArgs = { linewise: true };
885
+ this.evalInput(cm, vim);
886
+ return;
887
+ } else {
888
+ // 2 different operators in a row doesn't make sense.
889
+ vim.inputState = new InputState();
890
+ }
891
+ }
892
+ inputState.operator = command.operator;
893
+ inputState.operatorArgs = copyArgs(command.operatorArgs);
894
+ if (vim.visualMode) {
895
+ // Operating on a selection in visual mode. We don't need a motion.
896
+ this.evalInput(cm, vim);
897
+ }
898
+ },
899
+ processOperatorMotion: function(cm, vim, command) {
900
+ var visualMode = vim.visualMode;
901
+ var operatorMotionArgs = copyArgs(command.operatorMotionArgs);
902
+ if (operatorMotionArgs) {
903
+ // Operator motions may have special behavior in visual mode.
904
+ if (visualMode && operatorMotionArgs.visualLine) {
905
+ vim.visualLine = true;
906
+ }
907
+ }
908
+ this.processOperator(cm, vim, command);
909
+ if (!visualMode) {
910
+ this.processMotion(cm, vim, command);
911
+ }
912
+ },
913
+ processAction: function(cm, vim, command) {
914
+ var inputState = vim.inputState;
915
+ var repeat = inputState.getRepeat();
916
+ var repeatIsExplicit = !!repeat;
917
+ var actionArgs = copyArgs(command.actionArgs) || {};
918
+ if (inputState.selectedCharacter) {
919
+ actionArgs.selectedCharacter = inputState.selectedCharacter;
920
+ }
921
+ // Actions may or may not have motions and operators. Do these first.
922
+ if (command.operator) {
923
+ this.processOperator(cm, vim, command);
924
+ }
925
+ if (command.motion) {
926
+ this.processMotion(cm, vim, command);
927
+ }
928
+ if (command.motion || command.operator) {
929
+ this.evalInput(cm, vim);
930
+ }
931
+ actionArgs.repeat = repeat || 1;
932
+ actionArgs.repeatIsExplicit = repeatIsExplicit;
933
+ actionArgs.registerName = inputState.registerName;
934
+ vim.inputState = new InputState();
935
+ vim.lastMotion = null;
936
+ if (command.isEdit) {
937
+ this.recordLastEdit(vim, inputState, command);
938
+ }
939
+ actions[command.action](cm, actionArgs, vim);
940
+ },
941
+ processSearch: function(cm, vim, command) {
942
+ if (!cm.getSearchCursor) {
943
+ // Search depends on SearchCursor.
944
+ return;
945
+ }
946
+ var forward = command.searchArgs.forward;
947
+ getSearchState(cm).setReversed(!forward);
948
+ var promptPrefix = (forward) ? '/' : '?';
949
+ var originalQuery = getSearchState(cm).getQuery();
950
+ var originalScrollPos = cm.getScrollInfo();
951
+ function handleQuery(query, ignoreCase, smartCase) {
952
+ try {
953
+ updateSearchQuery(cm, query, ignoreCase, smartCase);
954
+ } catch (e) {
955
+ showConfirm(cm, 'Invalid regex: ' + query);
956
+ return;
957
+ }
958
+ commandDispatcher.processMotion(cm, vim, {
959
+ type: 'motion',
960
+ motion: 'findNext',
961
+ motionArgs: { forward: true, toJumplist: command.searchArgs.toJumplist }
962
+ });
963
+ }
964
+ function onPromptClose(query) {
965
+ cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
966
+ handleQuery(query, true /** ignoreCase */, true /** smartCase */);
967
+ }
968
+ function onPromptKeyUp(_e, query) {
969
+ var parsedQuery;
970
+ try {
971
+ parsedQuery = updateSearchQuery(cm, query,
972
+ true /** ignoreCase */, true /** smartCase */);
973
+ } catch (e) {
974
+ // Swallow bad regexes for incremental search.
975
+ }
976
+ if (parsedQuery) {
977
+ cm.scrollIntoView(findNext(cm, !forward, parsedQuery), 30);
978
+ } else {
979
+ clearSearchHighlight(cm);
980
+ cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
981
+ }
982
+ }
983
+ function onPromptKeyDown(e, _query, close) {
984
+ var keyName = CodeMirror.keyName(e);
985
+ if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') {
986
+ updateSearchQuery(cm, originalQuery);
987
+ clearSearchHighlight(cm);
988
+ cm.scrollTo(originalScrollPos.left, originalScrollPos.top);
989
+
990
+ CodeMirror.e_stop(e);
991
+ close();
992
+ cm.focus();
993
+ }
994
+ }
995
+ switch (command.searchArgs.querySrc) {
996
+ case 'prompt':
997
+ showPrompt(cm, {
998
+ onClose: onPromptClose,
999
+ prefix: promptPrefix,
1000
+ desc: searchPromptDesc,
1001
+ onKeyUp: onPromptKeyUp,
1002
+ onKeyDown: onPromptKeyDown
1003
+ });
1004
+ break;
1005
+ case 'wordUnderCursor':
1006
+ var word = expandWordUnderCursor(cm, false /** inclusive */,
1007
+ true /** forward */, false /** bigWord */,
1008
+ true /** noSymbol */);
1009
+ var isKeyword = true;
1010
+ if (!word) {
1011
+ word = expandWordUnderCursor(cm, false /** inclusive */,
1012
+ true /** forward */, false /** bigWord */,
1013
+ false /** noSymbol */);
1014
+ isKeyword = false;
1015
+ }
1016
+ if (!word) {
1017
+ return;
1018
+ }
1019
+ var query = cm.getLine(word.start.line).substring(word.start.ch,
1020
+ word.end.ch);
1021
+ if (isKeyword) {
1022
+ query = '\\b' + query + '\\b';
1023
+ } else {
1024
+ query = escapeRegex(query);
1025
+ }
1026
+
1027
+ // cachedCursor is used to save the old position of the cursor
1028
+ // when * or # causes vim to seek for the nearest word and shift
1029
+ // the cursor before entering the motion.
1030
+ vimGlobalState.jumpList.cachedCursor = cm.getCursor();
1031
+ cm.setCursor(word.start);
1032
+
1033
+ handleQuery(query, true /** ignoreCase */, false /** smartCase */);
1034
+ break;
1035
+ }
1036
+ },
1037
+ processEx: function(cm, vim, command) {
1038
+ function onPromptClose(input) {
1039
+ // Give the prompt some time to close so that if processCommand shows
1040
+ // an error, the elements don't overlap.
1041
+ exCommandDispatcher.processCommand(cm, input);
1042
+ }
1043
+ function onPromptKeyDown(e, _input, close) {
1044
+ var keyName = CodeMirror.keyName(e);
1045
+ if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') {
1046
+ CodeMirror.e_stop(e);
1047
+ close();
1048
+ cm.focus();
1049
+ }
1050
+ }
1051
+ if (command.type == 'keyToEx') {
1052
+ // Handle user defined Ex to Ex mappings
1053
+ exCommandDispatcher.processCommand(cm, command.exArgs.input);
1054
+ } else {
1055
+ if (vim.visualMode) {
1056
+ showPrompt(cm, { onClose: onPromptClose, prefix: ':', value: '\'<,\'>',
1057
+ onKeyDown: onPromptKeyDown});
1058
+ } else {
1059
+ showPrompt(cm, { onClose: onPromptClose, prefix: ':',
1060
+ onKeyDown: onPromptKeyDown});
1061
+ }
1062
+ }
1063
+ },
1064
+ evalInput: function(cm, vim) {
1065
+ // If the motion comand is set, execute both the operator and motion.
1066
+ // Otherwise return.
1067
+ var inputState = vim.inputState;
1068
+ var motion = inputState.motion;
1069
+ var motionArgs = inputState.motionArgs || {};
1070
+ var operator = inputState.operator;
1071
+ var operatorArgs = inputState.operatorArgs || {};
1072
+ var registerName = inputState.registerName;
1073
+ var selectionEnd = cm.getCursor('head');
1074
+ var selectionStart = cm.getCursor('anchor');
1075
+ // The difference between cur and selection cursors are that cur is
1076
+ // being operated on and ignores that there is a selection.
1077
+ var curStart = copyCursor(selectionEnd);
1078
+ var curOriginal = copyCursor(curStart);
1079
+ var curEnd;
1080
+ var repeat;
1081
+ if (operator) {
1082
+ this.recordLastEdit(vim, inputState);
1083
+ }
1084
+ if (inputState.repeatOverride !== undefined) {
1085
+ // If repeatOverride is specified, that takes precedence over the
1086
+ // input state's repeat. Used by Ex mode and can be user defined.
1087
+ repeat = inputState.repeatOverride;
1088
+ } else {
1089
+ repeat = inputState.getRepeat();
1090
+ }
1091
+ if (repeat > 0 && motionArgs.explicitRepeat) {
1092
+ motionArgs.repeatIsExplicit = true;
1093
+ } else if (motionArgs.noRepeat ||
1094
+ (!motionArgs.explicitRepeat && repeat === 0)) {
1095
+ repeat = 1;
1096
+ motionArgs.repeatIsExplicit = false;
1097
+ }
1098
+ if (inputState.selectedCharacter) {
1099
+ // If there is a character input, stick it in all of the arg arrays.
1100
+ motionArgs.selectedCharacter = operatorArgs.selectedCharacter =
1101
+ inputState.selectedCharacter;
1102
+ }
1103
+ motionArgs.repeat = repeat;
1104
+ vim.inputState = new InputState();
1105
+ if (motion) {
1106
+ var motionResult = motions[motion](cm, motionArgs, vim);
1107
+ vim.lastMotion = motions[motion];
1108
+ if (!motionResult) {
1109
+ return;
1110
+ }
1111
+ if (motionArgs.toJumplist) {
1112
+ var jumpList = vimGlobalState.jumpList;
1113
+ // if the current motion is # or *, use cachedCursor
1114
+ var cachedCursor = jumpList.cachedCursor;
1115
+ if (cachedCursor) {
1116
+ recordJumpPosition(cm, cachedCursor, motionResult);
1117
+ delete jumpList.cachedCursor;
1118
+ } else {
1119
+ recordJumpPosition(cm, curOriginal, motionResult);
1120
+ }
1121
+ }
1122
+ if (motionResult instanceof Array) {
1123
+ curStart = motionResult[0];
1124
+ curEnd = motionResult[1];
1125
+ } else {
1126
+ curEnd = motionResult;
1127
+ }
1128
+ // TODO: Handle null returns from motion commands better.
1129
+ if (!curEnd) {
1130
+ curEnd = { ch: curStart.ch, line: curStart.line };
1131
+ }
1132
+ if (vim.visualMode) {
1133
+ // Check if the selection crossed over itself. Will need to shift
1134
+ // the start point if that happened.
1135
+ if (cursorIsBefore(selectionStart, selectionEnd) &&
1136
+ (cursorEqual(selectionStart, curEnd) ||
1137
+ cursorIsBefore(curEnd, selectionStart))) {
1138
+ // The end of the selection has moved from after the start to
1139
+ // before the start. We will shift the start right by 1.
1140
+ selectionStart.ch += 1;
1141
+ } else if (cursorIsBefore(selectionEnd, selectionStart) &&
1142
+ (cursorEqual(selectionStart, curEnd) ||
1143
+ cursorIsBefore(selectionStart, curEnd))) {
1144
+ // The opposite happened. We will shift the start left by 1.
1145
+ selectionStart.ch -= 1;
1146
+ }
1147
+ selectionEnd = curEnd;
1148
+ if (vim.visualLine) {
1149
+ if (cursorIsBefore(selectionStart, selectionEnd)) {
1150
+ selectionStart.ch = 0;
1151
+
1152
+ var lastLine = cm.lastLine();
1153
+ if (selectionEnd.line > lastLine) {
1154
+ selectionEnd.line = lastLine;
1155
+ }
1156
+ selectionEnd.ch = lineLength(cm, selectionEnd.line);
1157
+ } else {
1158
+ selectionEnd.ch = 0;
1159
+ selectionStart.ch = lineLength(cm, selectionStart.line);
1160
+ }
1161
+ }
1162
+ cm.setSelection(selectionStart, selectionEnd);
1163
+ updateMark(cm, vim, '<',
1164
+ cursorIsBefore(selectionStart, selectionEnd) ? selectionStart
1165
+ : selectionEnd);
1166
+ updateMark(cm, vim, '>',
1167
+ cursorIsBefore(selectionStart, selectionEnd) ? selectionEnd
1168
+ : selectionStart);
1169
+ } else if (!operator) {
1170
+ curEnd = clipCursorToContent(cm, curEnd);
1171
+ cm.setCursor(curEnd.line, curEnd.ch);
1172
+ }
1173
+ }
1174
+
1175
+ if (operator) {
1176
+ var inverted = false;
1177
+ vim.lastMotion = null;
1178
+ operatorArgs.repeat = repeat; // Indent in visual mode needs this.
1179
+ if (vim.visualMode) {
1180
+ curStart = selectionStart;
1181
+ curEnd = selectionEnd;
1182
+ motionArgs.inclusive = true;
1183
+ }
1184
+ // Swap start and end if motion was backward.
1185
+ if (cursorIsBefore(curEnd, curStart)) {
1186
+ var tmp = curStart;
1187
+ curStart = curEnd;
1188
+ curEnd = tmp;
1189
+ inverted = true;
1190
+ }
1191
+ if (motionArgs.inclusive && !(vim.visualMode && inverted)) {
1192
+ // Move the selection end one to the right to include the last
1193
+ // character.
1194
+ curEnd.ch++;
1195
+ }
1196
+ var linewise = motionArgs.linewise ||
1197
+ (vim.visualMode && vim.visualLine);
1198
+ if (linewise) {
1199
+ // Expand selection to entire line.
1200
+ expandSelectionToLine(cm, curStart, curEnd);
1201
+ } else if (motionArgs.forward) {
1202
+ // Clip to trailing newlines only if the motion goes forward.
1203
+ clipToLine(cm, curStart, curEnd);
1204
+ }
1205
+ operatorArgs.registerName = registerName;
1206
+ // Keep track of linewise as it affects how paste and change behave.
1207
+ operatorArgs.linewise = linewise;
1208
+ operators[operator](cm, operatorArgs, vim, curStart,
1209
+ curEnd, curOriginal);
1210
+ if (vim.visualMode) {
1211
+ exitVisualMode(cm);
1212
+ }
1213
+ }
1214
+ },
1215
+ recordLastEdit: function(vim, inputState, actionCommand) {
1216
+ var macroModeState = vimGlobalState.macroModeState;
1217
+ if (macroModeState.inReplay) { return; }
1218
+ vim.lastEditInputState = inputState;
1219
+ vim.lastEditActionCommand = actionCommand;
1220
+ macroModeState.lastInsertModeChanges.changes = [];
1221
+ macroModeState.lastInsertModeChanges.expectCursorActivityForChange = false;
1222
+ }
1223
+ };
1224
+
1225
+ /**
1226
+ * typedef {Object{line:number,ch:number}} Cursor An object containing the
1227
+ * position of the cursor.
1228
+ */
1229
+ // All of the functions below return Cursor objects.
1230
+ var motions = {
1231
+ moveToTopLine: function(cm, motionArgs) {
1232
+ var line = getUserVisibleLines(cm).top + motionArgs.repeat -1;
1233
+ return { line: line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(line)) };
1234
+ },
1235
+ moveToMiddleLine: function(cm) {
1236
+ var range = getUserVisibleLines(cm);
1237
+ var line = Math.floor((range.top + range.bottom) * 0.5);
1238
+ return { line: line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(line)) };
1239
+ },
1240
+ moveToBottomLine: function(cm, motionArgs) {
1241
+ var line = getUserVisibleLines(cm).bottom - motionArgs.repeat +1;
1242
+ return { line: line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(line)) };
1243
+ },
1244
+ expandToLine: function(cm, motionArgs) {
1245
+ // Expands forward to end of line, and then to next line if repeat is
1246
+ // >1. Does not handle backward motion!
1247
+ var cur = cm.getCursor();
1248
+ return { line: cur.line + motionArgs.repeat - 1, ch: Infinity };
1249
+ },
1250
+ findNext: function(cm, motionArgs) {
1251
+ var state = getSearchState(cm);
1252
+ var query = state.getQuery();
1253
+ if (!query) {
1254
+ return;
1255
+ }
1256
+ var prev = !motionArgs.forward;
1257
+ // If search is initiated with ? instead of /, negate direction.
1258
+ prev = (state.isReversed()) ? !prev : prev;
1259
+ highlightSearchMatches(cm, query);
1260
+ return findNext(cm, prev/** prev */, query, motionArgs.repeat);
1261
+ },
1262
+ goToMark: function(_cm, motionArgs, vim) {
1263
+ var mark = vim.marks[motionArgs.selectedCharacter];
1264
+ if (mark) {
1265
+ return mark.find();
1266
+ }
1267
+ return null;
1268
+ },
1269
+ jumpToMark: function(cm, motionArgs, vim) {
1270
+ var best = cm.getCursor();
1271
+ for (var i = 0; i < motionArgs.repeat; i++) {
1272
+ var cursor = best;
1273
+ for (var key in vim.marks) {
1274
+ if (!isLowerCase(key)) {
1275
+ continue;
1276
+ }
1277
+ var mark = vim.marks[key].find();
1278
+ var isWrongDirection = (motionArgs.forward) ?
1279
+ cursorIsBefore(mark, cursor) : cursorIsBefore(cursor, mark);
1280
+
1281
+ if (isWrongDirection) {
1282
+ continue;
1283
+ }
1284
+ if (motionArgs.linewise && (mark.line == cursor.line)) {
1285
+ continue;
1286
+ }
1287
+
1288
+ var equal = cursorEqual(cursor, best);
1289
+ var between = (motionArgs.forward) ?
1290
+ cusrorIsBetween(cursor, mark, best) :
1291
+ cusrorIsBetween(best, mark, cursor);
1292
+
1293
+ if (equal || between) {
1294
+ best = mark;
1295
+ }
1296
+ }
1297
+ }
1298
+
1299
+ if (motionArgs.linewise) {
1300
+ // Vim places the cursor on the first non-whitespace character of
1301
+ // the line if there is one, else it places the cursor at the end
1302
+ // of the line, regardless of whether a mark was found.
1303
+ best.ch = findFirstNonWhiteSpaceCharacter(cm.getLine(best.line));
1304
+ }
1305
+ return best;
1306
+ },
1307
+ moveByCharacters: function(cm, motionArgs) {
1308
+ var cur = cm.getCursor();
1309
+ var repeat = motionArgs.repeat;
1310
+ var ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat;
1311
+ return { line: cur.line, ch: ch };
1312
+ },
1313
+ moveByLines: function(cm, motionArgs, vim) {
1314
+ var cur = cm.getCursor();
1315
+ var endCh = cur.ch;
1316
+ // Depending what our last motion was, we may want to do different
1317
+ // things. If our last motion was moving vertically, we want to
1318
+ // preserve the HPos from our last horizontal move. If our last motion
1319
+ // was going to the end of a line, moving vertically we should go to
1320
+ // the end of the line, etc.
1321
+ switch (vim.lastMotion) {
1322
+ case this.moveByLines:
1323
+ case this.moveByDisplayLines:
1324
+ case this.moveByScroll:
1325
+ case this.moveToColumn:
1326
+ case this.moveToEol:
1327
+ endCh = vim.lastHPos;
1328
+ break;
1329
+ default:
1330
+ vim.lastHPos = endCh;
1331
+ }
1332
+ var repeat = motionArgs.repeat+(motionArgs.repeatOffset||0);
1333
+ var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat;
1334
+ var first = cm.firstLine();
1335
+ var last = cm.lastLine();
1336
+ // Vim cancels linewise motions that start on an edge and move beyond
1337
+ // that edge. It does not cancel motions that do not start on an edge.
1338
+ if ((line < first && cur.line == first) ||
1339
+ (line > last && cur.line == last)) {
1340
+ return;
1341
+ }
1342
+ if(motionArgs.toFirstChar){
1343
+ endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line));
1344
+ vim.lastHPos = endCh;
1345
+ }
1346
+ vim.lastHSPos = cm.charCoords({line:line, ch:endCh},'div').left;
1347
+ return { line: line, ch: endCh };
1348
+ },
1349
+ moveByDisplayLines: function(cm, motionArgs, vim) {
1350
+ var cur = cm.getCursor();
1351
+ switch (vim.lastMotion) {
1352
+ case this.moveByDisplayLines:
1353
+ case this.moveByScroll:
1354
+ case this.moveByLines:
1355
+ case this.moveToColumn:
1356
+ case this.moveToEol:
1357
+ break;
1358
+ default:
1359
+ vim.lastHSPos = cm.charCoords(cur,'div').left;
1360
+ }
1361
+ var repeat = motionArgs.repeat;
1362
+ var res=cm.findPosV(cur,(motionArgs.forward ? repeat : -repeat),'line',vim.lastHSPos);
1363
+ if (res.hitSide) {
1364
+ if (motionArgs.forward) {
1365
+ var lastCharCoords = cm.charCoords(res, 'div');
1366
+ var goalCoords = { top: lastCharCoords.top + 8, left: vim.lastHSPos };
1367
+ var res = cm.coordsChar(goalCoords, 'div');
1368
+ } else {
1369
+ var resCoords = cm.charCoords({ line: cm.firstLine(), ch: 0}, 'div');
1370
+ resCoords.left = vim.lastHSPos;
1371
+ res = cm.coordsChar(resCoords, 'div');
1372
+ }
1373
+ }
1374
+ vim.lastHPos = res.ch;
1375
+ return res;
1376
+ },
1377
+ moveByPage: function(cm, motionArgs) {
1378
+ // CodeMirror only exposes functions that move the cursor page down, so
1379
+ // doing this bad hack to move the cursor and move it back. evalInput
1380
+ // will move the cursor to where it should be in the end.
1381
+ var curStart = cm.getCursor();
1382
+ var repeat = motionArgs.repeat;
1383
+ cm.moveV((motionArgs.forward ? repeat : -repeat), 'page');
1384
+ var curEnd = cm.getCursor();
1385
+ cm.setCursor(curStart);
1386
+ return curEnd;
1387
+ },
1388
+ moveByParagraph: function(cm, motionArgs) {
1389
+ var line = cm.getCursor().line;
1390
+ var repeat = motionArgs.repeat;
1391
+ var inc = motionArgs.forward ? 1 : -1;
1392
+ for (var i = 0; i < repeat; i++) {
1393
+ if ((!motionArgs.forward && line === cm.firstLine() ) ||
1394
+ (motionArgs.forward && line == cm.lastLine())) {
1395
+ break;
1396
+ }
1397
+ line += inc;
1398
+ while (line !== cm.firstLine() && line != cm.lastLine() && cm.getLine(line)) {
1399
+ line += inc;
1400
+ }
1401
+ }
1402
+ return { line: line, ch: 0 };
1403
+ },
1404
+ moveByScroll: function(cm, motionArgs, vim) {
1405
+ var scrollbox = cm.getScrollInfo();
1406
+ var curEnd = null;
1407
+ var repeat = motionArgs.repeat;
1408
+ if (!repeat) {
1409
+ repeat = scrollbox.clientHeight / (2 * cm.defaultTextHeight());
1410
+ }
1411
+ var orig = cm.charCoords(cm.getCursor(), 'local');
1412
+ motionArgs.repeat = repeat;
1413
+ var curEnd = motions.moveByDisplayLines(cm, motionArgs, vim);
1414
+ if (!curEnd) {
1415
+ return null;
1416
+ }
1417
+ var dest = cm.charCoords(curEnd, 'local');
1418
+ cm.scrollTo(null, scrollbox.top + dest.top - orig.top);
1419
+ return curEnd;
1420
+ },
1421
+ moveByWords: function(cm, motionArgs) {
1422
+ return moveToWord(cm, motionArgs.repeat, !!motionArgs.forward,
1423
+ !!motionArgs.wordEnd, !!motionArgs.bigWord);
1424
+ },
1425
+ moveTillCharacter: function(cm, motionArgs) {
1426
+ var repeat = motionArgs.repeat;
1427
+ var curEnd = moveToCharacter(cm, repeat, motionArgs.forward,
1428
+ motionArgs.selectedCharacter);
1429
+ var increment = motionArgs.forward ? -1 : 1;
1430
+ recordLastCharacterSearch(increment, motionArgs);
1431
+ if(!curEnd)return cm.getCursor();
1432
+ curEnd.ch += increment;
1433
+ return curEnd;
1434
+ },
1435
+ moveToCharacter: function(cm, motionArgs) {
1436
+ var repeat = motionArgs.repeat;
1437
+ recordLastCharacterSearch(0, motionArgs);
1438
+ return moveToCharacter(cm, repeat, motionArgs.forward,
1439
+ motionArgs.selectedCharacter) || cm.getCursor();
1440
+ },
1441
+ moveToSymbol: function(cm, motionArgs) {
1442
+ var repeat = motionArgs.repeat;
1443
+ return findSymbol(cm, repeat, motionArgs.forward,
1444
+ motionArgs.selectedCharacter) || cm.getCursor();
1445
+ },
1446
+ moveToColumn: function(cm, motionArgs, vim) {
1447
+ var repeat = motionArgs.repeat;
1448
+ // repeat is equivalent to which column we want to move to!
1449
+ vim.lastHPos = repeat - 1;
1450
+ vim.lastHSPos = cm.charCoords(cm.getCursor(),'div').left;
1451
+ return moveToColumn(cm, repeat);
1452
+ },
1453
+ moveToEol: function(cm, motionArgs, vim) {
1454
+ var cur = cm.getCursor();
1455
+ vim.lastHPos = Infinity;
1456
+ var retval={ line: cur.line + motionArgs.repeat - 1, ch: Infinity };
1457
+ var end=cm.clipPos(retval);
1458
+ end.ch--;
1459
+ vim.lastHSPos = cm.charCoords(end,'div').left;
1460
+ return retval;
1461
+ },
1462
+ moveToFirstNonWhiteSpaceCharacter: function(cm) {
1463
+ // Go to the start of the line where the text begins, or the end for
1464
+ // whitespace-only lines
1465
+ var cursor = cm.getCursor();
1466
+ return { line: cursor.line,
1467
+ ch: findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)) };
1468
+ },
1469
+ moveToMatchedSymbol: function(cm) {
1470
+ var cursor = cm.getCursor();
1471
+ var line = cursor.line;
1472
+ var ch = cursor.ch;
1473
+ var lineText = cm.getLine(line);
1474
+ var symbol;
1475
+ var startContext = cm.getTokenAt(cursor).type;
1476
+ var startCtxLevel = getContextLevel(startContext);
1477
+ do {
1478
+ symbol = lineText.charAt(ch++);
1479
+ if (symbol && isMatchableSymbol(symbol)) {
1480
+ var endContext = cm.getTokenAt({line:line, ch:ch}).type;
1481
+ var endCtxLevel = getContextLevel(endContext);
1482
+ if (startCtxLevel >= endCtxLevel) {
1483
+ break;
1484
+ }
1485
+ }
1486
+ } while (symbol);
1487
+ if (symbol) {
1488
+ return findMatchedSymbol(cm, {line:line, ch:ch-1}, symbol);
1489
+ } else {
1490
+ return cursor;
1491
+ }
1492
+ },
1493
+ moveToStartOfLine: function(cm) {
1494
+ var cursor = cm.getCursor();
1495
+ return { line: cursor.line, ch: 0 };
1496
+ },
1497
+ moveToLineOrEdgeOfDocument: function(cm, motionArgs) {
1498
+ var lineNum = motionArgs.forward ? cm.lastLine() : cm.firstLine();
1499
+ if (motionArgs.repeatIsExplicit) {
1500
+ lineNum = motionArgs.repeat - cm.getOption('firstLineNumber');
1501
+ }
1502
+ return { line: lineNum,
1503
+ ch: findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum)) };
1504
+ },
1505
+ textObjectManipulation: function(cm, motionArgs) {
1506
+ var character = motionArgs.selectedCharacter;
1507
+ // Inclusive is the difference between a and i
1508
+ // TODO: Instead of using the additional text object map to perform text
1509
+ // object operations, merge the map into the defaultKeyMap and use
1510
+ // motionArgs to define behavior. Define separate entries for 'aw',
1511
+ // 'iw', 'a[', 'i[', etc.
1512
+ var inclusive = !motionArgs.textObjectInner;
1513
+ if (!textObjects[character]) {
1514
+ // No text object defined for this, don't move.
1515
+ return null;
1516
+ }
1517
+ var tmp = textObjects[character](cm, inclusive);
1518
+ var start = tmp.start;
1519
+ var end = tmp.end;
1520
+ return [start, end];
1521
+ },
1522
+ repeatLastCharacterSearch: function(cm, motionArgs) {
1523
+ var lastSearch = vimGlobalState.lastChararacterSearch;
1524
+ var repeat = motionArgs.repeat;
1525
+ var forward = motionArgs.forward === lastSearch.forward;
1526
+ var increment = (lastSearch.increment ? 1 : 0) * (forward ? -1 : 1);
1527
+ cm.moveH(-increment, 'char');
1528
+ motionArgs.inclusive = forward ? true : false;
1529
+ var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter);
1530
+ if (!curEnd) {
1531
+ cm.moveH(increment, 'char');
1532
+ return cm.getCursor();
1533
+ }
1534
+ curEnd.ch += increment;
1535
+ return curEnd;
1536
+ }
1537
+ };
1538
+
1539
+ var operators = {
1540
+ change: function(cm, operatorArgs, _vim, curStart, curEnd) {
1541
+ vimGlobalState.registerController.pushText(
1542
+ operatorArgs.registerName, 'change', cm.getRange(curStart, curEnd),
1543
+ operatorArgs.linewise);
1544
+ if (operatorArgs.linewise) {
1545
+ // Push the next line back down, if there is a next line.
1546
+ var replacement = curEnd.line > cm.lastLine() ? '' : '\n';
1547
+ cm.replaceRange(replacement, curStart, curEnd);
1548
+ cm.indentLine(curStart.line, 'smart');
1549
+ // null ch so setCursor moves to end of line.
1550
+ curStart.ch = null;
1551
+ } else {
1552
+ // Exclude trailing whitespace if the range is not all whitespace.
1553
+ var text = cm.getRange(curStart, curEnd);
1554
+ if (!isWhiteSpaceString(text)) {
1555
+ var match = (/\s+$/).exec(text);
1556
+ if (match) {
1557
+ curEnd = offsetCursor(curEnd, 0, - match[0].length);
1558
+ }
1559
+ }
1560
+ cm.replaceRange('', curStart, curEnd);
1561
+ }
1562
+ actions.enterInsertMode(cm, {}, cm.state.vim);
1563
+ cm.setCursor(curStart);
1564
+ },
1565
+ // delete is a javascript keyword.
1566
+ 'delete': function(cm, operatorArgs, _vim, curStart, curEnd) {
1567
+ // If the ending line is past the last line, inclusive, instead of
1568
+ // including the trailing \n, include the \n before the starting line
1569
+ if (operatorArgs.linewise &&
1570
+ curEnd.line > cm.lastLine() && curStart.line > cm.firstLine()) {
1571
+ curStart.line--;
1572
+ curStart.ch = lineLength(cm, curStart.line);
1573
+ }
1574
+ vimGlobalState.registerController.pushText(
1575
+ operatorArgs.registerName, 'delete', cm.getRange(curStart, curEnd),
1576
+ operatorArgs.linewise);
1577
+ cm.replaceRange('', curStart, curEnd);
1578
+ if (operatorArgs.linewise) {
1579
+ cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
1580
+ } else {
1581
+ cm.setCursor(curStart);
1582
+ }
1583
+ },
1584
+ indent: function(cm, operatorArgs, vim, curStart, curEnd) {
1585
+ var startLine = curStart.line;
1586
+ var endLine = curEnd.line;
1587
+ // In visual mode, n> shifts the selection right n times, instead of
1588
+ // shifting n lines right once.
1589
+ var repeat = (vim.visualMode) ? operatorArgs.repeat : 1;
1590
+ if (operatorArgs.linewise) {
1591
+ // The only way to delete a newline is to delete until the start of
1592
+ // the next line, so in linewise mode evalInput will include the next
1593
+ // line. We don't want this in indent, so we go back a line.
1594
+ endLine--;
1595
+ }
1596
+ for (var i = startLine; i <= endLine; i++) {
1597
+ for (var j = 0; j < repeat; j++) {
1598
+ cm.indentLine(i, operatorArgs.indentRight);
1599
+ }
1600
+ }
1601
+ cm.setCursor(curStart);
1602
+ cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
1603
+ },
1604
+ swapcase: function(cm, operatorArgs, _vim, curStart, curEnd, curOriginal) {
1605
+ var toSwap = cm.getRange(curStart, curEnd);
1606
+ var swapped = '';
1607
+ for (var i = 0; i < toSwap.length; i++) {
1608
+ var character = toSwap.charAt(i);
1609
+ swapped += isUpperCase(character) ? character.toLowerCase() :
1610
+ character.toUpperCase();
1611
+ }
1612
+ cm.replaceRange(swapped, curStart, curEnd);
1613
+ if (!operatorArgs.shouldMoveCursor) {
1614
+ cm.setCursor(curOriginal);
1615
+ }
1616
+ },
1617
+ yank: function(cm, operatorArgs, _vim, curStart, curEnd, curOriginal) {
1618
+ vimGlobalState.registerController.pushText(
1619
+ operatorArgs.registerName, 'yank',
1620
+ cm.getRange(curStart, curEnd), operatorArgs.linewise);
1621
+ cm.setCursor(curOriginal);
1622
+ }
1623
+ };
1624
+
1625
+ var actions = {
1626
+ jumpListWalk: function(cm, actionArgs, vim) {
1627
+ if (vim.visualMode) {
1628
+ return;
1629
+ }
1630
+ var repeat = actionArgs.repeat;
1631
+ var forward = actionArgs.forward;
1632
+ var jumpList = vimGlobalState.jumpList;
1633
+
1634
+ var mark = jumpList.move(cm, forward ? repeat : -repeat);
1635
+ var markPos = mark ? mark.find() : undefined;
1636
+ markPos = markPos ? markPos : cm.getCursor();
1637
+ cm.setCursor(markPos);
1638
+ },
1639
+ scrollToCursor: function(cm, actionArgs) {
1640
+ var lineNum = cm.getCursor().line;
1641
+ var charCoords = cm.charCoords({line: lineNum, ch: 0}, 'local');
1642
+ var height = cm.getScrollInfo().clientHeight;
1643
+ var y = charCoords.top;
1644
+ var lineHeight = charCoords.bottom - y;
1645
+ switch (actionArgs.position) {
1646
+ case 'center': y = y - (height / 2) + lineHeight;
1647
+ break;
1648
+ case 'bottom': y = y - height + lineHeight*1.4;
1649
+ break;
1650
+ case 'top': y = y + lineHeight*0.4;
1651
+ break;
1652
+ }
1653
+ cm.scrollTo(null, y);
1654
+ },
1655
+ replayMacro: function(cm, actionArgs) {
1656
+ var registerName = actionArgs.selectedCharacter;
1657
+ var repeat = actionArgs.repeat;
1658
+ var macroModeState = vimGlobalState.macroModeState;
1659
+ if (registerName == '@') {
1660
+ registerName = macroModeState.latestRegister;
1661
+ }
1662
+ var keyBuffer = parseRegisterToKeyBuffer(macroModeState, registerName);
1663
+ while(repeat--){
1664
+ executeMacroKeyBuffer(cm, macroModeState, keyBuffer);
1665
+ }
1666
+ },
1667
+ exitMacroRecordMode: function() {
1668
+ var macroModeState = vimGlobalState.macroModeState;
1669
+ macroModeState.toggle();
1670
+ parseKeyBufferToRegister(macroModeState.latestRegister,
1671
+ macroModeState.macroKeyBuffer);
1672
+ },
1673
+ enterMacroRecordMode: function(cm, actionArgs) {
1674
+ var macroModeState = vimGlobalState.macroModeState;
1675
+ var registerName = actionArgs.selectedCharacter;
1676
+ macroModeState.toggle(cm, registerName);
1677
+ emptyMacroKeyBuffer(macroModeState);
1678
+ },
1679
+ enterInsertMode: function(cm, actionArgs, vim) {
1680
+ if (cm.getOption('readOnly')) { return; }
1681
+ vim.insertMode = true;
1682
+ vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1;
1683
+ var insertAt = (actionArgs) ? actionArgs.insertAt : null;
1684
+ if (insertAt == 'eol') {
1685
+ var cursor = cm.getCursor();
1686
+ cursor = { line: cursor.line, ch: lineLength(cm, cursor.line) };
1687
+ cm.setCursor(cursor);
1688
+ } else if (insertAt == 'charAfter') {
1689
+ cm.setCursor(offsetCursor(cm.getCursor(), 0, 1));
1690
+ } else if (insertAt == 'firstNonBlank') {
1691
+ cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm));
1692
+ }
1693
+ cm.setOption('keyMap', 'vim-insert');
1694
+ if (actionArgs && actionArgs.replace) {
1695
+ // Handle Replace-mode as a special case of insert mode.
1696
+ cm.toggleOverwrite(true);
1697
+ cm.setOption('keyMap', 'vim-replace');
1698
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"});
1699
+ } else {
1700
+ cm.setOption('keyMap', 'vim-insert');
1701
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"});
1702
+ }
1703
+ if (!vimGlobalState.macroModeState.inReplay) {
1704
+ // Only record if not replaying.
1705
+ cm.on('change', onChange);
1706
+ cm.on('cursorActivity', onCursorActivity);
1707
+ CodeMirror.on(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
1708
+ }
1709
+ },
1710
+ toggleVisualMode: function(cm, actionArgs, vim) {
1711
+ var repeat = actionArgs.repeat;
1712
+ var curStart = cm.getCursor();
1713
+ var curEnd;
1714
+ // TODO: The repeat should actually select number of characters/lines
1715
+ // equal to the repeat times the size of the previous visual
1716
+ // operation.
1717
+ if (!vim.visualMode) {
1718
+ cm.on('mousedown', exitVisualMode);
1719
+ vim.visualMode = true;
1720
+ vim.visualLine = !!actionArgs.linewise;
1721
+ if (vim.visualLine) {
1722
+ curStart.ch = 0;
1723
+ curEnd = clipCursorToContent(cm, {
1724
+ line: curStart.line + repeat - 1,
1725
+ ch: lineLength(cm, curStart.line)
1726
+ }, true /** includeLineBreak */);
1727
+ } else {
1728
+ curEnd = clipCursorToContent(cm, {
1729
+ line: curStart.line,
1730
+ ch: curStart.ch + repeat
1731
+ }, true /** includeLineBreak */);
1732
+ }
1733
+ // Make the initial selection.
1734
+ if (!actionArgs.repeatIsExplicit && !vim.visualLine) {
1735
+ // This is a strange case. Here the implicit repeat is 1. The
1736
+ // following commands lets the cursor hover over the 1 character
1737
+ // selection.
1738
+ cm.setCursor(curEnd);
1739
+ cm.setSelection(curEnd, curStart);
1740
+ } else {
1741
+ cm.setSelection(curStart, curEnd);
1742
+ }
1743
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : ""});
1744
+ } else {
1745
+ curStart = cm.getCursor('anchor');
1746
+ curEnd = cm.getCursor('head');
1747
+ if (!vim.visualLine && actionArgs.linewise) {
1748
+ // Shift-V pressed in characterwise visual mode. Switch to linewise
1749
+ // visual mode instead of exiting visual mode.
1750
+ vim.visualLine = true;
1751
+ curStart.ch = cursorIsBefore(curStart, curEnd) ? 0 :
1752
+ lineLength(cm, curStart.line);
1753
+ curEnd.ch = cursorIsBefore(curStart, curEnd) ?
1754
+ lineLength(cm, curEnd.line) : 0;
1755
+ cm.setSelection(curStart, curEnd);
1756
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: "linewise"});
1757
+ } else if (vim.visualLine && !actionArgs.linewise) {
1758
+ // v pressed in linewise visual mode. Switch to characterwise visual
1759
+ // mode instead of exiting visual mode.
1760
+ vim.visualLine = false;
1761
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"});
1762
+ } else {
1763
+ exitVisualMode(cm);
1764
+ }
1765
+ }
1766
+ updateMark(cm, vim, '<', cursorIsBefore(curStart, curEnd) ? curStart
1767
+ : curEnd);
1768
+ updateMark(cm, vim, '>', cursorIsBefore(curStart, curEnd) ? curEnd
1769
+ : curStart);
1770
+ },
1771
+ joinLines: function(cm, actionArgs, vim) {
1772
+ var curStart, curEnd;
1773
+ if (vim.visualMode) {
1774
+ curStart = cm.getCursor('anchor');
1775
+ curEnd = cm.getCursor('head');
1776
+ curEnd.ch = lineLength(cm, curEnd.line) - 1;
1777
+ } else {
1778
+ // Repeat is the number of lines to join. Minimum 2 lines.
1779
+ var repeat = Math.max(actionArgs.repeat, 2);
1780
+ curStart = cm.getCursor();
1781
+ curEnd = clipCursorToContent(cm, { line: curStart.line + repeat - 1,
1782
+ ch: Infinity });
1783
+ }
1784
+ var finalCh = 0;
1785
+ cm.operation(function() {
1786
+ for (var i = curStart.line; i < curEnd.line; i++) {
1787
+ finalCh = lineLength(cm, curStart.line);
1788
+ var tmp = { line: curStart.line + 1,
1789
+ ch: lineLength(cm, curStart.line + 1) };
1790
+ var text = cm.getRange(curStart, tmp);
1791
+ text = text.replace(/\n\s*/g, ' ');
1792
+ cm.replaceRange(text, curStart, tmp);
1793
+ }
1794
+ var curFinalPos = { line: curStart.line, ch: finalCh };
1795
+ cm.setCursor(curFinalPos);
1796
+ });
1797
+ },
1798
+ newLineAndEnterInsertMode: function(cm, actionArgs, vim) {
1799
+ vim.insertMode = true;
1800
+ var insertAt = cm.getCursor();
1801
+ if (insertAt.line === cm.firstLine() && !actionArgs.after) {
1802
+ // Special case for inserting newline before start of document.
1803
+ cm.replaceRange('\n', { line: cm.firstLine(), ch: 0 });
1804
+ cm.setCursor(cm.firstLine(), 0);
1805
+ } else {
1806
+ insertAt.line = (actionArgs.after) ? insertAt.line :
1807
+ insertAt.line - 1;
1808
+ insertAt.ch = lineLength(cm, insertAt.line);
1809
+ cm.setCursor(insertAt);
1810
+ var newlineFn = CodeMirror.commands.newlineAndIndentContinueComment ||
1811
+ CodeMirror.commands.newlineAndIndent;
1812
+ newlineFn(cm);
1813
+ }
1814
+ this.enterInsertMode(cm, { repeat: actionArgs.repeat }, vim);
1815
+ },
1816
+ paste: function(cm, actionArgs) {
1817
+ var cur = cm.getCursor();
1818
+ var register = vimGlobalState.registerController.getRegister(
1819
+ actionArgs.registerName);
1820
+ if (!register.text) {
1821
+ return;
1822
+ }
1823
+ for (var text = '', i = 0; i < actionArgs.repeat; i++) {
1824
+ text += register.text;
1825
+ }
1826
+ var linewise = register.linewise;
1827
+ if (linewise) {
1828
+ if (actionArgs.after) {
1829
+ // Move the newline at the end to the start instead, and paste just
1830
+ // before the newline character of the line we are on right now.
1831
+ text = '\n' + text.slice(0, text.length - 1);
1832
+ cur.ch = lineLength(cm, cur.line);
1833
+ } else {
1834
+ cur.ch = 0;
1835
+ }
1836
+ } else {
1837
+ cur.ch += actionArgs.after ? 1 : 0;
1838
+ }
1839
+ cm.replaceRange(text, cur);
1840
+ // Now fine tune the cursor to where we want it.
1841
+ var curPosFinal;
1842
+ var idx;
1843
+ if (linewise && actionArgs.after) {
1844
+ curPosFinal = { line: cur.line + 1,
1845
+ ch: findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1)) };
1846
+ } else if (linewise && !actionArgs.after) {
1847
+ curPosFinal = { line: cur.line,
1848
+ ch: findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line)) };
1849
+ } else if (!linewise && actionArgs.after) {
1850
+ idx = cm.indexFromPos(cur);
1851
+ curPosFinal = cm.posFromIndex(idx + text.length - 1);
1852
+ } else {
1853
+ idx = cm.indexFromPos(cur);
1854
+ curPosFinal = cm.posFromIndex(idx + text.length);
1855
+ }
1856
+ cm.setCursor(curPosFinal);
1857
+ },
1858
+ undo: function(cm, actionArgs) {
1859
+ cm.operation(function() {
1860
+ repeatFn(cm, CodeMirror.commands.undo, actionArgs.repeat)();
1861
+ cm.setCursor(cm.getCursor('anchor'));
1862
+ });
1863
+ },
1864
+ redo: function(cm, actionArgs) {
1865
+ repeatFn(cm, CodeMirror.commands.redo, actionArgs.repeat)();
1866
+ },
1867
+ setRegister: function(_cm, actionArgs, vim) {
1868
+ vim.inputState.registerName = actionArgs.selectedCharacter;
1869
+ },
1870
+ setMark: function(cm, actionArgs, vim) {
1871
+ var markName = actionArgs.selectedCharacter;
1872
+ updateMark(cm, vim, markName, cm.getCursor());
1873
+ },
1874
+ replace: function(cm, actionArgs, vim) {
1875
+ var replaceWith = actionArgs.selectedCharacter;
1876
+ var curStart = cm.getCursor();
1877
+ var replaceTo;
1878
+ var curEnd;
1879
+ if(vim.visualMode){
1880
+ curStart=cm.getCursor('start');
1881
+ curEnd=cm.getCursor('end');
1882
+ // workaround to catch the character under the cursor
1883
+ // existing workaround doesn't cover actions
1884
+ curEnd=cm.clipPos({line: curEnd.line, ch: curEnd.ch+1});
1885
+ }else{
1886
+ var line = cm.getLine(curStart.line);
1887
+ replaceTo = curStart.ch + actionArgs.repeat;
1888
+ if (replaceTo > line.length) {
1889
+ replaceTo=line.length;
1890
+ }
1891
+ curEnd = { line: curStart.line, ch: replaceTo };
1892
+ }
1893
+ if(replaceWith=='\n'){
1894
+ if(!vim.visualMode) cm.replaceRange('', curStart, curEnd);
1895
+ // special case, where vim help says to replace by just one line-break
1896
+ (CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm);
1897
+ }else {
1898
+ var replaceWithStr=cm.getRange(curStart, curEnd);
1899
+ //replace all characters in range by selected, but keep linebreaks
1900
+ replaceWithStr=replaceWithStr.replace(/[^\n]/g,replaceWith);
1901
+ cm.replaceRange(replaceWithStr, curStart, curEnd);
1902
+ if(vim.visualMode){
1903
+ cm.setCursor(curStart);
1904
+ exitVisualMode(cm);
1905
+ }else{
1906
+ cm.setCursor(offsetCursor(curEnd, 0, -1));
1907
+ }
1908
+ }
1909
+ },
1910
+ incrementNumberToken: function(cm, actionArgs) {
1911
+ var cur = cm.getCursor();
1912
+ var lineStr = cm.getLine(cur.line);
1913
+ var re = /-?\d+/g;
1914
+ var match;
1915
+ var start;
1916
+ var end;
1917
+ var numberStr;
1918
+ var token;
1919
+ while ((match = re.exec(lineStr)) !== null) {
1920
+ token = match[0];
1921
+ start = match.index;
1922
+ end = start + token.length;
1923
+ if(cur.ch < end)break;
1924
+ }
1925
+ if(!actionArgs.backtrack && (end <= cur.ch))return;
1926
+ if (token) {
1927
+ var increment = actionArgs.increase ? 1 : -1;
1928
+ var number = parseInt(token) + (increment * actionArgs.repeat);
1929
+ var from = {ch:start, line:cur.line};
1930
+ var to = {ch:end, line:cur.line};
1931
+ numberStr = number.toString();
1932
+ cm.replaceRange(numberStr, from, to);
1933
+ } else {
1934
+ return;
1935
+ }
1936
+ cm.setCursor({line: cur.line, ch: start + numberStr.length - 1});
1937
+ },
1938
+ repeatLastEdit: function(cm, actionArgs, vim) {
1939
+ var lastEditInputState = vim.lastEditInputState;
1940
+ if (!lastEditInputState) { return; }
1941
+ var repeat = actionArgs.repeat;
1942
+ if (repeat && actionArgs.repeatIsExplicit) {
1943
+ vim.lastEditInputState.repeatOverride = repeat;
1944
+ } else {
1945
+ repeat = vim.lastEditInputState.repeatOverride || repeat;
1946
+ }
1947
+ repeatLastEdit(cm, vim, repeat, false /** repeatForInsert */);
1948
+ }
1949
+ };
1950
+
1951
+ var textObjects = {
1952
+ // TODO: lots of possible exceptions that can be thrown here. Try da(
1953
+ // outside of a () block.
1954
+ // TODO: implement text objects for the reverse like }. Should just be
1955
+ // an additional mapping after moving to the defaultKeyMap.
1956
+ 'w': function(cm, inclusive) {
1957
+ return expandWordUnderCursor(cm, inclusive, true /** forward */,
1958
+ false /** bigWord */);
1959
+ },
1960
+ 'W': function(cm, inclusive) {
1961
+ return expandWordUnderCursor(cm, inclusive,
1962
+ true /** forward */, true /** bigWord */);
1963
+ },
1964
+ '{': function(cm, inclusive) {
1965
+ return selectCompanionObject(cm, '}', inclusive);
1966
+ },
1967
+ '(': function(cm, inclusive) {
1968
+ return selectCompanionObject(cm, ')', inclusive);
1969
+ },
1970
+ '[': function(cm, inclusive) {
1971
+ return selectCompanionObject(cm, ']', inclusive);
1972
+ },
1973
+ '\'': function(cm, inclusive) {
1974
+ return findBeginningAndEnd(cm, "'", inclusive);
1975
+ },
1976
+ '"': function(cm, inclusive) {
1977
+ return findBeginningAndEnd(cm, '"', inclusive);
1978
+ }
1979
+ };
1980
+
1981
+ /*
1982
+ * Below are miscellaneous utility functions used by vim.js
1983
+ */
1984
+
1985
+ /**
1986
+ * Clips cursor to ensure that line is within the buffer's range
1987
+ * If includeLineBreak is true, then allow cur.ch == lineLength.
1988
+ */
1989
+ function clipCursorToContent(cm, cur, includeLineBreak) {
1990
+ var line = Math.min(Math.max(cm.firstLine(), cur.line), cm.lastLine() );
1991
+ var maxCh = lineLength(cm, line) - 1;
1992
+ maxCh = (includeLineBreak) ? maxCh + 1 : maxCh;
1993
+ var ch = Math.min(Math.max(0, cur.ch), maxCh);
1994
+ return { line: line, ch: ch };
1995
+ }
1996
+ function copyArgs(args) {
1997
+ var ret = {};
1998
+ for (var prop in args) {
1999
+ if (args.hasOwnProperty(prop)) {
2000
+ ret[prop] = args[prop];
2001
+ }
2002
+ }
2003
+ return ret;
2004
+ }
2005
+ function offsetCursor(cur, offsetLine, offsetCh) {
2006
+ return { line: cur.line + offsetLine, ch: cur.ch + offsetCh };
2007
+ }
2008
+ function matchKeysPartial(pressed, mapped) {
2009
+ for (var i = 0; i < pressed.length; i++) {
2010
+ // 'character' means any character. For mark, register commads, etc.
2011
+ if (pressed[i] != mapped[i] && mapped[i] != 'character') {
2012
+ return false;
2013
+ }
2014
+ }
2015
+ return true;
2016
+ }
2017
+ function repeatFn(cm, fn, repeat) {
2018
+ return function() {
2019
+ for (var i = 0; i < repeat; i++) {
2020
+ fn(cm);
2021
+ }
2022
+ };
2023
+ }
2024
+ function copyCursor(cur) {
2025
+ return { line: cur.line, ch: cur.ch };
2026
+ }
2027
+ function cursorEqual(cur1, cur2) {
2028
+ return cur1.ch == cur2.ch && cur1.line == cur2.line;
2029
+ }
2030
+ function cursorIsBefore(cur1, cur2) {
2031
+ if (cur1.line < cur2.line) {
2032
+ return true;
2033
+ }
2034
+ if (cur1.line == cur2.line && cur1.ch < cur2.ch) {
2035
+ return true;
2036
+ }
2037
+ return false;
2038
+ }
2039
+ function cusrorIsBetween(cur1, cur2, cur3) {
2040
+ // returns true if cur2 is between cur1 and cur3.
2041
+ var cur1before2 = cursorIsBefore(cur1, cur2);
2042
+ var cur2before3 = cursorIsBefore(cur2, cur3);
2043
+ return cur1before2 && cur2before3;
2044
+ }
2045
+ function lineLength(cm, lineNum) {
2046
+ return cm.getLine(lineNum).length;
2047
+ }
2048
+ function reverse(s){
2049
+ return s.split('').reverse().join('');
2050
+ }
2051
+ function trim(s) {
2052
+ if (s.trim) {
2053
+ return s.trim();
2054
+ }
2055
+ return s.replace(/^\s+|\s+$/g, '');
2056
+ }
2057
+ function escapeRegex(s) {
2058
+ return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1');
2059
+ }
2060
+
2061
+ function exitVisualMode(cm) {
2062
+ cm.off('mousedown', exitVisualMode);
2063
+ var vim = cm.state.vim;
2064
+ vim.visualMode = false;
2065
+ vim.visualLine = false;
2066
+ var selectionStart = cm.getCursor('anchor');
2067
+ var selectionEnd = cm.getCursor('head');
2068
+ if (!cursorEqual(selectionStart, selectionEnd)) {
2069
+ // Clear the selection and set the cursor only if the selection has not
2070
+ // already been cleared. Otherwise we risk moving the cursor somewhere
2071
+ // it's not supposed to be.
2072
+ cm.setCursor(clipCursorToContent(cm, selectionEnd));
2073
+ }
2074
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
2075
+ }
2076
+
2077
+ // Remove any trailing newlines from the selection. For
2078
+ // example, with the caret at the start of the last word on the line,
2079
+ // 'dw' should word, but not the newline, while 'w' should advance the
2080
+ // caret to the first character of the next line.
2081
+ function clipToLine(cm, curStart, curEnd) {
2082
+ var selection = cm.getRange(curStart, curEnd);
2083
+ // Only clip if the selection ends with trailing newline + whitespace
2084
+ if (/\n\s*$/.test(selection)) {
2085
+ var lines = selection.split('\n');
2086
+ // We know this is all whitepsace.
2087
+ lines.pop();
2088
+
2089
+ // Cases:
2090
+ // 1. Last word is an empty line - do not clip the trailing '\n'
2091
+ // 2. Last word is not an empty line - clip the trailing '\n'
2092
+ var line;
2093
+ // Find the line containing the last word, and clip all whitespace up
2094
+ // to it.
2095
+ for (var line = lines.pop(); lines.length > 0 && line && isWhiteSpaceString(line); line = lines.pop()) {
2096
+ curEnd.line--;
2097
+ curEnd.ch = 0;
2098
+ }
2099
+ // If the last word is not an empty line, clip an additional newline
2100
+ if (line) {
2101
+ curEnd.line--;
2102
+ curEnd.ch = lineLength(cm, curEnd.line);
2103
+ } else {
2104
+ curEnd.ch = 0;
2105
+ }
2106
+ }
2107
+ }
2108
+
2109
+ // Expand the selection to line ends.
2110
+ function expandSelectionToLine(_cm, curStart, curEnd) {
2111
+ curStart.ch = 0;
2112
+ curEnd.ch = 0;
2113
+ curEnd.line++;
2114
+ }
2115
+
2116
+ function findFirstNonWhiteSpaceCharacter(text) {
2117
+ if (!text) {
2118
+ return 0;
2119
+ }
2120
+ var firstNonWS = text.search(/\S/);
2121
+ return firstNonWS == -1 ? text.length : firstNonWS;
2122
+ }
2123
+
2124
+ function expandWordUnderCursor(cm, inclusive, _forward, bigWord, noSymbol) {
2125
+ var cur = cm.getCursor();
2126
+ var line = cm.getLine(cur.line);
2127
+ var idx = cur.ch;
2128
+
2129
+ // Seek to first word or non-whitespace character, depending on if
2130
+ // noSymbol is true.
2131
+ var textAfterIdx = line.substring(idx);
2132
+ var firstMatchedChar;
2133
+ if (noSymbol) {
2134
+ firstMatchedChar = textAfterIdx.search(/\w/);
2135
+ } else {
2136
+ firstMatchedChar = textAfterIdx.search(/\S/);
2137
+ }
2138
+ if (firstMatchedChar == -1) {
2139
+ return null;
2140
+ }
2141
+ idx += firstMatchedChar;
2142
+ textAfterIdx = line.substring(idx);
2143
+ var textBeforeIdx = line.substring(0, idx);
2144
+
2145
+ var matchRegex;
2146
+ // Greedy matchers for the "word" we are trying to expand.
2147
+ if (bigWord) {
2148
+ matchRegex = /^\S+/;
2149
+ } else {
2150
+ if ((/\w/).test(line.charAt(idx))) {
2151
+ matchRegex = /^\w+/;
2152
+ } else {
2153
+ matchRegex = /^[^\w\s]+/;
2154
+ }
2155
+ }
2156
+
2157
+ var wordAfterRegex = matchRegex.exec(textAfterIdx);
2158
+ var wordStart = idx;
2159
+ var wordEnd = idx + wordAfterRegex[0].length;
2160
+ // TODO: Find a better way to do this. It will be slow on very long lines.
2161
+ var revTextBeforeIdx = reverse(textBeforeIdx);
2162
+ var wordBeforeRegex = matchRegex.exec(revTextBeforeIdx);
2163
+ if (wordBeforeRegex) {
2164
+ wordStart -= wordBeforeRegex[0].length;
2165
+ }
2166
+
2167
+ if (inclusive) {
2168
+ // If present, trim all whitespace after word.
2169
+ // Otherwise, trim all whitespace before word.
2170
+ var textAfterWordEnd = line.substring(wordEnd);
2171
+ var whitespacesAfterWord = textAfterWordEnd.match(/^\s*/)[0].length;
2172
+ if (whitespacesAfterWord > 0) {
2173
+ wordEnd += whitespacesAfterWord;
2174
+ } else {
2175
+ var revTrim = revTextBeforeIdx.length - wordStart;
2176
+ var textBeforeWordStart = revTextBeforeIdx.substring(revTrim);
2177
+ var whitespacesBeforeWord = textBeforeWordStart.match(/^\s*/)[0].length;
2178
+ wordStart -= whitespacesBeforeWord;
2179
+ }
2180
+ }
2181
+
2182
+ return { start: { line: cur.line, ch: wordStart },
2183
+ end: { line: cur.line, ch: wordEnd }};
2184
+ }
2185
+
2186
+ function recordJumpPosition(cm, oldCur, newCur) {
2187
+ if(!cursorEqual(oldCur, newCur)) {
2188
+ vimGlobalState.jumpList.add(cm, oldCur, newCur);
2189
+ }
2190
+ }
2191
+
2192
+ function recordLastCharacterSearch(increment, args) {
2193
+ vimGlobalState.lastChararacterSearch.increment = increment;
2194
+ vimGlobalState.lastChararacterSearch.forward = args.forward;
2195
+ vimGlobalState.lastChararacterSearch.selectedCharacter = args.selectedCharacter;
2196
+ }
2197
+
2198
+ var symbolToMode = {
2199
+ '(': 'bracket', ')': 'bracket', '{': 'bracket', '}': 'bracket',
2200
+ '[': 'section', ']': 'section',
2201
+ '*': 'comment', '/': 'comment',
2202
+ 'm': 'method', 'M': 'method',
2203
+ '#': 'preprocess'
2204
+ };
2205
+ var findSymbolModes = {
2206
+ bracket: {
2207
+ isComplete: function(state) {
2208
+ if (state.nextCh === state.symb) {
2209
+ state.depth++;
2210
+ if(state.depth >= 1)return true;
2211
+ } else if (state.nextCh === state.reverseSymb) {
2212
+ state.depth--;
2213
+ }
2214
+ return false;
2215
+ }
2216
+ },
2217
+ section: {
2218
+ init: function(state) {
2219
+ state.curMoveThrough = true;
2220
+ state.symb = (state.forward ? ']' : '[') === state.symb ? '{' : '}';
2221
+ },
2222
+ isComplete: function(state) {
2223
+ return state.index === 0 && state.nextCh === state.symb;
2224
+ }
2225
+ },
2226
+ comment: {
2227
+ isComplete: function(state) {
2228
+ var found = state.lastCh === '*' && state.nextCh === '/';
2229
+ state.lastCh = state.nextCh;
2230
+ return found;
2231
+ }
2232
+ },
2233
+ // TODO: The original Vim implementation only operates on level 1 and 2.
2234
+ // The current implementation doesn't check for code block level and
2235
+ // therefore it operates on any levels.
2236
+ method: {
2237
+ init: function(state) {
2238
+ state.symb = (state.symb === 'm' ? '{' : '}');
2239
+ state.reverseSymb = state.symb === '{' ? '}' : '{';
2240
+ },
2241
+ isComplete: function(state) {
2242
+ if(state.nextCh === state.symb)return true;
2243
+ return false;
2244
+ }
2245
+ },
2246
+ preprocess: {
2247
+ init: function(state) {
2248
+ state.index = 0;
2249
+ },
2250
+ isComplete: function(state) {
2251
+ if (state.nextCh === '#') {
2252
+ var token = state.lineText.match(/#(\w+)/)[1];
2253
+ if (token === 'endif') {
2254
+ if (state.forward && state.depth === 0) {
2255
+ return true;
2256
+ }
2257
+ state.depth++;
2258
+ } else if (token === 'if') {
2259
+ if (!state.forward && state.depth === 0) {
2260
+ return true;
2261
+ }
2262
+ state.depth--;
2263
+ }
2264
+ if(token === 'else' && state.depth === 0)return true;
2265
+ }
2266
+ return false;
2267
+ }
2268
+ }
2269
+ };
2270
+ function findSymbol(cm, repeat, forward, symb) {
2271
+ var cur = cm.getCursor();
2272
+ var increment = forward ? 1 : -1;
2273
+ var endLine = forward ? cm.lineCount() : -1;
2274
+ var curCh = cur.ch;
2275
+ var line = cur.line;
2276
+ var lineText = cm.getLine(line);
2277
+ var state = {
2278
+ lineText: lineText,
2279
+ nextCh: lineText.charAt(curCh),
2280
+ lastCh: null,
2281
+ index: curCh,
2282
+ symb: symb,
2283
+ reverseSymb: (forward ? { ')': '(', '}': '{' } : { '(': ')', '{': '}' })[symb],
2284
+ forward: forward,
2285
+ depth: 0,
2286
+ curMoveThrough: false
2287
+ };
2288
+ var mode = symbolToMode[symb];
2289
+ if(!mode)return cur;
2290
+ var init = findSymbolModes[mode].init;
2291
+ var isComplete = findSymbolModes[mode].isComplete;
2292
+ if(init)init(state);
2293
+ while (line !== endLine && repeat) {
2294
+ state.index += increment;
2295
+ state.nextCh = state.lineText.charAt(state.index);
2296
+ if (!state.nextCh) {
2297
+ line += increment;
2298
+ state.lineText = cm.getLine(line) || '';
2299
+ if (increment > 0) {
2300
+ state.index = 0;
2301
+ } else {
2302
+ var lineLen = state.lineText.length;
2303
+ state.index = (lineLen > 0) ? (lineLen-1) : 0;
2304
+ }
2305
+ state.nextCh = state.lineText.charAt(state.index);
2306
+ }
2307
+ if (isComplete(state)) {
2308
+ cur.line = line;
2309
+ cur.ch = state.index;
2310
+ repeat--;
2311
+ }
2312
+ }
2313
+ if (state.nextCh || state.curMoveThrough) {
2314
+ return { line: line, ch: state.index };
2315
+ }
2316
+ return cur;
2317
+ }
2318
+
2319
+ /*
2320
+ * Returns the boundaries of the next word. If the cursor in the middle of
2321
+ * the word, then returns the boundaries of the current word, starting at
2322
+ * the cursor. If the cursor is at the start/end of a word, and we are going
2323
+ * forward/backward, respectively, find the boundaries of the next word.
2324
+ *
2325
+ * @param {CodeMirror} cm CodeMirror object.
2326
+ * @param {Cursor} cur The cursor position.
2327
+ * @param {boolean} forward True to search forward. False to search
2328
+ * backward.
2329
+ * @param {boolean} bigWord True if punctuation count as part of the word.
2330
+ * False if only [a-zA-Z0-9] characters count as part of the word.
2331
+ * @param {boolean} emptyLineIsWord True if empty lines should be treated
2332
+ * as words.
2333
+ * @return {Object{from:number, to:number, line: number}} The boundaries of
2334
+ * the word, or null if there are no more words.
2335
+ */
2336
+ function findWord(cm, cur, forward, bigWord, emptyLineIsWord) {
2337
+ var lineNum = cur.line;
2338
+ var pos = cur.ch;
2339
+ var line = cm.getLine(lineNum);
2340
+ var dir = forward ? 1 : -1;
2341
+ var regexps = bigWord ? bigWordRegexp : wordRegexp;
2342
+
2343
+ if (emptyLineIsWord && line == '') {
2344
+ lineNum += dir;
2345
+ line = cm.getLine(lineNum);
2346
+ if (!isLine(cm, lineNum)) {
2347
+ return null;
2348
+ }
2349
+ pos = (forward) ? 0 : line.length;
2350
+ }
2351
+
2352
+ while (true) {
2353
+ if (emptyLineIsWord && line == '') {
2354
+ return { from: 0, to: 0, line: lineNum };
2355
+ }
2356
+ var stop = (dir > 0) ? line.length : -1;
2357
+ var wordStart = stop, wordEnd = stop;
2358
+ // Find bounds of next word.
2359
+ while (pos != stop) {
2360
+ var foundWord = false;
2361
+ for (var i = 0; i < regexps.length && !foundWord; ++i) {
2362
+ if (regexps[i].test(line.charAt(pos))) {
2363
+ wordStart = pos;
2364
+ // Advance to end of word.
2365
+ while (pos != stop && regexps[i].test(line.charAt(pos))) {
2366
+ pos += dir;
2367
+ }
2368
+ wordEnd = pos;
2369
+ foundWord = wordStart != wordEnd;
2370
+ if (wordStart == cur.ch && lineNum == cur.line &&
2371
+ wordEnd == wordStart + dir) {
2372
+ // We started at the end of a word. Find the next one.
2373
+ continue;
2374
+ } else {
2375
+ return {
2376
+ from: Math.min(wordStart, wordEnd + 1),
2377
+ to: Math.max(wordStart, wordEnd),
2378
+ line: lineNum };
2379
+ }
2380
+ }
2381
+ }
2382
+ if (!foundWord) {
2383
+ pos += dir;
2384
+ }
2385
+ }
2386
+ // Advance to next/prev line.
2387
+ lineNum += dir;
2388
+ if (!isLine(cm, lineNum)) {
2389
+ return null;
2390
+ }
2391
+ line = cm.getLine(lineNum);
2392
+ pos = (dir > 0) ? 0 : line.length;
2393
+ }
2394
+ // Should never get here.
2395
+ throw new Error('The impossible happened.');
2396
+ }
2397
+
2398
+ /**
2399
+ * @param {CodeMirror} cm CodeMirror object.
2400
+ * @param {int} repeat Number of words to move past.
2401
+ * @param {boolean} forward True to search forward. False to search
2402
+ * backward.
2403
+ * @param {boolean} wordEnd True to move to end of word. False to move to
2404
+ * beginning of word.
2405
+ * @param {boolean} bigWord True if punctuation count as part of the word.
2406
+ * False if only alphabet characters count as part of the word.
2407
+ * @return {Cursor} The position the cursor should move to.
2408
+ */
2409
+ function moveToWord(cm, repeat, forward, wordEnd, bigWord) {
2410
+ var cur = cm.getCursor();
2411
+ var curStart = copyCursor(cur);
2412
+ var words = [];
2413
+ if (forward && !wordEnd || !forward && wordEnd) {
2414
+ repeat++;
2415
+ }
2416
+ // For 'e', empty lines are not considered words, go figure.
2417
+ var emptyLineIsWord = !(forward && wordEnd);
2418
+ for (var i = 0; i < repeat; i++) {
2419
+ var word = findWord(cm, cur, forward, bigWord, emptyLineIsWord);
2420
+ if (!word) {
2421
+ var eodCh = lineLength(cm, cm.lastLine());
2422
+ words.push(forward
2423
+ ? {line: cm.lastLine(), from: eodCh, to: eodCh}
2424
+ : {line: 0, from: 0, to: 0});
2425
+ break;
2426
+ }
2427
+ words.push(word);
2428
+ cur = {line: word.line, ch: forward ? (word.to - 1) : word.from};
2429
+ }
2430
+ var shortCircuit = words.length != repeat;
2431
+ var firstWord = words[0];
2432
+ var lastWord = words.pop();
2433
+ if (forward && !wordEnd) {
2434
+ // w
2435
+ if (!shortCircuit && (firstWord.from != curStart.ch || firstWord.line != curStart.line)) {
2436
+ // We did not start in the middle of a word. Discard the extra word at the end.
2437
+ lastWord = words.pop();
2438
+ }
2439
+ return {line: lastWord.line, ch: lastWord.from};
2440
+ } else if (forward && wordEnd) {
2441
+ return {line: lastWord.line, ch: lastWord.to - 1};
2442
+ } else if (!forward && wordEnd) {
2443
+ // ge
2444
+ if (!shortCircuit && (firstWord.to != curStart.ch || firstWord.line != curStart.line)) {
2445
+ // We did not start in the middle of a word. Discard the extra word at the end.
2446
+ lastWord = words.pop();
2447
+ }
2448
+ return {line: lastWord.line, ch: lastWord.to};
2449
+ } else {
2450
+ // b
2451
+ return {line: lastWord.line, ch: lastWord.from};
2452
+ }
2453
+ }
2454
+
2455
+ function moveToCharacter(cm, repeat, forward, character) {
2456
+ var cur = cm.getCursor();
2457
+ var start = cur.ch;
2458
+ var idx;
2459
+ for (var i = 0; i < repeat; i ++) {
2460
+ var line = cm.getLine(cur.line);
2461
+ idx = charIdxInLine(start, line, character, forward, true);
2462
+ if (idx == -1) {
2463
+ return null;
2464
+ }
2465
+ start = idx;
2466
+ }
2467
+ return { line: cm.getCursor().line, ch: idx };
2468
+ }
2469
+
2470
+ function moveToColumn(cm, repeat) {
2471
+ // repeat is always >= 1, so repeat - 1 always corresponds
2472
+ // to the column we want to go to.
2473
+ var line = cm.getCursor().line;
2474
+ return clipCursorToContent(cm, { line: line, ch: repeat - 1 });
2475
+ }
2476
+
2477
+ function updateMark(cm, vim, markName, pos) {
2478
+ if (!inArray(markName, validMarks)) {
2479
+ return;
2480
+ }
2481
+ if (vim.marks[markName]) {
2482
+ vim.marks[markName].clear();
2483
+ }
2484
+ vim.marks[markName] = cm.setBookmark(pos);
2485
+ }
2486
+
2487
+ function charIdxInLine(start, line, character, forward, includeChar) {
2488
+ // Search for char in line.
2489
+ // motion_options: {forward, includeChar}
2490
+ // If includeChar = true, include it too.
2491
+ // If forward = true, search forward, else search backwards.
2492
+ // If char is not found on this line, do nothing
2493
+ var idx;
2494
+ if (forward) {
2495
+ idx = line.indexOf(character, start + 1);
2496
+ if (idx != -1 && !includeChar) {
2497
+ idx -= 1;
2498
+ }
2499
+ } else {
2500
+ idx = line.lastIndexOf(character, start - 1);
2501
+ if (idx != -1 && !includeChar) {
2502
+ idx += 1;
2503
+ }
2504
+ }
2505
+ return idx;
2506
+ }
2507
+
2508
+ function getContextLevel(ctx) {
2509
+ return (ctx === 'string' || ctx === 'comment') ? 1 : 0;
2510
+ }
2511
+
2512
+ function findMatchedSymbol(cm, cur, symb) {
2513
+ var line = cur.line;
2514
+ var ch = cur.ch;
2515
+ symb = symb ? symb : cm.getLine(line).charAt(ch);
2516
+
2517
+ var symbContext = cm.getTokenAt({line:line, ch:ch+1}).type;
2518
+ var symbCtxLevel = getContextLevel(symbContext);
2519
+
2520
+ var reverseSymb = ({
2521
+ '(': ')', ')': '(',
2522
+ '[': ']', ']': '[',
2523
+ '{': '}', '}': '{'})[symb];
2524
+
2525
+ // Couldn't find a matching symbol, abort
2526
+ if (!reverseSymb) {
2527
+ return cur;
2528
+ }
2529
+
2530
+ // set our increment to move forward (+1) or backwards (-1)
2531
+ // depending on which bracket we're matching
2532
+ var increment = ({'(': 1, '{': 1, '[': 1})[symb] || -1;
2533
+ var endLine = increment === 1 ? cm.lineCount() : -1;
2534
+ var depth = 1, nextCh = symb, index = ch, lineText = cm.getLine(line);
2535
+ // Simple search for closing paren--just count openings and closings till
2536
+ // we find our match
2537
+ // TODO: use info from CodeMirror to ignore closing brackets in comments
2538
+ // and quotes, etc.
2539
+ while (line !== endLine && depth > 0) {
2540
+ index += increment;
2541
+ nextCh = lineText.charAt(index);
2542
+ if (!nextCh) {
2543
+ line += increment;
2544
+ lineText = cm.getLine(line) || '';
2545
+ if (increment > 0) {
2546
+ index = 0;
2547
+ } else {
2548
+ var lineLen = lineText.length;
2549
+ index = (lineLen > 0) ? (lineLen-1) : 0;
2550
+ }
2551
+ nextCh = lineText.charAt(index);
2552
+ }
2553
+ var revSymbContext = cm.getTokenAt({line:line, ch:index+1}).type;
2554
+ var revSymbCtxLevel = getContextLevel(revSymbContext);
2555
+ if (symbCtxLevel >= revSymbCtxLevel) {
2556
+ if (nextCh === symb) {
2557
+ depth++;
2558
+ } else if (nextCh === reverseSymb) {
2559
+ depth--;
2560
+ }
2561
+ }
2562
+ }
2563
+
2564
+ if (nextCh) {
2565
+ return { line: line, ch: index };
2566
+ }
2567
+ return cur;
2568
+ }
2569
+
2570
+ function selectCompanionObject(cm, revSymb, inclusive) {
2571
+ var cur = cm.getCursor();
2572
+
2573
+ var end = findMatchedSymbol(cm, cur, revSymb);
2574
+ var start = findMatchedSymbol(cm, end);
2575
+ start.ch += inclusive ? 1 : 0;
2576
+ end.ch += inclusive ? 0 : 1;
2577
+
2578
+ return { start: start, end: end };
2579
+ }
2580
+
2581
+ // Takes in a symbol and a cursor and tries to simulate text objects that
2582
+ // have identical opening and closing symbols
2583
+ // TODO support across multiple lines
2584
+ function findBeginningAndEnd(cm, symb, inclusive) {
2585
+ var cur = cm.getCursor();
2586
+ var line = cm.getLine(cur.line);
2587
+ var chars = line.split('');
2588
+ var start, end, i, len;
2589
+ var firstIndex = chars.indexOf(symb);
2590
+
2591
+ // the decision tree is to always look backwards for the beginning first,
2592
+ // but if the cursor is in front of the first instance of the symb,
2593
+ // then move the cursor forward
2594
+ if (cur.ch < firstIndex) {
2595
+ cur.ch = firstIndex;
2596
+ // Why is this line even here???
2597
+ // cm.setCursor(cur.line, firstIndex+1);
2598
+ }
2599
+ // otherwise if the cursor is currently on the closing symbol
2600
+ else if (firstIndex < cur.ch && chars[cur.ch] == symb) {
2601
+ end = cur.ch; // assign end to the current cursor
2602
+ --cur.ch; // make sure to look backwards
2603
+ }
2604
+
2605
+ // if we're currently on the symbol, we've got a start
2606
+ if (chars[cur.ch] == symb && !end) {
2607
+ start = cur.ch + 1; // assign start to ahead of the cursor
2608
+ } else {
2609
+ // go backwards to find the start
2610
+ for (i = cur.ch; i > -1 && !start; i--) {
2611
+ if (chars[i] == symb) {
2612
+ start = i + 1;
2613
+ }
2614
+ }
2615
+ }
2616
+
2617
+ // look forwards for the end symbol
2618
+ if (start && !end) {
2619
+ for (i = start, len = chars.length; i < len && !end; i++) {
2620
+ if (chars[i] == symb) {
2621
+ end = i;
2622
+ }
2623
+ }
2624
+ }
2625
+
2626
+ // nothing found
2627
+ if (!start || !end) {
2628
+ return { start: cur, end: cur };
2629
+ }
2630
+
2631
+ // include the symbols
2632
+ if (inclusive) {
2633
+ --start; ++end;
2634
+ }
2635
+
2636
+ return {
2637
+ start: { line: cur.line, ch: start },
2638
+ end: { line: cur.line, ch: end }
2639
+ };
2640
+ }
2641
+
2642
+ // Search functions
2643
+ function SearchState() {}
2644
+ SearchState.prototype = {
2645
+ getQuery: function() {
2646
+ return vimGlobalState.query;
2647
+ },
2648
+ setQuery: function(query) {
2649
+ vimGlobalState.query = query;
2650
+ },
2651
+ getOverlay: function() {
2652
+ return this.searchOverlay;
2653
+ },
2654
+ setOverlay: function(overlay) {
2655
+ this.searchOverlay = overlay;
2656
+ },
2657
+ isReversed: function() {
2658
+ return vimGlobalState.isReversed;
2659
+ },
2660
+ setReversed: function(reversed) {
2661
+ vimGlobalState.isReversed = reversed;
2662
+ }
2663
+ };
2664
+ function getSearchState(cm) {
2665
+ var vim = cm.state.vim;
2666
+ return vim.searchState_ || (vim.searchState_ = new SearchState());
2667
+ }
2668
+ function dialog(cm, template, shortText, onClose, options) {
2669
+ if (cm.openDialog) {
2670
+ cm.openDialog(template, onClose, { bottom: true, value: options.value,
2671
+ onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp });
2672
+ }
2673
+ else {
2674
+ onClose(prompt(shortText, ''));
2675
+ }
2676
+ }
2677
+
2678
+ function findUnescapedSlashes(str) {
2679
+ var escapeNextChar = false;
2680
+ var slashes = [];
2681
+ for (var i = 0; i < str.length; i++) {
2682
+ var c = str.charAt(i);
2683
+ if (!escapeNextChar && c == '/') {
2684
+ slashes.push(i);
2685
+ }
2686
+ escapeNextChar = (c == '\\');
2687
+ }
2688
+ return slashes;
2689
+ }
2690
+ /**
2691
+ * Extract the regular expression from the query and return a Regexp object.
2692
+ * Returns null if the query is blank.
2693
+ * If ignoreCase is passed in, the Regexp object will have the 'i' flag set.
2694
+ * If smartCase is passed in, and the query contains upper case letters,
2695
+ * then ignoreCase is overridden, and the 'i' flag will not be set.
2696
+ * If the query contains the /i in the flag part of the regular expression,
2697
+ * then both ignoreCase and smartCase are ignored, and 'i' will be passed
2698
+ * through to the Regex object.
2699
+ */
2700
+ function parseQuery(query, ignoreCase, smartCase) {
2701
+ // Check if the query is already a regex.
2702
+ if (query instanceof RegExp) { return query; }
2703
+ // First try to extract regex + flags from the input. If no flags found,
2704
+ // extract just the regex. IE does not accept flags directly defined in
2705
+ // the regex string in the form /regex/flags
2706
+ var slashes = findUnescapedSlashes(query);
2707
+ var regexPart;
2708
+ var forceIgnoreCase;
2709
+ if (!slashes.length) {
2710
+ // Query looks like 'regexp'
2711
+ regexPart = query;
2712
+ } else {
2713
+ // Query looks like 'regexp/...'
2714
+ regexPart = query.substring(0, slashes[0]);
2715
+ var flagsPart = query.substring(slashes[0]);
2716
+ forceIgnoreCase = (flagsPart.indexOf('i') != -1);
2717
+ }
2718
+ if (!regexPart) {
2719
+ return null;
2720
+ }
2721
+ if (smartCase) {
2722
+ ignoreCase = (/^[^A-Z]*$/).test(regexPart);
2723
+ }
2724
+ var regexp = new RegExp(regexPart,
2725
+ (ignoreCase || forceIgnoreCase) ? 'i' : undefined);
2726
+ return regexp;
2727
+ }
2728
+ function showConfirm(cm, text) {
2729
+ if (cm.openConfirm) {
2730
+ cm.openConfirm('<span style="color: red">' + text +
2731
+ '</span> <button type="button">OK</button>', function() {},
2732
+ {bottom: true});
2733
+ } else {
2734
+ alert(text);
2735
+ }
2736
+ }
2737
+ function makePrompt(prefix, desc) {
2738
+ var raw = '';
2739
+ if (prefix) {
2740
+ raw += '<span style="font-family: monospace">' + prefix + '</span>';
2741
+ }
2742
+ raw += '<input type="text"/> ' +
2743
+ '<span style="color: #888">';
2744
+ if (desc) {
2745
+ raw += '<span style="color: #888">';
2746
+ raw += desc;
2747
+ raw += '</span>';
2748
+ }
2749
+ return raw;
2750
+ }
2751
+ var searchPromptDesc = '(Javascript regexp)';
2752
+ function showPrompt(cm, options) {
2753
+ var shortText = (options.prefix || '') + ' ' + (options.desc || '');
2754
+ var prompt = makePrompt(options.prefix, options.desc);
2755
+ dialog(cm, prompt, shortText, options.onClose, options);
2756
+ }
2757
+ function regexEqual(r1, r2) {
2758
+ if (r1 instanceof RegExp && r2 instanceof RegExp) {
2759
+ var props = ['global', 'multiline', 'ignoreCase', 'source'];
2760
+ for (var i = 0; i < props.length; i++) {
2761
+ var prop = props[i];
2762
+ if (r1[prop] !== r2[prop]) {
2763
+ return false;
2764
+ }
2765
+ }
2766
+ return true;
2767
+ }
2768
+ return false;
2769
+ }
2770
+ // Returns true if the query is valid.
2771
+ function updateSearchQuery(cm, rawQuery, ignoreCase, smartCase) {
2772
+ if (!rawQuery) {
2773
+ return;
2774
+ }
2775
+ var state = getSearchState(cm);
2776
+ var query = parseQuery(rawQuery, !!ignoreCase, !!smartCase);
2777
+ if (!query) {
2778
+ return;
2779
+ }
2780
+ highlightSearchMatches(cm, query);
2781
+ if (regexEqual(query, state.getQuery())) {
2782
+ return query;
2783
+ }
2784
+ state.setQuery(query);
2785
+ return query;
2786
+ }
2787
+ function searchOverlay(query) {
2788
+ if (query.source.charAt(0) == '^') {
2789
+ var matchSol = true;
2790
+ }
2791
+ return {
2792
+ token: function(stream) {
2793
+ if (matchSol && !stream.sol()) {
2794
+ stream.skipToEnd();
2795
+ return;
2796
+ }
2797
+ var match = stream.match(query, false);
2798
+ if (match) {
2799
+ if (match[0].length == 0) {
2800
+ // Matched empty string, skip to next.
2801
+ stream.next();
2802
+ return 'searching';
2803
+ }
2804
+ if (!stream.sol()) {
2805
+ // Backtrack 1 to match \b
2806
+ stream.backUp(1);
2807
+ if (!query.exec(stream.next() + match[0])) {
2808
+ stream.next();
2809
+ return null;
2810
+ }
2811
+ }
2812
+ stream.match(query);
2813
+ return 'searching';
2814
+ }
2815
+ while (!stream.eol()) {
2816
+ stream.next();
2817
+ if (stream.match(query, false)) break;
2818
+ }
2819
+ },
2820
+ query: query
2821
+ };
2822
+ }
2823
+ function highlightSearchMatches(cm, query) {
2824
+ var overlay = getSearchState(cm).getOverlay();
2825
+ if (!overlay || query != overlay.query) {
2826
+ if (overlay) {
2827
+ cm.removeOverlay(overlay);
2828
+ }
2829
+ overlay = searchOverlay(query);
2830
+ cm.addOverlay(overlay);
2831
+ getSearchState(cm).setOverlay(overlay);
2832
+ }
2833
+ }
2834
+ function findNext(cm, prev, query, repeat) {
2835
+ if (repeat === undefined) { repeat = 1; }
2836
+ return cm.operation(function() {
2837
+ var pos = cm.getCursor();
2838
+ var cursor = cm.getSearchCursor(query, pos);
2839
+ for (var i = 0; i < repeat; i++) {
2840
+ var found = cursor.find(prev);
2841
+ if (i == 0 && found && cursorEqual(cursor.from(), pos)) { found = cursor.find(prev); }
2842
+ if (!found) {
2843
+ // SearchCursor may have returned null because it hit EOF, wrap
2844
+ // around and try again.
2845
+ cursor = cm.getSearchCursor(query,
2846
+ (prev) ? { line: cm.lastLine() } : {line: cm.firstLine(), ch: 0} );
2847
+ if (!cursor.find(prev)) {
2848
+ return;
2849
+ }
2850
+ }
2851
+ }
2852
+ return cursor.from();
2853
+ });
2854
+ }
2855
+ function clearSearchHighlight(cm) {
2856
+ cm.removeOverlay(getSearchState(cm).getOverlay());
2857
+ getSearchState(cm).setOverlay(null);
2858
+ }
2859
+ /**
2860
+ * Check if pos is in the specified range, INCLUSIVE.
2861
+ * Range can be specified with 1 or 2 arguments.
2862
+ * If the first range argument is an array, treat it as an array of line
2863
+ * numbers. Match pos against any of the lines.
2864
+ * If the first range argument is a number,
2865
+ * if there is only 1 range argument, check if pos has the same line
2866
+ * number
2867
+ * if there are 2 range arguments, then check if pos is in between the two
2868
+ * range arguments.
2869
+ */
2870
+ function isInRange(pos, start, end) {
2871
+ if (typeof pos != 'number') {
2872
+ // Assume it is a cursor position. Get the line number.
2873
+ pos = pos.line;
2874
+ }
2875
+ if (start instanceof Array) {
2876
+ return inArray(pos, start);
2877
+ } else {
2878
+ if (end) {
2879
+ return (pos >= start && pos <= end);
2880
+ } else {
2881
+ return pos == start;
2882
+ }
2883
+ }
2884
+ }
2885
+ function getUserVisibleLines(cm) {
2886
+ var scrollInfo = cm.getScrollInfo();
2887
+ var occludeToleranceTop = 6;
2888
+ var occludeToleranceBottom = 10;
2889
+ var from = cm.coordsChar({left:0, top: occludeToleranceTop + scrollInfo.top}, 'local');
2890
+ var bottomY = scrollInfo.clientHeight - occludeToleranceBottom + scrollInfo.top;
2891
+ var to = cm.coordsChar({left:0, top: bottomY}, 'local');
2892
+ return {top: from.line, bottom: to.line};
2893
+ }
2894
+
2895
+ // Ex command handling
2896
+ // Care must be taken when adding to the default Ex command map. For any
2897
+ // pair of commands that have a shared prefix, at least one of their
2898
+ // shortNames must not match the prefix of the other command.
2899
+ var defaultExCommandMap = [
2900
+ { name: 'map', type: 'builtIn' },
2901
+ { name: 'write', shortName: 'w', type: 'builtIn' },
2902
+ { name: 'undo', shortName: 'u', type: 'builtIn' },
2903
+ { name: 'redo', shortName: 'red', type: 'builtIn' },
2904
+ { name: 'sort', shortName: 'sor', type: 'builtIn'},
2905
+ { name: 'substitute', shortName: 's', type: 'builtIn'},
2906
+ { name: 'nohlsearch', shortName: 'noh', type: 'builtIn'},
2907
+ { name: 'delmarks', shortName: 'delm', type: 'builtin'}
2908
+ ];
2909
+ Vim.ExCommandDispatcher = function() {
2910
+ this.buildCommandMap_();
2911
+ };
2912
+ Vim.ExCommandDispatcher.prototype = {
2913
+ processCommand: function(cm, input) {
2914
+ var vim = cm.state.vim;
2915
+ if (vim.visualMode) {
2916
+ exitVisualMode(cm);
2917
+ }
2918
+ var inputStream = new CodeMirror.StringStream(input);
2919
+ var params = {};
2920
+ params.input = input;
2921
+ try {
2922
+ this.parseInput_(cm, inputStream, params);
2923
+ } catch(e) {
2924
+ showConfirm(cm, e);
2925
+ return;
2926
+ }
2927
+ var commandName;
2928
+ if (!params.commandName) {
2929
+ // If only a line range is defined, move to the line.
2930
+ if (params.line !== undefined) {
2931
+ commandName = 'move';
2932
+ }
2933
+ } else {
2934
+ var command = this.matchCommand_(params.commandName);
2935
+ if (command) {
2936
+ commandName = command.name;
2937
+ this.parseCommandArgs_(inputStream, params, command);
2938
+ if (command.type == 'exToKey') {
2939
+ // Handle Ex to Key mapping.
2940
+ for (var i = 0; i < command.toKeys.length; i++) {
2941
+ CodeMirror.Vim.handleKey(cm, command.toKeys[i]);
2942
+ }
2943
+ return;
2944
+ } else if (command.type == 'exToEx') {
2945
+ // Handle Ex to Ex mapping.
2946
+ this.processCommand(cm, command.toInput);
2947
+ return;
2948
+ }
2949
+ }
2950
+ }
2951
+ if (!commandName) {
2952
+ showConfirm(cm, 'Not an editor command ":' + input + '"');
2953
+ return;
2954
+ }
2955
+ try {
2956
+ exCommands[commandName](cm, params);
2957
+ } catch(e) {
2958
+ showConfirm(cm, e);
2959
+ }
2960
+ },
2961
+ parseInput_: function(cm, inputStream, result) {
2962
+ inputStream.eatWhile(':');
2963
+ // Parse range.
2964
+ if (inputStream.eat('%')) {
2965
+ result.line = cm.firstLine();
2966
+ result.lineEnd = cm.lastLine();
2967
+ } else {
2968
+ result.line = this.parseLineSpec_(cm, inputStream);
2969
+ if (result.line !== undefined && inputStream.eat(',')) {
2970
+ result.lineEnd = this.parseLineSpec_(cm, inputStream);
2971
+ }
2972
+ }
2973
+
2974
+ // Parse command name.
2975
+ var commandMatch = inputStream.match(/^(\w+)/);
2976
+ if (commandMatch) {
2977
+ result.commandName = commandMatch[1];
2978
+ } else {
2979
+ result.commandName = inputStream.match(/.*/)[0];
2980
+ }
2981
+
2982
+ return result;
2983
+ },
2984
+ parseLineSpec_: function(cm, inputStream) {
2985
+ var numberMatch = inputStream.match(/^(\d+)/);
2986
+ if (numberMatch) {
2987
+ return parseInt(numberMatch[1], 10) - 1;
2988
+ }
2989
+ switch (inputStream.next()) {
2990
+ case '.':
2991
+ return cm.getCursor().line;
2992
+ case '$':
2993
+ return cm.lastLine();
2994
+ case '\'':
2995
+ var mark = cm.state.vim.marks[inputStream.next()];
2996
+ if (mark && mark.find()) {
2997
+ return mark.find().line;
2998
+ }
2999
+ throw new Error('Mark not set');
3000
+ default:
3001
+ inputStream.backUp(1);
3002
+ return undefined;
3003
+ }
3004
+ },
3005
+ parseCommandArgs_: function(inputStream, params, command) {
3006
+ if (inputStream.eol()) {
3007
+ return;
3008
+ }
3009
+ params.argString = inputStream.match(/.*/)[0];
3010
+ // Parse command-line arguments
3011
+ var delim = command.argDelimiter || /\s+/;
3012
+ var args = trim(params.argString).split(delim);
3013
+ if (args.length && args[0]) {
3014
+ params.args = args;
3015
+ }
3016
+ },
3017
+ matchCommand_: function(commandName) {
3018
+ // Return the command in the command map that matches the shortest
3019
+ // prefix of the passed in command name. The match is guaranteed to be
3020
+ // unambiguous if the defaultExCommandMap's shortNames are set up
3021
+ // correctly. (see @code{defaultExCommandMap}).
3022
+ for (var i = commandName.length; i > 0; i--) {
3023
+ var prefix = commandName.substring(0, i);
3024
+ if (this.commandMap_[prefix]) {
3025
+ var command = this.commandMap_[prefix];
3026
+ if (command.name.indexOf(commandName) === 0) {
3027
+ return command;
3028
+ }
3029
+ }
3030
+ }
3031
+ return null;
3032
+ },
3033
+ buildCommandMap_: function() {
3034
+ this.commandMap_ = {};
3035
+ for (var i = 0; i < defaultExCommandMap.length; i++) {
3036
+ var command = defaultExCommandMap[i];
3037
+ var key = command.shortName || command.name;
3038
+ this.commandMap_[key] = command;
3039
+ }
3040
+ },
3041
+ map: function(lhs, rhs) {
3042
+ if (lhs != ':' && lhs.charAt(0) == ':') {
3043
+ var commandName = lhs.substring(1);
3044
+ if (rhs != ':' && rhs.charAt(0) == ':') {
3045
+ // Ex to Ex mapping
3046
+ this.commandMap_[commandName] = {
3047
+ name: commandName,
3048
+ type: 'exToEx',
3049
+ toInput: rhs.substring(1)
3050
+ };
3051
+ } else {
3052
+ // Ex to key mapping
3053
+ this.commandMap_[commandName] = {
3054
+ name: commandName,
3055
+ type: 'exToKey',
3056
+ toKeys: parseKeyString(rhs)
3057
+ };
3058
+ }
3059
+ } else {
3060
+ if (rhs != ':' && rhs.charAt(0) == ':') {
3061
+ // Key to Ex mapping.
3062
+ defaultKeymap.unshift({
3063
+ keys: parseKeyString(lhs),
3064
+ type: 'keyToEx',
3065
+ exArgs: { input: rhs.substring(1) }});
3066
+ } else {
3067
+ // Key to key mapping
3068
+ defaultKeymap.unshift({
3069
+ keys: parseKeyString(lhs),
3070
+ type: 'keyToKey',
3071
+ toKeys: parseKeyString(rhs)
3072
+ });
3073
+ }
3074
+ }
3075
+ }
3076
+ };
3077
+
3078
+ // Converts a key string sequence of the form a<C-w>bd<Left> into Vim's
3079
+ // keymap representation.
3080
+ function parseKeyString(str) {
3081
+ var key, match;
3082
+ var keys = [];
3083
+ while (str) {
3084
+ match = (/<\w+-.+?>|<\w+>|./).exec(str);
3085
+ if(match === null)break;
3086
+ key = match[0];
3087
+ str = str.substring(match.index + key.length);
3088
+ keys.push(key);
3089
+ }
3090
+ return keys;
3091
+ }
3092
+
3093
+ var exCommands = {
3094
+ map: function(cm, params) {
3095
+ var mapArgs = params.args;
3096
+ if (!mapArgs || mapArgs.length < 2) {
3097
+ if (cm) {
3098
+ showConfirm(cm, 'Invalid mapping: ' + params.input);
3099
+ }
3100
+ return;
3101
+ }
3102
+ exCommandDispatcher.map(mapArgs[0], mapArgs[1], cm);
3103
+ },
3104
+ move: function(cm, params) {
3105
+ commandDispatcher.processCommand(cm, cm.state.vim, {
3106
+ type: 'motion',
3107
+ motion: 'moveToLineOrEdgeOfDocument',
3108
+ motionArgs: { forward: false, explicitRepeat: true,
3109
+ linewise: true },
3110
+ repeatOverride: params.line+1});
3111
+ },
3112
+ sort: function(cm, params) {
3113
+ var reverse, ignoreCase, unique, number;
3114
+ function parseArgs() {
3115
+ if (params.argString) {
3116
+ var args = new CodeMirror.StringStream(params.argString);
3117
+ if (args.eat('!')) { reverse = true; }
3118
+ if (args.eol()) { return; }
3119
+ if (!args.eatSpace()) { throw new Error('invalid arguments ' + args.match(/.*/)[0]); }
3120
+ var opts = args.match(/[a-z]+/);
3121
+ if (opts) {
3122
+ opts = opts[0];
3123
+ ignoreCase = opts.indexOf('i') != -1;
3124
+ unique = opts.indexOf('u') != -1;
3125
+ var decimal = opts.indexOf('d') != -1 && 1;
3126
+ var hex = opts.indexOf('x') != -1 && 1;
3127
+ var octal = opts.indexOf('o') != -1 && 1;
3128
+ if (decimal + hex + octal > 1) { throw new Error('invalid arguments'); }
3129
+ number = decimal && 'decimal' || hex && 'hex' || octal && 'octal';
3130
+ }
3131
+ if (args.eatSpace() && args.match(/\/.*\//)) { throw new Error('patterns not supported'); }
3132
+ }
3133
+ }
3134
+ parseArgs();
3135
+ var lineStart = params.line || cm.firstLine();
3136
+ var lineEnd = params.lineEnd || params.line || cm.lastLine();
3137
+ if (lineStart == lineEnd) { return; }
3138
+ var curStart = { line: lineStart, ch: 0 };
3139
+ var curEnd = { line: lineEnd, ch: lineLength(cm, lineEnd) };
3140
+ var text = cm.getRange(curStart, curEnd).split('\n');
3141
+ var numberRegex = (number == 'decimal') ? /(-?)([\d]+)/ :
3142
+ (number == 'hex') ? /(-?)(?:0x)?([0-9a-f]+)/i :
3143
+ (number == 'octal') ? /([0-7]+)/ : null;
3144
+ var radix = (number == 'decimal') ? 10 : (number == 'hex') ? 16 : (number == 'octal') ? 8 : null;
3145
+ var numPart = [], textPart = [];
3146
+ if (number) {
3147
+ for (var i = 0; i < text.length; i++) {
3148
+ if (numberRegex.exec(text[i])) {
3149
+ numPart.push(text[i]);
3150
+ } else {
3151
+ textPart.push(text[i]);
3152
+ }
3153
+ }
3154
+ } else {
3155
+ textPart = text;
3156
+ }
3157
+ function compareFn(a, b) {
3158
+ if (reverse) { var tmp; tmp = a; a = b; b = tmp; }
3159
+ if (ignoreCase) { a = a.toLowerCase(); b = b.toLowerCase(); }
3160
+ var anum = number && numberRegex.exec(a);
3161
+ var bnum = number && numberRegex.exec(b);
3162
+ if (!anum) { return a < b ? -1 : 1; }
3163
+ anum = parseInt((anum[1] + anum[2]).toLowerCase(), radix);
3164
+ bnum = parseInt((bnum[1] + bnum[2]).toLowerCase(), radix);
3165
+ return anum - bnum;
3166
+ }
3167
+ numPart.sort(compareFn);
3168
+ textPart.sort(compareFn);
3169
+ text = (!reverse) ? textPart.concat(numPart) : numPart.concat(textPart);
3170
+ if (unique) { // Remove duplicate lines
3171
+ var textOld = text;
3172
+ var lastLine;
3173
+ text = [];
3174
+ for (var i = 0; i < textOld.length; i++) {
3175
+ if (textOld[i] != lastLine) {
3176
+ text.push(textOld[i]);
3177
+ }
3178
+ lastLine = textOld[i];
3179
+ }
3180
+ }
3181
+ cm.replaceRange(text.join('\n'), curStart, curEnd);
3182
+ },
3183
+ substitute: function(cm, params) {
3184
+ if (!cm.getSearchCursor) {
3185
+ throw new Error('Search feature not available. Requires searchcursor.js or ' +
3186
+ 'any other getSearchCursor implementation.');
3187
+ }
3188
+ var argString = params.argString;
3189
+ var slashes = findUnescapedSlashes(argString);
3190
+ if (slashes[0] !== 0) {
3191
+ showConfirm(cm, 'Substitutions should be of the form ' +
3192
+ ':s/pattern/replace/');
3193
+ return;
3194
+ }
3195
+ var regexPart = argString.substring(slashes[0] + 1, slashes[1]);
3196
+ var replacePart = '';
3197
+ var flagsPart;
3198
+ var count;
3199
+ var confirm = false; // Whether to confirm each replace.
3200
+ if (slashes[1]) {
3201
+ replacePart = argString.substring(slashes[1] + 1, slashes[2]);
3202
+ }
3203
+ if (slashes[2]) {
3204
+ // After the 3rd slash, we can have flags followed by a space followed
3205
+ // by count.
3206
+ var trailing = argString.substring(slashes[2] + 1).split(' ');
3207
+ flagsPart = trailing[0];
3208
+ count = parseInt(trailing[1]);
3209
+ }
3210
+ if (flagsPart) {
3211
+ if (flagsPart.indexOf('c') != -1) {
3212
+ confirm = true;
3213
+ flagsPart.replace('c', '');
3214
+ }
3215
+ regexPart = regexPart + '/' + flagsPart;
3216
+ }
3217
+ if (regexPart) {
3218
+ // If regex part is empty, then use the previous query. Otherwise use
3219
+ // the regex part as the new query.
3220
+ try {
3221
+ updateSearchQuery(cm, regexPart, true /** ignoreCase */,
3222
+ true /** smartCase */);
3223
+ } catch (e) {
3224
+ showConfirm(cm, 'Invalid regex: ' + regexPart);
3225
+ return;
3226
+ }
3227
+ }
3228
+ var state = getSearchState(cm);
3229
+ var query = state.getQuery();
3230
+ var lineStart = (params.line !== undefined) ? params.line : cm.getCursor().line;
3231
+ var lineEnd = params.lineEnd || lineStart;
3232
+ if (count) {
3233
+ lineStart = lineEnd;
3234
+ lineEnd = lineStart + count - 1;
3235
+ }
3236
+ var startPos = clipCursorToContent(cm, { line: lineStart, ch: 0 });
3237
+ var cursor = cm.getSearchCursor(query, startPos);
3238
+ doReplace(cm, confirm, lineStart, lineEnd, cursor, query, replacePart);
3239
+ },
3240
+ redo: CodeMirror.commands.redo,
3241
+ undo: CodeMirror.commands.undo,
3242
+ write: function(cm) {
3243
+ if (CodeMirror.commands.save) {
3244
+ // If a save command is defined, call it.
3245
+ CodeMirror.commands.save(cm);
3246
+ } else {
3247
+ // Saves to text area if no save command is defined.
3248
+ cm.save();
3249
+ }
3250
+ },
3251
+ nohlsearch: function(cm) {
3252
+ clearSearchHighlight(cm);
3253
+ },
3254
+ delmarks: function(cm, params) {
3255
+ if (!params.argString || !params.argString.trim()) {
3256
+ showConfirm(cm, 'Argument required');
3257
+ return;
3258
+ }
3259
+
3260
+ var state = cm.state.vim;
3261
+ var stream = new CodeMirror.StringStream(params.argString.trim());
3262
+ while (!stream.eol()) {
3263
+ stream.eatSpace();
3264
+
3265
+ // Record the streams position at the beginning of the loop for use
3266
+ // in error messages.
3267
+ var count = stream.pos;
3268
+
3269
+ if (!stream.match(/[a-zA-Z]/, false)) {
3270
+ showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
3271
+ return;
3272
+ }
3273
+
3274
+ var sym = stream.next();
3275
+ // Check if this symbol is part of a range
3276
+ if (stream.match('-', true)) {
3277
+ // This symbol is part of a range.
3278
+
3279
+ // The range must terminate at an alphabetic character.
3280
+ if (!stream.match(/[a-zA-Z]/, false)) {
3281
+ showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
3282
+ return;
3283
+ }
3284
+
3285
+ var startMark = sym;
3286
+ var finishMark = stream.next();
3287
+ // The range must terminate at an alphabetic character which
3288
+ // shares the same case as the start of the range.
3289
+ if (isLowerCase(startMark) && isLowerCase(finishMark) ||
3290
+ isUpperCase(startMark) && isUpperCase(finishMark)) {
3291
+ var start = startMark.charCodeAt(0);
3292
+ var finish = finishMark.charCodeAt(0);
3293
+ if (start >= finish) {
3294
+ showConfirm(cm, 'Invalid argument: ' + params.argString.substring(count));
3295
+ return;
3296
+ }
3297
+
3298
+ // Because marks are always ASCII values, and we have
3299
+ // determined that they are the same case, we can use
3300
+ // their char codes to iterate through the defined range.
3301
+ for (var j = 0; j <= finish - start; j++) {
3302
+ var mark = String.fromCharCode(start + j);
3303
+ delete state.marks[mark];
3304
+ }
3305
+ } else {
3306
+ showConfirm(cm, 'Invalid argument: ' + startMark + '-');
3307
+ return;
3308
+ }
3309
+ } else {
3310
+ // This symbol is a valid mark, and is not part of a range.
3311
+ delete state.marks[sym];
3312
+ }
3313
+ }
3314
+ }
3315
+ };
3316
+
3317
+ var exCommandDispatcher = new Vim.ExCommandDispatcher();
3318
+
3319
+ /**
3320
+ * @param {CodeMirror} cm CodeMirror instance we are in.
3321
+ * @param {boolean} confirm Whether to confirm each replace.
3322
+ * @param {Cursor} lineStart Line to start replacing from.
3323
+ * @param {Cursor} lineEnd Line to stop replacing at.
3324
+ * @param {RegExp} query Query for performing matches with.
3325
+ * @param {string} replaceWith Text to replace matches with. May contain $1,
3326
+ * $2, etc for replacing captured groups using Javascript replace.
3327
+ */
3328
+ function doReplace(cm, confirm, lineStart, lineEnd, searchCursor, query,
3329
+ replaceWith) {
3330
+ // Set up all the functions.
3331
+ cm.state.vim.exMode = true;
3332
+ var done = false;
3333
+ var lastPos = searchCursor.from();
3334
+ function replaceAll() {
3335
+ cm.operation(function() {
3336
+ while (!done) {
3337
+ replace();
3338
+ next();
3339
+ }
3340
+ stop();
3341
+ });
3342
+ }
3343
+ function replace() {
3344
+ var text = cm.getRange(searchCursor.from(), searchCursor.to());
3345
+ var newText = text.replace(query, replaceWith);
3346
+ searchCursor.replace(newText);
3347
+ }
3348
+ function next() {
3349
+ var found = searchCursor.findNext();
3350
+ if (!found) {
3351
+ done = true;
3352
+ } else if (isInRange(searchCursor.from(), lineStart, lineEnd)) {
3353
+ cm.scrollIntoView(searchCursor.from(), 30);
3354
+ cm.setSelection(searchCursor.from(), searchCursor.to());
3355
+ lastPos = searchCursor.from();
3356
+ done = false;
3357
+ } else {
3358
+ done = true;
3359
+ }
3360
+ }
3361
+ function stop(close) {
3362
+ if (close) { close(); }
3363
+ cm.focus();
3364
+ if (lastPos) {
3365
+ cm.setCursor(lastPos);
3366
+ var vim = cm.state.vim;
3367
+ vim.exMode = false;
3368
+ vim.lastHPos = vim.lastHSPos = lastPos.ch;
3369
+ }
3370
+ }
3371
+ function onPromptKeyDown(e, _value, close) {
3372
+ // Swallow all keys.
3373
+ CodeMirror.e_stop(e);
3374
+ var keyName = CodeMirror.keyName(e);
3375
+ switch (keyName) {
3376
+ case 'Y':
3377
+ replace(); next(); break;
3378
+ case 'N':
3379
+ next(); break;
3380
+ case 'A':
3381
+ cm.operation(replaceAll); break;
3382
+ case 'L':
3383
+ replace();
3384
+ // fall through and exit.
3385
+ case 'Q':
3386
+ case 'Esc':
3387
+ case 'Ctrl-C':
3388
+ case 'Ctrl-[':
3389
+ stop(close);
3390
+ break;
3391
+ }
3392
+ if (done) { stop(close); }
3393
+ }
3394
+
3395
+ // Actually do replace.
3396
+ next();
3397
+ if (done) {
3398
+ throw new Error('No matches for ' + query.source);
3399
+ }
3400
+ if (!confirm) {
3401
+ replaceAll();
3402
+ return;
3403
+ }
3404
+ showPrompt(cm, {
3405
+ prefix: 'replace with <strong>' + replaceWith + '</strong> (y/n/a/q/l)',
3406
+ onKeyDown: onPromptKeyDown
3407
+ });
3408
+ }
3409
+
3410
+ // Register Vim with CodeMirror
3411
+ function buildVimKeyMap() {
3412
+ /**
3413
+ * Handle the raw key event from CodeMirror. Translate the
3414
+ * Shift + key modifier to the resulting letter, while preserving other
3415
+ * modifers.
3416
+ */
3417
+ // TODO: Figure out a way to catch capslock.
3418
+ function cmKeyToVimKey(key, modifier) {
3419
+ var vimKey = key;
3420
+ if (isUpperCase(vimKey)) {
3421
+ // Convert to lower case if shift is not the modifier since the key
3422
+ // we get from CodeMirror is always upper case.
3423
+ if (modifier == 'Shift') {
3424
+ modifier = null;
3425
+ }
3426
+ else {
3427
+ vimKey = vimKey.toLowerCase();
3428
+ }
3429
+ }
3430
+ if (modifier) {
3431
+ // Vim will parse modifier+key combination as a single key.
3432
+ vimKey = modifier.charAt(0) + '-' + vimKey;
3433
+ }
3434
+ var specialKey = ({Enter:'CR',Backspace:'BS',Delete:'Del'})[vimKey];
3435
+ vimKey = specialKey ? specialKey : vimKey;
3436
+ vimKey = vimKey.length > 1 ? '<'+ vimKey + '>' : vimKey;
3437
+ return vimKey;
3438
+ }
3439
+
3440
+ // Closure to bind CodeMirror, key, modifier.
3441
+ function keyMapper(vimKey) {
3442
+ return function(cm) {
3443
+ CodeMirror.Vim.handleKey(cm, vimKey);
3444
+ };
3445
+ }
3446
+
3447
+ var cmToVimKeymap = {
3448
+ 'nofallthrough': true,
3449
+ 'disableInput': true,
3450
+ 'style': 'fat-cursor'
3451
+ };
3452
+ function bindKeys(keys, modifier) {
3453
+ for (var i = 0; i < keys.length; i++) {
3454
+ var key = keys[i];
3455
+ if (!modifier && inArray(key, specialSymbols)) {
3456
+ // Wrap special symbols with '' because that's how CodeMirror binds
3457
+ // them.
3458
+ key = "'" + key + "'";
3459
+ }
3460
+ var vimKey = cmKeyToVimKey(keys[i], modifier);
3461
+ var cmKey = modifier ? modifier + '-' + key : key;
3462
+ cmToVimKeymap[cmKey] = keyMapper(vimKey);
3463
+ }
3464
+ }
3465
+ bindKeys(upperCaseAlphabet);
3466
+ bindKeys(upperCaseAlphabet, 'Shift');
3467
+ bindKeys(upperCaseAlphabet, 'Ctrl');
3468
+ bindKeys(specialSymbols);
3469
+ bindKeys(specialSymbols, 'Ctrl');
3470
+ bindKeys(numbers);
3471
+ bindKeys(numbers, 'Ctrl');
3472
+ bindKeys(specialKeys);
3473
+ bindKeys(specialKeys, 'Ctrl');
3474
+ return cmToVimKeymap;
3475
+ }
3476
+ CodeMirror.keyMap.vim = buildVimKeyMap();
3477
+
3478
+ function exitInsertMode(cm) {
3479
+ var vim = cm.state.vim;
3480
+ var inReplay = vimGlobalState.macroModeState.inReplay;
3481
+ if (!inReplay) {
3482
+ cm.off('change', onChange);
3483
+ cm.off('cursorActivity', onCursorActivity);
3484
+ CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown);
3485
+ }
3486
+ if (!inReplay && vim.insertModeRepeat > 1) {
3487
+ // Perform insert mode repeat for commands like 3,a and 3,o.
3488
+ repeatLastEdit(cm, vim, vim.insertModeRepeat - 1,
3489
+ true /** repeatForInsert */);
3490
+ vim.lastEditInputState.repeatOverride = vim.insertModeRepeat;
3491
+ }
3492
+ delete vim.insertModeRepeat;
3493
+ cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1, true);
3494
+ vim.insertMode = false;
3495
+ cm.setOption('keyMap', 'vim');
3496
+ cm.toggleOverwrite(false); // exit replace mode if we were in it.
3497
+ CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"});
3498
+ }
3499
+
3500
+ CodeMirror.keyMap['vim-insert'] = {
3501
+ // TODO: override navigation keys so that Esc will cancel automatic
3502
+ // indentation from o, O, i_<CR>
3503
+ 'Esc': exitInsertMode,
3504
+ 'Ctrl-[': exitInsertMode,
3505
+ 'Ctrl-C': exitInsertMode,
3506
+ 'Ctrl-N': 'autocomplete',
3507
+ 'Ctrl-P': 'autocomplete',
3508
+ 'Enter': function(cm) {
3509
+ var fn = CodeMirror.commands.newlineAndIndentContinueComment ||
3510
+ CodeMirror.commands.newlineAndIndent;
3511
+ fn(cm);
3512
+ },
3513
+ fallthrough: ['default']
3514
+ };
3515
+
3516
+ CodeMirror.keyMap['vim-replace'] = {
3517
+ 'Backspace': 'goCharLeft',
3518
+ fallthrough: ['vim-insert']
3519
+ };
3520
+
3521
+ function parseRegisterToKeyBuffer(macroModeState, registerName) {
3522
+ var match, key;
3523
+ var register = vimGlobalState.registerController.getRegister(registerName);
3524
+ var text = register.toString();
3525
+ var macroKeyBuffer = macroModeState.macroKeyBuffer;
3526
+ emptyMacroKeyBuffer(macroModeState);
3527
+ do {
3528
+ match = (/<\w+-.+?>|<\w+>|./).exec(text);
3529
+ if(match === null)break;
3530
+ key = match[0];
3531
+ text = text.substring(match.index + key.length);
3532
+ macroKeyBuffer.push(key);
3533
+ } while (text);
3534
+ return macroKeyBuffer;
3535
+ }
3536
+
3537
+ function parseKeyBufferToRegister(registerName, keyBuffer) {
3538
+ var text = keyBuffer.join('');
3539
+ vimGlobalState.registerController.setRegisterText(registerName, text);
3540
+ }
3541
+
3542
+ function emptyMacroKeyBuffer(macroModeState) {
3543
+ if(macroModeState.isMacroPlaying)return;
3544
+ var macroKeyBuffer = macroModeState.macroKeyBuffer;
3545
+ macroKeyBuffer.length = 0;
3546
+ }
3547
+
3548
+ function executeMacroKeyBuffer(cm, macroModeState, keyBuffer) {
3549
+ macroModeState.isMacroPlaying = true;
3550
+ for (var i = 0, len = keyBuffer.length; i < len; i++) {
3551
+ CodeMirror.Vim.handleKey(cm, keyBuffer[i]);
3552
+ };
3553
+ macroModeState.isMacroPlaying = false;
3554
+ }
3555
+
3556
+ function logKey(macroModeState, key) {
3557
+ if(macroModeState.isMacroPlaying)return;
3558
+ var macroKeyBuffer = macroModeState.macroKeyBuffer;
3559
+ macroKeyBuffer.push(key);
3560
+ }
3561
+
3562
+ /**
3563
+ * Listens for changes made in insert mode.
3564
+ * Should only be active in insert mode.
3565
+ */
3566
+ function onChange(_cm, changeObj) {
3567
+ var macroModeState = vimGlobalState.macroModeState;
3568
+ var lastChange = macroModeState.lastInsertModeChanges;
3569
+ while (changeObj) {
3570
+ lastChange.expectCursorActivityForChange = true;
3571
+ if (changeObj.origin == '+input' || changeObj.origin == 'paste'
3572
+ || changeObj.origin === undefined /* only in testing */) {
3573
+ var text = changeObj.text.join('\n');
3574
+ lastChange.changes.push(text);
3575
+ }
3576
+ // Change objects may be chained with next.
3577
+ changeObj = changeObj.next;
3578
+ }
3579
+ }
3580
+
3581
+ /**
3582
+ * Listens for any kind of cursor activity on CodeMirror.
3583
+ * - For tracking cursor activity in insert mode.
3584
+ * - Should only be active in insert mode.
3585
+ */
3586
+ function onCursorActivity() {
3587
+ var macroModeState = vimGlobalState.macroModeState;
3588
+ var lastChange = macroModeState.lastInsertModeChanges;
3589
+ if (lastChange.expectCursorActivityForChange) {
3590
+ lastChange.expectCursorActivityForChange = false;
3591
+ } else {
3592
+ // Cursor moved outside the context of an edit. Reset the change.
3593
+ lastChange.changes = [];
3594
+ }
3595
+ }
3596
+
3597
+ /** Wrapper for special keys pressed in insert mode */
3598
+ function InsertModeKey(keyName) {
3599
+ this.keyName = keyName;
3600
+ }
3601
+
3602
+ /**
3603
+ * Handles raw key down events from the text area.
3604
+ * - Should only be active in insert mode.
3605
+ * - For recording deletes in insert mode.
3606
+ */
3607
+ function onKeyEventTargetKeyDown(e) {
3608
+ var macroModeState = vimGlobalState.macroModeState;
3609
+ var lastChange = macroModeState.lastInsertModeChanges;
3610
+ var keyName = CodeMirror.keyName(e);
3611
+ function onKeyFound() {
3612
+ lastChange.changes.push(new InsertModeKey(keyName));
3613
+ return true;
3614
+ }
3615
+ if (keyName.indexOf('Delete') != -1 || keyName.indexOf('Backspace') != -1) {
3616
+ CodeMirror.lookupKey(keyName, ['vim-insert'], onKeyFound);
3617
+ }
3618
+ }
3619
+
3620
+ /**
3621
+ * Repeats the last edit, which includes exactly 1 command and at most 1
3622
+ * insert. Operator and motion commands are read from lastEditInputState,
3623
+ * while action commands are read from lastEditActionCommand.
3624
+ *
3625
+ * If repeatForInsert is true, then the function was called by
3626
+ * exitInsertMode to repeat the insert mode changes the user just made. The
3627
+ * corresponding enterInsertMode call was made with a count.
3628
+ */
3629
+ function repeatLastEdit(cm, vim, repeat, repeatForInsert) {
3630
+ var macroModeState = vimGlobalState.macroModeState;
3631
+ macroModeState.inReplay = true;
3632
+ var isAction = !!vim.lastEditActionCommand;
3633
+ var cachedInputState = vim.inputState;
3634
+ function repeatCommand() {
3635
+ if (isAction) {
3636
+ commandDispatcher.processAction(cm, vim, vim.lastEditActionCommand);
3637
+ } else {
3638
+ commandDispatcher.evalInput(cm, vim);
3639
+ }
3640
+ }
3641
+ function repeatInsert(repeat) {
3642
+ if (macroModeState.lastInsertModeChanges.changes.length > 0) {
3643
+ // For some reason, repeat cw in desktop VIM will does not repeat
3644
+ // insert mode changes. Will conform to that behavior.
3645
+ repeat = !vim.lastEditActionCommand ? 1 : repeat;
3646
+ repeatLastInsertModeChanges(cm, repeat, macroModeState);
3647
+ }
3648
+ }
3649
+ vim.inputState = vim.lastEditInputState;
3650
+ if (isAction && vim.lastEditActionCommand.interlaceInsertRepeat) {
3651
+ // o and O repeat have to be interlaced with insert repeats so that the
3652
+ // insertions appear on separate lines instead of the last line.
3653
+ for (var i = 0; i < repeat; i++) {
3654
+ repeatCommand();
3655
+ repeatInsert(1);
3656
+ }
3657
+ } else {
3658
+ if (!repeatForInsert) {
3659
+ // Hack to get the cursor to end up at the right place. If I is
3660
+ // repeated in insert mode repeat, cursor will be 1 insert
3661
+ // change set left of where it should be.
3662
+ repeatCommand();
3663
+ }
3664
+ repeatInsert(repeat);
3665
+ }
3666
+ vim.inputState = cachedInputState;
3667
+ if (vim.insertMode && !repeatForInsert) {
3668
+ // Don't exit insert mode twice. If repeatForInsert is set, then we
3669
+ // were called by an exitInsertMode call lower on the stack.
3670
+ exitInsertMode(cm);
3671
+ }
3672
+ macroModeState.inReplay = false;
3673
+ };
3674
+
3675
+ function repeatLastInsertModeChanges(cm, repeat, macroModeState) {
3676
+ var lastChange = macroModeState.lastInsertModeChanges;
3677
+ function keyHandler(binding) {
3678
+ if (typeof binding == 'string') {
3679
+ CodeMirror.commands[binding](cm);
3680
+ } else {
3681
+ binding(cm);
3682
+ }
3683
+ return true;
3684
+ }
3685
+ for (var i = 0; i < repeat; i++) {
3686
+ for (var j = 0; j < lastChange.changes.length; j++) {
3687
+ var change = lastChange.changes[j];
3688
+ if (change instanceof InsertModeKey) {
3689
+ CodeMirror.lookupKey(change.keyName, ['vim-insert'], keyHandler);
3690
+ } else {
3691
+ var cur = cm.getCursor();
3692
+ cm.replaceRange(change, cur, cur);
3693
+ }
3694
+ }
3695
+ }
3696
+ }
3697
+
3698
+ resetVimGlobalState();
3699
+ return vimApi;
3700
+ };
3701
+ // Initialize Vim and make it available as an API.
3702
+ CodeMirror.Vim = Vim();
3703
+ }
3704
+ )();