diff --git a/build b/build index 75ba742f..1e3407af 160000 --- a/build +++ b/build @@ -1 +1 @@ -Subproject commit 75ba742f4f00cefadfef1ae451cfb2a4280ad367 +Subproject commit 1e3407af692eaf661800083897c73d46d0a4bc46 diff --git a/lib/ace/commands/default_commands.js b/lib/ace/commands/default_commands.js index 94f68d76..88621eaf 100644 --- a/lib/ace/commands/default_commands.js +++ b/lib/ace/commands/default_commands.js @@ -295,6 +295,7 @@ exports.commands = [{ name: "selecttomatching", bindKey: bindKey("Ctrl-Shift-P", null), exec: function(editor) { editor.jumpToMatching(true); }, + multiSelectAction: "forEach", readOnly: true }, @@ -315,7 +316,7 @@ exports.commands = [{ name: "removeline", bindKey: bindKey("Ctrl-D", "Command-D"), exec: function(editor) { editor.removeLines(); }, - multiSelectAction: "forEach" + multiSelectAction: "forEachLine" }, { name: "duplicateSelection", bindKey: bindKey("Ctrl-Shift-D", "Command-Shift-D"), @@ -325,12 +326,12 @@ exports.commands = [{ name: "sortlines", bindKey: bindKey("Ctrl-Alt-S", "Command-Alt-S"), exec: function(editor) { editor.sortLines(); }, - multiSelectAction: "forEach" + multiSelectAction: "forEachLine" }, { name: "togglecomment", bindKey: bindKey("Ctrl-/", "Command-/"), exec: function(editor) { editor.toggleCommentLines(); }, - multiSelectAction: "forEach" + multiSelectAction: "forEachLine" }, { name: "modifyNumberUp", bindKey: bindKey("Ctrl-Shift-Up", "Alt-Shift-Up"), @@ -418,12 +419,12 @@ exports.commands = [{ name: "blockoutdent", bindKey: bindKey("Ctrl-[", "Ctrl-["), exec: function(editor) { editor.blockOutdent(); }, - multiSelectAction: "forEach" + multiSelectAction: "forEachLine" },{ name: "blockindent", bindKey: bindKey("Ctrl-]", "Ctrl-]"), exec: function(editor) { editor.blockIndent(); }, - multiSelectAction: "forEach" + multiSelectAction: "forEachLine" }, { name: "insertstring", exec: function(editor, str) { editor.insert(str); }, diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index efd1270f..0d4efec8 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -3,7 +3,7 @@ * * 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 @@ -14,7 +14,7 @@ * * 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 @@ -31,9 +31,9 @@ define(function(require, exports, module) { "use strict"; -var config = require("./config"); var oop = require("./lib/oop"); var lang = require("./lib/lang"); +var config = require("./config"); var EventEmitter = require("./lib/event_emitter").EventEmitter; var Selection = require("./selection").Selection; var TextMode = require("./mode/text").Mode; @@ -41,18 +41,15 @@ var Range = require("./range").Range; var Document = require("./document").Document; var BackgroundTokenizer = require("./background_tokenizer").BackgroundTokenizer; var SearchHighlight = require("./search_highlight").SearchHighlight; -var config = require("./config"); /** + * Stores all the data about [[Editor `Editor`]] state providing easy way to change editors state. * - * - * Stores all the data about [[Editor `Editor`]] state providing easy way to change editors state. - * * `EditSession` can be attached to only one [[Document `Document`]]. Same `Document` can be attached to several `EditSession`s. * @class EditSession **/ -// events +//{ events /** * * Emitted when the document changes. @@ -96,19 +93,19 @@ var config = require("./config"); * @param {Object} e An object containing one property, `"data"`, that contains information about the changing rows * **/ -/** +/** * Emitted when the current mode changes. * * @event changeMode * **/ -/** +/** * Emitted when the wrap mode changes. * * @event changeWrapMode * **/ -/** +/** * Emitted when the wrapping limit changes. * * @event changeWrapLimit @@ -132,7 +129,7 @@ var config = require("./config"); * * @param {Number} scrollLeft The new scroll left value **/ - +//} /** * @@ -150,18 +147,14 @@ var EditSession = function(text, mode) { this.$backMarkers = {}; this.$markerId = 1; this.$undoSelect = true; - + this.$foldData = []; this.$foldData.toString = function() { - var str = ""; - this.forEach(function(foldLine) { - str += "\n" + foldLine.toString(); - }); - return str; + return this.join("\n"); } this.on("changeFold", this.onChangeFold.bind(this)); this.$onChange = this.onChange.bind(this); - + if (typeof text != "object" || !text.getLine) text = new Document(text); @@ -200,13 +193,13 @@ var EditSession = function(text, mode) { /** * Returns the `Document` associated with this session. - * @return {Document} + * @return {Document} **/ this.getDocument = function() { return this.doc; }; - /** + /** * @param {Number} row The row to work with * **/ @@ -299,19 +292,19 @@ var EditSession = function(text, mode) { this.getUndoManager().reset(); }; - /** + /** * Returns the current [[Document `Document`]] as a string. * @method toString * @returns {String} - * @alias EditSession.getValue + * @alias EditSession.getValue * **/ - - /** + + /** * Returns the current [[Document `Document`]] as a string. * @method getValue * @returns {String} - * @alias EditSession.toString + * @alias EditSession.toString **/ this.getValue = this.toString = function() { @@ -325,7 +318,7 @@ var EditSession = function(text, mode) { return this.selection; }; - /** + /** * {:BackgroundTokenizer.getState} * @param {Number} row The row to start at * @@ -335,7 +328,7 @@ var EditSession = function(text, mode) { return this.bgTokenizer.getState(row); }; - /** + /** * Starts tokenizing at the row indicated. Returns a list of objects of the tokenized rows. * @param {Number} row The row to start at * @@ -489,7 +482,7 @@ var EditSession = function(text, mode) { this.$overwrite = false; /** - * Pass in `true` to enable overwrites in your session, or `false` to disable. + * Pass in `true` to enable overwrites in your session, or `false` to disable. * * If overwrites is enabled, any text you enter will type over any text after it. If the value of `overwrite` changes, this function also emites the `changeOverwrite` event. * @@ -543,7 +536,7 @@ var EditSession = function(text, mode) { this.$decorations[row] = (this.$decorations[row] || "").replace(" " + className, ""); this._emit("changeBreakpoint", {}); }; - + /** * Returns an array of numbers, indicating which rows have breakpoints. * @returns {[Number]} @@ -641,7 +634,7 @@ var EditSession = function(text, mode) { * @param {Object} marker object with update method * @param {Boolean} inFront Set to `true` to establish a front marker * - * + * * @return {Object} The added marker **/ this.addDynamicMarker = function(marker, inFront) { @@ -698,7 +691,7 @@ var EditSession = function(text, mode) { } this.$searchHighlight.setRegexp(re); } - + // experimental this.highlightLines = function(startRow, endRow, clazz, inFront) { if (typeof endRow != "number") { @@ -707,12 +700,12 @@ var EditSession = function(text, mode) { } if (!clazz) clazz = "ace_step"; - + var range = new Range(startRow, 0, endRow, Infinity); range.id = this.addMarker(range, clazz, "fullLine", inFront); return range; }; - + /* * Error: * { @@ -747,7 +740,7 @@ var EditSession = function(text, mode) { this.setAnnotations([]); }; - /** + /** * If `text` contains either the newline (`\n`) or carriage-return ('\r') characters, `$autoNewLine` stores that value. * @param {String} text A block of text * @@ -778,7 +771,7 @@ var EditSession = function(text, mode) { if (!inToken) inToken = !!line.charAt(column).match(this.tokenRe); - + if (inToken) var re = this.tokenRe; else if (/^\s+$/.test(line.slice(column-1, column+1))) @@ -820,7 +813,7 @@ var EditSession = function(text, mode) { return wordRange; }; - /** + /** * {:Document.setNewLineMode.desc} * @param {String} newLineMode {:Document.setNewLineMode.param} * @@ -831,7 +824,7 @@ var EditSession = function(text, mode) { this.doc.setNewLineMode(newLineMode); }; - /** + /** * * Returns the current new line mode. * @returns {String} @@ -958,7 +951,6 @@ var EditSession = function(text, mode) { this.$worker = null; }; - this.$startWorker = function() { if (typeof Worker !== "undefined" && !require.noWorker) { try { @@ -1068,7 +1060,7 @@ var EditSession = function(text, mode) { } }; - /** + /** * Returns a verbatim copy of the given line as it is in the document * @param {Number} row The row to retrieve from * @@ -1080,7 +1072,7 @@ var EditSession = function(text, mode) { return this.doc.getLine(row); }; - /** + /** * Returns an array of strings of the rows between `firstRow` and `lastRow`. This function is inclusive of `lastRow`. * @param {Number} firstRow The first row index to retrieve * @param {Number} lastRow The final row index to retrieve @@ -1092,7 +1084,7 @@ var EditSession = function(text, mode) { return this.doc.getLines(firstRow, lastRow); }; - /** + /** * Returns the number of rows in the document. * @returns {Number} **/ @@ -1100,7 +1092,7 @@ var EditSession = function(text, mode) { return this.doc.getLength(); }; - /** + /** * {:Document.getTextRange.desc} * @param {Range} range The range to work with * @@ -1110,7 +1102,7 @@ var EditSession = function(text, mode) { return this.doc.getTextRange(range || this.selection.getRange()); }; - /** + /** * Inserts a block of `text` and the indicated `position`. * @param {Object} position The position {row, column} to start inserting at * @param {String} text A chunk of text to insert @@ -1122,7 +1114,7 @@ var EditSession = function(text, mode) { return this.doc.insert(position, text); }; - /** + /** * Removes the `range` from the document. * @param {Range} range A specified Range to remove * @returns {Object} The new `start` property of the range, which contains `startRow` and `startColumn`. If `range` is empty, this function returns the unmodified value of `range.start`. @@ -1139,7 +1131,7 @@ var EditSession = function(text, mode) { * @param {Array} deltas An array of previous changes * @param {Boolean} dontSelect [If `true`, doesn't select the range of where the change occured]{: #dontSelect} * - * + * * @returns {Range} **/ this.undoChanges = function(deltas, dontSelect) { @@ -1210,7 +1202,7 @@ var EditSession = function(text, mode) { this.$getUndoSelection = function(deltas, isUndo, lastUndoRange) { function isInsert(delta) { var insert = - delta.action == "insertText" || delta.action == "insertLines"; + delta.action === "insertText" || delta.action === "insertLines"; return isUndo ? !insert : insert; } @@ -1261,16 +1253,16 @@ var EditSession = function(text, mode) { return range; }; - /** + /** * Replaces a range in the document with the new `text`. * * @param {Range} range A specified Range to replace * @param {String} text The new text to use as a replacement - * @returns {Object} An object containing the final row and column, like this: + * @returns {Object} An object containing the final row and column, like this: * ``` * {row: endRow, column: 0} - * ``` - * If the text and range are empty, this function returns an object containing the current `range.start` value. + * ``` + * If the text and range are empty, this function returns an object containing the current `range.start` value. * If the text is the exact same as what currently exists, this function returns an object containing the current `range.end` value. * * @@ -1295,32 +1287,44 @@ var EditSession = function(text, mode) { * * **/ - this.moveText = function(fromRange, toPosition) { + this.moveText = function(fromRange, toPosition, copy) { var text = this.getTextRange(fromRange); - this.remove(fromRange); + var folds = this.getFoldsInRange(fromRange); - var toRow = toPosition.row; - var toColumn = toPosition.column; - - // Make sure to update the insert location, when text is removed in - // front of the chosen point of insertion. - if (!fromRange.isMultiLine() && fromRange.start.row == toRow && - fromRange.end.column < toColumn) - toColumn -= text.length; - - if (fromRange.isMultiLine() && fromRange.end.row < toRow) { - var lines = this.doc.$split(text); - toRow -= lines.length - 1; + var toRange = Range.fromPoints(toPosition, toPosition); + if (!copy) { + this.remove(fromRange); + var rowDiff = fromRange.start.row - fromRange.end.row; + var collDiff = rowDiff ? -fromRange.end.column : fromRange.start.column - fromRange.end.column; + if (collDiff) { + if (toRange.start.row == fromRange.end.row && toRange.start.column > fromRange.end.column) + toRange.start.column += collDiff; + if (toRange.end.row == fromRange.end.row && toRange.end.column > fromRange.end.column) + toRange.end.column += collDiff; + } + if (rowDiff && toRange.start.row >= fromRange.end.row) { + toRange.start.row += rowDiff; + toRange.end.row += rowDiff; + } } - var endRow = toRow + fromRange.end.row - fromRange.start.row; - var endColumn = fromRange.isMultiLine() ? - fromRange.end.column : - toColumn + fromRange.end.column - fromRange.start.column; - - var toRange = new Range(toRow, toColumn, endRow, endColumn); - this.insert(toRange.start, text); + if (folds.length) { + var oldStart = fromRange.start; + var newStart = toRange.start; + var rowDiff = newStart.row - oldStart.row; + var collDiff = newStart.column - oldStart.column; + this.addFolds(folds.map(function(x) { + x = x.clone(); + if (x.start.row == oldStart.row) + x.start.column += collDiff; + if (x.end.row == oldStart.row) + x.end.column += collDiff; + x.start.row += rowDiff; + x.end.row += rowDiff; + return x; + })); + } return toRange; }; @@ -1371,7 +1375,39 @@ var EditSession = function(text, mode) { } }; - /** + this.$moveLines = function(firstRow, lastRow, dir) { + firstRow = this.getRowFoldStart(firstRow); + lastRow = this.getRowFoldEnd(lastRow); + if (dir < 0) { + var row = this.getRowFoldStart(firstRow + dir); + if (row < 0) return 0; + var diff = row-firstRow; + } else if (dir > 0) { + var row = this.getRowFoldEnd(lastRow + dir); + if (row > this.doc.getLength()-1) return 0; + var diff = row-lastRow; + } else { + firstRow = this.$clipRowToDocument(firstRow); + lastRow = this.$clipRowToDocument(lastRow); + var diff = lastRow - firstRow + 1; + } + + var range = new Range(firstRow, 0, lastRow, Number.MAX_VALUE); + var folds = this.getFoldsInRange(range).map(function(x){ + x = x.clone(); + x.start.row += diff; + x.end.row += diff; + return x; + }); + + var lines = dir == 0 + ? this.doc.getLines(firstRow, lastRow) + : this.doc.removeLines(firstRow, lastRow); + this.doc.insertLines(firstRow+diff, lines); + folds.length && this.addFolds(folds); + return diff; + }; + /** * Shifts all the lines in the document up one, starting from `firstRow` and ending at `lastRow`. * @param {Number} firstRow The starting row to move up * @param {Number} lastRow The final row to move up @@ -1381,29 +1417,19 @@ var EditSession = function(text, mode) { * **/ this.moveLinesUp = function(firstRow, lastRow) { - if (firstRow <= 0) return 0; - - var removed = this.doc.removeLines(firstRow, lastRow); - this.doc.insertLines(firstRow - 1, removed); - return -1; + return this.$moveLines(firstRow, lastRow, -1); }; - /** + /** * Shifts all the lines in the document down one, starting from `firstRow` and ending at `lastRow`. * @param {Number} firstRow The starting row to move down * @param {Number} lastRow The final row to move down * @returns {Number} If `firstRow` is less-than or equal to 0, this function returns 0. Otherwise, on success, it returns -1. * - * - * * @related Document.insertLines **/ this.moveLinesDown = function(firstRow, lastRow) { - if (lastRow >= this.doc.getLength()-1) return 0; - - var removed = this.doc.removeLines(firstRow, lastRow); - this.doc.insertLines(firstRow+1, removed); - return 1; + return this.$moveLines(firstRow, lastRow, 1); }; /** @@ -1415,14 +1441,7 @@ var EditSession = function(text, mode) { * **/ this.duplicateLines = function(firstRow, lastRow) { - var firstRow = this.$clipRowToDocument(firstRow); - var lastRow = this.$clipRowToDocument(lastRow); - - var lines = this.getLines(firstRow, lastRow); - this.doc.insertLines(firstRow, lines); - - var addedRows = lastRow - firstRow + 1; - return addedRows; + return this.$moveLines(firstRow, lastRow, 0); }; @@ -1546,12 +1565,12 @@ var EditSession = function(text, mode) { } }; - /** + /** * This should generally only be called by the renderer when a resize is detected. * @param {Number} desiredLimit The new wrap limit * @returns {Boolean} * - * + * * @private **/ this.adjustWrapLimit = function(desiredLimit) { @@ -2000,7 +2019,7 @@ var EditSession = function(text, mode) { } }; - /** + /** * Returns the position (on screen) for the last character in the provided screen row. * @param {Number} screenRow The screen row to check * @returns {Number} @@ -2012,21 +2031,21 @@ var EditSession = function(text, mode) { return this.documentToScreenColumn(pos.row, pos.column); }; - /** + /** * For the given document row and column, this returns the column position of the last screen row. - * @param {Number} docRow + * @param {Number} docRow * - * @param {Number} docColumn + * @param {Number} docColumn **/ this.getDocumentLastRowColumn = function(docRow, docColumn) { var screenRow = this.documentToScreenRow(docRow, docColumn); return this.getScreenLastRowColumn(screenRow); }; - /** + /** * For the given document row and column, this returns the document position of the last row. - * @param {Number} docRow - * @param {Number} docColumn + * @param {Number} docRow + * @param {Number} docColumn * * **/ @@ -2035,7 +2054,7 @@ var EditSession = function(text, mode) { return this.screenToDocumentPosition(screenRow, Number.MAX_VALUE / 10); }; - /** + /** * For the given row, this returns the split data. * @returns {String} **/ @@ -2051,7 +2070,7 @@ var EditSession = function(text, mode) { * The distance to the next tab stop at the specified screen column. * @param {Number} screenColumn The screen column to check * - * + * * @returns {Number} **/ this.getScreenTabSize = function(screenColumn) { @@ -2068,7 +2087,7 @@ var EditSession = function(text, mode) { return this.screenToDocumentPosition(screenRow, screenColumn).column; }; - /** + /** * Converts characters coordinates on the screen to characters coordinates within the document. [This takes into account code folding, word wrap, tab size, and any other visual modifications.]{: #conversionConsiderations} * @param {Number} screenRow The screen row to check * @param {Number} screenColumn The screen column to check @@ -2117,7 +2136,7 @@ var EditSession = function(text, mode) { foldStart = foldLine ? foldLine.start.row : Infinity; } } - + if (doCache) { this.$docRowCache.push(docRow); this.$screenRowCache.push(row); @@ -2162,7 +2181,7 @@ var EditSession = function(text, mode) { return {row: docRow, column: docColumn}; }; - /** + /** * Converts document coordinates to screen coordinates. {:conversionConsiderations} * @param {Number} docRow The document row to check * @param {Number} docColumn The document column to check @@ -2381,9 +2400,9 @@ config.defineOptions(EditSession.prototype, "session", { if (!value) { this.setUseWrapMode(false); } else { - var col = typeof value == "number" && value; + var col = typeof value == "number" ? value : null; this.setUseWrapMode(true); - this.setWrapLimitRange(value, value); + this.setWrapLimitRange(col, col); } this.$wrap = value; }, diff --git a/lib/ace/edit_session/fold.js b/lib/ace/edit_session/fold.js index 3a66f9c5..0f898021 100644 --- a/lib/ace/edit_session/fold.js +++ b/lib/ace/edit_session/fold.js @@ -31,6 +31,9 @@ define(function(require, exports, module) { "use strict"; +var Range = require("../range").Range; +var RangeList = require("../range_list").RangeList; +var oop = require("../lib/oop") /* * Simple fold-data struct. **/ @@ -42,9 +45,11 @@ var Fold = exports.Fold = function(range, placeholder) { this.end = range.end; this.sameRow = range.start.row == range.end.row; - this.subFolds = []; + this.subFolds = this.ranges = []; }; +oop.inherits(Fold, RangeList); + (function() { this.toString = function() { @@ -64,17 +69,21 @@ var Fold = exports.Fold = function(range, placeholder) { this.subFolds.forEach(function(subFold) { fold.subFolds.push(subFold.clone()); }); + fold.collapseChildren = this.collapseChildren; return fold; }; this.addSubFold = function(fold) { if (this.range.isEqual(fold)) - return this; + return; if (!this.range.containsRange(fold)) throw "A fold can't intersect already existing fold" + fold.range + this.range; - var row = fold.range.start.row, column = fold.range.start.column; + // transform fold to local coordinates + consumeRange(fold, this.start); + + var row = fold.start.row, column = fold.start.column; for (var i = 0, cmp = -1; i < this.subFolds.length; i++) { cmp = this.subFolds[i].range.compare(row, column); if (cmp != 1) @@ -102,7 +111,30 @@ var Fold = exports.Fold = function(range, placeholder) { return fold; }; + + this.restoreRange = function(range) { + return restoreRange(range, this.start); + }; }).call(Fold.prototype); +function consumePoint(point, anchor) { + point.row -= anchor.row; + if (point.row == 0) + point.column -= anchor.column; +} +function consumeRange(range, anchor) { + consumePoint(range.start, anchor); + consumePoint(range.end, anchor); +} +function restorePoint(point, anchor) { + if (point.row == 0) + point.column += anchor.column; + point.row += anchor.row; +} +function restoreRange(range, anchor) { + restorePoint(range.start, anchor); + restorePoint(range.end, anchor); +} + }); diff --git a/lib/ace/edit_session/fold_line.js b/lib/ace/edit_session/fold_line.js index cec3eb4c..e9f732c4 100644 --- a/lib/ace/edit_session/fold_line.js +++ b/lib/ace/edit_session/fold_line.js @@ -192,13 +192,13 @@ function FoldLine(foldData, folds) { } this.split = function(row, column) { - var fold = this.getNextFoldTo(row, column).fold, - folds = this.folds; + var fold = this.getNextFoldTo(row, column).fold; + var folds = this.folds; var foldData = this.foldData; - if (!fold) { + if (!fold) return null; - } + var i = folds.indexOf(fold); var foldBefore = folds[i - 1]; this.end.row = foldBefore.end.row; diff --git a/lib/ace/edit_session/folding.js b/lib/ace/edit_session/folding.js index 3480bad2..765ec81c 100644 --- a/lib/ace/edit_session/folding.js +++ b/lib/ace/edit_session/folding.js @@ -118,11 +118,6 @@ function Folding() { function addFold(fold) { folds.push(fold); - if (!fold.subFolds) - return; - - for (var i = 0; i < fold.subFolds.length; i++) - addFold(fold.subFolds[i]); } for (var i = 0; i < foldLines.length; i++) @@ -265,9 +260,10 @@ function Folding() { if (placeholder instanceof Fold) fold = placeholder; - else + else { fold = new Fold(range, placeholder); - + fold.collapseChildren = range.collapseChildren; + } this.$clipRangeToDocument(fold.range); var startRow = fold.start.row; @@ -297,7 +293,9 @@ function Folding() { // Remove the folds from fold data. this.removeFolds(folds); // Add the removed folds as subfolds on the new fold. - fold.subFolds = folds; + folds.forEach(function(subFold) { + fold.addSubFold(subFold); + }); } for (var i = 0; i < foldData.length; i++) { @@ -306,8 +304,7 @@ function Folding() { foldLine.addFold(fold); added = true; break; - } - else if (startRow == foldLine.end.row) { + } else if (startRow == foldLine.end.row) { foldLine.addFold(fold); added = true; if (!fold.sameRow) { @@ -320,8 +317,7 @@ function Folding() { } } break; - } - else if (endRow <= foldLine.start.row) { + } else if (endRow <= foldLine.start.row) { break; } } @@ -414,10 +410,14 @@ function Folding() { }; this.expandFold = function(fold) { - this.removeFold(fold); - fold.subFolds.forEach(function(fold) { - this.addFold(fold); + this.removeFold(fold); + fold.subFolds.forEach(function(subFold) { + fold.restoreRange(subFold); + this.addFold(subFold); }, this); + if (fold.collapseChildren > 0) { + this.foldAll(fold.start.row+1, fold.end.row, fold.collapseChildren-1); + } fold.subFolds = []; }; @@ -429,9 +429,10 @@ function Folding() { this.unfold = function(location, expandInner) { var range, folds; - if (location == null) + if (location == null) { range = new Range(0, 0, this.getLength(), 0); - else if (typeof location == "number") + expandInner = true; + } else if (typeof location == "number") range = new Range(location, 0, location, this.getLine(location).length); else if ("row" in location) range = Range.fromPoints(location, location); @@ -464,6 +465,11 @@ function Folding() { return foldLine ? foldLine.end.row : docRow; }; + this.getRowFoldStart = function(docRow, startFoldRow) { + var foldLine = this.getFoldLine(docRow, startFoldRow); + return foldLine ? foldLine.start.row : docRow; + }; + this.getFoldDisplayLine = function(foldLine, endRow, endColumn, startRow, startColumn) { if (startRow == null) { startRow = foldLine.start.row; @@ -480,20 +486,20 @@ function Folding() { var textLine = ""; foldLine.walk(function(placeholder, row, column, lastColumn) { - if (row < startRow) { + if (row < startRow) return; - } else if (row == startRow) { - if (column < startColumn) { + if (row == startRow) { + if (column < startColumn) return; - } lastColumn = Math.max(startColumn, lastColumn); } + if (placeholder != null) { textLine += placeholder; } else { textLine += doc.getLine(row).substring(lastColumn, column); } - }.bind(this), endRow, endColumn); + }, endRow, endColumn); return textLine; }; @@ -535,26 +541,22 @@ function Folding() { if (fold) { this.expandFold(fold); return; - } - else if (bracketPos = this.findMatchingBracket(cursor)) { + } else if (bracketPos = this.findMatchingBracket(cursor)) { if (range.comparePoint(bracketPos) == 1) { range.end = bracketPos; - } - else { + } else { range.start = bracketPos; range.start.column++; range.end.column--; } - } - else if (bracketPos = this.findMatchingBracket({row: cursor.row, column: cursor.column + 1})) { + } else if (bracketPos = this.findMatchingBracket({row: cursor.row, column: cursor.column + 1})) { if (range.comparePoint(bracketPos) == 1) range.end = bracketPos; else range.start = bracketPos; range.start.column++; - } - else { + } else { range = this.getCommentFoldRange(cursor.row, cursor.column) || range; } } else { @@ -562,8 +564,7 @@ function Folding() { if (tryToUnfold && folds.length) { this.expandFolds(folds); return; - } - else if (folds.length == 1 ) { + } else if (folds.length == 1 ) { fold = folds[0]; } } @@ -619,7 +620,9 @@ function Folding() { } }; - this.foldAll = function(startRow, endRow) { + this.foldAll = function(startRow, endRow, depth) { + if (depth == undefined) + depth = 100000; // JSON.stringify doesn't hanle Infinity var foldWidgets = this.foldWidgets; endRow = endRow || this.getLength(); for (var row = startRow || 0; row < endRow; row++) { @@ -630,13 +633,16 @@ function Folding() { var range = this.getFoldWidgetRange(row); // sometimes range can be incompatible with existing fold - // wouldn't it be better for addFold to return null istead of throwing? + // TODO change addFold to return null istead of throwing if (range && range.end.row <= endRow) try { - this.addFold("...", range); + var fold = this.addFold("...", range); + fold.collapseChildren = depth; } catch(e) {} + row = range.end.row; } }; + // structured folding this.$foldStyles = { "manual": 1, "markbegin": 1, @@ -661,7 +667,6 @@ function Folding() { this.$setFolding(mode); }; - // structured folding this.$setFolding = function(foldMode) { if (this.$foldMode == foldMode) return; @@ -685,21 +690,46 @@ function Folding() { }; + this.getParentFoldRangeData = function (row, ignoreCurrent) { + var fw = this.foldWidgets; + if (!fw || (ignoreCurrent && fw[row])) + return {}; + + var i = row - 1, firstRange; + while (i >= 0) { + var c = fw[i]; + if (c == null) + c = fw[i] = this.getFoldWidget(i); + + if (c == "start") { + var range = this.getFoldWidgetRange(i); + if (!firstRange) + firstRange = range; + if (range && range.end.row >= row) + break; + } + i--; + } + + return { + range: i !== -1 && range, + firstRange: firstRange + }; + } + this.onFoldWidgetClick = function(row, e) { - e = e.domEvent; var type = this.getFoldWidget(row); var line = this.getLine(row); - var onlySubfolds = e.shiftKey; - var addSubfolds = onlySubfolds || e.ctrlKey || e.altKey || e.metaKey; - var fold; + e = e.domEvent; + var children = e.shiftKey; + var all = e.ctrlKey || e.metaKey; + var siblings = e.altKey; - if (type == "end") - fold = this.getFoldAt(row, 0, -1); - else - fold = this.getFoldAt(row, line.length, 1); + var dir = type === "end" ? -1 : 1; + var fold = this.getFoldAt(row, dir === -1 ? 0 : line.length, dir); if (fold) { - if (addSubfolds) + if (children || all) this.removeFold(fold); else this.expandFold(fold); @@ -707,28 +737,35 @@ function Folding() { } var range = this.getFoldWidgetRange(row); - if (range) { - // sometimes singleline folds can be missed by the code above - if (!range.isMultiLine()) { - fold = this.getFoldAt(range.start.row, range.start.column, 1); - if (fold && range.isEqual(fold.range)) { - this.removeFold(fold); - return; - } + // sometimes singleline folds can be missed by the code above + if (range && !range.isMultiLine()) { + fold = this.getFoldAt(range.start.row, range.start.column, 1); + if (fold && range.isEqual(fold.range)) { + this.removeFold(fold); + return; } - - if (!onlySubfolds) - this.addFold("...", range); - - if (addSubfolds) - this.foldAll(range.start.row + 1, range.end.row); - } else { - if (addSubfolds) - this.foldAll(row + 1, this.getLength()); - (e.target || e.srcElement).className += " ace_invalid" } + + if (siblings) { + var data = this.getParentFoldRangeData(row); + if (data.range) { + var startRow = data.range.start.row + 1; + var endRow = data.range.end.row; + } + this.foldAll(startRow, endRow, all ? 10000 : 0); + } else if (children) { + var endRow = range ? range.end.row : this.getLength(); + this.foldAll(row + 1, range.end.row, all ? 10000 : 0); + } else if (range) { + if (all) + range.collapseChildren = 10000; + this.addFold("...", range); + } + + if (!range) + (e.target || e.srcElement).className += " ace_invalid" }; - + this.updateFoldWidgets = function(e) { var delta = e.data; var range = delta.range; diff --git a/lib/ace/editor.js b/lib/ace/editor.js index e77bab90..bd534d06 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -3,7 +3,7 @@ * * 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 @@ -14,7 +14,7 @@ * * 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 @@ -51,9 +51,9 @@ var config = require("./config"); /** * * - * The main entry point into the Ace functionality. + * The main entry point into the Ace functionality. * - * The `Editor` manages the [[EditSession]] (which manages [[Document]]s), as well as the [[VirtualRenderer]], which draws everything to the screen. + * The `Editor` manages the [[EditSession]] (which manages [[Document]]s), as well as the [[VirtualRenderer]], which draws everything to the screen. * * Event sessions dealing with the mouse and keyboard are bubbled up from `Document` to the `Editor`, which decides what to do with them. * @class Editor @@ -100,7 +100,7 @@ var Editor = function(renderer, session) { * Sets a new key handler, such as "vim" or "windows". * @param {String} keyboardHandler The new key handler * - * + * **/ this.setKeyboardHandler = function(keyboardHandler) { if (typeof keyboardHandler == "string" && keyboardHandler) { @@ -116,11 +116,11 @@ var Editor = function(renderer, session) { } }; - /** + /** * Returns the keyboard handler, such as "vim" or "windows". * * @returns {String} - * + * **/ this.getKeyboardHandler = function() { return this.keyBinding.getKeyboardHandler(); @@ -247,7 +247,7 @@ var Editor = function(renderer, session) { return this.session; }; - /** + /** * Sets the current document to `val`. * @param {String} val The new value to set for the document * @param {Number} cursorPos Where to set the new value. `undefined` or 0 is selectAll, -1 is at the document start, and 1 is at the end @@ -268,7 +268,7 @@ var Editor = function(renderer, session) { return val; }; - /** + /** * Returns the current session's content. * * @returns {String} @@ -279,7 +279,7 @@ var Editor = function(renderer, session) { }; /** - * + * * Returns the currently highlighted selection. * @returns {String} The highlighted selection **/ @@ -287,11 +287,11 @@ var Editor = function(renderer, session) { return this.selection; }; - /** + /** * {:VirtualRenderer.onResize} * @param {Boolean} force If `true`, recomputes the size, even if the height and width haven't changed * - * + * * @related VirtualRenderer.onResize **/ this.resize = function(force) { @@ -308,9 +308,9 @@ var Editor = function(renderer, session) { this.renderer.setTheme(theme); }; - /** + /** * {:VirtualRenderer.getTheme} - * + * * @returns {String} The set theme * @related VirtualRenderer.getTheme **/ @@ -322,14 +322,14 @@ var Editor = function(renderer, session) { * {:VirtualRenderer.setStyle} * @param {String} style A class name * - * + * * @related VirtualRenderer.setStyle **/ this.setStyle = function(style) { this.renderer.setStyle(style); }; - /** + /** * {:VirtualRenderer.unsetStyle} * @related VirtualRenderer.unsetStyle **/ @@ -340,8 +340,8 @@ var Editor = function(renderer, session) { /** * Set a new font size (in pixels) for the editor text. * @param {String} size A font size ( _e.g._ "12px") - * - * + * + * **/ this.setFontSize = function(size) { if (typeof size == "number") @@ -375,7 +375,7 @@ var Editor = function(renderer, session) { }; /** - * + * * Brings the current `textInput` into focus. **/ this.focus = function() { @@ -398,7 +398,7 @@ var Editor = function(renderer, session) { }; /** - * + * * Blurs the current `textInput`. **/ this.blur = function() { @@ -407,9 +407,9 @@ var Editor = function(renderer, session) { /** * Emitted once the editor comes into focus. - * @event focus - * - * + * @event focus + * + * **/ this.onFocus = function() { if (this.$isFocused) @@ -423,8 +423,8 @@ var Editor = function(renderer, session) { /** * Emitted once the editor has been blurred. * @event blur - * - * + * + * **/ this.onBlur = function() { if (!this.$isFocused) @@ -440,12 +440,12 @@ var Editor = function(renderer, session) { }; /** - * Emitted whenever the document is changed. + * Emitted whenever the document is changed. * @event change * @param {Object} e Contains a single property, `data`, which has the delta of changes * * - * + * **/ this.onDocumentChange = function(e) { var delta = e.data; @@ -473,14 +473,14 @@ var Editor = function(renderer, session) { this.onScrollTopChange = function() { this.renderer.scrollToY(this.session.getScrollTop()); }; - + this.onScrollLeftChange = function() { this.renderer.scrollToX(this.session.getScrollLeft()); }; /** * Emitted when the selection changes. - * + * **/ this.onCursorChange = function() { this.$cursorChange(); @@ -508,7 +508,7 @@ var Editor = function(renderer, session) { session.$highlightLineMarker = null; } else if (!session.$highlightLineMarker && highlight) { var range = new Range(highlight.row, highlight.column, highlight.row, Infinity); - range.id = session.addMarker(range, "ace_active-line", "screenLine"); + range.id = session.addMarker(range, "ace_active-line", "screenLine"); session.$highlightLineMarker = range; } else if (highlight) { session.$highlightLineMarker.start.row = highlight.row; @@ -536,7 +536,7 @@ var Editor = function(renderer, session) { var re = this.$highlightSelectedWord && this.$getSelectionHighLightRegexp() this.session.highlight(re); - + this._emit("changeSelection"); }; @@ -616,10 +616,10 @@ var Editor = function(renderer, session) { /** * Emitted when text is copied. - * @event copy + * @event copy * @param {String} text The copied text * - * + * **/ /** * @@ -665,7 +665,7 @@ var Editor = function(renderer, session) { **/ this.onPaste = function(text) { // todo this should change when paste becomes a command - if (this.$readOnly) + if (this.$readOnly) return; this._emit("paste", text); this.insert(text); @@ -679,8 +679,8 @@ var Editor = function(renderer, session) { /** * Inserts `text` into wherever the cursor is pointing. * @param {String} text The new text to add - * - * + * + * **/ this.insert = function(text) { var session = this.session; @@ -780,10 +780,10 @@ var Editor = function(renderer, session) { this.keyBinding.onCommandKey(e, hashId, keyCode); }; - /** + /** * Pass in `true` to enable overwrites in your session, or `false` to disable. If overwrites is enabled, any text you enter will type over any text after it. If the value of `overwrite` changes, this function also emites the `changeOverwrite` event. * @param {Boolean} overwrite Defines wheter or not to set overwrites - * + * * * @related EditSession.setOverwrite **/ @@ -791,7 +791,7 @@ var Editor = function(renderer, session) { this.session.setOverwrite(overwrite); }; - /** + /** * Returns `true` if overwrites are enabled; `false` otherwise. * @returns {Boolean} * @related EditSession.getOverwrite @@ -800,7 +800,7 @@ var Editor = function(renderer, session) { return this.session.getOverwrite(); }; - /** + /** * Sets the value of overwrite to the opposite of whatever it currently is. * @related EditSession.toggleOverwrite **/ @@ -861,7 +861,7 @@ var Editor = function(renderer, session) { this.getSelectionStyle = function() { return this.getOption("selectionStyle"); }; - + /** * Determines whether or not the current line should be highlighted. * @param {Boolean} shouldHighlight Set to `true` to highlight the current line @@ -911,7 +911,7 @@ var Editor = function(renderer, session) { /** * If `showInvisibiles` is set to `true`, invisible characters—like spaces or new lines—are show in the editor. * @param {Boolean} showInvisibles Specifies whether or not to show invisible characters - * + * **/ this.setShowInvisibles = function(showInvisibles) { this.renderer.setShowInvisibles(showInvisibles); @@ -936,7 +936,7 @@ var Editor = function(renderer, session) { /** * If `showPrintMargin` is set to `true`, the print margin is shown in the editor. * @param {Boolean} showPrintMargin Specifies whether or not to show the print margin - * + * **/ this.setShowPrintMargin = function(showPrintMargin) { this.renderer.setShowPrintMargin(showPrintMargin); @@ -970,7 +970,7 @@ var Editor = function(renderer, session) { /** * If `readOnly` is true, then the editor is set to read-only mode, and none of the content can change. * @param {Boolean} readOnly Specifies whether the editor can be modified or not - * + * **/ this.setReadOnly = function(readOnly) { this.setOption("readOnly", readOnly); @@ -987,7 +987,7 @@ var Editor = function(renderer, session) { /** * Specifies whether to use behaviors or not. ["Behaviors" in this case is the auto-pairing of special characters, like quotation marks, parenthesis, or brackets.]{: #BehaviorsDef} * @param {Boolean} enabled Enables or disables behaviors - * + * **/ this.setBehavioursEnabled = function (enabled) { this.setOption("behavioursEnabled", enabled); @@ -995,7 +995,7 @@ var Editor = function(renderer, session) { /** * Returns `true` if the behaviors are currently enabled. {:BehaviorsDef} - * + * * @returns {Boolean} **/ this.getBehavioursEnabled = function () { @@ -1006,7 +1006,7 @@ var Editor = function(renderer, session) { * Specifies whether to use wrapping behaviors or not, i.e. automatically wrapping the selection with characters such as brackets * when such a character is typed in. * @param {Boolean} enabled Enables or disables wrapping behaviors - * + * **/ this.setWrapBehavioursEnabled = function (enabled) { this.setOption("wrapBehavioursEnabled", enabled); @@ -1046,7 +1046,7 @@ var Editor = function(renderer, session) { /** * Removes words of text from the editor. A "word" is defined as a string of characters bookended by whitespace. * @param {String} dir The direction of the deletion to occur, either "left" or "right" - * + * **/ this.remove = function(dir) { if (this.selection.isEmpty()){ @@ -1191,7 +1191,7 @@ var Editor = function(renderer, session) { /** * Inserts an indentation into the current cursor position or indents the selected lines. - * + * * @related EditSession.indentRows **/ this.indent = function() { @@ -1261,7 +1261,7 @@ var Editor = function(renderer, session) { }; /** - * + * * Given the currently selected range, this function either comments all the lines, or uncomments all of them. **/ this.toggleCommentLines = function() { @@ -1293,7 +1293,7 @@ var Editor = function(renderer, session) { } return null; }; - + /** * If the character before the cursor is a number, this functions changes its value by `amount`. * @param {Number} amount The value to change the numeral by (can be negative to decrease value) @@ -1318,14 +1318,14 @@ var Editor = function(renderer, session) { var t = parseFloat(nr.value); t *= Math.pow(10, decimals); - + if(fp !== nr.end && column < fp){ amount *= Math.pow(10, nr.end - column - 1); } else { amount *= Math.pow(10, nr.end - column); } - + t += amount; t /= Math.pow(10, decimals); var nnr = t.toFixed(decimals); @@ -1340,8 +1340,8 @@ var Editor = function(renderer, session) { } } }; - - /** + + /** * Removes all the lines in the current selection * @related EditSession.remove **/ @@ -1367,17 +1367,16 @@ var Editor = function(renderer, session) { var row = range.start.row; doc.duplicateLines(row, row); } else { - var reverse = sel.isBackwards() - var point = sel.isBackwards() ? range.start : range.end; + var point = reverse ? range.start : range.end; var endPoint = doc.insert(point, doc.getTextRange(range), false); range.start = point; range.end = endPoint; - + sel.setSelectionRange(range, reverse) } }; - - /** + + /** * Shifts all the selected lines down one row. * * @returns {Number} On success, it returns -1. @@ -1389,7 +1388,7 @@ var Editor = function(renderer, session) { }); }; - /** + /** * Shifts all the selected lines up one row. * @returns {Number} On success, it returns -1. * @related EditSession.moveLinesDown @@ -1400,28 +1399,25 @@ var Editor = function(renderer, session) { }); }; - /** + /** * Moves a range of text from the given range to the given position. `toPosition` is an object that looks like this: * ```json * { row: newRowLocation, column: newColumnLocation } * ``` * @param {Range} fromRange The range of text you want moved within the document * @param {Object} toPosition The location (row and column) where you want to move the text to - * + * * @returns {Range} The new range where the text was moved to. * @related EditSession.moveText **/ this.moveText = function(range, toPosition) { - if (this.$readOnly) - return null; - return this.session.moveText(range, toPosition); }; - /** + /** * Copies all the selected lines up one row. * @returns {Number} On success, returns 0. - * + * **/ this.copyLinesUp = function() { this.$moveLines(function(firstRow, lastRow) { @@ -1430,7 +1426,7 @@ var Editor = function(renderer, session) { }); }; - /** + /** * Copies all the selected lines down one row. * @returns {Number} On success, returns the number of new rows added; in other words, `lastRow - firstRow + 1`. * @related EditSession.duplicateLines @@ -1446,29 +1442,43 @@ var Editor = function(renderer, session) { /** * Executes a specific function, which can be anything that manipulates selected lines, such as copying them, duplicating them, or shifting them. * @param {Function} mover A method to call on each selected row - * + * * **/ this.$moveLines = function(mover) { - var rows = this.$getSelectedRows(); var selection = this.selection; - if (!selection.isMultiLine()) { - var range = selection.getRange(); - var reverse = selection.isBackwards(); - } - - var linesMoved = mover.call(this, rows.first, rows.last); - - if (range) { - range.start.row += linesMoved; - range.end.row += linesMoved; - selection.setSelectionRange(range, reverse); - } - else { - selection.setSelectionAnchor(rows.last+linesMoved+1, 0); - selection.$moveSelection(function() { - selection.moveCursorTo(rows.first+linesMoved, 0); - }); + if (!selection.inMultiSelectMode || this.inVirtualSelectionMode) { + var range = selection.toOrientedRange(); + var rows = this.$getSelectedRows(range); + var linesMoved = mover.call(this, rows.first, rows.last); + range.moveBy(linesMoved, 0); + selection.fromOrientedRange(range); + } else { + var ranges = selection.rangeList.ranges; + selection.rangeList.detach(this.session); + + for (var i = ranges.length; i--; ) { + var rangeIndex = i; + var rows = ranges[i].collapseRows(); + var last = rows.end.row; + var first = rows.start.row; + while (i--) { + var rows = ranges[i].collapseRows(); + if (first - rows.end.row <= 1) + first = rows.end.row; + else + break; + } + i++; + + var linesMoved = mover.call(this, first, last); + while (rangeIndex >= i) { + ranges[rangeIndex].moveBy(linesMoved, 0); + rangeIndex--; + } + } + selection.fromOrientedRange(selection.ranges[0]); + selection.rangeList.attach(this.session); } }; @@ -1502,7 +1512,7 @@ var Editor = function(renderer, session) { this.renderer.hideComposition(); }; - /** + /** * {:VirtualRenderer.getFirstVisibleRow} * * @returns {Number} @@ -1512,7 +1522,7 @@ var Editor = function(renderer, session) { return this.renderer.getFirstVisibleRow(); }; - /** + /** * {:VirtualRenderer.getLastVisibleRow} * * @returns {Number} @@ -1525,7 +1535,7 @@ var Editor = function(renderer, session) { /** * Indicates if the row is currently visible on the screen. * @param {Number} row The row to check - * + * * @returns {Boolean} **/ this.isRowVisible = function(row) { @@ -1535,8 +1545,8 @@ var Editor = function(renderer, session) { /** * Indicates if the entire row is currently visible on the screen. * @param {Number} row The row to check - * - * + * + * * @returns {Boolean} **/ this.isRowFullyVisible = function(row) { @@ -1618,7 +1628,7 @@ var Editor = function(renderer, session) { this.$moveByPage(-1); }; - /** + /** * Moves the editor to the specified row. * @related VirtualRenderer.scrollToRow **/ @@ -1626,14 +1636,14 @@ var Editor = function(renderer, session) { this.renderer.scrollToRow(row); }; - /** + /** * Scrolls to a line. If `center` is `true`, it puts the line in middle of screen (or attempts to). * @param {Number} line The line to scroll to - * @param {Boolean} center If `true` + * @param {Boolean} center If `true` * @param {Boolean} animate If `true` animates scrolling * @param {Function} callback Function to be called when the animation has finished * - * + * * @related VirtualRenderer.scrollToLine **/ this.scrollToLine = function(line, center, animate, callback) { @@ -1652,9 +1662,9 @@ var Editor = function(renderer, session) { this.renderer.alignCursor(pos, 0.5); }; - /** + /** * Gets the current position of the cursor. - * @returns {Object} An object that looks something like this: + * @returns {Object} An object that looks something like this: * * ```json * { row: currRow, column: currCol } @@ -1666,7 +1676,7 @@ var Editor = function(renderer, session) { return this.selection.getCursor(); }; - /** + /** * Returns the screen position of the cursor. * @returns {Number} * @related EditSession.documentToScreenPosition @@ -1675,7 +1685,7 @@ var Editor = function(renderer, session) { return this.session.documentToScreenPosition(this.getCursorPosition()); }; - /** + /** * {:Selection.getRange} * @returns {Range} * @related Selection.getRange @@ -1685,7 +1695,7 @@ var Editor = function(renderer, session) { }; - /** + /** * Selects all the text in editor. * @related Selection.selectAll **/ @@ -1695,7 +1705,7 @@ var Editor = function(renderer, session) { this.$blockScrolling -= 1; }; - /** + /** * {:Selection.clearSelection} * @related Selection.clearSelection **/ @@ -1703,7 +1713,7 @@ var Editor = function(renderer, session) { this.selection.clearSelection(); }; - /** + /** * Moves the cursor to the specified row and column. Note that this does not de-select the current selection. * @param {Number} row The new row number * @param {Number} column The new column number @@ -1715,10 +1725,10 @@ var Editor = function(renderer, session) { this.selection.moveCursorTo(row, column); }; - /** + /** * Moves the cursor to the position indicated by `pos.row` and `pos.column`. * @param {Object} pos An object with two properties, row and column - * + * * * @related Selection.moveCursorToPosition **/ @@ -1726,7 +1736,7 @@ var Editor = function(renderer, session) { this.selection.moveCursorToPosition(pos); }; - /** + /** * Moves the cursor's row and column to the next matching bracket. * **/ @@ -1746,7 +1756,7 @@ var Editor = function(renderer, session) { if (pos.row == cursor.row && Math.abs(pos.column - cursor.column) < 2) range = this.session.getBracketRange(pos); } - + pos = range && range.cursor || pos; if (pos) { if (select) { @@ -1766,7 +1776,7 @@ var Editor = function(renderer, session) { * @param {Number} lineNumber The line number to go to * @param {Number} column A column number to go to * @param {Boolean} animate If `true` animates scolling - * + * **/ this.gotoLine = function(lineNumber, column, animate) { this.selection.clearSelection(); @@ -1780,7 +1790,7 @@ var Editor = function(renderer, session) { this.scrollToLine(lineNumber - 1, true, animate); }; - /** + /** * Moves the cursor to the specified row and column. Note that this does de-select the current selection. * @param {Number} row The new row number * @param {Number} column The new column number @@ -1796,8 +1806,8 @@ var Editor = function(renderer, session) { /** * Moves the cursor up in the document the specified number of times. Note that this does de-select the current selection. * @param {Number} times The number of times to change navigation - * - * + * + * **/ this.navigateUp = function(times) { if (this.selection.isMultiLine() && !this.selection.isBackwards()) { @@ -1812,8 +1822,8 @@ var Editor = function(renderer, session) { /** * Moves the cursor down in the document the specified number of times. Note that this does de-select the current selection. * @param {Number} times The number of times to change navigation - * - * + * + * **/ this.navigateDown = function(times) { if (this.selection.isMultiLine() && this.selection.isBackwards()) { @@ -1828,8 +1838,8 @@ var Editor = function(renderer, session) { /** * Moves the cursor left in the document the specified number of times. Note that this does de-select the current selection. * @param {Number} times The number of times to change navigation - * - * + * + * **/ this.navigateLeft = function(times) { if (!this.selection.isEmpty()) { @@ -1848,8 +1858,8 @@ var Editor = function(renderer, session) { /** * Moves the cursor right in the document the specified number of times. Note that this does de-select the current selection. * @param {Number} times The number of times to change navigation - * - * + * + * **/ this.navigateRight = function(times) { if (!this.selection.isEmpty()) { @@ -1866,7 +1876,7 @@ var Editor = function(renderer, session) { }; /** - * + * * Moves the cursor to the start of the current line. Note that this does de-select the current selection. **/ this.navigateLineStart = function() { @@ -1875,7 +1885,7 @@ var Editor = function(renderer, session) { }; /** - * + * * Moves the cursor to the end of the current line. Note that this does de-select the current selection. **/ this.navigateLineEnd = function() { @@ -1884,7 +1894,7 @@ var Editor = function(renderer, session) { }; /** - * + * * Moves the cursor to the end of the current file. Note that this does de-select the current selection. **/ this.navigateFileEnd = function() { @@ -1895,7 +1905,7 @@ var Editor = function(renderer, session) { }; /** - * + * * Moves the cursor to the start of the current file. Note that this does de-select the current selection. **/ this.navigateFileStart = function() { @@ -1906,7 +1916,7 @@ var Editor = function(renderer, session) { }; /** - * + * * Moves the cursor to the word immediately to the right of the current position. Note that this does de-select the current selection. **/ this.navigateWordRight = function() { @@ -1915,7 +1925,7 @@ var Editor = function(renderer, session) { }; /** - * + * * Moves the cursor to the word immediately to the left of the current position. Note that this does de-select the current selection. **/ this.navigateWordLeft = function() { @@ -1996,7 +2006,7 @@ var Editor = function(renderer, session) { } }; - /** + /** * {:Search.getOptions} For more information on `options`, see [[Search `Search`]]. * @related Search.getOptions * @returns {Object} @@ -2005,7 +2015,7 @@ var Editor = function(renderer, session) { return this.$search.getOptions(); }; - /** + /** * Attempts to find `needle` within the document. For more information on `options`, see [[Search `Search`]]. * @param {String} needle The text to search for (optional) * @param {Object} options An object defining various search properties @@ -2053,7 +2063,7 @@ var Editor = function(renderer, session) { this.selection.setRange(range); }; - /** + /** * Performs another search for `needle` in the document. For more information on `options`, see [[Search `Search`]]. * @param {Object} options search options * @param {Boolean} animate If `true` animate scrolling @@ -2065,7 +2075,7 @@ var Editor = function(renderer, session) { this.find({skipCurrent: true, backwards: false}, options, animate); }; - /** + /** * Performs a search for `needle` backwards. For more information on `options`, see [[Search `Search`]]. * @param {Object} options search options * @param {Boolean} animate If `true` animate scrolling @@ -2089,7 +2099,7 @@ var Editor = function(renderer, session) { this.renderer.animateScrolling(scrollTop); }; - /** + /** * {:UndoManager.undo} * @related UndoManager.undo **/ @@ -2100,7 +2110,7 @@ var Editor = function(renderer, session) { this.renderer.scrollCursorIntoView(null, 0.5); }; - /** + /** * {:UndoManager.redo} * @related UndoManager.redo **/ @@ -2111,16 +2121,16 @@ var Editor = function(renderer, session) { this.renderer.scrollCursorIntoView(null, 0.5); }; - /** - * + /** + * * Cleans up the entire editor. **/ this.destroy = function() { this.renderer.destroy(); this._emit("destroy", this); }; - - /** + + /** * Enables automatic scrolling of the cursor into view when editor itself is inside scrollable element * @param {Boolean} enable default true **/ @@ -2228,4 +2238,4 @@ config.defineOptions(Editor.prototype, "editor", { }); exports.Editor = Editor; -}); \ No newline at end of file +}); diff --git a/lib/ace/editor_text_edit_test.js b/lib/ace/editor_text_edit_test.js index 7f8a6dbf..c145f474 100644 --- a/lib/ace/editor_text_edit_test.js +++ b/lib/ace/editor_text_edit_test.js @@ -248,7 +248,7 @@ module.exports = { assert.range(editor.getSelectionRange(), 0, 3, 1, 0); }, - "test: move lines down should select moved lines" : function() { + "test: move lines down should keep selection on moved lines" : function() { var session = new EditSession(["11", "22", "33", "44"].join("\n")); var editor = new Editor(new MockRenderer(), session); @@ -257,25 +257,25 @@ module.exports = { editor.moveLinesDown(); assert.equal(["33", "11", "22", "44"].join("\n"), session.toString()); - assert.position(editor.getCursorPosition(), 1, 0); - assert.position(editor.getSelection().getSelectionAnchor(), 3, 0); - assert.position(editor.getSelection().getSelectionLead(), 1, 0); + assert.position(editor.getCursorPosition(), 2, 1); + assert.position(editor.getSelection().getSelectionAnchor(), 1, 1); + assert.position(editor.getSelection().getSelectionLead(), 2, 1); editor.moveLinesDown(); assert.equal(["33", "44", "11", "22"].join("\n"), session.toString()); - assert.position(editor.getCursorPosition(), 2, 0); - assert.position(editor.getSelection().getSelectionAnchor(), 3, 2); - assert.position(editor.getSelection().getSelectionLead(), 2, 0); + assert.position(editor.getCursorPosition(), 3, 1); + assert.position(editor.getSelection().getSelectionAnchor(), 2, 1); + assert.position(editor.getSelection().getSelectionLead(), 3, 1); // moving again should have no effect editor.moveLinesDown(); assert.equal(["33", "44", "11", "22"].join("\n"), session.toString()); - assert.position(editor.getCursorPosition(), 2, 0); - assert.position(editor.getSelection().getSelectionAnchor(), 3, 2); - assert.position(editor.getSelection().getSelectionLead(), 2, 0); + assert.position(editor.getCursorPosition(), 3, 1); + assert.position(editor.getSelection().getSelectionAnchor(), 2, 1); + assert.position(editor.getSelection().getSelectionLead(), 3, 1); }, - "test: move lines up should select moved lines" : function() { + "test: move lines up should keep selection on moved lines" : function() { var session = new EditSession(["11", "22", "33", "44"].join("\n")); var editor = new Editor(new MockRenderer(), session); @@ -284,19 +284,18 @@ module.exports = { editor.moveLinesUp(); assert.equal(session.toString(), ["11", "33", "44", "22"].join("\n")); - assert.position(editor.getCursorPosition(), 1, 0); - assert.position(editor.getSelection().getSelectionAnchor(), 3, 0); - assert.position(editor.getSelection().getSelectionLead(), 1, 0); + assert.position(editor.getCursorPosition(), 2, 1); + assert.position(editor.getSelection().getSelectionAnchor(), 1, 1); + assert.position(editor.getSelection().getSelectionLead(), 2, 1); editor.moveLinesUp(); assert.equal(session.toString(), ["33", "44", "11", "22"].join("\n")); - assert.position(editor.getCursorPosition(), 0, 0); - assert.position(editor.getSelection().getSelectionAnchor(), 2, 0); - assert.position(editor.getSelection().getSelectionLead(), 0, 0); + assert.position(editor.getCursorPosition(), 1, 1); + assert.position(editor.getSelection().getSelectionAnchor(), 0, 1); + assert.position(editor.getSelection().getSelectionLead(), 1, 1); }, - "test: move line without active selection should not move cursor relative to the moved line" : function() - { + "test: move line without active selection should not move cursor relative to the moved line" : function() { var session = new EditSession(["11", "22", "33", "44"].join("\n")); var editor = new Editor(new MockRenderer(), session); @@ -314,7 +313,7 @@ module.exports = { assert.position(editor.getCursorPosition(), 1, 1); }, - "test: copy lines down should select lines and place cursor at the selection start" : function() { + "test: copy lines down should keep selection" : function() { var session = new EditSession(["11", "22", "33", "44"].join("\n")); var editor = new Editor(new MockRenderer(), session); @@ -324,12 +323,12 @@ module.exports = { editor.copyLinesDown(); assert.equal(["11", "22", "33", "22", "33", "44"].join("\n"), session.toString()); - assert.position(editor.getCursorPosition(), 3, 0); - assert.position(editor.getSelection().getSelectionAnchor(), 5, 0); - assert.position(editor.getSelection().getSelectionLead(), 3, 0); + assert.position(editor.getCursorPosition(), 4, 1); + assert.position(editor.getSelection().getSelectionAnchor(), 3, 1); + assert.position(editor.getSelection().getSelectionLead(), 4, 1); }, - "test: copy lines up should select lines and place cursor at the selection start" : function() { + "test: copy lines up should keep selection" : function() { var session = new EditSession(["11", "22", "33", "44"].join("\n")); var editor = new Editor(new MockRenderer(), session); @@ -339,9 +338,9 @@ module.exports = { editor.copyLinesUp(); assert.equal(["11", "22", "33", "22", "33", "44"].join("\n"), session.toString()); - assert.position(editor.getCursorPosition(), 1, 0); - assert.position(editor.getSelection().getSelectionAnchor(), 3, 0); - assert.position(editor.getSelection().getSelectionLead(), 1, 0); + assert.position(editor.getCursorPosition(), 2, 1); + assert.position(editor.getSelection().getSelectionAnchor(), 1, 1); + assert.position(editor.getSelection().getSelectionLead(), 2, 1); }, "test: input a tab with soft tab should convert it to spaces" : function() { diff --git a/lib/ace/mode/folding/latex.js b/lib/ace/mode/folding/latex.js index a8c9db74..b80775eb 100644 --- a/lib/ace/mode/folding/latex.js +++ b/lib/ace/mode/folding/latex.js @@ -114,8 +114,8 @@ oop.inherits(FoldMode, BaseFoldMode); var row = stream.getCurrentTokenRow(); if (dir === -1) return new Range(row, session.getLine(row).length, startRow, startColumn); - else - return new Range(startRow, startColumn, row, stream.getCurrentTokenColumn()); + stream.stepBackward(); + return new Range(startRow, startColumn, row, stream.getCurrentTokenColumn()); }; this.latexSection = function(session, row, column) { diff --git a/lib/ace/mouse/default_handlers.js b/lib/ace/mouse/default_handlers.js index 3a7e97af..af741ce6 100644 --- a/lib/ace/mouse/default_handlers.js +++ b/lib/ace/mouse/default_handlers.js @@ -84,14 +84,14 @@ function DefaultHandlers(mouseHandler) { // selection if (inSelection && !editor.isFocused()) { editor.focus(); - if (this.$focusTimout && !this.$clickSelection) { + if (this.$focusTimout && !this.$clickSelection && !editor.inMultiSelectMode) { this.setState("focusWait"); this.captureMouse(ev); return ev.preventDefault(); } } - if (!inSelection || this.$clickSelection || ev.getShiftKey()) { + if (!inSelection || this.$clickSelection || ev.getShiftKey() || editor.inMultiSelectMode) { // Directly pick STATE_SELECT, since the user is not clicking inside // a selection. this.startSelect(pos); diff --git a/lib/ace/mouse/fold_handler.js b/lib/ace/mouse/fold_handler.js index 997ff314..3141b26a 100644 --- a/lib/ace/mouse/fold_handler.js +++ b/lib/ace/mouse/fold_handler.js @@ -50,8 +50,6 @@ function FoldHandler(editor) { }); editor.on("guttermousedown", function(e) { - if (!editor.isFocused()) - return; var gutterRegion = editor.renderer.$gutterLayer.getRegion(e); if (gutterRegion == "foldWidgets") { @@ -59,6 +57,8 @@ function FoldHandler(editor) { var session = editor.session; if (session.foldWidgets && session.foldWidgets[row]) editor.session.onFoldWidgetClick(row, e); + if (!editor.isFocused()) + editor.focus(); e.stop(); } }); @@ -69,27 +69,8 @@ function FoldHandler(editor) { if (gutterRegion == "foldWidgets") { var row = e.getDocumentPosition().row; var session = editor.session; - var fw = session.foldWidgets; - if (!fw || fw[row]) - return; - - var i = row - 1, firstRange; - while (i >= 0) { - var c = fw[i]; - if (c == null) - c = fw[i] = session.getFoldWidget(i); - - if (c == "start") { - var range = session.getFoldWidgetRange(i); - if (!firstRange) - firstRange = range; - if (range && range.end.row >= row) - break; - } - i--; - } - if (i == -1) - range = firstRange; + var data = session.getParentFoldRangeData(row, true); + var range = data.range || data.firstRange; if (range) { var row = range.start.row; diff --git a/lib/ace/multi_select.js b/lib/ace/multi_select.js index 9ae3b847..ee41f98a 100644 --- a/lib/ace/multi_select.js +++ b/lib/ace/multi_select.js @@ -78,9 +78,13 @@ var EditSession = require("./edit_session").EditSession; if (!this.inMultiSelectMode && this.rangeCount == 0) { var oldRange = this.toOrientedRange(); - if (range.intersects(oldRange)) + 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); } @@ -446,6 +450,8 @@ var Editor = require("./editor").Editor; editor.multiSelect.mergeOverlappingRanges(); } else if (command.multiSelectAction == "forEach") { editor.forEachSelection(command, e.args); + } else if (command.multiSelectAction == "forEachLine") { + editor.forEachSelection(command, e.args, true); } else if (command.multiSelectAction == "single") { editor.exitMultiSelectMode(); command.exec(editor, e.args || {}); @@ -461,7 +467,7 @@ var Editor = require("./editor").Editor; * @param {String} args Any arguments for the command * @method Editor.forEachSelection **/ - this.forEachSelection = function(cmd, args) { + this.forEachSelection = function(cmd, args, $byLines) { if (this.inVirtualSelectionMode) return; @@ -475,6 +481,10 @@ var Editor = require("./editor").Editor; var tmpSel = new Selection(session); this.inVirtualSelectionMode = true; for (var i = rangeList.ranges.length; i--;) { + if ($byLines) { + while (i > 0 && rangeList.ranges[i].start.row == rangeList.ranges[i - 1].end.row) + i--; + } tmpSel.fromOrientedRange(rangeList.ranges[i]); this.selection = session.selection = tmpSel; cmd.exec(this, args || {}); diff --git a/lib/ace/range.js b/lib/ace/range.js index e7d8a217..9538531a 100644 --- a/lib/ace/range.js +++ b/lib/ace/range.js @@ -3,7 +3,7 @@ * * 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 @@ -14,7 +14,7 @@ * * 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 @@ -30,10 +30,10 @@ define(function(require, exports, module) { "use strict"; - +var comparePoints = function(p1, p2) { + return p1.row - p2.row || p1.column - p2.column; +}; /** - * - * * This object is used in various places to indicate a region within the editor. To better visualize how this works, imagine a rectangle. Each quadrant of the rectangle is analogus to a range, as ranges contain a starting row and starting column, and an ending row, and ending column. * @class Range **/ @@ -45,7 +45,6 @@ define(function(require, exports, module) { * @param {Number} endRow The ending row * @param {Number} endColumn The ending column * - * * @constructor **/ var Range = function(startRow, startColumn, endRow, endColumn) { @@ -65,30 +64,29 @@ var Range = function(startRow, startColumn, endRow, endColumn) { * Returns `true` if and only if the starting row and column, and ending row and column, are equivalent to those given by `range`. * @param {Range} range A range to check against * - * * @return {Boolean} - **/ + **/ this.isEqual = function(range) { - return this.start.row == range.start.row && - this.end.row == range.end.row && - this.start.column == range.start.column && - this.end.column == range.end.column + return this.start.row === range.start.row && + this.end.row === range.end.row && + this.start.column === range.start.column && + this.end.column === range.end.column; }; /** - * + * * Returns a string containing the range's row and column information, given like this: * ``` * [start.row/start.column] -> [end.row/end.column] * ``` * @return {String} - **/ + **/ this.toString = function() { return ("Range: [" + this.start.row + "/" + this.start.column + "] -> [" + this.end.row + "/" + this.end.column + "]"); }; - /** + /** * * Returns `true` if the `row` and `column` provided are within the given range. This can better be expressed as returning `true` if: * ```javascript @@ -99,17 +97,16 @@ var Range = function(startRow, startColumn, endRow, endColumn) { * @param {Number} column A column to check for * @returns {Boolean} * @related Range.compare - **/ + **/ this.contains = function(row, column) { return this.compare(row, column) == 0; }; - /** + /** * Compares `this` range (A) with another range (B). * @param {Range} range A range to compare with - * - * + * * @related Range.compare * @returns {Number} This method returns one of the following numbers:
*
@@ -119,7 +116,7 @@ var Range = function(startRow, startColumn, endRow, endColumn) { * * `+1`: (B) begins inside of (A) but ends outside of (A)
* * `+2`: (B) is after (A) and doesn't intersect with (A)
* * `42`: FTW state: (B) ends in (A) but starts outside of (A) - **/ + **/ this.compareRange = function(range) { var cmp, end = range.end, @@ -149,12 +146,11 @@ var Range = function(startRow, startColumn, endRow, endColumn) { } }; - /** - * + /** * Checks the row and column points of `p` with the row and column points of the calling range. * * @param {Range} p A point to compare with - * + * * @related Range.compare * @returns {Number} This method returns one of the following numbers:
* * `0` if the two points are exactly equal
@@ -168,19 +164,18 @@ var Range = function(startRow, startColumn, endRow, endColumn) { * If the ending row of the calling range is equal to `p.row`, and:
* * `p.column` is less than or equal to the calling range's ending column, this returns `0`
* * Otherwise, it returns 1
- **/ + **/ this.comparePoint = function(p) { return this.compare(p.row, p.column); }; - /** + /** * Checks the start and end points of `range` and compares them to the calling range. Returns `true` if the `range` is contained within the caller's range. * @param {Range} range A range to compare with * * @returns {Boolean} * @related Range.comparePoint - * - **/ + **/ this.containsRange = function(range) { return this.comparePoint(range.start) == 0 && this.comparePoint(range.end) == 0; }; @@ -189,7 +184,6 @@ var Range = function(startRow, startColumn, endRow, endColumn) { * Returns `true` if passed in `range` intersects with the one calling this method. * @param {Range} range A range to compare with * - * * @returns {Boolean} **/ this.intersects = function(range) { @@ -202,7 +196,6 @@ var Range = function(startRow, startColumn, endRow, endColumn) { * @param {Number} row A row point to compare with * @param {Number} column A column point to compare with * - * * @returns {Boolean} **/ this.isEnd = function(row, column) { @@ -214,9 +207,8 @@ var Range = function(startRow, startColumn, endRow, endColumn) { * @param {Number} row A row point to compare with * @param {Number} column A column point to compare with * - * * @returns {Boolean} - **/ + **/ this.isStart = function(row, column) { return this.start.row == row && this.start.column == column; }; @@ -226,9 +218,7 @@ var Range = function(startRow, startColumn, endRow, endColumn) { * @param {Number} row A row point to set * @param {Number} column A column point to set * - * - * - **/ + **/ this.setStart = function(row, column) { if (typeof row == "object") { this.start.column = row.column; @@ -244,9 +234,7 @@ var Range = function(startRow, startColumn, endRow, endColumn) { * @param {Number} row A row point to set * @param {Number} column A column point to set * - * - * - **/ + **/ this.setEnd = function(row, column) { if (typeof row == "object") { this.end.column = row.column; @@ -257,15 +245,15 @@ var Range = function(startRow, startColumn, endRow, endColumn) { } }; - /** + /** * Returns `true` if the `row` and `column` are within the given range. * @param {Number} row A row point to compare with * @param {Number} column A column point to compare with * - * + * * @returns {Boolean} * @related Range.compare - **/ + **/ this.inside = function(row, column) { if (this.compare(row, column) == 0) { if (this.isEnd(row, column) || this.isStart(row, column)) { @@ -277,15 +265,14 @@ var Range = function(startRow, startColumn, endRow, endColumn) { return false; }; - /** + /** * Returns `true` if the `row` and `column` are within the given range's starting points. * @param {Number} row A row point to compare with * @param {Number} column A column point to compare with * - * * @returns {Boolean} * @related Range.compare - **/ + **/ this.insideStart = function(row, column) { if (this.compare(row, column) == 0) { if (this.isEnd(row, column)) { @@ -297,16 +284,15 @@ var Range = function(startRow, startColumn, endRow, endColumn) { return false; }; - /** + /** * Returns `true` if the `row` and `column` are within the given range's ending points. * @param {Number} row A row point to compare with * @param {Number} column A column point to compare with * - * * @returns {Boolean} * @related Range.compare - * - **/ + * + **/ this.insideEnd = function(row, column) { if (this.compare(row, column) == 0) { if (this.isStart(row, column)) { @@ -318,11 +304,11 @@ var Range = function(startRow, startColumn, endRow, endColumn) { return false; }; - /** + /** * Checks the row and column points with the row and column points of the calling range. * @param {Number} row A row point to compare with * @param {Number} column A column point to compare with - * + * * * @returns {Number} This method returns one of the following numbers:
* `0` if the two points are exactly equal
@@ -363,8 +349,6 @@ var Range = function(startRow, startColumn, endRow, endColumn) { * Checks the row and column points with the row and column points of the calling range. * @param {Number} row A row point to compare with * @param {Number} column A column point to compare with - * - * * * @returns {Number} This method returns one of the following numbers:
*
@@ -393,7 +377,7 @@ var Range = function(startRow, startColumn, endRow, endColumn) { * Checks the row and column points with the row and column points of the calling range. * @param {Number} row A row point to compare with * @param {Number} column A column point to compare with - * + * * * @returns {Number} This method returns one of the following numbers:
* `0` if the two points are exactly equal
@@ -416,11 +400,11 @@ var Range = function(startRow, startColumn, endRow, endColumn) { } }; - /** + /** * Checks the row and column points with the row and column points of the calling range. * @param {Number} row A row point to compare with * @param {Number} column A column point to compare with - * + * * * @returns {Number} This method returns one of the following numbers:
* * `1` if the ending row of the calling range is equal to `row`, and the ending column of the calling range is equal to `column`
@@ -439,51 +423,34 @@ var Range = function(startRow, startColumn, endRow, endColumn) { } }; - /** + /** * Returns the part of the current `Range` that occurs within the boundaries of `firstRow` and `lastRow` as a new `Range` object. * @param {Number} firstRow The starting row * @param {Number} lastRow The ending row * - * + * * @returns {Range} **/ this.clipRows = function(firstRow, lastRow) { - if (this.end.row > lastRow) { - var end = { - row: lastRow+1, - column: 0 - }; - } + if (this.end.row > lastRow) + var end = {row: lastRow + 1, column: 0}; + else if (this.end.row < firstRow) + var end = {row: firstRow, column: 0}; - if (this.start.row > lastRow) { - var start = { - row: lastRow+1, - column: 0 - }; - } + if (this.start.row > lastRow) + var start = {row: lastRow + 1, column: 0}; + else if (this.start.row < firstRow) + var start = {row: firstRow, column: 0}; - if (this.start.row < firstRow) { - var start = { - row: firstRow, - column: 0 - }; - } - - if (this.end.row < firstRow) { - var end = { - row: firstRow, - column: 0 - }; - } return Range.fromPoints(start || this.start, end || this.end); }; - /** + /** * Changes the row and column points for the calling range for both the starting and ending points. * @param {Number} row A new row to extend to * @param {Number} column A new column to extend to * - * + * * @returns {Range} The original range with the new row **/ this.extend = function(row, column) { @@ -500,11 +467,11 @@ var Range = function(startRow, startColumn, endRow, endColumn) { }; this.isEmpty = function() { - return (this.start.row == this.end.row && this.start.column == this.end.column); + return (this.start.row === this.end.row && this.start.column === this.end.column); }; - /** - * + /** + * * Returns `true` if the range spans across multiple lines. * @returns {Boolean} **/ @@ -512,8 +479,8 @@ var Range = function(startRow, startColumn, endRow, endColumn) { return (this.start.row !== this.end.row); }; - /** - * + /** + * * Returns a duplicate of the calling range. * @returns {Range} **/ @@ -521,7 +488,7 @@ var Range = function(startRow, startColumn, endRow, endColumn) { return Range.fromPoints(this.start, this.end); }; - /** + /** * * Returns a range containing the starting and ending rows of the original range, but with a column value of `0`. * @returns {Range} @@ -533,38 +500,45 @@ var Range = function(startRow, startColumn, endRow, endColumn) { return new Range(this.start.row, 0, this.end.row, 0) }; - /** + /** * Given the current `Range`, this function converts those starting and ending points into screen positions, and then returns a new `Range` object. * @param {EditSession} session The `EditSession` to retrieve coordinates from - * - * + * + * * @returns {Range} **/ this.toScreenRange = function(session) { - var screenPosStart = - session.documentToScreenPosition(this.start); - var screenPosEnd = - session.documentToScreenPosition(this.end); + var screenPosStart = session.documentToScreenPosition(this.start); + var screenPosEnd = session.documentToScreenPosition(this.end); return new Range( screenPosStart.row, screenPosStart.column, screenPosEnd.row, screenPosEnd.column ); }; + + + /* experimental */ + this.moveBy = function(row, column) { + this.start.row += row; + this.start.column += column; + this.end.row += row; + this.end.column += column; + }; }).call(Range.prototype); -/** +/** * Creates and returns a new `Range` based on the row and column of the given parameters. * @param {Range} start A starting point to use * @param {Range} end An ending point to use - * - * + * * @returns {Range} **/ Range.fromPoints = function(start, end) { return new Range(start.row, start.column, end.row, end.column); }; +Range.comparePoints = comparePoints; exports.Range = Range; }); diff --git a/lib/ace/range_list.js b/lib/ace/range_list.js index 68c5c330..e186959b 100644 --- a/lib/ace/range_list.js +++ b/lib/ace/range_list.js @@ -30,30 +30,28 @@ define(function(require, exports, module) { "use strict"; - +var Range = require("./range").Range; +var comparePoints = Range.comparePoints; var RangeList = function() { this.ranges = []; }; (function() { - this.comparePoints = function(p1, p2) { - return p1.row - p2.row || p1.column - p2.column; - }; + this.comparePoints = comparePoints; - this.pointIndex = function(pos, startIndex) { + this.pointIndex = function(pos, excludeEdges, startIndex) { var list = this.ranges; for (var i = startIndex || 0; i < list.length; i++) { var range = list[i]; - var cmp = this.comparePoints(pos, range.end); - - if (cmp > 0) + var cmpEnd = comparePoints(pos, range.end); + if (cmpEnd > 0) continue; - if (cmp == 0) - return i; - cmp = this.comparePoints(pos, range.start); - if (cmp >= 0) + var cmpStart = comparePoints(pos, range.start); + if (cmpEnd === 0) + return excludeEdges && cmpStart !== 0 ? -i-2 : i; + if (cmpStart > 0 || (cmpStart === 0 && !excludeEdges)) return i; return -i-1; @@ -62,17 +60,17 @@ var RangeList = function() { }; this.add = function(range) { - var startIndex = this.pointIndex(range.start); + var excludeEdges = !range.isEmpty(); + var startIndex = this.pointIndex(range.start, excludeEdges); if (startIndex < 0) startIndex = -startIndex - 1; - var endIndex = this.pointIndex(range.end, startIndex); + var endIndex = this.pointIndex(range.end, excludeEdges, startIndex); if (endIndex < 0) endIndex = -endIndex - 1; else endIndex++; - return this.ranges.splice(startIndex, endIndex - startIndex, range); }; @@ -95,18 +93,23 @@ var RangeList = function() { this.merge = function() { var removed = []; var list = this.ranges; + + list = list.sort(function(a, b) { + return comparePoints(a.start, b.start); + }); + var next = list[0], range; for (var i = 1; i < list.length; i++) { range = next; next = list[i]; - var cmp = this.comparePoints(range.end, next.start); + var cmp = comparePoints(range.end, next.start); if (cmp < 0) continue; - if (cmp == 0 && !(range.isEmpty() || next.isEmpty())) + if (cmp == 0 && !range.isEmpty() && !next.isEmpty()) continue; - if (this.comparePoints(range.end, next.end) < 0) { + if (comparePoints(range.end, next.end) < 0) { range.end.row = next.end.row; range.end.column = next.end.column; } @@ -116,6 +119,8 @@ var RangeList = function() { next = range; i--; } + + this.ranges = list; return removed; }; @@ -199,10 +204,16 @@ var RangeList = function() { break; if (r.start.row == startRow && r.start.column >= start.column ) { + r.start.column += colDiff; r.start.row += lineDif; } - if (r.end.row == startRow && r.end.column >= start.column) { + if (r.end.row == startRow && r.end.column >= start.column) { + // special handling for the case when two ranges share an edge + if (r.end.column == start.column && colDiff > 0 && i < n - 1) { + if (r.end.column > r.start.column && r.end.column == ranges[i+1].start.column) + r.end.column -= colDiff; + } r.end.column += colDiff; r.end.row += lineDif; } diff --git a/lib/ace/range_list_test.js b/lib/ace/range_list_test.js index 8623a945..82e7da67 100644 --- a/lib/ace/range_list_test.js +++ b/lib/ace/range_list_test.js @@ -72,6 +72,26 @@ module.exports = { assert.equal(rangeList.pointIndex({row: 8, column: 9}), 2); assert.equal(rangeList.pointIndex({row: 18, column: 9}), -4); }, + + "test: rangeList pointIndex excludeEdges": function() { + var rangeList = new RangeList(); + rangeList.ranges = [ + new Range(1,2,3,4), + new Range(4,2,5,4), + new Range(8,8,9,9), + new Range(10,10,10,10) + ]; + + assert.equal(rangeList.pointIndex({row: 0, column: 1}, true), -1); + assert.equal(rangeList.pointIndex({row: 1, column: 2}, true), -1); + assert.equal(rangeList.pointIndex({row: 1, column: 3}, true), 0); + assert.equal(rangeList.pointIndex({row: 3, column: 4}, true), -2); + assert.equal(rangeList.pointIndex({row: 4, column: 1}, true), -2); + assert.equal(rangeList.pointIndex({row: 5, column: 1}, true), 1); + assert.equal(rangeList.pointIndex({row: 8, column: 9}, true), 2); + assert.equal(rangeList.pointIndex({row: 10, column: 10}, true), 3); + assert.equal(rangeList.pointIndex({row: 18, column: 9}, true), -5); + }, "test: rangeList add": function() { var rangeList = new RangeList(); @@ -153,7 +173,6 @@ module.exports = { rangeList.substractPoint({row: 6, column: 7}); assert.equal(rangeList.ranges.length, 2); } - }; }); diff --git a/lib/ace/token_iterator.js b/lib/ace/token_iterator.js index ffddafff..74376fb3 100644 --- a/lib/ace/token_iterator.js +++ b/lib/ace/token_iterator.js @@ -85,11 +85,12 @@ var TokenIterator = function(session, initialRow, initialColumn) { * @returns {String} **/ this.stepForward = function() { - var rowCount = this.$session.getLength(); this.$tokenIndex += 1; - + var rowCount; while (this.$tokenIndex >= this.$rowTokens.length) { this.$row += 1; + if (!rowCount) + rowCount = this.$session.getLength(); if (this.$row >= rowCount) { this.$row = rowCount - 1; return null;