lib/gollum/frontend/public/gollum/livepreview/js/ace/lib/ace/multi_select.js in gollum-2.4.4 vs lib/gollum/frontend/public/gollum/livepreview/js/ace/lib/ace/multi_select.js in gollum-2.4.5

- old
+ new

@@ -1,50 +1,43 @@ -/* vim:ts=4:sts=4:sw=4: - * ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: * - * 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/ + * Copyright (c) 2010, Ajax.org B.V. + * All rights reserved. * - * 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. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Ajax.org B.V. nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. * - * The Original Code is Ajax.org Code Editor (ACE). + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * 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): - * Harutyun Amirjanyan <amirjanyan AT gmail DOT com> - * - * 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 RangeList = require("./range_list").RangeList; var Range = require("./range").Range; var Selection = require("./selection").Selection; var onMouseDown = require("./mouse/multi_select_handler").onMouseDown; var event = require("./lib/event"); +var lang = require("./lib/lang"); var commands = require("./commands/multi_select_commands"); exports.commands = commands.defaultCommands.concat(commands.multiSelectCommands); // Todo: session.find or editor.findVolatile that returns range var Search = require("./search").Search; @@ -71,16 +64,15 @@ this.ranges = null; // automatically sorted list of ranges this.rangeList = null; - /** extension - * Selection.addRange(range, $blockChangeEvents) - * - range (Range): The new range to add - * - $blockChangeEvents (Boolean): Whether or not to block changing events - * + /** * Adds a range to a selection by entering multiselect mode, if necessary. + * @param {Range} range The new range to add + * @param {Boolean} $blockChangeEvents Whether or not to block changing events + * @method Selection.addRange **/ this.addRange = function(range, $blockChangeEvents) { if (!range) return; @@ -111,37 +103,39 @@ } return $blockChangeEvents || this.fromOrientedRange(range); }; + /** + * @method Selection.toSingleRange + **/ + this.toSingleRange = function(range) { range = range || this.ranges[0]; var removed = this.rangeList.removeAll(); if (removed.length) this.$onRemoveRange(removed); range && this.fromOrientedRange(range); }; - - /** extension - * Selection.substractPoint(pos) -> Range - * - pos (Range): The position to remove, as a `{row, column}` object - * + + /** * Removes a Range containing pos (if it exists). + * @param {Range} pos The position to remove, as a `{row, column}` object + * @method Selection.substractPoint **/ this.substractPoint = function(pos) { var removed = this.rangeList.substractPoint(pos); if (removed) { this.$onRemoveRange(removed); return removed[0]; } }; - /** extension - * Selection.mergeOverlappingRanges() - * + /** * Merges overlapping ranges ensuring consistency after changes + * @method Selection.mergeOverlappingRanges **/ this.mergeOverlappingRanges = function() { var removed = this.rangeList.merge(); if (removed.length) this.$onRemoveRange(removed); @@ -190,28 +184,47 @@ this.rangeList = new RangeList(); this.ranges = []; this.rangeCount = 0; }; + /** + * Returns a concatenation of all the ranges. + * @returns Array + * @method Selection.getAllRanges + **/ this.getAllRanges = function() { return this.rangeList.ranges.concat(); }; + /** + * Splits all the ranges into lines. + * @method Selection.splitIntoLines + **/ + this.splitIntoLines = function () { if (this.rangeCount > 1) { var ranges = this.rangeList.ranges; var lastRange = ranges[ranges.length - 1]; var range = Range.fromPoints(ranges[0].start, lastRange.end); this.toSingleRange(); this.setSelectionRange(range, lastRange.cursor == lastRange.start); } else { var range = this.getRange(); + var isBackwards = this.isBackwards(); var startRow = range.start.row; var endRow = range.end.row; - if (startRow == endRow) + if (startRow == endRow) { + if (isBackwards) + var start = range.end, end = range.start; + else + var start = range.start, end = range.end; + + this.addRange(Range.fromPoints(end, end)); + this.addRange(Range.fromPoints(start, start)); return; + } var rectSel = []; var r = this.getLineRange(startRow, true); r.start.column = range.start.column; rectSel.push(r); @@ -225,10 +238,13 @@ rectSel.forEach(this.addRange, this); } }; + /** + * @method Selection.toggleBlockSelection + **/ this.toggleBlockSelection = function () { if (this.rangeCount > 1) { var ranges = this.rangeList.ranges; var lastRange = ranges[ranges.length - 1]; var range = Range.fromPoints(ranges[0].start, lastRange.end); @@ -242,19 +258,20 @@ var rectSel = this.rectangularRangeBlock(cursor, anchor); rectSel.forEach(this.addRange, this); } }; - /** extension - * Selection.rectangularRangeBlock(screenCursor, screenAnchor, includeEmptyLines) -> Range - * - screenCursor (Cursor): The cursor to use - * - screenAnchor (Anchor): The anchor to use - * - includeEmptyLins (Boolean): If true, this includes ranges inside the block which are empty due to clipping - * + /** + * * Gets list of ranges composing rectangular block on the screen - * - */ + * + * @param {Cursor} screenCursor The cursor to use + * @param {Anchor} screenAnchor The anchor to use + * @param {Boolean} includeEmptyLines If true, this includes ranges inside the block which are empty due to clipping + * @returns Range + * @method Selection.rectangularRangeBlock + **/ this.rectangularRangeBlock = function(screenCursor, screenAnchor, includeEmptyLines) { var rectSel = []; var xBackwards = screenCursor.column < screenAnchor.column; if (xBackwards) { @@ -319,26 +336,27 @@ }).call(Selection.prototype); // extend Editor var Editor = require("./editor").Editor; (function() { - - /** extension - * Editor.updateSelectionMarkers() - * + + /** + * * Updates the cursor and marker layers. + * @method Editor.updateSelectionMarkers + * **/ this.updateSelectionMarkers = function() { this.renderer.updateCursor(); this.renderer.updateBackMarkers(); }; - /** extension - * Editor.addSelectionMarker(orientedRange) -> Range - * - orientedRange (Range): A range containing a cursor - * + /** * Adds the selection and cursor. + * @param {Range} orientedRange A range containing a cursor + * @returns Range + * @method Editor.addSelectionMarker **/ this.addSelectionMarker = function(orientedRange) { if (!orientedRange.cursor) orientedRange.cursor = orientedRange.end; @@ -348,15 +366,14 @@ this.session.$selectionMarkers.push(orientedRange); this.session.selectionMarkerCount = this.session.$selectionMarkers.length; return orientedRange; }; - /** extension - * Editor.removeSelectionMarker(range) - * - range (Range): The selection range added with [[Editor.addSelectionMarker `addSelectionMarker()`]]. - * + /** * Removes the selection marker. + * @param {Range} The selection range added with [[Editor.addSelectionMarker `addSelectionMarker()`]]. + * @method Editor.removeSelectionMarker **/ this.removeSelectionMarker = function(range) { if (!range.marker) return; this.session.removeMarker(range.marker); @@ -395,11 +412,11 @@ this.$onMultiSelect = function(e) { if (this.inMultiSelectMode) return; this.inMultiSelectMode = true; - this.setStyle("multiselect"); + this.setStyle("ace_multiselect"); this.keyBinding.addKeyboardHandler(commands.keyboardHandler); this.commands.on("exec", this.$onMultiSelectExec); this.renderer.updateCursor(); this.renderer.updateBackMarkers(); @@ -408,11 +425,11 @@ this.$onSingleSelect = function(e) { if (this.session.multiSelect.inVirtualMode) return; this.inMultiSelectMode = false; - this.unsetStyle("multiselect"); + this.unsetStyle("ace_multiselect"); this.keyBinding.removeKeyboardHandler(commands.keyboardHandler); this.commands.removeEventListener("exec", this.$onMultiSelectExec); this.renderer.updateCursor(); this.renderer.updateBackMarkers(); @@ -436,17 +453,16 @@ command.multiSelectAction(editor, e.args || {}); } e.preventDefault(); }; - /** extension - * Editor.forEachSelection(cmd, args) - * - cmd (String): The command to execute - * - args (String): Any arguments for the command - * + /** * Executes a command for each selection range. - **/ + * @param {String} cmd The command to execute + * @param {String} args Any arguments for the command + * @method Editor.forEachSelection + **/ this.forEachSelection = function(cmd, args) { if (this.inVirtualSelectionMode) return; var session = this.session; @@ -473,14 +489,13 @@ this.onCursorChange(); this.onSelectionChange(); }; - /** extension - * Editor.exitMultiSelectMode() -> Void - * + /** * Removes all the selections except the last added one. + * @method Editor.exitMultiSelectMode **/ this.exitMultiSelectMode = function() { if (this.inVirtualSelectionMode) return; this.multiSelect.toSingleRange(); @@ -500,11 +515,15 @@ } return text; }; + // todo this should change when paste becomes a command this.onPaste = function(text) { + if (this.$readOnly) + return; + this._emit("paste", text); if (!this.inMultiSelectMode) return this.insert(text); var lines = text.split(/\r\n|\r|\n/); @@ -520,17 +539,17 @@ this.session.insert(range.start, lines[i]); } }; - /** extension - * Editor.findAll(needle, options, additive) -> Number - * - needle (String): The text to find - * - options (Object): The search options - * - additive (Boolean): keeps - * + /** * Finds and selects all the occurences of `needle`. + * @param {String} The text to find + * @param {Object} The search options + * @param {Boolean} keeps + * @returns Number + * @method Editor.findAll **/ this.findAll = function(needle, options, additive) { options = options || {}; options.needle = needle || options.needle; this.$search.set(options); @@ -551,18 +570,18 @@ this.$blockScrolling -= 1; return ranges.length; }; - // commands - /** extension - * Editor.selectMoreLines(dir, skip) - * - dir (Number): The direction of lines to select: -1 for up, 1 for down - * - skip (Boolean): If `true`, removes the active selection range - * + /** * Adds a cursor above or below the active cursor. - **/ + * + * @param {Number} dir The direction of lines to select: -1 for up, 1 for down + * @param {Boolean} skip If `true`, removes the active selection range + * + * @method Editor.selectMoreLines + */ this.selectMoreLines = function(dir, skip) { var range = this.selection.toOrientedRange(); var isBackwards = range.cursor == range.end; var screenLead = this.session.documentToScreenPosition(range.cursor); @@ -597,15 +616,14 @@ this.selection.addRange(newRange); if (toRemove) this.selection.substractPoint(toRemove); }; - /** extension - * Editor.transposeSelections(dir) - * - dir (Number): The direction to rotate selections - * - * Transposes the selected ranges. + /** + * Transposes the selected ranges. + * @param dir {Number} The direction to rotate selections + * @method Editor.transposeSelections **/ this.transposeSelections = function(dir) { var session = this.session; var sel = session.multiSelect; var all = sel.ranges; @@ -638,20 +656,19 @@ var tmp = range.clone(); session.replace(range, words[i]); range.start.row = tmp.start.row; range.start.column = tmp.start.column; } - } + }; - /** extension - * Editor.selectMore(dir, skip) - * - dir (Number): The direction of lines to select: -1 for up, 1 for down - * - skip (Boolean): If `true`, removes the active selection range - * + /** * Finds the next occurence of text in an active selection and adds it to the selections. + * @param {Number} dir The direction of lines to select: -1 for up, 1 for down + * @param {Boolean} skip If `true`, removes the active selection range + * @method Editor.selectMore **/ - this.selectMore = function (dir, skip) { + this.selectMore = function(dir, skip) { var session = this.session; var sel = session.multiSelect; var range = sel.toOrientedRange(); if (range.isEmpty()) { @@ -667,10 +684,124 @@ this.multiSelect.addRange(newRange); } if (skip) this.multiSelect.substractPoint(range.cursor); }; + + /** + * Aligns the cursors or selected text. + * @method Editor.alignCursors + **/ + this.alignCursors = function() { + var session = this.session; + var sel = session.multiSelect; + var ranges = sel.ranges; + + if (!ranges.length) { + var range = this.selection.getRange(); + var fr = range.start.row, lr = range.end.row; + var lines = this.session.doc.removeLines(fr, lr); + lines = this.$reAlignText(lines); + this.session.doc.insertLines(fr, lines); + range.start.column = 0; + range.end.column = lines[lines.length - 1].length; + this.selection.setRange(range); + } else { + // filter out ranges on same row + var row = -1; + var sameRowRanges = ranges.filter(function(r) { + if (r.cursor.row == row) + return true; + row = r.cursor.row; + }); + sel.$onRemoveRange(sameRowRanges); + + var maxCol = 0; + var minSpace = Infinity; + var spaceOffsets = ranges.map(function(r) { + var p = r.cursor; + var line = session.getLine(p.row); + var spaceOffset = line.substr(p.column).search(/\S/g); + if (spaceOffset == -1) + spaceOffset = 0; + + if (p.column > maxCol) + maxCol = p.column; + if (spaceOffset < minSpace) + minSpace = spaceOffset; + return spaceOffset; + }); + ranges.forEach(function(r, i) { + var p = r.cursor; + var l = maxCol - p.column; + var d = spaceOffsets[i] - minSpace; + if (l > d) + session.insert(p, lang.stringRepeat(" ", l - d)); + else + session.remove(new Range(p.row, p.column, p.row, p.column - l + d)); + + r.start.column = r.end.column = maxCol; + r.start.row = r.end.row = p.row; + r.cursor = r.end; + }); + sel.fromOrientedRange(ranges[0]); + this.renderer.updateCursor(); + this.renderer.updateBackMarkers(); + } + }; + + this.$reAlignText = function(lines) { + var isLeftAligned = true, isRightAligned = true; + var startW, textW, endW; + + return lines.map(function(line) { + var m = line.match(/(\s*)(.*?)(\s*)([=:].*)/); + if (!m) + return [line]; + + if (startW == null) { + startW = m[1].length; + textW = m[2].length; + endW = m[3].length; + return m; + } + + if (startW + textW + endW != m[1].length + m[2].length + m[3].length) + isRightAligned = false; + if (startW != m[1].length) + isLeftAligned = false; + + if (startW > m[1].length) + startW = m[1].length; + if (textW < m[2].length) + textW = m[2].length; + if (endW > m[3].length) + endW = m[3].length; + + return m; + }).map(isLeftAligned ? isRightAligned ? alignRight : alignLeft : unAlign); + + function strRepeat(n, ch) { + return Array(n + 1).join(ch) + } + + function alignLeft(m) { + return !m[2] ? m[0] : strRepeat(startW, " ") + m[2] + + strRepeat(textW - m[2].length + endW, " ") + + m[4].replace(/^([=:])\s+/, "$1 ") + } + function alignRight(m) { + return !m[2] ? m[0] : strRepeat(startW + textW - m[2].length, " ") + m[2] + + strRepeat(endW, " ") + + m[4].replace(/^([=:])\s+/, "$1 ") + } + function unAlign(m) { + return !m[2] ? m[0] : strRepeat(startW, " ") + m[2] + + strRepeat(endW, " ") + + m[4].replace(/^([=:])\s+/, "$1 ") + } + } }).call(Editor.prototype); function isSamePoint(p1, p2) { return p1.row == p2.row && p1.column == p2.column; @@ -712,10 +843,10 @@ else this.$onSingleSelect(); } }; -// MultiSelect(editor) +// MultiSelect(editor) // adds multiple selection support to the editor // (note: should be called only once for each editor instance) function MultiSelect(editor) { editor.$onAddRange = editor.$onAddRange.bind(editor); editor.$onRemoveRange = editor.$onRemoveRange.bind(editor);