recoder-code
Version:
Complete AI-powered development platform with ML model training, plugin registry, real-time collaboration, monitoring, infrastructure automation, and enterprise deployment capabilities
257 lines (219 loc) • 7.75 kB
JavaScript
// DEPRECATED!
//
// This type works, but is not exported. Its included here because the JSON0
// embedded string operations use this library.
// A simple text implementation
//
// Operations are lists of components. Each component either inserts or deletes
// at a specified position in the document.
//
// Components are either:
// {i:'str', p:100}: Insert 'str' at position 100 in the document
// {d:'str', p:100}: Delete 'str' at position 100 in the document
//
// Components in an operation are executed sequentially, so the position of components
// assumes previous components have already executed.
//
// Eg: This op:
// [{i:'abc', p:0}]
// is equivalent to this op:
// [{i:'a', p:0}, {i:'b', p:1}, {i:'c', p:2}]
var text = module.exports = {
name: 'text0',
uri: 'http://sharejs.org/types/textv0',
create: function(initial) {
if ((initial != null) && typeof initial !== 'string') {
throw new Error('Initial data must be a string');
}
return initial || '';
}
};
/** Insert s2 into s1 at pos. */
var strInject = function(s1, pos, s2) {
return s1.slice(0, pos) + s2 + s1.slice(pos);
};
/** Check that an operation component is valid. Throws if its invalid. */
var checkValidComponent = function(c) {
if (typeof c.p !== 'number')
throw new Error('component missing position field');
if ((typeof c.i === 'string') === (typeof c.d === 'string'))
throw new Error('component needs an i or d field');
if (c.p < 0)
throw new Error('position cannot be negative');
};
/** Check that an operation is valid */
var checkValidOp = function(op) {
for (var i = 0; i < op.length; i++) {
checkValidComponent(op[i]);
}
};
/** Apply op to snapshot */
text.apply = function(snapshot, op) {
var deleted;
checkValidOp(op);
for (var i = 0; i < op.length; i++) {
var component = op[i];
if (component.i != null) {
snapshot = strInject(snapshot, component.p, component.i);
} else {
deleted = snapshot.slice(component.p, component.p + component.d.length);
if (component.d !== deleted)
throw new Error("Delete component '" + component.d + "' does not match deleted text '" + deleted + "'");
snapshot = snapshot.slice(0, component.p) + snapshot.slice(component.p + component.d.length);
}
}
return snapshot;
};
/**
* Append a component to the end of newOp. Exported for use by the random op
* generator and the JSON0 type.
*/
var append = text._append = function(newOp, c) {
if (c.i === '' || c.d === '') return;
if (newOp.length === 0) {
newOp.push(c);
} else {
var last = newOp[newOp.length - 1];
if (last.i != null && c.i != null && last.p <= c.p && c.p <= last.p + last.i.length) {
// Compose the insert into the previous insert
newOp[newOp.length - 1] = {i:strInject(last.i, c.p - last.p, c.i), p:last.p};
} else if (last.d != null && c.d != null && c.p <= last.p && last.p <= c.p + c.d.length) {
// Compose the deletes together
newOp[newOp.length - 1] = {d:strInject(c.d, last.p - c.p, last.d), p:c.p};
} else {
newOp.push(c);
}
}
};
/** Compose op1 and op2 together */
text.compose = function(op1, op2) {
checkValidOp(op1);
checkValidOp(op2);
var newOp = op1.slice();
for (var i = 0; i < op2.length; i++) {
append(newOp, op2[i]);
}
return newOp;
};
/** Clean up an op */
text.normalize = function(op) {
var newOp = [];
// Normalize should allow ops which are a single (unwrapped) component:
// {i:'asdf', p:23}.
// There's no good way to test if something is an array:
// http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
// so this is probably the least bad solution.
if (op.i != null || op.p != null) op = [op];
for (var i = 0; i < op.length; i++) {
var c = op[i];
if (c.p == null) c.p = 0;
append(newOp, c);
}
return newOp;
};
// This helper method transforms a position by an op component.
//
// If c is an insert, insertAfter specifies whether the transform
// is pushed after the insert (true) or before it (false).
//
// insertAfter is optional for deletes.
var transformPosition = function(pos, c, insertAfter) {
// This will get collapsed into a giant ternary by uglify.
if (c.i != null) {
if (c.p < pos || (c.p === pos && insertAfter)) {
return pos + c.i.length;
} else {
return pos;
}
} else {
// I think this could also be written as: Math.min(c.p, Math.min(c.p -
// otherC.p, otherC.d.length)) but I think its harder to read that way, and
// it compiles using ternary operators anyway so its no slower written like
// this.
if (pos <= c.p) {
return pos;
} else if (pos <= c.p + c.d.length) {
return c.p;
} else {
return pos - c.d.length;
}
}
};
// Helper method to transform a cursor position as a result of an op.
//
// Like transformPosition above, if c is an insert, insertAfter specifies
// whether the cursor position is pushed after an insert (true) or before it
// (false).
text.transformCursor = function(position, op, side) {
var insertAfter = side === 'right';
for (var i = 0; i < op.length; i++) {
position = transformPosition(position, op[i], insertAfter);
}
return position;
};
// Transform an op component by another op component. Asymmetric.
// The result will be appended to destination.
//
// exported for use in JSON type
var transformComponent = text._tc = function(dest, c, otherC, side) {
//var cIntersect, intersectEnd, intersectStart, newC, otherIntersect, s;
checkValidComponent(c);
checkValidComponent(otherC);
if (c.i != null) {
// Insert.
append(dest, {i:c.i, p:transformPosition(c.p, otherC, side === 'right')});
} else {
// Delete
if (otherC.i != null) {
// Delete vs insert
var s = c.d;
if (c.p < otherC.p) {
append(dest, {d:s.slice(0, otherC.p - c.p), p:c.p});
s = s.slice(otherC.p - c.p);
}
if (s !== '')
append(dest, {d: s, p: c.p + otherC.i.length});
} else {
// Delete vs delete
if (c.p >= otherC.p + otherC.d.length)
append(dest, {d: c.d, p: c.p - otherC.d.length});
else if (c.p + c.d.length <= otherC.p)
append(dest, c);
else {
// They overlap somewhere.
var newC = {d: '', p: c.p};
if (c.p < otherC.p)
newC.d = c.d.slice(0, otherC.p - c.p);
if (c.p + c.d.length > otherC.p + otherC.d.length)
newC.d += c.d.slice(otherC.p + otherC.d.length - c.p);
// This is entirely optional - I'm just checking the deleted text in
// the two ops matches
var intersectStart = Math.max(c.p, otherC.p);
var intersectEnd = Math.min(c.p + c.d.length, otherC.p + otherC.d.length);
var cIntersect = c.d.slice(intersectStart - c.p, intersectEnd - c.p);
var otherIntersect = otherC.d.slice(intersectStart - otherC.p, intersectEnd - otherC.p);
if (cIntersect !== otherIntersect)
throw new Error('Delete ops delete different text in the same region of the document');
if (newC.d !== '') {
newC.p = transformPosition(newC.p, otherC);
append(dest, newC);
}
}
}
}
return dest;
};
var invertComponent = function(c) {
return (c.i != null) ? {d:c.i, p:c.p} : {i:c.d, p:c.p};
};
// No need to use append for invert, because the components won't be able to
// cancel one another.
text.invert = function(op) {
// Shallow copy & reverse that sucka.
op = op.slice().reverse();
for (var i = 0; i < op.length; i++) {
op[i] = invertComponent(op[i]);
}
return op;
};
require('./bootstrapTransform')(text, transformComponent, checkValidOp, append);