Skip to content

Commit d19a34e

Browse files
committed
Changeset: Migrate from smartOpAssembler() to canonicalizeOps()
1 parent 8b5bcf4 commit d19a34e

File tree

6 files changed

+152
-148
lines changed

6 files changed

+152
-148
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
`squashOps()` generator function with `serializeOps()` instead.
4242
* The `appendATextToAssembler()` function is deprecated; use the new
4343
`opsFromAText()` generator function instead.
44+
* The `smartOpAssembler()` function is deprecated; use the new
45+
`canonicalizeOps()` generator function with `serializeOps()` instead.
4446

4547
### Notable enhancements
4648

src/node/db/Pad.js

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

491-
// based on Changeset.makeSplice
492-
const assem = Changeset.smartOpAssembler();
493-
for (const op of Changeset.opsFromAText(oldAText)) assem.append(op);
494-
assem.endDocument();
491+
let newLength;
492+
const serializedOps = Changeset.serializeOps((function* () {
493+
newLength = yield* Changeset.canonicalizeOps(Changeset.opsFromAText(oldAText), true);
494+
})());
495495

496496
// although we have instantiated the newPad with '\n', an additional '\n' is
497497
// added internally, so the pad text on the revision 0 is "\n\n"
498498
const oldLength = 2;
499499

500-
const newLength = assem.getLengthChange();
501500
const newText = oldAText.text;
502501

503502
// create a changeset that removes the previous text and add the newText with
504503
// all atributes present on the source pad
505-
const changeset = Changeset.pack(oldLength, newLength, assem.toString(), newText);
504+
const changeset = Changeset.pack(oldLength, newLength, serializedOps, newText);
506505
newPad.appendRevision(changeset);
507506

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

src/static/js/Changeset.js

