Folds realign when inserting/deleting any kind of content from the document. Adds a ton of unit tests. Fixes bugs in EditSession.getFoldsInRange and add .toString() functions to make debugging way easier.

This commit is contained in:
Julian Viereck 2011-04-25 21:27:33 +02:00
commit dac1b38064
2 changed files with 284 additions and 130 deletions

View file

@ -57,6 +57,13 @@ var EditSession = function(text, mode) {
this.$markerId = 1;
this.$wrapData = [];
this.$foldData = [];
this.$foldData.toString = function() {
var str = "";
this.forEach(function(foldLine) {
str += "\n" + foldLine.toString();
});
return str;
}
if (text instanceof Document) {
this.setDocument(text);
@ -140,14 +147,17 @@ var EditSession = function(text, mode) {
if (undoManager) {
var self = this;
this.$informUndoManager = lang.deferredCall(function() {
this.$syncInformUndoManager = function() {
self.$informUndoManager.cancel();
if (self.$deltas.length > 0)
undoManager.execute({
action : "aceupdate",
args : [self.$deltas, self]
});
self.$deltas = [];
});
}
this.$informUndoManager =
lang.deferredCall(this.$syncInformUndoManager);
}
};
@ -865,6 +875,8 @@ var EditSession = function(text, mode) {
start = e.data.range.start,
end = e.data.range.end;
console.log("onChange", action, e.data.range + "");
if (action.indexOf("Lines") != -1) {
if (action == "insertLines") {
lastRow = firstRow + (e.data.lines.length);
@ -879,15 +891,28 @@ var EditSession = function(text, mode) {
if (len != 0) {
if (action.indexOf("remove") != -1) {
useWrapMode && this.$wrapData.splice(firstRow, len);
// TODO: Remove no longer needed folds here.
// TODO: Update row data on folds.
var foldLines = this.$foldData;
var folds = this.getFoldsInRange(e.data.range);
this.removeFolds(folds);
var foldLine = this.getFoldLine(lastRow);
var idx = 0;
if (foldLine) {
foldLine.addRemoveChars(end.row, end.column, start.column - end.column);
foldLine.shiftRow(-len);
for (var i = 0; i < foldLines.length; i++) {
var foldLine = foldLines[i];
if (foldLine.start.row >= firstRow + len) {
var foldLineBefore = this.getFoldLine(firstRow);
if (foldLineBefore && foldLineBefore !== foldLine) {
foldLineBefore.merge(foldLine);
foldLine = foldLineBefore;
}
idx = foldLines.indexOf(foldLine) + 1;
}
for (idx; idx < foldLines.length; idx++) {
var foldLine = foldLines[idx];
if (foldLine.start.row >= lastRow) {
foldLine.shiftRow(-len);
}
}
@ -901,8 +926,6 @@ var EditSession = function(text, mode) {
this.$wrapData.splice.apply(this.$wrapData, args);
}
// TODO: Expand folds here if needed.
// If some new line is added inside of a foldLine, then split
// the fold line up.
var foldLines = this.$foldData;
@ -918,7 +941,6 @@ var EditSession = function(text, mode) {
foldLine.shiftRow(len);
foldLine.addRemoveChars(
lastRow, 0, end.column - start.column);
this.$addFoldLine(foldLine);
} else
// Infront of the foldLine but same row. Need to shift column.
if (cmp == -1) {
@ -937,17 +959,24 @@ var EditSession = function(text, mode) {
}
}
} else {
// Realign folds. E.g. if you add some new chars before a fold, the
// fold should "move" to the right.
var column;
len = Math.abs(e.data.range.start.column - e.data.range.end.column);
if (action.indexOf("insert") != -1) {
column = e.data.range.start.column;
} else {
column = e.data.range.end.column;
if (action.indexOf("remove") != -1) {
// Get all the folds in the change range and remove them.
this.removeFolds(this.getFoldsInRange(e.data.range));
len = -len;
}
// if (action.indexOf("insert") != -1) {
// column = start.column;
// } else {
// column = end.column;
// len = -len;
// }
var foldLine = this.getFoldLine(firstRow);
if (foldLine) {
foldLine.addRemoveChars(firstRow, column, len);
foldLine.addRemoveChars(firstRow, start.column, len);
}
}
@ -1436,11 +1465,16 @@ var EditSession = function(text, mode) {
this.sameRow = range.start.row == range.end.row;
}
Fold.prototype.toString = function() {
return '"' + this.placeholder + '" ' + this.range.toString();
}
/**
* Creates a new FoldLine. If the an array is passed in, the folds are
* expected to be sorted already.
*/
function FoldLine(folds) {
function FoldLine(foldData, folds) {
this.foldData = foldData;
if (Array.isArray(folds)) {
this.folds = folds;
} else {
@ -1495,6 +1529,7 @@ var EditSession = function(text, mode) {
} else {
throw "Trying to add fold to FoldRow that doesn't have a matching row";
}
fold.foldLine = this;
}
this.getRowLength = function() {
@ -1553,7 +1588,7 @@ var EditSession = function(text, mode) {
}
this.getNextFoldTo = function(row, column) {
var fold;
var fold, cmp;
for (var i = 0; i < this.folds.length; i++) {
fold = this.folds[i];
cmp = fold.range.compareEnd(row, column);
@ -1605,6 +1640,8 @@ var EditSession = function(text, mode) {
this.split = function(row, column) {
var fold = this.getNextFoldTo(row, column).fold,
folds = this.folds;
var foldData = this.foldData;
if (!fold) {
return null;
}
@ -1615,8 +1652,34 @@ var EditSession = function(text, mode) {
// Remove the folds after row/column and create a new FoldLine
// containing these removed folds.
var folds = folds.splice(i, folds.length - i);
return new FoldLine(folds);
folds = folds.splice(i, folds.length - i);
var newFoldLine = new FoldLine(foldData, folds);
foldData.splice(foldData.indexOf(this) + 1, 0, newFoldLine);
return newFoldLine;
}
this.merge = function(foldLineNext) {
var folds = foldLineNext.folds;
for (var i = 0; i < folds.length; i++) {
this.addFold(folds[i]);
}
// Remove the foldLineNext - no longer needed, as
// it's merged now with foldLineNext.
var foldData = this.foldData;
foldData.splice(foldData.indexOf(foldLineNext), 1);
}
this.toString = function() {
var ret = [this.range.toString() + ": [" ];
this.folds.forEach(function(fold) {
ret.push(" " + fold.toString());
});
ret.push("]")
return ret.join("\n");
}
}).call(FoldLine.prototype);
@ -1661,7 +1724,7 @@ var EditSession = function(text, mode) {
for (var i = 0; i < foldLines.length; i++) {
cmp = foldLines[i].range.compare(start.row, start.column + 1);
if (cmp == 1) {
break;
continue;
} else if (cmp == -1) {
cmp = foldLines[i].range.compare(end.row, end.column - 1);
if (cmp == -1) {
@ -1672,12 +1735,21 @@ var EditSession = function(text, mode) {
folds = foldLines[i].folds;
for (var j = 0; j < folds.length; j++) {
fold = folds[j];
cmp = fold.range.compare(end.row, end.column + 1);
if (cmp == -1) {
cmp = fold.range.compare(end.row, end.column - 1);
if (cmp == 1) {
cmp = fold.range.compare(start.row, start.column + 1);
if (cmp == 1) {
continue;
}
} else if (cmp == -1) {
break;
} else {
foundFolds.push(fold);
cmp = fold.range.compare(start.row, start.column + 1);
if (cmp == 1) {
continue;
}
}
foundFolds.push(fold);
}
}
return foundFolds;
@ -1784,13 +1856,7 @@ var EditSession = function(text, mode) {
foldLineNext = foldData[i + 1];
if (foldLineNext && foldLineNext.start.row == endRow) {
// We need to merge!
var nextFolds = foldLineNext.folds;
for (var i = 0; i < nextFolds.length; i++) {
foldLine.addFold(nextFolds[i]);
}
// Remove the foldLineNext - no longer needed, as
// it's merged now with foldLine.
foldData.splice(foldData.indexOf(foldLineNext), 1);
foldLine.merge(foldLineNext);
break;
}
}
@ -1801,7 +1867,7 @@ var EditSession = function(text, mode) {
}
if (!added) {
this.$addFoldLine(new FoldLine(fold));
this.$addFoldLine(new FoldLine(this.$foldData, fold));
}
// TODO: Recalculate wrapData
@ -1812,10 +1878,8 @@ var EditSession = function(text, mode) {
this._dispatchEvent("changeFold");
};
this.removeFold = function(fold, foldLine) {
if (foldLine.folds.indexOf(fold) == -1) {
throw "FoldLine doesn't contain fold.";
}
this.removeFold = function(fold) {
var foldLine = fold.foldLine;
var foldLines = this.$foldData,
folds = foldLine.folds;
@ -1827,12 +1891,14 @@ var EditSession = function(text, mode) {
// If the fold is the last fold of the foldLine, just remove it.
if (foldLine.range.isEnd(fold.end.row, fold.end.column)) {
folds.pop();
foldLine.end = folds[folds.length - 1].end;
foldLine.end.row = folds[folds.length - 1].end.row;
foldLine.end.column = folds[folds.length - 1].end.column;
} else
// If the fold is the first fold of the foldLine, just remove it.
if (foldLine.range.isStart(fold.start.row, fold.start.column)) {
folds.shift();
foldLine.start = folds[0].start;
foldLine.start.row = folds[0].start.row;
foldLine.start.column = folds[0].start.column;
} else
// We know there are more then 2 folds and the fold is not at the edge.
// This means, the fold is somewhere in between.
@ -1846,7 +1912,8 @@ var EditSession = function(text, mode) {
{
var newFoldLine = foldLine.split(fold.start.row, fold.start.column);
newFoldLine.folds.shift();
newFoldLine.start = newFoldLine.folds[0].start;
foldLine.start.row = folds[0].start.row;
foldLine.start.column = folds[0].start.column;
this.$addFoldLine(newFoldLine);
}
@ -1856,6 +1923,12 @@ var EditSession = function(text, mode) {
this._dispatchEvent("changeFold");
}
this.removeFolds = function(folds) {
folds.forEach(function(fold) {
this.removeFold(fold);
}, this);
}
/**
* Checks if a given documentRow is folded. This is true if there are some
* folded parts such that some parts of the line is still visible.

View file

@ -59,6 +59,7 @@ function createFoldTestSession() {
"}"
];
var session = new EditSession(lines.join("\n"));
session.setUndoManager(new UndoManager());
session.addFold(new Range(0, 13, 0, 18), "args...");
session.addFold(new Range(1, 10, 2, 10), "foo...");
session.addFold(new Range(2, 20, 2, 25), "bar...");
@ -451,98 +452,6 @@ module.exports = {
assertScreen2Doc(3, 0, 2, 0);
},
"test fold one-line text insert": function() {
// These are mostly test for the FoldLine.addRemoveChars function.
var session = createFoldTestSession(),
foldLines = session.$foldData;
function insert(row, column, text) {
session.insert({row: row, column: column}, text);
}
var foldLine, fold, folds;
// First line.
foldLine = session.$foldData[0];
fold = foldLine.folds[0];
insert(0, 0, "F");
assert.range(foldLine.range, 0, 14, 0, 19);
assert.range(fold.range, 0, 14, 0, 19);
insert(0, 14, "F");
assert.range(foldLine.range, 0, 15, 0, 20);
assert.range(fold.range, 0, 15, 0, 20);
insert(0, 20, "F");
assert.range(foldLine.range, 0, 15, 0, 20);
assert.range(fold.range, 0, 15, 0, 20);
// Second line.
foldLine = session.$foldData[1];
folds = foldLine.folds;
insert(1, 0, "F");
assert.range(foldLine.range, 1, 11, 2, 25);
assert.range(folds[0].range, 1, 11, 2, 10);
assert.range(folds[1].range, 2, 20, 2, 25);
insert(1, 11, "F");
assert.range(foldLine.range, 1, 12, 2, 25);
assert.range(folds[0].range, 1, 12, 2, 10);
assert.range(folds[1].range, 2, 20, 2, 25);
insert(2, 10, "F");
assert.range(foldLine.range, 1, 12, 2, 26);
assert.range(folds[0].range, 1, 12, 2, 10);
assert.range(folds[1].range, 2, 21, 2, 26);
insert(2, 21, "F");
assert.range(foldLine.range, 1, 12, 2, 27);
assert.range(folds[0].range, 1, 12, 2, 10);
assert.range(folds[1].range, 2, 22, 2, 27);
insert(2, 27, "F");
assert.range(foldLine.range, 1, 12, 2, 27);
assert.range(folds[0].range, 1, 12, 2, 10);
assert.range(folds[1].range, 2, 22, 2, 27);
},
"test fold multi-line insert": function() {
var session = createFoldTestSession(),
foldLines = session.$foldData;
function insert(row, column, text) {
session.insert({row: row, column: column}, text);
}
var foldLines = session.$foldData, foldLine, fold, folds;
insert(0, 0, "\nfoo");
assert.equal(foldLines.length, 2);
assert.range(foldLines[0].range, 1, 16, 1, 21);
assert.range(foldLines[1].range, 2, 10, 3, 25);
insert(2, 0, "\nbar");
assert.equal(foldLines.length, 2);
assert.range(foldLines[0].range, 1, 16, 1, 21);
assert.range(foldLines[1].range, 3, 13, 4, 25);
insert(3, 10, "\nfoo");
assert.equal(foldLines.length, 2);
assert.range(foldLines[0].range, 1, 16, 1, 21);
assert.range(foldLines[1].range, 4, 6, 5, 25);
insert(5, 10, "\nbar");
assert.equal(foldLines.length, 3);
assert.range(foldLines[0].range, 1, 16, 1, 21);
assert.range(foldLines[1].range, 4, 6, 5, 10);
assert.range(foldLines[2].range, 6, 13, 6, 18);
insert(6, 18, "\nfoo");
assert.equal(foldLines.length, 3);
assert.range(foldLines[0].range, 1, 16, 1, 21);
assert.range(foldLines[1].range, 4, 6, 5, 10);
assert.range(foldLines[2].range, 6, 13, 6, 18);
// TODO: Add test for inseration inside of folds.
},
"test getFoldsInRange()": function() {
var session = createFoldTestSession(),
foldLines = session.$foldData;
@ -564,6 +473,178 @@ module.exports = {
test(0, 0, 1, 10, [ folds[0] ]);
test(0, 0, 1, 11, [ folds[0], folds[1] ]);
test(0, 18, 1, 11, [ folds[1] ]);
test(2, 0, 2, 13, [ folds[1] ]);
test(2, 10, 2, 20, [ ]);
test(2, 10, 2, 11, [ ]);
},
"test fold one-line text insert": function() {
// These are mostly test for the FoldLine.addRemoveChars function.
var session = createFoldTestSession(),
undoManager = session.getUndoManager(),
foldLines = session.$foldData;
function insert(row, column, text) {
session.insert({row: row, column: column}, text);
// Force the session to store all changes made to the document NOW
// on the undoManager's queue. Otherwise we can't undo in separate
// steps later.
session.$syncInformUndoManager();
}
var foldLine, fold, folds;
// First line.
foldLine = session.$foldData[0];
fold = foldLine.folds[0];
insert(0, 0, "0");
assert.range(foldLine.range, 0, 14, 0, 19);
assert.range(fold.range, 0, 14, 0, 19);
insert(0, 14, "1");
assert.range(foldLine.range, 0, 15, 0, 20);
assert.range(fold.range, 0, 15, 0, 20);
insert(0, 20, "2");
assert.range(foldLine.range, 0, 15, 0, 20);
assert.range(fold.range, 0, 15, 0, 20);
// Second line.
foldLine = session.$foldData[1];
folds = foldLine.folds;
insert(1, 0, "3");
assert.range(foldLine.range, 1, 11, 2, 25);
assert.range(folds[0].range, 1, 11, 2, 10);
assert.range(folds[1].range, 2, 20, 2, 25);
insert(1, 11, "4");
assert.range(foldLine.range, 1, 12, 2, 25);
assert.range(folds[0].range, 1, 12, 2, 10);
assert.range(folds[1].range, 2, 20, 2, 25);
insert(2, 10, "5");
assert.range(foldLine.range, 1, 12, 2, 26);
assert.range(folds[0].range, 1, 12, 2, 10);
assert.range(folds[1].range, 2, 21, 2, 26);
insert(2, 21, "6");
assert.range(foldLine.range, 1, 12, 2, 27);
assert.range(folds[0].range, 1, 12, 2, 10);
assert.range(folds[1].range, 2, 22, 2, 27);
insert(2, 27, "7");
assert.range(foldLine.range, 1, 12, 2, 27);
assert.range(folds[0].range, 1, 12, 2, 10);
assert.range(folds[1].range, 2, 22, 2, 27);
// UNDO = REMOVE
undoManager.undo(); // 6
assert.range(foldLine.range, 1, 12, 2, 27);
assert.range(folds[0].range, 1, 12, 2, 10);
assert.range(folds[1].range, 2, 22, 2, 27);
undoManager.undo(); // 5
assert.range(foldLine.range, 1, 12, 2, 26);
assert.range(folds[0].range, 1, 12, 2, 10);
assert.range(folds[1].range, 2, 21, 2, 26);
undoManager.undo(); // 4
assert.range(foldLine.range, 1, 12, 2, 25);
assert.range(folds[0].range, 1, 12, 2, 10);
assert.range(folds[1].range, 2, 20, 2, 25);
undoManager.undo(); // 3
assert.range(foldLine.range, 1, 11, 2, 25);
assert.range(folds[0].range, 1, 11, 2, 10);
assert.range(folds[1].range, 2, 20, 2, 25);
undoManager.undo(); // Beginning first line.
assert.equal(foldLines.length, 2);
assert.range(foldLines[0].range, 0, 15, 0, 20);
assert.range(foldLines[1].range, 1, 10, 2, 25);
foldLine = session.$foldData[0];
fold = foldLine.folds[0];
undoManager.undo(); // 2
assert.range(foldLine.range, 0, 15, 0, 20);
assert.range(fold.range, 0, 15, 0, 20);
undoManager.undo(); // 1
assert.range(foldLine.range, 0, 14, 0, 19);
assert.range(fold.range, 0, 14, 0, 19);
undoManager.undo(); // 0
assert.range(foldLine.range, 0, 13, 0, 18);
assert.range(fold.range, 0, 13, 0, 18);
},
"test fold multi-line insert/remove": function() {
var session = createFoldTestSession(),
undoManager = session.getUndoManager(),
foldLines = session.$foldData;
function insert(row, column, text) {
session.insert({row: row, column: column}, text);
// Force the session to store all changes made to the document NOW
// on the undoManager's queue. Otherwise we can't undo in separate
// steps later.
session.$syncInformUndoManager();
}
var foldLines = session.$foldData, foldLine, fold, folds;
insert(0, 0, "\nfo0");
assert.equal(foldLines.length, 2);
assert.range(foldLines[0].range, 1, 16, 1, 21);
assert.range(foldLines[1].range, 2, 10, 3, 25);
insert(2, 0, "\nba1");
assert.equal(foldLines.length, 2);
assert.range(foldLines[0].range, 1, 16, 1, 21);
assert.range(foldLines[1].range, 3, 13, 4, 25);
insert(3, 10, "\nfo2");
assert.equal(foldLines.length, 2);
assert.range(foldLines[0].range, 1, 16, 1, 21);
assert.range(foldLines[1].range, 4, 6, 5, 25);
insert(5, 10, "\nba3");
assert.equal(foldLines.length, 3);
assert.range(foldLines[0].range, 1, 16, 1, 21);
assert.range(foldLines[1].range, 4, 6, 5, 10);
assert.range(foldLines[2].range, 6, 13, 6, 18);
insert(6, 18, "\nfo4");
assert.equal(foldLines.length, 3);
assert.range(foldLines[0].range, 1, 16, 1, 21);
assert.range(foldLines[1].range, 4, 6, 5, 10);
assert.range(foldLines[2].range, 6, 13, 6, 18);
undoManager.undo(); // 3
assert.equal(foldLines.length, 3);
assert.range(foldLines[0].range, 1, 16, 1, 21);
assert.range(foldLines[1].range, 4, 6, 5, 10);
assert.range(foldLines[2].range, 6, 13, 6, 18);
undoManager.undo(); // 2
assert.equal(foldLines.length, 2);
assert.range(foldLines[0].range, 1, 16, 1, 21);
assert.range(foldLines[1].range, 4, 6, 5, 25);
undoManager.undo(); // 1
assert.equal(foldLines.length, 2);
assert.range(foldLines[0].range, 1, 16, 1, 21);
assert.range(foldLines[1].range, 3, 13, 4, 25);
undoManager.undo(); // 0
assert.equal(foldLines.length, 2);
assert.range(foldLines[0].range, 1, 16, 1, 21);
assert.range(foldLines[1].range, 2, 10, 3, 25);
undoManager.undo(); // Beginning
assert.equal(foldLines.length, 2);
assert.range(foldLines[0].range, 0, 13, 0, 18);
assert.range(foldLines[1].range, 1, 10, 2, 25);
// TODO: Add test for inseration inside of folds.
}
};