@kud/ink-markdown 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -481,12 +481,12 @@ var chain = (fn, key) => {
481
481
  return typeof next === "function" ? next : fn;
482
482
  };
483
483
  var capitalize = (value) => value.charAt(0).toUpperCase() + value.slice(1);
484
+ var colour = (fn, value, bg) => value.startsWith("#") ? bg ? fn.bgHex(value) : fn.hex(value) : chain(fn, bg ? `bg${capitalize(value)}` : value);
484
485
  var applyStyle = (text, style) => {
485
486
  if (!style) return text;
486
487
  let fn = chalk;
487
- if (style.color) fn = chain(fn, style.color);
488
- if (style.backgroundColor)
489
- fn = chain(fn, `bg${capitalize(style.backgroundColor)}`);
488
+ if (style.color) fn = colour(fn, style.color, false);
489
+ if (style.backgroundColor) fn = colour(fn, style.backgroundColor, true);
490
490
  if (style.bold) fn = chain(fn, "bold");
491
491
  if (style.italic) fn = chain(fn, "italic");
492
492
  if (style.underline) fn = chain(fn, "underline");
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/hash.ts","../src/core/segment.ts","../src/core/document.ts","../src/core/spans.ts","../src/core/table.ts","../src/core/layout.ts","../src/ink/markdown-viewport.tsx","../src/ink/ansi.ts","../src/ink/use-markdown-scroll.ts"],"sourcesContent":["// FNV-1a — cheap, stable content hash used for block identity and layout-cache keys.\nexport const hash = (input: string): string => {\n let h = 0x811c9dc5\n for (let i = 0; i < input.length; i++) {\n h ^= input.charCodeAt(i)\n h = Math.imul(h, 0x01000193)\n }\n return (h >>> 0).toString(36)\n}\n","import { hash } from \"./hash.js\"\nimport type { MarkdownBlock, MarkdownBlockType } from \"./document.js\"\n\nconst FENCE = /^```(\\S*)\\s*$/\nconst HEADING = /^#{1,6}\\s+/\nconst THEMATIC_BREAK = /^(-{3,}|\\*{3,}|_{3,})\\s*$/\nconst QUOTE = /^>\\s?/\nconst LIST = /^(\\s*)([-*+]|\\d+\\.)\\s+/\n// A pipe-table row: starts and ends with a pipe. Two such lines in a row are\n// a table; one on its own is prose that happens to start with a bar.\nconst TABLE_ROW = /^\\s*\\|.*\\|\\s*$/\n\n// Line-scan segmenter → renderer-independent top-level blocks with stable, content-based\n// identity. Minimal GFM-ish block set; inline parsing happens later in the layout stage.\n// An unterminated fence stays a single open block, which is what the streaming tail needs.\nexport const segment = (source: string): MarkdownBlock[] => {\n const lines = source.split(\"\\n\")\n const lineStart: number[] = []\n let offset = 0\n for (const line of lines) {\n lineStart.push(offset)\n offset += line.length + 1 // + newline\n }\n\n const blocks: MarkdownBlock[] = []\n const seen = new Map<string, number>()\n\n const push = (\n type: MarkdownBlockType,\n fromLine: number,\n toLine: number,\n lang?: string,\n ) => {\n const lastLine = toLine - 1\n const sourceStart = lineStart[fromLine]\n const sourceEnd = lineStart[lastLine] + lines[lastLine].length\n const text = lines.slice(fromLine, toLine).join(\"\\n\")\n const sourceHash = hash(text)\n // Content hash is the identity; disambiguate genuinely-identical blocks by occurrence so\n // React keys stay unique without reintroducing position sensitivity for unique content.\n const count = seen.get(sourceHash) ?? 0\n seen.set(sourceHash, count + 1)\n const id = count === 0 ? sourceHash : `${sourceHash}~${count}`\n blocks.push({\n id,\n type,\n sourceStart,\n sourceEnd,\n sourceHash,\n ...(lang ? { lang } : {}),\n })\n }\n\n let i = 0\n while (i < lines.length) {\n const line = lines[i]\n\n if (line.trim() === \"\") {\n i++\n continue\n }\n\n const fence = line.match(FENCE)\n if (fence) {\n let j = i + 1\n while (j < lines.length && !FENCE.test(lines[j])) j++\n const toLine = j < lines.length ? j + 1 : lines.length // include closing fence, or EOF\n const lang = fence[1] || \"\"\n push(lang === \"diff\" ? \"diff\" : \"code\", i, toLine, lang || undefined)\n i = toLine\n continue\n }\n\n if (HEADING.test(line)) {\n push(\"heading\", i, i + 1)\n i++\n continue\n }\n\n if (THEMATIC_BREAK.test(line)) {\n push(\"thematic-break\", i, i + 1)\n i++\n continue\n }\n\n if (QUOTE.test(line)) {\n let j = i\n while (j < lines.length && QUOTE.test(lines[j])) j++\n push(\"quote\", i, j)\n i = j\n continue\n }\n\n if (LIST.test(line)) {\n let j = i\n while (\n j < lines.length &&\n lines[j].trim() !== \"\" &&\n !FENCE.test(lines[j])\n )\n j++\n push(\"list\", i, j)\n i = j\n continue\n }\n\n if (TABLE_ROW.test(line) && i + 1 < lines.length && TABLE_ROW.test(lines[i + 1])) {\n let j = i\n while (j < lines.length && TABLE_ROW.test(lines[j])) j++\n push(\"table\", i, j)\n i = j\n continue\n }\n\n // Paragraph — consume consecutive non-blank, non-structural lines.\n let j = i\n while (\n j < lines.length &&\n lines[j].trim() !== \"\" &&\n !FENCE.test(lines[j]) &&\n !HEADING.test(lines[j]) &&\n !THEMATIC_BREAK.test(lines[j]) &&\n !QUOTE.test(lines[j]) &&\n !LIST.test(lines[j]) &&\n !(TABLE_ROW.test(lines[j]) && j + 1 < lines.length && TABLE_ROW.test(lines[j + 1]))\n )\n j++\n push(\"paragraph\", i, j)\n i = j\n }\n\n return blocks\n}\n","import { segment } from \"./segment.js\"\n\nexport type MarkdownBlockType =\n | \"paragraph\"\n | \"heading\"\n | \"code\"\n | \"diff\"\n | \"list\"\n | \"quote\"\n | \"thematic-break\"\n | \"table\"\n\nexport type MarkdownBlock = {\n id: string\n type: MarkdownBlockType\n sourceStart: number\n sourceEnd: number\n sourceHash: string\n /** Fence info string for `code` / `diff` blocks (e.g. \"ts\", \"diff\"). */\n lang?: string\n}\n\nexport type MarkdownDocument = {\n version: number\n blocks: readonly MarkdownBlock[]\n sourceLength: number\n}\n\nexport const createMarkdownDocument = (\n source: string,\n version = 1,\n): MarkdownDocument => ({\n version,\n blocks: segment(source),\n sourceLength: source.length,\n})\n\n// Reparse after an edit. Block identity is content-based, so unchanged blocks keep their ids\n// automatically — cached layouts downstream stay valid — and this just restamps the version.\nexport const updateMarkdownDocument = (\n previous: MarkdownDocument,\n source: string,\n): MarkdownDocument => createMarkdownDocument(source, previous.version + 1)\n","import stringWidth from \"string-width\"\n\n// Semantic (renderer-independent) styling. No ANSI here — the Ink view maps these to\n// <Text> props, a future OpenTUI view maps them to its own styling, and a path-A composer\n// turns them into ANSI. Colours are resolved theme values (e.g. \"cyan\"), not tokens.\nexport type SpanStyle = {\n color?: string\n backgroundColor?: string\n bold?: boolean\n italic?: boolean\n underline?: boolean\n dim?: boolean\n inverse?: boolean\n}\n\nexport type TextSpan = {\n text: string\n style?: SpanStyle\n}\n\nexport type LayoutLine = {\n id: string\n spans: readonly TextSpan[]\n plainText: string\n displayWidth: number\n}\n\nexport type BlockLayout = {\n blockId: string\n width: number\n height: number\n lines: readonly LayoutLine[]\n}\n\nexport type MarkdownTheme = {\n foreground?: string\n muted: string\n heading: string\n link: string\n inlineCode: string\n quote: string\n added: string\n removed: string\n context: string\n}\n\nexport const defaultTheme: MarkdownTheme = {\n muted: \"gray\",\n heading: \"cyan\",\n link: \"blue\",\n inlineCode: \"yellow\",\n quote: \"gray\",\n added: \"green\",\n removed: \"red\",\n context: \"gray\",\n}\n\n/** Display width in terminal columns (Unicode-aware: wide chars count as 2). */\nexport const measureWidth = (text: string): number => stringWidth(text)\n\n/** Slice a string to at most `max` display columns without splitting a code point. */\nexport const sliceByWidth = (text: string, max: number): string => {\n let used = 0\n let out = \"\"\n for (const ch of text) {\n const cw = stringWidth(ch)\n if (used + cw > max) break\n out += ch\n used += cw\n }\n return out\n}\n\nconst sameStyle = (a?: SpanStyle, b?: SpanStyle): boolean =>\n JSON.stringify(a ?? null) === JSON.stringify(b ?? null)\n\n// Append text to a span list, merging into the previous span when the style matches so\n// runs of identical styling stay a single span (fewer nodes for the renderer).\nconst append = (line: TextSpan[], text: string, style?: SpanStyle): void => {\n const last = line[line.length - 1]\n if (last && sameStyle(last.style, style))\n line[line.length - 1] = { text: last.text + text, style }\n else line.push(style ? { text, style } : { text })\n}\n\n// A bare URL is the last alternative, so a `[text](url)` link wins over the URL\n// inside it. Trailing punctuation is left out of the match: `see https://x.y.`\n// links `https://x.y`, and a URL wrapped in parentheses keeps its closer.\nconst INLINE =\n /(\\*\\*([^*]+)\\*\\*|`([^`]+)`|\\*([^*]+)\\*|_([^_]+)_|\\[((?:[^\\[\\]]|\\[[^\\[\\]]*\\])+)\\]\\(([^)]+)\\)|(https?:\\/\\/[^\\s<>()]+?)(?=[.,;:!?]*(?:\\s|$|\\))))/g\n\n// Minimal inline parser → styled spans: bold, italic, inline code, links,\n// bare URLs. Markdown-it can replace this later for fuller GFM inline; this\n// covers the common PR-comment cases. Bare URLs are styled as links because a\n// pasted `PR: https://…` is usually the one actionable thing in a comment, and\n// it was the only thing rendered plain.\nexport const parseInline = (text: string, theme: MarkdownTheme): TextSpan[] => {\n const spans: TextSpan[] = []\n const re = new RegExp(INLINE)\n const link = { color: theme.link, underline: true }\n let last = 0\n let m: RegExpExecArray | null\n while ((m = re.exec(text)) !== null) {\n if (m.index > last) spans.push({ text: text.slice(last, m.index) })\n if (m[2] !== undefined) spans.push({ text: m[2], style: { bold: true } })\n else if (m[3] !== undefined)\n spans.push({ text: m[3], style: { color: theme.inlineCode } })\n else if (m[4] !== undefined)\n spans.push({ text: m[4], style: { italic: true } })\n else if (m[5] !== undefined)\n spans.push({ text: m[5], style: { italic: true } })\n else if (m[6] !== undefined) spans.push({ text: m[6], style: link })\n else if (m[8] !== undefined) spans.push({ text: m[8], style: link })\n last = m.index + m[0].length\n }\n if (last < text.length) spans.push({ text: text.slice(last) })\n return spans.length > 0 ? spans : [{ text }]\n}\n\n// Word-wrap a run of styled spans to `max` display columns, splitting spans at column\n// boundaries and hard-breaking any single token longer than the line.\nexport const wrapSpans = (\n spans: readonly TextSpan[],\n max: number,\n): TextSpan[][] => {\n const width = Math.max(1, max)\n const lines: TextSpan[][] = []\n let line: TextSpan[] = []\n let used = 0\n const flush = () => {\n lines.push(line)\n line = []\n used = 0\n }\n\n for (const span of spans) {\n const tokens = span.text.match(/\\s+|\\S+/g) ?? []\n for (const token of tokens) {\n const w = stringWidth(token)\n if (/^\\s+$/.test(token)) {\n // Keep inter-word whitespace, but drop it at a line start (no leading spaces on wraps).\n if (used > 0 && used + w <= width) {\n append(line, token, span.style)\n used += w\n }\n continue\n }\n if (used + w <= width) {\n append(line, token, span.style)\n used += w\n continue\n }\n if (used > 0) flush()\n if (w <= width) {\n append(line, token, span.style)\n used += w\n } else {\n let rest = token\n while (stringWidth(rest) > width) {\n const chunk = sliceByWidth(rest, width)\n append(line, chunk, span.style)\n flush()\n rest = rest.slice(chunk.length)\n }\n if (rest) {\n append(line, rest, span.style)\n used = stringWidth(rest)\n }\n }\n }\n }\n if (line.length > 0) lines.push(line)\n return lines.length > 0 ? lines : [[]]\n}\n\n/** Truncate a run of spans to `max` display columns (for code / diff — clip, don't wrap). */\nexport const clipSpans = (\n spans: readonly TextSpan[],\n max: number,\n): TextSpan[] => {\n const out: TextSpan[] = []\n let used = 0\n for (const span of spans) {\n if (used >= max) break\n const w = stringWidth(span.text)\n if (used + w <= max) {\n out.push(span)\n used += w\n } else {\n out.push({ text: sliceByWidth(span.text, max - used), style: span.style })\n break\n }\n }\n return out\n}\n","import {\n type MarkdownTheme,\n type TextSpan,\n clipSpans,\n measureWidth,\n parseInline,\n} from \"./spans.js\"\n\n// A pipe table, laid out as a grid the eye can travel DOWN: one line per row,\n// always. Columns are padded to their widest cell and joined by two spaces\n// with no vertical rule — width is the scarce resource, and a four-column\n// table spends nine cells on ` │ ` against six on gaps for the same\n// information. The column structure is drawn once instead, as per-column `─`\n// segments under the header in `theme.muted`, the thematic break's glyph and\n// token; the padding carries the boundaries down from there. The header takes\n// `theme.heading` bold, exactly as a heading block does, so it reads as the\n// same class of thing. Not underlined: underline is what a link wears, and a\n// header that looks clickable is a lie.\n//\n// Overflow shrinks and truncates rather than wrapping or clipping. A row two\n// lines tall is a list wearing a grid; clipping at the right edge loses the\n// rightmost columns, which in a ticket table are the short informative ones\n// (points, status) while sparing the long prose. So while the row overflows,\n// one column at a time comes off whichever is currently widest, floored at\n// its header's width, and cells past their column end in `…`. If every\n// column is at its floor and it still does not fit, the row clips like a code\n// line — never a hidden column. A silently missing column is a lie; a visibly\n// truncated one is honest.\n//\n// Empty cells stay blank. The ADF converter already emits `—` for \"none\", so\n// inventing a dash here would collide with a real value. A leading empty\n// header cell is a row-label column: blank header, but still a `─` segment,\n// because the segment marks the column and is the one thing that says \"this\n// is a column\" when the header cannot.\n\ntype Align = \"left\" | \"right\"\n\nconst GUTTER = \" \"\n\nconst splitRow = (line: string): string[] => {\n const trimmed = line.trim().replace(/^\\|/, \"\").replace(/\\|$/, \"\")\n return trimmed.split(\"|\").map((c) => c.trim())\n}\n\nconst SEPARATOR = /^\\s*\\|?\\s*:?-+:?\\s*(\\|\\s*:?-+:?\\s*)*\\|?\\s*$/\n\nconst alignOf = (cell: string): Align =>\n /^-+:$/.test(cell.trim()) ? \"right\" : \"left\"\n\nconst spanWidth = (spans: readonly TextSpan[]): number =>\n spans.reduce((n, s) => n + measureWidth(s.text), 0)\n\n// Truncate a cell's spans to `max` columns, ending in `…` in the last span's\n// style when anything was lost. The ellipsis inherits rather than taking a\n// colour of its own — a clipped link stays a link to the end.\nconst truncateSpans = (spans: readonly TextSpan[], max: number): TextSpan[] => {\n if (spanWidth(spans) <= max) return [...spans]\n if (max <= 1) return [{ text: max === 1 ? \"…\" : \"\" }]\n const kept = clipSpans(spans, max - 1)\n const last = kept[kept.length - 1]\n if (last) kept[kept.length - 1] = { ...last, text: last.text + \"…\" }\n else kept.push({ text: \"…\" })\n return kept\n}\n\nconst pad = (spans: TextSpan[], width: number, align: Align): TextSpan[] => {\n const gap = width - spanWidth(spans)\n if (gap <= 0) return spans\n const fill = { text: \" \".repeat(gap) }\n return align === \"right\" ? [fill, ...spans] : [...spans, fill]\n}\n\nexport const layoutTable = (\n text: string,\n width: number,\n theme: MarkdownTheme,\n): TextSpan[][] => {\n const lines = text.split(\"\\n\").filter((l) => l.trim() !== \"\")\n const separatorAt = lines.findIndex((l, i) => i > 0 && SEPARATOR.test(l))\n const header = separatorAt > 0 ? splitRow(lines[0]) : null\n const aligns =\n separatorAt > 0 ? splitRow(lines[separatorAt]).map(alignOf) : []\n const bodyLines = lines.filter(\n (_, i) => i !== separatorAt && !(header && i === 0),\n )\n const body = bodyLines.map(splitRow)\n const columns = Math.max(header?.length ?? 0, ...body.map((r) => r.length))\n if (columns === 0) return []\n\n const cellSpans = (\n cells: string[],\n style?: TextSpan[\"style\"],\n ): TextSpan[][] =>\n Array.from({ length: columns }, (_, c) => {\n const raw = cells[c] ?? \"\"\n const spans = parseInline(raw, theme)\n return style\n ? spans.map((s) => ({ ...s, style: { ...s.style, ...style } }))\n : spans\n })\n\n const headerSpans = header\n ? cellSpans(header, { color: theme.heading, bold: true })\n : null\n const rowSpans = body.map((cells) => cellSpans(cells))\n const allRows = headerSpans ? [headerSpans, ...rowSpans] : rowSpans\n\n const natural = Array.from({ length: columns }, (_, c) =>\n Math.max(1, ...allRows.map((r) => spanWidth(r[c]))),\n )\n const floors = Array.from({ length: columns }, (_, c) =>\n Math.max(1, headerSpans ? spanWidth(headerSpans[c]) : 1),\n )\n const widths = [...natural]\n const total = () =>\n widths.reduce((n, w) => n + w, 0) + GUTTER.length * (columns - 1)\n while (total() > width) {\n let widest = -1\n for (let c = 0; c < columns; c++)\n if (widths[c] > floors[c] && (widest < 0 || widths[c] > widths[widest]))\n widest = c\n if (widest < 0) break\n widths[widest] -= 1\n }\n\n const rowLine = (cells: TextSpan[][]): TextSpan[] => {\n const out: TextSpan[] = []\n cells.forEach((spans, c) => {\n if (c > 0) out.push({ text: GUTTER })\n out.push(\n ...pad(truncateSpans(spans, widths[c]), widths[c], aligns[c] ?? \"left\"),\n )\n })\n return clipSpans(out, width)\n }\n\n const rows: TextSpan[][] = []\n if (headerSpans) {\n rows.push(rowLine(headerSpans))\n const rule: TextSpan[] = []\n widths.forEach((w, c) => {\n if (c > 0) rule.push({ text: GUTTER })\n rule.push({ text: \"─\".repeat(w), style: { color: theme.muted } })\n })\n rows.push(clipSpans(rule, width))\n }\n for (const r of rowSpans) rows.push(rowLine(r))\n return rows\n}\n","import { hash } from \"./hash.js\"\nimport { layoutTable } from \"./table.js\"\nimport type { MarkdownBlock, MarkdownDocument } from \"./document.js\"\nimport {\n type BlockLayout,\n type LayoutLine,\n type MarkdownTheme,\n type TextSpan,\n clipSpans,\n defaultTheme,\n measureWidth,\n parseInline,\n wrapSpans,\n} from \"./spans.js\"\n\nconst blockText = (block: MarkdownBlock, source: string): string =>\n source.slice(block.sourceStart, block.sourceEnd)\n\nconst toLine = (\n blockId: string,\n index: number,\n spans: TextSpan[],\n): LayoutLine => {\n const plainText = spans.map((span) => span.text).join(\"\")\n return {\n id: `${blockId}:${index}`,\n spans,\n plainText,\n displayWidth: measureWidth(plainText),\n }\n}\n\nconst stripFences = (text: string): string[] => {\n const lines = text.split(\"\\n\")\n if (lines[0]?.startsWith(\"```\")) lines.shift()\n if (lines.length > 0 && lines[lines.length - 1]?.startsWith(\"```\"))\n lines.pop()\n return lines\n}\n\nconst diffStyle = (line: string, theme: MarkdownTheme) => {\n if (line.startsWith(\"@@\")) return { color: theme.heading }\n if (/^(\\+\\+\\+|---|diff |index )/.test(line)) return { bold: true }\n if (line.startsWith(\"+\")) return { color: theme.added }\n if (line.startsWith(\"-\")) return { color: theme.removed }\n return { color: theme.context }\n}\n\n// One block → its visual terminal lines. Prose wraps; code/diff clip. Pure + deterministic\n// given (block text, width, theme) → safe to cache. Syntax highlighting arrives in M4;\n// code renders plain here.\nconst layoutBlock = (\n block: MarkdownBlock,\n source: string,\n width: number,\n theme: MarkdownTheme,\n): LayoutLine[] => {\n const text = blockText(block, source)\n const rows: TextSpan[][] = []\n\n switch (block.type) {\n case \"heading\": {\n const content = text.replace(/^#{1,6}\\s+/, \"\")\n rows.push(\n ...wrapSpans(\n [{ text: content, style: { color: theme.heading, bold: true } }],\n width,\n ),\n )\n break\n }\n case \"thematic-break\": {\n rows.push([\n { text: \"─\".repeat(Math.max(1, width)), style: { color: theme.muted } },\n ])\n break\n }\n case \"quote\": {\n const content = text.replace(/^>\\s?/gm, \"\").split(\"\\n\").join(\" \")\n const prefix: TextSpan = { text: \"│ \", style: { color: theme.quote } }\n for (const wrapped of wrapSpans(parseInline(content, theme), width - 2))\n rows.push([prefix, ...wrapped])\n break\n }\n case \"list\": {\n for (const raw of text.split(\"\\n\")) {\n const item = raw.match(/^(\\s*)([-*+]|\\d+\\.)\\s+(.*)$/)\n if (!item) continue\n wrapSpans(parseInline(item[3], theme), width - 2).forEach(\n (wrapped, i) => {\n const prefix: TextSpan =\n i === 0\n ? { text: \"• \", style: { color: theme.heading } }\n : { text: \" \" }\n rows.push([prefix, ...wrapped])\n },\n )\n }\n break\n }\n case \"code\": {\n for (const codeLine of stripFences(text))\n rows.push(clipSpans([{ text: codeLine }], width))\n break\n }\n case \"diff\": {\n const body = block.lang === \"diff\" ? stripFences(text) : text.split(\"\\n\")\n for (const diffLine of body)\n rows.push(\n clipSpans(\n [{ text: diffLine, style: diffStyle(diffLine, theme) }],\n width,\n ),\n )\n break\n }\n case \"table\": {\n rows.push(...layoutTable(text, width, theme))\n break\n }\n default: {\n const content = text.split(\"\\n\").join(\" \")\n rows.push(...wrapSpans(parseInline(content, theme), width))\n }\n }\n\n return rows.map((spans, index) => toLine(block.id, index, spans))\n}\n\nexport type LayoutOptions = {\n width: number\n theme?: MarkdownTheme\n themeId?: string\n /** Optional cross-call cache (survives scroll/resize) keyed by block id + width + theme. */\n cache?: Map<string, readonly LayoutLine[]>\n}\n\nexport type MarkdownLayout = {\n width: number\n themeId: string\n blocks: readonly BlockLayout[]\n /** All lines in document order — O(1) viewport slicing. */\n lines: readonly LayoutLine[]\n /** Cumulative first-line index of each block (parallel to blocks). */\n lineOffsets: readonly number[]\n totalLines: number\n reused: number\n computed: number\n}\n\n// Lay out every block for a given width, reusing cached block layouts where possible. Scroll\n// never recomputes; a width change invalidates only width-dependent entries via the key.\nexport const createMarkdownLayout = (\n document: MarkdownDocument,\n source: string,\n options: LayoutOptions,\n): MarkdownLayout => {\n const theme = options.theme ?? defaultTheme\n const themeId =\n options.themeId ?? (options.theme ? hash(JSON.stringify(theme)) : \"default\")\n const width = options.width\n const cache = options.cache\n const blocks: BlockLayout[] = []\n const lines: LayoutLine[] = []\n const lineOffsets: number[] = []\n let reused = 0\n let computed = 0\n\n // One blank line between any two adjacent blocks, none before the first or\n // after the last. Every Markdown renderer separates blocks with vertical\n // space; without it a bold-paragraph \"heading\" runs straight into the list\n // beneath it and the eye has to parse where one thought ends. A list is one\n // block, so its items stay tight; a fence is one block, so its lines do; a\n // rule gets air on both sides. Pushed HERE rather than inside layoutBlock so\n // the per-block cache stays a function of the block alone, and `blocks[i]`\n // still reports its own height — the gap belongs to the document, not to\n // either neighbour. `lineOffsets` count it, so viewport math stays honest.\n document.blocks.forEach((block, i) => {\n if (i > 0) lines.push(toLine(block.id, -1, []))\n const key = `${block.id}:${width}:${themeId}`\n let blockLines = cache?.get(key)\n if (blockLines) {\n reused++\n } else {\n blockLines = layoutBlock(block, source, width, theme)\n cache?.set(key, blockLines)\n computed++\n }\n lineOffsets.push(lines.length)\n blocks.push({\n blockId: block.id,\n width,\n height: blockLines.length,\n lines: blockLines,\n })\n for (const line of blockLines) lines.push(line)\n })\n\n return {\n width,\n themeId,\n blocks,\n lines,\n lineOffsets,\n totalLines: lines.length,\n reused,\n computed,\n }\n}\n\n/** Visible slice for a viewport — clamped so the last page never scrolls past the end. */\nexport const sliceLines = (\n layout: MarkdownLayout,\n offset: number,\n height: number,\n): readonly LayoutLine[] => {\n const start = Math.max(\n 0,\n Math.min(offset, Math.max(0, layout.totalLines - height)),\n )\n return layout.lines.slice(start, start + height)\n}\n\n/** Index of the first block whose cumulative offset ≤ line — the log-n first-visible lookup. */\nexport const firstBlockAt = (layout: MarkdownLayout, line: number): number => {\n const offsets = layout.lineOffsets\n let lo = 0\n let hi = offsets.length - 1\n let answer = 0\n while (lo <= hi) {\n const mid = (lo + hi) >> 1\n if (offsets[mid] <= line) {\n answer = mid\n lo = mid + 1\n } else {\n hi = mid - 1\n }\n }\n return answer\n}\n","import { useMemo } from \"react\"\nimport { Box, Text } from \"ink\"\nimport {\n createMarkdownDocument,\n createMarkdownLayout,\n sliceLines,\n type LayoutLine,\n type MarkdownDocument,\n type MarkdownLayout,\n type MarkdownTheme,\n} from \"../core/index.js\"\nimport { linesToAnsi } from \"./ansi.js\"\n\nexport type MarkdownViewportProps = {\n /** Raw Markdown source. Ignored when `layout` is provided. */\n source?: string\n /** Pre-parsed document (reused with `source` to skip re-parsing). */\n document?: MarkdownDocument\n /** Pre-computed layout — skips parse + layout entirely. */\n layout?: MarkdownLayout\n width: number\n height: number\n /** First visible document line. Clamped so the last page never scrolls past the end. */\n scrollOffset?: number\n theme?: MarkdownTheme\n /** Layout cache shared across renders (survives scroll/resize). */\n cache?: Map<string, readonly LayoutLine[]>\n}\n\n// The viewport mounts exactly ONE <Text>, whatever the document size: the visible slice is\n// composed into a single pre-rendered ANSI string (render-path A). React/Yoga work stays\n// O(viewport), not O(document) — the whole performance thesis, made concrete.\nexport const MarkdownViewport = ({\n source,\n document,\n layout: providedLayout,\n width,\n height,\n scrollOffset = 0,\n theme,\n cache,\n}: MarkdownViewportProps) => {\n const layout = useMemo(() => {\n if (providedLayout) return providedLayout\n const text = source ?? \"\"\n const doc = document ?? createMarkdownDocument(text)\n return createMarkdownLayout(doc, text, { width, theme, cache })\n }, [providedLayout, document, source, width, theme, cache])\n\n const composed = linesToAnsi(sliceLines(layout, scrollOffset, height))\n\n return (\n <Box width={width} height={height} flexDirection=\"column\">\n <Text wrap=\"truncate\">{composed}</Text>\n </Box>\n )\n}\n","import chalk, { type ChalkInstance } from \"chalk\"\nimport type { LayoutLine, SpanStyle } from \"../core/index.js\"\n\n// Path-A composition lives here: semantic spans → one ANSI string. Chalk auto-detects the\n// terminal's colour support, so this degrades to plain text where colour is unavailable.\nconst chain = (fn: ChalkInstance, key: string): ChalkInstance => {\n const next = (fn as unknown as Record<string, unknown>)[key]\n return typeof next === \"function\" ? (next as ChalkInstance) : fn\n}\n\nconst capitalize = (value: string): string =>\n value.charAt(0).toUpperCase() + value.slice(1)\n\nconst applyStyle = (text: string, style?: SpanStyle): string => {\n if (!style) return text\n let fn: ChalkInstance = chalk\n if (style.color) fn = chain(fn, style.color)\n if (style.backgroundColor)\n fn = chain(fn, `bg${capitalize(style.backgroundColor)}`)\n if (style.bold) fn = chain(fn, \"bold\")\n if (style.italic) fn = chain(fn, \"italic\")\n if (style.underline) fn = chain(fn, \"underline\")\n if (style.dim) fn = chain(fn, \"dim\")\n if (style.inverse) fn = chain(fn, \"inverse\")\n return fn(text)\n}\n\n/** Compose one layout line's styled spans into a single ANSI string. */\nexport const lineToAnsi = (line: LayoutLine): string =>\n line.spans.map((span) => applyStyle(span.text, span.style)).join(\"\")\n\n/** Path-A composition: the visible span-lines → one ANSI string for a single <Text>. */\nexport const linesToAnsi = (lines: readonly LayoutLine[]): string =>\n lines.map(lineToAnsi).join(\"\\n\")\n","import { useInput } from \"ink\"\n\nexport type MarkdownScrollOptions = {\n totalLines: number\n height: number\n offset: number\n onChange: (offset: number) => void\n /** Disable the key handler (e.g. when another pane has focus). */\n active?: boolean\n}\n\nconst clamp = (value: number, max: number): number =>\n Math.max(0, Math.min(value, max))\n\n// Optional keyboard controller — the core owns scrolling *primitives* (offset in/out), not\n// keybindings. Apps that want their own bindings ignore this and drive `scrollOffset` directly.\nexport const useMarkdownScroll = ({\n totalLines,\n height,\n offset,\n onChange,\n active = true,\n}: MarkdownScrollOptions): void => {\n const max = Math.max(0, totalLines - height)\n useInput(\n (input, key) => {\n if (key.downArrow || input === \"j\") onChange(clamp(offset + 1, max))\n else if (key.upArrow || input === \"k\") onChange(clamp(offset - 1, max))\n else if (key.pageDown || input === \" \")\n onChange(clamp(offset + height, max))\n else if (key.pageUp || input === \"b\")\n onChange(clamp(offset - height, max))\n else if (input === \"g\") onChange(0)\n else if (input === \"G\") onChange(max)\n },\n { isActive: active },\n )\n}\n"],"mappings":"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","names":["toLine","j"]}
1
+ {"version":3,"sources":["../src/core/hash.ts","../src/core/segment.ts","../src/core/document.ts","../src/core/spans.ts","../src/core/table.ts","../src/core/layout.ts","../src/ink/markdown-viewport.tsx","../src/ink/ansi.ts","../src/ink/use-markdown-scroll.ts"],"sourcesContent":["// FNV-1a — cheap, stable content hash used for block identity and layout-cache keys.\nexport const hash = (input: string): string => {\n let h = 0x811c9dc5\n for (let i = 0; i < input.length; i++) {\n h ^= input.charCodeAt(i)\n h = Math.imul(h, 0x01000193)\n }\n return (h >>> 0).toString(36)\n}\n","import { hash } from \"./hash.js\"\nimport type { MarkdownBlock, MarkdownBlockType } from \"./document.js\"\n\nconst FENCE = /^```(\\S*)\\s*$/\nconst HEADING = /^#{1,6}\\s+/\nconst THEMATIC_BREAK = /^(-{3,}|\\*{3,}|_{3,})\\s*$/\nconst QUOTE = /^>\\s?/\nconst LIST = /^(\\s*)([-*+]|\\d+\\.)\\s+/\n// A pipe-table row: starts and ends with a pipe. Two such lines in a row are\n// a table; one on its own is prose that happens to start with a bar.\nconst TABLE_ROW = /^\\s*\\|.*\\|\\s*$/\n\n// Line-scan segmenter → renderer-independent top-level blocks with stable, content-based\n// identity. Minimal GFM-ish block set; inline parsing happens later in the layout stage.\n// An unterminated fence stays a single open block, which is what the streaming tail needs.\nexport const segment = (source: string): MarkdownBlock[] => {\n const lines = source.split(\"\\n\")\n const lineStart: number[] = []\n let offset = 0\n for (const line of lines) {\n lineStart.push(offset)\n offset += line.length + 1 // + newline\n }\n\n const blocks: MarkdownBlock[] = []\n const seen = new Map<string, number>()\n\n const push = (\n type: MarkdownBlockType,\n fromLine: number,\n toLine: number,\n lang?: string,\n ) => {\n const lastLine = toLine - 1\n const sourceStart = lineStart[fromLine]\n const sourceEnd = lineStart[lastLine] + lines[lastLine].length\n const text = lines.slice(fromLine, toLine).join(\"\\n\")\n const sourceHash = hash(text)\n // Content hash is the identity; disambiguate genuinely-identical blocks by occurrence so\n // React keys stay unique without reintroducing position sensitivity for unique content.\n const count = seen.get(sourceHash) ?? 0\n seen.set(sourceHash, count + 1)\n const id = count === 0 ? sourceHash : `${sourceHash}~${count}`\n blocks.push({\n id,\n type,\n sourceStart,\n sourceEnd,\n sourceHash,\n ...(lang ? { lang } : {}),\n })\n }\n\n let i = 0\n while (i < lines.length) {\n const line = lines[i]\n\n if (line.trim() === \"\") {\n i++\n continue\n }\n\n const fence = line.match(FENCE)\n if (fence) {\n let j = i + 1\n while (j < lines.length && !FENCE.test(lines[j])) j++\n const toLine = j < lines.length ? j + 1 : lines.length // include closing fence, or EOF\n const lang = fence[1] || \"\"\n push(lang === \"diff\" ? \"diff\" : \"code\", i, toLine, lang || undefined)\n i = toLine\n continue\n }\n\n if (HEADING.test(line)) {\n push(\"heading\", i, i + 1)\n i++\n continue\n }\n\n if (THEMATIC_BREAK.test(line)) {\n push(\"thematic-break\", i, i + 1)\n i++\n continue\n }\n\n if (QUOTE.test(line)) {\n let j = i\n while (j < lines.length && QUOTE.test(lines[j])) j++\n push(\"quote\", i, j)\n i = j\n continue\n }\n\n if (LIST.test(line)) {\n let j = i\n while (\n j < lines.length &&\n lines[j].trim() !== \"\" &&\n !FENCE.test(lines[j])\n )\n j++\n push(\"list\", i, j)\n i = j\n continue\n }\n\n if (TABLE_ROW.test(line) && i + 1 < lines.length && TABLE_ROW.test(lines[i + 1])) {\n let j = i\n while (j < lines.length && TABLE_ROW.test(lines[j])) j++\n push(\"table\", i, j)\n i = j\n continue\n }\n\n // Paragraph — consume consecutive non-blank, non-structural lines.\n let j = i\n while (\n j < lines.length &&\n lines[j].trim() !== \"\" &&\n !FENCE.test(lines[j]) &&\n !HEADING.test(lines[j]) &&\n !THEMATIC_BREAK.test(lines[j]) &&\n !QUOTE.test(lines[j]) &&\n !LIST.test(lines[j]) &&\n !(TABLE_ROW.test(lines[j]) && j + 1 < lines.length && TABLE_ROW.test(lines[j + 1]))\n )\n j++\n push(\"paragraph\", i, j)\n i = j\n }\n\n return blocks\n}\n","import { segment } from \"./segment.js\"\n\nexport type MarkdownBlockType =\n | \"paragraph\"\n | \"heading\"\n | \"code\"\n | \"diff\"\n | \"list\"\n | \"quote\"\n | \"thematic-break\"\n | \"table\"\n\nexport type MarkdownBlock = {\n id: string\n type: MarkdownBlockType\n sourceStart: number\n sourceEnd: number\n sourceHash: string\n /** Fence info string for `code` / `diff` blocks (e.g. \"ts\", \"diff\"). */\n lang?: string\n}\n\nexport type MarkdownDocument = {\n version: number\n blocks: readonly MarkdownBlock[]\n sourceLength: number\n}\n\nexport const createMarkdownDocument = (\n source: string,\n version = 1,\n): MarkdownDocument => ({\n version,\n blocks: segment(source),\n sourceLength: source.length,\n})\n\n// Reparse after an edit. Block identity is content-based, so unchanged blocks keep their ids\n// automatically — cached layouts downstream stay valid — and this just restamps the version.\nexport const updateMarkdownDocument = (\n previous: MarkdownDocument,\n source: string,\n): MarkdownDocument => createMarkdownDocument(source, previous.version + 1)\n","import stringWidth from \"string-width\"\n\n// Semantic (renderer-independent) styling. No ANSI here — the Ink view maps these to\n// <Text> props, a future OpenTUI view maps them to its own styling, and a path-A composer\n// turns them into ANSI. Colours are resolved theme values (e.g. \"cyan\"), not tokens.\nexport type SpanStyle = {\n color?: string\n backgroundColor?: string\n bold?: boolean\n italic?: boolean\n underline?: boolean\n dim?: boolean\n inverse?: boolean\n}\n\nexport type TextSpan = {\n text: string\n style?: SpanStyle\n}\n\nexport type LayoutLine = {\n id: string\n spans: readonly TextSpan[]\n plainText: string\n displayWidth: number\n}\n\nexport type BlockLayout = {\n blockId: string\n width: number\n height: number\n lines: readonly LayoutLine[]\n}\n\nexport type MarkdownTheme = {\n foreground?: string\n muted: string\n heading: string\n link: string\n inlineCode: string\n quote: string\n added: string\n removed: string\n context: string\n}\n\nexport const defaultTheme: MarkdownTheme = {\n muted: \"gray\",\n heading: \"cyan\",\n link: \"blue\",\n inlineCode: \"yellow\",\n quote: \"gray\",\n added: \"green\",\n removed: \"red\",\n context: \"gray\",\n}\n\n/** Display width in terminal columns (Unicode-aware: wide chars count as 2). */\nexport const measureWidth = (text: string): number => stringWidth(text)\n\n/** Slice a string to at most `max` display columns without splitting a code point. */\nexport const sliceByWidth = (text: string, max: number): string => {\n let used = 0\n let out = \"\"\n for (const ch of text) {\n const cw = stringWidth(ch)\n if (used + cw > max) break\n out += ch\n used += cw\n }\n return out\n}\n\nconst sameStyle = (a?: SpanStyle, b?: SpanStyle): boolean =>\n JSON.stringify(a ?? null) === JSON.stringify(b ?? null)\n\n// Append text to a span list, merging into the previous span when the style matches so\n// runs of identical styling stay a single span (fewer nodes for the renderer).\nconst append = (line: TextSpan[], text: string, style?: SpanStyle): void => {\n const last = line[line.length - 1]\n if (last && sameStyle(last.style, style))\n line[line.length - 1] = { text: last.text + text, style }\n else line.push(style ? { text, style } : { text })\n}\n\n// A bare URL is the last alternative, so a `[text](url)` link wins over the URL\n// inside it. Trailing punctuation is left out of the match: `see https://x.y.`\n// links `https://x.y`, and a URL wrapped in parentheses keeps its closer.\nconst INLINE =\n /(\\*\\*([^*]+)\\*\\*|`([^`]+)`|\\*([^*]+)\\*|_([^_]+)_|\\[((?:[^\\[\\]]|\\[[^\\[\\]]*\\])+)\\]\\(([^)]+)\\)|(https?:\\/\\/[^\\s<>()]+?)(?=[.,;:!?]*(?:\\s|$|\\))))/g\n\n// Minimal inline parser → styled spans: bold, italic, inline code, links,\n// bare URLs. Markdown-it can replace this later for fuller GFM inline; this\n// covers the common PR-comment cases. Bare URLs are styled as links because a\n// pasted `PR: https://…` is usually the one actionable thing in a comment, and\n// it was the only thing rendered plain.\nexport const parseInline = (text: string, theme: MarkdownTheme): TextSpan[] => {\n const spans: TextSpan[] = []\n const re = new RegExp(INLINE)\n const link = { color: theme.link, underline: true }\n let last = 0\n let m: RegExpExecArray | null\n while ((m = re.exec(text)) !== null) {\n if (m.index > last) spans.push({ text: text.slice(last, m.index) })\n if (m[2] !== undefined) spans.push({ text: m[2], style: { bold: true } })\n else if (m[3] !== undefined)\n spans.push({ text: m[3], style: { color: theme.inlineCode } })\n else if (m[4] !== undefined)\n spans.push({ text: m[4], style: { italic: true } })\n else if (m[5] !== undefined)\n spans.push({ text: m[5], style: { italic: true } })\n else if (m[6] !== undefined) spans.push({ text: m[6], style: link })\n else if (m[8] !== undefined) spans.push({ text: m[8], style: link })\n last = m.index + m[0].length\n }\n if (last < text.length) spans.push({ text: text.slice(last) })\n return spans.length > 0 ? spans : [{ text }]\n}\n\n// Word-wrap a run of styled spans to `max` display columns, splitting spans at column\n// boundaries and hard-breaking any single token longer than the line.\nexport const wrapSpans = (\n spans: readonly TextSpan[],\n max: number,\n): TextSpan[][] => {\n const width = Math.max(1, max)\n const lines: TextSpan[][] = []\n let line: TextSpan[] = []\n let used = 0\n const flush = () => {\n lines.push(line)\n line = []\n used = 0\n }\n\n for (const span of spans) {\n const tokens = span.text.match(/\\s+|\\S+/g) ?? []\n for (const token of tokens) {\n const w = stringWidth(token)\n if (/^\\s+$/.test(token)) {\n // Keep inter-word whitespace, but drop it at a line start (no leading spaces on wraps).\n if (used > 0 && used + w <= width) {\n append(line, token, span.style)\n used += w\n }\n continue\n }\n if (used + w <= width) {\n append(line, token, span.style)\n used += w\n continue\n }\n if (used > 0) flush()\n if (w <= width) {\n append(line, token, span.style)\n used += w\n } else {\n let rest = token\n while (stringWidth(rest) > width) {\n const chunk = sliceByWidth(rest, width)\n append(line, chunk, span.style)\n flush()\n rest = rest.slice(chunk.length)\n }\n if (rest) {\n append(line, rest, span.style)\n used = stringWidth(rest)\n }\n }\n }\n }\n if (line.length > 0) lines.push(line)\n return lines.length > 0 ? lines : [[]]\n}\n\n/** Truncate a run of spans to `max` display columns (for code / diff — clip, don't wrap). */\nexport const clipSpans = (\n spans: readonly TextSpan[],\n max: number,\n): TextSpan[] => {\n const out: TextSpan[] = []\n let used = 0\n for (const span of spans) {\n if (used >= max) break\n const w = stringWidth(span.text)\n if (used + w <= max) {\n out.push(span)\n used += w\n } else {\n out.push({ text: sliceByWidth(span.text, max - used), style: span.style })\n break\n }\n }\n return out\n}\n","import {\n type MarkdownTheme,\n type TextSpan,\n clipSpans,\n measureWidth,\n parseInline,\n} from \"./spans.js\"\n\n// A pipe table, laid out as a grid the eye can travel DOWN: one line per row,\n// always. Columns are padded to their widest cell and joined by two spaces\n// with no vertical rule — width is the scarce resource, and a four-column\n// table spends nine cells on ` │ ` against six on gaps for the same\n// information. The column structure is drawn once instead, as per-column `─`\n// segments under the header in `theme.muted`, the thematic break's glyph and\n// token; the padding carries the boundaries down from there. The header takes\n// `theme.heading` bold, exactly as a heading block does, so it reads as the\n// same class of thing. Not underlined: underline is what a link wears, and a\n// header that looks clickable is a lie.\n//\n// Overflow shrinks and truncates rather than wrapping or clipping. A row two\n// lines tall is a list wearing a grid; clipping at the right edge loses the\n// rightmost columns, which in a ticket table are the short informative ones\n// (points, status) while sparing the long prose. So while the row overflows,\n// one column at a time comes off whichever is currently widest, floored at\n// its header's width, and cells past their column end in `…`. If every\n// column is at its floor and it still does not fit, the row clips like a code\n// line — never a hidden column. A silently missing column is a lie; a visibly\n// truncated one is honest.\n//\n// Empty cells stay blank. The ADF converter already emits `—` for \"none\", so\n// inventing a dash here would collide with a real value. A leading empty\n// header cell is a row-label column: blank header, but still a `─` segment,\n// because the segment marks the column and is the one thing that says \"this\n// is a column\" when the header cannot.\n\ntype Align = \"left\" | \"right\"\n\nconst GUTTER = \" \"\n\nconst splitRow = (line: string): string[] => {\n const trimmed = line.trim().replace(/^\\|/, \"\").replace(/\\|$/, \"\")\n return trimmed.split(\"|\").map((c) => c.trim())\n}\n\nconst SEPARATOR = /^\\s*\\|?\\s*:?-+:?\\s*(\\|\\s*:?-+:?\\s*)*\\|?\\s*$/\n\nconst alignOf = (cell: string): Align =>\n /^-+:$/.test(cell.trim()) ? \"right\" : \"left\"\n\nconst spanWidth = (spans: readonly TextSpan[]): number =>\n spans.reduce((n, s) => n + measureWidth(s.text), 0)\n\n// Truncate a cell's spans to `max` columns, ending in `…` in the last span's\n// style when anything was lost. The ellipsis inherits rather than taking a\n// colour of its own — a clipped link stays a link to the end.\nconst truncateSpans = (spans: readonly TextSpan[], max: number): TextSpan[] => {\n if (spanWidth(spans) <= max) return [...spans]\n if (max <= 1) return [{ text: max === 1 ? \"…\" : \"\" }]\n const kept = clipSpans(spans, max - 1)\n const last = kept[kept.length - 1]\n if (last) kept[kept.length - 1] = { ...last, text: last.text + \"…\" }\n else kept.push({ text: \"…\" })\n return kept\n}\n\nconst pad = (spans: TextSpan[], width: number, align: Align): TextSpan[] => {\n const gap = width - spanWidth(spans)\n if (gap <= 0) return spans\n const fill = { text: \" \".repeat(gap) }\n return align === \"right\" ? [fill, ...spans] : [...spans, fill]\n}\n\nexport const layoutTable = (\n text: string,\n width: number,\n theme: MarkdownTheme,\n): TextSpan[][] => {\n const lines = text.split(\"\\n\").filter((l) => l.trim() !== \"\")\n const separatorAt = lines.findIndex((l, i) => i > 0 && SEPARATOR.test(l))\n const header = separatorAt > 0 ? splitRow(lines[0]) : null\n const aligns =\n separatorAt > 0 ? splitRow(lines[separatorAt]).map(alignOf) : []\n const bodyLines = lines.filter(\n (_, i) => i !== separatorAt && !(header && i === 0),\n )\n const body = bodyLines.map(splitRow)\n const columns = Math.max(header?.length ?? 0, ...body.map((r) => r.length))\n if (columns === 0) return []\n\n const cellSpans = (\n cells: string[],\n style?: TextSpan[\"style\"],\n ): TextSpan[][] =>\n Array.from({ length: columns }, (_, c) => {\n const raw = cells[c] ?? \"\"\n const spans = parseInline(raw, theme)\n return style\n ? spans.map((s) => ({ ...s, style: { ...s.style, ...style } }))\n : spans\n })\n\n const headerSpans = header\n ? cellSpans(header, { color: theme.heading, bold: true })\n : null\n const rowSpans = body.map((cells) => cellSpans(cells))\n const allRows = headerSpans ? [headerSpans, ...rowSpans] : rowSpans\n\n const natural = Array.from({ length: columns }, (_, c) =>\n Math.max(1, ...allRows.map((r) => spanWidth(r[c]))),\n )\n const floors = Array.from({ length: columns }, (_, c) =>\n Math.max(1, headerSpans ? spanWidth(headerSpans[c]) : 1),\n )\n const widths = [...natural]\n const total = () =>\n widths.reduce((n, w) => n + w, 0) + GUTTER.length * (columns - 1)\n while (total() > width) {\n let widest = -1\n for (let c = 0; c < columns; c++)\n if (widths[c] > floors[c] && (widest < 0 || widths[c] > widths[widest]))\n widest = c\n if (widest < 0) break\n widths[widest] -= 1\n }\n\n const rowLine = (cells: TextSpan[][]): TextSpan[] => {\n const out: TextSpan[] = []\n cells.forEach((spans, c) => {\n if (c > 0) out.push({ text: GUTTER })\n out.push(\n ...pad(truncateSpans(spans, widths[c]), widths[c], aligns[c] ?? \"left\"),\n )\n })\n return clipSpans(out, width)\n }\n\n const rows: TextSpan[][] = []\n if (headerSpans) {\n rows.push(rowLine(headerSpans))\n const rule: TextSpan[] = []\n widths.forEach((w, c) => {\n if (c > 0) rule.push({ text: GUTTER })\n rule.push({ text: \"─\".repeat(w), style: { color: theme.muted } })\n })\n rows.push(clipSpans(rule, width))\n }\n for (const r of rowSpans) rows.push(rowLine(r))\n return rows\n}\n","import { hash } from \"./hash.js\"\nimport { layoutTable } from \"./table.js\"\nimport type { MarkdownBlock, MarkdownDocument } from \"./document.js\"\nimport {\n type BlockLayout,\n type LayoutLine,\n type MarkdownTheme,\n type TextSpan,\n clipSpans,\n defaultTheme,\n measureWidth,\n parseInline,\n wrapSpans,\n} from \"./spans.js\"\n\nconst blockText = (block: MarkdownBlock, source: string): string =>\n source.slice(block.sourceStart, block.sourceEnd)\n\nconst toLine = (\n blockId: string,\n index: number,\n spans: TextSpan[],\n): LayoutLine => {\n const plainText = spans.map((span) => span.text).join(\"\")\n return {\n id: `${blockId}:${index}`,\n spans,\n plainText,\n displayWidth: measureWidth(plainText),\n }\n}\n\nconst stripFences = (text: string): string[] => {\n const lines = text.split(\"\\n\")\n if (lines[0]?.startsWith(\"```\")) lines.shift()\n if (lines.length > 0 && lines[lines.length - 1]?.startsWith(\"```\"))\n lines.pop()\n return lines\n}\n\nconst diffStyle = (line: string, theme: MarkdownTheme) => {\n if (line.startsWith(\"@@\")) return { color: theme.heading }\n if (/^(\\+\\+\\+|---|diff |index )/.test(line)) return { bold: true }\n if (line.startsWith(\"+\")) return { color: theme.added }\n if (line.startsWith(\"-\")) return { color: theme.removed }\n return { color: theme.context }\n}\n\n// One block → its visual terminal lines. Prose wraps; code/diff clip. Pure + deterministic\n// given (block text, width, theme) → safe to cache. Syntax highlighting arrives in M4;\n// code renders plain here.\nconst layoutBlock = (\n block: MarkdownBlock,\n source: string,\n width: number,\n theme: MarkdownTheme,\n): LayoutLine[] => {\n const text = blockText(block, source)\n const rows: TextSpan[][] = []\n\n switch (block.type) {\n case \"heading\": {\n const content = text.replace(/^#{1,6}\\s+/, \"\")\n rows.push(\n ...wrapSpans(\n [{ text: content, style: { color: theme.heading, bold: true } }],\n width,\n ),\n )\n break\n }\n case \"thematic-break\": {\n rows.push([\n { text: \"─\".repeat(Math.max(1, width)), style: { color: theme.muted } },\n ])\n break\n }\n case \"quote\": {\n const content = text.replace(/^>\\s?/gm, \"\").split(\"\\n\").join(\" \")\n const prefix: TextSpan = { text: \"│ \", style: { color: theme.quote } }\n for (const wrapped of wrapSpans(parseInline(content, theme), width - 2))\n rows.push([prefix, ...wrapped])\n break\n }\n case \"list\": {\n for (const raw of text.split(\"\\n\")) {\n const item = raw.match(/^(\\s*)([-*+]|\\d+\\.)\\s+(.*)$/)\n if (!item) continue\n wrapSpans(parseInline(item[3], theme), width - 2).forEach(\n (wrapped, i) => {\n const prefix: TextSpan =\n i === 0\n ? { text: \"• \", style: { color: theme.heading } }\n : { text: \" \" }\n rows.push([prefix, ...wrapped])\n },\n )\n }\n break\n }\n case \"code\": {\n for (const codeLine of stripFences(text))\n rows.push(clipSpans([{ text: codeLine }], width))\n break\n }\n case \"diff\": {\n const body = block.lang === \"diff\" ? stripFences(text) : text.split(\"\\n\")\n for (const diffLine of body)\n rows.push(\n clipSpans(\n [{ text: diffLine, style: diffStyle(diffLine, theme) }],\n width,\n ),\n )\n break\n }\n case \"table\": {\n rows.push(...layoutTable(text, width, theme))\n break\n }\n default: {\n const content = text.split(\"\\n\").join(\" \")\n rows.push(...wrapSpans(parseInline(content, theme), width))\n }\n }\n\n return rows.map((spans, index) => toLine(block.id, index, spans))\n}\n\nexport type LayoutOptions = {\n width: number\n theme?: MarkdownTheme\n themeId?: string\n /** Optional cross-call cache (survives scroll/resize) keyed by block id + width + theme. */\n cache?: Map<string, readonly LayoutLine[]>\n}\n\nexport type MarkdownLayout = {\n width: number\n themeId: string\n blocks: readonly BlockLayout[]\n /** All lines in document order — O(1) viewport slicing. */\n lines: readonly LayoutLine[]\n /** Cumulative first-line index of each block (parallel to blocks). */\n lineOffsets: readonly number[]\n totalLines: number\n reused: number\n computed: number\n}\n\n// Lay out every block for a given width, reusing cached block layouts where possible. Scroll\n// never recomputes; a width change invalidates only width-dependent entries via the key.\nexport const createMarkdownLayout = (\n document: MarkdownDocument,\n source: string,\n options: LayoutOptions,\n): MarkdownLayout => {\n const theme = options.theme ?? defaultTheme\n const themeId =\n options.themeId ?? (options.theme ? hash(JSON.stringify(theme)) : \"default\")\n const width = options.width\n const cache = options.cache\n const blocks: BlockLayout[] = []\n const lines: LayoutLine[] = []\n const lineOffsets: number[] = []\n let reused = 0\n let computed = 0\n\n // One blank line between any two adjacent blocks, none before the first or\n // after the last. Every Markdown renderer separates blocks with vertical\n // space; without it a bold-paragraph \"heading\" runs straight into the list\n // beneath it and the eye has to parse where one thought ends. A list is one\n // block, so its items stay tight; a fence is one block, so its lines do; a\n // rule gets air on both sides. Pushed HERE rather than inside layoutBlock so\n // the per-block cache stays a function of the block alone, and `blocks[i]`\n // still reports its own height — the gap belongs to the document, not to\n // either neighbour. `lineOffsets` count it, so viewport math stays honest.\n document.blocks.forEach((block, i) => {\n if (i > 0) lines.push(toLine(block.id, -1, []))\n const key = `${block.id}:${width}:${themeId}`\n let blockLines = cache?.get(key)\n if (blockLines) {\n reused++\n } else {\n blockLines = layoutBlock(block, source, width, theme)\n cache?.set(key, blockLines)\n computed++\n }\n lineOffsets.push(lines.length)\n blocks.push({\n blockId: block.id,\n width,\n height: blockLines.length,\n lines: blockLines,\n })\n for (const line of blockLines) lines.push(line)\n })\n\n return {\n width,\n themeId,\n blocks,\n lines,\n lineOffsets,\n totalLines: lines.length,\n reused,\n computed,\n }\n}\n\n/** Visible slice for a viewport — clamped so the last page never scrolls past the end. */\nexport const sliceLines = (\n layout: MarkdownLayout,\n offset: number,\n height: number,\n): readonly LayoutLine[] => {\n const start = Math.max(\n 0,\n Math.min(offset, Math.max(0, layout.totalLines - height)),\n )\n return layout.lines.slice(start, start + height)\n}\n\n/** Index of the first block whose cumulative offset ≤ line — the log-n first-visible lookup. */\nexport const firstBlockAt = (layout: MarkdownLayout, line: number): number => {\n const offsets = layout.lineOffsets\n let lo = 0\n let hi = offsets.length - 1\n let answer = 0\n while (lo <= hi) {\n const mid = (lo + hi) >> 1\n if (offsets[mid] <= line) {\n answer = mid\n lo = mid + 1\n } else {\n hi = mid - 1\n }\n }\n return answer\n}\n","import { useMemo } from \"react\"\nimport { Box, Text } from \"ink\"\nimport {\n createMarkdownDocument,\n createMarkdownLayout,\n sliceLines,\n type LayoutLine,\n type MarkdownDocument,\n type MarkdownLayout,\n type MarkdownTheme,\n} from \"../core/index.js\"\nimport { linesToAnsi } from \"./ansi.js\"\n\nexport type MarkdownViewportProps = {\n /** Raw Markdown source. Ignored when `layout` is provided. */\n source?: string\n /** Pre-parsed document (reused with `source` to skip re-parsing). */\n document?: MarkdownDocument\n /** Pre-computed layout — skips parse + layout entirely. */\n layout?: MarkdownLayout\n width: number\n height: number\n /** First visible document line. Clamped so the last page never scrolls past the end. */\n scrollOffset?: number\n theme?: MarkdownTheme\n /** Layout cache shared across renders (survives scroll/resize). */\n cache?: Map<string, readonly LayoutLine[]>\n}\n\n// The viewport mounts exactly ONE <Text>, whatever the document size: the visible slice is\n// composed into a single pre-rendered ANSI string (render-path A). React/Yoga work stays\n// O(viewport), not O(document) — the whole performance thesis, made concrete.\nexport const MarkdownViewport = ({\n source,\n document,\n layout: providedLayout,\n width,\n height,\n scrollOffset = 0,\n theme,\n cache,\n}: MarkdownViewportProps) => {\n const layout = useMemo(() => {\n if (providedLayout) return providedLayout\n const text = source ?? \"\"\n const doc = document ?? createMarkdownDocument(text)\n return createMarkdownLayout(doc, text, { width, theme, cache })\n }, [providedLayout, document, source, width, theme, cache])\n\n const composed = linesToAnsi(sliceLines(layout, scrollOffset, height))\n\n return (\n <Box width={width} height={height} flexDirection=\"column\">\n <Text wrap=\"truncate\">{composed}</Text>\n </Box>\n )\n}\n","import chalk, { type ChalkInstance } from \"chalk\"\nimport type { LayoutLine, SpanStyle } from \"../core/index.js\"\n\n// Path-A composition lives here: semantic spans → one ANSI string. Chalk auto-detects the\n// terminal's colour support, so this degrades to plain text where colour is unavailable.\nconst chain = (fn: ChalkInstance, key: string): ChalkInstance => {\n const next = (fn as unknown as Record<string, unknown>)[key]\n return typeof next === \"function\" ? (next as ChalkInstance) : fn\n}\n\nconst capitalize = (value: string): string =>\n value.charAt(0).toUpperCase() + value.slice(1)\n\n// A theme colour is either a chalk name (`cyan`) or a hex (`#FF8C00`) — Ink's\n// `<Text color>` takes both, and a host handing its design tokens through the\n// theme reasonably expects the same here. Chalk exposes the two differently:\n// a name is a property, a hex goes through `.hex()` / `.bgHex()`. Routed by\n// shape, because a hex looked up as a property is `undefined`, and `chain`\n// then hands back the unstyled chain — which is how a cockpit's orange links\n// rendered plain for a week without an error anywhere.\nconst colour = (\n fn: ChalkInstance,\n value: string,\n bg: boolean,\n): ChalkInstance =>\n value.startsWith(\"#\")\n ? bg\n ? fn.bgHex(value)\n : fn.hex(value)\n : chain(fn, bg ? `bg${capitalize(value)}` : value)\n\nconst applyStyle = (text: string, style?: SpanStyle): string => {\n if (!style) return text\n let fn: ChalkInstance = chalk\n if (style.color) fn = colour(fn, style.color, false)\n if (style.backgroundColor) fn = colour(fn, style.backgroundColor, true)\n if (style.bold) fn = chain(fn, \"bold\")\n if (style.italic) fn = chain(fn, \"italic\")\n if (style.underline) fn = chain(fn, \"underline\")\n if (style.dim) fn = chain(fn, \"dim\")\n if (style.inverse) fn = chain(fn, \"inverse\")\n return fn(text)\n}\n\n/** Compose one layout line's styled spans into a single ANSI string. */\nexport const lineToAnsi = (line: LayoutLine): string =>\n line.spans.map((span) => applyStyle(span.text, span.style)).join(\"\")\n\n/** Path-A composition: the visible span-lines → one ANSI string for a single <Text>. */\nexport const linesToAnsi = (lines: readonly LayoutLine[]): string =>\n lines.map(lineToAnsi).join(\"\\n\")\n","import { useInput } from \"ink\"\n\nexport type MarkdownScrollOptions = {\n totalLines: number\n height: number\n offset: number\n onChange: (offset: number) => void\n /** Disable the key handler (e.g. when another pane has focus). */\n active?: boolean\n}\n\nconst clamp = (value: number, max: number): number =>\n Math.max(0, Math.min(value, max))\n\n// Optional keyboard controller — the core owns scrolling *primitives* (offset in/out), not\n// keybindings. Apps that want their own bindings ignore this and drive `scrollOffset` directly.\nexport const useMarkdownScroll = ({\n totalLines,\n height,\n offset,\n onChange,\n active = true,\n}: MarkdownScrollOptions): void => {\n const max = Math.max(0, totalLines - height)\n useInput(\n (input, key) => {\n if (key.downArrow || input === \"j\") onChange(clamp(offset + 1, max))\n else if (key.upArrow || input === \"k\") onChange(clamp(offset - 1, max))\n else if (key.pageDown || input === \" \")\n onChange(clamp(offset + height, max))\n else if (key.pageUp || input === \"b\")\n onChange(clamp(offset - height, max))\n else if (input === \"g\") onChange(0)\n else if (input === \"G\") onChange(max)\n },\n { isActive: active },\n )\n}\n"],"mappings":"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","names":["toLine","j"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/ink-markdown",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "type": "module",
5
5
  "description": "A high-performance Markdown / code / diff rendering engine for Ink 7.",
6
6
  "exports": {
@@ -42,7 +42,6 @@
42
42
  "dependencies": {
43
43
  "chalk": "5.6.2",
44
44
  "cli-highlight": "2.1.11",
45
- "markdown-it": "14.3.0",
46
45
  "slice-ansi": "9.0.0",
47
46
  "string-width": "8.2.2",
48
47
  "wrap-ansi": "10.0.0"