Fix / complete validateDelta

This uncovered the fact that until now delta.range had not always been a
Range object. This inconsistency has been resolved by my changes in
mirror.js.
This commit is contained in:
aldendaniels 2014-01-11 11:20:34 -06:00
commit ef0e8da522
2 changed files with 62 additions and 47 deletions

View file

@ -33,59 +33,63 @@ define(function(require, exports, module) {
var Range = require("./range").Range;
function splitLine (lines, point) {
var text = lines[point.row];
lines[point.row] = text.slice(0, point.column);
lines.splice(point.row + 1, 0, text.slice(point.column));
function splitLine (docLines, position) {
var text = docLines[position.row];
docLines[position.row] = text.slice(0, position.column);
docLines.splice(position.row + 1, 0, text.slice(position.column));
}
function joinLineWithNext(lines, row) {
lines[row] += lines[row + 1];
lines.splice(row + 1, 1);
function joinLineWithNext(docLines, row) {
docLines[row] += docLines[row + 1];
docLines.splice(row + 1, 1);
}
function throwDeltaError(delta, errorText){
errorText = 'Invalid Delta: ' + errorText;
console.log(errorText, delta);
throw errorText;
console.log('Invalid Delta:', delta);
throw 'Invalid Delta: ' + errorText;
}
function validateDelta(lines, delta) {
function positionInDocument(docLines, position)
{
return position.row >= 0 && position.row < docLines.length &&
position.column >= 0 && position.column <= docLines[position.row].length;
}
// Validate action.
function validateDelta(docLines, delta) {
// Validate action string.
if (delta.action != 'insert' && delta.action != 'remove')
fnThrow('Delta action must be "insert" or "remove".');
// Validate lines.
if (!delta.lines instanceof Array)
fnThrow('Delta lines must be an array');
throwDeltaError(delta, 'delta.action must be "insert" or "remove"');
// Validate lines type.
if (!(delta.lines instanceof Array))
throwDeltaError(delta, 'delta.lines must be an Array');
// Validate range type.
if (!delta.range instanceof Range)
fnThrow('Range object is not an instance of the Range class');
// Validate start point.
if (!(delta.range instanceof Range))
throwDeltaError(delta, 'delta.range must be an instance of the Range class');
// Validate that the start point is contained in the document.
var start = delta.range.start;
if (Math.min(Math.max(start.row, 0), lines.length - 1 ) != start.row ||
Math.min(Math.max(start.column, 0), lines[start.row].length) != start.column)
{
fnThrow('Range start point not contained in document');
}
if (!positionInDocument(docLines, delta.range.start))
throwDeltaError(delta, 'delta.range.start must be contained in document');
// Validate ending row offset.
if (delta.lines.length - 1 != delta.range.end.row - delta.range.start.row)
fnThrow('Range row offsets does not match delta lines');
// Validate that the end point is contained in the document (remove deltas only).
var end = delta.range.end;
if (delta.action == 'remove' && !positionInDocument(docLines, end))
throwDeltaError(delta, 'delta.range.end must contained in document for "remove" actions');
// TODO:
// - Validate that the ending column offset matches the lines.
// - Validate the deleted lines match the lines in the document.
// - Vaiidate that an insert delta does not contain more than 65001 entries.
// Validate that the .range size matches the .lines size.
var numRangeRows = end.row - start.row;
var numRangeLastLineChars = (end.column - (numRangeRows == 0 ? start.column : 0));
if (numRangeRows != delta.lines.length - 1 || delta.lines[numRangeRows].length != numRangeLastLineChars)
throwDeltaError(delta, 'delta.range must match delta lines');
}
exports.applyDelta = function(lines, delta) {
exports.applyDelta = function(docLines, delta) {
// Validate delta.
validateDelta(lines, delta);
validateDelta(docLines, delta);
// Apply delta.
if (delta.range.start.row == delta.range.end.row)
@ -96,15 +100,15 @@ exports.applyDelta = function(lines, delta) {
var row = delta.range.start.row;
var startColumn = delta.range.start.column;
var endColumn = delta.range.end.column;
var line = lines[row];
var line = docLines[row];
switch (delta.action) {
case 'insert':
lines[row] = line.substring(0, startColumn) + delta.lines[0] + line.substring(startColumn);
docLines[row] = line.substring(0, startColumn) + delta.lines[0] + line.substring(startColumn);
break;
case 'remove':
lines[row] = line.substring(0, startColumn) + line.substring(endColumn);
docLines[row] = line.substring(0, startColumn) + line.substring(endColumn);
break;
}
} else {
@ -113,20 +117,20 @@ exports.applyDelta = function(lines, delta) {
switch (delta.action) {
case 'insert':
splitLine(lines, delta.range.start);
lines.splice.apply(lines, [delta.range.start.row + 1, 0].concat(delta.lines));
joinLineWithNext(lines, delta.range.start.row);
joinLineWithNext(lines, delta.range.end.row);
splitLine(docLines, delta.range.start);
docLines.splice.apply(docLines, [delta.range.start.row + 1, 0].concat(delta.lines));
joinLineWithNext(docLines, delta.range.start.row);
joinLineWithNext(docLines, delta.range.end.row);
break;
case 'remove':
splitLine(lines, delta.range.end);
splitLine(lines, delta.range.start);
lines.splice(
splitLine(docLines, delta.range.end);
splitLine(docLines, delta.range.start);
docLines.splice(
delta.range.start.row + 1, // Where to start deleting
delta.range.end.row - delta.range.start.row + 1 // Num lines to delete.
);
joinLineWithNext(lines, delta.range.start.row);
joinLineWithNext(docLines, delta.range.start.row);
break;
}
}

View file

@ -1,6 +1,7 @@
define(function(require, exports, module) {
"use strict";
var Range = require("../range").Range;
var Document = require("../document").Document;
var lang = require("../lib/lang");
@ -12,7 +13,17 @@ var Mirror = exports.Mirror = function(sender) {
var _self = this;
sender.on("change", function(e) {
doc.applyDeltas(e.data);
// Convert delta.range back into a Range instance since
// window.onMessage loses non-primitive data. See http://jsfiddle.net/nqJfw/1/.
var deltas = e.data;
for (var i in deltas)
{
var delta = deltas[i];
delta.range = Range.fromPoints(delta.range.start, delta.range.end);
}
doc.applyDeltas(deltas);
if (_self.$timeout)
return deferredUpdate.schedule(_self.$timeout);
_self.onUpdate();