Compare commits
19 Commits
develop
...
rhansen-ch
Author | SHA1 | Date |
---|---|---|
Richard Hansen | 60f2a059ec | |
Richard Hansen | b718d88157 | |
Richard Hansen | a470253779 | |
Richard Hansen | cca906e8fc | |
Richard Hansen | 2d0e393839 | |
Richard Hansen | 3fe2b17094 | |
Richard Hansen | 29da9815ae | |
Richard Hansen | a1c4382386 | |
Richard Hansen | d3d2090ca5 | |
Richard Hansen | 23e7809b4a | |
Richard Hansen | 2448fb8e41 | |
Richard Hansen | daa6b9074a | |
Richard Hansen | b5486b6753 | |
Richard Hansen | d5a7bf7a8f | |
Richard Hansen | 04ed432a01 | |
Richard Hansen | 8c01b66d40 | |
Richard Hansen | 36d06006dc | |
Richard Hansen | cf82261d2b | |
Richard Hansen | 9ea424c8f9 |
|
@ -36,8 +36,14 @@
|
|||
* `opAttributeValue()`
|
||||
* `opIterator()`: Deprecated in favor of the new `deserializeOps()` generator
|
||||
function.
|
||||
* `opAssembler()`: Deprecated in favor of the new `serializeOps()` function.
|
||||
* `mergingOpAssembler()`: Deprecated in favor of the new `squashOps()`
|
||||
generator function (combined with `serializeOps()`).
|
||||
* `smartOpAssembler()`: Deprecated in favor of the new `canonicalizeOps()`
|
||||
generator function (combined with `serializeOps()`).
|
||||
* `appendATextToAssembler()`: Deprecated in favor of the new `opsFromAText()`
|
||||
generator function.
|
||||
* `builder()`: Deprecated in favor of the new `Builder` class.
|
||||
* `newOp()`: Deprecated in favor of the new `Op` class.
|
||||
|
||||
# 1.8.16
|
||||
|
|
|
@ -538,7 +538,7 @@ exports.restoreRevision = async (padID, rev) => {
|
|||
};
|
||||
|
||||
// create a new changeset with a helper builder object
|
||||
const builder = Changeset.builder(oldText.length);
|
||||
const builder = new Changeset.Builder(oldText.length);
|
||||
|
||||
// assemble each line into the builder
|
||||
eachAttribRun(atext.attribs, (start, end, attribs) => {
|
||||
|
|
|
@ -494,21 +494,20 @@ Pad.prototype.copyPadWithoutHistory = async function (destinationID, force) {
|
|||
|
||||
const oldAText = this.atext;
|
||||
|
||||
// based on Changeset.makeSplice
|
||||
const assem = Changeset.smartOpAssembler();
|
||||
for (const op of Changeset.opsFromAText(oldAText)) assem.append(op);
|
||||
assem.endDocument();
|
||||
let newLength;
|
||||
const serializedOps = Changeset.serializeOps((function* () {
|
||||
newLength = yield* Changeset.canonicalizeOps(Changeset.opsFromAText(oldAText), true);
|
||||
})());
|
||||
|
||||
// although we have instantiated the newPad with '\n', an additional '\n' is
|
||||
// added internally, so the pad text on the revision 0 is "\n\n"
|
||||
const oldLength = 2;
|
||||
|
||||
const newLength = assem.getLengthChange();
|
||||
const newText = oldAText.text;
|
||||
|
||||
// create a changeset that removes the previous text and add the newText with
|
||||
// all atributes present on the source pad
|
||||
const changeset = Changeset.pack(oldLength, newLength, assem.toString(), newText);
|
||||
const changeset = Changeset.pack(oldLength, newLength, serializedOps, newText);
|
||||
newPad.appendRevision(changeset);
|
||||
|
||||
await hooks.aCallAll('padCopy', {originalPad: this, destinationID});
|
||||
|
|
|
@ -582,11 +582,10 @@ const handleUserChanges = async (socket, message) => {
|
|||
const wireApool = (new AttributePool()).fromJsonable(apool);
|
||||
const pad = await padManager.getPad(thisSession.padId);
|
||||
|
||||
// Verify that the changeset has valid syntax and is in canonical form
|
||||
Changeset.checkRep(changeset);
|
||||
const cs = Changeset.unpack(changeset).validate();
|
||||
|
||||
// Validate all added 'author' attribs to be the same value as the current user
|
||||
for (const op of Changeset.deserializeOps(Changeset.unpack(changeset).ops)) {
|
||||
for (const op of Changeset.deserializeOps(cs.ops)) {
|
||||
// + can add text with attribs
|
||||
// = can change or add attribs
|
||||
// - can have attribs, but they are discarded and don't show up in the attribs -
|
||||
|
@ -629,10 +628,10 @@ const handleUserChanges = async (socket, message) => {
|
|||
|
||||
const prevText = pad.text();
|
||||
|
||||
if (Changeset.oldLen(rebasedChangeset) !== prevText.length) {
|
||||
if (Changeset.unpack(rebasedChangeset).oldLen !== prevText.length) {
|
||||
throw new Error(
|
||||
`Can't apply changeset ${rebasedChangeset} with oldLen ` +
|
||||
`${Changeset.oldLen(rebasedChangeset)} to document of length ${prevText.length}`);
|
||||
`${Changeset.unpack(rebasedChangeset).oldLen} to document of length ${prevText.length}`);
|
||||
}
|
||||
|
||||
const newRev = await pad.appendRevision(rebasedChangeset, thisSession.author);
|
||||
|
@ -759,7 +758,7 @@ const _correctMarkersInPad = (atext, apool) => {
|
|||
// create changeset that removes these bad markers
|
||||
offset = 0;
|
||||
|
||||
const builder = Changeset.builder(text.length);
|
||||
const builder = new Changeset.Builder(text.length);
|
||||
|
||||
badMarkers.forEach((pos) => {
|
||||
builder.keepText(text.substring(offset, pos));
|
||||
|
|
|
@ -125,7 +125,7 @@ const getHTMLFromAtext = async (pad, atext, authorColors) => {
|
|||
// becomes
|
||||
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
|
||||
const taker = Changeset.stringIterator(text);
|
||||
const assem = Changeset.stringAssembler();
|
||||
let assem = '';
|
||||
const openTags = [];
|
||||
|
||||
const getSpanClassFor = (i) => {
|
||||
|
@ -161,16 +161,7 @@ const getHTMLFromAtext = async (pad, atext, authorColors) => {
|
|||
const emitOpenTag = (i) => {
|
||||
openTags.unshift(i);
|
||||
const spanClass = getSpanClassFor(i);
|
||||
|
||||
if (spanClass) {
|
||||
assem.append('<span class="');
|
||||
assem.append(spanClass);
|
||||
assem.append('">');
|
||||
} else {
|
||||
assem.append('<');
|
||||
assem.append(tags[i]);
|
||||
assem.append('>');
|
||||
}
|
||||
assem += spanClass ? `<span class="${spanClass}">` : `<${tags[i]}>`;
|
||||
};
|
||||
|
||||
// this closes an open tag and removes its reference from openTags
|
||||
|
@ -178,14 +169,7 @@ const getHTMLFromAtext = async (pad, atext, authorColors) => {
|
|||
openTags.shift();
|
||||
const spanClass = getSpanClassFor(i);
|
||||
const spanWithData = isSpanWithData(i);
|
||||
|
||||
if (spanClass || spanWithData) {
|
||||
assem.append('</span>');
|
||||
} else {
|
||||
assem.append('</');
|
||||
assem.append(tags[i]);
|
||||
assem.append('>');
|
||||
}
|
||||
assem += spanClass || spanWithData ? '</span>' : `</${tags[i]}>`;
|
||||
};
|
||||
|
||||
const urls = padutils.findURLs(text);
|
||||
|
@ -246,7 +230,7 @@ const getHTMLFromAtext = async (pad, atext, authorColors) => {
|
|||
// from but they break the abiword parser and are completly useless
|
||||
s = s.replace(String.fromCharCode(12), '');
|
||||
|
||||
assem.append(_encodeWhitespace(Security.escapeHTML(s)));
|
||||
assem += _encodeWhitespace(Security.escapeHTML(s));
|
||||
} // end iteration over spans in line
|
||||
|
||||
// close all the tags that are open after the last op
|
||||
|
@ -269,14 +253,14 @@ const getHTMLFromAtext = async (pad, atext, authorColors) => {
|
|||
// https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
|
||||
// https://mathiasbynens.github.io/rel-noopener/
|
||||
// https://github.com/ether/etherpad-lite/pull/3636
|
||||
assem.append(`<a href="${Security.escapeHTMLAttribute(url)}" rel="noreferrer noopener">`);
|
||||
assem += `<a href="${Security.escapeHTMLAttribute(url)}" rel="noreferrer noopener">`;
|
||||
processNextChars(urlLength);
|
||||
assem.append('</a>');
|
||||
assem += '</a>';
|
||||
});
|
||||
}
|
||||
processNextChars(text.length - idx);
|
||||
|
||||
return _processSpaces(assem.toString());
|
||||
return _processSpaces(assem);
|
||||
};
|
||||
// end getLineHTML
|
||||
const pieces = [css];
|
||||
|
|
|
@ -67,7 +67,7 @@ const getTXTFromAtext = (pad, atext, authorColors) => {
|
|||
// becomes
|
||||
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
|
||||
const taker = Changeset.stringIterator(text);
|
||||
const assem = Changeset.stringAssembler();
|
||||
let assem = '';
|
||||
|
||||
let idx = 0;
|
||||
|
||||
|
@ -161,7 +161,7 @@ const getTXTFromAtext = (pad, atext, authorColors) => {
|
|||
// plugins from being able to display * at the beginning of a line
|
||||
// s = s.replace("*", ""); // Then remove it
|
||||
|
||||
assem.append(s);
|
||||
assem += s;
|
||||
} // end iteration over spans in line
|
||||
|
||||
const tags2close = [];
|
||||
|
@ -175,7 +175,7 @@ const getTXTFromAtext = (pad, atext, authorColors) => {
|
|||
// end processNextChars
|
||||
|
||||
processNextChars(text.length - idx);
|
||||
return (assem.toString());
|
||||
return assem;
|
||||
};
|
||||
// end getLineHTML
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ exports.setPadHTML = async (pad, html) => {
|
|||
const newAttribs = `${result.lineAttribs.join('|1+1')}|1+1`;
|
||||
|
||||
// create a new changeset with a helper builder object
|
||||
const builder = Changeset.builder(1);
|
||||
const builder = new Changeset.Builder(1);
|
||||
|
||||
// assemble each line into the builder
|
||||
let textIndex = 0;
|
||||
|
|
|
@ -69,7 +69,7 @@ PadDiff.prototype._createClearAuthorship = async function (rev) {
|
|||
const atext = await this._pad.getInternalRevisionAText(rev);
|
||||
|
||||
// build clearAuthorship changeset
|
||||
const builder = Changeset.builder(atext.text.length);
|
||||
const builder = new Changeset.Builder(atext.text.length);
|
||||
builder.keepText(atext.text, [['author', '']], this._pad.pool);
|
||||
const changeset = builder.toString();
|
||||
|
||||
|
@ -206,28 +206,26 @@ PadDiff.prototype._extendChangesetWithAuthor = (changeset, author, apool) => {
|
|||
// unpack
|
||||
const unpacked = Changeset.unpack(changeset);
|
||||
|
||||
const assem = Changeset.opAssembler();
|
||||
|
||||
// create deleted attribs
|
||||
const authorAttrib = apool.putAttrib(['author', author || '']);
|
||||
const deletedAttrib = apool.putAttrib(['removed', true]);
|
||||
const attribs = `*${Changeset.numToString(authorAttrib)}*${Changeset.numToString(deletedAttrib)}`;
|
||||
|
||||
for (const operator of Changeset.deserializeOps(unpacked.ops)) {
|
||||
if (operator.opcode === '-') {
|
||||
// this is a delete operator, extend it with the author
|
||||
operator.attribs = attribs;
|
||||
} else if (operator.opcode === '=' && operator.attribs) {
|
||||
// this is operator changes only attributes, let's mark which author did that
|
||||
operator.attribs += `*${Changeset.numToString(authorAttrib)}`;
|
||||
const serializedOps = Changeset.serializeOps((function* () {
|
||||
for (const operator of Changeset.deserializeOps(unpacked.ops)) {
|
||||
if (operator.opcode === '-') {
|
||||
// this is a delete operator, extend it with the author
|
||||
operator.attribs = attribs;
|
||||
} else if (operator.opcode === '=' && operator.attribs) {
|
||||
// this is operator changes only attributes, let's mark which author did that
|
||||
operator.attribs += `*${Changeset.numToString(authorAttrib)}`;
|
||||
}
|
||||
yield operator;
|
||||
}
|
||||
|
||||
// append the new operator to our assembler
|
||||
assem.append(operator);
|
||||
}
|
||||
})());
|
||||
|
||||
// return the modified changeset
|
||||
return Changeset.pack(unpacked.oldLen, unpacked.newLen, assem.toString(), unpacked.charBank);
|
||||
return Changeset.pack(unpacked.oldLen, unpacked.newLen, serializedOps, unpacked.charBank);
|
||||
};
|
||||
|
||||
// this method is 80% like Changeset.inverse. I just changed so instead of reverting,
|
||||
|
@ -264,7 +262,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
|
|||
let curLineNextOp = new Changeset.Op('+');
|
||||
|
||||
const unpacked = Changeset.unpack(cs);
|
||||
const builder = Changeset.builder(unpacked.newLen);
|
||||
const builder = new Changeset.Builder(unpacked.newLen);
|
||||
|
||||
const consumeAttribRuns = (numChars, func /* (len, attribs, endsLine)*/) => {
|
||||
if (!curLineOps || curLineOpsLine !== curLine) {
|
||||
|
@ -330,21 +328,21 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
|
|||
|
||||
const nextText = (numChars) => {
|
||||
let len = 0;
|
||||
const assem = Changeset.stringAssembler();
|
||||
let assem = '';
|
||||
const firstString = linesGet(curLine).substring(curChar);
|
||||
len += firstString.length;
|
||||
assem.append(firstString);
|
||||
assem += firstString;
|
||||
|
||||
let lineNum = curLine + 1;
|
||||
|
||||
while (len < numChars) {
|
||||
const nextString = linesGet(lineNum);
|
||||
len += nextString.length;
|
||||
assem.append(nextString);
|
||||
assem += nextString;
|
||||
lineNum++;
|
||||
}
|
||||
|
||||
return assem.toString().substring(0, numChars);
|
||||
return assem.substring(0, numChars);
|
||||
};
|
||||
|
||||
const cachedStrFunc = (func) => {
|
||||
|
@ -440,7 +438,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
|
|||
}
|
||||
}
|
||||
|
||||
return Changeset.checkRep(builder.toString());
|
||||
return builder.build().validate().toString();
|
||||
};
|
||||
|
||||
// export the constructor
|
||||
|
|
|
@ -125,7 +125,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
|||
* @param attribs an array of attributes
|
||||
*/
|
||||
_setAttributesOnRangeByLine(row, startCol, endCol, attribs) {
|
||||
const builder = Changeset.builder(this.rep.lines.totalWidth());
|
||||
const builder = new Changeset.Builder(this.rep.lines.totalWidth());
|
||||
ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [row, startCol]);
|
||||
ChangesetUtils.buildKeepRange(
|
||||
this.rep, builder, [row, startCol], [row, endCol], attribs, this.rep.apool);
|
||||
|
@ -285,7 +285,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
|||
*/
|
||||
setAttributeOnLine(lineNum, attributeName, attributeValue) {
|
||||
let loc = [0, 0];
|
||||
const builder = Changeset.builder(this.rep.lines.totalWidth());
|
||||
const builder = new Changeset.Builder(this.rep.lines.totalWidth());
|
||||
const hasMarker = this.lineHasMarker(lineNum);
|
||||
|
||||
ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0]));
|
||||
|
@ -314,7 +314,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
|||
* @param attributeValue if given only attributes with equal value will be removed
|
||||
*/
|
||||
removeAttributeOnLine(lineNum, attributeName, attributeValue) {
|
||||
const builder = Changeset.builder(this.rep.lines.totalWidth());
|
||||
const builder = new Changeset.Builder(this.rep.lines.totalWidth());
|
||||
const hasMarker = this.lineHasMarker(lineNum);
|
||||
let found = false;
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -170,7 +170,7 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
// CCCCCCCCCCCCCCCCCCCC\n
|
||||
// CCCC\n
|
||||
// end[0]: <CCC end[1] CCC>-------\n
|
||||
const builder = Changeset.builder(rep.lines.totalWidth());
|
||||
const builder = new Changeset.Builder(rep.lines.totalWidth());
|
||||
ChangesetUtils.buildKeepToStartOfRange(rep, builder, start);
|
||||
ChangesetUtils.buildRemoveRange(rep, builder, start, end);
|
||||
builder.insert(newText, [
|
||||
|
@ -522,18 +522,24 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
const numLines = rep.lines.length();
|
||||
const upToLastLine = rep.lines.offsetOfIndex(numLines - 1);
|
||||
const lastLineLength = rep.lines.atIndex(numLines - 1).text.length;
|
||||
const assem = Changeset.smartOpAssembler();
|
||||
const o = new Changeset.Op('-');
|
||||
o.chars = upToLastLine;
|
||||
o.lines = numLines - 1;
|
||||
assem.append(o);
|
||||
o.chars = lastLineLength;
|
||||
o.lines = 0;
|
||||
assem.append(o);
|
||||
for (const op of Changeset.opsFromAText(atext)) assem.append(op);
|
||||
const newLen = oldLen + assem.getLengthChange();
|
||||
const changeset = Changeset.checkRep(
|
||||
Changeset.pack(oldLen, newLen, assem.toString(), atext.text.slice(0, -1)));
|
||||
const ops = (function* () {
|
||||
const op1 = new Changeset.Op('-');
|
||||
op1.chars = upToLastLine;
|
||||
op1.lines = numLines - 1;
|
||||
yield op1;
|
||||
const op2 = new Changeset.Op('-');
|
||||
op2.chars = lastLineLength;
|
||||
op2.lines = 0;
|
||||
yield op2;
|
||||
yield* Changeset.opsFromAText(atext);
|
||||
})();
|
||||
let lengthChange;
|
||||
const serializedOps = Changeset.serializeOps((function* () {
|
||||
lengthChange = yield* Changeset.canonicalizeOps(ops, false);
|
||||
})());
|
||||
const newLen = oldLen + lengthChange;
|
||||
const changeset = Changeset.pack(oldLen, newLen, serializedOps, atext.text.slice(0, -1));
|
||||
Changeset.unpack(changeset).validate();
|
||||
performDocumentApplyChangeset(changeset);
|
||||
|
||||
performSelectionChange(
|
||||
|
@ -1266,7 +1272,7 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
if (shouldIndent && /[[(:{]\s*$/.exec(prevLineText)) {
|
||||
theIndent += THE_TAB;
|
||||
}
|
||||
const cs = Changeset.builder(rep.lines.totalWidth()).keep(
|
||||
const cs = new Changeset.Builder(rep.lines.totalWidth()).keep(
|
||||
rep.lines.offsetOfIndex(lineNum), lineNum).insert(
|
||||
theIndent, [
|
||||
['author', thisAuthor],
|
||||
|
@ -1441,11 +1447,10 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
};
|
||||
|
||||
const doRepApplyChangeset = (changes, insertsAfterSelection) => {
|
||||
Changeset.checkRep(changes);
|
||||
const cs = Changeset.unpack(changes).validate();
|
||||
|
||||
if (Changeset.oldLen(changes) !== rep.alltext.length) {
|
||||
const errMsg = `${Changeset.oldLen(changes)}/${rep.alltext.length}`;
|
||||
throw new Error(`doRepApplyChangeset length mismatch: ${errMsg}`);
|
||||
if (cs.oldLen !== rep.alltext.length) {
|
||||
throw new Error(`doRepApplyChangeset length mismatch: ${cs.oldLen}/${rep.alltext.length}`);
|
||||
}
|
||||
|
||||
const editEvent = currentCallStack.editEvent;
|
||||
|
@ -1740,7 +1745,7 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
const spliceStartLineStart = rep.lines.offsetOfIndex(spliceStartLine);
|
||||
|
||||
const startBuilder = () => {
|
||||
const builder = Changeset.builder(oldLen);
|
||||
const builder = new Changeset.Builder(oldLen);
|
||||
builder.keep(spliceStartLineStart, spliceStartLine);
|
||||
builder.keep(spliceStart - spliceStartLineStart);
|
||||
return builder;
|
||||
|
@ -2291,7 +2296,7 @@ function Ace2Inner(editorInfo, cssManagers) {
|
|||
|
||||
// 3-renumber every list item of the same level from the beginning, level 1
|
||||
// IMPORTANT: never skip a level because there imbrication may be arbitrary
|
||||
const builder = Changeset.builder(rep.lines.totalWidth());
|
||||
const builder = new Changeset.Builder(rep.lines.totalWidth());
|
||||
let loc = [0, 0];
|
||||
const applyNumberList = (line, level) => {
|
||||
// init
|
||||
|
|
|
@ -143,22 +143,22 @@ const makeChangesetTracker = (scheduler, apool, aceCallbacksProvider) => {
|
|||
// Sanitize authorship: Replace all author attributes with this user's author ID in case the
|
||||
// text was copied from another author.
|
||||
const cs = Changeset.unpack(userChangeset);
|
||||
const assem = Changeset.mergingOpAssembler();
|
||||
|
||||
for (const op of Changeset.deserializeOps(cs.ops)) {
|
||||
if (op.opcode === '+') {
|
||||
const attribs = AttributeMap.fromString(op.attribs, apool);
|
||||
const oldAuthorId = attribs.get('author');
|
||||
if (oldAuthorId != null && oldAuthorId !== authorId) {
|
||||
attribs.set('author', authorId);
|
||||
op.attribs = attribs.toString();
|
||||
const ops = (function* () {
|
||||
for (const op of Changeset.deserializeOps(cs.ops)) {
|
||||
if (op.opcode === '+') {
|
||||
const attribs = AttributeMap.fromString(op.attribs, apool);
|
||||
const oldAuthorId = attribs.get('author');
|
||||
if (oldAuthorId != null && oldAuthorId !== authorId) {
|
||||
attribs.set('author', authorId);
|
||||
op.attribs = attribs.toString();
|
||||
}
|
||||
}
|
||||
yield op;
|
||||
}
|
||||
assem.append(op);
|
||||
}
|
||||
assem.endDocument();
|
||||
userChangeset = Changeset.pack(cs.oldLen, cs.newLen, assem.toString(), cs.charBank);
|
||||
Changeset.checkRep(userChangeset);
|
||||
})();
|
||||
const serializedOps = Changeset.serializeOps(Changeset.squashOps(ops, true));
|
||||
userChangeset = Changeset.pack(cs.oldLen, cs.newLen, serializedOps, cs.charBank);
|
||||
Changeset.unpack(userChangeset).validate();
|
||||
|
||||
if (Changeset.isIdentity(userChangeset)) toSubmit = null;
|
||||
else toSubmit = userChangeset;
|
||||
|
@ -167,7 +167,7 @@ const makeChangesetTracker = (scheduler, apool, aceCallbacksProvider) => {
|
|||
let cs = null;
|
||||
if (toSubmit) {
|
||||
submittedChangeset = toSubmit;
|
||||
userChangeset = Changeset.identity(Changeset.newLen(toSubmit));
|
||||
userChangeset = Changeset.identity(Changeset.unpack(toSubmit).newLen);
|
||||
|
||||
cs = toSubmit;
|
||||
}
|
||||
|
|
|
@ -82,31 +82,30 @@ const makeContentCollector = (collectStyles, abrowser, apool, className2Author)
|
|||
const lines = (() => {
|
||||
const textArray = [];
|
||||
const attribsArray = [];
|
||||
let attribsBuilder = null;
|
||||
const op = new Changeset.Op('+');
|
||||
let ops = null;
|
||||
const self = {
|
||||
length: () => textArray.length,
|
||||
atColumnZero: () => textArray[textArray.length - 1] === '',
|
||||
startNew: () => {
|
||||
textArray.push('');
|
||||
self.flush(true);
|
||||
attribsBuilder = Changeset.smartOpAssembler();
|
||||
ops = [];
|
||||
},
|
||||
textOfLine: (i) => textArray[i],
|
||||
appendText: (txt, attrString = '') => {
|
||||
textArray[textArray.length - 1] += txt;
|
||||
const op = new Changeset.Op('+');
|
||||
op.attribs = attrString;
|
||||
op.chars = txt.length;
|
||||
attribsBuilder.append(op);
|
||||
ops.push(op);
|
||||
},
|
||||
textLines: () => textArray.slice(),
|
||||
attribLines: () => attribsArray,
|
||||
// call flush only when you're done
|
||||
flush: (withNewline) => {
|
||||
if (attribsBuilder) {
|
||||
attribsArray.push(attribsBuilder.toString());
|
||||
attribsBuilder = null;
|
||||
}
|
||||
if (ops == null) return;
|
||||
attribsArray.push(Changeset.serializeOps(Changeset.canonicalizeOps(ops, false)));
|
||||
ops = null;
|
||||
},
|
||||
};
|
||||
self.startNew();
|
||||
|
|
|
@ -20,29 +20,19 @@ const poolOrArray = (attribs) => {
|
|||
exports.poolOrArray = poolOrArray;
|
||||
|
||||
const randomInlineString = (len) => {
|
||||
const assem = Changeset.stringAssembler();
|
||||
for (let i = 0; i < len; i++) {
|
||||
assem.append(String.fromCharCode(randInt(26) + 97));
|
||||
}
|
||||
return assem.toString();
|
||||
let assem = '';
|
||||
for (let i = 0; i < len; i++) assem += String.fromCharCode(randInt(26) + 97);
|
||||
return assem;
|
||||
};
|
||||
|
||||
const randomMultiline = (approxMaxLines, approxMaxCols) => {
|
||||
const numParts = randInt(approxMaxLines * 2) + 1;
|
||||
const txt = Changeset.stringAssembler();
|
||||
txt.append(randInt(2) ? '\n' : '');
|
||||
let txt = '';
|
||||
txt += randInt(2) ? '\n' : '';
|
||||
for (let i = 0; i < numParts; i++) {
|
||||
if ((i % 2) === 0) {
|
||||
if (randInt(10)) {
|
||||
txt.append(randomInlineString(randInt(approxMaxCols) + 1));
|
||||
} else {
|
||||
txt.append('\n');
|
||||
}
|
||||
} else {
|
||||
txt.append('\n');
|
||||
}
|
||||
txt += i % 2 === 0 && randInt(10) ? randomInlineString(randInt(approxMaxCols) + 1) : '\n';
|
||||
}
|
||||
return txt.toString();
|
||||
return txt;
|
||||
};
|
||||
exports.randomMultiline = randomMultiline;
|
||||
|
||||
|
@ -165,29 +155,25 @@ const randomTwoPropAttribs = (opcode) => {
|
|||
};
|
||||
|
||||
const randomTestChangeset = (origText, withAttribs) => {
|
||||
const charBank = Changeset.stringAssembler();
|
||||
let charBank = '';
|
||||
let textLeft = origText; // always keep final newline
|
||||
const outTextAssem = Changeset.stringAssembler();
|
||||
const opAssem = Changeset.smartOpAssembler();
|
||||
let outTextAssem = '';
|
||||
const ops = [];
|
||||
const oldLen = origText.length;
|
||||
|
||||
const nextOp = new Changeset.Op();
|
||||
|
||||
const appendMultilineOp = (opcode, txt) => {
|
||||
nextOp.opcode = opcode;
|
||||
if (withAttribs) {
|
||||
nextOp.attribs = randomTwoPropAttribs(opcode);
|
||||
}
|
||||
const attribs = withAttribs ? randomTwoPropAttribs(opcode) : '';
|
||||
txt.replace(/\n|[^\n]+/g, (t) => {
|
||||
const nextOp = new Changeset.Op(opcode);
|
||||
nextOp.attribs = attribs;
|
||||
if (t === '\n') {
|
||||
nextOp.chars = 1;
|
||||
nextOp.lines = 1;
|
||||
opAssem.append(nextOp);
|
||||
} else {
|
||||
nextOp.chars = t.length;
|
||||
nextOp.lines = 0;
|
||||
opAssem.append(nextOp);
|
||||
}
|
||||
ops.push(nextOp);
|
||||
return '';
|
||||
});
|
||||
};
|
||||
|
@ -196,13 +182,13 @@ const randomTestChangeset = (origText, withAttribs) => {
|
|||
const o = randomStringOperation(textLeft.length);
|
||||
if (o.insert) {
|
||||
const txt = o.insert;
|
||||
charBank.append(txt);
|
||||
outTextAssem.append(txt);
|
||||
charBank += txt;
|
||||
outTextAssem += txt;
|
||||
appendMultilineOp('+', txt);
|
||||
} else if (o.skip) {
|
||||
const txt = textLeft.substring(0, o.skip);
|
||||
textLeft = textLeft.substring(o.skip);
|
||||
outTextAssem.append(txt);
|
||||
outTextAssem += txt;
|
||||
appendMultilineOp('=', txt);
|
||||
} else if (o.remove) {
|
||||
const txt = textLeft.substring(0, o.remove);
|
||||
|
@ -213,10 +199,10 @@ const randomTestChangeset = (origText, withAttribs) => {
|
|||
|
||||
while (textLeft.length > 1) doOp();
|
||||
for (let i = 0; i < 5; i++) doOp(); // do some more (only insertions will happen)
|
||||
const outText = `${outTextAssem.toString()}\n`;
|
||||
opAssem.endDocument();
|
||||
const cs = Changeset.pack(oldLen, outText.length, opAssem.toString(), charBank.toString());
|
||||
Changeset.checkRep(cs);
|
||||
const outText = `${outTextAssem}\n`;
|
||||
const serializedOps = Changeset.serializeOps(Changeset.canonicalizeOps(ops, true));
|
||||
const cs = Changeset.pack(oldLen, outText.length, serializedOps, charBank);
|
||||
Changeset.unpack(cs).validate();
|
||||
return [cs, outText];
|
||||
};
|
||||
exports.randomTestChangeset = randomTestChangeset;
|
||||
|
|
|
@ -3,27 +3,23 @@
|
|||
const Changeset = require('../../../static/js/Changeset');
|
||||
|
||||
describe('easysync-assembler', function () {
|
||||
it('opAssembler', async function () {
|
||||
it('deserialize and serialize', async function () {
|
||||
const x = '-c*3*4+6|3=az*asdf0*1*2*3+1=1-1+1*0+1=1-1+1|c=c-1';
|
||||
const assem = Changeset.opAssembler();
|
||||
for (const op of Changeset.deserializeOps(x)) assem.append(op);
|
||||
expect(assem.toString()).to.equal(x);
|
||||
expect(Changeset.serializeOps(Changeset.deserializeOps(x))).to.equal(x);
|
||||
});
|
||||
|
||||
it('smartOpAssembler', async function () {
|
||||
it('canonicalizeOps', async function () {
|
||||
const x = '-c*3*4+6|3=az*asdf0*1*2*3+1=1-1+1*0+1=1-1+1|c=c-1';
|
||||
const assem = Changeset.smartOpAssembler();
|
||||
for (const op of Changeset.deserializeOps(x)) assem.append(op);
|
||||
assem.endDocument();
|
||||
expect(assem.toString()).to.equal(x);
|
||||
expect(Changeset.serializeOps(Changeset.canonicalizeOps(Changeset.deserializeOps(x), true)))
|
||||
.to.equal(x);
|
||||
});
|
||||
|
||||
describe('append atext to assembler', function () {
|
||||
const testAppendATextToAssembler = (testId, atext, correctOps) => {
|
||||
it(`testAppendATextToAssembler#${testId}`, async function () {
|
||||
const assem = Changeset.smartOpAssembler();
|
||||
for (const op of Changeset.opsFromAText(atext)) assem.append(op);
|
||||
expect(assem.toString()).to.equal(correctOps);
|
||||
const serializedOps =
|
||||
Changeset.serializeOps(Changeset.canonicalizeOps(Changeset.opsFromAText(atext), false));
|
||||
expect(serializedOps).to.equal(correctOps);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -24,10 +24,14 @@ describe('easysync-compose', function () {
|
|||
const change3 = x3[0];
|
||||
const text3 = x3[1];
|
||||
|
||||
const change12 = Changeset.checkRep(Changeset.compose(change1, change2, p));
|
||||
const change23 = Changeset.checkRep(Changeset.compose(change2, change3, p));
|
||||
const change123 = Changeset.checkRep(Changeset.compose(change12, change3, p));
|
||||
const change123a = Changeset.checkRep(Changeset.compose(change1, change23, p));
|
||||
const change12 = Changeset.compose(change1, change2, p);
|
||||
Changeset.unpack(change12).validate();
|
||||
const change23 = Changeset.compose(change2, change3, p);
|
||||
Changeset.unpack(change23).validate();
|
||||
const change123 = Changeset.compose(change12, change3, p);
|
||||
Changeset.unpack(change123).validate();
|
||||
const change123a = Changeset.compose(change1, change23, p);
|
||||
Changeset.unpack(change123a).validate();
|
||||
expect(change123a).to.equal(change123);
|
||||
|
||||
expect(Changeset.applyToText(change12, startText)).to.equal(text2);
|
||||
|
@ -44,9 +48,12 @@ describe('easysync-compose', function () {
|
|||
const p = new AttributePool();
|
||||
p.putAttrib(['bold', '']);
|
||||
p.putAttrib(['bold', 'true']);
|
||||
const cs1 = Changeset.checkRep('Z:2>1*1+1*1=1$x');
|
||||
const cs2 = Changeset.checkRep('Z:3>0*0|1=3$');
|
||||
const cs12 = Changeset.checkRep(Changeset.compose(cs1, cs2, p));
|
||||
const cs1 = 'Z:2>1*1+1*1=1$x';
|
||||
Changeset.unpack(cs1).validate();
|
||||
const cs2 = 'Z:3>0*0|1=3$';
|
||||
Changeset.unpack(cs2).validate();
|
||||
const cs12 = Changeset.compose(cs1, cs2, p);
|
||||
Changeset.unpack(cs12).validate();
|
||||
expect(cs12).to.equal('Z:2>1+1*0|1=2$x');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,11 +15,15 @@ describe('easysync-follow', function () {
|
|||
const cs1 = randomTestChangeset(startText)[0];
|
||||
const cs2 = randomTestChangeset(startText)[0];
|
||||
|
||||
const afb = Changeset.checkRep(Changeset.follow(cs1, cs2, false, p));
|
||||
const bfa = Changeset.checkRep(Changeset.follow(cs2, cs1, true, p));
|
||||
const afb = Changeset.follow(cs1, cs2, false, p);
|
||||
Changeset.unpack(afb).validate();
|
||||
const bfa = Changeset.follow(cs2, cs1, true, p);
|
||||
Changeset.unpack(bfa).validate();
|
||||
|
||||
const merge1 = Changeset.checkRep(Changeset.compose(cs1, afb));
|
||||
const merge2 = Changeset.checkRep(Changeset.compose(cs2, bfa));
|
||||
const merge1 = Changeset.compose(cs1, afb);
|
||||
Changeset.unpack(merge1).validate();
|
||||
const merge2 = Changeset.compose(cs2, bfa);
|
||||
Changeset.unpack(merge2).validate();
|
||||
|
||||
expect(merge2).to.equal(merge1);
|
||||
});
|
||||
|
@ -60,7 +64,7 @@ describe('easysync-follow', function () {
|
|||
describe('chracterRangeFollow', function () {
|
||||
const testCharacterRangeFollow = (testId, cs, oldRange, insertionsAfter, correctNewRange) => {
|
||||
it(`testCharacterRangeFollow#${testId}`, async function () {
|
||||
cs = Changeset.checkRep(cs);
|
||||
Changeset.unpack(cs).validate();
|
||||
expect(Changeset.characterRangeFollow(cs, oldRange[0], oldRange[1], insertionsAfter))
|
||||
.to.eql(correctNewRange);
|
||||
});
|
||||
|
|
|
@ -41,7 +41,8 @@ describe('easysync-inverseRandom', function () {
|
|||
const testInverse = (testId, cs, lines, alines, pool, correctOutput) => {
|
||||
it(`testInverse#${testId}`, async function () {
|
||||
pool = poolOrArray(pool);
|
||||
const str = Changeset.inverse(Changeset.checkRep(cs), lines, alines, pool);
|
||||
Changeset.unpack(cs).validate();
|
||||
const str = Changeset.inverse(cs, lines, alines, pool);
|
||||
expect(str).to.equal(correctOutput);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -15,37 +15,36 @@ describe('easysync-mutations', function () {
|
|||
};
|
||||
|
||||
const mutationsToChangeset = (oldLen, arrayOfArrays) => {
|
||||
const assem = Changeset.smartOpAssembler();
|
||||
const op = new Changeset.Op();
|
||||
const bank = Changeset.stringAssembler();
|
||||
let bank = '';
|
||||
let oldPos = 0;
|
||||
let newLen = 0;
|
||||
arrayOfArrays.forEach((a) => {
|
||||
if (a[0] === 'skip') {
|
||||
op.opcode = '=';
|
||||
op.chars = a[1];
|
||||
op.lines = (a[2] || 0);
|
||||
assem.append(op);
|
||||
oldPos += op.chars;
|
||||
newLen += op.chars;
|
||||
} else if (a[0] === 'remove') {
|
||||
op.opcode = '-';
|
||||
op.chars = a[1];
|
||||
op.lines = (a[2] || 0);
|
||||
assem.append(op);
|
||||
oldPos += op.chars;
|
||||
} else if (a[0] === 'insert') {
|
||||
op.opcode = '+';
|
||||
bank.append(a[1]);
|
||||
op.chars = a[1].length;
|
||||
op.lines = (a[2] || 0);
|
||||
assem.append(op);
|
||||
newLen += op.chars;
|
||||
const ops = (function* () {
|
||||
for (const a of arrayOfArrays) {
|
||||
const op = new Changeset.Op();
|
||||
if (a[0] === 'skip') {
|
||||
op.opcode = '=';
|
||||
op.chars = a[1];
|
||||
op.lines = (a[2] || 0);
|
||||
oldPos += op.chars;
|
||||
newLen += op.chars;
|
||||
} else if (a[0] === 'remove') {
|
||||
op.opcode = '-';
|
||||
op.chars = a[1];
|
||||
op.lines = (a[2] || 0);
|
||||
oldPos += op.chars;
|
||||
} else if (a[0] === 'insert') {
|
||||
op.opcode = '+';
|
||||
bank += a[1];
|
||||
op.chars = a[1].length;
|
||||
op.lines = (a[2] || 0);
|
||||
newLen += op.chars;
|
||||
}
|
||||
yield op;
|
||||
}
|
||||
});
|
||||
})();
|
||||
const serializedOps = Changeset.serializeOps(Changeset.canonicalizeOps(ops, true));
|
||||
newLen += oldLen - oldPos;
|
||||
assem.endDocument();
|
||||
return Changeset.pack(oldLen, newLen, assem.toString(), bank.toString());
|
||||
return Changeset.pack(oldLen, newLen, serializedOps, bank);
|
||||
};
|
||||
|
||||
const runMutationTest = (testId, origLines, muts, correct) => {
|
||||
|
@ -205,7 +204,8 @@ describe('easysync-mutations', function () {
|
|||
it(`runMutateAttributionTest#${testId}`, async function () {
|
||||
const p = poolOrArray(attribs);
|
||||
const alines2 = Array.prototype.slice.call(alines);
|
||||
Changeset.mutateAttributionLines(Changeset.checkRep(cs), alines2, p);
|
||||
Changeset.unpack(cs).validate();
|
||||
Changeset.mutateAttributionLines(cs, alines2, p);
|
||||
expect(alines2).to.eql(outCorrect);
|
||||
|
||||
const removeQuestionMarks = (a) => a.replace(/\?/g, '');
|
||||
|
|
|
@ -78,7 +78,8 @@ describe('easysync-other', function () {
|
|||
});
|
||||
|
||||
it('testToSplices', async function () {
|
||||
const cs = Changeset.checkRep('Z:z>9*0=1=4-3+9=1|1-4-4+1*0+a$123456789abcdefghijk');
|
||||
const cs = 'Z:z>9*0=1=4-3+9=1|1-4-4+1*0+a$123456789abcdefghijk';
|
||||
Changeset.unpack(cs).validate();
|
||||
const correctSplices = [
|
||||
[5, 8, '123456789'],
|
||||
[9, 17, 'abcdefghijk'],
|
||||
|
@ -112,7 +113,8 @@ describe('easysync-other', function () {
|
|||
const runApplyToAttributionTest = (testId, attribs, cs, inAttr, outCorrect) => {
|
||||
it(`applyToAttribution#${testId}`, async function () {
|
||||
const p = poolOrArray(attribs);
|
||||
const result = Changeset.applyToAttribution(Changeset.checkRep(cs), inAttr, p);
|
||||
Changeset.unpack(cs).validate();
|
||||
const result = Changeset.applyToAttribution(cs, inAttr, p);
|
||||
expect(result).to.equal(outCorrect);
|
||||
});
|
||||
};
|
||||
|
@ -129,16 +131,17 @@ describe('easysync-other', function () {
|
|||
describe('split/join attribution lines', function () {
|
||||
const testSplitJoinAttributionLines = (randomSeed) => {
|
||||
const stringToOps = (str) => {
|
||||
const assem = Changeset.mergingOpAssembler();
|
||||
const o = new Changeset.Op('+');
|
||||
o.chars = 1;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const c = str.charAt(i);
|
||||
o.lines = (c === '\n' ? 1 : 0);
|
||||
o.attribs = (c === 'a' || c === 'b' ? `*${c}` : '');
|
||||
assem.append(o);
|
||||
}
|
||||
return assem.toString();
|
||||
const ops = (function* () {
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const c = str.charAt(i);
|
||||
const o = new Changeset.Op('+');
|
||||
o.chars = 1;
|
||||
o.lines = (c === '\n' ? 1 : 0);
|
||||
o.attribs = (c === 'a' || c === 'b' ? `*${c}` : '');
|
||||
yield o;
|
||||
}
|
||||
})();
|
||||
return Changeset.serializeOps(Changeset.squashOps(ops, false));
|
||||
};
|
||||
|
||||
it(`testSplitJoinAttributionLines#${randomSeed}`, async function () {
|
||||
|
|
Loading…
Reference in New Issue