Skip to content

Commit 657492e

Browse files
committed
Changeset: Turn newOp() into a real class
1 parent fba0bb6 commit 657492e

File tree

7 files changed

+99
-61
lines changed

7 files changed

+99
-61
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
* `opAttributeValue()`
2424
* `appendATextToAssembler()`: Deprecated in favor of the new `opsFromAText()`
2525
generator function.
26+
* `newOp()`: Deprecated in favor of the new `Op` class.
2627

2728
# 1.8.15
2829

src/node/utils/padDiff.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
270270
let curChar = 0;
271271
let curLineOpIter = null;
272272
let curLineOpIterLine;
273-
let curLineNextOp = Changeset.newOp('+');
273+
let curLineNextOp = new Changeset.Op('+');
274274

275275
const unpacked = Changeset.unpack(cs);
276276
const csIter = Changeset.opIterator(unpacked.ops);
@@ -302,7 +302,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
302302
}
303303

304304
if (!curLineNextOp.chars) {
305-
curLineNextOp = curLineOpIter.hasNext() ? curLineOpIter.next() : Changeset.newOp();
305+
curLineNextOp = curLineOpIter.hasNext() ? curLineOpIter.next() : new Changeset.Op();
306306
}
307307

308308
const charsToUse = Math.min(numChars, curLineNextOp.chars);

src/static/js/Changeset.js

Lines changed: 90 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -83,31 +83,74 @@ exports.numToString = (num) => num.toString(36).toLowerCase();
8383

8484
/**
8585
* An operation to apply to a shared document.
86-
*
87-
* @typedef {object} Op
88-
* @property {('+'|'-'|'='|'')} opcode - The operation's operator:
89-
* - '=': Keep the next `chars` characters (containing `lines` newlines) from the base
90-
* document.
91-
* - '-': Remove the next `chars` characters (containing `lines` newlines) from the base
92-
* document.
93-
* - '+': Insert `chars` characters (containing `lines` newlines) at the current position in
94-
* the document. The inserted characters come from the changeset's character bank.
95-
* - '' (empty string): Invalid operator used in some contexts to signifiy the lack of an
96-
* operation.
97-
* @property {number} chars - The number of characters to keep, insert, or delete.
98-
* @property {number} lines - The number of characters among the `chars` characters that are
99-
* newlines. If non-zero, the last character must be a newline.
100-
* @property {string} attribs - Identifiers of attributes to apply to the text, represented as a
101-
* repeated (zero or more) sequence of asterisk followed by a non-negative base-36 (lower-case)
102-
* integer. For example, '*2*1o' indicates that attributes 2 and 60 apply to the text affected
103-
* by the operation. The identifiers come from the document's attribute pool. This is the empty
104-
* string for remove ('-') operations. For keep ('=') operations, the attributes are merged with
105-
* the base text's existing attributes:
106-
* - A keep op attribute with a non-empty value replaces an existing base text attribute that
107-
* has the same key.
108-
* - A keep op attribute with an empty value is interpreted as an instruction to remove an
109-
* existing base text attribute that has the same key, if one exists.
11086
*/
87+
class Op {
88+
/**
89+
* @param {(''|'='|'+'|'-')} [opcode=''] - Initial value of the `opcode` property.
90+
*/
91+
constructor(opcode = '') {
92+
/**
93+
* The operation's operator:
94+
* - '=': Keep the next `chars` characters (containing `lines` newlines) from the base
95+
* document.
96+
* - '-': Remove the next `chars` characters (containing `lines` newlines) from the base
97+
* document.
98+
* - '+': Insert `chars` characters (containing `lines` newlines) at the current position in
99+
* the document. The inserted characters come from the changeset's character bank.
100+
* - '' (empty string): Invalid operator used in some contexts to signifiy the lack of an
101+
* operation.
102+
*
103+
* @type {(''|'='|'+'|'-')}
104+
* @public
105+
*/
106+
this.opcode = opcode;
107+
108+
/**
109+
* The number of characters to keep, insert, or delete.
110+
*
111+
* @type {number}
112+
* @public
113+
*/
114+
this.chars = 0;
115+
116+
/**
117+
* The number of characters among the `chars` characters that are newlines. If non-zero, the
118+
* last character must be a newline.
119+
*
120+
* @type {number}
121+
* @public
122+
*/
123+
this.lines = 0;
124+
125+
/**
126+
* Identifiers of attributes to apply to the text, represented as a repeated (zero or more)
127+
* sequence of asterisk followed by a non-negative base-36 (lower-case) integer. For example,
128+
* '*2*1o' indicates that attributes 2 and 60 apply to the text affected by the operation. The
129+
* identifiers come from the document's attribute pool.
130+
*
131+
* For keep ('=') operations, the attributes are merged with the base text's existing
132+
* attributes:
133+
* - A keep op attribute with a non-empty value replaces an existing base text attribute that
134+
* has the same key.
135+
* - A keep op attribute with an empty value is interpreted as an instruction to remove an
136+
* existing base text attribute that has the same key, if one exists.
137+
*
138+
* This is the empty string for remove ('-') operations.
139+
*
140+
* @type {string}
141+
* @public
142+
*/
143+
this.attribs = '';
144+
}
145+
146+
toString() {
147+
if (!this.opcode) throw new TypeError('null op');
148+
if (typeof this.attribs !== 'string') throw new TypeError('attribs must be a string');
149+
const l = this.lines ? `|${exports.numToString(this.lines)}` : '';
150+
return this.attribs + l + this.opcode + exports.numToString(this.chars);
151+
}
152+
}
153+
exports.Op = Op;
111154

