/** * Free-form selection over the painted transcript. * * The terminal cannot select text for us: the TUI claims the mouse (`?1000h` * plus SGR reports) so the wheel scrolls the transcript and a click expands a * card or opens a link. That is why a reply could only be copied with `/copy` * and the focused card. This module is the in-app replacement the user asked * for: drag across a reply, and the dragged text goes to the clipboard over * OSC 52 (the channel `/copy` already uses). * * Only **model replies** are freely selectable. Everything here is pure: the * caller passes the painted lines (plain text, whether each one came from a * reply, and how many leading cells are chrome) and gets back the ordered * selection, the cell spans to paint in reverse video, and the text to copy. * The IO — mouse reports, OSC 52, notices — stays in `tui.ts`. * * Columns are **cells**, not characters: a CJK glyph is two cells wide, and a * selection that touches either half includes the whole glyph. Surrogate pairs * (emoji) are treated the same way. */ /** One painted screen line, with the origin the selection rules care about. */ export interface SelectableLine { /** The line as the user sees it: no escape sequences, no padding. */ text: string; /** True when this line came from a model reply, the only freely copyable kind. */ copyable: boolean; /** * Cells at the start of the line that are chrome, not content. * * The focused card or reply carries a `▶ ` marker, and a reply's lines are * copyable, so without this the marker was part of the text a drag copied off * its first line. Only the first line of the focused row carries one. */ gutter?: number; } /** A point in the transcript: `line` indexes the painted lines, `column` is a cell. */ export interface SelectionPoint { line: number; column: number; } /** Two ordered points; `from` is never after `to`. */ export interface ScreenSelection { from: SelectionPoint; to: SelectionPoint; } /** A cell range on one line, `end` exclusive — what the painter inverts. */ export interface SelectionSpan { line: number; start: number; end: number; } /** Order two dragged points, whichever way the drag went. */ export declare function orderPoints(anchor: SelectionPoint, extent: SelectionPoint): ScreenSelection; /** * The contiguous run of copyable lines around `line`, or undefined when that * line is not copyable at all — a drag that starts on a tool card or a notice * selects nothing, and the caller keeps its click behavior. */ export declare function copyableRun(lines: readonly SelectableLine[], line: number): { first: number; last: number; } | undefined; /** * Clamp a drag to the copyable run it started in, so dragging past a reply's * end cannot pick up a system notice or another card's text. */ export declare function clampSelection(lines: readonly SelectableLine[], anchor: SelectionPoint, extent: SelectionPoint): ScreenSelection | undefined; /** * The offset of the glyph whose cell contains `column` — the **start** snap. * * A column inside a wide glyph (its second cell) maps to that glyph's own * offset, so starting a drag on either half includes the whole character. */ export declare function offsetAtColumn(text: string, column: number): number; /** * The offset just past the glyph a boundary cuts through — the **end** snap. * * The two ends snap differently on purpose: a drag that stops on the second * cell of a wide glyph has visually covered that glyph, and half a glyph cannot * be copied, so the whole of it is included rather than dropped. */ export declare function offsetAfterColumn(text: string, column: number): number; /** The cell spans to paint in reverse video, one per covered line. */ export declare function selectionSpans(lines: readonly SelectableLine[], selection: ScreenSelection): SelectionSpan[]; /** * The text a drag selected: the lines as painted, joined with newlines and with * each line's trailing padding removed. Empty when the drag covered nothing. */ export declare function selectionText(lines: readonly SelectableLine[], selection: ScreenSelection): string;