Merge pull request #1274 from ajaxorg/folding

preserve folds when dragging or moving lines
This commit is contained in:
Lennart Kats 2013-03-10 04:27:59 -07:00
commit 4ab6cab5d9
16 changed files with 594 additions and 500 deletions

2
build

@ -1 +1 @@
Subproject commit 75ba742f4f00cefadfef1ae451cfb2a4280ad367 Subproject commit 1e3407af692eaf661800083897c73d46d0a4bc46

View file

@ -295,6 +295,7 @@ exports.commands = [{
name: "selecttomatching", name: "selecttomatching",
bindKey: bindKey("Ctrl-Shift-P", null), bindKey: bindKey("Ctrl-Shift-P", null),
exec: function(editor) { editor.jumpToMatching(true); }, exec: function(editor) { editor.jumpToMatching(true); },
multiSelectAction: "forEach",
readOnly: true readOnly: true
}, },
@ -315,7 +316,7 @@ exports.commands = [{
name: "removeline", name: "removeline",
bindKey: bindKey("Ctrl-D", "Command-D"), bindKey: bindKey("Ctrl-D", "Command-D"),
exec: function(editor) { editor.removeLines(); }, exec: function(editor) { editor.removeLines(); },
multiSelectAction: "forEach" multiSelectAction: "forEachLine"
}, { }, {
name: "duplicateSelection", name: "duplicateSelection",
bindKey: bindKey("Ctrl-Shift-D", "Command-Shift-D"), bindKey: bindKey("Ctrl-Shift-D", "Command-Shift-D"),
@ -325,12 +326,12 @@ exports.commands = [{
name: "sortlines", name: "sortlines",
bindKey: bindKey("Ctrl-Alt-S", "Command-Alt-S"), bindKey: bindKey("Ctrl-Alt-S", "Command-Alt-S"),
exec: function(editor) { editor.sortLines(); }, exec: function(editor) { editor.sortLines(); },
multiSelectAction: "forEach" multiSelectAction: "forEachLine"
}, { }, {
name: "togglecomment", name: "togglecomment",
bindKey: bindKey("Ctrl-/", "Command-/"), bindKey: bindKey("Ctrl-/", "Command-/"),
exec: function(editor) { editor.toggleCommentLines(); }, exec: function(editor) { editor.toggleCommentLines(); },
multiSelectAction: "forEach" multiSelectAction: "forEachLine"
}, { }, {
name: "modifyNumberUp", name: "modifyNumberUp",
bindKey: bindKey("Ctrl-Shift-Up", "Alt-Shift-Up"), bindKey: bindKey("Ctrl-Shift-Up", "Alt-Shift-Up"),
@ -418,12 +419,12 @@ exports.commands = [{
name: "blockoutdent", name: "blockoutdent",
bindKey: bindKey("Ctrl-[", "Ctrl-["), bindKey: bindKey("Ctrl-[", "Ctrl-["),
exec: function(editor) { editor.blockOutdent(); }, exec: function(editor) { editor.blockOutdent(); },
multiSelectAction: "forEach" multiSelectAction: "forEachLine"
},{ },{
name: "blockindent", name: "blockindent",
bindKey: bindKey("Ctrl-]", "Ctrl-]"), bindKey: bindKey("Ctrl-]", "Ctrl-]"),
exec: function(editor) { editor.blockIndent(); }, exec: function(editor) { editor.blockIndent(); },
multiSelectAction: "forEach" multiSelectAction: "forEachLine"
}, { }, {
name: "insertstring", name: "insertstring",
exec: function(editor, str) { editor.insert(str); }, exec: function(editor, str) { editor.insert(str); },

View file

@ -31,9 +31,9 @@
define(function(require, exports, module) { define(function(require, exports, module) {
"use strict"; "use strict";
var config = require("./config");
var oop = require("./lib/oop"); var oop = require("./lib/oop");
var lang = require("./lib/lang"); var lang = require("./lib/lang");
var config = require("./config");
var EventEmitter = require("./lib/event_emitter").EventEmitter; var EventEmitter = require("./lib/event_emitter").EventEmitter;
var Selection = require("./selection").Selection; var Selection = require("./selection").Selection;
var TextMode = require("./mode/text").Mode; var TextMode = require("./mode/text").Mode;
@ -41,18 +41,15 @@ var Range = require("./range").Range;
var Document = require("./document").Document; var Document = require("./document").Document;
var BackgroundTokenizer = require("./background_tokenizer").BackgroundTokenizer; var BackgroundTokenizer = require("./background_tokenizer").BackgroundTokenizer;
var SearchHighlight = require("./search_highlight").SearchHighlight; 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. * `EditSession` can be attached to only one [[Document `Document`]]. Same `Document` can be attached to several `EditSession`s.
* @class EditSession * @class EditSession
**/ **/
// events //{ events
/** /**
* *
* Emitted when the document changes. * Emitted when the document changes.
@ -132,7 +129,7 @@ var config = require("./config");
* *
* @param {Number} scrollLeft The new scroll left value * @param {Number} scrollLeft The new scroll left value
**/ **/
//}
/** /**
* *
@ -153,11 +150,7 @@ var EditSession = function(text, mode) {
this.$foldData = []; this.$foldData = [];
this.$foldData.toString = function() { this.$foldData.toString = function() {
var str = ""; return this.join("\n");
this.forEach(function(foldLine) {
str += "\n" + foldLine.toString();
});
return str;
} }
this.on("changeFold", this.onChangeFold.bind(this)); this.on("changeFold", this.onChangeFold.bind(this));
this.$onChange = this.onChange.bind(this); this.$onChange = this.onChange.bind(this);
@ -958,7 +951,6 @@ var EditSession = function(text, mode) {
this.$worker = null; this.$worker = null;
}; };
this.$startWorker = function() { this.$startWorker = function() {
if (typeof Worker !== "undefined" && !require.noWorker) { if (typeof Worker !== "undefined" && !require.noWorker) {
try { try {
@ -1210,7 +1202,7 @@ var EditSession = function(text, mode) {
this.$getUndoSelection = function(deltas, isUndo, lastUndoRange) { this.$getUndoSelection = function(deltas, isUndo, lastUndoRange) {
function isInsert(delta) { function isInsert(delta) {
var insert = var insert =
delta.action == "insertText" || delta.action == "insertLines"; delta.action === "insertText" || delta.action === "insertLines";
return isUndo ? !insert : insert; return isUndo ? !insert : insert;
} }
@ -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); var text = this.getTextRange(fromRange);
this.remove(fromRange); var folds = this.getFoldsInRange(fromRange);
var toRow = toPosition.row; var toRange = Range.fromPoints(toPosition, toPosition);
var toColumn = toPosition.column; if (!copy) {
this.remove(fromRange);
// Make sure to update the insert location, when text is removed in var rowDiff = fromRange.start.row - fromRange.end.row;
// front of the chosen point of insertion. var collDiff = rowDiff ? -fromRange.end.column : fromRange.start.column - fromRange.end.column;
if (!fromRange.isMultiLine() && fromRange.start.row == toRow && if (collDiff) {
fromRange.end.column < toColumn) if (toRange.start.row == fromRange.end.row && toRange.start.column > fromRange.end.column)
toColumn -= text.length; toRange.start.column += collDiff;
if (toRange.end.row == fromRange.end.row && toRange.end.column > fromRange.end.column)
if (fromRange.isMultiLine() && fromRange.end.row < toRow) { toRange.end.column += collDiff;
var lines = this.doc.$split(text); }
toRow -= lines.length - 1; 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); 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; return toRange;
}; };
@ -1371,6 +1375,38 @@ 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`. * 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} firstRow The starting row to move up
@ -1381,11 +1417,7 @@ var EditSession = function(text, mode) {
* *
**/ **/
this.moveLinesUp = function(firstRow, lastRow) { this.moveLinesUp = function(firstRow, lastRow) {
if (firstRow <= 0) return 0; return this.$moveLines(firstRow, lastRow, -1);
var removed = this.doc.removeLines(firstRow, lastRow);
this.doc.insertLines(firstRow - 1, removed);
return -1;
}; };
/** /**
@ -1394,16 +1426,10 @@ var EditSession = function(text, mode) {
* @param {Number} lastRow The final 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. * @returns {Number} If `firstRow` is less-than or equal to 0, this function returns 0. Otherwise, on success, it returns -1.
* *
*
*
* @related Document.insertLines * @related Document.insertLines
**/ **/
this.moveLinesDown = function(firstRow, lastRow) { this.moveLinesDown = function(firstRow, lastRow) {
if (lastRow >= this.doc.getLength()-1) return 0; return this.$moveLines(firstRow, lastRow, 1);
var removed = this.doc.removeLines(firstRow, lastRow);
this.doc.insertLines(firstRow+1, removed);
return 1;
}; };
/** /**
@ -1415,14 +1441,7 @@ var EditSession = function(text, mode) {
* *
**/ **/
this.duplicateLines = function(firstRow, lastRow) { this.duplicateLines = function(firstRow, lastRow) {
var firstRow = this.$clipRowToDocument(firstRow); return this.$moveLines(firstRow, lastRow, 0);
var lastRow = this.$clipRowToDocument(lastRow);
var lines = this.getLines(firstRow, lastRow);
this.doc.insertLines(firstRow, lines);
var addedRows = lastRow - firstRow + 1;
return addedRows;
}; };
@ -2381,9 +2400,9 @@ config.defineOptions(EditSession.prototype, "session", {
if (!value) { if (!value) {
this.setUseWrapMode(false); this.setUseWrapMode(false);
} else { } else {
var col = typeof value == "number" && value; var col = typeof value == "number" ? value : null;
this.setUseWrapMode(true); this.setUseWrapMode(true);
this.setWrapLimitRange(value, value); this.setWrapLimitRange(col, col);
} }
this.$wrap = value; this.$wrap = value;
}, },

View file

@ -31,6 +31,9 @@
define(function(require, exports, module) { define(function(require, exports, module) {
"use strict"; "use strict";
var Range = require("../range").Range;
var RangeList = require("../range_list").RangeList;
var oop = require("../lib/oop")
/* /*
* Simple fold-data struct. * Simple fold-data struct.
**/ **/
@ -42,9 +45,11 @@ var Fold = exports.Fold = function(range, placeholder) {
this.end = range.end; this.end = range.end;
this.sameRow = range.start.row == range.end.row; this.sameRow = range.start.row == range.end.row;
this.subFolds = []; this.subFolds = this.ranges = [];
}; };
oop.inherits(Fold, RangeList);
(function() { (function() {
this.toString = function() { this.toString = function() {
@ -64,17 +69,21 @@ var Fold = exports.Fold = function(range, placeholder) {
this.subFolds.forEach(function(subFold) { this.subFolds.forEach(function(subFold) {
fold.subFolds.push(subFold.clone()); fold.subFolds.push(subFold.clone());
}); });
fold.collapseChildren = this.collapseChildren;
return fold; return fold;
}; };
this.addSubFold = function(fold) { this.addSubFold = function(fold) {
if (this.range.isEqual(fold)) if (this.range.isEqual(fold))
return this; return;
if (!this.range.containsRange(fold)) if (!this.range.containsRange(fold))
throw "A fold can't intersect already existing fold" + fold.range + this.range; 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++) { for (var i = 0, cmp = -1; i < this.subFolds.length; i++) {
cmp = this.subFolds[i].range.compare(row, column); cmp = this.subFolds[i].range.compare(row, column);
if (cmp != 1) if (cmp != 1)
@ -103,6 +112,29 @@ var Fold = exports.Fold = function(range, placeholder) {
return fold; return fold;
}; };
this.restoreRange = function(range) {
return restoreRange(range, this.start);
};
}).call(Fold.prototype); }).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);
}
}); });

View file

@ -192,13 +192,13 @@ function FoldLine(foldData, folds) {
} }
this.split = function(row, column) { this.split = function(row, column) {
var fold = this.getNextFoldTo(row, column).fold, var fold = this.getNextFoldTo(row, column).fold;
folds = this.folds; var folds = this.folds;
var foldData = this.foldData; var foldData = this.foldData;
if (!fold) { if (!fold)
return null; return null;
}
var i = folds.indexOf(fold); var i = folds.indexOf(fold);
var foldBefore = folds[i - 1]; var foldBefore = folds[i - 1];
this.end.row = foldBefore.end.row; this.end.row = foldBefore.end.row;

View file

@ -118,11 +118,6 @@ function Folding() {
function addFold(fold) { function addFold(fold) {
folds.push(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++) for (var i = 0; i < foldLines.length; i++)
@ -265,9 +260,10 @@ function Folding() {
if (placeholder instanceof Fold) if (placeholder instanceof Fold)
fold = placeholder; fold = placeholder;
else else {
fold = new Fold(range, placeholder); fold = new Fold(range, placeholder);
fold.collapseChildren = range.collapseChildren;
}
this.$clipRangeToDocument(fold.range); this.$clipRangeToDocument(fold.range);
var startRow = fold.start.row; var startRow = fold.start.row;
@ -297,7 +293,9 @@ function Folding() {
// Remove the folds from fold data. // Remove the folds from fold data.
this.removeFolds(folds); this.removeFolds(folds);
// Add the removed folds as subfolds on the new fold. // 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++) { for (var i = 0; i < foldData.length; i++) {
@ -306,8 +304,7 @@ function Folding() {
foldLine.addFold(fold); foldLine.addFold(fold);
added = true; added = true;
break; break;
} } else if (startRow == foldLine.end.row) {
else if (startRow == foldLine.end.row) {
foldLine.addFold(fold); foldLine.addFold(fold);
added = true; added = true;
if (!fold.sameRow) { if (!fold.sameRow) {
@ -320,8 +317,7 @@ function Folding() {
} }
} }
break; break;
} } else if (endRow <= foldLine.start.row) {
else if (endRow <= foldLine.start.row) {
break; break;
} }
} }
@ -415,9 +411,13 @@ function Folding() {
this.expandFold = function(fold) { this.expandFold = function(fold) {
this.removeFold(fold); this.removeFold(fold);
fold.subFolds.forEach(function(fold) { fold.subFolds.forEach(function(subFold) {
this.addFold(fold); fold.restoreRange(subFold);
this.addFold(subFold);
}, this); }, this);
if (fold.collapseChildren > 0) {
this.foldAll(fold.start.row+1, fold.end.row, fold.collapseChildren-1);
}
fold.subFolds = []; fold.subFolds = [];
}; };
@ -429,9 +429,10 @@ function Folding() {
this.unfold = function(location, expandInner) { this.unfold = function(location, expandInner) {
var range, folds; var range, folds;
if (location == null) if (location == null) {
range = new Range(0, 0, this.getLength(), 0); 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); range = new Range(location, 0, location, this.getLine(location).length);
else if ("row" in location) else if ("row" in location)
range = Range.fromPoints(location, location); range = Range.fromPoints(location, location);
@ -464,6 +465,11 @@ function Folding() {
return foldLine ? foldLine.end.row : docRow; 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) { this.getFoldDisplayLine = function(foldLine, endRow, endColumn, startRow, startColumn) {
if (startRow == null) { if (startRow == null) {
startRow = foldLine.start.row; startRow = foldLine.start.row;
@ -480,20 +486,20 @@ function Folding() {
var textLine = ""; var textLine = "";
foldLine.walk(function(placeholder, row, column, lastColumn) { foldLine.walk(function(placeholder, row, column, lastColumn) {
if (row < startRow) { if (row < startRow)
return; return;
} else if (row == startRow) { if (row == startRow) {
if (column < startColumn) { if (column < startColumn)
return; return;
}
lastColumn = Math.max(startColumn, lastColumn); lastColumn = Math.max(startColumn, lastColumn);
} }
if (placeholder != null) { if (placeholder != null) {
textLine += placeholder; textLine += placeholder;
} else { } else {
textLine += doc.getLine(row).substring(lastColumn, column); textLine += doc.getLine(row).substring(lastColumn, column);
} }
}.bind(this), endRow, endColumn); }, endRow, endColumn);
return textLine; return textLine;
}; };
@ -535,26 +541,22 @@ function Folding() {
if (fold) { if (fold) {
this.expandFold(fold); this.expandFold(fold);
return; return;
} } else if (bracketPos = this.findMatchingBracket(cursor)) {
else if (bracketPos = this.findMatchingBracket(cursor)) {
if (range.comparePoint(bracketPos) == 1) { if (range.comparePoint(bracketPos) == 1) {
range.end = bracketPos; range.end = bracketPos;
} } else {
else {
range.start = bracketPos; range.start = bracketPos;
range.start.column++; range.start.column++;
range.end.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) if (range.comparePoint(bracketPos) == 1)
range.end = bracketPos; range.end = bracketPos;
else else
range.start = bracketPos; range.start = bracketPos;
range.start.column++; range.start.column++;
} } else {
else {
range = this.getCommentFoldRange(cursor.row, cursor.column) || range; range = this.getCommentFoldRange(cursor.row, cursor.column) || range;
} }
} else { } else {
@ -562,8 +564,7 @@ function Folding() {
if (tryToUnfold && folds.length) { if (tryToUnfold && folds.length) {
this.expandFolds(folds); this.expandFolds(folds);
return; return;
} } else if (folds.length == 1 ) {
else if (folds.length == 1 ) {
fold = folds[0]; 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; var foldWidgets = this.foldWidgets;
endRow = endRow || this.getLength(); endRow = endRow || this.getLength();
for (var row = startRow || 0; row < endRow; row++) { for (var row = startRow || 0; row < endRow; row++) {
@ -630,13 +633,16 @@ function Folding() {
var range = this.getFoldWidgetRange(row); var range = this.getFoldWidgetRange(row);
// sometimes range can be incompatible with existing fold // 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 { if (range && range.end.row <= endRow) try {
this.addFold("...", range); var fold = this.addFold("...", range);
fold.collapseChildren = depth;
} catch(e) {} } catch(e) {}
row = range.end.row;
} }
}; };
// structured folding
this.$foldStyles = { this.$foldStyles = {
"manual": 1, "manual": 1,
"markbegin": 1, "markbegin": 1,
@ -661,7 +667,6 @@ function Folding() {
this.$setFolding(mode); this.$setFolding(mode);
}; };
// structured folding
this.$setFolding = function(foldMode) { this.$setFolding = function(foldMode) {
if (this.$foldMode == foldMode) if (this.$foldMode == foldMode)
return; 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) { this.onFoldWidgetClick = function(row, e) {
e = e.domEvent;
var type = this.getFoldWidget(row); var type = this.getFoldWidget(row);
var line = this.getLine(row); var line = this.getLine(row);
var onlySubfolds = e.shiftKey; e = e.domEvent;
var addSubfolds = onlySubfolds || e.ctrlKey || e.altKey || e.metaKey; var children = e.shiftKey;
var fold; var all = e.ctrlKey || e.metaKey;
var siblings = e.altKey;
if (type == "end") var dir = type === "end" ? -1 : 1;
fold = this.getFoldAt(row, 0, -1); var fold = this.getFoldAt(row, dir === -1 ? 0 : line.length, dir);
else
fold = this.getFoldAt(row, line.length, 1);
if (fold) { if (fold) {
if (addSubfolds) if (children || all)
this.removeFold(fold); this.removeFold(fold);
else else
this.expandFold(fold); this.expandFold(fold);
@ -707,26 +737,33 @@ function Folding() {
} }
var range = this.getFoldWidgetRange(row); var range = this.getFoldWidgetRange(row);
if (range) { // sometimes singleline folds can be missed by the code above
// sometimes singleline folds can be missed by the code above if (range && !range.isMultiLine()) {
if (!range.isMultiLine()) { fold = this.getFoldAt(range.start.row, range.start.column, 1);
fold = this.getFoldAt(range.start.row, range.start.column, 1); if (fold && range.isEqual(fold.range)) {
if (fold && range.isEqual(fold.range)) { this.removeFold(fold);
this.removeFold(fold); return;
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) { this.updateFoldWidgets = function(e) {

View file

@ -1367,8 +1367,7 @@ var Editor = function(renderer, session) {
var row = range.start.row; var row = range.start.row;
doc.duplicateLines(row, row); doc.duplicateLines(row, row);
} else { } else {
var reverse = sel.isBackwards() var point = reverse ? range.start : range.end;
var point = sel.isBackwards() ? range.start : range.end;
var endPoint = doc.insert(point, doc.getTextRange(range), false); var endPoint = doc.insert(point, doc.getTextRange(range), false);
range.start = point; range.start = point;
range.end = endPoint; range.end = endPoint;
@ -1412,9 +1411,6 @@ var Editor = function(renderer, session) {
* @related EditSession.moveText * @related EditSession.moveText
**/ **/
this.moveText = function(range, toPosition) { this.moveText = function(range, toPosition) {
if (this.$readOnly)
return null;
return this.session.moveText(range, toPosition); return this.session.moveText(range, toPosition);
}; };
@ -1450,25 +1446,39 @@ var Editor = function(renderer, session) {
* *
**/ **/
this.$moveLines = function(mover) { this.$moveLines = function(mover) {
var rows = this.$getSelectedRows();
var selection = this.selection; var selection = this.selection;
if (!selection.isMultiLine()) { if (!selection.inMultiSelectMode || this.inVirtualSelectionMode) {
var range = selection.getRange(); var range = selection.toOrientedRange();
var reverse = selection.isBackwards(); 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);
var linesMoved = mover.call(this, rows.first, rows.last); 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++;
if (range) { var linesMoved = mover.call(this, first, last);
range.start.row += linesMoved; while (rangeIndex >= i) {
range.end.row += linesMoved; ranges[rangeIndex].moveBy(linesMoved, 0);
selection.setSelectionRange(range, reverse); rangeIndex--;
} }
else { }
selection.setSelectionAnchor(rows.last+linesMoved+1, 0); selection.fromOrientedRange(selection.ranges[0]);
selection.$moveSelection(function() { selection.rangeList.attach(this.session);
selection.moveCursorTo(rows.first+linesMoved, 0);
});
} }
}; };

View file

@ -248,7 +248,7 @@ module.exports = {
assert.range(editor.getSelectionRange(), 0, 3, 1, 0); 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 session = new EditSession(["11", "22", "33", "44"].join("\n"));
var editor = new Editor(new MockRenderer(), session); var editor = new Editor(new MockRenderer(), session);
@ -257,25 +257,25 @@ module.exports = {
editor.moveLinesDown(); editor.moveLinesDown();
assert.equal(["33", "11", "22", "44"].join("\n"), session.toString()); assert.equal(["33", "11", "22", "44"].join("\n"), session.toString());
assert.position(editor.getCursorPosition(), 1, 0); assert.position(editor.getCursorPosition(), 2, 1);
assert.position(editor.getSelection().getSelectionAnchor(), 3, 0); assert.position(editor.getSelection().getSelectionAnchor(), 1, 1);
assert.position(editor.getSelection().getSelectionLead(), 1, 0); assert.position(editor.getSelection().getSelectionLead(), 2, 1);
editor.moveLinesDown(); editor.moveLinesDown();
assert.equal(["33", "44", "11", "22"].join("\n"), session.toString()); assert.equal(["33", "44", "11", "22"].join("\n"), session.toString());
assert.position(editor.getCursorPosition(), 2, 0); assert.position(editor.getCursorPosition(), 3, 1);
assert.position(editor.getSelection().getSelectionAnchor(), 3, 2); assert.position(editor.getSelection().getSelectionAnchor(), 2, 1);
assert.position(editor.getSelection().getSelectionLead(), 2, 0); assert.position(editor.getSelection().getSelectionLead(), 3, 1);
// moving again should have no effect // moving again should have no effect
editor.moveLinesDown(); editor.moveLinesDown();
assert.equal(["33", "44", "11", "22"].join("\n"), session.toString()); assert.equal(["33", "44", "11", "22"].join("\n"), session.toString());
assert.position(editor.getCursorPosition(), 2, 0); assert.position(editor.getCursorPosition(), 3, 1);
assert.position(editor.getSelection().getSelectionAnchor(), 3, 2); assert.position(editor.getSelection().getSelectionAnchor(), 2, 1);
assert.position(editor.getSelection().getSelectionLead(), 2, 0); 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 session = new EditSession(["11", "22", "33", "44"].join("\n"));
var editor = new Editor(new MockRenderer(), session); var editor = new Editor(new MockRenderer(), session);
@ -284,19 +284,18 @@ module.exports = {
editor.moveLinesUp(); editor.moveLinesUp();
assert.equal(session.toString(), ["11", "33", "44", "22"].join("\n")); assert.equal(session.toString(), ["11", "33", "44", "22"].join("\n"));
assert.position(editor.getCursorPosition(), 1, 0); assert.position(editor.getCursorPosition(), 2, 1);
assert.position(editor.getSelection().getSelectionAnchor(), 3, 0); assert.position(editor.getSelection().getSelectionAnchor(), 1, 1);
assert.position(editor.getSelection().getSelectionLead(), 1, 0); assert.position(editor.getSelection().getSelectionLead(), 2, 1);
editor.moveLinesUp(); editor.moveLinesUp();
assert.equal(session.toString(), ["33", "44", "11", "22"].join("\n")); assert.equal(session.toString(), ["33", "44", "11", "22"].join("\n"));
assert.position(editor.getCursorPosition(), 0, 0); assert.position(editor.getCursorPosition(), 1, 1);
assert.position(editor.getSelection().getSelectionAnchor(), 2, 0); assert.position(editor.getSelection().getSelectionAnchor(), 0, 1);
assert.position(editor.getSelection().getSelectionLead(), 0, 0); 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 session = new EditSession(["11", "22", "33", "44"].join("\n"));
var editor = new Editor(new MockRenderer(), session); var editor = new Editor(new MockRenderer(), session);
@ -314,7 +313,7 @@ module.exports = {
assert.position(editor.getCursorPosition(), 1, 1); 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 session = new EditSession(["11", "22", "33", "44"].join("\n"));
var editor = new Editor(new MockRenderer(), session); var editor = new Editor(new MockRenderer(), session);
@ -324,12 +323,12 @@ module.exports = {
editor.copyLinesDown(); editor.copyLinesDown();
assert.equal(["11", "22", "33", "22", "33", "44"].join("\n"), session.toString()); assert.equal(["11", "22", "33", "22", "33", "44"].join("\n"), session.toString());
assert.position(editor.getCursorPosition(), 3, 0); assert.position(editor.getCursorPosition(), 4, 1);
assert.position(editor.getSelection().getSelectionAnchor(), 5, 0); assert.position(editor.getSelection().getSelectionAnchor(), 3, 1);
assert.position(editor.getSelection().getSelectionLead(), 3, 0); 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 session = new EditSession(["11", "22", "33", "44"].join("\n"));
var editor = new Editor(new MockRenderer(), session); var editor = new Editor(new MockRenderer(), session);
@ -339,9 +338,9 @@ module.exports = {
editor.copyLinesUp(); editor.copyLinesUp();
assert.equal(["11", "22", "33", "22", "33", "44"].join("\n"), session.toString()); assert.equal(["11", "22", "33", "22", "33", "44"].join("\n"), session.toString());
assert.position(editor.getCursorPosition(), 1, 0); assert.position(editor.getCursorPosition(), 2, 1);
assert.position(editor.getSelection().getSelectionAnchor(), 3, 0); assert.position(editor.getSelection().getSelectionAnchor(), 1, 1);
assert.position(editor.getSelection().getSelectionLead(), 1, 0); assert.position(editor.getSelection().getSelectionLead(), 2, 1);
}, },
"test: input a tab with soft tab should convert it to spaces" : function() { "test: input a tab with soft tab should convert it to spaces" : function() {

View file

@ -114,8 +114,8 @@ oop.inherits(FoldMode, BaseFoldMode);
var row = stream.getCurrentTokenRow(); var row = stream.getCurrentTokenRow();
if (dir === -1) if (dir === -1)
return new Range(row, session.getLine(row).length, startRow, startColumn); return new Range(row, session.getLine(row).length, startRow, startColumn);
else stream.stepBackward();
return new Range(startRow, startColumn, row, stream.getCurrentTokenColumn()); return new Range(startRow, startColumn, row, stream.getCurrentTokenColumn());
}; };
this.latexSection = function(session, row, column) { this.latexSection = function(session, row, column) {

View file

@ -84,14 +84,14 @@ function DefaultHandlers(mouseHandler) {
// selection // selection
if (inSelection && !editor.isFocused()) { if (inSelection && !editor.isFocused()) {
editor.focus(); editor.focus();
if (this.$focusTimout && !this.$clickSelection) { if (this.$focusTimout && !this.$clickSelection && !editor.inMultiSelectMode) {
this.setState("focusWait"); this.setState("focusWait");
this.captureMouse(ev); this.captureMouse(ev);
return ev.preventDefault(); 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 // Directly pick STATE_SELECT, since the user is not clicking inside
// a selection. // a selection.
this.startSelect(pos); this.startSelect(pos);

View file

@ -50,8 +50,6 @@ function FoldHandler(editor) {
}); });
editor.on("guttermousedown", function(e) { editor.on("guttermousedown", function(e) {
if (!editor.isFocused())
return;
var gutterRegion = editor.renderer.$gutterLayer.getRegion(e); var gutterRegion = editor.renderer.$gutterLayer.getRegion(e);
if (gutterRegion == "foldWidgets") { if (gutterRegion == "foldWidgets") {
@ -59,6 +57,8 @@ function FoldHandler(editor) {
var session = editor.session; var session = editor.session;
if (session.foldWidgets && session.foldWidgets[row]) if (session.foldWidgets && session.foldWidgets[row])
editor.session.onFoldWidgetClick(row, e); editor.session.onFoldWidgetClick(row, e);
if (!editor.isFocused())
editor.focus();
e.stop(); e.stop();
} }
}); });
@ -69,27 +69,8 @@ function FoldHandler(editor) {
if (gutterRegion == "foldWidgets") { if (gutterRegion == "foldWidgets") {
var row = e.getDocumentPosition().row; var row = e.getDocumentPosition().row;
var session = editor.session; var session = editor.session;
var fw = session.foldWidgets; var data = session.getParentFoldRangeData(row, true);
if (!fw || fw[row]) var range = data.range || data.firstRange;
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;
if (range) { if (range) {
var row = range.start.row; var row = range.start.row;

View file

@ -78,9 +78,13 @@ var EditSession = require("./edit_session").EditSession;
if (!this.inMultiSelectMode && this.rangeCount == 0) { if (!this.inMultiSelectMode && this.rangeCount == 0) {
var oldRange = this.toOrientedRange(); 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); return $blockChangeEvents || this.fromOrientedRange(range);
}
this.rangeList.removeAll();
this.rangeList.add(oldRange); this.rangeList.add(oldRange);
this.$onAddRange(oldRange); this.$onAddRange(oldRange);
} }
@ -446,6 +450,8 @@ var Editor = require("./editor").Editor;
editor.multiSelect.mergeOverlappingRanges(); editor.multiSelect.mergeOverlappingRanges();
} else if (command.multiSelectAction == "forEach") { } else if (command.multiSelectAction == "forEach") {
editor.forEachSelection(command, e.args); editor.forEachSelection(command, e.args);
} else if (command.multiSelectAction == "forEachLine") {
editor.forEachSelection(command, e.args, true);
} else if (command.multiSelectAction == "single") { } else if (command.multiSelectAction == "single") {
editor.exitMultiSelectMode(); editor.exitMultiSelectMode();
command.exec(editor, e.args || {}); command.exec(editor, e.args || {});
@ -461,7 +467,7 @@ var Editor = require("./editor").Editor;
* @param {String} args Any arguments for the command * @param {String} args Any arguments for the command
* @method Editor.forEachSelection * @method Editor.forEachSelection
**/ **/
this.forEachSelection = function(cmd, args) { this.forEachSelection = function(cmd, args, $byLines) {
if (this.inVirtualSelectionMode) if (this.inVirtualSelectionMode)
return; return;
@ -475,6 +481,10 @@ var Editor = require("./editor").Editor;
var tmpSel = new Selection(session); var tmpSel = new Selection(session);
this.inVirtualSelectionMode = true; this.inVirtualSelectionMode = true;
for (var i = rangeList.ranges.length; i--;) { 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]); tmpSel.fromOrientedRange(rangeList.ranges[i]);
this.selection = session.selection = tmpSel; this.selection = session.selection = tmpSel;
cmd.exec(this, args || {}); cmd.exec(this, args || {});

View file

@ -30,10 +30,10 @@
define(function(require, exports, module) { define(function(require, exports, module) {
"use strict"; "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. * 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 * @class Range
**/ **/
@ -45,7 +45,6 @@ define(function(require, exports, module) {
* @param {Number} endRow The ending row * @param {Number} endRow The ending row
* @param {Number} endColumn The ending column * @param {Number} endColumn The ending column
* *
*
* @constructor * @constructor
**/ **/
var Range = function(startRow, startColumn, endRow, endColumn) { var Range = function(startRow, startColumn, endRow, endColumn) {
@ -65,14 +64,13 @@ 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`. * 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 * @param {Range} range A range to check against
* *
*
* @return {Boolean} * @return {Boolean}
**/ **/
this.isEqual = function(range) { this.isEqual = function(range) {
return this.start.row == range.start.row && return this.start.row === range.start.row &&
this.end.row == range.end.row && this.end.row === range.end.row &&
this.start.column == range.start.column && this.start.column === range.start.column &&
this.end.column == range.end.column this.end.column === range.end.column;
}; };
/** /**
@ -109,7 +107,6 @@ var Range = function(startRow, startColumn, endRow, endColumn) {
* Compares `this` range (A) with another range (B). * Compares `this` range (A) with another range (B).
* @param {Range} range A range to compare with * @param {Range} range A range to compare with
* *
*
* @related Range.compare * @related Range.compare
* @returns {Number} This method returns one of the following numbers:<br/> * @returns {Number} This method returns one of the following numbers:<br/>
* <br/> * <br/>
@ -150,7 +147,6 @@ 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. * 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 * @param {Range} p A point to compare with
@ -179,7 +175,6 @@ var Range = function(startRow, startColumn, endRow, endColumn) {
* *
* @returns {Boolean} * @returns {Boolean}
* @related Range.comparePoint * @related Range.comparePoint
*
**/ **/
this.containsRange = function(range) { this.containsRange = function(range) {
return this.comparePoint(range.start) == 0 && this.comparePoint(range.end) == 0; 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. * Returns `true` if passed in `range` intersects with the one calling this method.
* @param {Range} range A range to compare with * @param {Range} range A range to compare with
* *
*
* @returns {Boolean} * @returns {Boolean}
**/ **/
this.intersects = function(range) { 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} row A row point to compare with
* @param {Number} column A column point to compare with * @param {Number} column A column point to compare with
* *
*
* @returns {Boolean} * @returns {Boolean}
**/ **/
this.isEnd = function(row, column) { this.isEnd = function(row, column) {
@ -214,7 +207,6 @@ var Range = function(startRow, startColumn, endRow, endColumn) {
* @param {Number} row A row point to compare with * @param {Number} row A row point to compare with
* @param {Number} column A column point to compare with * @param {Number} column A column point to compare with
* *
*
* @returns {Boolean} * @returns {Boolean}
**/ **/
this.isStart = function(row, column) { this.isStart = function(row, column) {
@ -226,8 +218,6 @@ var Range = function(startRow, startColumn, endRow, endColumn) {
* @param {Number} row A row point to set * @param {Number} row A row point to set
* @param {Number} column A column point to set * @param {Number} column A column point to set
* *
*
*
**/ **/
this.setStart = function(row, column) { this.setStart = function(row, column) {
if (typeof row == "object") { if (typeof row == "object") {
@ -244,8 +234,6 @@ var Range = function(startRow, startColumn, endRow, endColumn) {
* @param {Number} row A row point to set * @param {Number} row A row point to set
* @param {Number} column A column point to set * @param {Number} column A column point to set
* *
*
*
**/ **/
this.setEnd = function(row, column) { this.setEnd = function(row, column) {
if (typeof row == "object") { if (typeof row == "object") {
@ -282,7 +270,6 @@ var Range = function(startRow, startColumn, endRow, endColumn) {
* @param {Number} row A row point to compare with * @param {Number} row A row point to compare with
* @param {Number} column A column point to compare with * @param {Number} column A column point to compare with
* *
*
* @returns {Boolean} * @returns {Boolean}
* @related Range.compare * @related Range.compare
**/ **/
@ -302,7 +289,6 @@ var Range = function(startRow, startColumn, endRow, endColumn) {
* @param {Number} row A row point to compare with * @param {Number} row A row point to compare with
* @param {Number} column A column point to compare with * @param {Number} column A column point to compare with
* *
*
* @returns {Boolean} * @returns {Boolean}
* @related Range.compare * @related Range.compare
* *
@ -364,8 +350,6 @@ var Range = function(startRow, startColumn, endRow, endColumn) {
* @param {Number} row A row point to compare with * @param {Number} row A row point to compare with
* @param {Number} column A column point to compare with * @param {Number} column A column point to compare with
* *
*
*
* @returns {Number} This method returns one of the following numbers:<br/> * @returns {Number} This method returns one of the following numbers:<br/>
* <br/> * <br/>
* `0` if the two points are exactly equal<br/> * `0` if the two points are exactly equal<br/>
@ -448,33 +432,16 @@ var Range = function(startRow, startColumn, endRow, endColumn) {
* @returns {Range} * @returns {Range}
**/ **/
this.clipRows = function(firstRow, lastRow) { this.clipRows = function(firstRow, lastRow) {
if (this.end.row > lastRow) { if (this.end.row > lastRow)
var end = { var end = {row: lastRow + 1, column: 0};
row: lastRow+1, else if (this.end.row < firstRow)
column: 0 var end = {row: firstRow, column: 0};
};
}
if (this.start.row > lastRow) { if (this.start.row > lastRow)
var start = { var start = {row: lastRow + 1, column: 0};
row: lastRow+1, else if (this.start.row < firstRow)
column: 0 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); return Range.fromPoints(start || this.start, end || this.end);
}; };
@ -500,7 +467,7 @@ var Range = function(startRow, startColumn, endRow, endColumn) {
}; };
this.isEmpty = function() { 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);
}; };
/** /**
@ -541,10 +508,8 @@ var Range = function(startRow, startColumn, endRow, endColumn) {
* @returns {Range} * @returns {Range}
**/ **/
this.toScreenRange = function(session) { this.toScreenRange = function(session) {
var screenPosStart = var screenPosStart = session.documentToScreenPosition(this.start);
session.documentToScreenPosition(this.start); var screenPosEnd = session.documentToScreenPosition(this.end);
var screenPosEnd =
session.documentToScreenPosition(this.end);
return new Range( return new Range(
screenPosStart.row, screenPosStart.column, screenPosStart.row, screenPosStart.column,
@ -552,6 +517,15 @@ var Range = function(startRow, startColumn, endRow, endColumn) {
); );
}; };
/* 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); }).call(Range.prototype);
/** /**
@ -559,12 +533,12 @@ var Range = function(startRow, startColumn, endRow, endColumn) {
* @param {Range} start A starting point to use * @param {Range} start A starting point to use
* @param {Range} end An ending point to use * @param {Range} end An ending point to use
* *
*
* @returns {Range} * @returns {Range}
**/ **/
Range.fromPoints = function(start, end) { Range.fromPoints = function(start, end) {
return new Range(start.row, start.column, end.row, end.column); return new Range(start.row, start.column, end.row, end.column);
}; };
Range.comparePoints = comparePoints;
exports.Range = Range; exports.Range = Range;
}); });

View file

@ -30,30 +30,28 @@
define(function(require, exports, module) { define(function(require, exports, module) {
"use strict"; "use strict";
var Range = require("./range").Range;
var comparePoints = Range.comparePoints;
var RangeList = function() { var RangeList = function() {
this.ranges = []; this.ranges = [];
}; };
(function() { (function() {
this.comparePoints = function(p1, p2) { this.comparePoints = comparePoints;
return p1.row - p2.row || p1.column - p2.column;
};
this.pointIndex = function(pos, startIndex) { this.pointIndex = function(pos, excludeEdges, startIndex) {
var list = this.ranges; var list = this.ranges;
for (var i = startIndex || 0; i < list.length; i++) { for (var i = startIndex || 0; i < list.length; i++) {
var range = list[i]; var range = list[i];
var cmp = this.comparePoints(pos, range.end); var cmpEnd = comparePoints(pos, range.end);
if (cmpEnd > 0)
if (cmp > 0)
continue; continue;
if (cmp == 0) var cmpStart = comparePoints(pos, range.start);
return i; if (cmpEnd === 0)
cmp = this.comparePoints(pos, range.start); return excludeEdges && cmpStart !== 0 ? -i-2 : i;
if (cmp >= 0) if (cmpStart > 0 || (cmpStart === 0 && !excludeEdges))
return i; return i;
return -i-1; return -i-1;
@ -62,17 +60,17 @@ var RangeList = function() {
}; };
this.add = function(range) { this.add = function(range) {
var startIndex = this.pointIndex(range.start); var excludeEdges = !range.isEmpty();
var startIndex = this.pointIndex(range.start, excludeEdges);
if (startIndex < 0) if (startIndex < 0)
startIndex = -startIndex - 1; startIndex = -startIndex - 1;
var endIndex = this.pointIndex(range.end, startIndex); var endIndex = this.pointIndex(range.end, excludeEdges, startIndex);
if (endIndex < 0) if (endIndex < 0)
endIndex = -endIndex - 1; endIndex = -endIndex - 1;
else else
endIndex++; endIndex++;
return this.ranges.splice(startIndex, endIndex - startIndex, range); return this.ranges.splice(startIndex, endIndex - startIndex, range);
}; };
@ -95,18 +93,23 @@ var RangeList = function() {
this.merge = function() { this.merge = function() {
var removed = []; var removed = [];
var list = this.ranges; var list = this.ranges;
list = list.sort(function(a, b) {
return comparePoints(a.start, b.start);
});
var next = list[0], range; var next = list[0], range;
for (var i = 1; i < list.length; i++) { for (var i = 1; i < list.length; i++) {
range = next; range = next;
next = list[i]; next = list[i];
var cmp = this.comparePoints(range.end, next.start); var cmp = comparePoints(range.end, next.start);
if (cmp < 0) if (cmp < 0)
continue; continue;
if (cmp == 0 && !(range.isEmpty() || next.isEmpty())) if (cmp == 0 && !range.isEmpty() && !next.isEmpty())
continue; continue;
if (this.comparePoints(range.end, next.end) < 0) { if (comparePoints(range.end, next.end) < 0) {
range.end.row = next.end.row; range.end.row = next.end.row;
range.end.column = next.end.column; range.end.column = next.end.column;
} }
@ -117,6 +120,8 @@ var RangeList = function() {
i--; i--;
} }
this.ranges = list;
return removed; return removed;
}; };
@ -199,10 +204,16 @@ var RangeList = function() {
break; break;
if (r.start.row == startRow && r.start.column >= start.column ) { if (r.start.row == startRow && r.start.column >= start.column ) {
r.start.column += colDiff; r.start.column += colDiff;
r.start.row += lineDif; 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.column += colDiff;
r.end.row += lineDif; r.end.row += lineDif;
} }

View file

@ -73,6 +73,26 @@ module.exports = {
assert.equal(rangeList.pointIndex({row: 18, column: 9}), -4); 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() { "test: rangeList add": function() {
var rangeList = new RangeList(); var rangeList = new RangeList();
rangeList.addList([ rangeList.addList([
@ -153,7 +173,6 @@ module.exports = {
rangeList.substractPoint({row: 6, column: 7}); rangeList.substractPoint({row: 6, column: 7});
assert.equal(rangeList.ranges.length, 2); assert.equal(rangeList.ranges.length, 2);
} }
}; };
}); });

View file

@ -85,11 +85,12 @@ var TokenIterator = function(session, initialRow, initialColumn) {
* @returns {String} * @returns {String}
**/ **/
this.stepForward = function() { this.stepForward = function() {
var rowCount = this.$session.getLength();
this.$tokenIndex += 1; this.$tokenIndex += 1;
var rowCount;
while (this.$tokenIndex >= this.$rowTokens.length) { while (this.$tokenIndex >= this.$rowTokens.length) {
this.$row += 1; this.$row += 1;
if (!rowCount)
rowCount = this.$session.getLength();
if (this.$row >= rowCount) { if (this.$row >= rowCount) {
this.$row = rowCount - 1; this.$row = rowCount - 1;
return null; return null;