/** * Pre-computed layout of a single content line. * * - Grapheme clusters and ANSI codes are separated so row emission never * splits a sequence or miscounts a width. * - Row boundaries and per-row active styles are computed once per line and * screen width, then reused on every render. */ export interface LineLayout { /** Grapheme clusters, excluding ANSI codes. */ chars: string[]; /** Visual width of each cluster. */ widths: number[]; /** prefix[i] - total visual width of chars[0..i-1]. */ prefix: number[]; /** Cluster index each ANSI code is anchored before. */ codeIdx: number[]; /** ANSI codes in order of appearance. */ codes: string[]; /** Cluster index starting each wrapped row. */ rowStart: number[]; /** Active ANSI style prefix at each wrapped row start. */ rowStyle: string[]; } /** Bumped whenever the cached layouts stop applying. */ export declare const layoutGeneration: () => number; /** * Returns the cached layout for a line, building it on first access. * * - The cache is invalidated when the screen width changes. * * @param line - The raw content line. * @returns The line's layout for the current screen width. */ export declare function getLayout(line: string): LineLayout; /** * Whether any style is still open at the end of a string. * * A full reset is not the only way a style closes: ESC[27m ends the * inverse ESC[7m opened before it, and SGR_CLOSERS above knows every * such pairing. withReset used to look for a reset code alone, so a * line that closed its inverse properly looked unterminated and got a * second, redundant reset appended -- which less does not emit. * * @param text - The text to scan. */ export declare function stylesOpen(text: string): boolean; /** * Where the screen row that begins at `from` ends - one step of * buildRowStarts, taken from an arbitrary character rather than from a * boundary. * * less's forw_line reads from wherever table[TOP] points and stops when * the line no longer fits, so a row's extent depends on where it * STARTS. Under a plain width that is just from + width, but * --wordwrap breaks at spaces, so the answer cannot be translated from * the boundary grid - it has to be walked. */ export declare function rowEndFrom(layout: LineLayout, from: number): number; /** * The index into the raw line string that a display-CHARACTER offset * names. * * The two spaces differ on every styled line - the layout keeps its * escape codes in a separate list, so a character index is not a * string index - and on every line with clusters or wide characters. * Anything holding a position (the screen table, the view's top) works * in character space; anything scanning the text itself needs this. */ export declare function stringIndexAt(layout: LineLayout, at: number): number; /** * The display-CHARACTER offset naming a raw string index - the * inverse of stringIndexAt, for coming back from a position measured * in the line's own bytes. */ export declare function charIndexAt(layout: LineLayout, at: number): number; /** * The drawn text of the characters in [from, to), with the style in * force at `from` reopened so the row stands alone like less's (less * re-emits attributes per row through at_switch). * * A space run --wordwrap swallowed at the break is inside the range * but past the screen edge, so the width guard still drops it. */ export declare function emitRange(layout: LineLayout, from: number, to: number): string;