commands.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. /* ***** BEGIN LICENSE BLOCK *****
  2. * Distributed under the BSD license:
  3. *
  4. * Copyright (c) 2010, Ajax.org B.V.
  5. * All rights reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions are met:
  9. * * Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. * * Redistributions in binary form must reproduce the above copyright
  12. * notice, this list of conditions and the following disclaimer in the
  13. * documentation and/or other materials provided with the distribution.
  14. * * Neither the name of Ajax.org B.V. nor the
  15. * names of its contributors may be used to endorse or promote products
  16. * derived from this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  19. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
  22. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  24. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  25. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  27. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *
  29. * ***** END LICENSE BLOCK ***** */
  30. define(function(require, exports, module) {
  31. "never use strict";
  32. var lang = require("../../lib/lang");
  33. var util = require("./maps/util");
  34. var motions = require("./maps/motions");
  35. var operators = require("./maps/operators");
  36. var alias = require("./maps/aliases");
  37. var registers = require("./registers");
  38. var NUMBER = 1;
  39. var OPERATOR = 2;
  40. var MOTION = 3;
  41. var ACTION = 4;
  42. var HMARGIN = 8; // Minimum amount of line separation between margins;
  43. var repeat = function repeat(fn, count, args) {
  44. while (0 < count--)
  45. fn.apply(this, args);
  46. };
  47. var ensureScrollMargin = function(editor) {
  48. var renderer = editor.renderer;
  49. var pos = renderer.$cursorLayer.getPixelPosition();
  50. var top = pos.top;
  51. var margin = HMARGIN * renderer.layerConfig.lineHeight;
  52. if (2 * margin > renderer.$size.scrollerHeight)
  53. margin = renderer.$size.scrollerHeight / 2;
  54. if (renderer.scrollTop > top - margin) {
  55. renderer.session.setScrollTop(top - margin);
  56. }
  57. if (renderer.scrollTop + renderer.$size.scrollerHeight < top + margin + renderer.lineHeight) {
  58. renderer.session.setScrollTop(top + margin + renderer.lineHeight - renderer.$size.scrollerHeight);
  59. }
  60. };
  61. var actions = exports.actions = {
  62. "z": {
  63. param: true,
  64. fn: function(editor, range, count, param) {
  65. switch (param) {
  66. case "z":
  67. editor.renderer.alignCursor(null, 0.5);
  68. break;
  69. case "t":
  70. editor.renderer.alignCursor(null, 0);
  71. break;
  72. case "b":
  73. editor.renderer.alignCursor(null, 1);
  74. break;
  75. case "c":
  76. editor.session.onFoldWidgetClick(range.start.row, {domEvent:{target :{}}});
  77. break;
  78. case "o":
  79. editor.session.onFoldWidgetClick(range.start.row, {domEvent:{target :{}}});
  80. break;
  81. case "C":
  82. editor.session.foldAll();
  83. break;
  84. case "O":
  85. editor.session.unfold();
  86. break;
  87. }
  88. }
  89. },
  90. "r": {
  91. param: true,
  92. fn: function(editor, range, count, param) {
  93. if (param && param.length) {
  94. if (param.length > 1)
  95. param = param == "return" ? "\n" : param == "tab" ? "\t" : param;
  96. repeat(function() { editor.insert(param); }, count || 1);
  97. editor.navigateLeft();
  98. }
  99. }
  100. },
  101. "R": {
  102. fn: function(editor, range, count, param) {
  103. util.insertMode(editor);
  104. editor.setOverwrite(true);
  105. }
  106. },
  107. "~": {
  108. fn: function(editor, range, count) {
  109. repeat(function() {
  110. var range = editor.selection.getRange();
  111. if (range.isEmpty())
  112. range.end.column++;
  113. var text = editor.session.getTextRange(range);
  114. var toggled = text.toUpperCase();
  115. if (toggled == text)
  116. editor.navigateRight();
  117. else
  118. editor.session.replace(range, toggled);
  119. }, count || 1);
  120. }
  121. },
  122. "*": {
  123. fn: function(editor, range, count, param) {
  124. editor.selection.selectWord();
  125. editor.findNext();
  126. ensureScrollMargin(editor);
  127. var r = editor.selection.getRange();
  128. editor.selection.setSelectionRange(r, true);
  129. }
  130. },
  131. "#": {
  132. fn: function(editor, range, count, param) {
  133. editor.selection.selectWord();
  134. editor.findPrevious();
  135. ensureScrollMargin(editor);
  136. var r = editor.selection.getRange();
  137. editor.selection.setSelectionRange(r, true);
  138. }
  139. },
  140. "m": {
  141. param: true,
  142. fn: function(editor, range, count, param) {
  143. var s = editor.session;
  144. var markers = s.vimMarkers || (s.vimMarkers = {});
  145. var c = editor.getCursorPosition();
  146. if (!markers[param]) {
  147. markers[param] = editor.session.doc.createAnchor(c);
  148. }
  149. markers[param].setPosition(c.row, c.column, true);
  150. }
  151. },
  152. "n": {
  153. fn: function(editor, range, count, param) {
  154. var options = editor.getLastSearchOptions();
  155. options.backwards = false;
  156. editor.selection.moveCursorRight();
  157. editor.selection.clearSelection();
  158. editor.findNext(options);
  159. ensureScrollMargin(editor);
  160. var r = editor.selection.getRange();
  161. r.end.row = r.start.row;
  162. r.end.column = r.start.column;
  163. editor.selection.setSelectionRange(r, true);
  164. }
  165. },
  166. "N": {
  167. fn: function(editor, range, count, param) {
  168. var options = editor.getLastSearchOptions();
  169. options.backwards = true;
  170. editor.findPrevious(options);
  171. ensureScrollMargin(editor);
  172. var r = editor.selection.getRange();
  173. r.end.row = r.start.row;
  174. r.end.column = r.start.column;
  175. editor.selection.setSelectionRange(r, true);
  176. }
  177. },
  178. "v": {
  179. fn: function(editor, range, count, param) {
  180. editor.selection.selectRight();
  181. util.visualMode(editor, false);
  182. },
  183. acceptsMotion: true
  184. },
  185. "V": {
  186. fn: function(editor, range, count, param) {
  187. //editor.selection.selectLine();
  188. //editor.selection.selectLeft();
  189. var row = editor.getCursorPosition().row;
  190. editor.selection.clearSelection();
  191. editor.selection.moveCursorTo(row, 0);
  192. editor.selection.selectLineEnd();
  193. editor.selection.visualLineStart = row;
  194. util.visualMode(editor, true);
  195. },
  196. acceptsMotion: true
  197. },
  198. "Y": {
  199. fn: function(editor, range, count, param) {
  200. util.copyLine(editor);
  201. }
  202. },
  203. "p": {
  204. fn: function(editor, range, count, param) {
  205. var defaultReg = registers._default;
  206. editor.setOverwrite(false);
  207. if (defaultReg.isLine) {
  208. var pos = editor.getCursorPosition();
  209. pos.column = editor.session.getLine(pos.row).length;
  210. var text = lang.stringRepeat("\n" + defaultReg.text, count || 1);
  211. editor.session.insert(pos, text);
  212. editor.moveCursorTo(pos.row + 1, 0);
  213. }
  214. else {
  215. editor.navigateRight();
  216. editor.insert(lang.stringRepeat(defaultReg.text, count || 1));
  217. editor.navigateLeft();
  218. }
  219. editor.setOverwrite(true);
  220. editor.selection.clearSelection();
  221. }
  222. },
  223. "P": {
  224. fn: function(editor, range, count, param) {
  225. var defaultReg = registers._default;
  226. editor.setOverwrite(false);
  227. if (defaultReg.isLine) {
  228. var pos = editor.getCursorPosition();
  229. pos.column = 0;
  230. var text = lang.stringRepeat(defaultReg.text + "\n", count || 1);
  231. editor.session.insert(pos, text);
  232. editor.moveCursorToPosition(pos);
  233. }
  234. else {
  235. editor.insert(lang.stringRepeat(defaultReg.text, count || 1));
  236. }
  237. editor.setOverwrite(true);
  238. editor.selection.clearSelection();
  239. }
  240. },
  241. "J": {
  242. fn: function(editor, range, count, param) {
  243. var session = editor.session;
  244. range = editor.getSelectionRange();
  245. var pos = {row: range.start.row, column: range.start.column};
  246. count = count || range.end.row - range.start.row;
  247. var maxRow = Math.min(pos.row + (count || 1), session.getLength() - 1);
  248. range.start.column = session.getLine(pos.row).length;
  249. range.end.column = session.getLine(maxRow).length;
  250. range.end.row = maxRow;
  251. var text = "";
  252. for (var i = pos.row; i < maxRow; i++) {
  253. var nextLine = session.getLine(i + 1);
  254. text += " " + /^\s*(.*)$/.exec(nextLine)[1] || "";
  255. }
  256. session.replace(range, text);
  257. editor.moveCursorTo(pos.row, pos.column);
  258. }
  259. },
  260. "u": {
  261. fn: function(editor, range, count, param) {
  262. count = parseInt(count || 1, 10);
  263. for (var i = 0; i < count; i++) {
  264. editor.undo();
  265. }
  266. editor.selection.clearSelection();
  267. }
  268. },
  269. "ctrl-r": {
  270. fn: function(editor, range, count, param) {
  271. count = parseInt(count || 1, 10);
  272. for (var i = 0; i < count; i++) {
  273. editor.redo();
  274. }
  275. editor.selection.clearSelection();
  276. }
  277. },
  278. ":": {
  279. fn: function(editor, range, count, param) {
  280. var val = ":";
  281. if (count > 1)
  282. val = ".,.+" + count + val;
  283. if (editor.showCommandLine)
  284. editor.showCommandLine(val);
  285. }
  286. },
  287. "/": {
  288. fn: function(editor, range, count, param) {
  289. if (editor.showCommandLine)
  290. editor.showCommandLine("/");
  291. }
  292. },
  293. "?": {
  294. fn: function(editor, range, count, param) {
  295. if (editor.showCommandLine)
  296. editor.showCommandLine("?");
  297. }
  298. },
  299. ".": {
  300. fn: function(editor, range, count, param) {
  301. util.onInsertReplaySequence = inputBuffer.lastInsertCommands;
  302. var previous = inputBuffer.previous;
  303. if (previous) // If there is a previous action
  304. inputBuffer.exec(editor, previous.action, previous.param);
  305. }
  306. },
  307. "ctrl-x": {
  308. fn: function(editor, range, count, param) {
  309. editor.modifyNumber(-(count || 1));
  310. }
  311. },
  312. "ctrl-a": {
  313. fn: function(editor, range, count, param) {
  314. editor.modifyNumber(count || 1);
  315. }
  316. }
  317. };
  318. var inputBuffer = exports.inputBuffer = {
  319. accepting: [NUMBER, OPERATOR, MOTION, ACTION],
  320. currentCmd: null,
  321. //currentMode: 0,
  322. currentCount: "",
  323. status: "",
  324. // Types
  325. operator: null,
  326. motion: null,
  327. lastInsertCommands: [],
  328. push: function(editor, ch, keyId) {
  329. var status = this.status;
  330. var isKeyHandled = true;
  331. this.idle = false;
  332. var wObj = this.waitingForParam;
  333. if (/^numpad\d+$/i.test(ch))
  334. ch = ch.substr(6);
  335. if (wObj) {
  336. this.exec(editor, wObj, ch);
  337. }
  338. // If input is a number (that doesn't start with 0)
  339. else if (!(ch === "0" && !this.currentCount.length) &&
  340. (/^\d+$/.test(ch) && this.isAccepting(NUMBER))) {
  341. // Assuming that ch is always of type String, and not Number
  342. this.currentCount += ch;
  343. this.currentCmd = NUMBER;
  344. this.accepting = [NUMBER, OPERATOR, MOTION, ACTION];
  345. }
  346. else if (!this.operator && this.isAccepting(OPERATOR) && operators[ch]) {
  347. this.operator = {
  348. ch: ch,
  349. count: this.getCount()
  350. };
  351. this.currentCmd = OPERATOR;
  352. this.accepting = [NUMBER, MOTION, ACTION];
  353. this.exec(editor, { operator: this.operator });
  354. }
  355. else if (motions[ch] && this.isAccepting(MOTION)) {
  356. this.currentCmd = MOTION;
  357. var ctx = {
  358. operator: this.operator,
  359. motion: {
  360. ch: ch,
  361. count: this.getCount()
  362. }
  363. };
  364. if (motions[ch].param)
  365. this.waitForParam(ctx);
  366. else
  367. this.exec(editor, ctx);
  368. }
  369. else if (alias[ch] && this.isAccepting(MOTION)) {
  370. alias[ch].operator.count = this.getCount();
  371. this.exec(editor, alias[ch]);
  372. }
  373. else if (actions[ch] && this.isAccepting(ACTION)) {
  374. var actionObj = {
  375. action: {
  376. fn: actions[ch].fn,
  377. count: this.getCount()
  378. }
  379. };
  380. if (actions[ch].param) {
  381. this.waitForParam(actionObj);
  382. }
  383. else {
  384. this.exec(editor, actionObj);
  385. }
  386. if (actions[ch].acceptsMotion)
  387. this.idle = false;
  388. }
  389. else if (this.operator) {
  390. this.operator.count = this.getCount();
  391. this.exec(editor, { operator: this.operator }, ch);
  392. }
  393. else {
  394. isKeyHandled = ch.length == 1;
  395. this.reset();
  396. }
  397. if (this.waitingForParam || this.motion || this.operator) {
  398. this.status += ch;
  399. } else if (this.currentCount) {
  400. this.status = this.currentCount;
  401. } else if (this.status) {
  402. this.status = "";
  403. }
  404. if (this.status != status)
  405. editor._emit("changeStatus");
  406. return isKeyHandled;
  407. },
  408. waitForParam: function(cmd) {
  409. this.waitingForParam = cmd;
  410. },
  411. getCount: function() {
  412. var count = this.currentCount;
  413. this.currentCount = "";
  414. return count && parseInt(count, 10);
  415. },
  416. exec: function(editor, action, param) {
  417. var m = action.motion;
  418. var o = action.operator;
  419. var a = action.action;
  420. if (!param)
  421. param = action.param;
  422. if (o) {
  423. this.previous = {
  424. action: action,
  425. param: param
  426. };
  427. }
  428. if (o && !editor.selection.isEmpty()) {
  429. if (operators[o.ch].selFn) {
  430. operators[o.ch].selFn(editor, editor.getSelectionRange(), o.count, param);
  431. this.reset();
  432. }
  433. return;
  434. }
  435. // There is an operator, but no motion or action. We try to pass the
  436. // current ch to the operator to see if it responds to it (an example
  437. // of this is the 'dd' operator).
  438. else if (!m && !a && o && param) {
  439. operators[o.ch].fn(editor, null, o.count, param);
  440. this.reset();
  441. }
  442. else if (m) {
  443. var run = function(fn) {
  444. if (fn && typeof fn === "function") { // There should always be a motion
  445. if (m.count && !motionObj.handlesCount)
  446. repeat(fn, m.count, [editor, null, m.count, param]);
  447. else
  448. fn(editor, null, m.count, param);
  449. }
  450. };
  451. var motionObj = motions[m.ch];
  452. var selectable = motionObj.sel;
  453. if (!o) {
  454. if ((util.onVisualMode || util.onVisualLineMode) && selectable)
  455. run(motionObj.sel);
  456. else
  457. run(motionObj.nav);
  458. }
  459. else if (selectable) {
  460. repeat(function() {
  461. run(motionObj.sel);
  462. operators[o.ch].fn(editor, editor.getSelectionRange(), o.count, param);
  463. }, o.count || 1);
  464. }
  465. this.reset();
  466. }
  467. else if (a) {
  468. a.fn(editor, editor.getSelectionRange(), a.count, param);
  469. this.reset();
  470. }
  471. handleCursorMove(editor);
  472. },
  473. isAccepting: function(type) {
  474. return this.accepting.indexOf(type) !== -1;
  475. },
  476. reset: function() {
  477. this.operator = null;
  478. this.motion = null;
  479. this.currentCount = "";
  480. this.status = "";
  481. this.accepting = [NUMBER, OPERATOR, MOTION, ACTION];
  482. this.idle = true;
  483. this.waitingForParam = null;
  484. }
  485. };
  486. function setPreviousCommand(fn) {
  487. inputBuffer.previous = { action: { action: { fn: fn } } };
  488. }
  489. exports.coreCommands = {
  490. start: {
  491. exec: function start(editor) {
  492. util.insertMode(editor);
  493. setPreviousCommand(start);
  494. }
  495. },
  496. startBeginning: {
  497. exec: function startBeginning(editor) {
  498. editor.navigateLineStart();
  499. util.insertMode(editor);
  500. setPreviousCommand(startBeginning);
  501. }
  502. },
  503. // Stop Insert mode as soon as possible. Works like typing <Esc> in
  504. // insert mode.
  505. stop: {
  506. exec: function stop(editor) {
  507. inputBuffer.reset();
  508. util.onVisualMode = false;
  509. util.onVisualLineMode = false;
  510. inputBuffer.lastInsertCommands = util.normalMode(editor);
  511. }
  512. },
  513. append: {
  514. exec: function append(editor) {
  515. var pos = editor.getCursorPosition();
  516. var lineLen = editor.session.getLine(pos.row).length;
  517. if (lineLen)
  518. editor.navigateRight();
  519. util.insertMode(editor);
  520. setPreviousCommand(append);
  521. }
  522. },
  523. appendEnd: {
  524. exec: function appendEnd(editor) {
  525. editor.navigateLineEnd();
  526. util.insertMode(editor);
  527. setPreviousCommand(appendEnd);
  528. }
  529. }
  530. };
  531. var handleCursorMove = exports.onCursorMove = function(editor, e) {
  532. if (util.currentMode === 'insert' || handleCursorMove.running)
  533. return;
  534. else if(!editor.selection.isEmpty()) {
  535. handleCursorMove.running = true;
  536. if (util.onVisualLineMode) {
  537. var originRow = editor.selection.visualLineStart;
  538. var cursorRow = editor.getCursorPosition().row;
  539. if(originRow <= cursorRow) {
  540. var endLine = editor.session.getLine(cursorRow);
  541. editor.selection.clearSelection();
  542. editor.selection.moveCursorTo(originRow, 0);
  543. editor.selection.selectTo(cursorRow, endLine.length);
  544. } else {
  545. var endLine = editor.session.getLine(originRow);
  546. editor.selection.clearSelection();
  547. editor.selection.moveCursorTo(originRow, endLine.length);
  548. editor.selection.selectTo(cursorRow, 0);
  549. }
  550. }
  551. handleCursorMove.running = false;
  552. return;
  553. }
  554. else {
  555. if (e && (util.onVisualLineMode || util.onVisualMode)) {
  556. editor.selection.clearSelection();
  557. util.normalMode(editor);
  558. }
  559. handleCursorMove.running = true;
  560. var pos = editor.getCursorPosition();
  561. var lineLen = editor.session.getLine(pos.row).length;
  562. if (lineLen && pos.column === lineLen)
  563. editor.navigateLeft();
  564. handleCursorMove.running = false;
  565. }
  566. };
  567. });