import { Cursor } from "./cursor"; import { StrandPath } from "./path"; export class SelectionRange { readonly anchor: Cursor; readonly head: Cursor; constructor(anchor = new Cursor(), head = anchor.clone()) { this.anchor = anchor; this.head = head; } get commonStrandPath(): StrandPath { const minLength = Math.min( this.anchor.strandPath.length, this.head.strandPath.length ); const commonPath: StrandPath = []; for (let i = 0; i < minLength; i++) { const anchorLevel = this.anchor.strandPath[i]; const headLevel = this.head.strandPath[i]; if ( anchorLevel.tokenIndex === headLevel.tokenIndex && anchorLevel.childIndex === headLevel.childIndex ) { commonPath.push(anchorLevel); } else { break; } } return commonPath; } get commonStartPos(): number { const anchorPos = this.projectCursorPosOnCommonStrand(this.anchor, "left"); const headPos = this.projectCursorPosOnCommonStrand(this.head, "left"); return Math.min(anchorPos, headPos); } get commonEndPos(): number { const anchorPos = this.projectCursorPosOnCommonStrand(this.anchor, "right"); const headPos = this.projectCursorPosOnCommonStrand(this.head, "right"); return Math.max(anchorPos, headPos); } private projectCursorPosOnCommonStrand( cursor: Cursor, bias: "left" | "right" ): number { const commonStrandPath = this.commonStrandPath; for (let i = 0; i < commonStrandPath.length; i++) { const { tokenIndex, childIndex } = cursor.strandPath[i]; const commonLevel = commonStrandPath[i]; if ( tokenIndex !== commonLevel.tokenIndex || childIndex !== commonLevel.childIndex ) { throw new Error("Cursor is not on or within the common strand"); } } if (cursor.strandPath.length === commonStrandPath.length) { return cursor.pos; } const { tokenIndex } = cursor.strandPath[commonStrandPath.length]; if (bias === "left") { return tokenIndex; } else { return tokenIndex + 1; } } isCollapsed(): boolean { return this.anchor.equals(this.head); } }