element-web/src/editor/position.ts
David Langley 491f0cd08a
Change license (#13)
* Copyright headers 1

* Licence headers 2

* Copyright Headers 3

* Copyright Headers 4

* Copyright Headers 5

* Copyright Headers 6

* Copyright headers 7

* Add copyright headers for html and config file

* Replace license files and update package.json

* Update with CLA

* lint
2024-09-09 13:57:16 +00:00

134 lines
4.3 KiB
TypeScript

/*
Copyright 2024 New Vector Ltd.
Copyright 2019 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import DocumentOffset from "./offset";
import EditorModel from "./model";
import { Part } from "./parts";
export interface IPosition {
index: number;
offset: number;
}
type Callback = (part: Part, startIdx: number, endIdx: number) => void;
export type Predicate = (index: number, offset: number, part: Part) => boolean;
export default class DocumentPosition implements IPosition {
public constructor(
public readonly index: number,
public readonly offset: number,
) {}
public compare(otherPos: DocumentPosition): number {
if (this.index === otherPos.index) {
return this.offset - otherPos.offset;
} else {
return this.index - otherPos.index;
}
}
public iteratePartsBetween(other: DocumentPosition, model: EditorModel, callback: Callback): void {
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);
}
}
public forwardsWhile(model: EditorModel, predicate: Predicate): DocumentPosition {
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;
}
}
return this; // impossible but Typescript doesn't believe us
}
public backwardsWhile(model: EditorModel, predicate: Predicate): DocumentPosition {
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;
}
}
return this; // impossible but Typescript doesn't believe us
}
public asOffset(model: EditorModel): DocumentOffset {
if (this.index === -1) {
return new DocumentOffset(0, true);
}
let offset = 0;
for (let i = 0; i < this.index; ++i) {
offset += model.parts[i].text.length;
}
offset += this.offset;
const lastPart = model.parts[this.index];
const atEnd = !lastPart || offset >= lastPart.text.length; // if no last part, we're at the end
return new DocumentOffset(offset, atEnd);
}
public isAtEnd(model: EditorModel): boolean {
if (model.parts.length === 0) {
return true;
}
const lastPartIdx = model.parts.length - 1;
const lastPart = model.parts[lastPartIdx];
return this.index === lastPartIdx && this.offset === lastPart.text.length;
}
public isAtStart(): boolean {
return this.index === 0 && this.offset === 0;
}
}