112155
/**
113156
* Describes changes to apply to a document. Does not include the attribute pool or the original
@@ -166,8 +209,7 @@ exports.opIterator = (opsStr) => {
166209
};
167210
let regexResult = nextRegexMatch();
168211

169-
const next = (optOp) => {
170-
const op = optOp || exports.newOp();
212+
const next = (op = new Op()) => {
171213
if (regexResult[0]) {
172214
op.attribs = regexResult[1];
173215
op.lines = exports.parseNum(regexResult[2] || '0');
@@ -203,15 +245,14 @@ const clearOp = (op) => {
203245
/**
204246
* Creates a new Op object
205247
*
248+
* @deprecated Use the `Op` class instead.
206249
* @param {('+'|'-'|'='|'')} [optOpcode=''] - The operation's operator.
207250
* @returns {Op}
208251
*/
209-
exports.newOp = (optOpcode) => ({
210-
opcode: (optOpcode || ''),
211-
chars: 0,
212-
lines: 0,
213-
attribs: '',
214-
});
252+
exports.newOp = (optOpcode) => {
253+
padutils.warnWithStack('Changeset.newOp() is deprecated; use the Changeset.Op class instead');
254+
return new Op(optOpcode);
255+
};
215256

216257
/**
217258
* Copies op1 to op2
@@ -220,7 +261,7 @@ exports.newOp = (optOpcode) => ({
220261
* @param {Op} [op2] - dest Op. If not given, a new Op is used.
221262
* @returns {Op} `op2`
222263
*/
223-
const copyOp = (op1, op2 = exports.newOp()) => Object.assign(op2, op1);
264+
const copyOp = (op1, op2 = new Op()) => Object.assign(op2, op1);
224265

