Skip to content

Commit d3f313a

Browse files
committed
Changeset: Migrate from smartOpAssembler() to canonicalizeOps()
1 parent 254091f commit d3f313a

File tree

8 files changed

+152
-155
lines changed

8 files changed

+152
-155
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
* `opAssembler()`: Deprecated in favor of the new `serializeOps()` function.
2727
* `mergingOpAssembler()`: Deprecated in favor of the new `squashOps()`
2828
generator function (combined with `serializeOps()`).
29+
* `smartOpAssembler()`: Deprecated in favor of the new `canonicalizeOps()`
30+
generator function (combined with `serializeOps()`).
2931
* `appendATextToAssembler()`: Deprecated in favor of the new `opsFromAText()`
3032
generator function.
3133
* `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
@@ -496,21 +496,20 @@ Pad.prototype.copyPadWithoutHistory = async function (destinationID, force) {
496496

497497
const oldAText = this.atext;
498498

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

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

508-
const newLength = assem.getLengthChange();
509508
const newText = oldAText.text;
510509

511510
// create a changeset that removes the previous text and add the newText with
512511
// all atributes present on the source pad
513-
const changeset = Changeset.pack(oldLength, newLength, assem.toString(), newText);
512+
const changeset = Changeset.pack(oldLength, newLength, serializedOps, newText);
514513
newPad.appendRevision(changeset);
515514

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

src/static/js/Changeset.js

Lines changed: 82 additions & 84 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,51 +588,54 @@ 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;
595595
let numInserted = 0;
596-
for (const o of exports.deserializeOps(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-
{
608-
calcNewLen += o.chars;
609-
numInserted += o.chars;
610-
assert(calcNewLen <= newLen, `${calcNewLen} > ${newLen} in ${cs}`);
611-
break;
596+
const ops = (function* () {
597+
for (const o of exports.deserializeOps(unpacked.ops)) {
598+
switch (o.opcode) {
599+
case '=':
600+
oldPos += o.chars;
601+
calcNewLen += o.chars;
602+
break;
603+
case '-':
604+
oldPos += o.chars;
605+
assert(oldPos <= oldLen, `${oldPos} > ${oldLen} in ${cs}`);
606+
break;
607+
case '+':
608+
calcNewLen += o.chars;
609+
numInserted += o.chars;
610+
assert(calcNewLen <= newLen, `${calcNewLen} > ${newLen} in ${cs}`);
611+
break;
612612
}
613+
yield o;
613614
}
614-
assem.append(o);
615-
}
615+
})();
616+
const serializedOps = exports.serializeOps(exports.canonicalizeOps(ops, true));
616617

617618
calcNewLen += oldLen - oldPos;
618619
charBank = charBank.substring(0, numInserted);
619620
while (charBank.length < numInserted) {
620621
charBank += '?';
621622
}
622623

623-
assem.endDocument();
624-
const normalized = exports.pack(oldLen, calcNewLen, assem.toString(), charBank);
624+
const normalized = exports.pack(oldLen, calcNewLen, serializedOps, charBank);
625625
assert(normalized === cs, 'Invalid changeset (checkRep failed)');
626626

627627
return cs;
628628
};
629629

630630
/**
631+
* @deprecated Use `canonicalizeOps` with `serializeOps` instead.
631632
* @returns {SmartOpAssembler}
632633
*/
633-
exports.smartOpAssembler = () => new SmartOpAssembler();
634+
exports.smartOpAssembler = () => {
635+
padutils.warnWithStack(
636+
'Changeset.smartOpAssembler() is deprecated; use Changeset.canonicalizeOps() instead');
637+
return new SmartOpAssembler();
638+
};
634639

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

11001105
/**
@@ -1545,15 +1550,13 @@ exports.makeSplice = (oldFullText, spliceStart, numRemoved, newText, optNewTextA
15451550
const oldText = oldFullText.substring(spliceStart, spliceStart + numRemoved);
15461551
const newLen = oldLen + newText.length - oldText.length;
15471552

1548-
const assem = new SmartOpAssembler();
15491553
const ops = (function* () {
15501554
yield* opsFromText('=', oldFullText.substring(0, spliceStart));
15511555
yield* opsFromText('-', oldText);
15521556
yield* opsFromText('+', newText, optNewTextAPairs, pool);
15531557
})();
1554-
for (const op of ops) assem.append(op);
1555-
assem.endDocument();
1556-
return exports.pack(oldLen, newLen, assem.toString(), newText);
1558+
const serializedOps = exports.serializeOps(exports.canonicalizeOps(ops, true));
1559+
return exports.pack(oldLen, newLen, serializedOps, newText);
15571560
};
15581561

15591562
/**
@@ -1675,11 +1678,8 @@ exports.moveOpsToNewPool = (cs, oldPool, newPool) => {
16751678
* @param {string} text - text to insert
16761679
* @returns {string}
16771680
*/
1678-
exports.makeAttribution = (text) => {
1679-
const assem = new SmartOpAssembler();
1680-
for (const op of opsFromText('+', text)) assem.append(op);
1681-
return assem.toString();
1682-
};
1681+
exports.makeAttribution =
1682+
(text) => exports.serializeOps(exports.canonicalizeOps(opsFromText('+', text), false));
16831683

16841684
/**
16851685
* Iterates over attributes in exports, attribution string, or attribs property of an op and runs
@@ -1932,8 +1932,7 @@ exports.attribsAttributeValue = (attribs, key, pool) => {
19321932
* @returns {Builder}
19331933
*/
19341934
exports.builder = (oldLen) => {
1935-
const assem = new SmartOpAssembler();
1936-
const o = new Op();
1935+
const ops = [];
19371936
const charBank = exports.stringAssembler();
19381937

19391938
const self = {
@@ -1948,12 +1947,12 @@ exports.builder = (oldLen) => {
19481947
* @returns {Builder} this
19491948
*/
19501949
keep: (N, L, attribs, pool) => {
1951-
o.opcode = '=';
1950+
const o = new Op('=');
19521951
o.attribs = typeof attribs === 'string'
19531952
? attribs : new AttributeMap(pool).update(attribs || []).toString();
19541953
o.chars = N;
19551954
o.lines = (L || 0);
1956-
assem.append(o);
1955+
ops.push(o);
19571956
return self;
19581957
},
19591958

@@ -1966,7 +1965,7 @@ exports.builder = (oldLen) => {
19661965
* @returns {Builder} this
19671966
*/
19681967
keepText: (text, attribs, pool) => {
1969-
for (const op of opsFromText('=', text, attribs, pool)) assem.append(op);
1968+
ops.push(...opsFromText('=', text, attribs, pool));
19701969
return self;
19711970
},
19721971

@@ -1979,7 +1978,7 @@ exports.builder = (oldLen) => {
19791978
* @returns {Builder} this
19801979
*/
19811980
insert: (text, attribs, pool) => {
1982-
for (const op of opsFromText('+', text, attribs, pool)) assem.append(op);
1981+
ops.push(...opsFromText('+', text, attribs, pool));
19831982
charBank.append(text);
19841983
return self;
19851984
},
@@ -1991,18 +1990,22 @@ exports.builder = (oldLen) => {
19911990
* @returns {Builder} this
19921991
*/
19931992
remove: (N, L) => {
1994-
o.opcode = '-';
1993+
const o = new Op('-');
19951994
o.attribs = '';
19961995
o.chars = N;
19971996
o.lines = (L || 0);
1998-
assem.append(o);
1997+
ops.push(o);
19991998
return self;
20001999
},
20012000

20022001
toString: () => {
2003-
assem.endDocument();
2004-
const newLen = oldLen + assem.getLengthChange();
2005-
return exports.pack(oldLen, newLen, assem.toString(), charBank.toString());
2002+
/** @type {number} */
2003+
let lengthChange;
2004+
const serializedOps = exports.serializeOps((function* () {
2005+
lengthChange = yield* exports.canonicalizeOps(ops, true);
2006+
})());
2007+
const newLen = oldLen + lengthChange;
2008+
return exports.pack(oldLen, newLen, serializedOps, charBank.toString());
20062009
},
20072010
};
20082011

@@ -2037,11 +2040,11 @@ exports.makeAttribsString = (opcode, attribs, pool) => {
20372040
exports.subattribution = (astr, start, optEnd) => {
20382041
const attOps = exports.deserializeOps(astr);
20392042
let attOpsNext = attOps.next();
2040-
const assem = new SmartOpAssembler();
20412043
let attOp = new Op();
2042-
const csOp = new Op();
2044+
const csOp = new Op('-');
2045+
csOp.chars = start;
20432046

2044-
const doCsOp = () => {
2047+
const doCsOp = function* () {
20452048
if (!csOp.chars) return;
20462049
while (csOp.opcode && (attOp.opcode || !attOpsNext.done)) {
20472050
if (!attOp.opcode) {
@@ -2053,30 +2056,25 @@ exports.subattribution = (astr, start, optEnd) => {
20532056
csOp.lines++;
20542057
}
20552058
const opOut = slicerZipperFunc(attOp, csOp, null);
2056-
if (opOut.opcode) assem.append(opOut);
2059+
if (opOut.opcode) yield opOut;
20572060
}
20582061
};
20592062

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

20822080
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)