Lines changed: 83 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ class MergingOpAssembler {
451451
* @yields {Op} The canonicalized operations.
452452
* @returns {number} How much the sequence of operations changes the length (in characters).
453453
*/
454-
const canonicalizeOps = function* (ops, finalize) {
454+
exports.canonicalizeOps = function* (ops, finalize) {
455455
let minusOps = [];
456456
let plusOps = [];
457457
let keepOps = [];
@@ -533,6 +533,8 @@ const opsFromText = function* (opcode, text, attribs, pool) {
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

@@ -592,51 +594,54 @@ exports.checkRep = (cs) => {
592594
const unpacked = exports.unpack(cs);
593595
const oldLen = unpacked.oldLen;
594596
const newLen = unpacked.newLen;
595-
const ops = unpacked.ops;
596597
let charBank = unpacked.charBank;
597598

598-
const assem = new SmartOpAssembler();
599599
let oldPos = 0;
600600
let calcNewLen = 0;
601601
let numInserted = 0;
602-
for (const o of new exports.OpIter(ops)) {
603-
switch (o.opcode) {
604-
case '=':
605-
oldPos += o.chars;
606-
calcNewLen += o.chars;
607-
break;
608-
case '-':
609-
oldPos += o.chars;
610-
assert(oldPos <= oldLen, oldPos, ' > ', oldLen, ' in ', cs);
611-
break;
612-
case '+':
613-
{
614-
calcNewLen += o.chars;
615-
numInserted += o.chars;
616-
assert(calcNewLen <= newLen, calcNewLen, ' > ', newLen, ' in ', cs);
617-
break;
602+
const ops = (function* () {
603+
for (const o of new exports.OpIter(unpacked.ops)) {
604+
switch (o.opcode) {
605+
case '=':
606+
oldPos += o.chars;
607+
calcNewLen += o.chars;
608+
break;
609+
case '-':
610+
oldPos += o.chars;
611+
assert(oldPos <= oldLen, oldPos, ' > ', oldLen, ' in ', cs);
612+
break;
613+
case '+':
614+
calcNewLen += o.chars;
615+
numInserted += o.chars;
616+
assert(calcNewLen <= newLen, calcNewLen, ' > ', newLen, ' in ', cs);
617+
break;
618618
}
619+
yield o;
619620
}
620-
assem.append(o);
621-
}
621+
})();
622+
const serializedOps = exports.serializeOps(exports.canonicalizeOps(ops, true));
622623

623624
calcNewLen += oldLen - oldPos;
624625
charBank = charBank.substring(0, numInserted);
625626
while (charBank.length < numInserted) {
626627
charBank += '?';
627628
}
628629

629-
assem.endDocument();
630-
const normalized = exports.pack(oldLen, calcNewLen, assem.toString(), charBank);
630+
const normalized = exports.pack(oldLen, calcNewLen, serializedOps, charBank);
631631
assert(normalized === cs, 'Invalid changeset (checkRep failed)');
632632

633633
return cs;
634634
};
635635

636636
/**
637+
* @deprecated Use `canonicalizeOps` with `serializeOps` instead.
637638
* @returns {SmartOpAssembler}
638639
*/
639-
exports.smartOpAssembler = () => new SmartOpAssembler();
640+
exports.smartOpAssembler = () => {
641+
warnDeprecated(
642+
'Changeset.smartOpAssembler() is deprecated; use Changeset.canonicalizeOps() instead');
643+
return new SmartOpAssembler();
644+
};
640645

641646
/**
642647
* @deprecated Use `squashOps` with `serializeOps` instead.
@@ -1076,19 +1081,19 @@ class TextLinesMutator {
10761081
* @returns {string} the integrated changeset
10771082
*/
10781083
const applyZip = (in1, in2, func) => {
1079-
const iter1 = new exports.OpIter(in1);
1080-
const iter2 = new exports.OpIter(in2);
1081-
const assem = new SmartOpAssembler();
1082-
let op1 = new exports.Op();
1083-
let op2 = new exports.Op();
1084-
while (op1.opcode || iter1.hasNext() || op2.opcode || iter2.hasNext()) {
1085-
if (!op1.opcode && iter1.hasNext()) op1 = iter1.next();
1086-
if (!op2.opcode && iter2.hasNext()) op2 = iter2.next();
1087-
const opOut = func(op1, op2);
1088-
if (opOut && opOut.opcode) assem.append(opOut);
1089-
}
1090-
assem.endDocument();
1091-
return assem.toString();
1084+
const ops = (function* () {
1085+
const iter1 = new exports.OpIter(in1);
1086+
const iter2 = new exports.OpIter(in2);
1087+
let op1 = new exports.Op();
1088+
let op2 = new exports.Op();
1089+
while (op1.opcode || iter1.hasNext() || op2.opcode || iter2.hasNext()) {
1090+
if (!op1.opcode && iter1.hasNext()) op1 = iter1.next();
1091+
if (!op2.opcode && iter2.hasNext()) op2 = iter2.next();
1092+
const opOut = func(op1, op2);
1093+
if (opOut && opOut.opcode) yield opOut;
1094+
}
1095+
})();
1096+
return exports.serializeOps(exports.canonicalizeOps(ops, true));
10921097
};
10931098

10941099
/**
@@ -1546,15 +1551,13 @@ exports.makeSplice = (oldFullText, spliceStart, numRemoved, newText, optNewTextA
15461551
const oldText = oldFullText.substring(spliceStart, spliceStart + numRemoved);
15471552
const newLen = oldLen + newText.length - oldText.length;
15481553

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

15601563
/**
@@ -1683,11 +1686,8 @@ exports.moveOpsToNewPool = (cs, oldPool, newPool) => {
16831686
* @param {string} text - text to insert
16841687
* @returns {string}
16851688
*/
1686-
exports.makeAttribution = (text) => {
1687-
const assem = new SmartOpAssembler();
1688-
for (const op of opsFromText('+', text)) assem.append(op);
1689-
return assem.toString();
1690-
};
1689+
exports.makeAttribution =
1690+
(text) => exports.serializeOps(exports.canonicalizeOps(opsFromText('+', text), false));
16911691

16921692
/**
16931693
* Iterates over attributes in exports, attribution string, or attribs property of an op and runs
@@ -1919,8 +1919,8 @@ exports.attribsAttributeValue = (attribs, key, pool) => {
19191919
* @returns {Builder}
19201920
*/
19211921
exports.builder = (oldLen) => {
1922-
const assem = new SmartOpAssembler();
1923-
const o = new exports.Op();
1922+
const ops = [];
1923+
let packed = null;
19241924
const charBank = exports.stringAssembler();
19251925

19261926
const self = {
@@ -1929,34 +1929,44 @@ exports.builder = (oldLen) => {
19291929
* latter case).
19301930
*/
19311931
keep: (N, L, attribs, pool) => {
1932-
o.opcode = '=';
1932+
const o = new exports.Op('=');
19331933
o.attribs = (attribs && exports.makeAttribsString('=', attribs, pool)) || '';
19341934
o.chars = N;
19351935
o.lines = (L || 0);
1936-
assem.append(o);
1936+
packed = null;
1937+
ops.push(o);
19371938
return self;
19381939
},
19391940
keepText: (text, attribs, pool) => {
1940-
for (const op of opsFromText('=', text, attribs, pool)) assem.append(op);
1941+
packed = null;
1942+
ops.push(...opsFromText('=', text, attribs, pool));
19411943
return self;
19421944
},
19431945
insert: (text, attribs, pool) => {
1944-
for (const op of opsFromText('+', text, attribs, pool)) assem.append(op);
1946+
packed = null;
1947+
ops.push(...opsFromText('+', text, attribs, pool));
19451948
charBank.append(text);
19461949
return self;
19471950
},
19481951
remove: (N, L) => {
1949-
o.opcode = '-';
1952+
const o = new exports.Op('-');
19501953
o.attribs = '';
19511954
o.chars = N;
19521955
o.lines = (L || 0);
1953-
assem.append(o);
1956+
packed = null;
1957+
ops.push(o);
19541958
return self;
19551959
},
19561960
toString: () => {
1957-
assem.endDocument();
1958-
const newLen = oldLen + assem.getLengthChange();
1959-
return exports.pack(oldLen, newLen, assem.toString(), charBank.toString());
1961+
if (packed == null) {
1962+
let lengthChange;
1963+
const serializedOps = exports.serializeOps((function* () {
1964+
lengthChange = yield* exports.canonicalizeOps(ops, true);
1965+
})());
1966+
const newLen = oldLen + lengthChange;
1967+
packed = exports.pack(oldLen, newLen, serializedOps, charBank.toString());
1968+
}
1969+
return packed;
19601970
},
19611971
};
19621972

@@ -1989,11 +1999,11 @@ exports.makeAttribsString = (opcode, attribs, pool) => {
19891999
*/
19902000
exports.subattribution = (astr, start, optEnd) => {
19912001
const iter = new exports.OpIter(astr);
1992-
const assem = new SmartOpAssembler();
19932002
let attOp = new exports.Op();
1994-
const csOp = new exports.Op();
2003+
const csOp = new exports.Op('-');
2004+
csOp.chars = start;
19952005

1996-
const doCsOp = () => {
2006+
const doCsOp = function* () {
19972007
if (!csOp.chars) return;
19982008
while (csOp.opcode && (attOp.opcode || iter.hasNext())) {
19992009
if (!attOp.opcode) attOp = iter.next();
@@ -2002,27 +2012,22 @@ exports.subattribution = (astr, start, optEnd) => {
20022012
csOp.lines++;
20032013
}
20042014
const opOut = slicerZipperFunc(attOp, csOp, null);
2005-
if (opOut.opcode) assem.append(opOut);
2015+
if (opOut.opcode) yield opOut;
20062016
}
20072017
};
20082018

2009-
csOp.opcode = '-';
2010-
csOp.chars = start;
2011-
2012-
doCsOp();
2013-
2014-
if (optEnd === undefined) {
2015-
if (attOp.opcode) {
2016-
assem.append(attOp);
2019+
const ops = (function* () {
2020+
yield* doCsOp();
2021+
if (optEnd === undefined) {
2022+
if (attOp.opcode) yield attOp;
2023+
yield* iter;
2024+
} else {
2025+
csOp.opcode = '=';
2026+
csOp.chars = optEnd - start;
2027+
yield* doCsOp();
20172028
}
2018-
while (iter.hasNext()) assem.append(iter.next());
2019-
} else {
2020-
csOp.opcode = '=';
2021-
csOp.chars = optEnd - start;
2022-
doCsOp();
2023-
}
2024-
2025-
return assem.toString();
2029+
})();
2030+
return exports.serializeOps(exports.canonicalizeOps(ops, false));
20262031
};
20272032

20282033
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
@@ -530,18 +530,24 @@ function Ace2Inner(editorInfo, cssManagers) {
530530
const numLines = rep.lines.length();
531531
const upToLastLine = rep.lines.offsetOfIndex(numLines - 1);
532532
const lastLineLength = rep.lines.atIndex(numLines - 1).text.length;
533-
const assem = Changeset.smartOpAssembler();
534-
const o = new Changeset.Op('-');
535-
o.chars = upToLastLine;
536-
o.lines = numLines - 1;
537-
assem.append(o);
538-
o.chars = lastLineLength;
539-
o.lines = 0;
540-
assem.append(o);
541-
for (const op of Changeset.opsFromAText(atext)) assem.append(op);
542-
const newLen = oldLen + assem.getLengthChange();
543-
const changeset = Changeset.checkRep(
544-
Changeset.pack(oldLen, newLen, assem.toString(), atext.text.slice(0, -1)));
533+
const ops = (function* () {
534+
const op1 = new Changeset.Op('-');
535+
op1.chars = upToLastLine;
536+
op1.lines = numLines - 1;
537+
yield op1;
538+
const op2 = new Changeset.Op('-');
539+
op2.chars = lastLineLength;
540+
op2.lines = 0;
541+
yield op2;
542+
yield* Changeset.opsFromAText(atext);
543+
})();
544+
let lengthChange;
545+
const serializedOps = Changeset.serializeOps((function* () {
546+
lengthChange = yield* Changeset.canonicalizeOps(ops, false);
547+
})());
548+
const newLen = oldLen + lengthChange;
549+
const changeset =
550+
Changeset.checkRep(Changeset.pack(oldLen, newLen, serializedOps, atext.text.slice(0, -1)));
545551
performDocumentApplyChangeset(changeset);
546552

547553
performSelectionChange(

0 commit comments

Comments
 (0)