From dac1b38064c8a284aae04e884be383626a71a601 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 25 Apr 2011 21:27:33 +0200 Subject: [PATCH] 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. --- lib/ace/edit_session.js | 149 +++++++++++++++----- lib/ace/edit_session_test.js | 265 +++++++++++++++++++++++------------ 2 files changed, 284 insertions(+), 130 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index abf9d187..16d4ee08 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -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. diff --git a/lib/ace/edit_session_test.js b/lib/ace/edit_session_test.js index ca12ab69..0ff93650 100644 --- a/lib/ace/edit_session_test.js +++ b/lib/ace/edit_session_test.js @@ -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. } };