/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Ajax.org Code Editor (ACE). * * The Initial Developer of the Original Code is * Ajax.org B.V. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Sergi Mansilla * Harutyun Amirjanyan * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ "use strict" define(function(require, exports, module) { var util = require("./util"); var keepScrollPosition = function(editor, fn) { var scrollTopRow = editor.renderer.getScrollTopRow(); var initialRow = editor.getCursorPosition().row; var diff = initialRow - scrollTopRow; fn && fn.call(editor); editor.renderer.scrollToRow(editor.getCursorPosition().row - diff); }; function Motion(getRange, type){ if (type == 'extend') var extend = true; else var reverse = type; this.nav = function(editor) { var r = getRange(editor); if (!r) return; if (!r.end) var a = r; else if (reverse) var a = r.start; else var a = r.end; editor.clearSelection(); editor.moveCursorTo(a.row, a.column); } this.sel = function(editor){ var r = getRange(editor); if (!r) return; if (extend) return editor.selection.setSelectionRange(r); if (!r.end) var a = r; else if (reverse) var a = r.start; else var a = r.end; editor.selection.selectTo(a.row, a.column); } } var nonWordRe = /[\s.\/\\()\"'-:,.;<>~!@#$%^&*|+=\[\]{}`~?]/; var wordSeparatorRe = /[.\/\\()\"'-:,.;<>~!@#$%^&*|+=\[\]{}`~?]/; var whiteRe = /\s/; var StringStream = function(editor, cursor) { var sel = editor.selection; this.range = sel.getRange(); cursor = cursor || sel.selectionLead; this.row = cursor.row; this.col = cursor.column; var line = editor.session.getLine(this.row); var maxRow = editor.session.getLength() this.ch = line[this.col] || '\n' this.skippedLines = 0; this.next = function() { this.ch = line[++this.col] || this.handleNewLine(1); //this.debug() return this.ch; } this.prev = function() { this.ch = line[--this.col] || this.handleNewLine(-1); //this.debug() return this.ch; } this.peek = function(dir) { var ch = line[this.col + dir]; if (ch) return ch; if (dir == -1) return '\n'; if (this.col == line.length - 1) return '\n'; return editor.session.getLine(this.row + 1)[0] || '\n'; } this.handleNewLine = function(dir) { if (dir == 1){ if (this.col == line.length) return '\n'; if (this.row == maxRow - 1) return ''; this.col = 0; this.row ++; line = editor.session.getLine(this.row); this.skippedLines++; return line[0] || '\n'; } if (dir == -1) { if (this.row == 0) return ''; this.row --; line = editor.session.getLine(this.row); this.col = line.length; this.skippedLines--; return '\n'; } } this.debug = function() { console.log(line.substring(0, this.col)+'|'+this.ch+'\''+this.col+'\''+line.substr(this.col+1)); } } var Search = require("ace/search").Search; var search = new Search(); function find(editor, needle, dir) { search.$options.needle = needle; search.$options.backwards = dir == -1; return search.find(editor.session); } var Range = require("ace/range").Range; module.exports = { "w": new Motion(function(editor) { var str = new StringStream(editor); if (str.ch && wordSeparatorRe.test(str.ch)) { while (str.ch && wordSeparatorRe.test(str.ch)) str.next(); } else { while (str.ch && !nonWordRe.test(str.ch)) str.next(); } while (str.ch && whiteRe.test(str.ch) && str.skippedLines < 2) str.next(); str.skippedLines == 2 && str.prev(); return {column: str.col, row: str.row}; }), "W": new Motion(function(editor) { var str = new StringStream(editor); while(str.ch && !(whiteRe.test(str.ch) && !whiteRe.test(str.peek(1))) && str.skippedLines < 2) str.next(); if (str.skippedLines == 2) str.prev(); else str.next(); return {column: str.col, row: str.row} }), "b": new Motion(function(editor) { var str = new StringStream(editor); str.prev(); while (str.ch && whiteRe.test(str.ch) && str.skippedLines > -2) str.prev(); if (str.ch && wordSeparatorRe.test(str.ch)) { while (str.ch && wordSeparatorRe.test(str.ch)) str.prev(); } else { while (str.ch && !nonWordRe.test(str.ch)) str.prev(); } str.ch && str.next(); return {column: str.col, row: str.row}; }), "B": new Motion(function(editor) { var str = new StringStream(editor) str.prev(); while(str.ch && !(!whiteRe.test(str.ch) && whiteRe.test(str.peek(-1))) && str.skippedLines > -2) str.prev(); if (str.skippedLines == -2) str.next(); return {column: str.col, row: str.row}; }, true), "e": new Motion(function(editor) { var str = new StringStream(editor); str.next(); while (str.ch && whiteRe.test(str.ch)) str.next(); if (str.ch && wordSeparatorRe.test(str.ch)) { while (str.ch && wordSeparatorRe.test(str.ch)) str.next(); } else { while (str.ch && !nonWordRe.test(str.ch)) str.next(); } str.ch && str.prev(); return {column: str.col, row: str.row}; }), "E": new Motion(function(editor) { var str = new StringStream(editor); str.next(); while(str.ch && !(!whiteRe.test(str.ch) && whiteRe.test(str.peek(1)))) str.next(); return {column: str.col, row: str.row}; }), "l": { nav: function(editor) { editor.navigateRight(); }, sel: function(editor) { var pos = editor.getCursorPosition(); var col = pos.column; var lineLen = editor.session.getLine(pos.row).length; // Solving the behavior at the end of the line due to the // different 0 index-based colum positions in ACE. if (lineLen && col !== lineLen) //In selection mode you can select the newline editor.selection.selectRight(); } }, "h": { nav: function(editor) { var pos = editor.getCursorPosition(); if (pos.column > 0) editor.navigateLeft(); }, sel: function(editor) { var pos = editor.getCursorPosition(); if (pos.column > 0) editor.selection.selectLeft(); } }, "k": { nav: function(editor) { editor.navigateUp(); }, sel: function(editor) { editor.selection.selectUp(); } }, "j": { nav: function(editor) { editor.navigateDown(); }, sel: function(editor) { editor.selection.selectDown(); } }, "i": { param: true, sel: function(editor, range, count, param) { switch (param) { case "w": editor.selection.selectWord(); break; case "W": editor.selection.selectAWord(); break; case "(": case "{": case "[": var cursor = editor.getCursorPosition(); var end = editor.session.$findClosingBracket(param, cursor, /paren/); if (!end) return; var start = editor.session.$findOpeningBracket(editor.session.$brackets[param], cursor, /paren/); if (!start) return; start.column ++; editor.selection.setSelectionRange(Range.fromPoints(start, end)); break; case "'": case '"': case "/": var end = find(editor, param, 1); if (!end) return; var start = find(editor, param, -1); if (!start) return; editor.selection.setSelectionRange(Range.fromPoints(start.end, end.start)); break; } } }, "a": { param: true, sel: function(editor, range, count, param) { switch (param) { case "w": editor.selection.selectAWord(); break; case "W": editor.selection.selectAWord(); break; case "(": case "{": case "[": var cursor = editor.getCursorPosition(); var end = editor.session.$findClosingBracket(param, cursor, /paren/); if (!end) return; var start = editor.session.$findOpeningBracket(editor.session.$brackets[param], cursor, /paren/); if (!start) return; end.column ++; editor.selection.setSelectionRange(Range.fromPoints(start, end)); break; case "'": case "\"": case "/": var end = find(editor, param, 1); if (!end) return; var start = find(editor, param, -1); if (!start) return; end.column ++; editor.selection.setSelectionRange(Range.fromPoints(start.start, end.end)); break; } } }, "f": { param: true, handlesCount: true, nav: function(editor, range, count, param) { var ed = editor; var cursor = ed.getCursorPosition(); var column = util.getRightNthChar(editor, cursor, param, count || 1); if (typeof column === "number") { ed.selection.clearSelection(); // Why does it select in the first place? ed.moveCursorTo(cursor.row, column + cursor.column + 1); } }, sel: function(editor, range, count, param) { var ed = editor; var cursor = ed.getCursorPosition(); var column = util.getRightNthChar(editor, cursor, param, count || 1); if (typeof column === "number") { ed.moveCursorTo(cursor.row, column + cursor.column + 1); } } }, "F": { param: true, handlesCount: true, nav: function(editor, range, count, param) { count = parseInt(count, 10) || 1; var ed = editor; var cursor = ed.getCursorPosition(); var column = util.getLeftNthChar(editor, cursor, param, count); if (typeof column === "number") { ed.selection.clearSelection(); // Why does it select in the first place? ed.moveCursorTo(cursor.row, cursor.column - column - 1); } }, sel: function(editor, range, count, param) { var ed = editor; var cursor = ed.getCursorPosition(); var column = util.getLeftNthChar(editor, cursor, param, count || 1); if (typeof column === "number") { ed.moveCursorTo(cursor.row, cursor.column - column - 1); } } }, "t": { param: true, handlesCount: true, nav: function(editor, range, count, param) { var ed = editor; var cursor = ed.getCursorPosition(); var column = util.getRightNthChar(editor, cursor, param, count || 1); if (typeof column === "number") { ed.selection.clearSelection(); // Why does it select in the first place? ed.moveCursorTo(cursor.row, column + cursor.column); } }, sel: function(editor, range, count, param) { var ed = editor; var cursor = ed.getCursorPosition(); var column = util.getRightNthChar(editor, cursor, param, count || 1); if (typeof column === "number") { ed.moveCursorTo(cursor.row, column + cursor.column); } } }, "T": { param: true, handlesCount: true, nav: function(editor, range, count, param) { var ed = editor; var cursor = ed.getCursorPosition(); var column = util.getLeftNthChar(editor, cursor, param, count || 1); if (typeof column === "number") { ed.selection.clearSelection(); // Why does it select in the first place? ed.moveCursorTo(cursor.row, -column + cursor.column); } }, sel: function(editor, range, count, param) { var ed = editor; var cursor = ed.getCursorPosition(); var column = util.getLeftNthChar(editor, cursor, param, count || 1); if (typeof column === "number") { ed.moveCursorTo(cursor.row, -column + cursor.column); } } }, "^": { nav: function(editor) { editor.navigateLineStart(); }, sel: function(editor) { editor.selection.selectLineStart(); } }, "$": { nav: function(editor) { editor.navigateLineEnd(); }, sel: function(editor) { editor.selection.selectLineEnd(); } }, "0": { nav: function(editor) { var ed = editor; ed.navigateTo(ed.selection.selectionLead.row, 0); }, sel: function(editor) { var ed = editor; ed.selectTo(ed.selection.selectionLead.row, 0); } }, "G": { nav: function(editor, range, count, param) { if (!count && count !== 0) { // Stupid JS count = editor.session.getLength(); } editor.gotoLine(count); }, sel: function(editor, range, count, param) { if (!count && count !== 0) { // Stupid JS count = editor.session.getLength(); } editor.selection.selectTo(count, 0); } }, "g": { param: true, nav: function(editor, range, count, param) { switch(param) { case "m": console.log("Middle line"); break; case "e": console.log("End of prev word"); break; case "g": editor.gotoLine(count || 0); case "u": editor.gotoLine(count || 0); case "U": editor.gotoLine(count || 0); } }, sel: function(editor, range, count, param) { switch(param) { case "m": console.log("Middle line"); break; case "e": console.log("End of prev word"); break; case "g": editor.selection.selectTo(count || 0, 0); } } }, "o": { nav: function(editor, range, count, param) { count = count || 1; var content = ""; while (0 < count--) content += "\n"; if (content.length) { editor.navigateLineEnd() editor.insert(content); util.insertMode(editor); } } }, "O": { nav: function(editor, range, count, param) { var row = editor.getCursorPosition().row; count = count || 1; var content = ""; while (0 < count--) content += "\n"; if (content.length) { if(row > 0) { editor.navigateUp(); editor.navigateLineEnd() editor.insert(content); } else { editor.session.insert({row: 0, column: 0}, content); editor.navigateUp(); } util.insertMode(editor); } } }, "%": new Motion(function(editor){ var brRe = /[\[\]{}()]/g; var cursor = editor.getCursorPosition(); var ch = editor.session.getLine(cursor.row)[cursor.column]; if (!brRe.test(ch)) { var range = find(editor, brRe); if (!range) return; cursor = range.start; } var match = editor.session.findMatchingBracket({ row: cursor.row, column: cursor.column + 1 }); return match; }), "ctrl-d": { nav: function(editor, range, count, param) { editor.selection.clearSelection(); keepScrollPosition(editor, editor.gotoPageDown); }, sel: function(editor, range, count, param) { keepScrollPosition(editor, editor.selectPageDown); } }, "ctrl-u": { nav: function(editor, range, count, param) { editor.selection.clearSelection(); keepScrollPosition(editor, editor.gotoPageUp); }, sel: function(editor, range, count, param) { keepScrollPosition(editor, editor.selectPageUp); } }, }; module.exports.backspace = module.exports.left = module.exports.h; module.exports.right = module.exports.l; module.exports.up = module.exports.k; module.exports.down = module.exports.j; module.exports.pagedown = module.exports["ctrl-d"]; module.exports.pageup = module.exports["ctrl-u"]; });