continue refactoring

This commit is contained in:
nightwing 2014-02-14 22:35:22 +04:00
commit 6b60bcbbd6
7 changed files with 135 additions and 134 deletions

View file

@ -42,7 +42,6 @@ function positionInDocument(docLines, position) {
} }
function validateDelta(docLines, delta) { function validateDelta(docLines, delta) {
// Validate action string. // Validate action string.
if (delta.action != "insert" && delta.action != "remove") if (delta.action != "insert" && delta.action != "remove")
throwDeltaError(delta, "delta.action must be 'insert' or 'remove'"); throwDeltaError(delta, "delta.action must be 'insert' or 'remove'");
@ -73,47 +72,37 @@ function validateDelta(docLines, delta) {
} }
exports.applyDelta = function(docLines, delta, doNotValidate) { exports.applyDelta = function(docLines, delta, doNotValidate) {
// Validate delta.
if (!doNotValidate) if (!doNotValidate)
validateDelta(docLines, delta); validateDelta(docLines, delta);
var row = delta.start.row; var row = delta.start.row;
var startColumn = delta.start.column; var startColumn = delta.start.column;
var line = docLines[row]; var line = docLines[row];
// Apply delta. switch (delta.action) {
if (row == delta.end.row) { case "insert":
// Apply single-line delta. var lines = delta.lines;
// Note: The multi-line code below correctly handle single-line if (lines.length === 1) {
// deltas too, but we need to short-circuit for speed.
var endColumn = delta.end.column;
switch (delta.action) {
case "insert":
docLines[row] = line.substring(0, startColumn) + delta.lines[0] + line.substring(startColumn); docLines[row] = line.substring(0, startColumn) + delta.lines[0] + line.substring(startColumn);
break; } else {
case "remove":
docLines[row] = line.substring(0, startColumn) + line.substring(endColumn);
break;
}
} else {
// Apply multi-line delta.
switch (delta.action) {
case "insert":
var line = docLines[row]; var line = docLines[row];
var args = [row, 1].concat(delta.lines); var args = [row, 1].concat(delta.lines);
docLines.splice.apply(docLines, args); docLines.splice.apply(docLines, args);
docLines[row] = line.substring(0, startColumn) + docLines[row]; docLines[row] = line.substring(0, startColumn) + docLines[row];
docLines[row + delta.lines.length - 1] += line.substring(startColumn); docLines[row + delta.lines.length - 1] += line.substring(startColumn);
break; }
case "remove": break;
var endRow case "remove":
var endColumn = delta.end.column;
var endRow = delta.end.row;
if (row === endRow) {
docLines[row] = line.substring(0, startColumn) + line.substring(endColumn);
} else {
docLines.splice( docLines.splice(
row, // Where to start deleting row, endRow - row + 1,
delta.end.row - delta.start.row + 1, // Num lines to delete. line.substring(0, startColumn) + docLines[endRow].substring(endColumn)
line.substring(0, startColumn) + docLines[delta.end.row].substring(delta.end.column)
); );
break; }
} break;
} }
} }
}); });

View file

