123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972 |
- /* ***** BEGIN LICENSE BLOCK *****
- * Distributed under the BSD license:
- *
- * Copyright (c) 2010, Ajax.org B.V.
- * All rights reserved.
- *
- * 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.
- *
- * 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.
- *
- * ***** END LICENSE BLOCK ***** */
- define(function(ace_require, exports, module) {
- var RangeList = ace_require("./range_list").RangeList;
- var Range = ace_require("./range").Range;
- var Selection = ace_require("./selection").Selection;
- var onMouseDown = ace_require("./mouse/multi_select_handler").onMouseDown;
- var event = ace_require("./lib/event");
- var lang = ace_require("./lib/lang");
- var commands = ace_require("./commands/multi_select_commands");
- exports.commands = commands.defaultCommands.concat(commands.multiSelectCommands);
- // Todo: session.find or editor.findVolatile that returns range
- var Search = ace_require("./search").Search;
- var search = new Search();
- function find(session, needle, dir) {
- search.$options.wrap = true;
- search.$options.needle = needle;
- search.$options.backwards = dir == -1;
- return search.find(session);
- }
- // extend EditSession
- var EditSession = ace_require("./edit_session").EditSession;
- (function() {
- this.getSelectionMarkers = function() {
- return this.$selectionMarkers;
- };
- }).call(EditSession.prototype);
- // extend Selection
- (function() {
- // list of ranges in reverse addition order
- this.ranges = null;
- // automatically sorted list of ranges
- this.rangeList = null;
- /**
- * 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;
- if (!this.inMultiSelectMode && this.rangeCount === 0) {
- var oldRange = this.toOrientedRange();
- this.rangeList.add(oldRange);
- this.rangeList.add(range);
- if (this.rangeList.ranges.length != 2) {
- this.rangeList.removeAll();
- return $blockChangeEvents || this.fromOrientedRange(range);
- }
- this.rangeList.removeAll();
- this.rangeList.add(oldRange);
- this.$onAddRange(oldRange);
- }
- if (!range.cursor)
- range.cursor = range.end;
- var removed = this.rangeList.add(range);
- this.$onAddRange(range);
- if (removed.length)
- this.$onRemoveRange(removed);
- if (this.rangeCount > 1 && !this.inMultiSelectMode) {
- this._signal("multiSelect");
- this.inMultiSelectMode = true;
- this.session.$undoSelect = false;
- this.rangeList.attach(this.session);
- }
- 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);
- };
- /**
- * 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];
- }
- };
- /**
- * Merges overlapping ranges ensuring consistency after changes
- * @method Selection.mergeOverlappingRanges
- **/
- this.mergeOverlappingRanges = function() {
- var removed = this.rangeList.merge();
- if (removed.length)
- this.$onRemoveRange(removed);
- };
- this.$onAddRange = function(range) {
- this.rangeCount = this.rangeList.ranges.length;
- this.ranges.unshift(range);
- this._signal("addRange", {range: range});
- };
- this.$onRemoveRange = function(removed) {
- this.rangeCount = this.rangeList.ranges.length;
- if (this.rangeCount == 1 && this.inMultiSelectMode) {
- var lastRange = this.rangeList.ranges.pop();
- removed.push(lastRange);
- this.rangeCount = 0;
- }
- for (var i = removed.length; i--; ) {
- var index = this.ranges.indexOf(removed[i]);
- this.ranges.splice(index, 1);
- }
- this._signal("removeRange", {ranges: removed});
- if (this.rangeCount === 0 && this.inMultiSelectMode) {
- this.inMultiSelectMode = false;
- this._signal("singleSelect");
- this.session.$undoSelect = true;
- this.rangeList.detach(this.session);
- }
- lastRange = lastRange || this.ranges[0];
- if (lastRange && !lastRange.isEqual(this.getRange()))
- this.fromOrientedRange(lastRange);
- };
- // adds multicursor support to selection
- this.$initRangeList = function() {
- if (this.rangeList)
- return;
- 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.rangeCount ? this.rangeList.ranges.concat() : [this.getRange()];
- };
- /**
- * Splits all the ranges into lines.
- * @method Selection.splitIntoLines
- **/
- this.splitIntoLines = function () {
- var ranges = this.ranges.length ? this.ranges : [this.getRange()];
- var newRanges = [];
- for (var i = 0; i < ranges.length; i++) {
- var range = ranges[i];
- var row = range.start.row;
- var endRow = range.end.row;
- if (row === endRow) {
- newRanges.push(range.clone());
- } else {
- newRanges.push(new Range(row, range.start.column, row, this.session.getLine(row).length));
- while (++row < endRow)
- newRanges.push(this.getLineRange(row, true));
- newRanges.push(new Range(endRow, 0, endRow, range.end.column));
- }
- if (i == 0 && !this.isBackwards())
- newRanges = newRanges.reverse();
- }
- this.toSingleRange();
- for (var i = newRanges.length; i--;)
- this.addRange(newRanges[i]);
- };
-
- this.joinSelections = function () {
- 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);
- };
- /**
- * @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);
- this.toSingleRange();
- this.setSelectionRange(range, lastRange.cursor == lastRange.start);
- } else {
- var cursor = this.session.documentToScreenPosition(this.cursor);
- var anchor = this.session.documentToScreenPosition(this.anchor);
- var rectSel = this.rectangularRangeBlock(cursor, anchor);
- rectSel.forEach(this.addRange, this);
- }
- };
- /**
- *
- * 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) {
- var startColumn = screenCursor.column;
- var endColumn = screenAnchor.column;
- var startOffsetX = screenCursor.offsetX;
- var endOffsetX = screenAnchor.offsetX;
- } else {
- var startColumn = screenAnchor.column;
- var endColumn = screenCursor.column;
- var startOffsetX = screenAnchor.offsetX;
- var endOffsetX = screenCursor.offsetX;
- }
- var yBackwards = screenCursor.row < screenAnchor.row;
- if (yBackwards) {
- var startRow = screenCursor.row;
- var endRow = screenAnchor.row;
- } else {
- var startRow = screenAnchor.row;
- var endRow = screenCursor.row;
- }
- if (startColumn < 0)
- startColumn = 0;
- if (startRow < 0)
- startRow = 0;
- if (startRow == endRow)
- includeEmptyLines = true;
- var docEnd;
- for (var row = startRow; row <= endRow; row++) {
- var range = Range.fromPoints(
- this.session.screenToDocumentPosition(row, startColumn, startOffsetX),
- this.session.screenToDocumentPosition(row, endColumn, endOffsetX)
- );
- if (range.isEmpty()) {
- if (docEnd && isSamePoint(range.end, docEnd))
- break;
- docEnd = range.end;
- }
- range.cursor = xBackwards ? range.start : range.end;
- rectSel.push(range);
- }
- if (yBackwards)
- rectSel.reverse();
- if (!includeEmptyLines) {
- var end = rectSel.length - 1;
- while (rectSel[end].isEmpty() && end > 0)
- end--;
- if (end > 0) {
- var start = 0;
- while (rectSel[start].isEmpty())
- start++;
- }
- for (var i = end; i >= start; i--) {
- if (rectSel[i].isEmpty())
- rectSel.splice(i, 1);
- }
- }
- return rectSel;
- };
- }).call(Selection.prototype);
- // extend Editor
- var Editor = ace_require("./editor").Editor;
- (function() {
- /**
- *
- * Updates the cursor and marker layers.
- * @method Editor.updateSelectionMarkers
- *
- **/
- this.updateSelectionMarkers = function() {
- this.renderer.updateCursor();
- this.renderer.updateBackMarkers();
- };
- /**
- * 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;
- var style = this.getSelectionStyle();
- orientedRange.marker = this.session.addMarker(orientedRange, "ace_selection", style);
- this.session.$selectionMarkers.push(orientedRange);
- this.session.selectionMarkerCount = this.session.$selectionMarkers.length;
- return orientedRange;
- };
- /**
- * Removes the selection marker.
- * @param {Range} 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);
- var index = this.session.$selectionMarkers.indexOf(range);
- if (index != -1)
- this.session.$selectionMarkers.splice(index, 1);
- this.session.selectionMarkerCount = this.session.$selectionMarkers.length;
- };
- this.removeSelectionMarkers = function(ranges) {
- var markerList = this.session.$selectionMarkers;
- for (var i = ranges.length; i--; ) {
- var range = ranges[i];
- if (!range.marker)
- continue;
- this.session.removeMarker(range.marker);
- var index = markerList.indexOf(range);
- if (index != -1)
- markerList.splice(index, 1);
- }
- this.session.selectionMarkerCount = markerList.length;
- };
- this.$onAddRange = function(e) {
- this.addSelectionMarker(e.range);
- this.renderer.updateCursor();
- this.renderer.updateBackMarkers();
- };
- this.$onRemoveRange = function(e) {
- this.removeSelectionMarkers(e.ranges);
- this.renderer.updateCursor();
- this.renderer.updateBackMarkers();
- };
- this.$onMultiSelect = function(e) {
- if (this.inMultiSelectMode)
- return;
- this.inMultiSelectMode = true;
- this.setStyle("ace_multiselect");
- this.keyBinding.addKeyboardHandler(commands.keyboardHandler);
- this.commands.setDefaultHandler("exec", this.$onMultiSelectExec);
- this.renderer.updateCursor();
- this.renderer.updateBackMarkers();
- };
- this.$onSingleSelect = function(e) {
- if (this.session.multiSelect.inVirtualMode)
- return;
- this.inMultiSelectMode = false;
- this.unsetStyle("ace_multiselect");
- this.keyBinding.removeKeyboardHandler(commands.keyboardHandler);
- this.commands.removeDefaultHandler("exec", this.$onMultiSelectExec);
- this.renderer.updateCursor();
- this.renderer.updateBackMarkers();
- this._emit("changeSelection");
- };
- this.$onMultiSelectExec = function(e) {
- var command = e.command;
- var editor = e.editor;
- if (!editor.multiSelect)
- return;
- if (!command.multiSelectAction) {
- var result = command.exec(editor, e.args || {});
- editor.multiSelect.addRange(editor.multiSelect.toOrientedRange());
- editor.multiSelect.mergeOverlappingRanges();
- } else if (command.multiSelectAction == "forEach") {
- result = editor.forEachSelection(command, e.args);
- } else if (command.multiSelectAction == "forEachLine") {
- result = editor.forEachSelection(command, e.args, true);
- } else if (command.multiSelectAction == "single") {
- editor.exitMultiSelectMode();
- result = command.exec(editor, e.args || {});
- } else {
- result = command.multiSelectAction(editor, e.args || {});
- }
- return result;
- };
- /**
- * Executes a command for each selection range.
- * @param {Object} cmd The command to execute
- * @param {String} args Any arguments for the command
- * @method Editor.forEachSelection
- **/
- this.forEachSelection = function(cmd, args, options) {
- if (this.inVirtualSelectionMode)
- return;
- var keepOrder = options && options.keepOrder;
- var $byLines = options == true || options && options.$byLines;
- var session = this.session;
- var selection = this.selection;
- var rangeList = selection.rangeList;
- var ranges = (keepOrder ? selection : rangeList).ranges;
- var result;
-
- if (!ranges.length)
- return cmd.exec ? cmd.exec(this, args || {}) : cmd(this, args || {});
-
- var reg = selection._eventRegistry;
- selection._eventRegistry = {};
- var tmpSel = new Selection(session);
- this.inVirtualSelectionMode = true;
- for (var i = ranges.length; i--;) {
- if ($byLines) {
- while (i > 0 && ranges[i].start.row == ranges[i - 1].end.row)
- i--;
- }
- tmpSel.fromOrientedRange(ranges[i]);
- tmpSel.index = i;
- this.selection = session.selection = tmpSel;
- var cmdResult = cmd.exec ? cmd.exec(this, args || {}) : cmd(this, args || {});
- if (!result && cmdResult !== undefined)
- result = cmdResult;
- tmpSel.toOrientedRange(ranges[i]);
- }
- tmpSel.detach();
- this.selection = session.selection = selection;
- this.inVirtualSelectionMode = false;
- selection._eventRegistry = reg;
- selection.mergeOverlappingRanges();
- if (selection.ranges[0])
- selection.fromOrientedRange(selection.ranges[0]);
-
- var anim = this.renderer.$scrollAnimation;
- this.onCursorChange();
- this.onSelectionChange();
- if (anim && anim.from == anim.to)
- this.renderer.animateScrolling(anim.from);
-
- return result;
- };
- /**
- * Removes all the selections except the last added one.
- * @method Editor.exitMultiSelectMode
- **/
- this.exitMultiSelectMode = function() {
- if (!this.inMultiSelectMode || this.inVirtualSelectionMode)
- return;
- this.multiSelect.toSingleRange();
- };
- this.getSelectedText = function() {
- var text = "";
- if (this.inMultiSelectMode && !this.inVirtualSelectionMode) {
- var ranges = this.multiSelect.rangeList.ranges;
- var buf = [];
- for (var i = 0; i < ranges.length; i++) {
- buf.push(this.session.getTextRange(ranges[i]));
- }
- var nl = this.session.getDocument().getNewLineCharacter();
- text = buf.join(nl);
- if (text.length == (buf.length - 1) * nl.length)
- text = "";
- } else if (!this.selection.isEmpty()) {
- text = this.session.getTextRange(this.getSelectionRange());
- }
- return text;
- };
-
- this.$checkMultiselectChange = function(e, anchor) {
- if (this.inMultiSelectMode && !this.inVirtualSelectionMode) {
- var range = this.multiSelect.ranges[0];
- if (this.multiSelect.isEmpty() && anchor == this.multiSelect.anchor)
- return;
- var pos = anchor == this.multiSelect.anchor
- ? range.cursor == range.start ? range.end : range.start
- : range.cursor;
- if (pos.row != anchor.row
- || this.session.$clipPositionToDocument(pos.row, pos.column).column != anchor.column)
- this.multiSelect.toSingleRange(this.multiSelect.toOrientedRange());
- else
- this.multiSelect.mergeOverlappingRanges();
- }
- };
- /**
- * Finds and selects all the occurrences of `needle`.
- * @param {String} The text to find
- * @param {Object} The search options
- * @param {Boolean} keeps
- *
- * @returns {Number} The cumulative count of all found matches
- * @method Editor.findAll
- **/
- this.findAll = function(needle, options, additive) {
- options = options || {};
- options.needle = needle || options.needle;
- if (options.needle == undefined) {
- var range = this.selection.isEmpty()
- ? this.selection.getWordRange()
- : this.selection.getRange();
- options.needle = this.session.getTextRange(range);
- }
- this.$search.set(options);
-
- var ranges = this.$search.findAll(this.session);
- if (!ranges.length)
- return 0;
- var selection = this.multiSelect;
- if (!additive)
- selection.toSingleRange(ranges[0]);
- for (var i = ranges.length; i--; )
- selection.addRange(ranges[i], true);
- // keep old selection as primary if possible
- if (range && selection.rangeList.rangeAtPoint(range.start))
- selection.addRange(range, true);
-
- return ranges.length;
- };
- /**
- * 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);
- if (this.selection.$desiredColumn)
- screenLead.column = this.selection.$desiredColumn;
- var lead = this.session.screenToDocumentPosition(screenLead.row + dir, screenLead.column);
- if (!range.isEmpty()) {
- var screenAnchor = this.session.documentToScreenPosition(isBackwards ? range.end : range.start);
- var anchor = this.session.screenToDocumentPosition(screenAnchor.row + dir, screenAnchor.column);
- } else {
- var anchor = lead;
- }
- if (isBackwards) {
- var newRange = Range.fromPoints(lead, anchor);
- newRange.cursor = newRange.start;
- } else {
- var newRange = Range.fromPoints(anchor, lead);
- newRange.cursor = newRange.end;
- }
- newRange.desiredColumn = screenLead.column;
- if (!this.selection.inMultiSelectMode) {
- this.selection.addRange(range);
- } else {
- if (skip)
- var toRemove = range.cursor;
- }
- this.selection.addRange(newRange);
- if (toRemove)
- this.selection.substractPoint(toRemove);
- };
- /**
- * Transposes the selected ranges.
- * @param {Number} dir The direction to rotate selections
- * @method Editor.transposeSelections
- **/
- this.transposeSelections = function(dir) {
- var session = this.session;
- var sel = session.multiSelect;
- var all = sel.ranges;
- for (var i = all.length; i--; ) {
- var range = all[i];
- if (range.isEmpty()) {
- var tmp = session.getWordRange(range.start.row, range.start.column);
- range.start.row = tmp.start.row;
- range.start.column = tmp.start.column;
- range.end.row = tmp.end.row;
- range.end.column = tmp.end.column;
- }
- }
- sel.mergeOverlappingRanges();
- var words = [];
- for (var i = all.length; i--; ) {
- var range = all[i];
- words.unshift(session.getTextRange(range));
- }
- if (dir < 0)
- words.unshift(words.pop());
- else
- words.push(words.shift());
- for (var i = all.length; i--; ) {
- var range = all[i];
- var tmp = range.clone();
- session.replace(range, words[i]);
- range.start.row = tmp.start.row;
- range.start.column = tmp.start.column;
- }
- sel.fromOrientedRange(sel.ranges[0]);
- };
- /**
- * Finds the next occurrence 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, stopAtFirst) {
- var session = this.session;
- var sel = session.multiSelect;
- var range = sel.toOrientedRange();
- if (range.isEmpty()) {
- range = session.getWordRange(range.start.row, range.start.column);
- range.cursor = dir == -1 ? range.start : range.end;
- this.multiSelect.addRange(range);
- if (stopAtFirst)
- return;
- }
- var needle = session.getTextRange(range);
- var newRange = find(session, needle, dir);
- if (newRange) {
- newRange.cursor = dir == -1 ? newRange.start : newRange.end;
- this.session.unfold(newRange);
- this.multiSelect.addRange(newRange);
- this.renderer.scrollCursorIntoView(null, 0.5);
- }
- 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;
- // 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;
- });
-
- if (!ranges.length || sameRowRanges.length == ranges.length - 1) {
- var range = this.selection.getRange();
- var fr = range.start.row, lr = range.end.row;
- var guessRange = fr == lr;
- if (guessRange) {
- var max = this.session.getLength();
- var line;
- do {
- line = this.session.getLine(lr);
- } while (/[=:]/.test(line) && ++lr < max);
- do {
- line = this.session.getLine(fr);
- } while (/[=:]/.test(line) && --fr > 0);
-
- if (fr < 0) fr = 0;
- if (lr >= max) lr = max - 1;
- }
- var lines = this.session.removeFullLines(fr, lr);
- lines = this.$reAlignText(lines, guessRange);
- this.session.insert({row: fr, column: 0}, lines.join("\n") + "\n");
- if (!guessRange) {
- range.start.column = 0;
- range.end.column = lines[lines.length - 1].length;
- }
- this.selection.setRange(range);
- } else {
- sameRowRanges.forEach(function(r) {
- sel.substractPoint(r.cursor);
- });
- 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, forceLeft) {
- 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(forceLeft ? alignLeft :
- isLeftAligned ? isRightAligned ? alignRight : alignLeft : unAlign);
- function spaces(n) {
- return lang.stringRepeat(" ", n);
- }
- function alignLeft(m) {
- return !m[2] ? m[0] : spaces(startW) + m[2]
- + spaces(textW - m[2].length + endW)
- + m[4].replace(/^([=:])\s+/, "$1 ");
- }
- function alignRight(m) {
- return !m[2] ? m[0] : spaces(startW + textW - m[2].length) + m[2]
- + spaces(endW)
- + m[4].replace(/^([=:])\s+/, "$1 ");
- }
- function unAlign(m) {
- return !m[2] ? m[0] : spaces(startW) + m[2]
- + spaces(endW)
- + m[4].replace(/^([=:])\s+/, "$1 ");
- }
- };
- }).call(Editor.prototype);
- function isSamePoint(p1, p2) {
- return p1.row == p2.row && p1.column == p2.column;
- }
- // patch
- // adds multicursor support to a session
- exports.onSessionChange = function(e) {
- var session = e.session;
- if (session && !session.multiSelect) {
- session.$selectionMarkers = [];
- session.selection.$initRangeList();
- session.multiSelect = session.selection;
- }
- this.multiSelect = session && session.multiSelect;
- var oldSession = e.oldSession;
- if (oldSession) {
- oldSession.multiSelect.off("addRange", this.$onAddRange);
- oldSession.multiSelect.off("removeRange", this.$onRemoveRange);
- oldSession.multiSelect.off("multiSelect", this.$onMultiSelect);
- oldSession.multiSelect.off("singleSelect", this.$onSingleSelect);
- oldSession.multiSelect.lead.off("change", this.$checkMultiselectChange);
- oldSession.multiSelect.anchor.off("change", this.$checkMultiselectChange);
- }
- if (session) {
- session.multiSelect.on("addRange", this.$onAddRange);
- session.multiSelect.on("removeRange", this.$onRemoveRange);
- session.multiSelect.on("multiSelect", this.$onMultiSelect);
- session.multiSelect.on("singleSelect", this.$onSingleSelect);
- session.multiSelect.lead.on("change", this.$checkMultiselectChange);
- session.multiSelect.anchor.on("change", this.$checkMultiselectChange);
- }
- if (session && this.inMultiSelectMode != session.selection.inMultiSelectMode) {
- if (session.selection.inMultiSelectMode)
- this.$onMultiSelect();
- else
- this.$onSingleSelect();
- }
- };
- // MultiSelect(editor)
- // adds multiple selection support to the editor
- // (note: should be called only once for each editor instance)
- function MultiSelect(editor) {
- if (editor.$multiselectOnSessionChange)
- return;
- editor.$onAddRange = editor.$onAddRange.bind(editor);
- editor.$onRemoveRange = editor.$onRemoveRange.bind(editor);
- editor.$onMultiSelect = editor.$onMultiSelect.bind(editor);
- editor.$onSingleSelect = editor.$onSingleSelect.bind(editor);
- editor.$multiselectOnSessionChange = exports.onSessionChange.bind(editor);
- editor.$checkMultiselectChange = editor.$checkMultiselectChange.bind(editor);
- editor.$multiselectOnSessionChange(editor);
- editor.on("changeSession", editor.$multiselectOnSessionChange);
- editor.on("mousedown", onMouseDown);
- editor.commands.addCommands(commands.defaultCommands);
- addAltCursorListeners(editor);
- }
- function addAltCursorListeners(editor){
- if (!editor.textInput) return;
- var el = editor.textInput.getElement();
- var altCursor = false;
- event.addListener(el, "keydown", function(e) {
- var altDown = e.keyCode == 18 && !(e.ctrlKey || e.shiftKey || e.metaKey);
- if (editor.$blockSelectEnabled && altDown) {
- if (!altCursor) {
- editor.renderer.setMouseCursor("crosshair");
- altCursor = true;
- }
- } else if (altCursor) {
- reset();
- }
- }, editor);
- event.addListener(el, "keyup", reset, editor);
- event.addListener(el, "blur", reset, editor);
- function reset(e) {
- if (altCursor) {
- editor.renderer.setMouseCursor("");
- altCursor = false;
- // TODO disable menu popping up
- // e && e.preventDefault()
- }
- }
- }
- exports.MultiSelect = MultiSelect;
- ace_require("./config").defineOptions(Editor.prototype, "editor", {
- enableMultiselect: {
- set: function(val) {
- MultiSelect(this);
- if (val) {
- this.on("changeSession", this.$multiselectOnSessionChange);
- this.on("mousedown", onMouseDown);
- } else {
- this.off("changeSession", this.$multiselectOnSessionChange);
- this.off("mousedown", onMouseDown);
- }
- },
- value: true
- },
- enableBlockSelect: {
- set: function(val) {
- this.$blockSelectEnabled = val;
- },
- value: true
- }
- });
- });
|