scroll/src/common/row.ts

170 lines
3.3 KiB
JavaScript

import { SCROLL_TAB_SIZE } from './config.ts';
import * as Util from './fns.ts';
/**
* One row of text in the current document
*/
export class Row {
/**
* The actual characters in the current row
*/
chars: string[] = [];
/**
* The characters rendered for the current row
* (like replacing tabs with spaces)
*/
render: string[] = [];
private constructor(s: string | string[] = '') {
this.chars = Array.isArray(s) ? s : Util.strChars(s);
this.render = [];
}
public get size(): number {
return this.chars.length;
}
public get rsize(): number {
return this.render.length;
}
public rstring(offset: number = 0): string {
return this.render.slice(offset).join('');
}
public static default(): Row {
return new Row();
}
public static from(s: string | string[] | Row): Row {
if (s instanceof Row) {
return s;
}
return new Row(s);
}
public append(s: string): void {
this.chars = this.chars.concat(Util.strChars(s));
this.updateRender();
}
public insertChar(at: number, c: string): void {
const newSlice = Util.strChars(c);
if (at >= this.size) {
this.chars = this.chars.concat(newSlice);
} else {
this.chars = Util.arrayInsert(this.chars, at + 1, newSlice);
}
}
public split(at: number): Row {
const newRow = new Row(this.chars.slice(at));
this.chars = this.chars.slice(0, at);
this.updateRender();
return newRow;
}
public delete(at: number): void {
if (at >= this.size) {
return;
}
this.chars.splice(at, 1);
}
public find(s: string, offset: number = 0): number | null {
const thisStr = this.toString();
if (!this.toString().includes(s)) {
return null;
}
const byteCount = thisStr.indexOf(s, this.charIndexToByteIndex(offset));
// In many cases, the string length will
// equal the number of characters. So
// searching is fairly easy
if (thisStr.length === this.chars.length) {
return byteCount;
}
// Emoji/Extended Unicode-friendly search
return this.byteIndexToCharIndex(byteCount);
}
public cxToRx(cx: number): number {
let rx = 0;
let j;
for (j = 0; j < cx; j++) {
if (this.chars[j] === '\t') {
rx += (SCROLL_TAB_SIZE - 1) - (rx % SCROLL_TAB_SIZE);
}
rx++;
}
return rx;
}
public rxToCx(rx: number): number {
let curRx = 0;
let cx = 0;
for (; cx < this.size; cx++) {
if (this.chars[cx] === '\t') {
curRx += (SCROLL_TAB_SIZE - 1) - (curRx % SCROLL_TAB_SIZE);
}
curRx++;
if (curRx > rx) {
return cx;
}
}
return cx;
}
public byteIndexToCharIndex(byteIndex: number): number {
if (this.toString().length === this.chars.length) {
return byteIndex;
}
let n = 0;
let byteCount = 0;
for (; n < this.chars.length; n++) {
byteCount += this.chars[n].length;
if (byteCount > byteIndex) {
return n;
}
}
return this.chars.length;
}
public charIndexToByteIndex(charIndex: number): number {
if (charIndex === 0 || this.toString().length === this.chars.length) {
return charIndex;
}
return this.chars.slice(0, charIndex).reduce(
(prev, current) => prev += current.length,
0,
);
}
public toString(): string {
return this.chars.join('');
}
public updateRender(): void {
const newString = this.chars.join('').replaceAll(
'\t',
' '.repeat(SCROLL_TAB_SIZE),
);
this.render = Util.strChars(newString);
}
}
export default Row;