diff --git a/src/editor/position.js b/src/editor/position.js index c771482012..5dcb31fe65 100644 --- a/src/editor/position.js +++ b/src/editor/position.js @@ -35,4 +35,73 @@ export default class DocumentPosition { return this._index - otherPos._index; } } + + iteratePartsBetween(other, model, callback) { + if (this.index === -1 || other.index === -1) { + return; + } + const [startPos, endPos] = this.compare(other) < 0 ? [this, other] : [other, this]; + if (startPos.index === endPos.index) { + callback(model.parts[this.index], startPos.offset, endPos.offset); + } else { + const firstPart = model.parts[startPos.index]; + callback(firstPart, startPos.offset, firstPart.text.length); + for (let i = startPos.index + 1; i < endPos.index; ++i) { + const part = model.parts[i]; + callback(part, 0, part.text.length); + } + const lastPart = model.parts[endPos.index]; + callback(lastPart, 0, endPos.offset); + } + } + + forwardsWhile(model, predicate) { + if (this.index === -1) { + return this; + } + + let {index, offset} = this; + const {parts} = model; + while (index < parts.length) { + const part = parts[index]; + while (offset < part.text.length) { + if (!predicate(index, offset, part)) { + return new DocumentPosition(index, offset); + } + offset += 1; + } + // end reached + if (index === (parts.length - 1)) { + return new DocumentPosition(index, offset); + } else { + index += 1; + offset = 0; + } + } + } + + backwardsWhile(model, predicate) { + if (this.index === -1) { + return this; + } + + let {index, offset} = this; + const parts = model.parts; + while (index >= 0) { + const part = parts[index]; + while (offset > 0) { + if (!predicate(index, offset - 1, part)) { + return new DocumentPosition(index, offset); + } + offset -= 1; + } + // start reached + if (index === 0) { + return new DocumentPosition(index, offset); + } else { + index -= 1; + offset = parts[index].text.length; + } + } + } } diff --git a/test/editor/position-test.js b/test/editor/position-test.js new file mode 100644 index 0000000000..7ac4284c60 --- /dev/null +++ b/test/editor/position-test.js @@ -0,0 +1,80 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +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. +*/ + +import expect from 'expect'; +import EditorModel from "../../src/editor/model"; +import {createPartCreator} from "./mock"; + +function createRenderer() { + const render = (c) => { + render.caret = c; + render.count += 1; + }; + render.count = 0; + render.caret = null; + return render; +} + +describe('editor/position', function() { + it('move first position backward in empty model', function() { + const model = new EditorModel([], createPartCreator(), createRenderer()); + const pos = model.positionForOffset(0, true); + const pos2 = pos.backwardsWhile(model, () => true); + expect(pos).toBe(pos2); + }); + it('move first position forwards in empty model', function() { + const model = new EditorModel([], createPartCreator(), createRenderer()); + const pos = model.positionForOffset(0, true); + const pos2 = pos.forwardsWhile(() => true); + expect(pos).toBe(pos2); + }); + it('move forwards within one part', function() { + const pc = createPartCreator(); + const model = new EditorModel([pc.plain("hello")], pc, createRenderer()); + const pos = model.positionForOffset(1); + let n = 3; + const pos2 = pos.forwardsWhile(model, () => { n -= 1; return n >= 0; }); + expect(pos2.index).toBe(0); + expect(pos2.offset).toBe(4); + }); + it('move forwards crossing to other part', function() { + const pc = createPartCreator(); + const model = new EditorModel([pc.plain("hello"), pc.plain(" world")], pc, createRenderer()); + const pos = model.positionForOffset(4); + let n = 3; + const pos2 = pos.forwardsWhile(model, () => { n -= 1; return n >= 0; }); + expect(pos2.index).toBe(1); + expect(pos2.offset).toBe(2); + }); + it('move backwards within one part', function() { + const pc = createPartCreator(); + const model = new EditorModel([pc.plain("hello")], pc, createRenderer()); + const pos = model.positionForOffset(4); + let n = 3; + const pos2 = pos.backwardsWhile(model, () => { n -= 1; return n >= 0; }); + expect(pos2.index).toBe(0); + expect(pos2.offset).toBe(1); + }); + it('move backwards crossing to other part', function() { + const pc = createPartCreator(); + const model = new EditorModel([pc.plain("hello"), pc.plain(" world")], pc, createRenderer()); + const pos = model.positionForOffset(7); + let n = 3; + const pos2 = pos.backwardsWhile(model, () => { n -= 1; return n >= 0; }); + expect(pos2.index).toBe(0); + expect(pos2.offset).toBe(4); + }); +});