add undo/redo support to the new document and add
unit tests
This commit is contained in:
parent
c2f7944a75
commit
eece670fb0
2 changed files with 261 additions and 18 deletions
|
|
@ -38,7 +38,6 @@
|
||||||
define(function(require, exports, module) {
|
define(function(require, exports, module) {
|
||||||
|
|
||||||
var oop = require("pilot/oop");
|
var oop = require("pilot/oop");
|
||||||
var lang = require("pilot/lang");
|
|
||||||
var EventEmitter = require("pilot/event_emitter").EventEmitter;
|
var EventEmitter = require("pilot/event_emitter").EventEmitter;
|
||||||
var Range = require("ace/range").Range;
|
var Range = require("ace/range").Range;
|
||||||
|
|
||||||
|
|
@ -143,10 +142,21 @@ var Document = function(text) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.$clipPosition = function(position) {
|
||||||
|
var length = this.$lines.length;
|
||||||
|
if (position.row >= length) {
|
||||||
|
position.row = Math.max(0, length - 1);
|
||||||
|
position.column = this.getLine(length-1).length;
|
||||||
|
}
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
this.insert = function(position, text) {
|
this.insert = function(position, text) {
|
||||||
if (text.length == 0)
|
if (text.length == 0)
|
||||||
return position;
|
return position;
|
||||||
|
|
||||||
|
position = this.$clipPosition(position);
|
||||||
|
|
||||||
if (this.$lines.length <= 1) {
|
if (this.$lines.length <= 1) {
|
||||||
this.$detectNewLine(text);
|
this.$detectNewLine(text);
|
||||||
}
|
}
|
||||||
|
|
@ -160,7 +170,8 @@ var Document = function(text) {
|
||||||
var end = this.insertInLine(position, text);
|
var end = this.insertInLine(position, text);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.insertInLine(position, newLines[0]);
|
var end = this.insertInLine(position, newLines[0]);
|
||||||
|
this.insertNewLine(end);
|
||||||
if (newLines.length > 2)
|
if (newLines.length > 2)
|
||||||
this.insertLines(position.row+1, newLines.slice(1, newLines.length-1));
|
this.insertLines(position.row+1, newLines.slice(1, newLines.length-1));
|
||||||
|
|
||||||
|
|
@ -173,24 +184,23 @@ var Document = function(text) {
|
||||||
this.insertLines = function(row, lines) {
|
this.insertLines = function(row, lines) {
|
||||||
if (lines.length == 0)
|
if (lines.length == 0)
|
||||||
return {row: row, column: 0};
|
return {row: row, column: 0};
|
||||||
|
|
||||||
var args = [row, 0];
|
var args = [row, 0];
|
||||||
args.push.apply(args, lines);
|
args.push.apply(args, lines);
|
||||||
this.$lines.splice.apply(this.$lines, args);
|
this.$lines.splice.apply(this.$lines, args);
|
||||||
|
|
||||||
var range = new Range(row, 0, row + lines.length, 0);
|
var range = new Range(row, 0, row + lines.length - 1, 0);
|
||||||
var delta = {
|
var delta = {
|
||||||
action: "insertLines",
|
action: "insertLines",
|
||||||
nl: this.getNewLineCharacter(),
|
|
||||||
range: range,
|
range: range,
|
||||||
lines: lines
|
lines: lines
|
||||||
};
|
};
|
||||||
this._dispatchEvent("changeDelta", { data: delta });
|
this._dispatchEvent("change", { data: delta });
|
||||||
return range.end;
|
return range.end;
|
||||||
},
|
},
|
||||||
|
|
||||||
this.insertNewLine = function(position) {
|
this.insertNewLine = function(position) {
|
||||||
console.log(position)
|
position = this.$clipPosition(position);
|
||||||
var line = this.$lines[position.row] || "";
|
var line = this.$lines[position.row] || "";
|
||||||
this.$lines[position.row] = line.substring(0, position.column);
|
this.$lines[position.row] = line.substring(0, position.column);
|
||||||
this.$lines.splice(position.row + 1, 0, line.substring(position.column, line.length));
|
this.$lines.splice(position.row + 1, 0, line.substring(position.column, line.length));
|
||||||
|
|
@ -205,12 +215,15 @@ var Document = function(text) {
|
||||||
range: Range.fromPoints(position, end),
|
range: Range.fromPoints(position, end),
|
||||||
text: this.getNewLineCharacter()
|
text: this.getNewLineCharacter()
|
||||||
};
|
};
|
||||||
this._dispatchEvent("changeDelta", { data: delta });
|
this._dispatchEvent("change", { data: delta });
|
||||||
|
|
||||||
return end;
|
return end;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.insertInLine = function(position, text) {
|
this.insertInLine = function(position, text) {
|
||||||
|
if (text.length == 0)
|
||||||
|
return position;
|
||||||
|
|
||||||
var line = this.$lines[position.row] || "";
|
var line = this.$lines[position.row] || "";
|
||||||
this.$lines[position.row] = line.substring(0, position.column) + text
|
this.$lines[position.row] = line.substring(0, position.column) + text
|
||||||
+ line.substring(position.column);
|
+ line.substring(position.column);
|
||||||
|
|
@ -225,18 +238,23 @@ var Document = function(text) {
|
||||||
range: Range.fromPoints(position, end),
|
range: Range.fromPoints(position, end),
|
||||||
text: text
|
text: text
|
||||||
};
|
};
|
||||||
this._dispatchEvent("changeDelta", { data: delta });
|
this._dispatchEvent("change", { data: delta });
|
||||||
|
|
||||||
return end;
|
return end;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.remove = function(range) {
|
this.remove = function(range) {
|
||||||
|
// clip to document
|
||||||
|
range.start = this.$clipPosition(range.start);
|
||||||
|
range.end = this.$clipPosition(range.end);
|
||||||
|
|
||||||
if (range.isEmpty())
|
if (range.isEmpty())
|
||||||
return range.start;
|
return;
|
||||||
|
|
||||||
|
var firstRow = range.start.row;
|
||||||
|
var lastRow = range.end.row;
|
||||||
|
|
||||||
if (range.isMultiLine()) {
|
if (range.isMultiLine()) {
|
||||||
var firstRow = range.start.row;
|
|
||||||
var lastRow = range.end.row;
|
|
||||||
|
|
||||||
// TODO removeInLine can be optimized away!
|
// TODO removeInLine can be optimized away!
|
||||||
this.removeInLine(lastRow, 0, range.end.column);
|
this.removeInLine(lastRow, 0, range.end.column);
|
||||||
|
|
@ -246,13 +264,16 @@ var Document = function(text) {
|
||||||
this.removeNewLine(range.start.row);
|
this.removeNewLine(range.start.row);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.removeInLine(range)
|
this.removeInLine(firstRow, range.start.column, range.end.column);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.removeInLine = function(row, startColumn, endColumn) {
|
this.removeInLine = function(row, startColumn, endColumn) {
|
||||||
|
if (startColumn == endColumn)
|
||||||
|
return;
|
||||||
|
|
||||||
var range = new Range(row, startColumn, row, endColumn);
|
var range = new Range(row, startColumn, row, endColumn);
|
||||||
var line = this.$lines[row];
|
var line = this.getLine(row);
|
||||||
var removed = line.substring(startColumn, endColumn);
|
var removed = line.substring(startColumn, endColumn);
|
||||||
var newLine = line.substring(0, startColumn) + line.substring(endColumn, line.length);
|
var newLine = line.substring(0, startColumn) + line.substring(endColumn, line.length);
|
||||||
this.$lines.splice(row, 1, newLine);
|
this.$lines.splice(row, 1, newLine);
|
||||||
|
|
@ -262,6 +283,7 @@ var Document = function(text) {
|
||||||
range: range,
|
range: range,
|
||||||
text: removed
|
text: removed
|
||||||
};
|
};
|
||||||
|
this._dispatchEvent("change", { data: delta });
|
||||||
return range.start;
|
return range.start;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -275,7 +297,7 @@ var Document = function(text) {
|
||||||
nl: this.getNewLineCharacter(),
|
nl: this.getNewLineCharacter(),
|
||||||
lines: removed
|
lines: removed
|
||||||
};
|
};
|
||||||
this._dispatchEvent("changeDelta", { data: delta });
|
this._dispatchEvent("change", { data: delta });
|
||||||
};
|
};
|
||||||
|
|
||||||
this.removeNewLine = function(row) {
|
this.removeNewLine = function(row) {
|
||||||
|
|
@ -292,7 +314,7 @@ var Document = function(text) {
|
||||||
range: range,
|
range: range,
|
||||||
text: this.getNewLineCharacter()
|
text: this.getNewLineCharacter()
|
||||||
};
|
};
|
||||||
this._dispatchEvent("changeDelta", { data: delta });
|
this._dispatchEvent("change", { data: delta });
|
||||||
};
|
};
|
||||||
|
|
||||||
this.replace = function(range, text) {
|
this.replace = function(range, text) {
|
||||||
|
|
@ -316,6 +338,38 @@ var Document = function(text) {
|
||||||
|
|
||||||
return end;
|
return end;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.applyDeltas = function(deltas) {
|
||||||
|
for (var i=0; i<deltas.length; i++) {
|
||||||
|
var delta = deltas[i];
|
||||||
|
var range = delta.range;
|
||||||
|
|
||||||
|
if (delta.action == "insertLines")
|
||||||
|
this.insertLines(range.start.row, delta.lines)
|
||||||
|
else if (delta.action == "insertText")
|
||||||
|
this.insert(range.start, delta.text)
|
||||||
|
else if (delta.action == "removeLines")
|
||||||
|
this.removeLines(range.start.row, range.end.row)
|
||||||
|
else if (delta.action == "removeText")
|
||||||
|
this.remove(range)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.revertDeltas = function(deltas) {
|
||||||
|
for (var i=deltas.length-1; i>=0; i--) {
|
||||||
|
var delta = deltas[i];
|
||||||
|
var range = delta.range;
|
||||||
|
|
||||||
|
if (delta.action == "insertLines")
|
||||||
|
this.removeLines(range.start.row, range.end.row)
|
||||||
|
else if (delta.action == "insertText")
|
||||||
|
this.remove(range)
|
||||||
|
else if (delta.action == "removeLines")
|
||||||
|
this.insertLines(range.start.row, delta.lines)
|
||||||
|
else if (delta.action == "removeText")
|
||||||
|
this.insert(range.start, delta.text)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}).call(Document.prototype);
|
}).call(Document.prototype);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,198 @@ var Document = require("../document").Document,
|
||||||
|
|
||||||
var Test = {
|
var Test = {
|
||||||
|
|
||||||
|
"test: insert text in line" : function() {
|
||||||
|
var doc = new Document(["12", "34"]);
|
||||||
|
|
||||||
|
var deltas = [];
|
||||||
|
doc.on("change", function(e) { deltas.push(e.data); });
|
||||||
|
|
||||||
|
doc.insert({row: 0, column: 1}, "juhu");
|
||||||
|
assert.equal(doc.getValue(), ["1juhu2", "34"].join("\n"));
|
||||||
|
|
||||||
|
var d = deltas.concat();
|
||||||
|
doc.revertDeltas(d);
|
||||||
|
assert.equal(doc.getValue(), ["12", "34"].join("\n"));
|
||||||
|
|
||||||
|
doc.applyDeltas(d);
|
||||||
|
assert.equal(doc.getValue(), ["1juhu2", "34"].join("\n"));
|
||||||
|
},
|
||||||
|
|
||||||
|
"test: insert new line" : function() {
|
||||||
|
var doc = new Document(["12", "34"]);
|
||||||
|
|
||||||
|
var deltas = [];
|
||||||
|
doc.on("change", function(e) { deltas.push(e.data); });
|
||||||
|
|
||||||
|
doc.insertNewLine({row: 0, column: 1});
|
||||||
|
assert.equal(doc.getValue(), ["1", "2", "34"].join("\n"));
|
||||||
|
|
||||||
|
var d = deltas.concat();
|
||||||
|
doc.revertDeltas(d);
|
||||||
|
assert.equal(doc.getValue(), ["12", "34"].join("\n"));
|
||||||
|
|
||||||
|
doc.applyDeltas(d);
|
||||||
|
assert.equal(doc.getValue(), ["1", "2", "34"].join("\n"));
|
||||||
|
},
|
||||||
|
|
||||||
|
"test: insert lines at the beginning" : function() {
|
||||||
|
var doc = new Document(["12", "34"]);
|
||||||
|
|
||||||
|
var deltas = [];
|
||||||
|
doc.on("change", function(e) { deltas.push(e.data); });
|
||||||
|
|
||||||
|
doc.insertLines(0, ["aa", "bb"]);
|
||||||
|
assert.equal(doc.getValue(), ["aa", "bb", "12", "34"].join("\n"));
|
||||||
|
|
||||||
|
var d = deltas.concat();
|
||||||
|
doc.revertDeltas(d);
|
||||||
|
assert.equal(doc.getValue(), ["12", "34"].join("\n"));
|
||||||
|
|
||||||
|
doc.applyDeltas(d);
|
||||||
|
assert.equal(doc.getValue(), ["aa", "bb", "12", "34"].join("\n"));
|
||||||
|
},
|
||||||
|
|
||||||
|
"test: insert lines at the end" : function() {
|
||||||
|
var doc = new Document(["12", "34"]);
|
||||||
|
|
||||||
|
var deltas = [];
|
||||||
|
doc.on("change", function(e) { deltas.push(e.data); });
|
||||||
|
|
||||||
|
doc.insertLines(2, ["aa", "bb"]);
|
||||||
|
assert.equal(doc.getValue(), ["12", "34", "aa", "bb"].join("\n"));
|
||||||
|
},
|
||||||
|
|
||||||
|
"test: insert lines in the middle" : function() {
|
||||||
|
var doc = new Document(["12", "34"]);
|
||||||
|
|
||||||
|
var deltas = [];
|
||||||
|
doc.on("change", function(e) { deltas.push(e.data); });
|
||||||
|
|
||||||
|
doc.insertLines(1, ["aa", "bb"]);
|
||||||
|
assert.equal(doc.getValue(), ["12", "aa", "bb", "34"].join("\n"));
|
||||||
|
|
||||||
|
var d = deltas.concat();
|
||||||
|
doc.revertDeltas(d);
|
||||||
|
assert.equal(doc.getValue(), ["12", "34"].join("\n"));
|
||||||
|
|
||||||
|
doc.applyDeltas(d);
|
||||||
|
assert.equal(doc.getValue(), ["12", "aa", "bb", "34"].join("\n"));
|
||||||
|
},
|
||||||
|
|
||||||
|
"test: insert multi line string at the start" : function() {
|
||||||
|
var doc = new Document(["12", "34"]);
|
||||||
|
|
||||||
|
var deltas = [];
|
||||||
|
doc.on("change", function(e) { deltas.push(e.data); });
|
||||||
|
|
||||||
|
doc.insert({row: 0, column: 0}, "aa\nbb\ncc");
|
||||||
|
assert.equal(doc.getValue(), ["aa", "bb", "cc12", "34"].join("\n"));
|
||||||
|
|
||||||
|
var d = deltas.concat();
|
||||||
|
doc.revertDeltas(d);
|
||||||
|
assert.equal(doc.getValue(), ["12", "34"].join("\n"));
|
||||||
|
|
||||||
|
doc.applyDeltas(d);
|
||||||
|
assert.equal(doc.getValue(), ["aa", "bb", "cc12", "34"].join("\n"));
|
||||||
|
},
|
||||||
|
|
||||||
|
"test: insert multi line string at the end" : function() {
|
||||||
|
var doc = new Document(["12", "34"]);
|
||||||
|
|
||||||
|
var deltas = [];
|
||||||
|
doc.on("change", function(e) { deltas.push(e.data); });
|
||||||
|
|
||||||
|
doc.insert({row: 2, column: 0}, "aa\nbb\ncc");
|
||||||
|
assert.equal(doc.getValue(), ["12", "34aa", "bb", "cc"].join("\n"));
|
||||||
|
|
||||||
|
var d = deltas.concat();
|
||||||
|
doc.revertDeltas(d);
|
||||||
|
assert.equal(doc.getValue(), ["12", "34"].join("\n"));
|
||||||
|
|
||||||
|
doc.applyDeltas(d);
|
||||||
|
assert.equal(doc.getValue(), ["12", "34aa", "bb", "cc"].join("\n"));
|
||||||
|
},
|
||||||
|
|
||||||
|
"test: insert multi line string in the middle" : function() {
|
||||||
|
var doc = new Document(["12", "34"]);
|
||||||
|
|
||||||
|
var deltas = [];
|
||||||
|
doc.on("change", function(e) { deltas.push(e.data); });
|
||||||
|
|
||||||
|
doc.insert({row: 0, column: 1}, "aa\nbb\ncc");
|
||||||
|
assert.equal(doc.getValue(), ["1aa", "bb", "cc2", "34"].join("\n"));
|
||||||
|
|
||||||
|
var d = deltas.concat();
|
||||||
|
doc.revertDeltas(d);
|
||||||
|
assert.equal(doc.getValue(), ["12", "34"].join("\n"));
|
||||||
|
|
||||||
|
doc.applyDeltas(d);
|
||||||
|
assert.equal(doc.getValue(), ["1aa", "bb", "cc2", "34"].join("\n"));
|
||||||
|
},
|
||||||
|
|
||||||
|
"test: delete in line" : function() {
|
||||||
|
var doc = new Document(["1234", "5678"]);
|
||||||
|
|
||||||
|
var deltas = [];
|
||||||
|
doc.on("change", function(e) { deltas.push(e.data); });
|
||||||
|
|
||||||
|
doc.remove(new Range(0, 1, 0, 3));
|
||||||
|
assert.equal(doc.getValue(), ["14", "5678"].join("\n"));
|
||||||
|
|
||||||
|
var d = deltas.concat();
|
||||||
|
doc.revertDeltas(d);
|
||||||
|
assert.equal(doc.getValue(), ["1234", "5678"].join("\n"));
|
||||||
|
|
||||||
|
doc.applyDeltas(d);
|
||||||
|
assert.equal(doc.getValue(), ["14", "5678"].join("\n"));
|
||||||
|
},
|
||||||
|
|
||||||
|
"test: delete new line" : function() {
|
||||||
|
var doc = new Document(["1234", "5678"]);
|
||||||
|
|
||||||
|
var deltas = [];
|
||||||
|
doc.on("change", function(e) { deltas.push(e.data); });
|
||||||
|
|
||||||
|
doc.remove(new Range(0, 4, 1, 0));
|
||||||
|
assert.equal(doc.getValue(), ["12345678"].join("\n"));
|
||||||
|
|
||||||
|
var d = deltas.concat();
|
||||||
|
doc.revertDeltas(d);
|
||||||
|
assert.equal(doc.getValue(), ["1234", "5678"].join("\n"));
|
||||||
|
|
||||||
|
doc.applyDeltas(d);
|
||||||
|
assert.equal(doc.getValue(), ["12345678"].join("\n"));
|
||||||
|
},
|
||||||
|
|
||||||
|
"test: delete multi line range line" : function() {
|
||||||
|
var doc = new Document(["1234", "5678", "abcd"]);
|
||||||
|
|
||||||
|
var deltas = [];
|
||||||
|
doc.on("change", function(e) { deltas.push(e.data); });
|
||||||
|
|
||||||
|
doc.remove(new Range(0, 2, 2, 2));
|
||||||
|
assert.equal(doc.getValue(), ["12cd"].join("\n"));
|
||||||
|
|
||||||
|
var d = deltas.concat();
|
||||||
|
doc.revertDeltas(d);
|
||||||
|
assert.equal(doc.getValue(), ["1234", "5678", "abcd"].join("\n"));
|
||||||
|
|
||||||
|
doc.applyDeltas(d);
|
||||||
|
assert.equal(doc.getValue(), ["12cd"].join("\n"));
|
||||||
|
},
|
||||||
|
|
||||||
|
"test: delete full lines" : function() {
|
||||||
|
var doc = new Document(["1234", "5678", "abcd"]);
|
||||||
|
|
||||||
|
var deltas = [];
|
||||||
|
doc.on("change", function(e) { deltas.push(e.data); });
|
||||||
|
|
||||||
|
doc.remove(new Range(1, 0, 3, 0));
|
||||||
|
assert.equal(doc.getValue(), ["1234", ""].join("\n"));
|
||||||
|
},
|
||||||
|
|
||||||
"test: should handle unix style new lines" : function() {
|
"test: should handle unix style new lines" : function() {
|
||||||
var doc = new Document(["1", "2", "3"]);
|
var doc = new Document(["1", "2", "3"]);
|
||||||
|
|
||||||
assert.equal(doc.getValue(), ["1", "2", "3"].join("\n"));
|
assert.equal(doc.getValue(), ["1", "2", "3"].join("\n"));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue