Skip to content

Commit c488e9d

Browse files
committed
Changeset: Migrate from smartOpAssembler() to canonicalizeOps()
1 parent 4e5bd5e commit c488e9d

File tree

6 files changed

+151
-154
lines changed

6 files changed

+151
-154
lines changed

CHANGELOG.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,11 @@
4747
* `opAssembler()`: Deprecated in favor of the new `serializeOps()` function.
4848
* `mergingOpAssembler()`: Deprecated in favor of the new `squashOps()`
4949
generator function (combined with `serializeOps()`).
50-
* `smartOpAssembler()`: The returned object's `appendOpWithText()` method is
51-
deprecated without a replacement available to plugins (if you need one,
52-
let us know and we can make the private `opsFromText()` function public).
50+
* `smartOpAssembler()`: Deprecated in favor of the new `canonicalizeOps()`
51+
generator function (combined with `serializeOps()`). Also, the returned
52+
object's `appendOpWithText()` method is deprecated without a replacement
53+
available to plugins (if you need one, let us know and we can make the
54+
private `opsFromText()` function public).
5355
* Several functions that should have never been public are no longer
5456
exported: `applyZip()`, `assert()`, `clearOp()`, `cloneOp()`, `copyOp()`,
5557
`error()`, `followAttributes()`, `opString()`, `stringOp()`,

