Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changeset: Move SmartOpAssembler.appendOpWithText() to a standalone function #5278

Merged
merged 2 commits into from
Nov 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
* Changes to the `src/static/js/Changeset.js` library:
* `opIterator()`: The unused start index parameter has been removed, as has
the unused `lastIndex()` method on the returned object.
* `smartOpAssembler()`: The returned object's `appendOpWithText()` method is
deprecated without a replacement available to plugins (if you need one,
let us know and we can make the private `opsFromText()` function public).
* Several functions that should have never been public are no longer
exported: `applyZip()`, `assert()`, `clearOp()`, `cloneOp()`, `copyOp()`,
`error()`, `followAttributes()`, `opString()`, `stringOp()`,
Expand Down
65 changes: 44 additions & 21 deletions src/static/js/Changeset.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/

const AttributePool = require('./AttributePool');
const {padutils} = require('./pad_utils');

/**
* A `[key, value]` pair of strings describing a text attribute.
Expand Down Expand Up @@ -230,6 +231,36 @@ const copyOp = (op1, op2 = exports.newOp()) => Object.assign(op2, op1);
* @property {Function} toString -
*/

/**
* Generates operations from the given text and attributes.
*
* @param {('-'|'+'|'=')} opcode - The operator to use.
* @param {string} text - The text to remove/add/keep.
* @param {(string|Attribute[])} [attribs] - The attributes to apply to the operations. See
* `makeAttribsString`.
* @param {?AttributePool} [pool] - See `makeAttribsString`.
* @yields {Op} One or two ops (depending on the presense of newlines) that cover the given text.
* @returns {Generator<Op>}
*/
const opsFromText = function* (opcode, text, attribs = '', pool = null) {
const op = exports.newOp(opcode);
op.attribs = exports.makeAttribsString(opcode, attribs, pool);
const lastNewlinePos = text.lastIndexOf('\n');
if (lastNewlinePos < 0) {
op.chars = text.length;
op.lines = 0;
yield op;
} else {
op.chars = lastNewlinePos + 1;
op.lines = text.match(/\n/g).length;
yield op;
const op2 = copyOp(op);
op2.chars = text.length - (lastNewlinePos + 1);
op2.lines = 0;
yield op2;
}
};

/**
* Creates an object that allows you to append operations (type Op) and also compresses them if
* possible. Like MergingOpAssembler, but able to produce conforming exportss from slightly looser
Expand Down Expand Up @@ -353,28 +384,17 @@ exports.smartOpAssembler = () => {
/**
* Generates operations from the given text and attributes.
*
* @deprecated Use `opsFromText` instead.
* @param {('-'|'+'|'=')} opcode - The operator to use.
* @param {string} text - The text to remove/add/keep.
* @param {(string|Attribute[])} attribs - The attributes to apply to the operations. See
* `makeAttribsString`.
* @param {?AttributePool} pool - See `makeAttribsString`.
*/
const appendOpWithText = (opcode, text, attribs, pool) => {
const op = exports.newOp(opcode);
op.attribs = exports.makeAttribsString(opcode, attribs, pool);
const lastNewlinePos = text.lastIndexOf('\n');
if (lastNewlinePos < 0) {
op.chars = text.length;
op.lines = 0;
append(op);
} else {
op.chars = lastNewlinePos + 1;
op.lines = text.match(/\n/g).length;
append(op);
op.chars = text.length - (lastNewlinePos + 1);
op.lines = 0;
append(op);
}
padutils.warnWithStack('Changeset.smartOpAssembler().appendOpWithText() is deprecated; ' +
'use opsFromText() instead.');
for (const op of opsFromText(opcode, text, attribs, pool)) append(op);
};

const toString = () => {
Expand Down Expand Up @@ -1450,9 +1470,12 @@ exports.makeSplice = (oldFullText, spliceStart, numRemoved, newText, optNewTextA
const newLen = oldLen + newText.length - oldText.length;

const assem = exports.smartOpAssembler();
assem.appendOpWithText('=', oldFullText.substring(0, spliceStart));
assem.appendOpWithText('-', oldText);
assem.appendOpWithText('+', newText, optNewTextAPairs, pool);
const ops = (function* () {
yield* opsFromText('=', oldFullText.substring(0, spliceStart));
yield* opsFromText('-', oldText);
yield* opsFromText('+', newText, optNewTextAPairs, pool);
})();
for (const op of ops) assem.append(op);
assem.endDocument();
return exports.pack(oldLen, newLen, assem.toString(), newText);
};
Expand Down Expand Up @@ -1580,7 +1603,7 @@ exports.moveOpsToNewPool = (cs, oldPool, newPool) => {
*/
exports.makeAttribution = (text) => {
const assem = exports.smartOpAssembler();
assem.appendOpWithText('+', text);
for (const op of opsFromText('+', text)) assem.append(op);
return assem.toString();
};

Expand Down Expand Up @@ -1839,7 +1862,7 @@ exports.builder = (oldLen) => {
* @returns {Builder} this
*/
keepText: (text, attribs, pool) => {
assem.appendOpWithText('=', text, attribs, pool);
for (const op of opsFromText('=', text, attribs, pool)) assem.append(op);
return self;
},

Expand All @@ -1852,7 +1875,7 @@ exports.builder = (oldLen) => {
* @returns {Builder} this
*/
insert: (text, attribs, pool) => {
assem.appendOpWithText('+', text, attribs, pool);
for (const op of opsFromText('+', text, attribs, pool)) assem.append(op);
charBank.append(text);
return self;
},
Expand Down
19 changes: 19 additions & 0 deletions src/static/js/pad_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,25 @@ const urlRegex = (() => {
})();

const padutils = {
/**
* Prints a warning message followed by a stack trace (to make it easier to figure out what code
* is using the deprecated function).
*
* Most browsers include UI widget to examine the stack at the time of the warning, but this
* includes the stack in the log message for a couple of reasons:
* - This makes it possible to see the stack if the code runs in Node.js.
* - Users are more likely to paste the stack in bug reports they might file.
*
* @param {...*} args - Passed to `console.warn`, with a stack trace appended.
*/
warnWithStack: (...args) => {
const err = new Error();
if (Error.captureStackTrace) Error.captureStackTrace(err, padutils.warnWithStack);
err.name = '';
if (err.stack) args.push(err.stack);
console.warn(...args);
},

escapeHtml: (x) => Security.escapeHTML(String(x)),
uniqueId: () => {
const pad = require('./pad').pad; // Sidestep circular dependency
Expand Down