mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-22 16:36:15 -04:00
Continued writing
This commit is contained in:
parent
d1ffd5d02f
commit
cef2af15b9
18 changed files with 1455 additions and 1230 deletions
132
src/static/js/AttributionLinesMutator.ts
Normal file
132
src/static/js/AttributionLinesMutator.ts
Normal file
|
@ -0,0 +1,132 @@
|
|||
import {TextLinesMutator} from "./TextLinesMutator";
|
||||
import AttributePool from "./AttributePool";
|
||||
import {assert, copyOp, deserializeOps, slicerZipperFunc, unpack} from "./Changeset";
|
||||
import Op from "./Op";
|
||||
import {MergingOpAssembler} from "./MergingOpAssembler";
|
||||
|
||||
/**
|
||||
* Applies a changeset to an array of attribute lines.
|
||||
*
|
||||
* @param {string} cs - The encoded changeset.
|
||||
* @param {Array<string>} lines - Attribute lines. Modified in place.
|
||||
* @param {AttributePool.ts} pool - Attribute pool.
|
||||
*/
|
||||
export class AttributionLinesMutator {
|
||||
private unpacked
|
||||
private csOps: Generator<Op>|Op
|
||||
private csOpsNext: IteratorResult<Op>
|
||||
private csBank: string
|
||||
private csBankIndex: number
|
||||
private mut: TextLinesMutator
|
||||
private lineOps: Generator<Op>|null
|
||||
private lineOpsNext: IteratorResult<Op>|null
|
||||
private lineAssem: null|MergingOpAssembler
|
||||
private attOp: Op
|
||||
private csOp: Op
|
||||
constructor(cs: string, lines:string[], pool: AttributePool) {
|
||||
this.unpacked = unpack(cs);
|
||||
this.csOps = deserializeOps(this.unpacked.ops);
|
||||
this.csOpsNext = this.csOps.next();
|
||||
this.csBank = this.unpacked.charBank;
|
||||
this.csBankIndex = 0;
|
||||
// treat the attribution lines as text lines, mutating a line at a time
|
||||
this.mut = new TextLinesMutator(lines);
|
||||
/**
|
||||
* The Ops in the current line from `lines`.
|
||||
*
|
||||
* @type {?Generator<Op>}
|
||||
*/
|
||||
this.lineOps = null;
|
||||
this.lineOpsNext = null;
|
||||
this.lineAssem = null
|
||||
this.csOp = new Op()
|
||||
this.attOp = new Op()
|
||||
while (this.csOp.opcode || !this.csOpsNext.done || this.attOp.opcode || this.isNextMutOp()) {
|
||||
if (!this.csOp.opcode && !this.csOpsNext.done) {
|
||||
// coOp done, but more ops in cs.
|
||||
this.csOp = this.csOpsNext.value;
|
||||
this.csOpsNext = this.csOps.next();
|
||||
}
|
||||
if (!this.csOp.opcode && !this.attOp.opcode && !this.lineAssem && !this.lineOpsHasNext()) {
|
||||
break; // done
|
||||
} else if (this.csOp.opcode === '=' && this.csOp.lines > 0 && !this.csOp.attribs && !this.attOp.opcode &&
|
||||
!this.lineAssem && !this.lineOpsHasNext()) {
|
||||
// Skip multiple lines without attributes; this is what makes small changes not order of the
|
||||
// document size.
|
||||
this.mut.skipLines(this.csOp.lines);
|
||||
this.csOp.opcode = '';
|
||||
} else if (this.csOp.opcode === '+') {
|
||||
const opOut = copyOp(this.csOp);
|
||||
if (this.csOp.lines > 1) {
|
||||
// Copy the first line from `csOp` to `opOut`.
|
||||
const firstLineLen = this.csBank.indexOf('\n', this.csBankIndex) + 1 - this.csBankIndex;
|
||||
this.csOp.chars -= firstLineLen;
|
||||
this.csOp.lines--;
|
||||
opOut.lines = 1;
|
||||
opOut.chars = firstLineLen;
|
||||
} else {
|
||||
// Either one or no newlines in '+' `csOp`, copy to `opOut` and reset `csOp`.
|
||||
this.csOp.opcode = '';
|
||||
}
|
||||
this.outputMutOp(opOut);
|
||||
this.csBankIndex += opOut.chars;
|
||||
} else {
|
||||
if (!this.attOp.opcode && this.isNextMutOp()) {
|
||||
this.attOp = this.nextMutOp();
|
||||
}
|
||||
const opOut = slicerZipperFunc(this.attOp, this.csOp, pool);
|
||||
if (opOut.opcode) {
|
||||
this.outputMutOp(opOut);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(!this.lineAssem, `line assembler not finished:${cs}`);
|
||||
this.mut.close();
|
||||
}
|
||||
|
||||
lineOpsHasNext = () => this.lineOpsNext && !this.lineOpsNext.done;
|
||||
/**
|
||||
* Returns false if we are on the last attribute line in `lines` and there is no additional op in
|
||||
* that line.
|
||||
*
|
||||
* @returns {boolean} True if there are more ops to go through.
|
||||
*/
|
||||
isNextMutOp = () => this.lineOpsHasNext() || this.mut.hasMore();
|
||||
|
||||
|
||||
/**
|
||||
* @returns {Op} The next Op from `lineIter`. If there are no more Ops, `lineIter` is reset to
|
||||
* iterate over the next line, which is consumed from `mut`. If there are no more lines,
|
||||
* returns a null Op.
|
||||
*/
|
||||
nextMutOp = () => {
|
||||
if (!this.lineOpsHasNext() && this.mut.hasMore()) {
|
||||
// There are more attribute lines in `lines` to do AND either we just started so `lineIter` is
|
||||
// still null or there are no more ops in current `lineIter`.
|
||||
const line = this.mut.removeLines(1);
|
||||
this.lineOps = deserializeOps(line);
|
||||
this.lineOpsNext = this.lineOps.next();
|
||||
}
|
||||
if (!this.lineOpsHasNext()) return new Op(); // No more ops and no more lines.
|
||||
const op = this.lineOpsNext!.value;
|
||||
this.lineOpsNext = this.lineOps!.next();
|
||||
return op;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends an op to `lineAssem`. In case `lineAssem` includes one single newline, adds it to the
|
||||
* `lines` mutator.
|
||||
*/
|
||||
outputMutOp = (op: Op) => {
|
||||
if (!this.lineAssem) {
|
||||
this.lineAssem = new MergingOpAssembler()
|
||||
}
|
||||
this.lineAssem.append(op);
|
||||
if (op.lines <= 0) return;
|
||||
assert(op.lines === 1, `Can't have op.lines of ${op.lines} in attribution lines`);
|
||||
// ship it to the mut
|
||||
this.mut.insert(this.lineAssem.toString(), 1);
|
||||
this.lineAssem = null;
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue