Skip to content

Commit d3d2090

Browse files
committed
Changeset: Migrate from smartOpAssembler() to canonicalizeOps()
1 parent 23e7809 commit d3d2090

File tree

8 files changed

+162
-164
lines changed

8 files changed

+162
-164
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
* `opAssembler()`: Deprecated in favor of the new `serializeOps()` function.
4040
* `mergingOpAssembler()`: Deprecated in favor of the new `squashOps()`
4141
generator function (combined with `serializeOps()`).
42+
* `smartOpAssembler()`: Deprecated in favor of the new `canonicalizeOps()`
43+
generator function (combined with `serializeOps()`).
4244
* `appendATextToAssembler()`: Deprecated in favor of the new `opsFromAText()`
4345
generator function.
4446
* `newOp()`: Deprecated in favor of the new `Op` class.

src/node/db/Pad.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -494,21 +494,20 @@ Pad.prototype.copyPadWithoutHistory = async function (destinationID, force) {
494494

495495
const oldAText = this.atext;
496496

497-
// based on Changeset.makeSplice
498-
const assem = Changeset.smartOpAssembler();
499-
for (const op of Changeset.opsFromAText(oldAText)) assem.append(op);
500-
assem.endDocument();
497+
let newLength;
498+
const serializedOps = Changeset.serializeOps((function* () {
499+
newLength = yield* Changeset.canonicalizeOps(Changeset.opsFromAText(oldAText), true);
500+
})());
501501

502502
// although we have instantiated the newPad with '\n', an additional '\n' is
503503
// added internally, so the pad text on the revision 0 is "\n\n"
504504
const oldLength = 2;
505505

506-
const newLength = assem.getLengthChange();
507506
const newText = oldAText.text;
508507

509508
// create a changeset that removes the previous text and add the newText with
510509
// all atributes present on the source pad
511-
const changeset = Changeset.pack(oldLength, newLength, assem.toString(), newText);
510+
const changeset = Changeset.pack(oldLength, newLength, serializedOps, newText);
512511
newPad.appendRevision(changeset);
513512

514513
await hooks.aCallAll('padCopy', {originalPad: this, destinationID});

src/static/js/Changeset.js

Lines changed: 92 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ class MergingOpAssembler {
432432
* @returns {Generator<Op, number>} The done value indicates how much the sequence of operations
433433
* changes the length of the document (in characters).
434434
*/
435-
const canonicalizeOps = function* (ops, finalize) {
435+
exports.canonicalizeOps = function* (ops, finalize) {
436436
let minusOps = [];
437437
let plusOps = [];
438438
let keepOps = [];
@@ -519,6 +519,8 @@ const opsFromText = function* (opcode, text, attribs = '', pool = null) {
519519
* - strips final "="
520520
* - ignores 0-length changes
521521
* - reorders consecutive + and - (which MergingOpAssembler doesn't do)
522+
*
523+
* @deprecated Use `canonicalizeOps` with `serializeOps` instead.
522524
*/
523525
class SmartOpAssembler {
524526
constructor() {
@@ -533,7 +535,7 @@ class SmartOpAssembler {
533535

534536
_serialize(finalize) {
535537
this._serialized = exports.serializeOps((function* () {
536-
this._lengthChange = yield* canonicalizeOps(this._ops, finalize);
538+
this._lengthChange = yield* exports.canonicalizeOps(this._ops, finalize);
537539
}).call(this));
538540
}
539541

@@ -586,54 +588,58 @@ exports.checkRep = (cs) => {
586588
const unpacked = exports.unpack(cs);
587589
const oldLen = unpacked.oldLen;
588590
const newLen = unpacked.newLen;
589-
const ops = unpacked.ops;
590591
let charBank = unpacked.charBank;
591592

592-
const assem = new SmartOpAssembler();
593593
let oldPos = 0;
594594
let calcNewLen = 0;
595-
for (const o of exports.deserializeOps(ops)) {
596-
switch (o.opcode) {
597-
case '=':
598-
oldPos += o.chars;
599-
calcNewLen += o.chars;
600-
break;
601-
case '-':
602-
oldPos += o.chars;
603-
assert(oldPos <= oldLen, `${oldPos} > ${oldLen} in ${cs}`);
604-
break;
605-
case '+':
606-
{
607-
assert(charBank.length >= o.chars, 'Invalid changeset: not enough chars in charBank');
608-
const chars = charBank.slice(0, o.chars);
609-
const nlines = (chars.match(/\n/g) || []).length;
610-
assert(nlines === o.lines,
611-
'Invalid changeset: number of newlines in insert op does not match the charBank');
612-
assert(o.lines === 0 || chars.endsWith('\n'),
613-
'Invalid changeset: multiline insert op does not end with a newline');
614-
charBank = charBank.slice(o.chars);
615-
calcNewLen += o.chars;
616-
assert(calcNewLen <= newLen, `${calcNewLen} > ${newLen} in ${cs}`);
617-
break;
595+
const ops = (function* () {
596+
for (const o of exports.deserializeOps(unpacked.ops)) {
597+
switch (o.opcode) {
598+
case '=':
599+
oldPos += o.chars;
600+
calcNewLen += o.chars;
601+
break;
602+
case '-':
603+
oldPos += o.chars;
604+
assert(oldPos <= oldLen, `${oldPos} > ${oldLen} in ${cs}`);
605+
break;
606+
case '+': {
607+
assert(charBank.length >= o.chars, 'Invalid changeset: not enough chars in charBank');
608+
const chars = charBank.slice(0, o.chars);
609+
const nlines = (chars.match(/\n/g) || []).length;
610+
assert(nlines === o.lines,
611+
'Invalid changeset: number of newlines in insert op does not match the charBank');
612+
assert(o.lines === 0 || chars.endsWith('\n'),
613+
'Invalid changeset: multiline insert op does not end with a newline');
614+
charBank = charBank.slice(o.chars);
615+
calcNewLen += o.chars;
616+
assert(calcNewLen <= newLen, `${calcNewLen} > ${newLen} in ${cs}`);
617+
break;
618+
}
619+
default:
620+
assert(false, `Invalid changeset: Unknown opcode: ${JSON.stringify(o.opcode)}`);
618621
}
619-
default:
620-
assert(false, `Invalid changeset: Unknown opcode: ${JSON.stringify(o.opcode)}`);
622+
yield o;
621623
}
622-
assem.append(o);
623-
}
624+
})();
625+
const serializedOps = exports.serializeOps(exports.canonicalizeOps(ops, true));
624626
calcNewLen += oldLen - oldPos;
625627
assert(calcNewLen === newLen, 'Invalid changeset: claimed length does not match actual length');
626628
assert(charBank === '', 'Invalid changeset: excess characters in the charBank');
627-
assem.endDocument();
628-
const normalized = exports.pack(oldLen, calcNewLen, assem.toString(), unpacked.charBank);
629+
const normalized = exports.pack(oldLen, calcNewLen, serializedOps, unpacked.charBank);
629630
assert(normalized === cs, 'Invalid changeset: not in canonical form');
630631
return cs;
631632
};
632633

633634
/**
635+
* @deprecated Use `canonicalizeOps` with `serializeOps` instead.
634636
* @returns {SmartOpAssembler}
635637
*/
636-
exports.smartOpAssembler = () => new SmartOpAssembler();
638+
exports.smartOpAssembler = () => {
639+
padutils.warnDeprecated(
640+
'Changeset.smartOpAssembler() is deprecated; use Changeset.canonicalizeOps() instead');
641+
return new SmartOpAssembler();
642+
};
637643

638644
/**
639645
* @deprecated Use `squashOps` with `serializeOps` instead.
@@ -1082,22 +1088,22 @@ class TextLinesMutator {
10821088
* @returns {string} the integrated changeset
10831089
*/
10841090
const applyZip = (in1, in2, func) => {
1085-
const ops1 = exports.deserializeOps(in1);
1086-
const ops2 = exports.deserializeOps(in2);
1087-
let next1 = ops1.next();
1088-
let next2 = ops2.next();
1089-
const assem = new SmartOpAssembler();
1090-
while (!next1.done || !next2.done) {
1091-
if (!next1.done && !next1.value.opcode) next1 = ops1.next();
1092-
if (!next2.done && !next2.value.opcode) next2 = ops2.next();
1093-
if (next1.value == null) next1.value = new Op();
1094-
if (next2.value == null) next2.value = new Op();
1095-
if (!next1.value.opcode && !next2.value.opcode) break;
1096-
const opOut = func(next1.value, next2.value);
1097-
if (opOut && opOut.opcode) assem.append(opOut);
1098-
}
1099-
assem.endDocument();
1100-
return assem.toString();
1091+
const ops = (function* () {
1092+
const ops1 = exports.deserializeOps(in1);
1093+
const ops2 = exports.deserializeOps(in2);
1094+
let next1 = ops1.next();
1095+
let next2 = ops2.next();
1096+
while (!next1.done || !next2.done) {
1097+
if (!next1.done && !next1.value.opcode) next1 = ops1.next();
1098+
if (!next2.done && !next2.value.opcode) next2 = ops2.next();
1099+
if (next1.value == null) next1.value = new Op();
1100+
if (next2.value == null) next2.value = new Op();
1101+
if (!next1.value.opcode && !next2.value.opcode) break;
1102+
const opOut = func(next1.value, next2.value);
1103+
if (opOut && opOut.opcode) yield opOut;
1104+
}
1105+
})();
1106+
return exports.serializeOps(exports.canonicalizeOps(ops, true));
11011107
};
11021108

11031109
/**
@@ -1540,15 +1546,13 @@ exports.makeSplice = (orig, start, ndel, ins, attribs, pool) => {
15401546
if (start > orig.length) start = orig.length;
15411547
if (ndel > orig.length - start) ndel = orig.length - start;
15421548
const deleted = orig.substring(start, start + ndel);
1543-
const assem = new SmartOpAssembler();
15441549
const ops = (function* () {
15451550
yield* opsFromText('=', orig.substring(0, start));
15461551
yield* opsFromText('-', deleted);
15471552
yield* opsFromText('+', ins, attribs, pool);
15481553
})();
1549-
for (const op of ops) assem.append(op);
1550-
assem.endDocument();
1551-
return exports.pack(orig.length, orig.length + ins.length - ndel, assem.toString(), ins);
1554+
const serializedOps = exports.serializeOps(exports.canonicalizeOps(ops, true));
1555+
return exports.pack(orig.length, orig.length + ins.length - ndel, serializedOps, ins);
15521556
};
15531557

15541558
/**
@@ -1670,11 +1674,8 @@ exports.moveOpsToNewPool = (cs, oldPool, newPool) => {
16701674
* @param {string} text - text to insert
16711675
* @returns {string}
16721676
*/
1673-
exports.makeAttribution = (text) => {
1674-
const assem = new SmartOpAssembler();
1675-
for (const op of opsFromText('+', text)) assem.append(op);
1676-
return assem.toString();
1677-
};
1677+
exports.makeAttribution =
1678+
(text) => exports.serializeOps(exports.canonicalizeOps(opsFromText('+', text), false));
16781679

16791680
/**
16801681
* Iterates over attributes in exports, attribution string, or attribs property of an op and runs
@@ -1928,8 +1929,7 @@ exports.attribsAttributeValue = (attribs, key, pool) => {
19281929
* @returns {Builder}
19291930
*/
19301931
exports.builder = (oldLen) => {
1931-
const assem = new SmartOpAssembler();
1932-
const o = new Op();
1932+
const ops = [];
19331933
const charBank = exports.stringAssembler();
19341934

19351935
const self = {
@@ -1944,12 +1944,12 @@ exports.builder = (oldLen) => {
19441944
* @returns {Builder} this
19451945
*/
19461946
keep: (N, L, attribs, pool) => {
1947-
o.opcode = '=';
1947+
const o = new Op('=');
19481948
o.attribs = typeof attribs === 'string'
19491949
? attribs : new AttributeMap(pool).update(attribs || []).toString();
19501950
o.chars = N;
19511951
o.lines = (L || 0);
1952-
assem.append(o);
1952+
ops.push(o);
19531953
return self;
19541954
},
19551955

@@ -1962,7 +1962,7 @@ exports.builder = (oldLen) => {
19621962
* @returns {Builder} this
19631963
*/
19641964
keepText: (text, attribs, pool) => {
1965-
for (const op of opsFromText('=', text, attribs, pool)) assem.append(op);
1965+
ops.push(...opsFromText('=', text, attribs, pool));
19661966
return self;
19671967
},
19681968

@@ -1975,7 +1975,7 @@ exports.builder = (oldLen) => {
19751975
* @returns {Builder} this
19761976
*/
19771977
insert: (text, attribs, pool) => {
1978-
for (const op of opsFromText('+', text, attribs, pool)) assem.append(op);
1978+
ops.push(...opsFromText('+', text, attribs, pool));
19791979
charBank.append(text);
19801980
return self;
19811981
},
@@ -1987,18 +1987,22 @@ exports.builder = (oldLen) => {
19871987
* @returns {Builder} this
19881988
*/
19891989
remove: (N, L) => {
1990-
o.opcode = '-';
1990+
const o = new Op('-');
19911991
o.attribs = '';
19921992
o.chars = N;
19931993
o.lines = (L || 0);
1994-
assem.append(o);
1994+
ops.push(o);
19951995
return self;
19961996
},
19971997

19981998
toString: () => {
1999-
assem.endDocument();
2000-
const newLen = oldLen + assem.getLengthChange();
2001-
return exports.pack(oldLen, newLen, assem.toString(), charBank.toString());
1999+
/** @type {number} */
2000+
let lengthChange;
2001+
const serializedOps = exports.serializeOps((function* () {
2002+
lengthChange = yield* exports.canonicalizeOps(ops, true);
2003+
})());
2004+
const newLen = oldLen + lengthChange;
2005+
return exports.pack(oldLen, newLen, serializedOps, charBank.toString());
20022006
},
20032007
};
20042008

@@ -2033,11 +2037,11 @@ exports.makeAttribsString = (opcode, attribs, pool) => {
20332037
exports.subattribution = (astr, start, optEnd) => {
20342038
const attOps = exports.deserializeOps(astr);
20352039
let attOpsNext = attOps.next();
2036-
const assem = new SmartOpAssembler();
20372040
let attOp = new Op();
2038-
const csOp = new Op();
2041+
const csOp = new Op('-');
2042+
csOp.chars = start;
20392043

2040-
const doCsOp = () => {
2044+
const doCsOp = function* () {
20412045
if (!csOp.chars) return;
20422046
while (csOp.opcode && (attOp.opcode || !attOpsNext.done)) {
20432047
if (!attOp.opcode) {
@@ -2049,30 +2053,25 @@ exports.subattribution = (astr, start, optEnd) => {
20492053
csOp.lines++;
20502054
}
20512055
const opOut = slicerZipperFunc(attOp, csOp, null);
2052-
if (opOut.opcode) assem.append(opOut);
2056+
if (opOut.opcode) yield opOut;
20532057
}
20542058
};
20552059

2056-
csOp.opcode = '-';
2057-
csOp.chars = start;
2058-
2059-
doCsOp();
2060-
2061-
if (optEnd === undefined) {
2062-
if (attOp.opcode) {
2063-
assem.append(attOp);
2064-
}
2065-
while (!attOpsNext.done) {
2066-
assem.append(attOpsNext.value);
2067-
attOpsNext = attOps.next();
2060+
const ops = (function* () {
2061+
yield* doCsOp();
2062+
if (optEnd === undefined) {
2063+
if (attOp.opcode) yield attOp;
2064+
while (!attOpsNext.done) {
2065+
yield attOpsNext.value;
2066+
attOpsNext = attOps.next();
2067+
}
2068+
} else {
2069+
csOp.opcode = '=';
2070+
csOp.chars = optEnd - start;
2071+
yield* doCsOp();
20682072
}
2069-
} else {
2070-
csOp.opcode = '=';
2071-
csOp.chars = optEnd - start;
2072-
doCsOp();
2073-
}
2074-
2075-
return assem.toString();
2073+
})();
2074+
return exports.serializeOps(exports.canonicalizeOps(ops, false));
20762075
};
20772076

20782077
exports.inverse = (cs, lines, alines, pool) => {

src/static/js/ace2_inner.js

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -522,18 +522,24 @@ function Ace2Inner(editorInfo, cssManagers) {
522522
const numLines = rep.lines.length();
523523
const upToLastLine = rep.lines.offsetOfIndex(numLines - 1);
524524
const lastLineLength = rep.lines.atIndex(numLines - 1).text.length;
525-
const assem = Changeset.smartOpAssembler();
526-
const o = new Changeset.Op('-');
527-
o.chars = upToLastLine;
528-
o.lines = numLines - 1;
529-
assem.append(o);
530-
o.chars = lastLineLength;
531-
o.lines = 0;
532-
assem.append(o);
533-
for (const op of Changeset.opsFromAText(atext)) assem.append(op);
534-
const newLen = oldLen + assem.getLengthChange();
535-
const changeset = Changeset.checkRep(
536-
Changeset.pack(oldLen, newLen, assem.toString(), atext.text.slice(0, -1)));
525+
const ops = (function* () {
526+
const op1 = new Changeset.Op('-');
527+
op1.chars = upToLastLine;
528+
op1.lines = numLines - 1;
529+
yield op1;
530+
const op2 = new Changeset.Op('-');
531+
op2.chars = lastLineLength;
532+
op2.lines = 0;
533+
yield op2;
534+
yield* Changeset.opsFromAText(atext);
535+
})();
536+
let lengthChange;
537+
const serializedOps = Changeset.serializeOps((function* () {
538+
lengthChange = yield* Changeset.canonicalizeOps(ops, false);
539+
})());
540+
const newLen = oldLen + lengthChange;
541+
const changeset =
542+
Changeset.checkRep(Changeset.pack(oldLen, newLen, serializedOps, atext.text.slice(0, -1)));
537543
performDocumentApplyChangeset(changeset);
538544

539545
performSelectionChange(

0 commit comments

Comments
 (0)