@ -39,13 +39,12 @@ var Anchor = require("./anchor").Anchor;
/** /**
* Contains the text of the document. Document can be attached to several [[EditSession `EditSession`]]s. * Contains the text of the document. Document can be attached to several [[EditSession `EditSession`]]s.
*
* At its core, `Document`s are just an array of strings, with each row in the document matching up to the array index. * At its core, `Document`s are just an array of strings, with each row in the document matching up to the array index.
* *
* @class Document * @class Document
**/ **/
/** /**
* *
* Creates a new `Document`. If `text` is included, the `Document` contains those strings; otherwise, it's empty. * Creates a new `Document`. If `text` is included, the `Document` contains those strings; otherwise, it's empty.
* @param {String | Array} text The starting text * @param {String | Array} text The starting text
@ -76,9 +75,9 @@ var Document = function(textOrLines) {
* @param {String} text The text to use * @param {String} text The text to use
**/ **/
this.setValue = function(text) { this.setValue = function(text) {
var len = this.getLength(); var len = this.getLength() - 1;
this.remove(new Range(0, 0, len, this.getLine(len-1).length)); this.remove(new Range(0, 0, len, this.getLine(len).length));
this.insert({row: 0, column:0}, text); this.insert({row: 0, column: 0}, text);
}; };
/** /**
@ -224,7 +223,7 @@ var Document = function(textOrLines) {
**/ **/
this.getLinesForRange = function(range) { this.getLinesForRange = function(range) {
var lines; var lines;
if (range.start.row == range.end.row) { if (range.start.row === range.end.row) {
// Handle a single-line range. // Handle a single-line range.
lines = [this.getLine(range.start.row).substring(range.start.column, range.end.column)]; lines = [this.getLine(range.start.row).substring(range.start.column, range.end.column)];
} else { } else {
@ -238,29 +237,6 @@ var Document = function(textOrLines) {
return lines; return lines;
}; };
this.$clipPosition = function(position) {
var length = this.getLength();
if (position.row >= length) {
position.row = Math.max(0, length - 1);
position.column = this.getLine(length - 1).length;
} else {
position.row = Math.max(0, position.row);
position.column = Math.min(Math.max(position.column, 0), this.getLine(position.row).length);
}
return position;
};
this.$getClippedRange = function(range) {
// Get Range object.
if (!range instanceof Range)
range = Range.fromPoints(range.start, range.end);
// Return clipped range.
this.$clipPosition(range.start);
this.$clipPosition(range.end);
return range;
};
// Deprecated methods retained for backwards compatibility. // Deprecated methods retained for backwards compatibility.
this.insertLines = function(row, lines) { this.insertLines = function(row, lines) {
console.warn("Use of document.insertLines is deprecated. Use the insertFullLines method instead."); console.warn("Use of document.insertLines is deprecated. Use the insertFullLines method instead.");
@ -305,25 +281,54 @@ var Document = function(textOrLines) {
* ``` * ```
**/ **/
this.insertInLine = function(position, text) { this.insertInLine = function(position, text) {
// Calculate insertion range end point. var start = this.clippedPos(position.row, position.column);
this.$clipPosition(position); var end = this.pos(position.row, position.column + text.length);
var endPoint = {
row : position.row,
column : position.column + text.length
};
var range = Range.fromPoints(position, endPoint);
// Apply delta (emits change).
this.applyDelta({ this.applyDelta({
action: "insert", action: "insert",
start: range.start, start: start,
end: range.end, end: end,
lines: [text] lines: [text]
}, true /*doNotValidate*/); }, true);
return endPoint; return this.clonePos(end);
}; };
this.clippedPos = function(row, column) {
var length = this.getLength();
if (row === undefined) {
row = length;
} else if (row < 0) {
row = 0;
} else if (row >= length) {
row = length - 1;
column = undefined
}
var line = this.getLine(row);
column = Math.min(Math.max(column, 0), line.length);
return {row: row, column: column};
};
this.clonePos = function(pos) {
return {row: pos.row, column: pos.column};
};
this.pos = function(row, column) {
return {row: row, column: column};
};
this.$clipPosition = function(position) {
var length = this.getLength();
if (position.row >= length) {
position.row = Math.max(0, length - 1);
position.column = this.getLine(length - 1).length;
} else {
position.row = Math.max(0, position.row);
position.column = Math.min(Math.max(position.column, 0), this.getLine(position.row).length);
}
return position;
};
/** /**
* Fires whenever the document changes. * Fires whenever the document changes.
* *
@ -392,14 +397,12 @@ var Document = function(textOrLines) {
* *
**/ **/
this.insertMergedLines = function(position, lines) { this.insertMergedLines = function(position, lines) {
// Calculate insertion range end point.
this.$clipPosition(position); this.$clipPosition(position);
var endPoint = { var endPoint = {
row : position.row + lines.length - 1, row : position.row + lines.length - 1,
column : (lines.length == 1 ? position.column : 0) + lines[lines.length - 1].length column : (lines.length == 1 ? position.column : 0) + lines[lines.length - 1].length
}; };
// Apply delta (emits change).
this.applyDelta({ this.applyDelta({
action: "insert", action: "insert",
start: position, start: position,
@ -417,15 +420,14 @@ var Document = function(textOrLines) {
* *
**/ **/
this.remove = function(range) { this.remove = function(range) {
// Apply delta (emits change). var start = this.clippedPos(range.start.row, range.start.column);
range = this.$getClippedRange(range);
this.applyDelta({ this.applyDelta({
action: "remove", action: "remove",
start: range.start, start: start,
end: range.end, end: this.clippedPos(range.end.row, range.end.column),
lines: this.getLinesForRange(range), lines: this.getLinesForRange(range),
}); });
return range.start; return this.clonePos(start);
}; };
/** /**
@ -437,19 +439,16 @@ var Document = function(textOrLines) {
* *
**/ **/
this.removeInLine = function(row, startColumn, endColumn) { this.removeInLine = function(row, startColumn, endColumn) {
// Calculate deleteion range. var start = this.clippedPos(row, startColumn);
var range = new Range(row, startColumn, row, endColumn);
range = this.$getClippedRange(range);
// Apply delta (emits change).
this.applyDelta({ this.applyDelta({
action: "remove", action: "remove",
start: range.start, start: start,
end: range.end, end: this.clippedPos(row, endColumn),
lines: this.getLinesForRange(range), lines: this.getLinesForRange(range),
}, true /*doNotValidate*/); }, true);
return range.start; return this.clonePos(start);
}; };
/** /**
@ -478,7 +477,6 @@ var Document = function(textOrLines) {
// Store delelted lines with bounding newlines ommitted (maintains previous behavior). // Store delelted lines with bounding newlines ommitted (maintains previous behavior).
var deletedLines = this.$lines.slice(firstRow, lastRow + 1); var deletedLines = this.$lines.slice(firstRow, lastRow + 1);
// Apply delta (emits change).
this.applyDelta({ this.applyDelta({
action: "remove", action: "remove",
start: range.start, start: range.start,
@ -496,13 +494,11 @@ var Document = function(textOrLines) {
* *
**/ **/
this.removeNewLine = function(row) { this.removeNewLine = function(row) {
if (row < this.getLength() - 1 && row >= 0) { if (row < this.getLength() - 1 && row >= 0) {
var range = new Range(row, this.getLine(row).length, row + 1, 0);
// Apply delta (emits change).
this.applyDelta({ this.applyDelta({
action: "remove", action: "remove",
start: range.start, start: this.pos(row, this.getLine(row).length),
end: range.end, end: this.pos(row + 1, 0),
lines: ["", ""] lines: ["", ""]
}); });
} }
@ -566,10 +562,21 @@ var Document = function(textOrLines) {
* @param {Object} delta A delta object (can include "insert" and "remove" actions) * @param {Object} delta A delta object (can include "insert" and "remove" actions)
**/ **/
this.applyDelta = function(delta, doNotValidate) { this.applyDelta = function(delta, doNotValidate) {
var isInsert = delta.action == "insert";
// An empty range is a NOOP. // An empty range is a NOOP.
if (!Range.comparePoints(delta.start, delta.end)) if (isInsert ? !delta.lines.length
: !Range.comparePoints(delta.start, delta.end))
return; return;
if (isInsert && delta.lines.length > 0xFFFF)
this.$splitAndapplyLargeDelta(delta);
// Apply.
applyDelta(this.$lines, delta, doNotValidate);
this._signal("change", {data: delta});
};
this.$splitAndapplyLargeDelta = function(delta) {
// Split large insert deltas. This is necessary because: // Split large insert deltas. This is necessary because:
// 1. We need to support splicing delta lines into the document via $lines.splice.apply(...) // 1. We need to support splicing delta lines into the document via $lines.splice.apply(...)
// 2. fn.apply() doesn't work for a large number of params. The mallest threshold is on safari 0xFFFF. // 2. fn.apply() doesn't work for a large number of params. The mallest threshold is on safari 0xFFFF.
@ -578,28 +585,18 @@ var Document = function(textOrLines) {
// delta handling is too slow. If we make delete delta handling faster we can split all large deltas // delta handling is too slow. If we make delete delta handling faster we can split all large deltas
// as shown in https://gist.github.com/aldendaniels/8367109#file-document-snippet-js // as shown in https://gist.github.com/aldendaniels/8367109#file-document-snippet-js
// If we do this, update validateDelta() to limit the number of lines in a delete delta. // If we do this, update validateDelta() to limit the number of lines in a delete delta.
var bIsInsert = delta.action == "insert"; while (delta.lines.length > 0xFFFF) {
while (bIsInsert && delta.lines.length > 65001) {
// Get split deltas. // Get split deltas.
var lines = delta.lines.splice(0, 65000); var lines = delta.lines.splice(0, 0xFFFF);
lines.push(""); lines.push("");
var range = new Range(delta.start.row, delta.start.column, var start = delta.start;
delta.start.row + 65000, 0)
this.applyDelta({ this.applyDelta({
action: delta.action, action: delta.action,
lines: lines, lines: lines,
start: range.start, start: this.pos(start.row, start.column),
end: range.end, end: this.pos(start.row += 0xFFFF, start.column = 0) // Updates remaining delta.
}); }, true);
// Update remaining delta.
delta.start.row += 65000;
delta.start.column = 0;
} }
// Apply.
applyDelta(this.$lines, delta, doNotValidate);
this._emit("change", { data: delta });
}; };
/** /**

View file

@ -1394,25 +1394,25 @@ var EditSession = function(text, mode) {
**/ **/
this.outdentRows = function (range) { this.outdentRows = function (range) {
var rowRange = range.collapseRows(); var rowRange = range.collapseRows();
var deleteRange = new Range(0, 0, 0, 0);
var size = this.getTabSize(); var size = this.getTabSize();
for (var i = rowRange.start.row; i <= rowRange.end.row; ++i) { for (var i = rowRange.start.row; i <= rowRange.end.row; ++i) {
var line = this.getLine(i); var line = this.getLine(i);
var row = i;
var startCol = 0; deleteRange.start.row = i;
var endCol = 0; deleteRange.end.row = i;
for (var j = 0; j < size; ++j) for (var j = 0; j < size; ++j)
if (line.charAt(j) != ' ') if (line.charAt(j) != ' ')
break; break;
if (j < size && line.charAt(j) == '\t') { if (j < size && line.charAt(j) == '\t') {
startCol = j; deleteRange.start.column = j;
endCol = j + 1; deleteRange.end.column = j + 1;
} else { } else {
startCol = 0; deleteRange.start.column = 0;
endCol = j; deleteRange.end.column = j;
} }
this.doc.removeInLine(row, startCol, endCol); this.remove(deleteRange);
} }
}; };
@ -1684,7 +1684,7 @@ var EditSession = function(text, mode) {
this.$updating = true; this.$updating = true;
if (len != 0) { if (len != 0) {
if (action == "remove") { if (action === "remove") {
this[useWrapMode ? "$wrapData" : "$rowLengthCache"].splice(firstRow, len); this[useWrapMode ? "$wrapData" : "$rowLengthCache"].splice(firstRow, len);
var foldLines = this.$foldData; var foldLines = this.$foldData;
@ -1759,7 +1759,7 @@ var EditSession = function(text, mode) {
// Realign folds. E.g. if you add some new chars before a fold, the // Realign folds. E.g. if you add some new chars before a fold, the
// fold should "move" to the right. // fold should "move" to the right.
len = Math.abs(e.data.start.column - e.data.end.column); len = Math.abs(e.data.start.column - e.data.end.column);
if (action == "remove") { if (action === "remove") {
// Get all the folds in the change range and remove them. // Get all the folds in the change range and remove them.
removedFolds = this.getFoldsInRange(e.data); removedFolds = this.getFoldsInRange(e.data);
this.removeFolds(removedFolds); this.removeFolds(removedFolds);

View file

@ -181,13 +181,13 @@ var RangeList = function() {
}; };
this.$onChange = function(e) { this.$onChange = function(e) {
var changeRange = e.data.range; var delta = e.data;
if (e.data.action[0] == "i"){ if (delta.action == "insert"){
var start = changeRange.start; var start = delta.start;
var end = changeRange.end; var end = delta.end;
} else { } else {
var end = changeRange.start; var end = delta.start;
var start = changeRange.end; var start = delta.end;
} }
var startRow = start.row; var startRow = start.row;
var endRow = end.row; var endRow = end.row;

View file

@ -61,10 +61,9 @@ var UndoManager = function() {
* *
**/ **/
this.execute = function(options) { this.execute = function(options) {
// Normalize deltas for storage. // Normalize deltas for storage.
var deltaSets = this.$serializeDeltas(options.args[0]); // var deltaSets = this.$serializeDeltas(options.args[0]);
var deltaSets = options.args[0];
// Add deltas to undo stack. // Add deltas to undo stack.
this.$doc = options.args[1]; this.$doc = options.args[1];
if (options.merge && this.hasUndo()){ if (options.merge && this.hasUndo()){
@ -207,7 +206,7 @@ var UndoManager = function() {
deltaSets_new[i] = deltaSet_new; deltaSets_new[i] = deltaSet_new;
} }
return deltaSets_new; return deltaSets_new;
} }
}).call(UndoManager.prototype); }).call(UndoManager.prototype);

View file

@ -13,7 +13,19 @@ var Mirror = exports.Mirror = function(sender) {
var _self = this; var _self = this;
sender.on("change", function(e) { sender.on("change", function(e) {
doc.applyDeltas(e.data); var data = e.data;
if (data[0].start) {
doc.applyDeltas(data);
} else {
for (var i = 0; i < data.length; i += 2) {
if (Array.isArray(data[i+1]))
var d = {action: "insert", start: data[i], lines: data[i+1]};
else
var d = {action: "remove", start: data[i],end: data[i+1]};
doc.applyDelta(d, true);
}
}
if (_self.$timeout) if (_self.$timeout)
return deferredUpdate.schedule(_self.$timeout); return deferredUpdate.schedule(_self.$timeout);
_self.onUpdate(); _self.onUpdate();

View file

@ -162,17 +162,21 @@ var WorkerClient = function(topLevelNamespaces, mod, classname, workerUrl) {
this.changeListener = function(e) { this.changeListener = function(e) {
if (!this.deltaQueue) { if (!this.deltaQueue) {
this.deltaQueue = [e.data]; this.deltaQueue = [];
setTimeout(this.$sendDeltaQueue, 0); setTimeout(this.$sendDeltaQueue, 0);
} else }
this.deltaQueue.push(e.data); var delta = e.data;
if (delta.action == "insert")
this.deltaQueue.push(delta.start, delta.lines);
else
this.deltaQueue.push(delta.start, delta.end);
}; };
this.$sendDeltaQueue = function() { this.$sendDeltaQueue = function() {
var q = this.deltaQueue; var q = this.deltaQueue;
if (!q) return; if (!q) return;
this.deltaQueue = null; this.deltaQueue = null;
if (q.length > 20 && q.length > this.$doc.getLength() >> 1) { if (q.length > 50 && q.length > this.$doc.getLength() >> 1) {
this.call("setValue", [this.$doc.getValue()]); this.call("setValue", [this.$doc.getValue()]);
} else } else
this.emit("change", {data: q}); this.emit("change", {data: q});