/** * The ONLY module in parseman that talks to a terminal. * * Everything above it produces DATA — lines made of spans, each span carrying text and a * semantic tone — and this file turns lines into bytes. That split is not tidiness; it is * what makes the rendering diffable. A renderer that emits escape sequences inline has * two outputs (styled and plain) that can drift apart, and the plain one is what a * snapshot, a CI log and `docs/samples/` all read. * * WHY LINECRAFT AND NOT HAND-ROLLED ANSI * -------------------------------------- * The first cut of this CLI carried its own `ANSI` constant table. Writing that file * silently dropped the ESC bytes, so colour was dead for an entire session and nothing * said so — the output still looked structured, because an escape-less `[2m` prints as * `[2m`. Escape sequences are invisible failure surface, and a library that owns them * removes the surface. jess already renders its diagnostics through linecraft * (`packages/compiler/src/diagnostics.ts`, `packages/lint/src/index.ts`), so following * the same idiom also means a jess user and a parseman user see the same shape of output * rather than two parallel inventions. Pinned to `0.2.6`, exactly matching jess's pins. * * SPANS, NOT WHOLE-LINE STYLES * ---------------------------- * jess's lint table styles a whole row at a time, which is right for a table of one * thing per line. A grammar finding is not that: within one line the arm index, the * production, its first set and the annotation all mean different things, and the * annotation is the part that should be shouting. So a line here is a list of SPANS, and * a styled line is a linecraft `Grid` of `Styled` cells — which also gives the columns * their alignment for free. * * THE INVARIANT THAT MUST NOT BREAK * --------------------------------- * Padding happens in ONE place, before either path sees it, and the plain form is the * concatenation of the padded span texts. So the two paths differ in styling and in * nothing else — not in width, not in alignment, not in content. No escape byte is * produced at all when colour is off, so the diffable output is byte-stable by * construction rather than by stripping. `width` is passed explicitly and defaults to 80 * off-TTY, so a piped run cannot vary with the terminal it was piped from. */ import { type TextStyle } from 'linecraft'; import { groupDigits } from './format-number.ts'; export type Tone = TextStyle; /** * Semantic tones, named once so the two renderers cannot drift apart. * * The ladder matters more than the individual colours: within a finding, exactly one * thing should be the brightest, and provenance / accept keys / byte costs should * recede. A report where everything is emphasised is a report where nothing is. */ export declare const TONE: { /** The single most important thing in its block. */ readonly loud: Tone; /** A heading, a count, the thing being named. */ readonly strong: Tone; /** Supporting detail — never the finding itself. */ readonly quiet: Tone; /** Recedes furthest: provenance, snapshot keys, byte costs. */ readonly faint: Tone; /** This span IS the problem. */ readonly bad: Tone; /** Real, but not blocking. */ readonly warn: Tone; /** A location or an identifier the reader will search for. */ readonly ident: Tone; /** Clean, or the thing to do. */ readonly good: Tone; /** Structure: rules, box drawing, group frames. */ readonly frame: Tone; }; /** One styled run of text. `width` pads it, in BOTH paths, so columns line up. */ export type Span = { text: string; style?: Tone; /** Pad to this column width. Applied once, before either path renders. */ width?: number; /** Text that ALREADY contains escapes (a linecraft component's own output). */ raw?: boolean; /** * Absolute path this span points at. In the STYLED path it becomes a clickable * terminal hyperlink; the plain path ignores it entirely, which is what keeps the * diffable output free of escapes and of absolute paths. */ link?: string; }; /** A line is its spans. An empty array is a blank line. */ export type Line = Span[]; export declare const t: (text: string, style?: Tone, width?: number) => Span; export declare const blank: () => Line; /** A horizontal rule. Segmentation is what turns eighty undifferentiated lines into a * handful of blocks a reader can skip between. */ export declare const rule: (width: number, style?: Tone, char?: string) => Line; export type RenderTarget = { /** Emit ANSI. Default false — never sniffed here; the CLI decides. */ color?: boolean; /** Columns. Default 80, which is also what an off-TTY stream reports as nothing. */ width?: number; /** * Emit OSC-8 terminal hyperlinks on file locations. Only meaningful with `color`, * since both are escape sequences. Default: on when colouring. A terminal without * OSC-8 support prints the URL as visible junk, so this is the escape hatch. */ links?: boolean; }; export declare const DEFAULT_WIDTH = 80; /** Plain text of a line list — the byte-stable form, produced without a terminal. */ export declare const plain: (lines: readonly Line[]) => string; /** * Lines → a string. Without colour this never constructs a Region at all, so nothing on * the diffable path depends on linecraft's environment detection. */ export declare function render(lines: readonly Line[], target?: RenderTarget): string; export type CodeFrame = { /** Path as the reader should see it — relative, so no absolute path can be diffed. */ path: string; /** Absolute path, used ONLY for the terminal hyperlink, which the plain form strips. */ fullPath: string; line: number; column: number; endLine?: number; endColumn?: number; lineBefore?: string | null; lineText: string; lineAfter?: string | null; message: string; /** Short label attached to the underline — the one sentence the caret is making. */ shortMessage?: string; type?: 'error' | 'warning' | 'info'; }; /** * A source frame with the caret under the offending span — linecraft's `CodeDebug`, the * same component jess renders its compiler errors with. * * Its lines come back as RAW spans: the component styles per token, which a span list * cannot describe, and it is already the shape a reader wants. The plain form is * recovered by stripping — the one place in this file that has to — and is asserted * escape-free and absolute-path-free by test. */ export declare function codeFrame(frame: CodeFrame, target?: RenderTarget, indent?: string): Line[]; /** Deterministic thousands grouping — `toLocaleString()` differs between machines. * Defined in `./format-number.ts`, which depends on nothing, and re-exported here * because this module's callers import the formatting primitives from here. */ export { groupDigits }; export declare const pad: (s: string, w: number) => string; /** Hard-wrap on spaces at `width`, prefixing every line with `indent`. */ export declare function wrap(text: string, width: number, indent: string): string[]; //# sourceMappingURL=terminal.d.ts.map