225266
/**
226267
* Serializes a sequence of Ops.
@@ -257,7 +298,7 @@ const copyOp = (op1, op2 = exports.newOp()) => Object.assign(op2, op1);
257298
* @returns {Generator<Op>}
258299
*/
259300
const opsFromText = function* (opcode, text, attribs = '', pool = null) {
260-
const op = exports.newOp(opcode);
301+
const op = new Op(opcode);
261302
op.attribs = typeof attribs === 'string'
262303
? attribs : new AttributeMap(pool).update(attribs || [], opcode === '+').toString();
263304
const lastNewlinePos = text.lastIndexOf('\n');
@@ -447,7 +488,7 @@ exports.smartOpAssembler = () => {
447488
*/
448489
exports.mergingOpAssembler = () => {
449490
const assem = exports.opAssembler();
450-
const bufOp = exports.newOp();
491+
const bufOp = new Op();
451492

452493
// If we get, for example, insertions [xxx\n,yyy], those don't merge,
453494
// but if we get [xxx\n,yyy,zzz\n], that merges to [xxx\nyyyzzz\n].
@@ -523,12 +564,8 @@ exports.opAssembler = () => {
523564
* @param {Op} op - Operation to add. Ownership remains with the caller.
524565
*/
525566
const append = (op) => {
526-
if (!op.opcode) throw new TypeError('null op');
527-
if (typeof op.attribs !== 'string') throw new TypeError('attribs must be a string');
528-
serialized += op.attribs;
529-
if (op.lines) serialized += `|${exports.numToString(op.lines)}`;
530-
serialized += op.opcode;
531-
serialized += exports.numToString(op.chars);
567+
assert(op instanceof Op, 'argument must be an instance of Op');
568+
serialized += op.toString();
532569
};
533570

534571
const toString = () => serialized;
@@ -972,8 +1009,8 @@ const applyZip = (in1, in2, func) => {
9721009
const iter1 = exports.opIterator(in1);
9731010
const iter2 = exports.opIterator(in2);
9741011
const assem = exports.smartOpAssembler();
975-
const op1 = exports.newOp();
976-
const op2 = exports.newOp();
1012+
const op1 = new Op();
1013+
const op2 = new Op();
9771014
while (op1.opcode || iter1.hasNext() || op2.opcode || iter2.hasNext()) {
9781015
if ((!op1.opcode) && iter1.hasNext()) iter1.next(op1);
9791016
if ((!op2.opcode) && iter2.hasNext()) iter2.next(op2);
@@ -1148,7 +1185,7 @@ exports.composeAttributes = (att1, att2, resultIsMutation, pool) => {
11481185
* @returns {Op} The result of applying `csOp` to `attOp`.
11491186
*/
11501187
const slicerZipperFunc = (attOp, csOp, pool) => {
1151-
const opOut = exports.newOp();
1188+
const opOut = new Op();
11521189
if (!attOp.opcode) {
11531190
copyOp(csOp, opOut);
11541191
csOp.opcode = '';
@@ -1231,7 +1268,7 @@ exports.mutateAttributionLines = (cs, lines, pool) => {
12311268
const line = mut.removeLines(1);
12321269
lineIter = exports.opIterator(line);
12331270
}
1234-
if (!lineIter || !lineIter.hasNext()) return exports.newOp();
1271+
if (!lineIter || !lineIter.hasNext()) return new Op();
12351272
return lineIter.next();
12361273
};
12371274
let lineAssem = null;
@@ -1248,8 +1285,8 @@ exports.mutateAttributionLines = (cs, lines, pool) => {
12481285
lineAssem = null;
12491286
};
12501287

1251-
let csOp = exports.newOp();
1252-
let attOp = exports.newOp();
1288+
let csOp = new Op();
1289+
let attOp = new Op();
12531290
while (csOp.opcode || csIter.hasNext() || attOp.opcode || isNextMutOp()) {
12541291
if (!csOp.opcode && csIter.hasNext()) csOp = csIter.next();
12551292
if ((!csOp.opcode) && (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext()))) {
@@ -1826,7 +1863,7 @@ exports.attribsAttributeValue = (attribs, key, pool) => {
18261863
*/
18271864
exports.builder = (oldLen) => {
18281865
const assem = exports.smartOpAssembler();
1829-
const o = exports.newOp();
1866+
const o = new Op();
18301867
const charBank = exports.stringAssembler();
18311868

18321869
const self = {
@@ -1930,8 +1967,8 @@ exports.makeAttribsString = (opcode, attribs, pool) => {
19301967
exports.subattribution = (astr, start, optEnd) => {
19311968
const iter = exports.opIterator(astr);
19321969
const assem = exports.smartOpAssembler();
1933-
let attOp = exports.newOp();
1934-
const csOp = exports.newOp();
1970+
let attOp = new Op();
1971+
const csOp = new Op();
19351972

19361973
const doCsOp = () => {
19371974
if (!csOp.chars) return;
@@ -1994,7 +2031,7 @@ exports.inverse = (cs, lines, alines, pool) => {
19942031
let curChar = 0;
19952032
let curLineOpIter = null;
19962033
let curLineOpIterLine;
1997-
let curLineNextOp = exports.newOp('+');
2034+
let curLineNextOp = new Op('+');
19982035

19992036
const unpacked = exports.unpack(cs);
20002037
const csIter = exports.opIterator(unpacked.ops);
@@ -2025,7 +2062,7 @@ exports.inverse = (cs, lines, alines, pool) => {
20252062
curLineOpIter = exports.opIterator(alinesGet(curLine));
20262063
}
20272064
if (!curLineNextOp.chars) {
2028-
curLineNextOp = curLineOpIter.hasNext() ? curLineOpIter.next() : exports.newOp();
2065+
curLineNextOp = curLineOpIter.hasNext() ? curLineOpIter.next() : new Op();
20292066
}
20302067
const charsToUse = Math.min(numChars, curLineNextOp.chars);
20312068
func(charsToUse, curLineNextOp.attribs, charsToUse === curLineNextOp.chars &&
@@ -2135,7 +2172,7 @@ exports.follow = (cs1, cs2, reverseInsertOrder, pool) => {
21352172
const hasInsertFirst = exports.attributeTester(['insertorder', 'first'], pool);
21362173

21372174
const newOps = applyZip(unpacked1.ops, unpacked2.ops, (op1, op2) => {
2138-
const opOut = exports.newOp();
2175+
const opOut = new Op();
21392176
if (op1.opcode === '+' || op2.opcode === '+') {
21402177
let whichToDo;
21412178
if (op2.opcode !== '+') {

src/static/js/ace2_inner.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ function Ace2Inner(editorInfo, cssManagers) {
523523
const upToLastLine = rep.lines.offsetOfIndex(numLines - 1);
524524
const lastLineLength = rep.lines.atIndex(numLines - 1).text.length;
525525
const assem = Changeset.smartOpAssembler();
526-
const o = Changeset.newOp('-');
526+
const o = new Changeset.Op('-');
527527
o.chars = upToLastLine;
528528
o.lines = numLines - 1;
529529
assem.append(o);

src/static/js/contentcollector.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ const makeContentCollector = (collectStyles, abrowser, apool, className2Author)
8383
const textArray = [];
8484
const attribsArray = [];
8585
let attribsBuilder = null;
86-
const op = Changeset.newOp('+');
86+
const op = new Changeset.Op('+');
8787
const self = {
8888
length: () => textArray.length,
8989
atColumnZero: () => textArray[textArray.length - 1] === '',

src/static/js/linestylefilter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ linestylefilter.getLineStyleFilter = (lineLength, aline, textAndClassFunc, apool
102102
let nextOp, nextOpClasses;
103103

104104
const goNextOp = () => {
105-
nextOp = attributionIter.hasNext() ? attributionIter.next() : Changeset.newOp();
105+
nextOp = attributionIter.hasNext() ? attributionIter.next() : new Changeset.Op();
106106
nextOpClasses = (nextOp.opcode && attribsToClasses(nextOp.attribs));
107107
};
108108
goNextOp();

src/tests/frontend/specs/easysync.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ describe('easysync', function () {
5757

5858
const mutationsToChangeset = (oldLen, arrayOfArrays) => {
5959
const assem = Changeset.smartOpAssembler();
60-
const op = Changeset.newOp();
60+
const op = new Changeset.Op();
6161
const bank = Changeset.stringAssembler();
6262
let oldPos = 0;
6363
let newLen = 0;
@@ -507,7 +507,7 @@ describe('easysync', function () {
507507
const opAssem = Changeset.smartOpAssembler();
508508
const oldLen = origText.length;
509509

510-
const nextOp = Changeset.newOp();
510+
const nextOp = new Changeset.Op();
511511

512512
const appendMultilineOp = (opcode, txt) => {
513513
nextOp.opcode = opcode;
@@ -651,7 +651,7 @@ describe('easysync', function () {
651651
const testSplitJoinAttributionLines = (randomSeed) => {
652652
const stringToOps = (str) => {
653653
const assem = Changeset.mergingOpAssembler();
654-
const o = Changeset.newOp('+');
654+
const o = new Changeset.Op('+');
655655
o.chars = 1;
656656
for (let i = 0; i < str.length; i++) {
657657
const c = str.charAt(i);

0 commit comments

Comments
 (0)