src/node/db/Pad.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -497,21 +497,20 @@ Pad.prototype.copyPadWithoutHistory = async function (destinationID, force) {
497497
const newPool = newPad.pool;
498498
newPool.fromJsonable(sourcePad.pool.toJsonable()); // copy that sourceId pool to the new pad
499499

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

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

509-
const newLength = assem.getLengthChange();
510509
const newText = oldAText.text;
511510

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

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

src/static/js/Changeset.js

Lines changed: 79 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ class MergingOpAssembler {
450450
* @returns {Generator<Op, number>} The done value indicates how much the sequence of operations
451451
* changes the length of the document (in characters).
452452
*/
453-
const canonicalizeOps = function* (ops, finalize) {
453+
exports.canonicalizeOps = function* (ops, finalize) {
454454
let minusOps = [];
455455
let plusOps = [];
456456
let keepOps = [];
@@ -533,6 +533,8 @@ const opsFromText = function* (opcode, text, attribs = '', pool = null) {
533533
* - strips final "="
534534
* - ignores 0-length changes
535535
* - reorders consecutive + and - (which MergingOpAssembler doesn't do)
536+
*
537+
* @deprecated Use `canonicalizeOps` with `serializeOps` instead.
536538
*/
537539
class SmartOpAssembler {
538540
constructor() {
@@ -541,7 +543,7 @@ class SmartOpAssembler {
541543

542544
_serialize(finalize) {
543545
this._serialized = exports.serializeOps((function* () {
544-
this._lengthChange = yield* canonicalizeOps(this._ops, finalize);
546+
this._lengthChange = yield* exports.canonicalizeOps(this._ops, finalize);
545547
}).call(this));
546548
}
547549

@@ -599,51 +601,54 @@ exports.checkRep = (cs) => {
599601
const unpacked = exports.unpack(cs);
600602
const oldLen = unpacked.oldLen;
601603
const newLen = unpacked.newLen;
602-
const ops = unpacked.ops;
603604
let charBank = unpacked.charBank;
604605

605-
const assem = new SmartOpAssembler();
606606
let oldPos = 0;
607607
let calcNewLen = 0;
608608
let numInserted = 0;
609-
for (const o of exports.deserializeOps(ops)) {
610-
switch (o.opcode) {
611-
case '=':
612-
oldPos += o.chars;
613-
calcNewLen += o.chars;
614-
break;
615-
case '-':
616-
oldPos += o.chars;
617-
assert(oldPos <= oldLen, `${oldPos} > ${oldLen} in ${cs}`);
618-
break;
619-
case '+':
620-
{
621-
calcNewLen += o.chars;
622-
numInserted += o.chars;
623-
assert(calcNewLen <= newLen, `${calcNewLen} > ${newLen} in ${cs}`);
624-
break;
609+
const ops = (function* () {
610+
for (const o of exports.deserializeOps(unpacked.ops)) {
611+
switch (o.opcode) {
612+
case '=':
613+
oldPos += o.chars;
614+
calcNewLen += o.chars;
615+
break;
616+
case '-':
617+
oldPos += o.chars;
618+
assert(oldPos <= oldLen, `${oldPos} > ${oldLen} in ${cs}`);
619+
break;
620+
case '+':
621+
calcNewLen += o.chars;
622+
numInserted += o.chars;
623+
assert(calcNewLen <= newLen, `${calcNewLen} > ${newLen} in ${cs}`);
624+
break;
625625
}
626+
yield o;
626627
}
627-
assem.append(o);
628-
}
628+
})();
629+
const serializedOps = exports.serializeOps(exports.canonicalizeOps(ops, true));
629630

630631
calcNewLen += oldLen - oldPos;
631632
charBank = charBank.substring(0, numInserted);
632633
while (charBank.length < numInserted) {
633634
charBank += '?';
634635
}
635636

636-
assem.endDocument();
637-
const normalized = exports.pack(oldLen, calcNewLen, assem.toString(), charBank);
637+
const normalized = exports.pack(oldLen, calcNewLen, serializedOps, charBank);
638638
assert(normalized === cs, 'Invalid changeset (checkRep failed)');
639639

640640
return cs;
641641
};
642642

643643
/**
644+
* @deprecated Use `canonicalizeOps` with `serializeOps` instead.
644645
* @returns {SmartOpAssembler}
645646
*/
646-
exports.smartOpAssembler = () => new SmartOpAssembler();
647+
exports.smartOpAssembler = () => {
648+
warnDeprecated(
649+
'Changeset.smartOpAssembler() is deprecated; use Changeset.canonicalizeOps() instead');
650+
return new SmartOpAssembler();
651+
};
647652

648653
/**
649654
* @deprecated Use `squashOps` with `serializeOps` instead.
@@ -1089,22 +1094,22 @@ class TextLinesMutator {
10891094
* @returns {string} the integrated changeset
10901095
*/
10911096
const applyZip = (in1, in2, func) => {
1092-
const ops1 = exports.deserializeOps(in1);
1093-
const ops2 = exports.deserializeOps(in2);
1094-
let next1 = ops1.next();
1095-
let next2 = ops2.next();
1096-
const assem = new SmartOpAssembler();
1097-
while (!next1.done || !next2.done) {
1098-
if (!next1.done && !next1.value.opcode) next1 = ops1.next();
1099-
if (!next2.done && !next2.value.opcode) next2 = ops2.next();
1100-
if (next1.value == null) next1.value = new Op();
1101-
if (next2.value == null) next2.value = new Op();
1102-
if (!next1.value.opcode && !next2.value.opcode) break;
1103-
const opOut = func(next1.value, next2.value);
1104-
if (opOut && opOut.opcode) assem.append(opOut);
1105-
}
1106-
assem.endDocument();
1107-
return assem.toString();
1097+
const ops = (function* () {
1098+
const ops1 = exports.deserializeOps(in1);
1099+
const ops2 = exports.deserializeOps(in2);
1100+
let next1 = ops1.next();
1101+
let next2 = ops2.next();
1102+
while (!next1.done || !next2.done) {
1103+
if (!next1.done && !next1.value.opcode) next1 = ops1.next();
1104+
if (!next2.done && !next2.value.opcode) next2 = ops2.next();
1105+
if (next1.value == null) next1.value = new Op();
1106+
if (next2.value == null) next2.value = new Op();
1107+
if (!next1.value.opcode && !next2.value.opcode) break;
1108+
const opOut = func(next1.value, next2.value);
1109+
if (opOut && opOut.opcode) yield opOut;
1110+
}
1111+
})();
1112+
return exports.serializeOps(exports.canonicalizeOps(ops, true));
11081113
};
11091114

11101115
/**
@@ -1584,15 +1589,13 @@ exports.makeSplice = (oldFullText, spliceStart, numRemoved, newText, optNewTextA
15841589
const oldText = oldFullText.substring(spliceStart, spliceStart + numRemoved);
15851590
const newLen = oldLen + newText.length - oldText.length;
15861591

1587-
const assem = new SmartOpAssembler();
15881592
const ops = (function* () {
15891593
yield* opsFromText('=', oldFullText.substring(0, spliceStart));
15901594
yield* opsFromText('-', oldText);
15911595
yield* opsFromText('+', newText, optNewTextAPairs, pool);
15921596
})();
1593-
for (const op of ops) assem.append(op);
1594-
assem.endDocument();
1595-
return exports.pack(oldLen, newLen, assem.toString(), newText);
1597+
const serializedOps = exports.serializeOps(exports.canonicalizeOps(ops, true));
1598+
return exports.pack(oldLen, newLen, serializedOps, newText);
15961599
};
15971600

15981601
/**
@@ -1714,11 +1717,8 @@ exports.moveOpsToNewPool = (cs, oldPool, newPool) => {
17141717
* @param {string} text - text to insert
17151718
* @returns {string}
17161719
*/
1717-
exports.makeAttribution = (text) => {
1718-
const assem = new SmartOpAssembler();
1719-
for (const op of opsFromText('+', text)) assem.append(op);
1720-
return assem.toString();
1721-
};
1720+
exports.makeAttribution =
1721+
(text) => exports.serializeOps(exports.canonicalizeOps(opsFromText('+', text), false));
17221722

17231723
/**
17241724
* Iterates over attributes in exports, attribution string, or attribs property of an op and runs
@@ -1955,8 +1955,7 @@ exports.attribsAttributeValue = (attribs, key, pool) => {
19551955
* @returns {Builder}
19561956
*/
19571957
exports.builder = (oldLen) => {
1958-
const assem = new SmartOpAssembler();
1959-
const o = new Op();
1958+
const ops = [];
19601959
const charBank = exports.stringAssembler();
19611960

19621961
const self = {
@@ -1971,11 +1970,11 @@ exports.builder = (oldLen) => {
19711970
* @returns {Builder} this
19721971
*/
19731972
keep: (N, L, attribs, pool) => {
1974-
o.opcode = '=';
1973+
const o = new Op('=');
19751974
o.attribs = (attribs && exports.makeAttribsString('=', attribs, pool)) || '';
19761975
o.chars = N;
19771976
o.lines = (L || 0);
1978-
assem.append(o);
1977+
ops.push(o);
19791978
return self;
19801979
},
19811980

@@ -1988,7 +1987,7 @@ exports.builder = (oldLen) => {
19881987
* @returns {Builder} this
19891988
*/
19901989
keepText: (text, attribs, pool) => {
1991-
for (const op of opsFromText('=', text, attribs, pool)) assem.append(op);
1990+
ops.push(...opsFromText('=', text, attribs, pool));
19921991
return self;
19931992
},
19941993

@@ -2001,7 +2000,7 @@ exports.builder = (oldLen) => {
20012000
* @returns {Builder} this
20022001
*/
20032002
insert: (text, attribs, pool) => {
2004-
for (const op of opsFromText('+', text, attribs, pool)) assem.append(op);
2003+
ops.push(...opsFromText('+', text, attribs, pool));
20052004
charBank.append(text);
20062005
return self;
20072006
},
@@ -2013,18 +2012,22 @@ exports.builder = (oldLen) => {
20132012
* @returns {Builder} this
20142013
*/
20152014
remove: (N, L) => {
2016-
o.opcode = '-';
2015+
const o = new Op('-');
20172016
o.attribs = '';
20182017
o.chars = N;
20192018
o.lines = (L || 0);
2020-
assem.append(o);
2019+
ops.push(o);
20212020
return self;
20222021
},
20232022

20242023
toString: () => {
2025-
assem.endDocument();
2026-
const newLen = oldLen + assem.getLengthChange();
2027-
return exports.pack(oldLen, newLen, assem.toString(), charBank.toString());
2024+
/** @type {number} */
2025+
let lengthChange;
2026+
const serializedOps = exports.serializeOps((function* () {
2027+
lengthChange = yield* exports.canonicalizeOps(ops, true);
2028+
})());
2029+
const newLen = oldLen + lengthChange;
2030+
return exports.pack(oldLen, newLen, serializedOps, charBank.toString());
20282031
},
20292032
};
20302033

@@ -2058,11 +2061,11 @@ exports.makeAttribsString = (opcode, attribs, pool) => {
20582061
exports.subattribution = (astr, start, optEnd) => {
20592062
const attOps = exports.deserializeOps(astr);
20602063
let attOpsNext = attOps.next();
2061-
const assem = new SmartOpAssembler();
20622064
let attOp = new Op();
2063-
const csOp = new Op();
2065+
const csOp = new Op('-');
2066+
csOp.chars = start;
20642067

2065-
const doCsOp = () => {
2068+
const doCsOp = function* () {
20662069
if (!csOp.chars) return;
20672070
while (csOp.opcode && (attOp.opcode || !attOpsNext.done)) {
20682071
if (!attOp.opcode) {
@@ -2074,27 +2077,22 @@ exports.subattribution = (astr, start, optEnd) => {
20742077
csOp.lines++;
20752078
}
20762079
const opOut = slicerZipperFunc(attOp, csOp, null);
2077-
if (opOut.opcode) assem.append(opOut);
2080+
if (opOut.opcode) yield opOut;
20782081
}
20792082
};
20802083

2081-
csOp.opcode = '-';
2082-
csOp.chars = start;
2083-
2084-
doCsOp();
2085-
2086-
if (optEnd === undefined) {
2087-
if (attOp.opcode) {
2088-
assem.append(attOp);
2084+
const ops = (function* () {
2085+
yield* doCsOp();
2086+
if (optEnd === undefined) {
2087+
if (attOp.opcode) yield attOp;
2088+
yield* attOps;
2089+
} else {
2090+
csOp.opcode = '=';
2091+
csOp.chars = optEnd - start;
2092+
yield* doCsOp();
20892093
}
2090-
for (const attOp of attOps) assem.append(attOp);
2091-
} else {
2092-
csOp.opcode = '=';
2093-
csOp.chars = optEnd - start;
2094-
doCsOp();
2095-
}
2096-
2097-
return assem.toString();
2094+
})();
2095+
return exports.serializeOps(exports.canonicalizeOps(ops, false));
20982096
};
20992097

21002098
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
@@ -521,18 +521,24 @@ function Ace2Inner(editorInfo, cssManagers) {
521521
const numLines = rep.lines.length();
522522
const upToLastLine = rep.lines.offsetOfIndex(numLines - 1);
523523
const lastLineLength = rep.lines.atIndex(numLines - 1).text.length;
524-
const assem = Changeset.smartOpAssembler();
525-
const o = new Changeset.Op('-');
526-
o.chars = upToLastLine;
527-
o.lines = numLines - 1;
528-
assem.append(o);
529-
o.chars = lastLineLength;
530-
o.lines = 0;
531-
assem.append(o);
532-
for (const op of Changeset.opsFromAText(atext)) assem.append(op);
533-
const newLen = oldLen + assem.getLengthChange();
534-
const changeset = Changeset.checkRep(
535-
Changeset.pack(oldLen, newLen, assem.toString(), atext.text.slice(0, -1)));
524+
const ops = (function* () {
525+
const op1 = new Changeset.Op('-');
526+
op1.chars = upToLastLine;
527+
op1.lines = numLines - 1;
528+
yield op1;
529+
const op2 = new Changeset.Op('-');
530+
op2.chars = lastLineLength;
531+
op2.lines = 0;
532+
yield op2;
533+
yield* Changeset.opsFromAText(atext);
534+
})();
535+
let lengthChange;
536+
const serializedOps = Changeset.serializeOps((function* () {
537+
lengthChange = yield* Changeset.canonicalizeOps(ops, false);
538+
})());
539+
const newLen = oldLen + lengthChange;
540+
const changeset =
541+
Changeset.checkRep(Changeset.pack(oldLen, newLen, serializedOps, atext.text.slice(0, -1)));
536542
performDocumentApplyChangeset(changeset);
537543

538544
performSelectionChange(

0 commit comments

Comments
 (0)