summaryrefslogtreecommitdiffstats
path: root/trunk/etherpad/src/etherpad/collab/ace
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/etherpad/src/etherpad/collab/ace')
-rw-r--r--trunk/etherpad/src/etherpad/collab/ace/domline.js210
-rw-r--r--trunk/etherpad/src/etherpad/collab/ace/easysync1.js923
-rw-r--r--trunk/etherpad/src/etherpad/collab/ace/easysync2.js1968
-rw-r--r--trunk/etherpad/src/etherpad/collab/ace/easysync2_tests.js877
4 files changed, 0 insertions, 3978 deletions
diff --git a/trunk/etherpad/src/etherpad/collab/ace/domline.js b/trunk/etherpad/src/etherpad/collab/ace/domline.js
deleted file mode 100644
index de2e7d3..0000000
--- a/trunk/etherpad/src/etherpad/collab/ace/domline.js
+++ /dev/null
@@ -1,210 +0,0 @@
-// DO NOT EDIT THIS FILE, edit infrastructure/ace/www/domline.js
-
-/**
- * Copyright 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS-IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var domline = {};
-domline.noop = function() {};
-domline.identity = function(x) { return x; };
-
-domline.addToLineClass = function(lineClass, cls) {
- // an "empty span" at any point can be used to add classes to
- // the line, using line:className. otherwise, we ignore
- // the span.
- cls.replace(/\S+/g, function (c) {
- if (c.indexOf("line:") == 0) {
- // add class to line
- lineClass = (lineClass ? lineClass+' ' : '')+c.substring(5);
- }
- });
- return lineClass;
-}
-
-// if "document" is falsy we don't create a DOM node, just
-// an object with innerHTML and className
-domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument) {
- var result = { node: null,
- appendSpan: domline.noop,
- prepareForAdd: domline.noop,
- notifyAdded: domline.noop,
- clearSpans: domline.noop,
- finishUpdate: domline.noop,
- lineMarker: 0 };
-
- var browser = (optBrowser || {});
- var document = optDocument;
-
- if (document) {
- result.node = document.createElement("div");
- }
- else {
- result.node = {innerHTML: '', className: ''};
- }
-
- var html = [];
- var preHtml, postHtml;
- var curHTML = null;
- function processSpaces(s) {
- return domline.processSpaces(s, doesWrap);
- }
- var identity = domline.identity;
- var perTextNodeProcess = (doesWrap ? identity : processSpaces);
- var perHtmlLineProcess = (doesWrap ? processSpaces : identity);
- var lineClass = 'ace-line';
- result.appendSpan = function(txt, cls) {
- if (cls.indexOf('list') >= 0) {
- var listType = /(?:^| )list:(\S+)/.exec(cls);
- if (listType) {
- listType = listType[1];
- if (listType) {
- preHtml = '<ul class="list-'+listType+'"><li>';
- postHtml = '</li></ul>';
- }
- result.lineMarker += txt.length;
- return; // don't append any text
- }
- }
- var href = null;
- var simpleTags = null;
- if (cls.indexOf('url') >= 0) {
- cls = cls.replace(/(^| )url:(\S+)/g, function(x0, space, url) {
- href = url;
- return space+"url";
- });
- }
- if (cls.indexOf('tag') >= 0) {
- cls = cls.replace(/(^| )tag:(\S+)/g, function(x0, space, tag) {
- if (! simpleTags) simpleTags = [];
- simpleTags.push(tag.toLowerCase());
- return space+tag;
- });
- }
- if ((! txt) && cls) {
- lineClass = domline.addToLineClass(lineClass, cls);
- }
- else if (txt) {
- var extraOpenTags = "";
- var extraCloseTags = "";
- if (href) {
- extraOpenTags = extraOpenTags+'<a href="'+
- href.replace(/\"/g, '&quot;')+'">';
- extraCloseTags = '</a>'+extraCloseTags;
- }
- if (simpleTags) {
- simpleTags.sort();
- extraOpenTags = extraOpenTags+'<'+simpleTags.join('><')+'>';
- simpleTags.reverse();
- extraCloseTags = '</'+simpleTags.join('></')+'>'+extraCloseTags;
- }
- html.push('<span class="',cls||'','">',extraOpenTags,
- perTextNodeProcess(domline.escapeHTML(txt)),
- extraCloseTags,'</span>');
- }
- };
- result.clearSpans = function() {
- html = [];
- lineClass = ''; // non-null to cause update
- result.lineMarker = 0;
- };
- function writeHTML() {
- var newHTML = perHtmlLineProcess(html.join(''));
- if (! newHTML) {
- if ((! document) || (! optBrowser)) {
- newHTML += '&nbsp;';
- }
- else if (! browser.msie) {
- newHTML += '<br/>';
- }
- }
- if (nonEmpty) {
- newHTML = (preHtml||'')+newHTML+(postHtml||'');
- }
- html = preHtml = postHtml = null; // free memory
- if (newHTML !== curHTML) {
- curHTML = newHTML;
- result.node.innerHTML = curHTML;
- }
- if (lineClass !== null) result.node.className = lineClass;
- }
- result.prepareForAdd = writeHTML;
- result.finishUpdate = writeHTML;
- result.getInnerHTML = function() { return curHTML || ''; };
-
- return result;
-};
-
-domline.escapeHTML = function(s) {
- var re = /[&<>'"]/g; /']/; // stupid indentation thing
- if (! re.MAP) {
- // persisted across function calls!
- re.MAP = {
- '&': '&amp;',
- '<': '&lt;',
- '>': '&gt;',
- '"': '&#34;',
- "'": '&#39;'
- };
- }
- return s.replace(re, function(c) { return re.MAP[c]; });
-};
-
-domline.processSpaces = function(s, doesWrap) {
- if (s.indexOf("<") < 0 && ! doesWrap) {
- // short-cut
- return s.replace(/ /g, '&nbsp;');
- }
- var parts = [];
- s.replace(/<[^>]*>?| |[^ <]+/g, function(m) { parts.push(m); });
- if (doesWrap) {
- var endOfLine = true;
- var beforeSpace = false;
- // last space in a run is normal, others are nbsp,
- // end of line is nbsp
- for(var i=parts.length-1;i>=0;i--) {
- var p = parts[i];
- if (p == " ") {
- if (endOfLine || beforeSpace)
- parts[i] = '&nbsp;';
- endOfLine = false;
- beforeSpace = true;
- }
- else if (p.charAt(0) != "<") {
- endOfLine = false;
- beforeSpace = false;
- }
- }
- // beginning of line is nbsp
- for(var i=0;i<parts.length;i++) {
- var p = parts[i];
- if (p == " ") {
- parts[i] = '&nbsp;';
- break;
- }
- else if (p.charAt(0) != "<") {
- break;
- }
- }
- }
- else {
- for(var i=0;i<parts.length;i++) {
- var p = parts[i];
- if (p == " ") {
- parts[i] = '&nbsp;';
- }
- }
- }
- return parts.join('');
-};
diff --git a/trunk/etherpad/src/etherpad/collab/ace/easysync1.js b/trunk/etherpad/src/etherpad/collab/ace/easysync1.js
deleted file mode 100644
index 4f40aa0..0000000
--- a/trunk/etherpad/src/etherpad/collab/ace/easysync1.js
+++ /dev/null
@@ -1,923 +0,0 @@
-// DO NOT EDIT THIS FILE, edit infrastructure/ace/www/easy_sync.js
-
-/**
- * Copyright 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS-IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-function Changeset(arg) {
-
- var array;
- if ((typeof arg) == "string") {
- // constant
- array = [Changeset.MAGIC, 0, arg.length, 0, 0, arg];
- }
- else if ((typeof arg) == "number") {
- var n = Math.round(arg);
- // delete-all on n-length text (useful for making a "builder")
- array = [Changeset.MAGIC, n, 0, 0, 0, ""];
- }
- else if (! arg) {
- // identity on 0-length text
- array = [Changeset.MAGIC, 0, 0, 0, 0, ""];
- }
- else if (arg.isChangeset) {
- return arg;
- }
- else array = arg;
-
- array.isChangeset = true;
-
- // OOP style: attach generic methods to array object, hold no state in environment
-
- //function error(msg) { top.console.error(msg); top.console.trace(); }
- function error(msg) { var e = new Error(msg); e.easysync = true; throw e; }
- function assert(b, msg) { if (! b) error("Changeset: "+String(msg)); }
- function min(x, y) { return (x < y) ? x : y; }
- Changeset._assert = assert;
-
- array.isIdentity = function() {
- return this.length == 6 && this[1] == this[2] && this[3] == 0 &&
- this[4] == this[1] && this[5] == "";
- }
-
- array.eachStrip = function(func, thisObj) {
- // inside "func", the method receiver will be "this" by default,
- // or you can pass an object.
- for(var i=0;i<this.numStrips();i++) {
- var ptr = 3 + i*3;
- if (func.call(thisObj || this, this[ptr], this[ptr+1], this[ptr+2], i))
- return true;
- }
- return false;
- }
-
- array.numStrips = function() { return (this.length-3)/3; };
- array.oldLen = function() { return this[1]; };
- array.newLen = function() { return this[2]; };
-
- array.checkRep = function() {
- assert(this[0] == Changeset.MAGIC, "bad magic");
- assert(this[1] >= 0, "bad old text length");
- assert(this[2] >= 0, "bad new text length");
- assert((this.length % 3) == 0, "bad array length");
- assert(this.length >= 6, "must be at least one strip");
- var numStrips = this.numStrips();
- var oldLen = this[1];
- var newLen = this[2];
- // iterate over the "text strips"
- var actualNewLen = 0;
- this.eachStrip(function(startIndex, numTaken, newText, i) {
- var s = startIndex, t = numTaken, n = newText;
- var isFirst = (i == 0);
- var isLast = (i == numStrips-1);
- assert(t >= 0, "can't take negative number of chars");
- assert(isFirst || t > 0, "all strips but first must take");
- assert((t > 0) || (s == 0), "if first strip doesn't take, must have 0 startIndex");
- assert(s >= 0 && s + t <= oldLen, "bad index: "+this.toString());
- assert(t > 0 || n.length > 0 || (isFirst && isLast), "empty strip must be first and only");
- if (! isLast) {
- var s2 = this[3 + i*3 + 3]; // startIndex of following strip
- var gap = s2 - (s + t);
- assert(gap >= 0, "overlapping or out-of-order strips: "+this.toString());
- assert(gap > 0 || n.length > 0, "touching strips with no added text");
- }
- actualNewLen += t + n.length;
- });
- assert(newLen == actualNewLen, "calculated new text length doesn't match");
- }
-
- array.applyToText = function(text) {
- assert(text.length == this.oldLen(), "mismatched apply: "+text.length+" / "+this.oldLen());
- var buf = [];
- this.eachStrip(function (s, t, n) {
- buf.push(text.substr(s, t), n);
- });
- return buf.join('');
- }
-
- function _makeBuilder(oldLen, supportAuthors) {
- var C = Changeset(oldLen);
- if (supportAuthors) {
- _ensureAuthors(C);
- }
- return C.builder();
- }
-
- function _getNumInserted(C) {
- var numChars = 0;
- C.eachStrip(function(s,t,n) {
- numChars += n.length;
- });
- return numChars;
- }
-
- function _ensureAuthors(C) {
- if (! C.authors) {
- C.setAuthor();
- }
- return C;
- }
-
- array.setAuthor = function(author) {
- var C = this;
- // authors array has even length >= 2;
- // alternates [numChars1, author1, numChars2, author2];
- // all numChars > 0 unless there is exactly one, in which
- // case it can be == 0.
- C.authors = [_getNumInserted(C), author || ''];
- return C;
- }
-
- array.builder = function() {
- // normal pattern is Changeset(oldLength).builder().appendOldText(...). ...
- // builder methods mutate this!
- var C = this;
- // OOP style: state in environment
- var self;
- return self = {
- appendNewText: function(str, author) {
- C[C.length-1] += str;
- C[2] += str.length;
-
- if (C.authors) {
- var a = (author || '');
- var lastAuthorPtr = C.authors.length-1;
- var lastAuthorLengthPtr = C.authors.length-2;
- if ((!a) || a == C.authors[lastAuthorPtr]) {
- C.authors[lastAuthorLengthPtr] += str.length;
- }
- else if (0 == C.authors[lastAuthorLengthPtr]) {
- C.authors[lastAuthorLengthPtr] = str.length;
- C.authors[lastAuthorPtr] = (a || C.authors[lastAuthorPtr]);
- }
- else {
- C.authors.push(str.length, a);
- }
- }
-
- return self;
- },
- appendOldText: function(startIndex, numTaken) {
- if (numTaken == 0) return self;
- // properties of last strip...
- var s = C[C.length-3], t = C[C.length-2], n = C[C.length-1];
- if (t == 0 && n == "") {
- // must be empty changeset, one strip that doesn't take old chars or add new ones
- C[C.length-3] = startIndex;
- C[C.length-2] = numTaken;
- }
- else if (n == "" && (s+t == startIndex)) {
- C[C.length-2] += numTaken; // take more
- }
- else C.push(startIndex, numTaken, ""); // add a strip
- C[2] += numTaken;
- C.checkRep();
- return self;
- },
- toChangeset: function() { return C; }
- };
- }
-
- array.authorSlicer = function(outputBuilder) {
- return _makeAuthorSlicer(this, outputBuilder);
- }
-
- function _makeAuthorSlicer(changesetOrAuthorsIn, builderOut) {
- // "builderOut" only needs to support appendNewText
- var authors; // considered immutable
- if (changesetOrAuthorsIn.isChangeset) {
- authors = changesetOrAuthorsIn.authors;
- }
- else {
- authors = changesetOrAuthorsIn;
- }
-
- // OOP style: state in environment
- var authorPtr = 0;
- var charIndex = 0;
- var charWithinAuthor = 0; // 0 <= charWithinAuthor <= authors[authorPtr]; max value iff atEnd
- var atEnd = false;
- function curAuthor() { return authors[authorPtr+1]; }
- function curAuthorWidth() { return authors[authorPtr]; }
- function assertNotAtEnd() { assert(! atEnd, "_authorSlicer: can't move past end"); }
- function forwardInAuthor(numChars) {
- charWithinAuthor += numChars;
- charIndex += numChars;
- }
- function nextAuthor() {
- assertNotAtEnd();
- assert(charWithinAuthor == curAuthorWidth(), "_authorSlicer: not at author end");
- charWithinAuthor = 0;
- authorPtr += 2;
- if (authorPtr == authors.length) {
- atEnd = true;
- }
- }
-
- var self;
- return self = {
- skipChars: function(n) {
- assert(n >= 0, "_authorSlicer: can't skip negative n");
- if (n == 0) return;
- assertNotAtEnd();
-
- var leftToSkip = n;
- while (leftToSkip > 0) {
- var leftInAuthor = curAuthorWidth() - charWithinAuthor;
- if (leftToSkip >= leftInAuthor) {
- forwardInAuthor(leftInAuthor);
- leftToSkip -= leftInAuthor;
- nextAuthor();
- }
- else {
- forwardInAuthor(leftToSkip);
- leftToSkip = 0;
- }
- }
- },
- takeChars: function(n, text) {
- assert(n >= 0, "_authorSlicer: can't take negative n");
- if (n == 0) return;
- assertNotAtEnd();
- assert(n == text.length, "_authorSlicer: bad text length");
-
- var textLeft = text;
- var leftToTake = n;
- while (leftToTake > 0) {
- if (curAuthorWidth() > 0 && charWithinAuthor < curAuthorWidth()) {
- // at least one char to take from current author
- var leftInAuthor = (curAuthorWidth() - charWithinAuthor);
- assert(leftInAuthor > 0, "_authorSlicer: should have leftInAuthor > 0");
- var toTake = min(leftInAuthor, leftToTake);
- assert(toTake > 0, "_authorSlicer: should have toTake > 0");
- builderOut.appendNewText(textLeft.substring(0, toTake), curAuthor());
- forwardInAuthor(toTake);
- leftToTake -= toTake;
- textLeft = textLeft.substring(toTake);
- }
- assert(charWithinAuthor <= curAuthorWidth(), "_authorSlicer: past end of author");
- if (charWithinAuthor == curAuthorWidth()) {
- nextAuthor();
- }
- }
- },
- setBuilder: function(builder) {
- builderOut = builder;
- }
- };
- }
-
- function _makeSlicer(C, output) {
- // C: Changeset, output: builder from _makeBuilder
- // C is considered immutable, won't change or be changed
-
- // OOP style: state in environment
- var charIndex = 0; // 0 <= charIndex <= C.newLen(); maximum value iff atEnd
- var stripIndex = 0; // 0 <= stripIndex <= C.numStrips(); maximum value iff atEnd
- var charWithinStrip = 0; // 0 <= charWithinStrip < curStripWidth()
- var atEnd = false;
-
- var authorSlicer;
- if (C.authors) {
- authorSlicer = _makeAuthorSlicer(C.authors, output);
- }
-
- var ptr = 3;
- function curStartIndex() { return C[ptr]; }
- function curNumTaken() { return C[ptr+1]; }
- function curNewText() { return C[ptr+2]; }
- function curStripWidth() { return curNumTaken() + curNewText().length; }
- function assertNotAtEnd() { assert(! atEnd, "_slicer: can't move past changeset end"); }
- function forwardInStrip(numChars) {
- charWithinStrip += numChars;
- charIndex += numChars;
- }
- function nextStrip() {
- assertNotAtEnd();
- assert(charWithinStrip == curStripWidth(), "_slicer: not at strip end");
- charWithinStrip = 0;
- stripIndex++;
- ptr += 3;
- if (stripIndex == C.numStrips()) {
- atEnd = true;
- }
- }
- function curNumNewCharsInRange(start, end) {
- // takes two indices into the current strip's combined "taken" and "new"
- // chars, and returns how many "new" chars are included in the range
- assert(start <= end, "_slicer: curNumNewCharsInRange given out-of-order indices");
- var nt = curNumTaken();
- var nn = curNewText().length;
- var s = nt;
- var e = nt+nn;
- if (s < start) s = start;
- if (e > end) e = end;
- if (e < s) return 0;
- return e-s;
- }
-
- var self;
- return self = {
- skipChars: function (n) {
- assert(n >= 0, "_slicer: can't skip negative n");
- if (n == 0) return;
- assertNotAtEnd();
-
- var leftToSkip = n;
- while (leftToSkip > 0) {
- var leftInStrip = curStripWidth() - charWithinStrip;
- if (leftToSkip >= leftInStrip) {
- forwardInStrip(leftInStrip);
-
- if (authorSlicer)
- authorSlicer.skipChars(curNumNewCharsInRange(charWithinStrip,
- charWithinStrip + leftInStrip));
-
- leftToSkip -= leftInStrip;
- nextStrip();
- }
- else {
- if (authorSlicer)
- authorSlicer.skipChars(curNumNewCharsInRange(charWithinStrip,
- charWithinStrip + leftToSkip));
-
- forwardInStrip(leftToSkip);
- leftToSkip = 0;
- }
- }
- },
- takeChars: function (n) {
- assert(n >= 0, "_slicer: can't take negative n");
- if (n == 0) return;
- assertNotAtEnd();
-
- var leftToTake = n;
- while (leftToTake > 0) {
- if (curNumTaken() > 0 && charWithinStrip < curNumTaken()) {
- // at least one char to take from current strip's numTaken
- var leftInTaken = (curNumTaken() - charWithinStrip);
- assert(leftInTaken > 0, "_slicer: should have leftInTaken > 0");
- var toTake = min(leftInTaken, leftToTake);
- assert(toTake > 0, "_slicer: should have toTake > 0");
- output.appendOldText(curStartIndex() + charWithinStrip, toTake);
- forwardInStrip(toTake);
- leftToTake -= toTake;
- }
- if (leftToTake > 0 && curNewText().length > 0 && charWithinStrip >= curNumTaken() &&
- charWithinStrip < curStripWidth()) {
- // at least one char to take from current strip's newText
- var leftInNewText = (curStripWidth() - charWithinStrip);
- assert(leftInNewText > 0, "_slicer: should have leftInNewText > 0");
- var toTake = min(leftInNewText, leftToTake);
- assert(toTake > 0, "_slicer: should have toTake > 0");
- var newText = curNewText().substr(charWithinStrip - curNumTaken(), toTake);
- if (authorSlicer) {
- authorSlicer.takeChars(newText.length, newText);
- }
- else {
- output.appendNewText(newText);
- }
- forwardInStrip(toTake);
- leftToTake -= toTake;
- }
- assert(charWithinStrip <= curStripWidth(), "_slicer: past end of strip");
- if (charWithinStrip == curStripWidth()) {
- nextStrip();
- }
- }
- },
- skipTo: function(n) {
- self.skipChars(n - charIndex);
- }
- };
- }
-
- array.slicer = function(outputBuilder) {
- return _makeSlicer(this, outputBuilder);
- }
-
- array.compose = function(next) {
- assert(next.oldLen() == this.newLen(), "mismatched composition");
-
- var builder = _makeBuilder(this.oldLen(), !!(this.authors || next.authors));
- var slicer = _makeSlicer(this, builder);
-
- var authorSlicer;
- if (next.authors) {
- authorSlicer = _makeAuthorSlicer(next.authors, builder);
- }
-
- next.eachStrip(function(s, t, n) {
- slicer.skipTo(s);
- slicer.takeChars(t);
- if (authorSlicer) {
- authorSlicer.takeChars(n.length, n);
- }
- else {
- builder.appendNewText(n);
- }
- }, this);
-
- return builder.toChangeset();
- };
-
- array.traverser = function() {
- return _makeTraverser(this);
- }
-
- function _makeTraverser(C) {
- var s = C[3], t = C[4], n = C[5];
- var nextIndex = 6;
- var indexIntoNewText = 0;
-
- var authorSlicer;
- if (C.authors) {
- authorSlicer = _makeAuthorSlicer(C.authors, null);
- }
-
- function advanceIfPossible() {
- if (t == 0 && n == "" && nextIndex < C.length) {
- s = C[nextIndex];
- t = C[nextIndex+1];
- n = C[nextIndex+2];
- nextIndex += 3;
- }
- }
-
- var self;
- return self = {
- numTakenChars: function() {
- // if starts with taken characters, then how many, else 0
- return (t > 0) ? t : 0;
- },
- numNewChars: function() {
- // if starts with new characters, then how many, else 0
- return (t == 0 && n.length > 0) ? n.length : 0;
- },
- takenCharsStart: function() {
- return (self.numTakenChars() > 0) ? s : 0;
- },
- hasMore: function() {
- return self.numTakenChars() > 0 || self.numNewChars() > 0;
- },
- curIndex: function() {
- return indexIntoNewText;
- },
- consumeTakenChars: function (x) {
- assert(self.numTakenChars() > 0, "_traverser: no taken chars");
- assert(x >= 0 && x <= self.numTakenChars(), "_traverser: bad number of taken chars");
- if (x == 0) return;
- if (t == x) { s = 0; t = 0; }
- else { s += x; t -= x; }
- indexIntoNewText += x;
- advanceIfPossible();
- },
- consumeNewChars: function(x) {
- return self.appendNewChars(x, null);
- },
- appendNewChars: function(x, builder) {
- assert(self.numNewChars() > 0, "_traverser: no new chars");
- assert(x >= 0 && x <= self.numNewChars(), "_traverser: bad number of new chars");
- if (x == 0) return "";
- var str = n.substring(0, x);
- n = n.substring(x);
- indexIntoNewText += x;
- advanceIfPossible();
-
- if (builder) {
- if (authorSlicer) {
- authorSlicer.setBuilder(builder);
- authorSlicer.takeChars(x, str);
- }
- else {
- builder.appendNewText(str);
- }
- }
- else {
- if (authorSlicer) authorSlicer.skipChars(x);
- return str;
- }
- },
- consumeAvailableTakenChars: function() {
- return self.consumeTakenChars(self.numTakenChars());
- },
- consumeAvailableNewChars: function() {
- return self.consumeNewChars(self.numNewChars());
- },
- appendAvailableNewChars: function(builder) {
- return self.appendNewChars(self.numNewChars(), builder);
- }
- };
- }
-
- array.follow = function(prev, reverseInsertOrder) {
- // prev: Changeset, reverseInsertOrder: boolean
-
- // A.compose(B.follow(A)) is the merging of Changesets A and B, which operate on the same old text.
- // It is always the same as B.compose(A.follow(B, true)).
-
- assert(prev.oldLen() == this.oldLen(), "mismatched follow: "+prev.oldLen()+"/"+this.oldLen());
- var builder = _makeBuilder(prev.newLen(), !! this.authors);
- var a = _makeTraverser(prev);
- var b = _makeTraverser(this);
- while (a.hasMore() || b.hasMore()) {
- if (a.numNewChars() > 0 && ! reverseInsertOrder) {
- builder.appendOldText(a.curIndex(), a.numNewChars());
- a.consumeAvailableNewChars();
- }
- else if (b.numNewChars() > 0) {
- b.appendAvailableNewChars(builder);
- }
- else if (a.numNewChars() > 0 && reverseInsertOrder) {
- builder.appendOldText(a.curIndex(), a.numNewChars());
- a.consumeAvailableNewChars();
- }
- else if (! b.hasMore()) a.consumeAvailableTakenChars();
- else if (! a.hasMore()) b.consumeAvailableTakenChars();
- else {
- var x = a.takenCharsStart();
- var y = b.takenCharsStart();
- if (x < y) a.consumeTakenChars(min(a.numTakenChars(), y-x));
- else if (y < x) b.consumeTakenChars(min(b.numTakenChars(), x-y));
- else {
- var takenByBoth = min(a.numTakenChars(), b.numTakenChars());
- builder.appendOldText(a.curIndex(), takenByBoth);
- a.consumeTakenChars(takenByBoth);
- b.consumeTakenChars(takenByBoth);
- }
- }
- }
- return builder.toChangeset();
- }
-
- array.encodeToString = function(asBinary) {
- var stringDataArray = [];
- var numsArray = [];
- if (! asBinary) numsArray.push(this[0]);
- numsArray.push(this[1], this[2]);
- this.eachStrip(function(s, t, n) {
- numsArray.push(s, t, n.length);
- stringDataArray.push(n);
- }, this);
- if (! asBinary) {
- return numsArray.join(',')+'|'+stringDataArray.join('');
- }
- else {
- return "A" + Changeset.numberArrayToString(numsArray)
- +escapeCrazyUnicode(stringDataArray.join(''));
- }
- }
-
- function escapeCrazyUnicode(str) {
- return str.replace(/\\/g, '\\\\').replace(/[\ud800-\udfff]/g, function (c) {
- return "\\u"+("0000"+c.charCodeAt(0).toString(16)).slice(-4);
- });
- }
-
- array.applyToAttributedText = Changeset.applyToAttributedText;
-
- function splicesFromChanges(c) {
- var splices = [];
- // get a list of splices, [startChar, endChar, newText]
- var traverser = c.traverser();
- var oldTextLength = c.oldLen();
- var indexIntoOldText = 0;
- while (traverser.hasMore() || indexIntoOldText < oldTextLength) {
- var newText = "";
- var startChar = indexIntoOldText;
- var endChar = indexIntoOldText;
- if (traverser.numNewChars() > 0) {
- newText = traverser.consumeAvailableNewChars();
- }
- if (traverser.hasMore()) {
- endChar = traverser.takenCharsStart();
- indexIntoOldText = endChar + traverser.numTakenChars();
- traverser.consumeAvailableTakenChars();
- }
- else {
- endChar = oldTextLength;
- indexIntoOldText = endChar;
- }
- if (endChar != startChar || newText.length > 0) {
- splices.push([startChar, endChar, newText]);
- }
- }
- return splices;
- }
-
- array.toSplices = function() {
- return splicesFromChanges(this);
- }
-
- array.characterRangeFollowThis = function(selStartChar, selEndChar, insertionsAfter) {
- var changeset = this;
- // represent the selection as a changeset that replaces the selection with some finite string.
- // Because insertions indicate intention, it doesn't matter what this string is, and even
- // if the selectionChangeset is made to "follow" other changes it will still be the only
- // insertion.
- var selectionChangeset =
- Changeset(changeset.oldLen()).builder().appendOldText(0, selStartChar).appendNewText(
- "X").appendOldText(selEndChar, changeset.oldLen() - selEndChar).toChangeset();
- var newSelectionChangeset = selectionChangeset.follow(changeset, insertionsAfter);
- var selectionSplices = newSelectionChangeset.toSplices();
- function includeChar(i) {
- if (! includeChar.calledYet) {
- selStartChar = i;
- selEndChar = i;
- includeChar.calledYet = true;
- }
- else {
- if (i < selStartChar) selStartChar = i;
- if (i > selEndChar) selEndChar = i;
- }
- }
- for(var i=0; i<selectionSplices.length; i++) {
- var s = selectionSplices[i];
- includeChar(s[0]);
- includeChar(s[1]);
- }
- return [selStartChar, selEndChar];
- }
-
- return array;
-}
-
-Changeset.MAGIC = "Changeset";
-Changeset.makeSplice = function(oldLength, spliceStart, numRemoved, stringInserted) {
- oldLength = (oldLength || 0);
- spliceStart = (spliceStart || 0);
- numRemoved = (numRemoved || 0);
- stringInserted = String(stringInserted || "");
-
- var builder = Changeset(oldLength).builder();
- builder.appendOldText(0, spliceStart);
- builder.appendNewText(stringInserted);
- builder.appendOldText(spliceStart + numRemoved, oldLength - numRemoved - spliceStart);
- return builder.toChangeset();
-};
-Changeset.identity = function(len) {
- return Changeset(len).builder().appendOldText(0, len).toChangeset();
-};
-Changeset.decodeFromString = function(str) {
- function error(msg) { var e = new Error(msg); e.easysync = true; throw e; }
- function toHex(str) {
- var a = [];
- a.push("length["+str.length+"]:");
- var TRUNC=20;
- for(var i=0;i<str.substring(0,TRUNC).length;i++) {
- a.push(("000"+str.charCodeAt(i).toString(16)).slice(-4));
- }
- if (str.length > TRUNC) a.push("...");
- return a.join(' ');
- }
- function unescapeCrazyUnicode(str) {
- return str.replace(/\\(u....|\\)/g, function(seq) {
- if (seq == "\\\\") return "\\";
- return String.fromCharCode(Number("0x"+seq.substring(2)));
- });
- }
-
- var numData, stringData;
- var binary = false;
- var typ = str.charAt(0);
- if (typ == "B" || typ == "A") {
- var result = Changeset.numberArrayFromString(str, 1);
- numData = result[0];
- stringData = result[1];
- if (typ == "A") {
- stringData = unescapeCrazyUnicode(stringData);
- }
- binary = true;
- }
- else if (typ == "C") {
- var barPosition = str.indexOf('|');
- numData = str.substring(0, barPosition).split(',');
- stringData = str.substring(barPosition+1);
- }
- else {
- error("Not a changeset: "+toHex(str));
- }
- var stringDataOffset = 0;
- var array = [];
- var ptr;
- if (binary) {
- array.push("Changeset", numData[0], numData[1]);
- var ptr = 2;
- }
- else {
- array.push(numData[0], Number(numData[1]), Number(numData[2]));
- var ptr = 3;
- }
- while (ptr < numData.length) {
- array.push(Number(numData[ptr++]), Number(numData[ptr++]));
- var newTextLength = Number(numData[ptr++]);
- array.push(stringData.substr(stringDataOffset, newTextLength));
- stringDataOffset += newTextLength;
- }
- if (stringDataOffset != stringData.length) {
- error("Extra character data beyond end of encoded string ("+toHex(str)+")");
- }
- return Changeset(array);
-};
-
-Changeset.numberArrayToString = function(nums) {
- var array = [];
- function writeNum(n) {
- // does not support negative numbers
- var twentyEightBit = (n & 0xfffffff);
- if (twentyEightBit <= 0x7fff) {
- array.push(String.fromCharCode(twentyEightBit));
- }
- else {
- array.push(String.fromCharCode(0xa000 | (twentyEightBit >> 15),
- twentyEightBit & 0x7fff));
- }
- }
- writeNum(nums.length);
- var len = nums.length;
- for(var i=0;i<len;i++) {
- writeNum(nums[i]);
- }
- return array.join('');
-};
-
-Changeset.numberArrayFromString = function(str, startIndex) {
- // returns [numberArray, remainingString]
- var nums = [];
- var strIndex = (startIndex || 0);
- function readNum() {
- var n = str.charCodeAt(strIndex++);
- if (n > 0x7fff) {
- if (n >= 0xa000) {
- n = (((n & 0x1fff) << 15) | str.charCodeAt(strIndex++));
- }
- else {
- // legacy format
- n = (((n & 0x1fff) << 16) | str.charCodeAt(strIndex++));
- }
- }
- return n;
- }
- var len = readNum();
- for(var i=0;i<len;i++) {
- nums.push(readNum());
- }
- return [nums, str.substring(strIndex)];
-};
-
-(function() {
- function repeatString(str, times) {
- if (times <= 0) return "";
- var s = repeatString(str, times >> 1);
- s += s;
- if (times & 1) s += str;
- return s;
- }
- function chr(n) { return String.fromCharCode(n+48); }
- function ord(c) { return c.charCodeAt(0)-48; }
- function runMatcher(c) {
- // Takes "A" and returns /\u0041+/g .
- // Avoid creating new objects unnecessarily by caching matchers
- // as properties of this function.
- var re = runMatcher[c];
- if (re) return re;
- re = runMatcher[c] = new RegExp("\\u"+("0000"+c.charCodeAt(0).toString(16)).slice(-4)+"+", 'g');
- return re;
- }
- function runLength(str, idx, c) {
- var re = runMatcher(c);
- re.lastIndex = idx;
- var result = re.exec(str);
- if (result && result[0]) {
- return result[0].length;
- }
- return 0;
- }
-
- // emptyObj may be a StorableObject
- Changeset.initAttributedText = function(emptyObj, initialString, initialAuthor) {
- var obj = emptyObj;
- obj.authorMap = { 1: (initialAuthor || '') };
- obj.text = (initialString || '');
- obj.attribs = repeatString(chr(1), obj.text.length);
- return obj;
- };
- Changeset.gcAttributedText = function(atObj) {
- // "garbage collect" the list of authors
- var removedAuthors = [];
- for(var a in atObj.authorMap) {
- if (atObj.attribs.indexOf(chr(Number(a))) < 0) {
- removedAuthors.push(atObj.authorMap[a]);
- delete atObj.authorMap[a];
- }
- }
- return removedAuthors;
- };
- Changeset.cloneAttributedText = function(emptyObj, atObj) {
- var obj = emptyObj;
- obj.text = atObj.text; // string
- if (atObj.attribs) obj.attribs = atObj.attribs; // string
- if (atObj.attribs_c) obj.attribs_c = atObj.attribs_c; // string
- obj.authorMap = {};
- for(var a in atObj.authorMap) {
- obj.authorMap[a] = atObj.authorMap[a];
- }
- return obj;
- };
- Changeset.applyToAttributedText = function(atObj, C) {
- C = (C || this);
- var oldText = atObj.text;
- var oldAttribs = atObj.attribs;
- Changeset._assert(C.isChangeset, "applyToAttributedText: 'this' is not a changeset");
- Changeset._assert(oldText.length == C.oldLen(),
- "applyToAttributedText: mismatch "+oldText.length+" / "+C.oldLen());
- var textBuf = [];
- var attribsBuf = [];
- var authorMap = atObj.authorMap;
- function authorId(author) {
- for(var a in authorMap) {
- if (authorMap[Number(a)] === author) {
- return Number(a);
- }
- }
- for(var i=1;i<=60000;i++) {
- // don't use "in" because it's currently broken on StorableObjects
- if (authorMap[i] === undefined) {
- authorMap[i] = author;
- return i;
- }
- }
- }
- var myBuilder = { appendNewText: function(txt, author) {
- // object that acts as a "builder" in that it receives requests from
- // authorSlicer to append text attributed to different authors
- attribsBuf.push(repeatString(chr(authorId(author)), txt.length));
- } };
- var authorSlicer;
- if (C.authors) {
- authorSlicer = C.authorSlicer(myBuilder);
- }
- C.eachStrip(function (s, t, n) {
- textBuf.push(oldText.substr(s, t), n);
- attribsBuf.push(oldAttribs.substr(s, t));
- if (authorSlicer) {
- authorSlicer.takeChars(n.length, n);
- }
- else {
- myBuilder.appendNewText(n, '');
- }
- });
- atObj.text = textBuf.join('');
- atObj.attribs = attribsBuf.join('');
- return atObj;
- };
- Changeset.getAttributedTextCharAuthor = function(atObj, idx) {
- return atObj.authorMap[ord(atObj.attribs.charAt(idx))];
- };
- Changeset.getAttributedTextCharRunLength = function(atObj, idx) {
- var c = atObj.attribs.charAt(idx);
- return runLength(atObj.attribs, idx, c);
- };
- Changeset.eachAuthorInAttributedText = function(atObj, func) {
- // call func(author, authorNum)
- for(var a in atObj.authorMap) {
- if (func(atObj.authorMap[a], Number(a))) break;
- }
- };
- Changeset.getAttributedTextAuthorByNum = function(atObj, n) {
- return atObj.authorMap[n];
- };
- // Compressed attributed text can be cloned, but nothing else until uncompressed!!
- Changeset.compressAttributedText = function(atObj) {
- // idempotent, mutates the object, returns it
- if (atObj.attribs) {
- atObj.attribs_c = atObj.attribs.replace(/([\s\S])\1{0,63}/g, function(run) {
- return run.charAt(0)+chr(run.length);;
- });
- delete atObj.attribs;
- }
- return atObj;
- };
- Changeset.decompressAttributedText = function(atObj) {
- // idempotent, mutates the object, returns it
- if (atObj.attribs_c) {
- atObj.attribs = atObj.attribs_c.replace(/[\s\S][\s\S]/g, function(run) {
- return repeatString(run.charAt(0), ord(run.charAt(1)));
- });
- delete atObj.attribs_c;
- }
- return atObj;
- };
-})();
diff --git a/trunk/etherpad/src/etherpad/collab/ace/easysync2.js b/trunk/etherpad/src/etherpad/collab/ace/easysync2.js
deleted file mode 100644
index 0fa1ec4..0000000
--- a/trunk/etherpad/src/etherpad/collab/ace/easysync2.js
+++ /dev/null
@@ -1,1968 +0,0 @@
-// DO NOT EDIT THIS FILE, edit infrastructure/ace/www/easysync2.js
-jimport("com.etherpad.Easysync2Support");
-
-/**
- * Copyright 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS-IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//var _opt = (this.Easysync2Support || null);
-var _opt = null; // disable optimization for now
-
-function AttribPool() {
- var p = {};
- p.numToAttrib = {}; // e.g. {0: ['foo','bar']}
- p.attribToNum = {}; // e.g. {'foo,bar': 0}
- p.nextNum = 0;
-
- p.putAttrib = function(attrib, dontAddIfAbsent) {
- var str = String(attrib);
- if (str in p.attribToNum) {
- return p.attribToNum[str];
- }
- if (dontAddIfAbsent) {
- return -1;
- }
- var num = p.nextNum++;
- p.attribToNum[str] = num;
- p.numToAttrib[num] = [String(attrib[0]||''),
- String(attrib[1]||'')];
- return num;
- };
-
- p.getAttrib = function(num) {
- var pair = p.numToAttrib[num];
- if (! pair) return pair;
- return [pair[0], pair[1]]; // return a mutable copy
- };
-
- p.getAttribKey = function(num) {
- var pair = p.numToAttrib[num];
- if (! pair) return '';
- return pair[0];
- };
-
- p.getAttribValue = function(num) {
- var pair = p.numToAttrib[num];
- if (! pair) return '';
- return pair[1];
- };
-
- p.eachAttrib = function(func) {
- for(var n in p.numToAttrib) {
- var pair = p.numToAttrib[n];
- func(pair[0], pair[1]);
- }
- };
-
- p.toJsonable = function() {
- return {numToAttrib: p.numToAttrib, nextNum: p.nextNum};
- };
-
- p.fromJsonable = function(obj) {
- p.numToAttrib = obj.numToAttrib;
- p.nextNum = obj.nextNum;
- p.attribToNum = {};
- for(var n in p.numToAttrib) {
- p.attribToNum[String(p.numToAttrib[n])] = Number(n);
- }
- return p;
- };
-
- return p;
-}
-
-var Changeset = {};
-
-Changeset.error = function error(msg) { var e = new Error(msg); e.easysync = true; throw e; };
-Changeset.assert = function assert(b, msgParts) {
- if (! b) {
- var msg = Array.prototype.slice.call(arguments, 1).join('');
- Changeset.error("Changeset: "+msg);
- }
-};
-
-Changeset.parseNum = function(str) { return parseInt(str, 36); };
-Changeset.numToString = function(num) { return num.toString(36).toLowerCase(); };
-Changeset.toBaseTen = function(cs) {
- var dollarIndex = cs.indexOf('$');
- var beforeDollar = cs.substring(0, dollarIndex);
- var fromDollar = cs.substring(dollarIndex);
- return beforeDollar.replace(/[0-9a-z]+/g, function(s) {
- return String(Changeset.parseNum(s)); }) + fromDollar;
-};
-
-Changeset.oldLen = function(cs) {
- return Changeset.unpack(cs).oldLen;
-};
-Changeset.newLen = function(cs) {
- return Changeset.unpack(cs).newLen;
-};
-
-Changeset.opIterator = function(opsStr, optStartIndex) {
- //print(opsStr);
- var regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|\?|/g;
- var startIndex = (optStartIndex || 0);
- var curIndex = startIndex;
- var prevIndex = curIndex;
- function nextRegexMatch() {
- prevIndex = curIndex;
- var result;
- if (_opt) {
- result = _opt.nextOpInString(opsStr, curIndex);
- if (result) {
- if (result.opcode() == '?') {
- Changeset.error("Hit error opcode in op stream");
- }
- curIndex = result.lastIndex();
- }
- }
- else {
- regex.lastIndex = curIndex;
- result = regex.exec(opsStr);
- curIndex = regex.lastIndex;
- if (result[0] == '?') {
- Changeset.error("Hit error opcode in op stream");
- }
- }
- return result;
- }
- var regexResult = nextRegexMatch();
- var obj = Changeset.newOp();
- function next(optObj) {
- var op = (optObj || obj);
- if (_opt && regexResult) {
- op.attribs = regexResult.attribs();
- op.lines = regexResult.lines();
- op.chars = regexResult.chars();
- op.opcode = regexResult.opcode();
- regexResult = nextRegexMatch();
- }
- else if ((! _opt) && regexResult[0]) {
- op.attribs = regexResult[1];
- op.lines = Changeset.parseNum(regexResult[2] || 0);
- op.opcode = regexResult[3];
- op.chars = Changeset.parseNum(regexResult[4]);
- regexResult = nextRegexMatch();
- }
- else {
- Changeset.clearOp(op);
- }
- return op;
- }
- function hasNext() { return !! (_opt ? regexResult : regexResult[0]); }
- function lastIndex() { return prevIndex; }
- return {next: next, hasNext: hasNext, lastIndex: lastIndex};
-};
-
-Changeset.clearOp = function(op) {
- op.opcode = '';
- op.chars = 0;
- op.lines = 0;
- op.attribs = '';
-};
-Changeset.newOp = function(optOpcode) {
- return {opcode:(optOpcode || ''), chars:0, lines:0, attribs:''};
-};
-Changeset.cloneOp = function(op) {
- return {opcode: op.opcode, chars: op.chars, lines: op.lines, attribs: op.attribs};
-};
-Changeset.copyOp = function(op1, op2) {
- op2.opcode = op1.opcode;
- op2.chars = op1.chars;
- op2.lines = op1.lines;
- op2.attribs = op1.attribs;
-};
-Changeset.opString = function(op) {
- // just for debugging
- if (! op.opcode) return 'null';
- var assem = Changeset.opAssembler();
- assem.append(op);
- return assem.toString();
-};
-Changeset.stringOp = function(str) {
- // just for debugging
- return Changeset.opIterator(str).next();
-};
-
-Changeset.checkRep = function(cs) {
- // doesn't check things that require access to attrib pool (e.g. attribute order)
- // or original string (e.g. newline positions)
- var unpacked = Changeset.unpack(cs);
- var oldLen = unpacked.oldLen;
- var newLen = unpacked.newLen;
- var ops = unpacked.ops;
- var charBank = unpacked.charBank;
-
- var assem = Changeset.smartOpAssembler();
- var oldPos = 0;
- var calcNewLen = 0;
- var numInserted = 0;
- var iter = Changeset.opIterator(ops);
- while (iter.hasNext()) {
- var o = iter.next();
- switch (o.opcode) {
- case '=': oldPos += o.chars; calcNewLen += o.chars; break;
- case '-': oldPos += o.chars; Changeset.assert(oldPos < oldLen, oldPos," >= ",oldLen," in ",cs); break;
- case '+': {
- calcNewLen += o.chars; numInserted += o.chars;
- Changeset.assert(calcNewLen < newLen, calcNewLen," >= ",newLen," in ",cs);
- break;
- }
- }
- assem.append(o);
- }
-
- calcNewLen += oldLen - oldPos;
- charBank = charBank.substring(0, numInserted);
- while (charBank.length < numInserted) {
- charBank += "?";
- }
-
- assem.endDocument();
- var normalized = Changeset.pack(oldLen, calcNewLen, assem.toString(), charBank);
- Changeset.assert(normalized == cs, normalized,' != ',cs);
-
- return cs;
-}
-
-Changeset.smartOpAssembler = function() {
- // Like opAssembler but able to produce conforming changesets
- // from slightly looser input, at the cost of speed.
- // Specifically:
- // - merges consecutive operations that can be merged
- // - strips final "="
- // - ignores 0-length changes
- // - reorders consecutive + and - (which margingOpAssembler doesn't do)
-
- var minusAssem = Changeset.mergingOpAssembler();
- var plusAssem = Changeset.mergingOpAssembler();
- var keepAssem = Changeset.mergingOpAssembler();
- var assem = Changeset.stringAssembler();
- var lastOpcode = '';
- var lengthChange = 0;
-
- function flushKeeps() {
- assem.append(keepAssem.toString());
- keepAssem.clear();
- }
-
- function flushPlusMinus() {
- assem.append(minusAssem.toString());
- minusAssem.clear();
- assem.append(plusAssem.toString());
- plusAssem.clear();
- }
-
- function append(op) {
- if (! op.opcode) return;
- if (! op.chars) return;
-
- if (op.opcode == '-') {
- if (lastOpcode == '=') {
- flushKeeps();
- }
- minusAssem.append(op);
- lengthChange -= op.chars;
- }
- else if (op.opcode == '+') {
- if (lastOpcode == '=') {
- flushKeeps();
- }
- plusAssem.append(op);
- lengthChange += op.chars;
- }
- else if (op.opcode == '=') {
- if (lastOpcode != '=') {
- flushPlusMinus();
- }
- keepAssem.append(op);
- }
- lastOpcode = op.opcode;
- }
-
- function appendOpWithText(opcode, text, attribs, pool) {
- var op = Changeset.newOp(opcode);
- op.attribs = Changeset.makeAttribsString(opcode, attribs, pool);
- var 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);
- }
- }
-
- function toString() {
- flushPlusMinus();
- flushKeeps();
- return assem.toString();
- }
-
- function clear() {
- minusAssem.clear();
- plusAssem.clear();
- keepAssem.clear();
- assem.clear();
- lengthChange = 0;
- }
-
- function endDocument() {
- keepAssem.endDocument();
- }
-
- function getLengthChange() {
- return lengthChange;
- }
-
- return {append: append, toString: toString, clear: clear, endDocument: endDocument,
- appendOpWithText: appendOpWithText, getLengthChange: getLengthChange };
-};
-
-if (_opt) {
- Changeset.mergingOpAssembler = function() {
- var assem = _opt.mergingOpAssembler();
-
- function append(op) {
- assem.append(op.opcode, op.chars, op.lines, op.attribs);
- }
- function toString() {
- return assem.toString();
- }
- function clear() {
- assem.clear();
- }
- function endDocument() {
- assem.endDocument();
- }
-
- return {append: append, toString: toString, clear: clear, endDocument: endDocument};
- };
-}
-else {
- Changeset.mergingOpAssembler = function() {
- // This assembler can be used in production; it efficiently
- // merges consecutive operations that are mergeable, ignores
- // no-ops, and drops final pure "keeps". It does not re-order
- // operations.
- var assem = Changeset.opAssembler();
- var bufOp = Changeset.newOp();
-
- // If we get, for example, insertions [xxx\n,yyy], those don't merge,
- // but if we get [xxx\n,yyy,zzz\n], that merges to [xxx\nyyyzzz\n].
- // This variable stores the length of yyy and any other newline-less
- // ops immediately after it.
- var bufOpAdditionalCharsAfterNewline = 0;
-
- function flush(isEndDocument) {
- if (bufOp.opcode) {
- if (isEndDocument && bufOp.opcode == '=' && ! bufOp.attribs) {
- // final merged keep, leave it implicit
- }
- else {
- assem.append(bufOp);
- if (bufOpAdditionalCharsAfterNewline) {
- bufOp.chars = bufOpAdditionalCharsAfterNewline;
- bufOp.lines = 0;
- assem.append(bufOp);
- bufOpAdditionalCharsAfterNewline = 0;
- }
- }
- bufOp.opcode = '';
- }
- }
- function append(op) {
- if (op.chars > 0) {
- if (bufOp.opcode == op.opcode && bufOp.attribs == op.attribs) {
- if (op.lines > 0) {
- // bufOp and additional chars are all mergeable into a multi-line op
- bufOp.chars += bufOpAdditionalCharsAfterNewline + op.chars;
- bufOp.lines += op.lines;
- bufOpAdditionalCharsAfterNewline = 0;
- }
- else if (bufOp.lines == 0) {
- // both bufOp and op are in-line
- bufOp.chars += op.chars;
- }
- else {
- // append in-line text to multi-line bufOp
- bufOpAdditionalCharsAfterNewline += op.chars;
- }
- }
- else {
- flush();
- Changeset.copyOp(op, bufOp);
- }
- }
- }
- function endDocument() {
- flush(true);
- }
- function toString() {
- flush();
- return assem.toString();
- }
- function clear() {
- assem.clear();
- Changeset.clearOp(bufOp);
- }
- return {append: append, toString: toString, clear: clear, endDocument: endDocument};
- };
-}
-
-if (_opt) {
- Changeset.opAssembler = function() {
- var assem = _opt.opAssembler();
- // this function allows op to be mutated later (doesn't keep a ref)
- function append(op) {
- assem.append(op.opcode, op.chars, op.lines, op.attribs);
- }
- function toString() {
- return assem.toString();
- }
- function clear() {
- assem.clear();
- }
- return {append: append, toString: toString, clear: clear};
- };
-}
-else {
- Changeset.opAssembler = function() {
- var pieces = [];
- // this function allows op to be mutated later (doesn't keep a ref)
- function append(op) {
- pieces.push(op.attribs);
- if (op.lines) {
- pieces.push('|', Changeset.numToString(op.lines));
- }
- pieces.push(op.opcode);
- pieces.push(Changeset.numToString(op.chars));
- }
- function toString() {
- return pieces.join('');
- }
- function clear() {
- pieces.length = 0;
- }
- return {append: append, toString: toString, clear: clear};
- };
-}
-
-Changeset.stringIterator = function(str) {
- var curIndex = 0;
- function assertRemaining(n) {
- Changeset.assert(n <= remaining(), "!(",n," <= ",remaining(),")");
- }
- function take(n) {
- assertRemaining(n);
- var s = str.substr(curIndex, n);
- curIndex += n;
- return s;
- }
- function peek(n) {
- assertRemaining(n);
- var s = str.substr(curIndex, n);
- return s;
- }
- function skip(n) {
- assertRemaining(n);
- curIndex += n;
- }
- function remaining() {
- return str.length - curIndex;
- }
- return {take:take, skip:skip, remaining:remaining, peek:peek};
-};
-
-Changeset.stringAssembler = function() {
- var pieces = [];
- function append(x) {
- pieces.push(String(x));
- }
- function toString() {
- return pieces.join('');
- }
- return {append: append, toString: toString};
-};
-
-// "lines" need not be an array as long as it supports certain calls (lines_foo inside).
-Changeset.textLinesMutator = function(lines) {
- // Mutates lines, an array of strings, in place.
- // Mutation operations have the same constraints as changeset operations
- // with respect to newlines, but not the other additional constraints
- // (i.e. ins/del ordering, forbidden no-ops, non-mergeability, final newline).
- // Can be used to mutate lists of strings where the last char of each string
- // is not actually a newline, but for the purposes of N and L values,
- // the caller should pretend it is, and for things to work right in that case, the input
- // to insert() should be a single line with no newlines.
-
- var curSplice = [0,0];
- var inSplice = false;
- // position in document after curSplice is applied:
- var curLine = 0, curCol = 0;
- // invariant: if (inSplice) then (curLine is in curSplice[0] + curSplice.length - {2,3}) &&
- // curLine >= curSplice[0]
- // invariant: if (inSplice && (curLine >= curSplice[0] + curSplice.length - 2)) then
- // curCol == 0
-
- function lines_applySplice(s) {
- lines.splice.apply(lines, s);
- }
- function lines_toSource() {
- return lines.toSource();
- }
- function lines_get(idx) {
- if (lines.get) {
- return lines.get(idx);
- }
- else {
- return lines[idx];
- }
- }
- // can be unimplemented if removeLines's return value not needed
- function lines_slice(start, end) {
- if (lines.slice) {
- return lines.slice(start, end);
- }
- else {
- return [];
- }
- }
- function lines_length() {
- if ((typeof lines.length) == "number") {
- return lines.length;
- }
- else {
- return lines.length();
- }
- }
-
- function enterSplice() {
- curSplice[0] = curLine;
- curSplice[1] = 0;
- if (curCol > 0) {
- putCurLineInSplice();
- }
- inSplice = true;
- }
- function leaveSplice() {
- lines_applySplice(curSplice);
- curSplice.length = 2;
- curSplice[0] = curSplice[1] = 0;
- inSplice = false;
- }
- function isCurLineInSplice() {
- return (curLine - curSplice[0] < (curSplice.length - 2));
- }
- function debugPrint(typ) {
- print(typ+": "+curSplice.toSource()+" / "+curLine+","+curCol+" / "+lines_toSource());
- }
- function putCurLineInSplice() {
- if (! isCurLineInSplice()) {
- curSplice.push(lines_get(curSplice[0] + curSplice[1]));
- curSplice[1]++;
- }
- return 2 + curLine - curSplice[0];
- }
-
- function skipLines(L, includeInSplice) {
- if (L) {
- if (includeInSplice) {
- if (! inSplice) {
- enterSplice();
- }
- for(var i=0;i<L;i++) {
- curCol = 0;
- putCurLineInSplice();
- curLine++;
- }
- }
- else {
- if (inSplice) {
- if (L > 1) {
- leaveSplice();
- }
- else {
- putCurLineInSplice();
- }
- }
- curLine += L;
- curCol = 0;
- }
- //print(inSplice+" / "+isCurLineInSplice()+" / "+curSplice[0]+" / "+curSplice[1]+" / "+lines.length);
- /*if (inSplice && (! isCurLineInSplice()) && (curSplice[0] + curSplice[1] < lines.length)) {
- print("BLAH");
- putCurLineInSplice();
- }*/ // tests case foo in remove(), which isn't otherwise covered in current impl
- }
- //debugPrint("skip");
- }
-
- function skip(N, L, includeInSplice) {
- if (N) {
- if (L) {
- skipLines(L, includeInSplice);
- }
- else {
- if (includeInSplice && ! inSplice) {
- enterSplice();
- }
- if (inSplice) {
- putCurLineInSplice();
- }
- curCol += N;
- //debugPrint("skip");
- }
- }
- }
-
- function removeLines(L) {
- var removed = '';
- if (L) {
- if (! inSplice) {
- enterSplice();
- }
- function nextKLinesText(k) {
- var m = curSplice[0] + curSplice[1];
- return lines_slice(m, m+k).join('');
- }
- if (isCurLineInSplice()) {
- //print(curCol);
- if (curCol == 0) {
- removed = curSplice[curSplice.length-1];
- // print("FOO"); // case foo
- curSplice.length--;
- removed += nextKLinesText(L-1);
- curSplice[1] += L-1;
- }
- else {
- removed = nextKLinesText(L-1);
- curSplice[1] += L-1;
- var sline = curSplice.length - 1;
- removed = curSplice[sline].substring(curCol) + removed;
- curSplice[sline] = curSplice[sline].substring(0, curCol) +
- lines_get(curSplice[0] + curSplice[1]);
- curSplice[1] += 1;
- }
- }
- else {
- removed = nextKLinesText(L);
- curSplice[1] += L;
- }
- //debugPrint("remove");
- }
- return removed;
- }
-
- function remove(N, L) {
- var removed = '';
- if (N) {
- if (L) {
- return removeLines(L);
- }
- else {
- if (! inSplice) {
- enterSplice();
- }
- var sline = putCurLineInSplice();
- removed = curSplice[sline].substring(curCol, curCol+N);
- curSplice[sline] = curSplice[sline].substring(0, curCol) +
- curSplice[sline].substring(curCol+N);
- //debugPrint("remove");
- }
- }
- return removed;
- }
-
- function insert(text, L) {
- if (text) {
- if (! inSplice) {
- enterSplice();
- }
- if (L) {
- var newLines = Changeset.splitTextLines(text);
- if (isCurLineInSplice()) {
- //if (curCol == 0) {
- //curSplice.length--;
- //curSplice[1]--;
- //Array.prototype.push.apply(curSplice, newLines);
- //curLine += newLines.length;
- //}
- //else {
- var sline = curSplice.length - 1;
- var theLine = curSplice[sline];
- var lineCol = curCol;
- curSplice[sline] = theLine.substring(0, lineCol) + newLines[0];
- curLine++;
- newLines.splice(0, 1);
- Array.prototype.push.apply(curSplice, newLines);
- curLine += newLines.length;
- curSplice.push(theLine.substring(lineCol));
- curCol = 0;
- //}
- }
- else {
- Array.prototype.push.apply(curSplice, newLines);
- curLine += newLines.length;
- }
- }
- else {
- var sline = putCurLineInSplice();
- curSplice[sline] = curSplice[sline].substring(0, curCol) +
- text + curSplice[sline].substring(curCol);
- curCol += text.length;
- }
- //debugPrint("insert");
- }
- }
-
- function hasMore() {
- //print(lines.length+" / "+inSplice+" / "+(curSplice.length - 2)+" / "+curSplice[1]);
- var docLines = lines_length();
- if (inSplice) {
- docLines += curSplice.length - 2 - curSplice[1];
- }
- return curLine < docLines;
- }
-
- function close() {
- if (inSplice) {
- leaveSplice();
- }
- //debugPrint("close");
- }
-
- var self = {skip:skip, remove:remove, insert:insert, close:close, hasMore:hasMore,
- removeLines:removeLines, skipLines: skipLines};
- return self;
-};
-
-Changeset.applyZip = function(in1, idx1, in2, idx2, func) {
- var iter1 = Changeset.opIterator(in1, idx1);
- var iter2 = Changeset.opIterator(in2, idx2);
- var assem = Changeset.smartOpAssembler();
- var op1 = Changeset.newOp();
- var op2 = Changeset.newOp();
- var opOut = Changeset.newOp();
- while (op1.opcode || iter1.hasNext() || op2.opcode || iter2.hasNext()) {
- if ((! op1.opcode) && iter1.hasNext()) iter1.next(op1);
- if ((! op2.opcode) && iter2.hasNext()) iter2.next(op2);
- func(op1, op2, opOut);
- if (opOut.opcode) {
- //print(opOut.toSource());
- assem.append(opOut);
- opOut.opcode = '';
- }
- }
- assem.endDocument();
- return assem.toString();
-};
-
-Changeset.unpack = function(cs) {
- var headerRegex = /Z:([0-9a-z]+)([><])([0-9a-z]+)|/;
- var headerMatch = headerRegex.exec(cs);
- if ((! headerMatch) || (! headerMatch[0])) {
- Changeset.error("Not a changeset: "+cs);
- }
- var oldLen = Changeset.parseNum(headerMatch[1]);
- var changeSign = (headerMatch[2] == '>') ? 1 : -1;
- var changeMag = Changeset.parseNum(headerMatch[3]);
- var newLen = oldLen + changeSign*changeMag;
- var opsStart = headerMatch[0].length;
- var opsEnd = cs.indexOf("$");
- if (opsEnd < 0) opsEnd = cs.length;
- return {oldLen: oldLen, newLen: newLen, ops: cs.substring(opsStart, opsEnd),
- charBank: cs.substring(opsEnd+1)};
-};
-
-Changeset.pack = function(oldLen, newLen, opsStr, bank) {
- var lenDiff = newLen - oldLen;
- var lenDiffStr = (lenDiff >= 0 ?
- '>'+Changeset.numToString(lenDiff) :
- '<'+Changeset.numToString(-lenDiff));
- var a = [];
- a.push('Z:', Changeset.numToString(oldLen), lenDiffStr, opsStr, '$', bank);
- return a.join('');
-};
-
-Changeset.applyToText = function(cs, str) {
- var unpacked = Changeset.unpack(cs);
- Changeset.assert(str.length == unpacked.oldLen,
- "mismatched apply: ",str.length," / ",unpacked.oldLen);
- var csIter = Changeset.opIterator(unpacked.ops);
- var bankIter = Changeset.stringIterator(unpacked.charBank);
- var strIter = Changeset.stringIterator(str);
- var assem = Changeset.stringAssembler();
- while (csIter.hasNext()) {
- var op = csIter.next();
- switch(op.opcode) {
- case '+': assem.append(bankIter.take(op.chars)); break;
- case '-': strIter.skip(op.chars); break;
- case '=': assem.append(strIter.take(op.chars)); break;
- }
- }
- assem.append(strIter.take(strIter.remaining()));
- return assem.toString();
-};
-
-Changeset.mutateTextLines = function(cs, lines) {
- var unpacked = Changeset.unpack(cs);
- var csIter = Changeset.opIterator(unpacked.ops);
- var bankIter = Changeset.stringIterator(unpacked.charBank);
- var mut = Changeset.textLinesMutator(lines);
- while (csIter.hasNext()) {
- var op = csIter.next();
- switch(op.opcode) {
- case '+': mut.insert(bankIter.take(op.chars), op.lines); break;
- case '-': mut.remove(op.chars, op.lines); break;
- case '=': mut.skip(op.chars, op.lines, (!! op.attribs)); break;
- }
- }
- mut.close();
-};
-
-Changeset.composeAttributes = function(att1, att2, resultIsMutation, pool) {
- // att1 and att2 are strings like "*3*f*1c", asMutation is a boolean.
-
- // Sometimes attribute (key,value) pairs are treated as attribute presence
- // information, while other times they are treated as operations that
- // mutate a set of attributes, and this affects whether an empty value
- // is a deletion or a change.
- // Examples, of the form (att1Items, att2Items, resultIsMutation) -> result
- // ([], [(bold, )], true) -> [(bold, )]
- // ([], [(bold, )], false) -> []
- // ([], [(bold, true)], true) -> [(bold, true)]
- // ([], [(bold, true)], false) -> [(bold, true)]
- // ([(bold, true)], [(bold, )], true) -> [(bold, )]
- // ([(bold, true)], [(bold, )], false) -> []
-
- // pool can be null if att2 has no attributes.
-
- if ((! att1) && resultIsMutation) {
- // In the case of a mutation (i.e. composing two changesets),
- // an att2 composed with an empy att1 is just att2. If att1
- // is part of an attribution string, then att2 may remove
- // attributes that are already gone, so don't do this optimization.
- return att2;
- }
- if (! att2) return att1;
- var atts = [];
- att1.replace(/\*([0-9a-z]+)/g, function(_, a) {
- atts.push(pool.getAttrib(Changeset.parseNum(a)));
- return '';
- });
- att2.replace(/\*([0-9a-z]+)/g, function(_, a) {
- var pair = pool.getAttrib(Changeset.parseNum(a));
- var found = false;
- for(var i=0;i<atts.length;i++) {
- var oldPair = atts[i];
- if (oldPair[0] == pair[0]) {
- if (pair[1] || resultIsMutation) {
- oldPair[1] = pair[1];
- }
- else {
- atts.splice(i, 1);
- }
- found = true;
- break;
- }
- }
- if ((! found) && (pair[1] || resultIsMutation)) {
- atts.push(pair);
- }
- return '';
- });
- atts.sort();
- var buf = Changeset.stringAssembler();
- for(var i=0;i<atts.length;i++) {
- buf.append('*');
- buf.append(Changeset.numToString(pool.putAttrib(atts[i])));
- }
- //print(att1+" / "+att2+" / "+buf.toString());
- return buf.toString();
-};
-
-Changeset._slicerZipperFunc = function(attOp, csOp, opOut, pool) {
- // attOp is the op from the sequence that is being operated on, either an
- // attribution string or the earlier of two changesets being composed.
- // pool can be null if definitely not needed.
-
- //print(csOp.toSource()+" "+attOp.toSource()+" "+opOut.toSource());
- if (attOp.opcode == '-') {
- Changeset.copyOp(attOp, opOut);
- attOp.opcode = '';
- }
- else if (! attOp.opcode) {
- Changeset.copyOp(csOp, opOut);
- csOp.opcode = '';
- }
- else {
- switch (csOp.opcode) {
- case '-': {
- if (csOp.chars <= attOp.chars) {
- // delete or delete part
- if (attOp.opcode == '=') {
- opOut.opcode = '-';
- opOut.chars = csOp.chars;
- opOut.lines = csOp.lines;
- opOut.attribs = '';
- }
- attOp.chars -= csOp.chars;
- attOp.lines -= csOp.lines;
- csOp.opcode = '';
- if (! attOp.chars) {
- attOp.opcode = '';
- }
- }
- else {
- // delete and keep going
- if (attOp.opcode == '=') {
- opOut.opcode = '-';
- opOut.chars = attOp.chars;
- opOut.lines = attOp.lines;
- opOut.attribs = '';
- }
- csOp.chars -= attOp.chars;
- csOp.lines -= attOp.lines;
- attOp.opcode = '';
- }
- break;
- }
- case '+': {
- // insert
- Changeset.copyOp(csOp, opOut);
- csOp.opcode = '';
- break;
- }
- case '=': {
- if (csOp.chars <= attOp.chars) {
- // keep or keep part
- opOut.opcode = attOp.opcode;
- opOut.chars = csOp.chars;
- opOut.lines = csOp.lines;
- opOut.attribs = Changeset.composeAttributes(attOp.attribs, csOp.attribs,
- attOp.opcode == '=', pool);
- csOp.opcode = '';
- attOp.chars -= csOp.chars;
- attOp.lines -= csOp.lines;
- if (! attOp.chars) {
- attOp.opcode = '';
- }
- }
- else {
- // keep and keep going
- opOut.opcode = attOp.opcode;
- opOut.chars = attOp.chars;
- opOut.lines = attOp.lines;
- opOut.attribs = Changeset.composeAttributes(attOp.attribs, csOp.attribs,
- attOp.opcode == '=', pool);
- attOp.opcode = '';
- csOp.chars -= attOp.chars;
- csOp.lines -= attOp.lines;
- }
- break;
- }
- case '': {
- Changeset.copyOp(attOp, opOut);
- attOp.opcode = '';
- break;
- }
- }
- }
-};
-
-Changeset.applyToAttribution = function(cs, astr, pool) {
- var unpacked = Changeset.unpack(cs);
-
- return Changeset.applyZip(astr, 0, unpacked.ops, 0, function(op1, op2, opOut) {
- return Changeset._slicerZipperFunc(op1, op2, opOut, pool);
- });
-};
-
-/*Changeset.oneInsertedLineAtATimeOpIterator = function(opsStr, optStartIndex, charBank) {
- var iter = Changeset.opIterator(opsStr, optStartIndex);
- var bankIndex = 0;
-
-};*/
-
-Changeset.mutateAttributionLines = function(cs, lines, pool) {
- //dmesg(cs);
- //dmesg(lines.toSource()+" ->");
-
- var unpacked = Changeset.unpack(cs);
- var csIter = Changeset.opIterator(unpacked.ops);
- var csBank = unpacked.charBank;
- var csBankIndex = 0;
- // treat the attribution lines as text lines, mutating a line at a time
- var mut = Changeset.textLinesMutator(lines);
-
- var lineIter = null;
- function isNextMutOp() {
- return (lineIter && lineIter.hasNext()) || mut.hasMore();
- }
- function nextMutOp(destOp) {
- if ((!(lineIter && lineIter.hasNext())) && mut.hasMore()) {
- var line = mut.removeLines(1);
- lineIter = Changeset.opIterator(line);
- }
- if (lineIter && lineIter.hasNext()) {
- lineIter.next(destOp);
- }
- else {
- destOp.opcode = '';
- }
- }
- var lineAssem = null;
- function outputMutOp(op) {
- //print("outputMutOp: "+op.toSource());
- if (! lineAssem) {
- lineAssem = Changeset.mergingOpAssembler();
- }
- lineAssem.append(op);
- if (op.lines > 0) {
- Changeset.assert(op.lines == 1, "Can't have op.lines of ",op.lines," in attribution lines");
- // ship it to the mut
- mut.insert(lineAssem.toString(), 1);
- lineAssem = null;
- }
- }
-
- var csOp = Changeset.newOp();
- var attOp = Changeset.newOp();
- var opOut = Changeset.newOp();
- while (csOp.opcode || csIter.hasNext() || attOp.opcode || isNextMutOp()) {
- if ((! csOp.opcode) && csIter.hasNext()) {
- csIter.next(csOp);
- }
- //print(csOp.toSource()+" "+attOp.toSource()+" "+opOut.toSource());
- //print(csOp.opcode+"/"+csOp.lines+"/"+csOp.attribs+"/"+lineAssem+"/"+lineIter+"/"+(lineIter?lineIter.hasNext():null));
- //print("csOp: "+csOp.toSource());
- if ((! csOp.opcode) && (! attOp.opcode) &&
- (! lineAssem) && (! (lineIter && lineIter.hasNext()))) {
- break; // done
- }
- else if (csOp.opcode == '=' && csOp.lines > 0 && (! csOp.attribs) && (! attOp.opcode) &&
- (! lineAssem) && (! (lineIter && lineIter.hasNext()))) {
- // skip multiple lines; this is what makes small changes not order of the document size
- mut.skipLines(csOp.lines);
- //print("skipped: "+csOp.lines);
- csOp.opcode = '';
- }
- else if (csOp.opcode == '+') {
- if (csOp.lines > 1) {
- var firstLineLen = csBank.indexOf('\n', csBankIndex) + 1 - csBankIndex;
- Changeset.copyOp(csOp, opOut);
- csOp.chars -= firstLineLen;
- csOp.lines--;
- opOut.lines = 1;
- opOut.chars = firstLineLen;
- }
- else {
- Changeset.copyOp(csOp, opOut);
- csOp.opcode = '';
- }
- outputMutOp(opOut);
- csBankIndex += opOut.chars;
- opOut.opcode = '';
- }
- else {
- if ((! attOp.opcode) && isNextMutOp()) {
- nextMutOp(attOp);
- }
- //print("attOp: "+attOp.toSource());
- Changeset._slicerZipperFunc(attOp, csOp, opOut, pool);
- if (opOut.opcode) {
- outputMutOp(opOut);
- opOut.opcode = '';
- }
- }
- }
-
- Changeset.assert(! lineAssem, "line assembler not finished");
- mut.close();
-
- //dmesg("-> "+lines.toSource());
-};
-
-Changeset.joinAttributionLines = function(theAlines) {
- var assem = Changeset.mergingOpAssembler();
- for(var i=0;i<theAlines.length;i++) {
- var aline = theAlines[i];
- var iter = Changeset.opIterator(aline);
- while (iter.hasNext()) {
- assem.append(iter.next());
- }
- }
- return assem.toString();
-};
-
-Changeset.splitAttributionLines = function(attrOps, text) {
- var iter = Changeset.opIterator(attrOps);
- var assem = Changeset.mergingOpAssembler();
- var lines = [];
- var pos = 0;
-
- function appendOp(op) {
- assem.append(op);
- if (op.lines > 0) {
- lines.push(assem.toString());
- assem.clear();
- }
- pos += op.chars;
- }
-
- while (iter.hasNext()) {
- var op = iter.next();
- var numChars = op.chars;
- var numLines = op.lines;
- while (numLines > 1) {
- var newlineEnd = text.indexOf('\n', pos)+1;
- Changeset.assert(newlineEnd > 0, "newlineEnd <= 0 in splitAttributionLines");
- op.chars = newlineEnd - pos;
- op.lines = 1;
- appendOp(op);
- numChars -= op.chars;
- numLines -= op.lines;
- }
- if (numLines == 1) {
- op.chars = numChars;
- op.lines = 1;
- }
- appendOp(op);
- }
-
- return lines;
-};
-
-Changeset.splitTextLines = function(text) {
- return text.match(/[^\n]*(?:\n|[^\n]$)/g);
-};
-
-Changeset.compose = function(cs1, cs2, pool) {
- var unpacked1 = Changeset.unpack(cs1);
- var unpacked2 = Changeset.unpack(cs2);
- var len1 = unpacked1.oldLen;
- var len2 = unpacked1.newLen;
- Changeset.assert(len2 == unpacked2.oldLen, "mismatched composition");
- var len3 = unpacked2.newLen;
- var bankIter1 = Changeset.stringIterator(unpacked1.charBank);
- var bankIter2 = Changeset.stringIterator(unpacked2.charBank);
- var bankAssem = Changeset.stringAssembler();
-
- var newOps = Changeset.applyZip(unpacked1.ops, 0, unpacked2.ops, 0, function(op1, op2, opOut) {
- //var debugBuilder = Changeset.stringAssembler();
- //debugBuilder.append(Changeset.opString(op1));
- //debugBuilder.append(',');
- //debugBuilder.append(Changeset.opString(op2));
- //debugBuilder.append(' / ');
-
- var op1code = op1.opcode;
- var op2code = op2.opcode;
- if (op1code == '+' && op2code == '-') {
- bankIter1.skip(Math.min(op1.chars, op2.chars));
- }
- Changeset._slicerZipperFunc(op1, op2, opOut, pool);
- if (opOut.opcode == '+') {
- if (op2code == '+') {
- bankAssem.append(bankIter2.take(opOut.chars));
- }
- else {
- bankAssem.append(bankIter1.take(opOut.chars));
- }
- }
-
- //debugBuilder.append(Changeset.opString(op1));
- //debugBuilder.append(',');
- //debugBuilder.append(Changeset.opString(op2));
- //debugBuilder.append(' -> ');
- //debugBuilder.append(Changeset.opString(opOut));
- //print(debugBuilder.toString());
- });
-
- return Changeset.pack(len1, len3, newOps, bankAssem.toString());
-};
-
-Changeset.attributeTester = function(attribPair, pool) {
- // returns a function that tests if a string of attributes
- // (e.g. *3*4) contains a given attribute key,value that
- // is already present in the pool.
- if (! pool) {
- return never;
- }
- var attribNum = pool.putAttrib(attribPair, true);
- if (attribNum < 0) {
- return never;
- }
- else {
- var re = new RegExp('\\*'+Changeset.numToString(attribNum)+
- '(?!\\w)');
- return function(attribs) {
- return re.test(attribs);
- };
- }
- function never(attribs) { return false; }
-};
-
-Changeset.identity = function(N) {
- return Changeset.pack(N, N, "", "");
-};
-
-Changeset.makeSplice = function(oldFullText, spliceStart, numRemoved, newText, optNewTextAPairs, pool) {
- var oldLen = oldFullText.length;
-
- if (spliceStart >= oldLen) {
- spliceStart = oldLen - 1;
- }
- if (numRemoved > oldFullText.length - spliceStart - 1) {
- numRemoved = oldFullText.length - spliceStart - 1;
- }
- var oldText = oldFullText.substring(spliceStart, spliceStart+numRemoved);
- var newLen = oldLen + newText.length - oldText.length;
-
- var assem = Changeset.smartOpAssembler();
- assem.appendOpWithText('=', oldFullText.substring(0, spliceStart));
- assem.appendOpWithText('-', oldText);
- assem.appendOpWithText('+', newText, optNewTextAPairs, pool);
- assem.endDocument();
- return Changeset.pack(oldLen, newLen, assem.toString(), newText);
-};
-
-Changeset.toSplices = function(cs) {
- // get a list of splices, [startChar, endChar, newText]
-
- var unpacked = Changeset.unpack(cs);
- var splices = [];
-
- var oldPos = 0;
- var iter = Changeset.opIterator(unpacked.ops);
- var charIter = Changeset.stringIterator(unpacked.charBank);
- var inSplice = false;
- while (iter.hasNext()) {
- var op = iter.next();
- if (op.opcode == '=') {
- oldPos += op.chars;
- inSplice = false;
- }
- else {
- if (! inSplice) {
- splices.push([oldPos, oldPos, ""]);
- inSplice = true;
- }
- if (op.opcode == '-') {
- oldPos += op.chars;
- splices[splices.length-1][1] += op.chars;
- }
- else if (op.opcode == '+') {
- splices[splices.length-1][2] += charIter.take(op.chars);
- }
- }
- }
-
- return splices;
-};
-
-Changeset.characterRangeFollow = function(cs, startChar, endChar, insertionsAfter) {
- var newStartChar = startChar;
- var newEndChar = endChar;
- var splices = Changeset.toSplices(cs);
- var lengthChangeSoFar = 0;
- for(var i=0;i<splices.length;i++) {
- var splice = splices[i];
- var spliceStart = splice[0] + lengthChangeSoFar;
- var spliceEnd = splice[1] + lengthChangeSoFar;
- var newTextLength = splice[2].length;
- var thisLengthChange = newTextLength - (spliceEnd - spliceStart);
-
- if (spliceStart <= newStartChar && spliceEnd >= newEndChar) {
- // splice fully replaces/deletes range
- // (also case that handles insertion at a collapsed selection)
- if (insertionsAfter) {
- newStartChar = newEndChar = spliceStart;
- }
- else {
- newStartChar = newEndChar = spliceStart + newTextLength;
- }
- }
- else if (spliceEnd <= newStartChar) {
- // splice is before range
- newStartChar += thisLengthChange;
- newEndChar += thisLengthChange;
- }
- else if (spliceStart >= newEndChar) {
- // splice is after range
- }
- else if (spliceStart >= newStartChar && spliceEnd <= newEndChar) {
- // splice is inside range
- newEndChar += thisLengthChange;
- }
- else if (spliceEnd < newEndChar) {
- // splice overlaps beginning of range
- newStartChar = spliceStart + newTextLength;
- newEndChar += thisLengthChange;
- }
- else {
- // splice overlaps end of range
- newEndChar = spliceStart;
- }
-
- lengthChangeSoFar += thisLengthChange;
- }
-
- return [newStartChar, newEndChar];
-};
-
-Changeset.moveOpsToNewPool = function(cs, oldPool, newPool) {
- // works on changeset or attribution string
- var dollarPos = cs.indexOf('$');
- if (dollarPos < 0) {
- dollarPos = cs.length;
- }
- var upToDollar = cs.substring(0, dollarPos);
- var fromDollar = cs.substring(dollarPos);
- // order of attribs stays the same
- return upToDollar.replace(/\*([0-9a-z]+)/g, function(_, a) {
- var oldNum = Changeset.parseNum(a);
- var pair = oldPool.getAttrib(oldNum);
- var newNum = newPool.putAttrib(pair);
- return '*'+Changeset.numToString(newNum);
- }) + fromDollar;
-};
-
-Changeset.makeAttribution = function(text) {
- var assem = Changeset.smartOpAssembler();
- assem.appendOpWithText('+', text);
- return assem.toString();
-};
-
-// callable on a changeset, attribution string, or attribs property of an op
-Changeset.eachAttribNumber = function(cs, func) {
- var dollarPos = cs.indexOf('$');
- if (dollarPos < 0) {
- dollarPos = cs.length;
- }
- var upToDollar = cs.substring(0, dollarPos);
-
- upToDollar.replace(/\*([0-9a-z]+)/g, function(_, a) {
- func(Changeset.parseNum(a));
- return '';
- });
-};
-
-// callable on a changeset, attribution string, or attribs property of an op,
-// though it may easily create adjacent ops that can be merged.
-Changeset.filterAttribNumbers = function(cs, filter) {
- return Changeset.mapAttribNumbers(cs, filter);
-};
-
-Changeset.mapAttribNumbers = function(cs, func) {
- var dollarPos = cs.indexOf('$');
- if (dollarPos < 0) {
- dollarPos = cs.length;
- }
- var upToDollar = cs.substring(0, dollarPos);
-
- var newUpToDollar = upToDollar.replace(/\*([0-9a-z]+)/g, function(s, a) {
- var n = func(Changeset.parseNum(a));
- if (n === true) {
- return s;
- }
- else if ((typeof n) === "number") {
- return '*'+Changeset.numToString(n);
- }
- else {
- return '';
- }
- });
-
- return newUpToDollar + cs.substring(dollarPos);
-};
-
-Changeset.makeAText = function(text, attribs) {
- return { text: text, attribs: (attribs || Changeset.makeAttribution(text)) };
-};
-
-Changeset.applyToAText = function(cs, atext, pool) {
- return { text: Changeset.applyToText(cs, atext.text),
- attribs: Changeset.applyToAttribution(cs, atext.attribs, pool) };
-};
-
-Changeset.cloneAText = function(atext) {
- return { text: atext.text, attribs: atext.attribs };
-};
-
-Changeset.copyAText = function(atext1, atext2) {
- atext2.text = atext1.text;
- atext2.attribs = atext1.attribs;
-};
-
-Changeset.appendATextToAssembler = function(atext, assem) {
- // intentionally skips last newline char of atext
- var iter = Changeset.opIterator(atext.attribs);
- var op = Changeset.newOp();
- while (iter.hasNext()) {
- iter.next(op);
- if (! iter.hasNext()) {
- // last op, exclude final newline
- if (op.lines <= 1) {
- op.lines = 0;
- op.chars--;
- if (op.chars) {
- assem.append(op);
- }
- }
- else {
- var nextToLastNewlineEnd =
- atext.text.lastIndexOf('\n', atext.text.length-2) + 1;
- var lastLineLength = atext.text.length - nextToLastNewlineEnd - 1;
- op.lines--;
- op.chars -= (lastLineLength + 1);
- assem.append(op);
- op.lines = 0;
- op.chars = lastLineLength;
- if (op.chars) {
- assem.append(op);
- }
- }
- }
- else {
- assem.append(op);
- }
- }
-};
-
-Changeset.prepareForWire = function(cs, pool) {
- var newPool = new AttribPool();
- var newCs = Changeset.moveOpsToNewPool(cs, pool, newPool);
- return {translated: newCs, pool: newPool};
-};
-
-Changeset.isIdentity = function(cs) {
- var unpacked = Changeset.unpack(cs);
- return unpacked.ops == "" && unpacked.oldLen == unpacked.newLen;
-};
-
-Changeset.opAttributeValue = function(op, key, pool) {
- return Changeset.attribsAttributeValue(op.attribs, key, pool);
-};
-
-Changeset.attribsAttributeValue = function(attribs, key, pool) {
- var value = '';
- if (attribs) {
- Changeset.eachAttribNumber(attribs, function(n) {
- if (pool.getAttribKey(n) == key) {
- value = pool.getAttribValue(n);
- }
- });
- }
- return value;
-};
-
-Changeset.builder = function(oldLen) {
- var assem = Changeset.smartOpAssembler();
- var o = Changeset.newOp();
- var charBank = Changeset.stringAssembler();
-
- var self = {
- // attribs are [[key1,value1],[key2,value2],...] or '*0*1...' (no pool needed in latter case)
- keep: function(N, L, attribs, pool) {
- o.opcode = '=';
- o.attribs = (attribs &&
- Changeset.makeAttribsString('=', attribs, pool)) || '';
- o.chars = N;
- o.lines = (L || 0);
- assem.append(o);
- return self;
- },
- keepText: function(text, attribs, pool) {
- assem.appendOpWithText('=', text, attribs, pool);
- return self;
- },
- insert: function(text, attribs, pool) {
- assem.appendOpWithText('+', text, attribs, pool);
- charBank.append(text);
- return self;
- },
- remove: function(N, L) {
- o.opcode = '-';
- o.attribs = '';
- o.chars = N;
- o.lines = (L || 0);
- assem.append(o);
- return self;
- },
- toString: function() {
- assem.endDocument();
- var newLen = oldLen + assem.getLengthChange();
- return Changeset.pack(oldLen, newLen, assem.toString(),
- charBank.toString());
- }
- };
-
- return self;
-};
-
-Changeset.makeAttribsString = function(opcode, attribs, pool) {
- // makeAttribsString(opcode, '*3') or makeAttribsString(opcode, [['foo','bar']], myPool) work
- if (! attribs) {
- return '';
- }
- else if ((typeof attribs) == "string") {
- return attribs;
- }
- else if (pool && attribs && attribs.length) {
- if (attribs.length > 1) {
- attribs = attribs.slice();
- attribs.sort();
- }
- var result = [];
- for(var i=0;i<attribs.length;i++) {
- var pair = attribs[i];
- if (opcode == '=' || (opcode == '+' && pair[1])) {
- result.push('*'+Changeset.numToString(pool.putAttrib(pair)));
- }
- }
- return result.join('');
- }
-};
-
-// like "substring" but on a single-line attribution string
-Changeset.subattribution = function(astr, start, optEnd) {
- var iter = Changeset.opIterator(astr, 0);
- var assem = Changeset.smartOpAssembler();
- var attOp = Changeset.newOp();
- var csOp = Changeset.newOp();
- var opOut = Changeset.newOp();
-
- function doCsOp() {
- if (csOp.chars) {
- while (csOp.opcode && (attOp.opcode || iter.hasNext())) {
- if (! attOp.opcode) iter.next(attOp);
-
- if (csOp.opcode && attOp.opcode && csOp.chars >= attOp.chars &&
- attOp.lines > 0 && csOp.lines <= 0) {
- csOp.lines++;
- }
-
- Changeset._slicerZipperFunc(attOp, csOp, opOut, null);
- if (opOut.opcode) {
- assem.append(opOut);
- opOut.opcode = '';
- }
- }
- }
- }
-
- csOp.opcode = '-';
- csOp.chars = start;
-
- doCsOp();
-
- if (optEnd === undefined) {
- if (attOp.opcode) {
- assem.append(attOp);
- }
- while (iter.hasNext()) {
- iter.next(attOp);
- assem.append(attOp);
- }
- }
- else {
- csOp.opcode = '=';
- csOp.chars = optEnd - start;
- doCsOp();
- }
-
- return assem.toString();
-};
-
-Changeset.inverse = function(cs, lines, alines, pool) {
- // lines and alines are what the changeset is meant to apply to.
- // They may be arrays or objects with .get(i) and .length methods.
- // They include final newlines on lines.
- function lines_get(idx) {
- if (lines.get) {
- return lines.get(idx);
- }
- else {
- return lines[idx];
- }
- }
- function lines_length() {
- if ((typeof lines.length) == "number") {
- return lines.length;
- }
- else {
- return lines.length();
- }
- }
- function alines_get(idx) {
- if (alines.get) {
- return alines.get(idx);
- }
- else {
- return alines[idx];
- }
- }
- function alines_length() {
- if ((typeof alines.length) == "number") {
- return alines.length;
- }
- else {
- return alines.length();
- }
- }
-
- var curLine = 0;
- var curChar = 0;
- var curLineOpIter = null;
- var curLineOpIterLine;
- var curLineNextOp = Changeset.newOp('+');
-
- var unpacked = Changeset.unpack(cs);
- var csIter = Changeset.opIterator(unpacked.ops);
- var builder = Changeset.builder(unpacked.newLen);
-
- function consumeAttribRuns(numChars, func/*(len, attribs, endsLine)*/) {
-
- if ((! curLineOpIter) || (curLineOpIterLine != curLine)) {
- // create curLineOpIter and advance it to curChar
- curLineOpIter = Changeset.opIterator(alines_get(curLine));
- curLineOpIterLine = curLine;
- var indexIntoLine = 0;
- var done = false;
- while (! done) {
- curLineOpIter.next(curLineNextOp);
- if (indexIntoLine + curLineNextOp.chars >= curChar) {
- curLineNextOp.chars -= (curChar - indexIntoLine);
- done = true;
- }
- else {
- indexIntoLine += curLineNextOp.chars;
- }
- }
- }
-
- while (numChars > 0) {
- if ((! curLineNextOp.chars) && (! curLineOpIter.hasNext())) {
- curLine++;
- curChar = 0;
- curLineOpIterLine = curLine;
- curLineNextOp.chars = 0;
- curLineOpIter = Changeset.opIterator(alines_get(curLine));
- }
- if (! curLineNextOp.chars) {
- curLineOpIter.next(curLineNextOp);
- }
- var charsToUse = Math.min(numChars, curLineNextOp.chars);
- func(charsToUse, curLineNextOp.attribs,
- charsToUse == curLineNextOp.chars && curLineNextOp.lines > 0);
- numChars -= charsToUse;
- curLineNextOp.chars -= charsToUse;
- curChar += charsToUse;
- }
-
- if ((! curLineNextOp.chars) && (! curLineOpIter.hasNext())) {
- curLine++;
- curChar = 0;
- }
- }
-
- function skip(N, L) {
- if (L) {
- curLine += L;
- curChar = 0;
- }
- else {
- if (curLineOpIter && curLineOpIterLine == curLine) {
- consumeAttribRuns(N, function() {});
- }
- else {
- curChar += N;
- }
- }
- }
-
- function nextText(numChars) {
- var len = 0;
- var assem = Changeset.stringAssembler();
- var firstString = lines_get(curLine).substring(curChar);
- len += firstString.length;
- assem.append(firstString);
-
- var lineNum = curLine+1;
- while (len < numChars) {
- var nextString = lines_get(lineNum);
- len += nextString.length;
- assem.append(nextString);
- lineNum++;
- }
-
- return assem.toString().substring(0, numChars);
- }
-
- function cachedStrFunc(func) {
- var cache = {};
- return function(s) {
- if (! cache[s]) {
- cache[s] = func(s);
- }
- return cache[s];
- };
- }
-
- var attribKeys = [];
- var attribValues = [];
- while (csIter.hasNext()) {
- var csOp = csIter.next();
- if (csOp.opcode == '=') {
- if (csOp.attribs) {
- attribKeys.length = 0;
- attribValues.length = 0;
- Changeset.eachAttribNumber(csOp.attribs, function(n) {
- attribKeys.push(pool.getAttribKey(n));
- attribValues.push(pool.getAttribValue(n));
- });
- var undoBackToAttribs = cachedStrFunc(function(attribs) {
- var backAttribs = [];
- for(var i=0;i<attribKeys.length;i++) {
- var appliedKey = attribKeys[i];
- var appliedValue = attribValues[i];
- var oldValue = Changeset.attribsAttributeValue(attribs, appliedKey, pool);
- if (appliedValue != oldValue) {
- backAttribs.push([appliedKey, oldValue]);
- }
- }
- return Changeset.makeAttribsString('=', backAttribs, pool);
- });
- consumeAttribRuns(csOp.chars, function(len, attribs, endsLine) {
- builder.keep(len, endsLine ? 1 : 0, undoBackToAttribs(attribs));
- });
- }
- else {
- skip(csOp.chars, csOp.lines);
- builder.keep(csOp.chars, csOp.lines);
- }
- }
- else if (csOp.opcode == '+') {
- builder.remove(csOp.chars, csOp.lines);
- }
- else if (csOp.opcode == '-') {
- var textBank = nextText(csOp.chars);
- var textBankIndex = 0;
- consumeAttribRuns(csOp.chars, function(len, attribs, endsLine) {
- builder.insert(textBank.substr(textBankIndex, len), attribs);
- textBankIndex += len;
- });
- }
- }
-
- return Changeset.checkRep(builder.toString());
-};
-
-// %CLIENT FILE ENDS HERE%
-
-Changeset.follow = function(cs1, cs2, reverseInsertOrder, pool) {
- var unpacked1 = Changeset.unpack(cs1);
- var unpacked2 = Changeset.unpack(cs2);
- var len1 = unpacked1.oldLen;
- var len2 = unpacked2.oldLen;
- Changeset.assert(len1 == len2, "mismatched follow");
- var chars1 = Changeset.stringIterator(unpacked1.charBank);
- var chars2 = Changeset.stringIterator(unpacked2.charBank);
-
- var oldLen = unpacked1.newLen;
- var oldPos = 0;
- var newLen = 0;
-
- var hasInsertFirst = Changeset.attributeTester(['insertorder','first'],
- pool);
-
- var newOps = Changeset.applyZip(unpacked1.ops, 0, unpacked2.ops, 0, function(op1, op2, opOut) {
- if (op1.opcode == '+' || op2.opcode == '+') {
- var whichToDo;
- if (op2.opcode != '+') {
- whichToDo = 1;
- }
- else if (op1.opcode != '+') {
- whichToDo = 2;
- }
- else {
- // both +
- var firstChar1 = chars1.peek(1);
- var firstChar2 = chars2.peek(1);
- var insertFirst1 = hasInsertFirst(op1.attribs);
- var insertFirst2 = hasInsertFirst(op2.attribs);
- if (insertFirst1 && ! insertFirst2) {
- whichToDo = 1;
- }
- else if (insertFirst2 && ! insertFirst1) {
- whichToDo = 2;
- }
- // insert string that doesn't start with a newline first so as not to break up lines
- else if (firstChar1 == '\n' && firstChar2 != '\n') {
- whichToDo = 2;
- }
- else if (firstChar1 != '\n' && firstChar2 == '\n') {
- whichToDo = 1;
- }
- // break symmetry:
- else if (reverseInsertOrder) {
- whichToDo = 2;
- }
- else {
- whichToDo = 1;
- }
- }
- if (whichToDo == 1) {
- chars1.skip(op1.chars);
- opOut.opcode = '=';
- opOut.lines = op1.lines;
- opOut.chars = op1.chars;
- opOut.attribs = '';
- op1.opcode = '';
- }
- else {
- // whichToDo == 2
- chars2.skip(op2.chars);
- Changeset.copyOp(op2, opOut);
- op2.opcode = '';
- }
- }
- else if (op1.opcode == '-') {
- if (! op2.opcode) {
- op1.opcode = '';
- }
- else {
- if (op1.chars <= op2.chars) {
- op2.chars -= op1.chars;
- op2.lines -= op1.lines;
- op1.opcode = '';
- if (! op2.chars) {
- op2.opcode = '';
- }
- }
- else {
- op1.chars -= op2.chars;
- op1.lines -= op2.lines;
- op2.opcode = '';
- }
- }
- }
- else if (op2.opcode == '-') {
- Changeset.copyOp(op2, opOut);
- if (! op1.opcode) {
- op2.opcode = '';
- }
- else if (op2.chars <= op1.chars) {
- // delete part or all of a keep
- op1.chars -= op2.chars;
- op1.lines -= op2.lines;
- op2.opcode = '';
- if (! op1.chars) {
- op1.opcode = '';
- }
- }
- else {
- // delete all of a keep, and keep going
- opOut.lines = op1.lines;
- opOut.chars = op1.chars;
- op2.lines -= op1.lines;
- op2.chars -= op1.chars;
- op1.opcode = '';
- }
- }
- else if (! op1.opcode) {
- Changeset.copyOp(op2, opOut);
- op2.opcode = '';
- }
- else if (! op2.opcode) {
- Changeset.copyOp(op1, opOut);
- op1.opcode = '';
- }
- else {
- // both keeps
- opOut.opcode = '=';
- opOut.attribs = Changeset.followAttributes(op1.attribs, op2.attribs, pool);
- if (op1.chars <= op2.chars) {
- opOut.chars = op1.chars;
- opOut.lines = op1.lines;
- op2.chars -= op1.chars;
- op2.lines -= op1.lines;
- op1.opcode = '';
- if (! op2.chars) {
- op2.opcode = '';
- }
- }
- else {
- opOut.chars = op2.chars;
- opOut.lines = op2.lines;
- op1.chars -= op2.chars;
- op1.lines -= op2.lines;
- op2.opcode = '';
- }
- }
- switch (opOut.opcode) {
- case '=': oldPos += opOut.chars; newLen += opOut.chars; break;
- case '-': oldPos += opOut.chars; break;
- case '+': newLen += opOut.chars; break;
- }
- });
- newLen += oldLen - oldPos;
-
- return Changeset.pack(oldLen, newLen, newOps, unpacked2.charBank);
-};
-
-Changeset.followAttributes = function(att1, att2, pool) {
- // The merge of two sets of attribute changes to the same text
- // takes the lexically-earlier value if there are two values
- // for the same key. Otherwise, all key/value changes from
- // both attribute sets are taken. This operation is the "follow",
- // so a set of changes is produced that can be applied to att1
- // to produce the merged set.
- if ((! att2) || (! pool)) return '';
- if (! att1) return att2;
- var atts = [];
- att2.replace(/\*([0-9a-z]+)/g, function(_, a) {
- atts.push(pool.getAttrib(Changeset.parseNum(a)));
- return '';
- });
- att1.replace(/\*([0-9a-z]+)/g, function(_, a) {
- var pair1 = pool.getAttrib(Changeset.parseNum(a));
- for(var i=0;i<atts.length;i++) {
- var pair2 = atts[i];
- if (pair1[0] == pair2[0]) {
- if (pair1[1] <= pair2[1]) {
- // winner of merge is pair1, delete this attribute
- atts.splice(i, 1);
- }
- break;
- }
- }
- return '';
- });
- // we've only removed attributes, so they're already sorted
- var buf = Changeset.stringAssembler();
- for(var i=0;i<atts.length;i++) {
- buf.append('*');
- buf.append(Changeset.numToString(pool.putAttrib(atts[i])));
- }
- return buf.toString();
-};
diff --git a/trunk/etherpad/src/etherpad/collab/ace/easysync2_tests.js b/trunk/etherpad/src/etherpad/collab/ace/easysync2_tests.js
deleted file mode 100644
index 7a23dc0..0000000
--- a/trunk/etherpad/src/etherpad/collab/ace/easysync2_tests.js
+++ /dev/null
@@ -1,877 +0,0 @@
-// DO NOT EDIT THIS FILE, edit infrastructure/ace/www/easysync2_tests.js
-import("etherpad.collab.ace.easysync2.*")
-
-/**
- * Copyright 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS-IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-function runTests() {
-
- function print(str) {
- java.lang.System.out.println(str);
- }
-
- function assert(code, optMsg) {
- if (! eval(code)) throw new Error("FALSE: "+(optMsg || code));
- }
- function literal(v) {
- if ((typeof v) == "string") {
- return '"'+v.replace(/[\\\"]/g, '\\$1').replace(/\n/g, '\\n')+'"';
- }
- else return v.toSource();
- }
- function assertEqualArrays(a, b) {
- assert(literal(a)+".toSource() == "+literal(b)+".toSource()");
- }
- function assertEqualStrings(a, b) {
- assert(literal(a)+" == "+literal(b));
- }
-
- function throughIterator(opsStr) {
- var iter = Changeset.opIterator(opsStr);
- var assem = Changeset.opAssembler();
- while (iter.hasNext()) {
- assem.append(iter.next());
- }
- return assem.toString();
- }
-
- function throughSmartAssembler(opsStr) {
- var iter = Changeset.opIterator(opsStr);
- var assem = Changeset.smartOpAssembler();
- while (iter.hasNext()) {
- assem.append(iter.next());
- }
- assem.endDocument();
- return assem.toString();
- }
-
- (function() {
- print("> throughIterator");
- var x = '-c*3*4+6|3=az*asdf0*1*2*3+1=1-1+1*0+1=1-1+1|c=c-1';
- assert("throughIterator("+literal(x)+") == "+literal(x));
- })();
-
- (function() {
- print("> throughSmartAssembler");
- var x = '-c*3*4+6|3=az*asdf0*1*2*3+1=1-1+1*0+1=1-1+1|c=c-1';
- assert("throughSmartAssembler("+literal(x)+") == "+literal(x));
- })();
-
- function applyMutations(mu, arrayOfArrays) {
- arrayOfArrays.forEach(function (a) {
- var result = mu[a[0]].apply(mu, a.slice(1));
- if (a[0] == 'remove' && a[3]) {
- assertEqualStrings(a[3], result);
- }
- });
- }
-
- function mutationsToChangeset(oldLen, arrayOfArrays) {
- var assem = Changeset.smartOpAssembler();
- var op = Changeset.newOp();
- var bank = Changeset.stringAssembler();
- var oldPos = 0;
- var newLen = 0;
- arrayOfArrays.forEach(function (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;
- }
- });
- newLen += oldLen - oldPos;
- assem.endDocument();
- return Changeset.pack(oldLen, newLen, assem.toString(),
- bank.toString());
- }
-
- function runMutationTest(testId, origLines, muts, correct) {
- print("> runMutationTest#"+testId);
- var lines = origLines.slice();
- var mu = Changeset.textLinesMutator(lines);
- applyMutations(mu, muts);
- mu.close();
- assertEqualArrays(correct, lines);
-
- var inText = origLines.join('');
- var cs = mutationsToChangeset(inText.length, muts);
- lines = origLines.slice();
- Changeset.mutateTextLines(cs, lines);
- assertEqualArrays(correct, lines);
-
- var correctText = correct.join('');
- //print(literal(cs));
- var outText = Changeset.applyToText(cs, inText);
- assertEqualStrings(correctText, outText);
- }
-
- runMutationTest(1, ["apple\n", "banana\n", "cabbage\n", "duffle\n", "eggplant\n"],
- [['remove',1,0,"a"],['insert',"tu"],['remove',1,0,"p"],['skip',4,1],['skip',7,1],
- ['insert',"cream\npie\n",2],['skip',2],['insert',"bot"],['insert',"\n",1],
- ['insert',"bu"],['skip',3],['remove',3,1,"ge\n"],['remove',6,0,"duffle"]],
- ["tuple\n","banana\n","cream\n","pie\n", "cabot\n","bubba\n","eggplant\n"]);
-
- runMutationTest(2, ["apple\n", "banana\n", "cabbage\n", "duffle\n", "eggplant\n"],
- [['remove',1,0,"a"],['remove',1,0,"p"],['insert',"tu"],['skip',11,2],
- ['insert',"cream\npie\n",2],['skip',2],['insert',"bot"],['insert',"\n",1],
- ['insert',"bu"],['skip',3],['remove',3,1,"ge\n"],['remove',6,0,"duffle"]],
- ["tuple\n","banana\n","cream\n","pie\n", "cabot\n","bubba\n","eggplant\n"]);
-
- runMutationTest(3, ["apple\n", "banana\n", "cabbage\n", "duffle\n", "eggplant\n"],
- [['remove',6,1,"apple\n"],['skip',15,2],['skip',6],['remove',1,1,"\n"],
- ['remove',8,0,"eggplant"],['skip',1,1]],
- ["banana\n","cabbage\n","duffle\n"]);
-
- runMutationTest(4, ["15\n"],
- [['skip',1],['insert',"\n2\n3\n4\n",4],['skip',2,1]],
- ["1\n","2\n","3\n","4\n","5\n"]);
-
- runMutationTest(5, ["1\n","2\n","3\n","4\n","5\n"],
- [['skip',1],['remove',7,4,"\n2\n3\n4\n"],['skip',2,1]],
- ["15\n"]);
-
- runMutationTest(6, ["123\n","abc\n","def\n","ghi\n","xyz\n"],
- [['insert',"0"],['skip',4,1],['skip',4,1],['remove',8,2,"def\nghi\n"],['skip',4,1]],
- ["0123\n", "abc\n", "xyz\n"]);
-
- runMutationTest(7, ["apple\n", "banana\n", "cabbage\n", "duffle\n", "eggplant\n"],
- [['remove',6,1,"apple\n"],['skip',15,2,true],['skip',6,0,true],['remove',1,1,"\n"],
- ['remove',8,0,"eggplant"],['skip',1,1,true]],
- ["banana\n","cabbage\n","duffle\n"]);
-
- function poolOrArray(attribs) {
- if (attribs.getAttrib) {
- return attribs; // it's already an attrib pool
- }
- else {
- // assume it's an array of attrib strings to be split and added
- var p = new AttribPool();
- attribs.forEach(function (kv) { p.putAttrib(kv.split(',')); });
- return p;
- }
- }
-
- function runApplyToAttributionTest(testId, attribs, cs, inAttr, outCorrect) {
- print("> applyToAttribution#"+testId);
- var p = poolOrArray(attribs);
- var result = Changeset.applyToAttribution(
- Changeset.checkRep(cs), inAttr, p);
- assertEqualStrings(outCorrect, result);
- }
-
- // turn c<b>a</b>ctus\n into a<b>c</b>tusabcd\n
- runApplyToAttributionTest(1, ['bold,', 'bold,true'],
- "Z:7>3-1*0=1*1=1=3+4$abcd",
- "+1*1+1|1+5", "+1*1+1|1+8");
-
- // turn "david\ngreenspan\n" into "<b>david\ngreen</b>\n"
- runApplyToAttributionTest(2, ['bold,', 'bold,true'],
- "Z:g<4*1|1=6*1=5-4$",
- "|2+g", "*1|1+6*1+5|1+1");
-
- (function() {
- print("> mutatorHasMore");
- var lines = ["1\n", "2\n", "3\n", "4\n"];
- var mu;
-
- mu = Changeset.textLinesMutator(lines);
- assert(mu.hasMore()+' == true');
- mu.skip(8,4);
- assert(mu.hasMore()+' == false');
- mu.close();
- assert(mu.hasMore()+' == false');
-
- // still 1,2,3,4
- mu = Changeset.textLinesMutator(lines);
- assert(mu.hasMore()+' == true');
- mu.remove(2,1);
- assert(mu.hasMore()+' == true');
- mu.skip(2,1);
- assert(mu.hasMore()+' == true');
- mu.skip(2,1);
- assert(mu.hasMore()+' == true');
- mu.skip(2,1);
- assert(mu.hasMore()+' == false');
- mu.insert("5\n", 1);
- assert(mu.hasMore()+' == false');
- mu.close();
- assert(mu.hasMore()+' == false');
-
- // 2,3,4,5 now
- mu = Changeset.textLinesMutator(lines);
- assert(mu.hasMore()+' == true');
- mu.remove(6,3);
- assert(mu.hasMore()+' == true');
- mu.remove(2,1);
- assert(mu.hasMore()+' == false');
- mu.insert("hello\n", 1);
- assert(mu.hasMore()+' == false');
- mu.close();
- assert(mu.hasMore()+' == false');
-
- })();
-
- function runMutateAttributionTest(testId, attribs, cs, alines, outCorrect) {
- print("> runMutateAttributionTest#"+testId);
- var p = poolOrArray(attribs);
- var alines2 = Array.prototype.slice.call(alines);
- var result = Changeset.mutateAttributionLines(
- Changeset.checkRep(cs), alines2, p);
- assertEqualArrays(outCorrect, alines2);
-
- print("> runMutateAttributionTest#"+testId+".applyToAttribution");
- function removeQuestionMarks(a) { return a.replace(/\?/g, ''); }
- var inMerged = Changeset.joinAttributionLines(alines.map(removeQuestionMarks));
- var correctMerged = Changeset.joinAttributionLines(outCorrect.map(removeQuestionMarks));
- var mergedResult = Changeset.applyToAttribution(cs, inMerged, p);
- assertEqualStrings(correctMerged, mergedResult);
- }
-
- // turn 123\n 456\n 789\n into 123\n 4<b>5</b>6\n 789\n
- runMutateAttributionTest(1, ["bold,true"], "Z:c>0|1=4=1*0=1$", ["|1+4", "|1+4", "|1+4"],
- ["|1+4", "+1*0+1|1+2", "|1+4"]);
-
- // make a document bold
- runMutateAttributionTest(2, ["bold,true"], "Z:c>0*0|3=c$", ["|1+4", "|1+4", "|1+4"],
- ["*0|1+4", "*0|1+4", "*0|1+4"]);
-
- // clear bold on document
- runMutateAttributionTest(3, ["bold,","bold,true"], "Z:c>0*0|3=c$",
- ["*1+1+1*1+1|1+1", "+1*1+1|1+2", "*1+1+1*1+1|1+1"],
- ["|1+4", "|1+4", "|1+4"]);
-
- // add a character on line 3 of a document with 5 blank lines, and make sure
- // the optimization that skips purely-kept lines is working; if any attribution string
- // with a '?' is parsed it will cause an error.
- runMutateAttributionTest(4, ['foo,bar','line,1','line,2','line,3','line,4','line,5'],
- "Z:5>1|2=2+1$x",
- ["?*1|1+1", "?*2|1+1", "*3|1+1", "?*4|1+1", "?*5|1+1"],
- ["?*1|1+1", "?*2|1+1", "+1*3|1+1", "?*4|1+1", "?*5|1+1"]);
-
- var testPoolWithChars = (function() {
- var p = new AttribPool();
- p.putAttrib(['char','newline']);
- for(var i=1;i<36;i++) {
- p.putAttrib(['char',Changeset.numToString(i)]);
- }
- p.putAttrib(['char','']);
- return p;
- })();
-
- // based on runMutationTest#1
- runMutateAttributionTest(5, testPoolWithChars,
- "Z:11>7-2*t+1*u+1|2=b|2+a=2*b+1*o+1*t+1*0|1+1*b+1*u+1=3|1-3-6$"+
- "tucream\npie\nbot\nbu",
- ["*a+1*p+2*l+1*e+1*0|1+1",
- "*b+1*a+1*n+1*a+1*n+1*a+1*0|1+1",
- "*c+1*a+1*b+2*a+1*g+1*e+1*0|1+1",
- "*d+1*u+1*f+2*l+1*e+1*0|1+1",
- "*e+1*g+2*p+1*l+1*a+1*n+1*t+1*0|1+1"],
- ["*t+1*u+1*p+1*l+1*e+1*0|1+1",
- "*b+1*a+1*n+1*a+1*n+1*a+1*0|1+1",
- "|1+6",
- "|1+4",
- "*c+1*a+1*b+1*o+1*t+1*0|1+1",
- "*b+1*u+1*b+2*a+1*0|1+1",
- "*e+1*g+2*p+1*l+1*a+1*n+1*t+1*0|1+1"]);
-
- // based on runMutationTest#3
- runMutateAttributionTest(6, testPoolWithChars,
- "Z:11<f|1-6|2=f=6|1-1-8$",
- ["*a|1+6", "*b|1+7", "*c|1+8", "*d|1+7", "*e|1+9"],
- ["*b|1+7", "*c|1+8", "*d+6*e|1+1"]);
-
- // based on runMutationTest#4
- runMutateAttributionTest(7, testPoolWithChars,
- "Z:3>7=1|4+7$\n2\n3\n4\n",
- ["*1+1*5|1+2"],
- ["*1+1|1+1","|1+2","|1+2","|1+2","*5|1+2"]);
-
- // based on runMutationTest#5
- runMutateAttributionTest(8, testPoolWithChars,
- "Z:a<7=1|4-7$",
- ["*1|1+2","*2|1+2","*3|1+2","*4|1+2","*5|1+2"],
- ["*1+1*5|1+2"]);
-
- // based on runMutationTest#6
- runMutateAttributionTest(9, testPoolWithChars,
- "Z:k<7*0+1*10|2=8|2-8$0",
- ["*1+1*2+1*3+1|1+1","*a+1*b+1*c+1|1+1",
- "*d+1*e+1*f+1|1+1","*g+1*h+1*i+1|1+1","?*x+1*y+1*z+1|1+1"],
- ["*0+1|1+4", "|1+4", "?*x+1*y+1*z+1|1+1"]);
-
- runMutateAttributionTest(10, testPoolWithChars,
- "Z:6>4=1+1=1+1|1=1+1=1*0+1$abcd",
- ["|1+3", "|1+3"],
- ["|1+5", "+2*0+1|1+2"]);
-
-
- runMutateAttributionTest(11, testPoolWithChars,
- "Z:s>1|1=4=6|1+1$\n",
- ["*0|1+4", "*0|1+8", "*0+5|1+1", "*0|1+1", "*0|1+5", "*0|1+1", "*0|1+1", "*0|1+1", "|1+1"],
- ["*0|1+4", "*0+6|1+1", "*0|1+2", "*0+5|1+1", "*0|1+1", "*0|1+5", "*0|1+1", "*0|1+1", "*0|1+1", "|1+1"]);
-
- function randomInlineString(len, rand) {
- var assem = Changeset.stringAssembler();
- for(var i=0;i<len;i++) {
- assem.append(String.fromCharCode(rand.nextInt(26) + 97));
- }
- return assem.toString();
- }
-
- function randomMultiline(approxMaxLines, approxMaxCols, rand) {
- var numParts = rand.nextInt(approxMaxLines*2)+1;
- var txt = Changeset.stringAssembler();
- txt.append(rand.nextInt(2) ? '\n' : '');
- for(var i=0;i<numParts;i++) {
- if ((i % 2) == 0) {
- if (rand.nextInt(10)) {
- txt.append(randomInlineString(rand.nextInt(approxMaxCols)+1, rand));
- }
- else {
- txt.append('\n');
- }
- }
- else {
- txt.append('\n');
- }
- }
- return txt.toString();
- }
-
- function randomStringOperation(numCharsLeft, rand) {
- var result;
- switch(rand.nextInt(9)) {
- case 0: {
- // insert char
- result = {insert: randomInlineString(1, rand)};
- break;
- }
- case 1: {
- // delete char
- result = {remove: 1};
- break;
- }
- case 2: {
- // skip char
- result = {skip: 1};
- break;
- }
- case 3: {
- // insert small
- result = {insert: randomInlineString(rand.nextInt(4)+1, rand)};
- break;
- }
- case 4: {
- // delete small
- result = {remove: rand.nextInt(4)+1};
- break;
- }
- case 5: {
- // skip small
- result = {skip: rand.nextInt(4)+1};
- break;
- }
- case 6: {
- // insert multiline;
- result = {insert: randomMultiline(5, 20, rand)};
- break;
- }
- case 7: {
- // delete multiline
- result = {remove: Math.round(numCharsLeft * rand.nextDouble() * rand.nextDouble()) };
- break;
- }
- case 8: {
- // skip multiline
- result = {skip: Math.round(numCharsLeft * rand.nextDouble() * rand.nextDouble()) };
- break;
- }
- case 9: {
- // delete to end
- result = {remove: numCharsLeft};
- break;
- }
- case 10: {
- // skip to end
- result = {skip: numCharsLeft};
- break;
- }
- }
- var maxOrig = numCharsLeft - 1;
- if ('remove' in result) {
- result.remove = Math.min(result.remove, maxOrig);
- }
- else if ('skip' in result) {
- result.skip = Math.min(result.skip, maxOrig);
- }
- return result;
- }
-
- function randomTwoPropAttribs(opcode, rand) {
- // assumes attrib pool like ['apple,','apple,true','banana,','banana,true']
- if (opcode == '-' || rand.nextInt(3)) {
- return '';
- }
- else if (rand.nextInt(3)) {
- if (opcode == '+' || rand.nextInt(2)) {
- return '*'+Changeset.numToString(rand.nextInt(2)*2+1);
- }
- else {
- return '*'+Changeset.numToString(rand.nextInt(2)*2);
- }
- }
- else {
- if (opcode == '+' || rand.nextInt(4) == 0) {
- return '*1*3';
- }
- else {
- return ['*0*2', '*0*3', '*1*2'][rand.nextInt(3)];
- }
- }
- }
-
- function randomTestChangeset(origText, rand, withAttribs) {
- var charBank = Changeset.stringAssembler();
- var textLeft = origText; // always keep final newline
- var outTextAssem = Changeset.stringAssembler();
- var opAssem = Changeset.smartOpAssembler();
- var oldLen = origText.length;
-
- var nextOp = Changeset.newOp();
- function appendMultilineOp(opcode, txt) {
- nextOp.opcode = opcode;
- if (withAttribs) {
- nextOp.attribs = randomTwoPropAttribs(opcode, rand);
- }
- txt.replace(/\n|[^\n]+/g, function (t) {
- if (t == '\n') {
- nextOp.chars = 1;
- nextOp.lines = 1;
- opAssem.append(nextOp);
- }
- else {
- nextOp.chars = t.length;
- nextOp.lines = 0;
- opAssem.append(nextOp);
- }
- return '';
- });
- }
-
- function doOp() {
- var o = randomStringOperation(textLeft.length, rand);
- if (o.insert) {
- var txt = o.insert;
- charBank.append(txt);
- outTextAssem.append(txt);
- appendMultilineOp('+', txt);
- }
- else if (o.skip) {
- var txt = textLeft.substring(0, o.skip);
- textLeft = textLeft.substring(o.skip);
- outTextAssem.append(txt);
- appendMultilineOp('=', txt);
- }
- else if (o.remove) {
- var txt = textLeft.substring(0, o.remove);
- textLeft = textLeft.substring(o.remove);
- appendMultilineOp('-', txt);
- }
- }
-
- while (textLeft.length > 1) doOp();
- for(var i=0;i<5;i++) doOp(); // do some more (only insertions will happen)
-
- var outText = outTextAssem.toString()+'\n';
- opAssem.endDocument();
- var cs = Changeset.pack(oldLen, outText.length, opAssem.toString(), charBank.toString());
- Changeset.checkRep(cs);
- return [cs, outText];
- }
-
- function testCompose(randomSeed) {
- var rand = new java.util.Random(randomSeed);
- print("> testCompose#"+randomSeed);
-
- var p = new AttribPool();
-
- var startText = randomMultiline(10, 20, rand)+'\n';
-
- var x1 = randomTestChangeset(startText, rand);
- var change1 = x1[0];
- var text1 = x1[1];
-
- var x2 = randomTestChangeset(text1, rand);
- var change2 = x2[0];
- var text2 = x2[1];
-
- var x3 = randomTestChangeset(text2, rand);
- var change3 = x3[0];
- var text3 = x3[1];
-
- //print(literal(Changeset.toBaseTen(startText)));
- //print(literal(Changeset.toBaseTen(change1)));
- //print(literal(Changeset.toBaseTen(change2)));
- var change12 = Changeset.checkRep(Changeset.compose(change1, change2, p));
- var change23 = Changeset.checkRep(Changeset.compose(change2, change3, p));
- var change123 = Changeset.checkRep(Changeset.compose(change12, change3, p));
- var change123a = Changeset.checkRep(Changeset.compose(change1, change23, p));
- assertEqualStrings(change123, change123a);
-
- assertEqualStrings(text2, Changeset.applyToText(change12, startText));
- assertEqualStrings(text3, Changeset.applyToText(change23, text1));
- assertEqualStrings(text3, Changeset.applyToText(change123, startText));
- }
-
- for(var i=0;i<30;i++) testCompose(i);
-
- (function simpleComposeAttributesTest() {
- print("> simpleComposeAttributesTest");
- var p = new AttribPool();
- p.putAttrib(['bold','']);
- p.putAttrib(['bold','true']);
- var cs1 = Changeset.checkRep("Z:2>1*1+1*1=1$x");
- var cs2 = Changeset.checkRep("Z:3>0*0|1=3$");
- var cs12 = Changeset.checkRep(Changeset.compose(cs1, cs2, p));
- assertEqualStrings("Z:2>1+1*0|1=2$x", cs12);
- })();
-
- (function followAttributesTest() {
- var p = new AttribPool();
- p.putAttrib(['x','']);
- p.putAttrib(['x','abc']);
- p.putAttrib(['x','def']);
- p.putAttrib(['y','']);
- p.putAttrib(['y','abc']);
- p.putAttrib(['y','def']);
-
- function testFollow(a, b, afb, bfa, merge) {
- assertEqualStrings(afb, Changeset.followAttributes(a, b, p));
- assertEqualStrings(bfa, Changeset.followAttributes(b, a, p));
- assertEqualStrings(merge, Changeset.composeAttributes(a, afb, true, p));
- assertEqualStrings(merge, Changeset.composeAttributes(b, bfa, true, p));
- }
-
- testFollow('', '', '', '', '');
- testFollow('*0', '', '', '*0', '*0');
- testFollow('*0', '*0', '', '', '*0');
- testFollow('*0', '*1', '', '*0', '*0');
- testFollow('*1', '*2', '', '*1', '*1');
- testFollow('*0*1', '', '', '*0*1', '*0*1');
- testFollow('*0*4', '*2*3', '*3', '*0', '*0*3');
- testFollow('*0*4', '*2', '', '*0*4', '*0*4');
- })();
-
- function testFollow(randomSeed) {
- var rand = new java.util.Random(randomSeed + 1000);
- print("> testFollow#"+randomSeed);
-
- var p = new AttribPool();
-
- var startText = randomMultiline(10, 20, rand)+'\n';
-
- var cs1 = randomTestChangeset(startText, rand)[0];
- var cs2 = randomTestChangeset(startText, rand)[0];
-
- var afb = Changeset.checkRep(Changeset.follow(cs1, cs2, false, p));
- var bfa = Changeset.checkRep(Changeset.follow(cs2, cs1, true, p));
-
- var merge1 = Changeset.checkRep(Changeset.compose(cs1, afb));
- var merge2 = Changeset.checkRep(Changeset.compose(cs2, bfa));
-
- assertEqualStrings(merge1, merge2);
- }
-
- for(var i=0;i<30;i++) testFollow(i);
-
- function testSplitJoinAttributionLines(randomSeed) {
- var rand = new java.util.Random(randomSeed + 2000);
- print("> testSplitJoinAttributionLines#"+randomSeed);
-
- var doc = randomMultiline(10, 20, rand)+'\n';
-
- function stringToOps(str) {
- var assem = Changeset.mergingOpAssembler();
- var o = Changeset.newOp('+');
- o.chars = 1;
- for(var i=0;i<str.length;i++) {
- var c = str.charAt(i);
- o.lines = (c == '\n' ? 1 : 0);
- o.attribs = (c == 'a' || c == 'b' ? '*'+c : '');
- assem.append(o);
- }
- return assem.toString();
- }
-
- var theJoined = stringToOps(doc);
- var theSplit = doc.match(/[^\n]*\n/g).map(stringToOps);
-
- assertEqualArrays(theSplit, Changeset.splitAttributionLines(theJoined, doc));
- assertEqualStrings(theJoined, Changeset.joinAttributionLines(theSplit));
- }
-
- for(var i=0;i<10;i++) testSplitJoinAttributionLines(i);
-
- (function testMoveOpsToNewPool() {
- print("> testMoveOpsToNewPool");
-
- var pool1 = new AttribPool();
- var pool2 = new AttribPool();
-
- pool1.putAttrib(['baz','qux']);
- pool1.putAttrib(['foo','bar']);
-
- pool2.putAttrib(['foo','bar']);
-
- assertEqualStrings(Changeset.moveOpsToNewPool('Z:1>2*1+1*0+1$ab', pool1, pool2), 'Z:1>2*0+1*1+1$ab');
- assertEqualStrings(Changeset.moveOpsToNewPool('*1+1*0+1', pool1, pool2), '*0+1*1+1');
- })();
-
-
- (function testMakeSplice() {
- print("> testMakeSplice");
-
- var t = "a\nb\nc\n";
- var t2 = Changeset.applyToText(Changeset.makeSplice(t, 5, 0, "def"), t);
- assertEqualStrings("a\nb\ncdef\n", t2);
-
- })();
-
- (function testToSplices() {
- print("> testToSplices");
-
- var cs = Changeset.checkRep('Z:z>9*0=1=4-3+9=1|1-4-4+1*0+a$123456789abcdefghijk');
- var correctSplices = [[5, 8, "123456789"], [9, 17, "abcdefghijk"]];
- assertEqualArrays(correctSplices, Changeset.toSplices(cs));
- })();
-
- function testCharacterRangeFollow(testId, cs, oldRange, insertionsAfter, correctNewRange) {
- print("> testCharacterRangeFollow#"+testId);
-
- var cs = Changeset.checkRep(cs);
- assertEqualArrays(correctNewRange, Changeset.characterRangeFollow(cs, oldRange[0], oldRange[1],
- insertionsAfter));
-
- }
-
- testCharacterRangeFollow(1, 'Z:z>9*0=1=4-3+9=1|1-4-4+1*0+a$123456789abcdefghijk',
- [7, 10], false, [14, 15]);
- testCharacterRangeFollow(2, "Z:bc<6|x=b4|2-6$", [400, 407], false, [400, 401]);
- testCharacterRangeFollow(3, "Z:4>0-3+3$abc", [0,3], false, [3,3]);
- testCharacterRangeFollow(4, "Z:4>0-3+3$abc", [0,3], true, [0,0]);
- testCharacterRangeFollow(5, "Z:5>1+1=1-3+3$abcd", [1,4], false, [5,5]);
- testCharacterRangeFollow(6, "Z:5>1+1=1-3+3$abcd", [1,4], true, [2,2]);
- testCharacterRangeFollow(7, "Z:5>1+1=1-3+3$abcd", [0,6], false, [1,7]);
- testCharacterRangeFollow(8, "Z:5>1+1=1-3+3$abcd", [0,3], false, [1,2]);
- testCharacterRangeFollow(9, "Z:5>1+1=1-3+3$abcd", [2,5], false, [5,6]);
- testCharacterRangeFollow(10, "Z:2>1+1$a", [0,0], false, [1,1]);
- testCharacterRangeFollow(11, "Z:2>1+1$a", [0,0], true, [0,0]);
-
- (function testOpAttributeValue() {
- print("> testOpAttributeValue");
-
- var p = new AttribPool();
- p.putAttrib(['name','david']);
- p.putAttrib(['color','green']);
-
- assertEqualStrings("david", Changeset.opAttributeValue(Changeset.stringOp('*0*1+1'), 'name', p));
- assertEqualStrings("david", Changeset.opAttributeValue(Changeset.stringOp('*0+1'), 'name', p));
- assertEqualStrings("", Changeset.opAttributeValue(Changeset.stringOp('*1+1'), 'name', p));
- assertEqualStrings("", Changeset.opAttributeValue(Changeset.stringOp('+1'), 'name', p));
- assertEqualStrings("green", Changeset.opAttributeValue(Changeset.stringOp('*0*1+1'), 'color', p));
- assertEqualStrings("green", Changeset.opAttributeValue(Changeset.stringOp('*1+1'), 'color', p));
- assertEqualStrings("", Changeset.opAttributeValue(Changeset.stringOp('*0+1'), 'color', p));
- assertEqualStrings("", Changeset.opAttributeValue(Changeset.stringOp('+1'), 'color', p));
- })();
-
- function testAppendATextToAssembler(testId, atext, correctOps) {
- print("> testAppendATextToAssembler#"+testId);
-
- var assem = Changeset.smartOpAssembler();
- Changeset.appendATextToAssembler(atext, assem);
- assertEqualStrings(correctOps, assem.toString());
- }
-
- testAppendATextToAssembler(1, {text:"\n", attribs:"|1+1"}, "");
- testAppendATextToAssembler(2, {text:"\n\n", attribs:"|2+2"}, "|1+1");
- testAppendATextToAssembler(3, {text:"\n\n", attribs:"*x|2+2"}, "*x|1+1");
- testAppendATextToAssembler(4, {text:"\n\n", attribs:"*x|1+1|1+1"}, "*x|1+1");
- testAppendATextToAssembler(5, {text:"foo\n", attribs:"|1+4"}, "+3");
- testAppendATextToAssembler(6, {text:"\nfoo\n", attribs:"|2+5"}, "|1+1+3");
- testAppendATextToAssembler(7, {text:"\nfoo\n", attribs:"*x|2+5"}, "*x|1+1*x+3");
- testAppendATextToAssembler(8, {text:"\n\n\nfoo\n", attribs:"|2+2*x|2+5"}, "|2+2*x|1+1*x+3");
-
- function testMakeAttribsString(testId, pool, opcode, attribs, correctString) {
- print("> testMakeAttribsString#"+testId);
-
- var p = poolOrArray(pool);
- var str = Changeset.makeAttribsString(opcode, attribs, p);
- assertEqualStrings(correctString, str);
- }
-
- testMakeAttribsString(1, ['bold,'], '+', [['bold','']], '');
- testMakeAttribsString(2, ['abc,def','bold,'], '=', [['bold','']], '*1');
- testMakeAttribsString(3, ['abc,def','bold,true'], '+', [['abc','def'],['bold','true']], '*0*1');
- testMakeAttribsString(4, ['abc,def','bold,true'], '+', [['bold','true'],['abc','def']], '*0*1');
-
- function testSubattribution(testId, astr, start, end, correctOutput) {
- print("> testSubattribution#"+testId);
-
- var str = Changeset.subattribution(astr, start, end);
- assertEqualStrings(correctOutput, str);
- }
-
- testSubattribution(1, "+1", 0, 0, "");
- testSubattribution(2, "+1", 0, 1, "+1");
- testSubattribution(3, "+1", 0, undefined, "+1");
- testSubattribution(4, "|1+1", 0, 0, "");
- testSubattribution(5, "|1+1", 0, 1, "|1+1");
- testSubattribution(6, "|1+1", 0, undefined, "|1+1");
- testSubattribution(7, "*0+1", 0, 0, "");
- testSubattribution(8, "*0+1", 0, 1, "*0+1");
- testSubattribution(9, "*0+1", 0, undefined, "*0+1");
- testSubattribution(10, "*0|1+1", 0, 0, "");
- testSubattribution(11, "*0|1+1", 0, 1, "*0|1+1");
- testSubattribution(12, "*0|1+1", 0, undefined, "*0|1+1");
- testSubattribution(13, "*0+2+1*1+3", 0, 1, "*0+1");
- testSubattribution(14, "*0+2+1*1+3", 0, 2, "*0+2");
- testSubattribution(15, "*0+2+1*1+3", 0, 3, "*0+2+1");
- testSubattribution(16, "*0+2+1*1+3", 0, 4, "*0+2+1*1+1");
- testSubattribution(17, "*0+2+1*1+3", 0, 5, "*0+2+1*1+2");
- testSubattribution(18, "*0+2+1*1+3", 0, 6, "*0+2+1*1+3");
- testSubattribution(19, "*0+2+1*1+3", 0, 7, "*0+2+1*1+3");
- testSubattribution(20, "*0+2+1*1+3", 0, undefined, "*0+2+1*1+3");
- testSubattribution(21, "*0+2+1*1+3", 1, undefined, "*0+1+1*1+3");
- testSubattribution(22, "*0+2+1*1+3", 2, undefined, "+1*1+3");
- testSubattribution(23, "*0+2+1*1+3", 3, undefined, "*1+3");
- testSubattribution(24, "*0+2+1*1+3", 4, undefined, "*1+2");
- testSubattribution(25, "*0+2+1*1+3", 5, undefined, "*1+1");
- testSubattribution(26, "*0+2+1*1+3", 6, undefined, "");
- testSubattribution(27, "*0+2+1*1|1+3", 0, 1, "*0+1");
- testSubattribution(28, "*0+2+1*1|1+3", 0, 2, "*0+2");
- testSubattribution(29, "*0+2+1*1|1+3", 0, 3, "*0+2+1");
- testSubattribution(30, "*0+2+1*1|1+3", 0, 4, "*0+2+1*1+1");
- testSubattribution(31, "*0+2+1*1|1+3", 0, 5, "*0+2+1*1+2");
- testSubattribution(32, "*0+2+1*1|1+3", 0, 6, "*0+2+1*1|1+3");
- testSubattribution(33, "*0+2+1*1|1+3", 0, 7, "*0+2+1*1|1+3");
- testSubattribution(34, "*0+2+1*1|1+3", 0, undefined, "*0+2+1*1|1+3");
- testSubattribution(35, "*0+2+1*1|1+3", 1, undefined, "*0+1+1*1|1+3");
- testSubattribution(36, "*0+2+1*1|1+3", 2, undefined, "+1*1|1+3");
- testSubattribution(37, "*0+2+1*1|1+3", 3, undefined, "*1|1+3");
- testSubattribution(38, "*0+2+1*1|1+3", 4, undefined, "*1|1+2");
- testSubattribution(39, "*0+2+1*1|1+3", 5, undefined, "*1|1+1");
- testSubattribution(40, "*0+2+1*1|1+3", 1, 5, "*0+1+1*1+2");
- testSubattribution(41, "*0+2+1*1|1+3", 2, 6, "+1*1|1+3");
- testSubattribution(42, "*0+2+1*1+3", 2, 6, "+1*1+3");
-
- function testFilterAttribNumbers(testId, cs, filter, correctOutput) {
- print("> testFilterAttribNumbers#"+testId);
-
- var str = Changeset.filterAttribNumbers(cs, filter);
- assertEqualStrings(correctOutput, str);
- }
-
- testFilterAttribNumbers(1, "*0*1+1+2+3*1+4*2+5*0*2*1*b*c+6",
- function(n) { return (n%2) == 0; },
- "*0+1+2+3+4*2+5*0*2*c+6");
- testFilterAttribNumbers(2, "*0*1+1+2+3*1+4*2+5*0*2*1*b*c+6",
- function(n) { return (n%2) == 1; },
- "*1+1+2+3*1+4+5*1*b+6");
-
- function testInverse(testId, cs, lines, alines, pool, correctOutput) {
- print("> testInverse#"+testId);
-
- pool = poolOrArray(pool);
- var str = Changeset.inverse(Changeset.checkRep(cs), lines, alines, pool);
- assertEqualStrings(correctOutput, str);
- }
-
- // take "FFFFTTTTT" and apply "-FT--FFTT", the inverse of which is "--F--TT--"
- testInverse(1, "Z:9>0=1*0=1*1=1=2*0=2*1|1=2$", null, ["+4*1+5"], ['bold,','bold,true'],
- "Z:9>0=2*0=1=2*1=2$");
-
- function testMutateTextLines(testId, cs, lines, correctLines) {
- print("> testMutateTextLines#"+testId);
-
- var a = lines.slice();
- Changeset.mutateTextLines(cs, a);
- assertEqualArrays(correctLines, a);
- }
-
- testMutateTextLines(1, "Z:4<1|1-2-1|1+1+1$\nc", ["a\n", "b\n"], ["\n", "c\n"]);
- testMutateTextLines(2, "Z:4>0|1-2-1|2+3$\nc\n", ["a\n", "b\n"], ["\n", "c\n", "\n"]);
-
- function testInverseRandom(randomSeed) {
- var rand = new java.util.Random(randomSeed + 3000);
- print("> testInverseRandom#"+randomSeed);
-
- var p = poolOrArray(['apple,','apple,true','banana,','banana,true']);
-
- var startText = randomMultiline(10, 20, rand)+'\n';
- var alines = Changeset.splitAttributionLines(Changeset.makeAttribution(startText), startText);
- var lines = startText.slice(0,-1).split('\n').map(function(s) { return s+'\n'; });
-
- var stylifier = randomTestChangeset(startText, rand, true)[0];
-
- //print(alines.join('\n'));
- Changeset.mutateAttributionLines(stylifier, alines, p);
- //print(stylifier);
- //print(alines.join('\n'));
- Changeset.mutateTextLines(stylifier, lines);
-
- var changeset = randomTestChangeset(lines.join(''), rand, true)[0];
- var inverseChangeset = Changeset.inverse(changeset, lines, alines, p);
-
- var origLines = lines.slice();
- var origALines = alines.slice();
-
- Changeset.mutateTextLines(changeset, lines);
- Changeset.mutateAttributionLines(changeset, alines, p);
- //print(origALines.join('\n'));
- //print(changeset);
- //print(inverseChangeset);
- //print(origLines.map(function(s) { return '1: '+s.slice(0,-1); }).join('\n'));
- //print(lines.map(function(s) { return '2: '+s.slice(0,-1); }).join('\n'));
- //print(alines.join('\n'));
- Changeset.mutateTextLines(inverseChangeset, lines);
- Changeset.mutateAttributionLines(inverseChangeset, alines, p);
- //print(lines.map(function(s) { return '3: '+s.slice(0,-1); }).join('\n'));
-
- assertEqualArrays(origLines, lines);
- assertEqualArrays(origALines, alines);
- }
-
- for(var i=0;i<30;i++) testInverseRandom(i);
-} \ No newline at end of file