diff --git a/lib/rga.js b/lib/rga.js index 7f3fed5..efe52bd 100644 --- a/lib/rga.js +++ b/lib/rga.js @@ -179,78 +179,6 @@ RGA.prototype = { return node; }, - getRowColumnBefore: function (t) { - if (t === this.left.timestamp) - throw new Error("no position before the left edge of the document"); - var target = this._index.get(t); - if (target === undefined) - throw new Error("timestamp not present in document"); - var r = 0, c = 0; - for (var node = this.left.next; node != target; node = node.next) { - if (!node.removed) { - if (node.atom === "\n") { - r++; - c = 0; - } else { - c++; - } - } - } - return {row: r, column: c}; - }, - - getRowColumnAfter: function (t, wt) { - var target = this._index.get(t); - if (target === undefined) - throw new Error("timestamp not present in document"); - - var r = 0, c = -1; // c will be incremented to zero in the first pass through the loop - function advance(node) { - if (!node.removed) { - if (node.atom === "\n") { - r++; - c = 0; - } else { - c++; - } - } - } - - for (var node = this.left; ; node = node.next) { - advance(node); - if (node === target) - break; - } - while (node.next && wt < node.next.timestamp) { - node = node.next; - advance(node); - } - - return {row: r, column: c}; - }, - - insertRowColumn: function (source, line, column, text) { - var r = 1; - var u = this.getNodeAt(line, column).timestamp; - for (var i = 0; i < text.length; i++) { - var node = {atom: text[i], timestamp: this._timestamp()}; - this.downstream(source, {type: "addRight", t: u, w: node}); - u = node.timestamp; - } - }, - - removeRowColumn: function (source, line, column, length) { - var node = this.getNodeAt(line, column); - for (var i = 0; i < length; i++) { - node = node.next; - while (node && node.removed) - node = node.next; - if (!node) - throw new Error("tried to remove more characters than exist in document"); - this.downstream(source, {type: "remove", t: node.timestamp}); - } - }, - timestampToIndex: function (t) { var offset = -1; for (var node = this.left; node.timestamp !== t; node = node.next) { @@ -413,6 +341,56 @@ RGA.AceEditorRGA = function AceEditorRGA(id, editor, history, queue) { RGA.AceEditorRGA.prototype = Object.create(RGA.prototype); Object.assign(RGA.AceEditorRGA.prototype, { + getRowColumnBefore: function (t) { + if (t === this.left.timestamp) + throw new Error("no position before the left edge of the document"); + var target = this._index.get(t); + if (target === undefined) + throw new Error("timestamp not present in document"); + var r = 0, c = 0; + for (var node = this.left.next; node != target; node = node.next) { + if (!node.removed) { + if (node.atom === "\n") { + r++; + c = 0; + } else { + c++; + } + } + } + return {row: r, column: c}; + }, + + getRowColumnAfter: function (t, wt) { + var target = this._index.get(t); + if (target === undefined) + throw new Error("timestamp not present in document"); + + var r = 0, c = -1; // c will be incremented to zero in the first pass through the loop + function advance(node) { + if (!node.removed) { + if (node.atom === "\n") { + r++; + c = 0; + } else { + c++; + } + } + } + + for (var node = this.left; ; node = node.next) { + advance(node); + if (node === target) + break; + } + while (node.next && wt < node.next.timestamp) { + node = node.next; + advance(node); + } + + return {row: r, column: c}; + }, + _assertInSync: function () { var erText = this.text(); if (this._lastText != erText) { diff --git a/test/test.js b/test/test.js index 0726694..a1bc0c7 100644 --- a/test/test.js +++ b/test/test.js @@ -221,130 +221,4 @@ describe("RGA", () => { assert.strictEqual(p._subscribers.length, 0); }); }); - - describe("insertRowColumn", () => { - it("inserts text", () => { - var p = new RGA(0); - p.insertRowColumn(p, 0, 0, "hello world\n"); - assert.strictEqual(p.text(), "hello world\n"); - p.insertRowColumn(p, 0, 0, "## "); - assert.strictEqual(p.text(), "## hello world\n"); - p.insertRowColumn(p, 1, 0, "\nThis program prints a greeting to stdout.\n\n" + - " print 'hello'\n"); - assert.strictEqual(p.text(), "## hello world\n\n" + - "This program prints a greeting to stdout.\n\n" + - " print 'hello'\n"); - p.insertRowColumn(p, 4, 16, " world"); - assert.strictEqual(p.text(), "## hello world\n\n" + - "This program prints a greeting to stdout.\n\n" + - " print 'hello world'\n"); - }); - - it("can get row and column numbers right even after deletions", () => { - var p = new RGA(0); - p.insertRowColumn(p, 0, 0, "ab1234ch"); - p.removeRowColumn(p, 0, 2, 4); - p.insertRowColumn(p, 0, 3, "defg"); - assert.strictEqual(p.text(), "abcdefgh"); - p.insertRowColumn(p, 0, 8, "\n1234567\n89\nijkqrs"); - p.removeRowColumn(p, 0, 8, 12); - p.insertRowColumn(p, 0, 11, "lmnop"); - assert.strictEqual(p.text(), "abcdefghijklmnopqrs"); - }); - - var arbitraryChar = jsc.elements(['a', 'b', 'c', 'X', 'Y', 'Z', '0', '\n', ' ', '\u1234']); - var arbitraryStr = jsc.elements(['', 'xyzzy', 'hello\nworld\n', '\n\n\n\n', '\nok', - Array(16).join("wat" - 1) + " Batman!"]); - - var arbitraryRGA = jsc.bless({ - generator: function () { - var len = jsc.random(0, 30), pRemove = jsc.random.number(0, 1); - var timestamps = []; - for (var i = 0; i < len; i++) - timestamps[i] = i; - - var h = new RGA(0); - var prev = h.left.timestamp; - for (var i = 0; i < len; i++) { - var tIndex = jsc.random(0, timestamps.length - 1); - var t = timestamps[tIndex]; - timestamps[tIndex] = timestamps[timestamps.length - 1]; - timestamps.length--; - - var w = {atom: arbitraryChar.generator(), timestamp: t}; - h._downstream(h.downstream, {type: "addRight", t: prev, w: w}); - if (jsc.random.number(0, 1) < pRemove) - h._downstream(h.downstream, {type: "remove", t: t}); - prev = t; - } - return h; - } - }); - - function randomPositionIn(str) { - var offset = jsc.random(0, str.length); // note: inclusive of str.length - var lines = str.slice(0, offset).split("\n"); - var row = lines.length - 1; - return { - offset: offset, - row: row, - column: lines[row].length - }; - } - - var arbitraryTestCase = jsc.bless({ - generator: function () { - var a = arbitraryRGA.generator(); - var text = a.text(); - return { - history: a.history(), - text: text, - loc: randomPositionIn(text), - insert: arbitraryStr.generator() - }; - } - }); - - jsc.property("inserts text (in random test cases)", arbitraryTestCase, testCase => { - var p = new RGA(0, testCase.history); - p.insertRowColumn(p, testCase.loc.row, testCase.loc.column, testCase.insert); - var text = testCase.text; - var offset = testCase.loc.offset; - return p.text() === text.slice(0, offset) + testCase.insert + text.slice(offset); - }); - }); - - describe("removeRowColumn", () => { - it("can remove text at the beginning of the array", () => { - var p = new RGA(0); - type(p, p.left.timestamp, "abcdefg"); - p.removeRowColumn(p, 0, 0, 3); - assert.strictEqual(p.text(), "defg"); - }); - - it("can remove text at the end of the array", () => { - var p = new RGA(0); - type(p, p.left.timestamp, "hi\nthere\nyou kid"); - p.removeRowColumn(p, 2, 3, 4); - assert.strictEqual(p.text(), "hi\nthere\nyou"); - }); - - it("can remove everything from the array", () => { - var p = new RGA(0); - type(p, p.left.timestamp, "good morning, how are\nyou today?"); - p.removeRowColumn(p, 0, 0, p.text().length); - assert.strictEqual(p.text(), ""); - }); - - it("can remove characters when some characters have already been removed", () => { - var p = new RGA(0); - type(p, p.left.timestamp, "abcdefg"); - p.removeRowColumn(p, 0, 3, 1); - assert.strictEqual(p.text(), "abcefg"); - p.removeRowColumn(p, 0, 2, 2); - assert.strictEqual(p.text(), "abfg"); - p.removeRowColumn(p, 0, 0, 4); - assert.strictEqual(p.text(), ""); - }); - }); });