/* ***** 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): * Fabian Jakobs * Julian Viereck * * 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 ***** */ define(function(require, exports, module) { var oop = require("pilot/oop"); var lang = require("pilot/lang"); var EventEmitter = require("pilot/event_emitter").EventEmitter; var Range = require("ace/range").Range; /** * Keeps cursor position and the text selection of an edit session. * * The row/columns used in the selection are in document coordinates * representing ths coordinates as thez appear in the document * before applying soft wrap and folding. */ var Selection = function(session) { this.session = session; this.doc = session.getDocument(); this.clearSelection(); this.selectionLead = this.doc.createAnchor(0, 0); this.selectionAnchor = this.doc.createAnchor(0, 0); var _self = this; this.selectionLead.on("change", function(e) { _self._dispatchEvent("changeCursor"); if (!_self.$isEmpty) _self._dispatchEvent("changeSelection"); if (!_self.$preventUpdateDesiredColumnOnChange && e.old.column != e.value.column) _self.$updateDesiredColumn(); }); this.selectionAnchor.on("change", function() { if (!_self.$isEmpty) _self._dispatchEvent("changeSelection"); }); }; (function() { oop.implement(this, EventEmitter); this.isEmpty = function() { return (this.$isEmpty || ( this.selectionAnchor.row == this.selectionLead.row && this.selectionAnchor.column == this.selectionLead.column )); }; this.isMultiLine = function() { if (this.isEmpty()) { return false; } return this.getRange().isMultiLine(); }; this.getCursor = function() { return this.selectionLead.getPosition(); }; this.setSelectionAnchor = function(row, column) { this.selectionAnchor.setPosition(row, column); if (this.$isEmpty) { this.$isEmpty = false; this._dispatchEvent("changeSelection"); } }; this.getSelectionAnchor = function() { if (this.$isEmpty) return this.getSelectionLead() else return this.selectionAnchor.getPosition(); }; this.getSelectionLead = function() { return this.selectionLead.getPosition(); }; this.shiftSelection = function(columns) { if (this.$isEmpty) { this.moveCursorTo(this.selectionLead.row, this.selectionLead.column + columns); return; }; var anchor = this.getSelectionAnchor(); var lead = this.getSelectionLead(); var isBackwards = this.isBackwards(); if (!isBackwards || anchor.column !== 0) this.setSelectionAnchor(anchor.row, anchor.column + columns); if (isBackwards || lead.column !== 0) { this.$moveSelection(function() { this.moveCursorTo(lead.row, lead.column + columns); }); } }; this.isBackwards = function() { var anchor = this.selectionAnchor; var lead = this.selectionLead; return (anchor.row > lead.row || (anchor.row == lead.row && anchor.column > lead.column)); }; this.getRange = function() { var anchor = this.selectionAnchor; var lead = this.selectionLead; if (this.isEmpty()) return Range.fromPoints(lead, lead); if (this.isBackwards()) { return Range.fromPoints(lead, anchor); } else { return Range.fromPoints(anchor, lead); } }; this.clearSelection = function() { if (!this.$isEmpty) { this.$isEmpty = true; this._dispatchEvent("changeSelection"); } }; this.selectAll = function() { var lastRow = this.doc.getLength() - 1; this.setSelectionAnchor(lastRow, this.doc.getLine(lastRow).length); this.moveCursorTo(0, 0); }; this.setSelectionRange = function(range, reverse) { if (reverse) { this.setSelectionAnchor(range.end.row, range.end.column); this.selectTo(range.start.row, range.start.column); } else { this.setSelectionAnchor(range.start.row, range.start.column); this.selectTo(range.end.row, range.end.column); } this.$updateDesiredColumn(); }; this.$updateDesiredColumn = function() { var cursor = this.getCursor(); this.$desiredColumn = this.session.documentToScreenColumn(cursor.row, cursor.column); }; this.$moveSelection = function(mover) { var lead = this.selectionLead; if (this.$isEmpty) this.setSelectionAnchor(lead.row, lead.column); mover.call(this); }; this.selectTo = function(row, column) { this.$moveSelection(function() { this.moveCursorTo(row, column); }); }; this.selectToPosition = function(pos) { this.$moveSelection(function() { this.moveCursorToPosition(pos); }); }; this.selectUp = function() { this.$moveSelection(this.moveCursorUp); }; this.selectDown = function() { this.$moveSelection(this.moveCursorDown); }; this.selectRight = function() { this.$moveSelection(this.moveCursorRight); }; this.selectLeft = function() { this.$moveSelection(this.moveCursorLeft); }; this.selectLineStart = function() { this.$moveSelection(this.moveCursorLineStart); }; this.selectLineEnd = function() { this.$moveSelection(this.moveCursorLineEnd); }; this.selectFileEnd = function() { this.$moveSelection(this.moveCursorFileEnd); }; this.selectFileStart = function() { this.$moveSelection(this.moveCursorFileStart); }; this.selectWordRight = function() { this.$moveSelection(this.moveCursorWordRight); }; this.selectWordLeft = function() { this.$moveSelection(this.moveCursorWordLeft); }; this.selectWord = function() { var cursor = this.getCursor(); var range = this.session.getWordRange(cursor.row, cursor.column); this.setSelectionRange(range); }; this.selectLine = function() { var rowStart = this.selectionLead.row; var rowEnd; var foldLine = this.session.getFoldLine(rowStart); if (foldLine) { rowStart = foldLine.start.row; rowEnd = foldLine.end.row; } else { rowEnd = rowStart; } this.setSelectionAnchor(rowStart, 0); this.$moveSelection(function() { this.moveCursorTo(rowEnd + 1, 0); }); }; this.moveCursorUp = function() { this.moveCursorBy(-1, 0); }; this.moveCursorDown = function() { this.moveCursorBy(1, 0); }; this.moveCursorLeft = function() { var cursor = this.selectionLead.getPosition(), fold; if (fold = this.session.getFoldAt(cursor.row, cursor.column, -1)) { this.moveCursorTo(fold.start.row, fold.start.column); } else if (cursor.column == 0) { // cursor is a line (start if (cursor.row > 0) { this.moveCursorTo(cursor.row - 1, this.doc.getLine(cursor.row - 1).length); } } else { var tabSize = this.session.getTabSize(); if (this.session.isTabStop(cursor) && this.doc.getLine(cursor.row).slice(cursor.column-tabSize, cursor.column).split(" ").length-1 == tabSize) this.moveCursorBy(0, -tabSize); else this.moveCursorBy(0, -1); } }; this.moveCursorRight = function() { var cursor = this.selectionLead.getPosition(), fold; if (fold = this.session.getFoldAt(cursor.row, cursor.column, 1)) { this.moveCursorTo(fold.end.row, fold.end.column); } else if (this.selectionLead.column == this.doc.getLine(this.selectionLead.row).length) { if (this.selectionLead.row < this.doc.getLength() - 1) { this.moveCursorTo(this.selectionLead.row + 1, 0); } } else { var tabSize = this.session.getTabSize(); var cursor = this.selectionLead; if (this.session.isTabStop(cursor) && this.doc.getLine(cursor.row).slice(cursor.column, cursor.column+tabSize).split(" ").length-1 == tabSize) this.moveCursorBy(0, tabSize); else this.moveCursorBy(0, 1); } }; this.moveCursorLineStart = function() { var row = this.selectionLead.row; var column = this.selectionLead.column; var screenRow = this.session.documentToScreenRow(row, column); // Determ the doc-position of the first character at the screen line. var firstColumnPosition = this.session.screenToDocumentPosition(screenRow, 0); // Determ the line var beforeCursor = this.session.getDisplayLine( row, null, firstColumnPosition.row, firstColumnPosition.column ); var leadingSpace = beforeCursor.match(/^\s*/); if (leadingSpace[0].length == column) { this.moveCursorTo( firstColumnPosition.row, firstColumnPosition.column ); } else { this.moveCursorTo( firstColumnPosition.row, firstColumnPosition.column + leadingSpace[0].length ); } }; this.moveCursorLineEnd = function() { var lead = this.selectionLead; var lastRowColumnPosition = this.session.getDocumentLastRowColumnPosition(lead.row, lead.column); this.moveCursorTo( lastRowColumnPosition.row, lastRowColumnPosition.column ); }; this.moveCursorFileEnd = function() { var row = this.doc.getLength() - 1; var column = this.doc.getLine(row).length; this.moveCursorTo(row, column); }; this.moveCursorFileStart = function() { this.moveCursorTo(0, 0); }; this.moveCursorWordRight = function() { var row = this.selectionLead.row; var column = this.selectionLead.column; var line = this.doc.getLine(row); var rightOfCursor = line.substring(column); var match; this.session.nonTokenRe.lastIndex = 0; this.session.tokenRe.lastIndex = 0; var fold; if (fold = this.session.getFoldAt(row, column, 1)) { this.moveCursorTo(fold.end.row, fold.end.column); return; } else if (column == line.length) { this.moveCursorRight(); return; } else if (match = this.session.nonTokenRe.exec(rightOfCursor)) { column += this.session.nonTokenRe.lastIndex; this.session.nonTokenRe.lastIndex = 0; } else if (match = this.session.tokenRe.exec(rightOfCursor)) { column += this.session.tokenRe.lastIndex; this.session.tokenRe.lastIndex = 0; } this.moveCursorTo(row, column); }; this.moveCursorWordLeft = function() { var row = this.selectionLead.row; var column = this.selectionLead.column; var fold; if (fold = this.session.getFoldAt(row, column, -1)) { this.moveCursorTo(fold.start.row, fold.start.column); return; } if (column == 0) { this.moveCursorLeft(); return; } var str = this.session.getFoldStringAt(row, column, -1); if (str == null) { str = this.doc.getLine(row).substring(0, column) } var leftOfCursor = lang.stringReverse(str); var match; this.session.nonTokenRe.lastIndex = 0; this.session.tokenRe.lastIndex = 0; if (match = this.session.nonTokenRe.exec(leftOfCursor)) { column -= this.session.nonTokenRe.lastIndex; this.session.nonTokenRe.lastIndex = 0; } else if (match = this.session.tokenRe.exec(leftOfCursor)) { column -= this.session.tokenRe.lastIndex; this.session.tokenRe.lastIndex = 0; } this.moveCursorTo(row, column); }; this.moveCursorBy = function(rows, chars) { var screenPos = this.session.documentToScreenPosition( this.selectionLead.row, this.selectionLead.column ); var screenCol = (chars == 0 && this.$desiredColumn) || screenPos.column; var docPos = this.session.screenToDocumentPosition(screenPos.row + rows, screenCol); this.moveCursorTo(docPos.row, docPos.column + chars, chars == 0); }; this.moveCursorToPosition = function(position) { this.moveCursorTo(position.row, position.column); }; this.moveCursorTo = function(row, column, preventUpdateDesiredColumn) { // Ensure the row/column is not inside of a fold. var fold = this.session.getFoldAt(row, column, 1); if (fold) { row = fold.start.row; column = fold.start.column; } this.$preventUpdateDesiredColumnOnChange = true; this.selectionLead.setPosition(row, column); this.$preventUpdateDesiredColumnOnChange = false; if (!preventUpdateDesiredColumn) this.$updateDesiredColumn(this.selectionLead.column); }; this.moveCursorToScreen = function(row, column, preventUpdateDesiredColumn) { var pos = this.session.screenToDocumentPosition(row, column); row = pos.row; column = pos.column; this.moveCursorTo(row, column, preventUpdateDesiredColumn); }; }).call(Selection.prototype); exports.Selection = Selection; });