@zaly/tui 0.2.0 → 0.3.1

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.
Files changed (155) hide show
  1. package/README.md +3 -0
  2. package/dist/{actions-B0yyZ-Ww.d.mts → actions-CH6ndYvf.d.mts} +4 -4
  3. package/dist/{actions-B0yyZ-Ww.d.mts.map → actions-CH6ndYvf.d.mts.map} +1 -1
  4. package/dist/{ansi-CxgRPLA2.mjs → ansi-oiRnTQr9.mjs} +11 -2
  5. package/dist/ansi-oiRnTQr9.mjs.map +1 -0
  6. package/dist/ansi.d.mts +2 -0
  7. package/dist/ansi.d.mts.map +1 -1
  8. package/dist/ansi.mjs +1 -1
  9. package/dist/{autocomplete-wgceuhLr.d.mts → autocomplete-ZmzqQZNq.d.mts} +4 -4
  10. package/dist/{autocomplete-wgceuhLr.d.mts.map → autocomplete-ZmzqQZNq.d.mts.map} +1 -1
  11. package/dist/{box-DbIjxh39.mjs → box-BOJcFW8f.mjs} +4 -4
  12. package/dist/{box-DbIjxh39.mjs.map → box-BOJcFW8f.mjs.map} +1 -1
  13. package/dist/{builder-BdYlK1Ew.mjs → builder-D8Sx5N8a.mjs} +3 -3
  14. package/dist/{builder-BdYlK1Ew.mjs.map → builder-D8Sx5N8a.mjs.map} +1 -1
  15. package/dist/{callbacks-UaBfDStQ.mjs → callbacks-CruJEvm8.mjs} +12 -4
  16. package/dist/{callbacks-UaBfDStQ.mjs.map → callbacks-CruJEvm8.mjs.map} +1 -1
  17. package/dist/client-C7iR8kp-.mjs +1 -0
  18. package/dist/{client-BWm6dNZF.mjs → client-DsNEilJG.mjs} +1 -3
  19. package/dist/client-DsNEilJG.mjs.map +1 -0
  20. package/dist/{clipboard-UEoX9sup.mjs → clipboard-wzJ1EVfy.mjs} +2 -2
  21. package/dist/{clipboard-UEoX9sup.mjs.map → clipboard-wzJ1EVfy.mjs.map} +1 -1
  22. package/dist/clipboard.mjs +1 -1
  23. package/dist/ctx-CM0YMy26.mjs +1 -0
  24. package/dist/{ctx-DBeFcZt6.mjs → ctx-D_rMOcp5.mjs} +2 -2
  25. package/dist/{ctx-DBeFcZt6.mjs.map → ctx-D_rMOcp5.mjs.map} +1 -1
  26. package/dist/{divider-BPmnQ0Eu.mjs → divider-CiNpoiuP.mjs} +2 -2
  27. package/dist/{divider-BPmnQ0Eu.mjs.map → divider-CiNpoiuP.mjs.map} +1 -1
  28. package/dist/{files-CdiDnwA_.d.mts → files-82Il_nUh.d.mts} +3 -3
  29. package/dist/{files-CdiDnwA_.d.mts.map → files-82Il_nUh.d.mts.map} +1 -1
  30. package/dist/{github-DC0XemKa.d.mts → github-yVfk9BE4.d.mts} +3 -3
  31. package/dist/{github-DC0XemKa.d.mts.map → github-yVfk9BE4.d.mts.map} +1 -1
  32. package/dist/{image-M--Bw4bs.mjs → image--PBiHfwV.mjs} +2 -2
  33. package/dist/{image-M--Bw4bs.mjs.map → image--PBiHfwV.mjs.map} +1 -1
  34. package/dist/{image-DdRDv7q6.d.mts → image-CO6YJj6F.d.mts} +2 -2
  35. package/dist/{image-DdRDv7q6.d.mts.map → image-CO6YJj6F.d.mts.map} +1 -1
  36. package/dist/image.d.mts +1 -1
  37. package/dist/index.d.mts +1 -1
  38. package/dist/index.mjs +6 -6
  39. package/dist/{input-CtGmVrKa.mjs → input-5ktO2fvO.mjs} +4 -4
  40. package/dist/{input-CtGmVrKa.mjs.map → input-5ktO2fvO.mjs.map} +1 -1
  41. package/dist/{input-C3kfYoEJ.d.mts → input-C8MEpAg6.d.mts} +2 -2
  42. package/dist/{input-C3kfYoEJ.d.mts.map → input-C8MEpAg6.d.mts.map} +1 -1
  43. package/dist/{inspect-Cli1beu8.mjs → inspect-CxtcQch-.mjs} +2 -2
  44. package/dist/{inspect-Cli1beu8.mjs.map → inspect-CxtcQch-.mjs.map} +1 -1
  45. package/dist/{log-D6gGicwQ.mjs → log-D7PTmOzE.mjs} +8 -5
  46. package/dist/log-D7PTmOzE.mjs.map +1 -0
  47. package/dist/{logger-C0WuIlAh.mjs → logger-DXK8D8Vb.mjs} +3 -3
  48. package/dist/{logger-C0WuIlAh.mjs.map → logger-DXK8D8Vb.mjs.map} +1 -1
  49. package/dist/{markdown-DvDIPT5L.mjs → markdown-D6quhsm0.mjs} +10 -9
  50. package/dist/markdown-D6quhsm0.mjs.map +1 -0
  51. package/dist/markdown.d.mts +3 -1
  52. package/dist/markdown.d.mts.map +1 -1
  53. package/dist/markdown.mjs +1 -1
  54. package/dist/{overlay-zDNpwKu0.mjs → overlay-6kh2gG4K.mjs} +2 -2
  55. package/dist/{overlay-zDNpwKu0.mjs.map → overlay-6kh2gG4K.mjs.map} +1 -1
  56. package/dist/{picker-DV_oxKEv.mjs → picker-B3XS2HKe.mjs} +4 -4
  57. package/dist/{picker-DV_oxKEv.mjs.map → picker-B3XS2HKe.mjs.map} +1 -1
  58. package/dist/{picker-DsB6--GN.d.mts → picker-H_ubc2nk.d.mts} +5 -5
  59. package/dist/{picker-DsB6--GN.d.mts.map → picker-H_ubc2nk.d.mts.map} +1 -1
  60. package/dist/{queries-Dmd3IjSd.d.mts → queries-BqFMpQAf.d.mts} +3 -1
  61. package/dist/queries-BqFMpQAf.d.mts.map +1 -0
  62. package/dist/{render-Bp2N2i6J.mjs → render-bm-x85jl.mjs} +2 -2
  63. package/dist/{render-Bp2N2i6J.mjs.map → render-bm-x85jl.mjs.map} +1 -1
  64. package/dist/{renderer-OplFCBMq.mjs → renderer-Cc8-s72G.mjs} +9 -9
  65. package/dist/{renderer-OplFCBMq.mjs.map → renderer-Cc8-s72G.mjs.map} +1 -1
  66. package/dist/renderer-DJg8vAoQ.mjs +1 -0
  67. package/dist/{renderer-Ksfkfv5X.mjs → renderer-D_n6LCWd.mjs} +2 -2
  68. package/dist/renderer-D_n6LCWd.mjs.map +1 -0
  69. package/dist/{select-BZCFXdjp.d.mts → select-BeP7rFkP.d.mts} +2 -2
  70. package/dist/{select-BZCFXdjp.d.mts.map → select-BeP7rFkP.d.mts.map} +1 -1
  71. package/dist/{select-CUmAFKJh.mjs → select-BlsSDBlV.mjs} +2 -2
  72. package/dist/{select-CUmAFKJh.mjs.map → select-BlsSDBlV.mjs.map} +1 -1
  73. package/dist/services/logger.d.mts +1 -1
  74. package/dist/services/logger.mjs +1 -1
  75. package/dist/services/notifier.d.mts +1 -1
  76. package/dist/services/notifier.mjs +2 -2
  77. package/dist/services/picker.d.mts +5 -5
  78. package/dist/services/picker.mjs +6 -6
  79. package/dist/services/prompt.d.mts +4 -4
  80. package/dist/services/prompt.mjs +5 -5
  81. package/dist/shiki/server.mjs +2 -2
  82. package/dist/{shiki-BAf9PdIc.mjs → shiki-CUJQssUJ.mjs} +2 -2
  83. package/dist/{shiki-BAf9PdIc.mjs.map → shiki-CUJQssUJ.mjs.map} +1 -1
  84. package/dist/{show-D5XNXgZf.mjs → show-BfIUw8A1.mjs} +2 -2
  85. package/dist/{show-D5XNXgZf.mjs.map → show-BfIUw8A1.mjs.map} +1 -1
  86. package/dist/{style-Cw_EuszU.mjs → style-DTwmlkfR.mjs} +2 -2
  87. package/dist/{style-Cw_EuszU.mjs.map → style-DTwmlkfR.mjs.map} +1 -1
  88. package/dist/style.d.mts +1 -1
  89. package/dist/style.mjs +2 -2
  90. package/dist/{text-H_2kzwaz.mjs → text-BKLHPMsQ.mjs} +3 -3
  91. package/dist/{text-H_2kzwaz.mjs.map → text-BKLHPMsQ.mjs.map} +1 -1
  92. package/dist/{text-BEuhhxr7.mjs → text-BLQXIh6Z.mjs} +2 -2
  93. package/dist/{text-BEuhhxr7.mjs.map → text-BLQXIh6Z.mjs.map} +1 -1
  94. package/dist/{text-DdkJXgoC.d.mts → text-BnlU1Q9y.d.mts} +2 -2
  95. package/dist/{text-DdkJXgoC.d.mts.map → text-BnlU1Q9y.d.mts.map} +1 -1
  96. package/dist/text.d.mts +1 -1
  97. package/dist/text.mjs +1 -1
  98. package/dist/{theme-B8hb1lPU.mjs → theme-CG51x7aG.mjs} +2 -2
  99. package/dist/{theme-B8hb1lPU.mjs.map → theme-CG51x7aG.mjs.map} +1 -1
  100. package/dist/{tree-D6NvMQx8.mjs → tree-4aaQKFtF.mjs} +2 -2
  101. package/dist/{tree-D6NvMQx8.mjs.map → tree-4aaQKFtF.mjs.map} +1 -1
  102. package/dist/{tree-Berzv8Rz.d.mts → tree-DNAE_C7w.d.mts} +3 -3
  103. package/dist/{tree-Berzv8Rz.d.mts.map → tree-DNAE_C7w.d.mts.map} +1 -1
  104. package/dist/{widget-sPJaKVU2.d.mts → widget-CHWHCg1j.d.mts} +2 -2
  105. package/dist/{widget-sPJaKVU2.d.mts.map → widget-CHWHCg1j.d.mts.map} +1 -1
  106. package/dist/widgets/autocomplete.d.mts +1 -1
  107. package/dist/widgets/autocomplete.mjs +3 -3
  108. package/dist/widgets/box.d.mts +1 -1
  109. package/dist/widgets/box.mjs +1 -1
  110. package/dist/widgets/code.d.mts +2 -2
  111. package/dist/widgets/code.mjs +7 -7
  112. package/dist/widgets/completions/actions.d.mts +1 -1
  113. package/dist/widgets/completions/files.d.mts +1 -1
  114. package/dist/widgets/completions/github.d.mts +1 -1
  115. package/dist/widgets/completions/index.d.mts +3 -3
  116. package/dist/widgets/diff.d.mts +2 -2
  117. package/dist/widgets/diff.mjs +5 -5
  118. package/dist/widgets/divider.d.mts +1 -1
  119. package/dist/widgets/divider.mjs +1 -1
  120. package/dist/widgets/image.d.mts +1 -1
  121. package/dist/widgets/image.mjs +2 -2
  122. package/dist/widgets/input.d.mts +1 -1
  123. package/dist/widgets/input.mjs +1 -1
  124. package/dist/widgets/log.d.mts +1 -1
  125. package/dist/widgets/log.mjs +1 -1
  126. package/dist/widgets/markdown.d.mts +6 -4
  127. package/dist/widgets/markdown.d.mts.map +1 -1
  128. package/dist/widgets/markdown.mjs +1 -1
  129. package/dist/widgets/overlay.d.mts +1 -1
  130. package/dist/widgets/overlay.mjs +1 -1
  131. package/dist/widgets/picker.d.mts +1 -1
  132. package/dist/widgets/picker.mjs +1 -1
  133. package/dist/widgets/progress.d.mts +1 -1
  134. package/dist/widgets/progress.mjs +1 -1
  135. package/dist/widgets/select.d.mts +1 -1
  136. package/dist/widgets/select.mjs +1 -1
  137. package/dist/widgets/show.d.mts +1 -1
  138. package/dist/widgets/show.mjs +1 -1
  139. package/dist/widgets/spinner.d.mts +1 -1
  140. package/dist/widgets/spinner.mjs +1 -1
  141. package/dist/widgets/text.d.mts +1 -1
  142. package/dist/widgets/text.mjs +1 -1
  143. package/dist/widgets/tree.d.mts +1 -1
  144. package/dist/widgets/tree.mjs +1 -1
  145. package/dist/widgets/widget.d.mts +1 -1
  146. package/package.json +6 -6
  147. package/dist/ansi-CxgRPLA2.mjs.map +0 -1
  148. package/dist/client-BWm6dNZF.mjs.map +0 -1
  149. package/dist/client-C69qWhMG.mjs +0 -1
  150. package/dist/ctx-CYEzzH-7.mjs +0 -1
  151. package/dist/log-D6gGicwQ.mjs.map +0 -1
  152. package/dist/markdown-DvDIPT5L.mjs.map +0 -1
  153. package/dist/queries-Dmd3IjSd.d.mts.map +0 -1
  154. package/dist/renderer-Dv5fIuIk.mjs +0 -1
  155. package/dist/renderer-Ksfkfv5X.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"clipboard-UEoX9sup.mjs","names":[],"sources":["../src/input/clipboard.ts"],"sourcesContent":["import { safeStat } from \"@zaly/shared\"\nimport { isSSH } from \"@zaly/shared/env\"\nimport { Spawn, spawnText, spawnWithInput, which } from \"@zaly/shared/process\"\nimport { mkdtempSync, writeFileSync } from \"node:fs\"\nimport { tmpdir } from \"node:os\"\nimport { platform } from \"node:process\"\nimport { join } from \"pathe\"\n\n/**\n * Read text or image content from the system clipboard.\n *\n * Agent CLIs need this to make `ctrl-v` \"just work\" — most terminals\n * don't forward clipboard paste events (they only forward typed keys),\n * so the app has to query the OS clipboard itself via the platform's\n * native tool. The detection order mirrors Neovim's clipboard provider:\n *\n * 1. macOS → `pbpaste` + `osascript` (for images)\n * 2. Wayland → `wl-paste`\n * 3. X11 (xsel) → `xsel`\n * 4. X11 (xclip) → `xclip`\n * 5. WSL → `win32yank.exe`\n * 6. Windows → PowerShell `Get-Clipboard`\n *\n * The chosen provider is cached per process. Spawn primitives live in\n * `@zaly/shared/process` — this file is just glue plus the per-platform\n * AppleScript/PowerShell snippets.\n */\n\n// ---- types -----------------------------------------------------------\n\n/** Image bytes on the clipboard, already written to a temp PNG. */\nexport interface ClipboardImage {\n kind: \"image\"\n /** Absolute path to a temporary PNG file. Caller takes ownership. */\n path: string\n /** MIME type. PNG only for now; kept for future formats. */\n type: \"image/png\"\n}\n\n/** One or more file references on the clipboard — what you get when\n * the user copies a file in their file manager and pastes. */\nexport interface ClipboardFiles {\n kind: \"files\"\n /** Absolute filesystem paths. */\n paths: string[]\n}\n\n/** Plain text on the clipboard — the common case. */\nexport interface ClipboardText {\n kind: \"text\"\n text: string\n}\n\n/** Anything the clipboard can hold that this library surfaces. */\nexport type ClipboardContent = ClipboardImage | ClipboardFiles | ClipboardText\n\n/** One of the content kinds `ClipboardContent` can take. Derived from\n * the union so adding a new variant doesn't require updating this\n * alias separately. */\nexport type ClipboardKind = ClipboardContent[\"kind\"]\n\n/** Internal provider contract. Each backend owns its own \"probe types,\n * pick best, fetch once\" logic — on platforms like Wayland and X11\n * that can enumerate available types, this avoids doing three or four\n * probe round-trips for every paste.\n *\n * `read(kind?)` takes an optional hint: when specified, the backend\n * can skip type enumeration and fetch that kind directly (fast path\n * for callers that already know what they want). When omitted, the\n * backend returns the richest available content. `writeText` is\n * optional since not every backend supports writes. */\ninterface Provider {\n readonly id: string\n read: (kind?: ClipboardKind) => Promise<ClipboardContent | undefined>\n writeText?: (text: string) => Promise<boolean>\n}\n\n// ---- public API ------------------------------------------------------\n\nlet cached: Provider | undefined\nlet cacheResolved = false\n\n/** Detect + cache the clipboard provider for this process. Returns\n * `undefined` when no provider is available. */\nfunction getClipboard(): Provider | undefined {\n if (cacheResolved) return cached\n cached = detect()\n cacheResolved = true\n return cached\n}\n\n/**\n * Read the system clipboard, returning the richest content available.\n *\n * Priority order (decided by each backend internally):\n * 1. **image** — raw image bytes on the clipboard (e.g. a screenshot\n * from `cmd-shift-4`, or an image copied from a web page).\n * 2. **files** — file URIs (e.g. the user copied a PDF / video in\n * their file manager).\n * 3. **text** — plain text.\n *\n * Resolves to `undefined` when the clipboard is empty, no provider is\n * available, or every read fails. Never throws — clipboard access is\n * best-effort.\n */\nasync function readClipboard(): Promise<ClipboardContent | undefined>\nasync function readClipboard(kind: \"text\"): Promise<ClipboardText | undefined>\nasync function readClipboard(kind: \"image\"): Promise<ClipboardImage | undefined>\nasync function readClipboard(kind: \"files\"): Promise<ClipboardFiles | undefined>\nasync function readClipboard(kind?: ClipboardKind): Promise<ClipboardContent | undefined> {\n const p = getClipboard()\n if (!p) return undefined\n try {\n return await p.read(kind)\n } catch {\n return undefined\n }\n}\n\n/** Reset the cached provider — mostly for tests that want to force a\n * re-probe. Also useful if the environment changes (e.g. Wayland\n * display started after process boot). */\nfunction resetClipboardCache(): void {\n cached = undefined\n cacheResolved = false\n}\n\n/**\n * Copy text to the system clipboard.\n *\n * Policy:\n * - **SSH session** → always OSC 52. Native tools on the remote box\n * would write to the remote clipboard, which isn't what the user\n * wants. OSC 52 routes through the local terminal.\n * - **Native tool available** → use it (fast, silent, no prompts).\n * - **Neither** → OSC 52 anyway, fire-and-forget.\n *\n * Resolves to `true` on best-effort delivery; `false` only when we had\n * nothing to try. OSC 52 writes are silent by design — the terminal\n * never confirms receipt — so `true` means \"we emitted\" not \"the\n * user's clipboard definitely has it\".\n */\nasync function writeClipboardText(text: string): Promise<boolean> {\n if (isSSH) {\n writeOsc52(text)\n return true\n }\n const p = getClipboard()\n if (p?.writeText) {\n try {\n if (await p.writeText(text)) return true\n } catch {\n /* fall through to OSC 52 */\n }\n }\n writeOsc52(text)\n return true\n}\n\n/**\n * Namespace export for readable call sites:\n *\n * ```ts\n * import { clipboard } from \"@zaly/tui\"\n *\n * await clipboard.read()\n * await clipboard.read(\"text\")\n * await clipboard.write(\"hello\")\n * clipboard.reset()\n * ```\n */\nexport const clipboard = {\n read: readClipboard,\n reset: resetClipboardCache,\n write: writeClipboardText,\n}\n\n/** Emit an OSC 52 clipboard write. Fire-and-forget; some terminals\n * ignore or prompt on this, and there's no response channel. */\nfunction writeOsc52(text: string): void {\n const b64 = Buffer.from(text, \"utf8\").toString(\"base64\")\n // `\\x1b]52;c;<base64>\\x07` — `c` targets the system clipboard.\n process.stdout.write(`\\x1b]52;c;${b64}\\x07`)\n}\n\n// ---- detection -------------------------------------------------------\n\nfunction detect(): Provider | undefined {\n if (platform === \"darwin\" && which(\"pbpaste\")) return macos()\n\n // Wayland before X11 — XWayland means both vars can be set on a\n // Wayland session, and wl-paste is the right tool there.\n if (process.env.WAYLAND_DISPLAY && which(\"wl-paste\")) return wayland()\n\n // X11: prefer xsel (nicer selection handling, per Neovim), then xclip.\n if (process.env.DISPLAY) {\n if (which(\"xsel\")) return xsel()\n if (which(\"xclip\")) return xclip()\n }\n\n // WSL exposes Windows' clipboard via win32yank if the user installed it.\n if (which(\"win32yank.exe\")) return wsl()\n\n // Plain Windows: PowerShell is always present on supported versions.\n if (platform === \"win32\") return windows()\n\n return undefined\n}\n\n// ---- backends --------------------------------------------------------\n\nfunction macos(): Provider {\n return {\n id: \"pbpaste\",\n read: async (kind) => {\n // macOS has no cheap \"list types\" call, so when no kind is\n // specified we probe in richness order: image → files → text.\n // Each AppleScript is wrapped in `try…on error` so a miss is\n // an empty string, not a spawn failure.\n if (kind === \"image\") return macosImage()\n if (kind === \"files\") return macosFiles()\n if (kind === \"text\") return macosText()\n const img = await macosImage()\n if (img) return img\n const files = await macosFiles()\n if (files) return files\n return macosText()\n },\n writeText: (text) => spawnWithInput(\"pbcopy\", [], text),\n }\n}\n\nasync function macosText(): Promise<ClipboardText | undefined> {\n const text = await spawnText(\"pbpaste\", [])\n return text === undefined ? undefined : { kind: \"text\", text }\n}\n\nasync function macosImage(): Promise<ClipboardImage | undefined> {\n const script = `\n try\n set png to (the clipboard as «class PNGf»)\n set b64 to do shell script \"base64\" with input (png as string)\n return b64\n on error\n return \"\"\n end try\n `\n const out = await spawnText(\"osascript\", [\"-e\", script])\n if (!out) return undefined\n return writeTempPng(Buffer.from(out.trim(), \"base64\"))\n}\n\nasync function macosFiles(): Promise<ClipboardFiles | undefined> {\n const script = `\n try\n set theClipboard to the clipboard as «class furl»\n return POSIX path of theClipboard\n on error\n try\n set theList to the clipboard as list\n set posix to \"\"\n repeat with item_ in theList\n try\n set posix to posix & (POSIX path of item_) & linefeed\n end try\n end repeat\n return posix\n on error\n return \"\"\n end try\n end try\n `\n const out = await spawnText(\"osascript\", [\"-e\", script])\n if (!out) return undefined\n const paths = out\n .split(/\\r?\\n/)\n .map((s) => s.trim())\n .filter((s) => s !== \"\")\n return paths.length > 0 ? { kind: \"files\", paths } : undefined\n}\n\nfunction wayland(): Provider {\n return {\n id: \"wl-paste\",\n read: async (kind) => {\n if (kind === \"image\") return waylandImage()\n if (kind === \"files\") return waylandFiles()\n if (kind === \"text\") return waylandText()\n // Richest-first: one `--list-types` call decides everything.\n const types = await spawnText(\"wl-paste\", [\"--list-types\"])\n if (!types) return undefined\n if (/\\bimage\\/png\\b/.test(types)) {\n const img = await waylandImage()\n if (img) return img\n }\n if (/\\btext\\/uri-list\\b/.test(types)) {\n const files = await waylandFiles()\n if (files) return files\n }\n return waylandText()\n },\n writeText: (text) => spawnWithInput(\"wl-copy\", [\"--type\", \"text/plain\"], text),\n }\n}\n\nasync function waylandImage(): Promise<ClipboardImage | undefined> {\n const r = await new Spawn(\"wl-paste\", [\"--type\", \"image/png\"]).result\n return r.code === 0 && r.stdout.length > 0 ? writeTempPng(r.stdout) : undefined\n}\n\nasync function waylandFiles(): Promise<ClipboardFiles | undefined> {\n const paths = parseUriList(await spawnText(\"wl-paste\", [\"--type\", \"text/uri-list\"]))\n return paths.length > 0 ? { kind: \"files\", paths } : undefined\n}\n\nasync function waylandText(): Promise<ClipboardText | undefined> {\n const text = await spawnText(\"wl-paste\", [\"--no-newline\"])\n return text === undefined ? undefined : { kind: \"text\", text }\n}\n\nfunction xsel(): Provider {\n // xsel doesn't support MIME-typed targets (no image or file-list\n // reads). When xclip is also installed, delegate non-text reads to\n // it and keep xsel for text only.\n const xclipFallback = which(\"xclip\")\n return {\n id: xclipFallback ? \"xsel+xclip\" : \"xsel\",\n read: async (kind) => {\n if (kind === \"text\") return xselText()\n if (xclipFallback) {\n if (kind === \"image\") return xclipImage()\n if (kind === \"files\") return xclipFiles()\n const rich = await xclipRead()\n if (rich && rich.kind !== \"text\") return rich\n }\n return xselText()\n },\n writeText: (text) => spawnWithInput(\"xsel\", [\"--nodetach\", \"-i\", \"-b\"], text),\n }\n}\n\nfunction xclip(): Provider {\n return {\n id: \"xclip\",\n read: (kind) => {\n if (kind === \"image\") return xclipImage()\n if (kind === \"files\") return xclipFiles()\n if (kind === \"text\") return xclipText()\n return xclipRead()\n },\n writeText: (text) => spawnWithInput(\"xclip\", [\"-quiet\", \"-i\", \"-selection\", \"clipboard\"], text),\n }\n}\n\nasync function xclipRead(): Promise<ClipboardContent | undefined> {\n // One TARGETS call covers the whole decision tree.\n const targets = await spawnText(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", \"TARGETS\", \"-o\"])\n if (!targets) return undefined\n if (/\\bimage\\/png\\b/.test(targets)) {\n const img = await xclipImage()\n if (img) return img\n }\n if (/\\btext\\/uri-list\\b/.test(targets)) {\n const files = await xclipFiles()\n if (files) return files\n }\n return xclipText()\n}\n\nasync function xclipImage(): Promise<ClipboardImage | undefined> {\n const r = await new Spawn(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", \"image/png\", \"-o\"]).result\n return r.code === 0 && r.stdout.length > 0 ? writeTempPng(r.stdout) : undefined\n}\n\nasync function xclipFiles(): Promise<ClipboardFiles | undefined> {\n const paths = parseUriList(\n await spawnText(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", \"text/uri-list\", \"-o\"])\n )\n return paths.length > 0 ? { kind: \"files\", paths } : undefined\n}\n\nasync function xclipText(): Promise<ClipboardText | undefined> {\n const text = await spawnText(\"xclip\", [\"-o\", \"-selection\", \"clipboard\"])\n return text === undefined ? undefined : { kind: \"text\", text }\n}\n\nasync function xselText(): Promise<ClipboardText | undefined> {\n const text = await spawnText(\"xsel\", [\"-o\", \"-b\"])\n return text === undefined ? undefined : { kind: \"text\", text }\n}\n\nfunction wsl(): Provider {\n // win32yank is text-only; image / file paste from WSL would need a\n // different shim, so we only expose text here. `kind === \"image\"`\n // or `\"files\"` always resolves to undefined.\n return {\n id: \"win32yank\",\n read: async (kind) => {\n if (kind === \"image\" || kind === \"files\") return undefined\n const text = await spawnText(\"win32yank.exe\", [\"-o\", \"--lf\"])\n return text === undefined ? undefined : { kind: \"text\", text }\n },\n writeText: (text) => spawnWithInput(\"win32yank.exe\", [\"-i\", \"--crlf\"], text),\n }\n}\n\nfunction windows(): Provider {\n return {\n id: \"powershell\",\n read: async (kind) => {\n if (kind === \"image\") return windowsImage()\n if (kind === \"files\") return windowsFiles()\n if (kind === \"text\") return windowsText()\n const img = await windowsImage()\n if (img) return img\n const files = await windowsFiles()\n if (files) return files\n return windowsText()\n },\n writeText: (text) => spawnWithInput(\"clip\", [], text),\n }\n}\n\nasync function windowsText(): Promise<ClipboardText | undefined> {\n const text = await spawnText(\"powershell\", [\"-NoProfile\", \"-NoLogo\", \"-Command\", \"Get-Clipboard\"])\n return text === undefined ? undefined : { kind: \"text\", text }\n}\n\nasync function windowsImage(): Promise<ClipboardImage | undefined> {\n // PowerShell: fetch image from clipboard, save as PNG to a temp path.\n // Nothing on clipboard → the `if` skips and the file stays empty.\n const tmp = writeTempPath(\"zaly-clip-\", \".png\")\n const script = [\n \"Add-Type -AssemblyName System.Windows.Forms\",\n \"$img = [System.Windows.Forms.Clipboard]::GetImage()\",\n `if ($img) { $img.Save('${tmp.replace(/\\\\/g, String.raw`\\\\`)}') }`,\n ].join(\"; \")\n await spawnText(\"powershell\", [\"-NoProfile\", \"-NoLogo\", \"-Command\", script])\n if ((safeStat(tmp)?.size ?? 0) > 0) return { kind: \"image\", path: tmp, type: \"image/png\" }\n return undefined\n}\n\nasync function windowsFiles(): Promise<ClipboardFiles | undefined> {\n const out = await spawnText(\"powershell\", [\n \"-NoProfile\",\n \"-NoLogo\",\n \"-Command\",\n \"Get-Clipboard -Format FileDropList | ForEach-Object { $_.FullName }\",\n ])\n if (!out) return undefined\n const paths = out\n .split(/\\r?\\n/)\n .map((s) => s.trim())\n .filter((s) => s !== \"\")\n return paths.length > 0 ? { kind: \"files\", paths } : undefined\n}\n\n// ---- temp files + URI list parsing ----------------------------------\n\nfunction writeTempPng(bytes: Buffer): ClipboardImage {\n const path = writeTempPath(\"zaly-clip-\", \".png\")\n writeFileSync(path, bytes)\n return { kind: \"image\", path, type: \"image/png\" }\n}\n\n/** Parse a `text/uri-list` blob (RFC 2483): `file://…\\r\\n` lines, skip\n * blanks and comment lines (`#…`), decode percent-encoding. Returns\n * absolute filesystem paths. Non-`file://` URIs are dropped — we\n * can't hand a remote URL off as an attachment path. */\nfunction parseUriList(raw: string | undefined): string[] {\n if (!raw) return []\n const out: string[] = []\n for (const line of raw.split(/\\r?\\n/)) {\n const s = line.trim()\n if (s === \"\" || s.startsWith(\"#\")) continue\n if (!s.startsWith(\"file://\")) continue\n try {\n let path = decodeURIComponent(s.slice(\"file://\".length))\n // `file:///C:/foo` → `/C:/foo` after the slice. Strip the leading\n // slash so we end up with a real Windows path.\n if (/^\\/[A-Za-z]:\\//.test(path)) path = path.slice(1)\n out.push(path)\n } catch {\n /* malformed URI — skip */\n }\n }\n return out\n}\n\nfunction writeTempPath(prefix: string, ext: string): string {\n const dir = mkdtempSync(join(tmpdir(), prefix))\n return join(dir, `clip${ext}`)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"clipboard-wzJ1EVfy.mjs","names":[],"sources":["../src/input/clipboard.ts"],"sourcesContent":["import { safeStat } from \"@zaly/shared\"\nimport { isSSH } from \"@zaly/shared/env\"\nimport { Spawn, spawnText, spawnWithInput, which } from \"@zaly/shared/process\"\nimport { mkdtempSync, writeFileSync } from \"node:fs\"\nimport { tmpdir } from \"node:os\"\nimport { platform } from \"node:process\"\nimport { join } from \"pathe\"\n\n/**\n * Read text or image content from the system clipboard.\n *\n * Agent CLIs need this to make `ctrl-v` \"just work\" — most terminals\n * don't forward clipboard paste events (they only forward typed keys),\n * so the app has to query the OS clipboard itself via the platform's\n * native tool. The detection order mirrors Neovim's clipboard provider:\n *\n * 1. macOS → `pbpaste` + `osascript` (for images)\n * 2. Wayland → `wl-paste`\n * 3. X11 (xsel) → `xsel`\n * 4. X11 (xclip) → `xclip`\n * 5. WSL → `win32yank.exe`\n * 6. Windows → PowerShell `Get-Clipboard`\n *\n * The chosen provider is cached per process. Spawn primitives live in\n * `@zaly/shared/process` — this file is just glue plus the per-platform\n * AppleScript/PowerShell snippets.\n */\n\n// ---- types -----------------------------------------------------------\n\n/** Image bytes on the clipboard, already written to a temp PNG. */\nexport interface ClipboardImage {\n kind: \"image\"\n /** Absolute path to a temporary PNG file. Caller takes ownership. */\n path: string\n /** MIME type. PNG only for now; kept for future formats. */\n type: \"image/png\"\n}\n\n/** One or more file references on the clipboard — what you get when\n * the user copies a file in their file manager and pastes. */\nexport interface ClipboardFiles {\n kind: \"files\"\n /** Absolute filesystem paths. */\n paths: string[]\n}\n\n/** Plain text on the clipboard — the common case. */\nexport interface ClipboardText {\n kind: \"text\"\n text: string\n}\n\n/** Anything the clipboard can hold that this library surfaces. */\nexport type ClipboardContent = ClipboardImage | ClipboardFiles | ClipboardText\n\n/** One of the content kinds `ClipboardContent` can take. Derived from\n * the union so adding a new variant doesn't require updating this\n * alias separately. */\nexport type ClipboardKind = ClipboardContent[\"kind\"]\n\n/** Internal provider contract. Each backend owns its own \"probe types,\n * pick best, fetch once\" logic — on platforms like Wayland and X11\n * that can enumerate available types, this avoids doing three or four\n * probe round-trips for every paste.\n *\n * `read(kind?)` takes an optional hint: when specified, the backend\n * can skip type enumeration and fetch that kind directly (fast path\n * for callers that already know what they want). When omitted, the\n * backend returns the richest available content. `writeText` is\n * optional since not every backend supports writes. */\ninterface Provider {\n readonly id: string\n read: (kind?: ClipboardKind) => Promise<ClipboardContent | undefined>\n writeText?: (text: string) => Promise<boolean>\n}\n\n// ---- public API ------------------------------------------------------\n\nlet cached: Provider | undefined\nlet cacheResolved = false\n\n/** Detect + cache the clipboard provider for this process. Returns\n * `undefined` when no provider is available. */\nfunction getClipboard(): Provider | undefined {\n if (cacheResolved) return cached\n cached = detect()\n cacheResolved = true\n return cached\n}\n\n/**\n * Read the system clipboard, returning the richest content available.\n *\n * Priority order (decided by each backend internally):\n * 1. **image** — raw image bytes on the clipboard (e.g. a screenshot\n * from `cmd-shift-4`, or an image copied from a web page).\n * 2. **files** — file URIs (e.g. the user copied a PDF / video in\n * their file manager).\n * 3. **text** — plain text.\n *\n * Resolves to `undefined` when the clipboard is empty, no provider is\n * available, or every read fails. Never throws — clipboard access is\n * best-effort.\n */\nasync function readClipboard(): Promise<ClipboardContent | undefined>\nasync function readClipboard(kind: \"text\"): Promise<ClipboardText | undefined>\nasync function readClipboard(kind: \"image\"): Promise<ClipboardImage | undefined>\nasync function readClipboard(kind: \"files\"): Promise<ClipboardFiles | undefined>\nasync function readClipboard(kind?: ClipboardKind): Promise<ClipboardContent | undefined> {\n const p = getClipboard()\n if (!p) return undefined\n try {\n return await p.read(kind)\n } catch {\n return undefined\n }\n}\n\n/** Reset the cached provider — mostly for tests that want to force a\n * re-probe. Also useful if the environment changes (e.g. Wayland\n * display started after process boot). */\nfunction resetClipboardCache(): void {\n cached = undefined\n cacheResolved = false\n}\n\n/**\n * Copy text to the system clipboard.\n *\n * Policy:\n * - **SSH session** → always OSC 52. Native tools on the remote box\n * would write to the remote clipboard, which isn't what the user\n * wants. OSC 52 routes through the local terminal.\n * - **Native tool available** → use it (fast, silent, no prompts).\n * - **Neither** → OSC 52 anyway, fire-and-forget.\n *\n * Resolves to `true` on best-effort delivery; `false` only when we had\n * nothing to try. OSC 52 writes are silent by design — the terminal\n * never confirms receipt — so `true` means \"we emitted\" not \"the\n * user's clipboard definitely has it\".\n */\nasync function writeClipboardText(text: string): Promise<boolean> {\n if (isSSH) {\n writeOsc52(text)\n return true\n }\n const p = getClipboard()\n if (p?.writeText) {\n try {\n if (await p.writeText(text)) return true\n } catch {\n /* fall through to OSC 52 */\n }\n }\n writeOsc52(text)\n return true\n}\n\n/**\n * Namespace export for readable call sites:\n *\n * ```ts\n * import { clipboard } from \"@zaly/tui\"\n *\n * await clipboard.read()\n * await clipboard.read(\"text\")\n * await clipboard.write(\"hello\")\n * clipboard.reset()\n * ```\n */\nexport const clipboard = {\n read: readClipboard,\n reset: resetClipboardCache,\n write: writeClipboardText,\n}\n\n/** Emit an OSC 52 clipboard write. Fire-and-forget; some terminals\n * ignore or prompt on this, and there's no response channel. */\nfunction writeOsc52(text: string): void {\n const b64 = Buffer.from(text, \"utf8\").toString(\"base64\")\n // `\\x1b]52;c;<base64>\\x07` — `c` targets the system clipboard.\n process.stdout.write(`\\x1b]52;c;${b64}\\x07`)\n}\n\n// ---- detection -------------------------------------------------------\n\nfunction detect(): Provider | undefined {\n if (platform === \"darwin\" && which(\"pbpaste\")) return macos()\n\n // Wayland before X11 — XWayland means both vars can be set on a\n // Wayland session, and wl-paste is the right tool there.\n if (process.env.WAYLAND_DISPLAY && which(\"wl-paste\")) return wayland()\n\n // X11: prefer xsel (nicer selection handling, per Neovim), then xclip.\n if (process.env.DISPLAY) {\n if (which(\"xsel\")) return xsel()\n if (which(\"xclip\")) return xclip()\n }\n\n // WSL exposes Windows' clipboard via win32yank if the user installed it.\n if (which(\"win32yank.exe\")) return wsl()\n\n // Plain Windows: PowerShell is always present on supported versions.\n if (platform === \"win32\") return windows()\n\n return undefined\n}\n\n// ---- backends --------------------------------------------------------\n\nfunction macos(): Provider {\n return {\n id: \"pbpaste\",\n read: async (kind) => {\n // macOS has no cheap \"list types\" call, so when no kind is\n // specified we probe in richness order: image → files → text.\n // Each AppleScript is wrapped in `try…on error` so a miss is\n // an empty string, not a spawn failure.\n if (kind === \"image\") return macosImage()\n if (kind === \"files\") return macosFiles()\n if (kind === \"text\") return macosText()\n const img = await macosImage()\n if (img) return img\n const files = await macosFiles()\n if (files) return files\n return macosText()\n },\n writeText: (text) => spawnWithInput(\"pbcopy\", [], text),\n }\n}\n\nasync function macosText(): Promise<ClipboardText | undefined> {\n const text = await spawnText(\"pbpaste\", [])\n return text === undefined ? undefined : { kind: \"text\", text }\n}\n\nasync function macosImage(): Promise<ClipboardImage | undefined> {\n const script = `\n try\n set png to (the clipboard as «class PNGf»)\n set b64 to do shell script \"base64\" with input (png as string)\n return b64\n on error\n return \"\"\n end try\n `\n const out = await spawnText(\"osascript\", [\"-e\", script])\n if (!out) return undefined\n return writeTempPng(Buffer.from(out.trim(), \"base64\"))\n}\n\nasync function macosFiles(): Promise<ClipboardFiles | undefined> {\n const script = `\n try\n set theClipboard to the clipboard as «class furl»\n return POSIX path of theClipboard\n on error\n try\n set theList to the clipboard as list\n set posix to \"\"\n repeat with item_ in theList\n try\n set posix to posix & (POSIX path of item_) & linefeed\n end try\n end repeat\n return posix\n on error\n return \"\"\n end try\n end try\n `\n const out = await spawnText(\"osascript\", [\"-e\", script])\n if (!out) return undefined\n const paths = out\n .split(/\\r?\\n/)\n .map((s) => s.trim())\n .filter((s) => s !== \"\")\n return paths.length > 0 ? { kind: \"files\", paths } : undefined\n}\n\nfunction wayland(): Provider {\n return {\n id: \"wl-paste\",\n read: async (kind) => {\n if (kind === \"image\") return waylandImage()\n if (kind === \"files\") return waylandFiles()\n if (kind === \"text\") return waylandText()\n // Richest-first: one `--list-types` call decides everything.\n const types = await spawnText(\"wl-paste\", [\"--list-types\"])\n if (!types) return undefined\n if (/\\bimage\\/png\\b/.test(types)) {\n const img = await waylandImage()\n if (img) return img\n }\n if (/\\btext\\/uri-list\\b/.test(types)) {\n const files = await waylandFiles()\n if (files) return files\n }\n return waylandText()\n },\n writeText: (text) => spawnWithInput(\"wl-copy\", [\"--type\", \"text/plain\"], text),\n }\n}\n\nasync function waylandImage(): Promise<ClipboardImage | undefined> {\n const r = await new Spawn(\"wl-paste\", [\"--type\", \"image/png\"]).result\n return r.code === 0 && r.stdout.length > 0 ? writeTempPng(r.stdout) : undefined\n}\n\nasync function waylandFiles(): Promise<ClipboardFiles | undefined> {\n const paths = parseUriList(await spawnText(\"wl-paste\", [\"--type\", \"text/uri-list\"]))\n return paths.length > 0 ? { kind: \"files\", paths } : undefined\n}\n\nasync function waylandText(): Promise<ClipboardText | undefined> {\n const text = await spawnText(\"wl-paste\", [\"--no-newline\"])\n return text === undefined ? undefined : { kind: \"text\", text }\n}\n\nfunction xsel(): Provider {\n // xsel doesn't support MIME-typed targets (no image or file-list\n // reads). When xclip is also installed, delegate non-text reads to\n // it and keep xsel for text only.\n const xclipFallback = which(\"xclip\")\n return {\n id: xclipFallback ? \"xsel+xclip\" : \"xsel\",\n read: async (kind) => {\n if (kind === \"text\") return xselText()\n if (xclipFallback) {\n if (kind === \"image\") return xclipImage()\n if (kind === \"files\") return xclipFiles()\n const rich = await xclipRead()\n if (rich && rich.kind !== \"text\") return rich\n }\n return xselText()\n },\n writeText: (text) => spawnWithInput(\"xsel\", [\"--nodetach\", \"-i\", \"-b\"], text),\n }\n}\n\nfunction xclip(): Provider {\n return {\n id: \"xclip\",\n read: (kind) => {\n if (kind === \"image\") return xclipImage()\n if (kind === \"files\") return xclipFiles()\n if (kind === \"text\") return xclipText()\n return xclipRead()\n },\n writeText: (text) => spawnWithInput(\"xclip\", [\"-quiet\", \"-i\", \"-selection\", \"clipboard\"], text),\n }\n}\n\nasync function xclipRead(): Promise<ClipboardContent | undefined> {\n // One TARGETS call covers the whole decision tree.\n const targets = await spawnText(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", \"TARGETS\", \"-o\"])\n if (!targets) return undefined\n if (/\\bimage\\/png\\b/.test(targets)) {\n const img = await xclipImage()\n if (img) return img\n }\n if (/\\btext\\/uri-list\\b/.test(targets)) {\n const files = await xclipFiles()\n if (files) return files\n }\n return xclipText()\n}\n\nasync function xclipImage(): Promise<ClipboardImage | undefined> {\n const r = await new Spawn(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", \"image/png\", \"-o\"]).result\n return r.code === 0 && r.stdout.length > 0 ? writeTempPng(r.stdout) : undefined\n}\n\nasync function xclipFiles(): Promise<ClipboardFiles | undefined> {\n const paths = parseUriList(\n await spawnText(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", \"text/uri-list\", \"-o\"])\n )\n return paths.length > 0 ? { kind: \"files\", paths } : undefined\n}\n\nasync function xclipText(): Promise<ClipboardText | undefined> {\n const text = await spawnText(\"xclip\", [\"-o\", \"-selection\", \"clipboard\"])\n return text === undefined ? undefined : { kind: \"text\", text }\n}\n\nasync function xselText(): Promise<ClipboardText | undefined> {\n const text = await spawnText(\"xsel\", [\"-o\", \"-b\"])\n return text === undefined ? undefined : { kind: \"text\", text }\n}\n\nfunction wsl(): Provider {\n // win32yank is text-only; image / file paste from WSL would need a\n // different shim, so we only expose text here. `kind === \"image\"`\n // or `\"files\"` always resolves to undefined.\n return {\n id: \"win32yank\",\n read: async (kind) => {\n if (kind === \"image\" || kind === \"files\") return undefined\n const text = await spawnText(\"win32yank.exe\", [\"-o\", \"--lf\"])\n return text === undefined ? undefined : { kind: \"text\", text }\n },\n writeText: (text) => spawnWithInput(\"win32yank.exe\", [\"-i\", \"--crlf\"], text),\n }\n}\n\nfunction windows(): Provider {\n return {\n id: \"powershell\",\n read: async (kind) => {\n if (kind === \"image\") return windowsImage()\n if (kind === \"files\") return windowsFiles()\n if (kind === \"text\") return windowsText()\n const img = await windowsImage()\n if (img) return img\n const files = await windowsFiles()\n if (files) return files\n return windowsText()\n },\n writeText: (text) => spawnWithInput(\"clip\", [], text),\n }\n}\n\nasync function windowsText(): Promise<ClipboardText | undefined> {\n const text = await spawnText(\"powershell\", [\"-NoProfile\", \"-NoLogo\", \"-Command\", \"Get-Clipboard\"])\n return text === undefined ? undefined : { kind: \"text\", text }\n}\n\nasync function windowsImage(): Promise<ClipboardImage | undefined> {\n // PowerShell: fetch image from clipboard, save as PNG to a temp path.\n // Nothing on clipboard → the `if` skips and the file stays empty.\n const tmp = writeTempPath(\"zaly-clip-\", \".png\")\n const script = [\n \"Add-Type -AssemblyName System.Windows.Forms\",\n \"$img = [System.Windows.Forms.Clipboard]::GetImage()\",\n `if ($img) { $img.Save('${tmp.replace(/\\\\/g, String.raw`\\\\`)}') }`,\n ].join(\"; \")\n await spawnText(\"powershell\", [\"-NoProfile\", \"-NoLogo\", \"-Command\", script])\n if ((safeStat(tmp)?.size ?? 0) > 0) return { kind: \"image\", path: tmp, type: \"image/png\" }\n return undefined\n}\n\nasync function windowsFiles(): Promise<ClipboardFiles | undefined> {\n const out = await spawnText(\"powershell\", [\n \"-NoProfile\",\n \"-NoLogo\",\n \"-Command\",\n \"Get-Clipboard -Format FileDropList | ForEach-Object { $_.FullName }\",\n ])\n if (!out) return undefined\n const paths = out\n .split(/\\r?\\n/)\n .map((s) => s.trim())\n .filter((s) => s !== \"\")\n return paths.length > 0 ? { kind: \"files\", paths } : undefined\n}\n\n// ---- temp files + URI list parsing ----------------------------------\n\nfunction writeTempPng(bytes: Buffer): ClipboardImage {\n const path = writeTempPath(\"zaly-clip-\", \".png\")\n writeFileSync(path, bytes)\n return { kind: \"image\", path, type: \"image/png\" }\n}\n\n/** Parse a `text/uri-list` blob (RFC 2483): `file://…\\r\\n` lines, skip\n * blanks and comment lines (`#…`), decode percent-encoding. Returns\n * absolute filesystem paths. Non-`file://` URIs are dropped — we\n * can't hand a remote URL off as an attachment path. */\nfunction parseUriList(raw: string | undefined): string[] {\n if (!raw) return []\n const out: string[] = []\n for (const line of raw.split(/\\r?\\n/)) {\n const s = line.trim()\n if (s === \"\" || s.startsWith(\"#\")) continue\n if (!s.startsWith(\"file://\")) continue\n try {\n let path = decodeURIComponent(s.slice(\"file://\".length))\n // `file:///C:/foo` → `/C:/foo` after the slice. Strip the leading\n // slash so we end up with a real Windows path.\n if (/^\\/[A-Za-z]:\\//.test(path)) path = path.slice(1)\n out.push(path)\n } catch {\n /* malformed URI — skip */\n }\n }\n return out\n}\n\nfunction writeTempPath(prefix: string, ext: string): string {\n const dir = mkdtempSync(join(tmpdir(), prefix))\n return join(dir, `clip${ext}`)\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- export { t as clipboard } from "./clipboard-UEoX9sup.mjs";
1
+ export { t as clipboard } from "./clipboard-wzJ1EVfy.mjs";
@@ -0,0 +1 @@
1
+ export { t as createCtx } from "./ctx-D_rMOcp5.mjs";
@@ -1,5 +1,5 @@
1
+ import { t as styleBuilder } from "./builder-D8Sx5N8a.mjs";
1
2
  import { t as defaultTheme } from "./registry-CQU0VwpM.mjs";
2
- import { t as styleBuilder } from "./builder-BdYlK1Ew.mjs";
3
3
  // src/core/ctx.ts
4
4
  /**
5
5
  * Current terminal width in cells. Returns `undefined` when stdout isn't a
@@ -39,4 +39,4 @@ function createCtx(opts = {}) {
39
39
  }
40
40
  export { createCtx as t };
41
41
 
42
- //# sourceMappingURL=ctx-DBeFcZt6.mjs.map
42
+ //# sourceMappingURL=ctx-D_rMOcp5.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"ctx-DBeFcZt6.mjs","names":[],"sources":["../src/core/ctx.ts"],"sourcesContent":["import type { Emitter } from \"@zaly/shared\"\nimport type { Logger } from \"@zaly/shared/logger\"\nimport type { Actions } from \"../input/actions.ts\"\nimport type { TerminalQueries } from \"../input/queries.ts\"\nimport type { InputRouterEvents } from \"../input/router.ts\"\nimport type { SurfaceType } from \"../renderer/renderer.ts\"\nimport type { StyleBuilder } from \"../style/builder.ts\"\nimport type { Theme } from \"../themes/types.ts\"\nimport type { Overlay } from \"../widgets/overlay.ts\"\nimport type { Node } from \"./node.ts\"\n\nimport { styleBuilder } from \"../style/builder.ts\"\nimport { defaultTheme } from \"../themes/registry.ts\"\n\n/**\n * Passed to every `render(ctx)` call. Width flows in; height emerges from\n * the returned row count. Theme is ambient — children share the parent's.\n *\n * `style` is a theme-bound chainable builder made available to components so\n * they can produce inline-styled strings without re-binding the theme.\n *\n * `version` is a monotonic cache-key bumped by the `Renderer` whenever any\n * identity-relevant ctx field changes (resize, theme swap). Node caches\n * compare this integer — no hashing, no string keys.\n */\nexport interface RenderCtx {\n width: number\n style: StyleBuilder\n version: number\n /** Queue a side-channel ANSI payload (e.g. KGP image transmit) for\n * the renderer to flush before the next paint. Use this to keep\n * side-effecting bytes out of cached row strings — what `_render`\n * returns should be safe to reuse across paints, and transmit\n * bytes shouldn't be. */\n readonly transmit: (seq: string) => void\n}\n\n/**\n * Context handed to a node when it mounts onto a surface. Gives widget\n * authors a scoped handle to the services they legitimately need —\n * focus, overlays, tree lookups, out-of-band repaints — without\n * exposing the full Renderer.\n *\n * MountCtx is *per-lifetime*: set once when `mount()` is called and\n * cleared on `unmount()`. Distinct from `RenderCtx` (per-tick, carries\n * width/theme/version). If a node moves between surfaces, it receives\n * a fresh MountCtx via the subsequent `mount()` call.\n */\nexport interface MountCtx {\n /** Which surface owns this subtree. */\n readonly surface: SurfaceType\n\n /** Overlay capabilities — open/close an Overlay node from inside a\n * widget (e.g. a confirm dialog, autocomplete popover, tooltip). */\n readonly overlay: {\n readonly add: (o: () => Overlay) => void\n readonly remove: (o: Overlay) => void\n }\n\n /** Input capabilities — a narrow slice of the router. We deliberately\n * don't expose the full `InputRouter`: `setKeymap` / `dispatch` are\n * app-level concerns. Widgets legitimately need to install direct\n * global key bindings (autocomplete) and move focus. */\n readonly input: {\n readonly terminalFocus: boolean\n readonly events: Emitter<InputRouterEvents>\n readonly bind: Actions[\"bind\"]\n /** Move focus to `node`. Mirrors `router.focus(node)`. */\n readonly focus: (node: Node) => void\n /** Clear focus. */\n readonly blur: (node: Node) => void\n readonly queries: TerminalQueries\n }\n\n /** Action registry — widgets and plugins can `register` catalog\n * entries (with desc + default keys + optional `fn`) and `dispatch`\n * actions by id. The Router feeds key-triggered dispatches through\n * here; programmatic callers do the same. */\n readonly actions: Actions\n\n /** Look up a node anywhere in the tree by its `id`. First match\n * wins; `undefined` when nothing matches. Same semantics as\n * `Renderer.getNode`. */\n readonly getNode: (id: string) => Node | undefined\n /** Find every node matching a predicate. Strings match `node.type`;\n * pass a function for richer predicates. Same semantics as\n * `Renderer.findNode`. */\n readonly findNode: (match: string | ((n: Node) => boolean)) => Node[]\n\n logger: Logger\n}\n\n/**\n * Current terminal width in cells. Returns `undefined` when stdout isn't a\n * TTY (piped output, tests, non-Node/Bun runtimes) — callers should fall\n * back to a sensible default when this is `undefined`.\n *\n * @internal\n */\nfunction termWidth(): number | undefined {\n if (typeof process === \"undefined\") return undefined\n const cols = process.stdout.columns\n return typeof cols === \"number\" && cols > 0 ? cols : undefined\n}\n\n/**\n * Build a fresh `RenderCtx` from a resolved theme + width. Binds a `style`\n * builder to the theme so components can use `ctx.style.primary(...)`\n * without constructing their own.\n *\n * Width is clamped to the terminal's current column count (falling back to\n * 80 when stdout isn't a TTY). No width passed → terminal width is used\n * directly; an explicit `width` wider than the terminal is capped so\n * components never produce rows that wrap the display. Narrower explicit\n * widths (e.g. for fixed-layout tests) pass through unchanged.\n *\n * Dynamic theme loading (from a string name) lives in `themes/loadTheme`\n * — resolve the name first, then pass the `Theme` object here.\n */\nexport function createCtx(opts: Partial<RenderCtx> & { theme?: Theme } = {}): RenderCtx {\n const tw = termWidth() ?? 80\n return {\n style: opts.style ?? styleBuilder(opts.theme ?? defaultTheme),\n transmit: (data) => process.stdout.write(data),\n version: opts.version ?? 0,\n width: Math.min(opts.width ?? tw, tw),\n ...opts,\n }\n}\n"],"mappings":";;;;;;;;;;AAmGA,SAAS,YAAgC;CACvC,IAAI,OAAO,YAAY,aAAa,OAAO,KAAA;CAC3C,MAAM,OAAO,QAAQ,OAAO;CAC5B,OAAO,OAAO,SAAS,YAAY,OAAO,IAAI,OAAO,KAAA;AACvD;;;;;;;;;;;;;;;AAgBA,SAAgB,UAAU,OAA+C,CAAC,GAAc;CACtF,MAAM,KAAK,UAAU,KAAK;CAC1B,OAAO;EACL,OAAO,KAAK,SAAS,aAAa,KAAK,SAAS,YAAY;EAC5D,WAAW,SAAS,QAAQ,OAAO,MAAM,IAAI;EAC7C,SAAS,KAAK,WAAW;EACzB,OAAO,KAAK,IAAI,KAAK,SAAS,IAAI,EAAE;EACpC,GAAG;CACL;AACF"}
1
+ {"version":3,"file":"ctx-D_rMOcp5.mjs","names":[],"sources":["../src/core/ctx.ts"],"sourcesContent":["import type { Emitter } from \"@zaly/shared\"\nimport type { Logger } from \"@zaly/shared/logger\"\nimport type { Actions } from \"../input/actions.ts\"\nimport type { TerminalQueries } from \"../input/queries.ts\"\nimport type { InputRouterEvents } from \"../input/router.ts\"\nimport type { SurfaceType } from \"../renderer/renderer.ts\"\nimport type { StyleBuilder } from \"../style/builder.ts\"\nimport type { Theme } from \"../themes/types.ts\"\nimport type { Overlay } from \"../widgets/overlay.ts\"\nimport type { Node } from \"./node.ts\"\n\nimport { styleBuilder } from \"../style/builder.ts\"\nimport { defaultTheme } from \"../themes/registry.ts\"\n\n/**\n * Passed to every `render(ctx)` call. Width flows in; height emerges from\n * the returned row count. Theme is ambient — children share the parent's.\n *\n * `style` is a theme-bound chainable builder made available to components so\n * they can produce inline-styled strings without re-binding the theme.\n *\n * `version` is a monotonic cache-key bumped by the `Renderer` whenever any\n * identity-relevant ctx field changes (resize, theme swap). Node caches\n * compare this integer — no hashing, no string keys.\n */\nexport interface RenderCtx {\n width: number\n style: StyleBuilder\n version: number\n /** Queue a side-channel ANSI payload (e.g. KGP image transmit) for\n * the renderer to flush before the next paint. Use this to keep\n * side-effecting bytes out of cached row strings — what `_render`\n * returns should be safe to reuse across paints, and transmit\n * bytes shouldn't be. */\n readonly transmit: (seq: string) => void\n}\n\n/**\n * Context handed to a node when it mounts onto a surface. Gives widget\n * authors a scoped handle to the services they legitimately need —\n * focus, overlays, tree lookups, out-of-band repaints — without\n * exposing the full Renderer.\n *\n * MountCtx is *per-lifetime*: set once when `mount()` is called and\n * cleared on `unmount()`. Distinct from `RenderCtx` (per-tick, carries\n * width/theme/version). If a node moves between surfaces, it receives\n * a fresh MountCtx via the subsequent `mount()` call.\n */\nexport interface MountCtx {\n /** Which surface owns this subtree. */\n readonly surface: SurfaceType\n\n /** Overlay capabilities — open/close an Overlay node from inside a\n * widget (e.g. a confirm dialog, autocomplete popover, tooltip). */\n readonly overlay: {\n readonly add: (o: () => Overlay) => void\n readonly remove: (o: Overlay) => void\n }\n\n /** Input capabilities — a narrow slice of the router. We deliberately\n * don't expose the full `InputRouter`: `setKeymap` / `dispatch` are\n * app-level concerns. Widgets legitimately need to install direct\n * global key bindings (autocomplete) and move focus. */\n readonly input: {\n readonly terminalFocus: boolean\n readonly events: Emitter<InputRouterEvents>\n readonly bind: Actions[\"bind\"]\n /** Move focus to `node`. Mirrors `router.focus(node)`. */\n readonly focus: (node: Node) => void\n /** Clear focus. */\n readonly blur: (node: Node) => void\n readonly queries: TerminalQueries\n }\n\n /** Action registry — widgets and plugins can `register` catalog\n * entries (with desc + default keys + optional `fn`) and `dispatch`\n * actions by id. The Router feeds key-triggered dispatches through\n * here; programmatic callers do the same. */\n readonly actions: Actions\n\n /** Look up a node anywhere in the tree by its `id`. First match\n * wins; `undefined` when nothing matches. Same semantics as\n * `Renderer.getNode`. */\n readonly getNode: (id: string) => Node | undefined\n /** Find every node matching a predicate. Strings match `node.type`;\n * pass a function for richer predicates. Same semantics as\n * `Renderer.findNode`. */\n readonly findNode: (match: string | ((n: Node) => boolean)) => Node[]\n\n logger: Logger\n}\n\n/**\n * Current terminal width in cells. Returns `undefined` when stdout isn't a\n * TTY (piped output, tests, non-Node/Bun runtimes) — callers should fall\n * back to a sensible default when this is `undefined`.\n *\n * @internal\n */\nfunction termWidth(): number | undefined {\n if (typeof process === \"undefined\") return undefined\n const cols = process.stdout.columns\n return typeof cols === \"number\" && cols > 0 ? cols : undefined\n}\n\n/**\n * Build a fresh `RenderCtx` from a resolved theme + width. Binds a `style`\n * builder to the theme so components can use `ctx.style.primary(...)`\n * without constructing their own.\n *\n * Width is clamped to the terminal's current column count (falling back to\n * 80 when stdout isn't a TTY). No width passed → terminal width is used\n * directly; an explicit `width` wider than the terminal is capped so\n * components never produce rows that wrap the display. Narrower explicit\n * widths (e.g. for fixed-layout tests) pass through unchanged.\n *\n * Dynamic theme loading (from a string name) lives in `themes/loadTheme`\n * — resolve the name first, then pass the `Theme` object here.\n */\nexport function createCtx(opts: Partial<RenderCtx> & { theme?: Theme } = {}): RenderCtx {\n const tw = termWidth() ?? 80\n return {\n style: opts.style ?? styleBuilder(opts.theme ?? defaultTheme),\n transmit: (data) => process.stdout.write(data),\n version: opts.version ?? 0,\n width: Math.min(opts.width ?? tw, tw),\n ...opts,\n }\n}\n"],"mappings":";;;;;;;;;;AAmGA,SAAS,YAAgC;CACvC,IAAI,OAAO,YAAY,aAAa,OAAO,KAAA;CAC3C,MAAM,OAAO,QAAQ,OAAO;CAC5B,OAAO,OAAO,SAAS,YAAY,OAAO,IAAI,OAAO,KAAA;AACvD;;;;;;;;;;;;;;;AAgBA,SAAgB,UAAU,OAA+C,CAAC,GAAc;CACtF,MAAM,KAAK,UAAU,KAAK;CAC1B,OAAO;EACL,OAAO,KAAK,SAAS,aAAa,KAAK,SAAS,YAAY;EAC5D,WAAW,SAAS,QAAQ,OAAO,MAAM,IAAI;EAC7C,SAAS,KAAK,WAAW;EACzB,OAAO,KAAK,IAAI,KAAK,SAAS,IAAI,EAAE;EACpC,GAAG;CACL;AACF"}
@@ -1,8 +1,8 @@
1
- import { n as text } from "./text-H_2kzwaz.mjs";
1
+ import { n as text } from "./text-BKLHPMsQ.mjs";
2
2
  // src/widgets/divider.ts
3
3
  function divider(state = { style: "divider" }) {
4
4
  return text((ctx) => (state.char ?? "─").repeat(state.length ?? ctx.width), state);
5
5
  }
6
6
  export { divider as t };
7
7
 
8
- //# sourceMappingURL=divider-BPmnQ0Eu.mjs.map
8
+ //# sourceMappingURL=divider-CiNpoiuP.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"divider-BPmnQ0Eu.mjs","names":[],"sources":["../src/widgets/divider.ts"],"sourcesContent":["import type { Style } from \"../style/types.ts\"\nimport type { Text } from \"./text.ts\"\n\nimport { text } from \"./text.ts\"\n\nexport type DividerState = Style<false> & {\n char?: string\n length?: number\n}\n\nexport function divider(state: DividerState = { style: \"divider\" }): Text {\n return text((ctx) => (state.char ?? \"─\").repeat(state.length ?? ctx.width), state)\n}\n"],"mappings":";;AAUA,SAAgB,QAAQ,QAAsB,EAAE,OAAO,UAAU,GAAS;CACxE,OAAO,MAAM,SAAS,MAAM,QAAQ,IAAA,CAAK,OAAO,MAAM,UAAU,IAAI,KAAK,GAAG,KAAK;AACnF"}
1
+ {"version":3,"file":"divider-CiNpoiuP.mjs","names":[],"sources":["../src/widgets/divider.ts"],"sourcesContent":["import type { Style } from \"../style/types.ts\"\nimport type { Text } from \"./text.ts\"\n\nimport { text } from \"./text.ts\"\n\nexport type DividerState = Style<false> & {\n char?: string\n length?: number\n}\n\nexport function divider(state: DividerState = { style: \"divider\" }): Text {\n return text((ctx) => (state.char ?? \"─\").repeat(state.length ?? ctx.width), state)\n}\n"],"mappings":";;AAUA,SAAgB,QAAQ,QAAsB,EAAE,OAAO,UAAU,GAAS;CACxE,OAAO,MAAM,SAAS,MAAM,QAAQ,IAAA,CAAK,OAAO,MAAM,UAAU,IAAI,KAAK,GAAG,KAAK;AACnF"}
@@ -1,5 +1,5 @@
1
- import { n as PickerItem } from "./picker-DsB6--GN.mjs";
2
- import { o as CompletionSource } from "./autocomplete-wgceuhLr.mjs";
1
+ import { o as CompletionSource } from "./autocomplete-ZmzqQZNq.mjs";
2
+ import { n as PickerItem } from "./picker-H_ubc2nk.mjs";
3
3
  import { FindOptions } from "@zaly/shared/find";
4
4
  interface FilesSourceOptions extends FindOptions {
5
5
  /** Base directory relative queries resolve against. Default:
@@ -24,4 +24,4 @@ type File = PickerItem & {
24
24
  * supports all the same options and respects .gitignore by default. */
25
25
  declare function filesSource(opts?: FilesSourceOptions): CompletionSource<File>;
26
26
  export { filesSource as n, FilesSourceOptions as t };
27
- //# sourceMappingURL=files-CdiDnwA_.d.mts.map
27
+ //# sourceMappingURL=files-82Il_nUh.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"files-CdiDnwA_.d.mts","names":[],"sources":["../src/widgets/completions/files.ts"],"mappings":";;;UAQiB,2BAA2B;;;EAG1C;;;;EAIA,UAAU;;;;;;EAMV;EACA;;KAGG,OAAO;EAAe;;;;iBAIX,YAAY,OAAM,qBAA0B,iBAAiB"}
1
+ {"version":3,"file":"files-82Il_nUh.d.mts","names":[],"sources":["../src/widgets/completions/files.ts"],"mappings":";;;UAQiB,2BAA2B;;;EAG1C;;;;EAIA,UAAU;;;;;;EAMV;EACA;;KAGG,OAAO;EAAe;;;;iBAIX,YAAY,OAAM,qBAA0B,iBAAiB"}
@@ -1,5 +1,5 @@
1
- import { n as PickerItem } from "./picker-DsB6--GN.mjs";
2
- import { o as CompletionSource } from "./autocomplete-wgceuhLr.mjs";
1
+ import { o as CompletionSource } from "./autocomplete-ZmzqQZNq.mjs";
2
+ import { n as PickerItem } from "./picker-H_ubc2nk.mjs";
3
3
  /** A GitHub issue or pull request returned by `githubSource`. Shape
4
4
  * mirrors the `gh` CLI's `--json number,title,state,author,url`
5
5
  * output, with `type` added to distinguish issues from PRs. */
@@ -59,4 +59,4 @@ interface GithubSourceOptions {
59
59
  */
60
60
  declare function githubSource(opts?: GithubSourceOptions): CompletionSource<GithubItem>;
61
61
  export { githubSource as a, GithubState as i, GithubItem as n, GithubSourceOptions as r, GithubFetcher as t };
62
- //# sourceMappingURL=github-DC0XemKa.d.mts.map
62
+ //# sourceMappingURL=github-yVfk9BE4.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"github-DC0XemKa.d.mts","names":[],"sources":["../src/widgets/completions/github.ts"],"mappings":";;;;;KAWY,aAAa;EACvB;EACA;;;EAGA;EACA;EACA;EACA;IAAW;;;KAGD;KAEA,iBAAiB,aAAa,OAAO,gBAAgB,QAAQ;UAExD;;;EAGf;;;EAGA,UAAU;;;EAGV;;EAEA,QAAQ;;EAER;;;;EAIA,UAAU;;;;;;;;;;;;;;;;;;;;;;;iBAwBI,aAAa,OAAM,sBAA2B,iBAAiB"}
1
+ {"version":3,"file":"github-yVfk9BE4.d.mts","names":[],"sources":["../src/widgets/completions/github.ts"],"mappings":";;;;;KAWY,aAAa;EACvB;EACA;;;EAGA;EACA;EACA;EACA;IAAW;;;KAGD;KAEA,iBAAiB,aAAa,OAAO,gBAAgB,QAAQ;UAExD;;;EAGf;;;EAGA,UAAU;;;EAGV;;EAEA,QAAQ;;EAER;;;;EAIA,UAAU;;;;;;;;;;;;;;;;;;;;;;;iBAwBI,aAAa,OAAM,sBAA2B,iBAAiB"}
@@ -1,4 +1,4 @@
1
- import { n as resolveAnsiStyle } from "./style-Cw_EuszU.mjs";
1
+ import { n as resolveAnsiStyle } from "./style-DTwmlkfR.mjs";
2
2
  import { n as replaceMath } from "./math-VPuwFCid.mjs";
3
3
  import { stringWidth, stripAnsi } from "@zaly/shared/ansi";
4
4
  import { basename } from "pathe";
@@ -174,4 +174,4 @@ function labelOf(entry) {
174
174
  const MARKER_RE = /<img id=(\d+)>/g;
175
175
  export { createImageCallback };
176
176
 
177
- //# sourceMappingURL=image-M--Bw4bs.mjs.map
177
+ //# sourceMappingURL=image--PBiHfwV.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"image-M--Bw4bs.mjs","names":[],"sources":["../src/markdown/image.ts"],"sourcesContent":["import type { RenderCtx } from \"../core/ctx.ts\"\nimport type { Node } from \"../core/node.ts\"\nimport type { TerminalQueries } from \"../input/queries.ts\"\nimport type { Image } from \"../widgets/image.ts\"\nimport type { MathExpression } from \"./math.ts\"\nimport type { MdCallbacks, MdImageMeta } from \"./types.ts\"\n\nimport { stringWidth, stripAnsi } from \"@zaly/shared/ansi\"\nimport { basename } from \"pathe\"\nimport { resolveAnsiStyle } from \"../style/style.ts\"\nimport { MATH_PIXELS_PER_CELL, replaceMath } from \"./math.ts\"\n\n/** Minimal host shape the image callback needs. Any Node-like parent\n * with a per-src `Image` cache satisfies it — the `Markdown` widget is\n * the primary caller but the interface keeps this file decoupled from\n * widget internals (and breaks the widgets↔markdown import loop). */\nexport interface ImageHost {\n /** Per-src cache so re-renders reuse the same `Image` — same KGP\n * placement id, which the spec guarantees is a flicker-free\n * move/resize. */\n images: Map<string, Image>\n queries?: TerminalQueries\n /** Attach a freshly-created `Image` as a child so mount/unmount\n * propagate. */\n add: (child: Node) => unknown\n remove: (child: Node) => unknown\n}\n\n/** Per-occurrence image metadata collected during markdown rendering. */\ninterface ImageEntry {\n src: string\n alt: string\n math?: MathExpression\n}\n\n/**\n * Image callback + post-processor for the markdown pipeline.\n *\n * The markdown renderer is synchronous (callbacks return strings), but\n * image preparation is async (sharp metadata, KGP transmit, format\n * conversion). Math shares the same markers and placement path:\n *\n * 1. Before rendering, `prepare` collects math expressions and protects\n * their markers from ordinary Markdown escaping.\n * 2. During rendering, the `image` callback pushes `{ src, alt }` into\n * the entries array and emits a compact `<img id=N>` marker where\n * `N` is the entry's index. The marker is plain text so any\n * renderer (marked or `Bun.markdown.render`) emits it verbatim.\n * 3. After rendering, `resolve(ctx, rendered)` typesets math to cached\n * SVG files, renders every unique\n * src concurrently via cached `Image` nodes, then replaces each\n * marker with rendered rows. Inline images share a top-aligned row\n * band, with continuation rows padded to each image's terminal column.\n *\n * The caller passes in a persistent `cache: Map<src, Image>` owned by\n * the Markdown instance so re-renders reuse the same Image node (and\n * therefore the same KGP placement id, which the spec guarantees is a\n * flicker-free move/resize).\n */\nexport function createImageCallback(host: ImageHost): {\n cb: MdCallbacks[\"image\"]\n prepare: (source: string) => string\n resolve: (ctx: RenderCtx, rendered: string) => Promise<string>\n} {\n const entries: ImageEntry[] = []\n const cache = host.images\n\n return {\n cb(alt: string, meta: MdImageMeta): string {\n const id = entries.length\n entries.push({ alt, src: meta.src })\n return `<img id=${id}>`\n },\n\n prepare(source: string): string {\n return replaceMath(source, (math) => {\n const id = entries.length\n entries.push({ alt: math.source, math, src: \"\" })\n // A bare tag on its own line starts an HTML block and swallows following Markdown.\n return `\\`<img id=${id}>\\``\n })\n },\n\n async resolve(ctx: RenderCtx, rendered: string): Promise<string> {\n if (entries.length === 0) {\n for (const img of cache.values()) host.remove(img)\n cache.clear()\n return rendered\n }\n\n // `Image` is heavy (pulls image-meta, sharp lazy loaders, KGP\n // encoder) and 99% of markdown has no images. Load it only when\n // we have something to place.\n const { Image } = await import(\"../widgets/image.ts\")\n\n // One Image (and therefore one KGP `placementId`) per occurrence\n // so multiple references to the same `src` show up as distinct\n // placements on screen. Cache key is `${src}\\0${ordinal-within-src}`\n // — the ordinal makes each repeated reference stable across\n // re-renders (slot 0 is always slot 0, so the placementId stays\n // pinned and KGP gives a flicker-free move/resize). Image bytes\n // are de-duped at a different layer (`transmitOnce` in kitty.ts),\n // so multiple occurrences cost N placements but only one transmit.\n const { loadKittyGraphics } = await import(\"../image/kitty.ts\")\n const graphics =\n entries.some((entry) => entry.math) && host.queries\n ? await loadKittyGraphics(host.queries)\n : undefined\n const fg = resolveAnsiStyle(\"mdMath\", ctx.style.theme).fg\n const color = typeof fg === \"string\" && fg.startsWith(\"#\") ? fg : undefined\n const ordinals = new Map<string, number>()\n const rowsByEntry: string[][] = []\n const used = new Set<string>()\n await Promise.all(\n entries.map(async (entry, idx) => {\n if (entry.math && (!graphics?.supported || !color)) {\n rowsByEntry[idx] = [entry.alt]\n return\n }\n const identity = entry.math\n ? `math:${JSON.stringify([entry.math.tex, entry.math.display, color])}`\n : entry.src\n const ord = ordinals.get(identity) ?? 0\n ordinals.set(identity, ord + 1)\n const key = `${identity}\\u0000${ord}`\n used.add(key)\n let img = cache.get(key)\n if (img === undefined) {\n let src = entry.src\n let width: number | undefined\n if (entry.math) {\n try {\n const { mathImage } = await import(\"./math.ts\")\n src = await mathImage(entry.math, color!)\n const { fileDetect } = await import(\"@zaly/shared/detect\")\n const { imageInfo } = await import(\"@zaly/shared/image\")\n const detected = await fileDetect(src)\n if (detected?.type !== \"image\") throw new Error(\"Expected a math SVG\")\n const info = await imageInfo(detected)\n width = Math.max(1, Math.ceil(info.width / MATH_PIXELS_PER_CELL))\n } catch {\n rowsByEntry[idx] = [entry.alt]\n return\n }\n }\n img = new Image({ alt: entry.alt, src, width })\n host.add(img)\n cache.set(key, img)\n }\n try {\n rowsByEntry[idx] = await img.render(ctx)\n } catch (error) {\n if (!entry.math) throw error\n rowsByEntry[idx] = [entry.alt]\n }\n })\n )\n\n for (const [key] of cache) {\n if (!used.has(key)) {\n const img = cache.get(key)\n if (img) {\n host.remove(img)\n cache.delete(key)\n }\n }\n }\n\n // Walk the rendered output line by line:\n // - Marker alone on its line (whitespace + ANSI tolerated) →\n // replace the line with the full image rows.\n // - Marker mid-line → top-align image rows in a shared band.\n // Coordinates are terminal cells, not string offsets. As with tables,\n // subsequent wrapping to a narrower width can invalidate this layout.\n const out: string[] = []\n for (const line of rendered.split(\"\\n\")) {\n const matched = new Map<number, { entry: ImageEntry; rows: string[] }>()\n for (const m of line.matchAll(MARKER_RE)) {\n const idx = Number(m[1])\n if (entries[idx]) matched.set(idx, { entry: entries[idx], rows: rowsByEntry[idx] ?? [] })\n }\n\n if (matched.size === 0) {\n out.push(line)\n continue\n }\n\n if (matched.size === 1 && stripAnsi(line.replace(MARKER_RE, \"\")).trim() === \"\") {\n const img = matched.values().next().value\n if (img && img.rows.length > 0) {\n out.push(...img.rows)\n continue\n }\n }\n\n const band = [\"\"]\n let cursor = 0\n for (const marker of line.matchAll(MARKER_RE)) {\n band[0] += line.slice(cursor, marker.index)\n const image = matched.get(Number(marker[1]))\n if (!image) {\n band[0] += marker[0]\n } else {\n const column = stringWidth(band[0])\n const rows = image.rows.length ? image.rows : [labelOf(image.entry)]\n band[0] += rows[0]\n for (let row = 1; row < rows.length; row++) {\n band[row] ??= \"\"\n band[row] += \" \".repeat(Math.max(0, column - stringWidth(band[row]))) + rows[row]\n }\n }\n cursor = marker.index + marker[0].length\n }\n band[0] += line.slice(cursor)\n out.push(...band)\n }\n return out.join(\"\\n\")\n },\n }\n}\n\n/** Friendly inline placeholder for an image that can't render at this\n * position. Math keeps its original source. Otherwise prefers the markdown alt text;\n * falls back to the basename\n * of the source path (so `[clip.png]` instead of `[/tmp/long/path/clip.png]`),\n * and \"image\" for anything weirder. */\nfunction labelOf(entry: ImageEntry): string {\n if (entry.math) return entry.alt\n const label = entry.alt !== \"\" ? entry.alt : basename(entry.src) || \"image\"\n return `[${label}]`\n}\n\nconst MARKER_RE = /<img id=(\\d+)>/g\n"],"mappings":"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"}
1
+ {"version":3,"file":"image--PBiHfwV.mjs","names":[],"sources":["../src/markdown/image.ts"],"sourcesContent":["import type { RenderCtx } from \"../core/ctx.ts\"\nimport type { Node } from \"../core/node.ts\"\nimport type { TerminalQueries } from \"../input/queries.ts\"\nimport type { Image } from \"../widgets/image.ts\"\nimport type { MathExpression } from \"./math.ts\"\nimport type { MdCallbacks, MdImageMeta } from \"./types.ts\"\n\nimport { stringWidth, stripAnsi } from \"@zaly/shared/ansi\"\nimport { basename } from \"pathe\"\nimport { resolveAnsiStyle } from \"../style/style.ts\"\nimport { MATH_PIXELS_PER_CELL, replaceMath } from \"./math.ts\"\n\n/** Minimal host shape the image callback needs. Any Node-like parent\n * with a per-src `Image` cache satisfies it — the `Markdown` widget is\n * the primary caller but the interface keeps this file decoupled from\n * widget internals (and breaks the widgets↔markdown import loop). */\nexport interface ImageHost {\n /** Per-src cache so re-renders reuse the same `Image` — same KGP\n * placement id, which the spec guarantees is a flicker-free\n * move/resize. */\n images: Map<string, Image>\n queries?: TerminalQueries\n /** Attach a freshly-created `Image` as a child so mount/unmount\n * propagate. */\n add: (child: Node) => unknown\n remove: (child: Node) => unknown\n}\n\n/** Per-occurrence image metadata collected during markdown rendering. */\ninterface ImageEntry {\n src: string\n alt: string\n math?: MathExpression\n}\n\n/**\n * Image callback + post-processor for the markdown pipeline.\n *\n * The markdown renderer is synchronous (callbacks return strings), but\n * image preparation is async (sharp metadata, KGP transmit, format\n * conversion). Math shares the same markers and placement path:\n *\n * 1. Before rendering, `prepare` collects math expressions and protects\n * their markers from ordinary Markdown escaping.\n * 2. During rendering, the `image` callback pushes `{ src, alt }` into\n * the entries array and emits a compact `<img id=N>` marker where\n * `N` is the entry's index. The marker is plain text so any\n * renderer (marked or `Bun.markdown.render`) emits it verbatim.\n * 3. After rendering, `resolve(ctx, rendered)` typesets math to cached\n * SVG files, renders every unique\n * src concurrently via cached `Image` nodes, then replaces each\n * marker with rendered rows. Inline images share a top-aligned row\n * band, with continuation rows padded to each image's terminal column.\n *\n * The caller passes in a persistent `cache: Map<src, Image>` owned by\n * the Markdown instance so re-renders reuse the same Image node (and\n * therefore the same KGP placement id, which the spec guarantees is a\n * flicker-free move/resize).\n */\nexport function createImageCallback(host: ImageHost): {\n cb: MdCallbacks[\"image\"]\n prepare: (source: string) => string\n resolve: (ctx: RenderCtx, rendered: string) => Promise<string>\n} {\n const entries: ImageEntry[] = []\n const cache = host.images\n\n return {\n cb(alt: string, meta: MdImageMeta): string {\n const id = entries.length\n entries.push({ alt, src: meta.src })\n return `<img id=${id}>`\n },\n\n prepare(source: string): string {\n return replaceMath(source, (math) => {\n const id = entries.length\n entries.push({ alt: math.source, math, src: \"\" })\n // A bare tag on its own line starts an HTML block and swallows following Markdown.\n return `\\`<img id=${id}>\\``\n })\n },\n\n async resolve(ctx: RenderCtx, rendered: string): Promise<string> {\n if (entries.length === 0) {\n for (const img of cache.values()) host.remove(img)\n cache.clear()\n return rendered\n }\n\n // `Image` is heavy (pulls image-meta, sharp lazy loaders, KGP\n // encoder) and 99% of markdown has no images. Load it only when\n // we have something to place.\n const { Image } = await import(\"../widgets/image.ts\")\n\n // One Image (and therefore one KGP `placementId`) per occurrence\n // so multiple references to the same `src` show up as distinct\n // placements on screen. Cache key is `${src}\\0${ordinal-within-src}`\n // — the ordinal makes each repeated reference stable across\n // re-renders (slot 0 is always slot 0, so the placementId stays\n // pinned and KGP gives a flicker-free move/resize). Image bytes\n // are de-duped at a different layer (`transmitOnce` in kitty.ts),\n // so multiple occurrences cost N placements but only one transmit.\n const { loadKittyGraphics } = await import(\"../image/kitty.ts\")\n const graphics =\n entries.some((entry) => entry.math) && host.queries\n ? await loadKittyGraphics(host.queries)\n : undefined\n const fg = resolveAnsiStyle(\"mdMath\", ctx.style.theme).fg\n const color = typeof fg === \"string\" && fg.startsWith(\"#\") ? fg : undefined\n const ordinals = new Map<string, number>()\n const rowsByEntry: string[][] = []\n const used = new Set<string>()\n await Promise.all(\n entries.map(async (entry, idx) => {\n if (entry.math && (!graphics?.supported || !color)) {\n rowsByEntry[idx] = [entry.alt]\n return\n }\n const identity = entry.math\n ? `math:${JSON.stringify([entry.math.tex, entry.math.display, color])}`\n : entry.src\n const ord = ordinals.get(identity) ?? 0\n ordinals.set(identity, ord + 1)\n const key = `${identity}\\u0000${ord}`\n used.add(key)\n let img = cache.get(key)\n if (img === undefined) {\n let src = entry.src\n let width: number | undefined\n if (entry.math) {\n try {\n const { mathImage } = await import(\"./math.ts\")\n src = await mathImage(entry.math, color!)\n const { fileDetect } = await import(\"@zaly/shared/detect\")\n const { imageInfo } = await import(\"@zaly/shared/image\")\n const detected = await fileDetect(src)\n if (detected?.type !== \"image\") throw new Error(\"Expected a math SVG\")\n const info = await imageInfo(detected)\n width = Math.max(1, Math.ceil(info.width / MATH_PIXELS_PER_CELL))\n } catch {\n rowsByEntry[idx] = [entry.alt]\n return\n }\n }\n img = new Image({ alt: entry.alt, src, width })\n host.add(img)\n cache.set(key, img)\n }\n try {\n rowsByEntry[idx] = await img.render(ctx)\n } catch (error) {\n if (!entry.math) throw error\n rowsByEntry[idx] = [entry.alt]\n }\n })\n )\n\n for (const [key] of cache) {\n if (!used.has(key)) {\n const img = cache.get(key)\n if (img) {\n host.remove(img)\n cache.delete(key)\n }\n }\n }\n\n // Walk the rendered output line by line:\n // - Marker alone on its line (whitespace + ANSI tolerated) →\n // replace the line with the full image rows.\n // - Marker mid-line → top-align image rows in a shared band.\n // Coordinates are terminal cells, not string offsets. As with tables,\n // subsequent wrapping to a narrower width can invalidate this layout.\n const out: string[] = []\n for (const line of rendered.split(\"\\n\")) {\n const matched = new Map<number, { entry: ImageEntry; rows: string[] }>()\n for (const m of line.matchAll(MARKER_RE)) {\n const idx = Number(m[1])\n if (entries[idx]) matched.set(idx, { entry: entries[idx], rows: rowsByEntry[idx] ?? [] })\n }\n\n if (matched.size === 0) {\n out.push(line)\n continue\n }\n\n if (matched.size === 1 && stripAnsi(line.replace(MARKER_RE, \"\")).trim() === \"\") {\n const img = matched.values().next().value\n if (img && img.rows.length > 0) {\n out.push(...img.rows)\n continue\n }\n }\n\n const band = [\"\"]\n let cursor = 0\n for (const marker of line.matchAll(MARKER_RE)) {\n band[0] += line.slice(cursor, marker.index)\n const image = matched.get(Number(marker[1]))\n if (!image) {\n band[0] += marker[0]\n } else {\n const column = stringWidth(band[0])\n const rows = image.rows.length ? image.rows : [labelOf(image.entry)]\n band[0] += rows[0]\n for (let row = 1; row < rows.length; row++) {\n band[row] ??= \"\"\n band[row] += \" \".repeat(Math.max(0, column - stringWidth(band[row]))) + rows[row]\n }\n }\n cursor = marker.index + marker[0].length\n }\n band[0] += line.slice(cursor)\n out.push(...band)\n }\n return out.join(\"\\n\")\n },\n }\n}\n\n/** Friendly inline placeholder for an image that can't render at this\n * position. Math keeps its original source. Otherwise prefers the markdown alt text;\n * falls back to the basename\n * of the source path (so `[clip.png]` instead of `[/tmp/long/path/clip.png]`),\n * and \"image\" for anything weirder. */\nfunction labelOf(entry: ImageEntry): string {\n if (entry.math) return entry.alt\n const label = entry.alt !== \"\" ? entry.alt : basename(entry.src) || \"image\"\n return `[${label}]`\n}\n\nconst MARKER_RE = /<img id=(\\d+)>/g\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { I as RenderCtx, X as Layout, h as Node } from "./queries-Dmd3IjSd.mjs";
1
+ import { I as RenderCtx, X as Layout, h as Node } from "./queries-BqFMpQAf.mjs";
2
2
  interface ImageState {
3
3
  /**
4
4
  * Path to an image file. PNG passes through with zero decoding; other
@@ -32,4 +32,4 @@ declare class Image extends Node<ImageState> {
32
32
  declare function image(src: string, style?: Omit<ImageState, "src">): Image;
33
33
  declare function image(state: ImageState): Image;
34
34
  export { ImageState as n, image as r, Image as t };
35
- //# sourceMappingURL=image-DdRDv7q6.d.mts.map
35
+ //# sourceMappingURL=image-CO6YJj6F.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"image-DdRDv7q6.d.mts","names":[],"sources":["../src/widgets/image.ts"],"mappings":";UAWiB;;;;;;;EAOf;;EAEA;;;;;EAKA;;EAEA;;;;;EAKA;;cAGW,cAAc,KAAK;;EAI9B,YAAY,OAAO;MAOf;EAIK,UAAU;YAIH,QAAQ,KAAK,YAAY;;iBAuC3B,MAAM,aAAa,QAAQ,KAAK,qBAAqB;iBACrD,MAAM,OAAO,aAAa"}
1
+ {"version":3,"file":"image-CO6YJj6F.d.mts","names":[],"sources":["../src/widgets/image.ts"],"mappings":";UAWiB;;;;;;;EAOf;;EAEA;;;;;EAKA;;EAEA;;;;;EAKA;;cAGW,cAAc,KAAK;;EAI9B,YAAY,OAAO;MAOf;EAIK,UAAU;YAIH,QAAQ,KAAK,YAAY;;iBAuC3B,MAAM,aAAa,QAAQ,KAAK,qBAAqB;iBACrD,MAAM,OAAO,aAAa"}
package/dist/image.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as TerminalQueries, r as TerminalQuery } from "./queries-Dmd3IjSd.mjs";
1
+ import { n as TerminalQueries, r as TerminalQuery } from "./queries-BqFMpQAf.mjs";
2
2
  import { MaybePromise } from "@zaly/shared";
3
3
  import { ImageInfo } from "@zaly/shared/image";
4
4
  export type KittyPlacement = {
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
+ import { $ as StyleState, $t as keyMatches, At as unwrap, Bt as ActionFn, C as UI, Ct as memo, Dt as signal, E as Terminal, Et as provideContext, F as MountCtx, Gt as NodeActionMap, Ht as Actions, I as RenderCtx, It as Action, Jt as KeyPattern, Kt as KeyEvent, Lt as ActionCtx, Mt as useContext, Nt as withActiveNode, Ot as toAccessor, Pt as withOwner, Q as State, Qt as isKeyPattern, Rt as ActionDef, S as SurfaceType, St as isAccessor, T as OverlaySurface, Tt as peek, Ut as KeyBinding, Vt as ActionMap, Wt as NodeAction, X as Layout, Xt as SpecialKeyName, Y as FlexState, Yt as KeyPatternError, Z as LayoutState, Zt as canonical, _ as NodeVisitor, _t as createRoot, a as parseAttrs, at as Ref, b as Renderer, bt as effect, c as InputRouterEvents, ct as SignalStore, d as KeymapEntry, dt as createAsync, et as Accessor, f as RoutedKey, ft as createContext, g as isNode, gt as createRef, h as Node, ht as createProgressive, i as TerminalQueryOpts, it as Reactive, jt as useActiveOwner, kt as untrack, l as KeyHandler, lt as SuspenseBoundary, m as BaseEvents, mt as createNode, n as TerminalQueries, nt as Owner, o as parseTerminalColor, ot as Setter, p as RoutedPaste, pt as createIterable, qt as KeyModifier, r as TerminalQuery, rt as Progressive, s as InputRouter, st as Signal, t as TerminalPaletteSnapshot, tt as Context, u as KeyPatterns, ut as SuspenseContext, v as RenderEvents, vt as createStore, w as Stream, wt as onCleanup, x as RendererOptions, xt as inRenderContextOf, y as RenderStats, yt as createSuspenseBoundary, zt as ActionFilter } from "./queries-BqFMpQAf.mjs";
1
2
  import { C as ShikiThemeInput, S as ShikiTheme, T as ShikiWorkerRequest, _ as ShikiLanguage, a as BrightAnsiColorName, b as ShikiResult, c as HexColor, d as ThemeKey, f as Theme, g as ShikiJob, h as GeneratedShikiTheme, i as AnyStyle, l as RGB, m as ThemeValue, n as AnsiColorName, o as Color, p as ThemeStyle, r as AnsiStyle, s as ColorLightness, t as AnsiColor, u as Style, v as ShikiOpts, w as ShikiWorkerMessage, x as ShikiSelection, y as ShikiRequest } from "./types-ByMHEdCF.mjs";
2
- import { $ as StyleState, $t as keyMatches, At as unwrap, Bt as ActionFn, C as UI, Ct as memo, Dt as signal, E as Terminal, Et as provideContext, F as MountCtx, Gt as NodeActionMap, Ht as Actions, I as RenderCtx, It as Action, Jt as KeyPattern, Kt as KeyEvent, Lt as ActionCtx, Mt as useContext, Nt as withActiveNode, Ot as toAccessor, Pt as withOwner, Q as State, Qt as isKeyPattern, Rt as ActionDef, S as SurfaceType, St as isAccessor, T as OverlaySurface, Tt as peek, Ut as KeyBinding, Vt as ActionMap, Wt as NodeAction, X as Layout, Xt as SpecialKeyName, Y as FlexState, Yt as KeyPatternError, Z as LayoutState, Zt as canonical, _ as NodeVisitor, _t as createRoot, a as parseAttrs, at as Ref, b as Renderer, bt as effect, c as InputRouterEvents, ct as SignalStore, d as KeymapEntry, dt as createAsync, et as Accessor, f as RoutedKey, ft as createContext, g as isNode, gt as createRef, h as Node, ht as createProgressive, i as TerminalQueryOpts, it as Reactive, jt as useActiveOwner, kt as untrack, l as KeyHandler, lt as SuspenseBoundary, m as BaseEvents, mt as createNode, n as TerminalQueries, nt as Owner, o as parseTerminalColor, ot as Setter, p as RoutedPaste, pt as createIterable, qt as KeyModifier, r as TerminalQuery, rt as Progressive, s as InputRouter, st as Signal, t as TerminalPaletteSnapshot, tt as Context, u as KeyPatterns, ut as SuspenseContext, v as RenderEvents, vt as createStore, w as Stream, wt as onCleanup, x as RendererOptions, xt as inRenderContextOf, y as RenderStats, yt as createSuspenseBoundary, zt as ActionFilter } from "./queries-Dmd3IjSd.mjs";
3
3
  import { a as StyleBuilder, i as isMarkdown, n as inspect, r as inspectFormat, t as InspectOpts } from "./inspect-hSl3U2BD.mjs";
4
4
  import { ArgsOpts } from "@zaly/shared/args";
5
5
  export declare function defineAction<T extends ArgsOpts = ArgsOpts>(action: ActionDef<T>): ActionDef<T>;
package/dist/index.mjs CHANGED
@@ -1,22 +1,22 @@
1
1
  import { C as useActiveOwner, E as withOwner, S as unwrap, T as withActiveNode, _ as peek, a as createIterable, b as toAccessor, c as createRef, d as createSuspenseBoundary, f as effect, g as onCleanup, h as memo, i as createContext, l as createRoot, m as isAccessor, n as SuspenseContext, o as createNode, p as inRenderContextOf, r as createAsync, s as createProgressive, t as Owner, u as createStore, v as provideContext, w as useContext, x as untrack, y as signal } from "./reactive-DkP75TPk.mjs";
2
- import { n as inspectFormat, r as isMarkdown, t as inspect } from "./inspect-Cli1beu8.mjs";
3
- import { n as createRender, t as RenderContext } from "./render-Bp2N2i6J.mjs";
4
- import { n as shikiThemes, t as codeToAnsi } from "./shiki-BAf9PdIc.mjs";
5
- import { t as shikiTheme } from "./theme-B8hb1lPU.mjs";
2
+ import { n as createRender, t as RenderContext } from "./render-bm-x85jl.mjs";
6
3
  import { a as canonical, i as KeyPatternError, n as parseAttrs, o as isKeyPattern, r as parseTerminalColor, s as keyMatches, t as TerminalQueries } from "./queries-BIniKnLL.mjs";
4
+ import { n as inspectFormat, r as isMarkdown, t as inspect } from "./inspect-CxtcQch-.mjs";
5
+ import { n as shikiThemes, t as codeToAnsi } from "./shiki-CUJQssUJ.mjs";
6
+ import { t as shikiTheme } from "./theme-CG51x7aG.mjs";
7
7
  // src/input/index.ts
8
8
  function defineAction(action) {
9
9
  return action;
10
10
  }
11
11
  // src/core/index.ts
12
12
  async function createCtx(opts = {}) {
13
- const { createCtx: create } = await import("./ctx-CYEzzH-7.mjs");
13
+ const { createCtx: create } = await import("./ctx-CM0YMy26.mjs");
14
14
  return create(opts);
15
15
  }
16
16
  // src/renderer/index.ts
17
17
  /** Factory shorthand for `new Renderer(opts)`. */
18
18
  async function createRenderer(opts = {}) {
19
- const { Renderer } = await import("./renderer-OplFCBMq.mjs");
19
+ const { Renderer } = await import("./renderer-Cc8-s72G.mjs");
20
20
  return new Renderer(opts);
21
21
  }
22
22
  export { KeyPatternError, Owner, RenderContext, SuspenseContext, TerminalQueries, canonical, codeToAnsi, createAsync, createContext, createCtx, createIterable, createNode, createProgressive, createRef, createRender, createRenderer, createRoot, createStore, createSuspenseBoundary, defineAction, effect, inRenderContextOf, inspect, inspectFormat, isAccessor, isKeyPattern, isMarkdown, keyMatches, memo, onCleanup, parseAttrs, parseTerminalColor, peek, provideContext, shikiTheme, shikiThemes, signal, toAccessor, untrack, unwrap, useActiveOwner, useContext, withActiveNode, withOwner };
@@ -1,7 +1,7 @@
1
- import { S as unwrap, x as untrack } from "./reactive-DkP75TPk.mjs";
2
1
  import { t as Node } from "./node-CXNobGaP.mjs";
3
- import { o as formatText } from "./text-BEuhhxr7.mjs";
4
- import { t as clipboard } from "./clipboard-UEoX9sup.mjs";
2
+ import { S as unwrap, x as untrack } from "./reactive-DkP75TPk.mjs";
3
+ import { t as clipboard } from "./clipboard-wzJ1EVfy.mjs";
4
+ import { o as formatText } from "./text-BLQXIh6Z.mjs";
5
5
  import { hash, isPromiseLike } from "@zaly/shared";
6
6
  import { sliceAnsi, splitAnsi, stringWidth } from "@zaly/shared/ansi";
7
7
  import { basename } from "pathe";
@@ -498,4 +498,4 @@ function countLines(value) {
498
498
  }
499
499
  export { input as n, Input as t };
500
500
 
501
- //# sourceMappingURL=input-CtGmVrKa.mjs.map
501
+ //# sourceMappingURL=input-5ktO2fvO.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"input-CtGmVrKa.mjs","names":[],"sources":["../src/widgets/input.ts"],"sourcesContent":["import type { MaybePromise } from \"@zaly/shared\"\nimport type { DetectedFile } from \"@zaly/shared/detect\"\nimport type { RenderCtx } from \"../core/ctx.ts\"\nimport type { BaseEvents } from \"../core/node.ts\"\nimport type { Reactive } from \"../core/reactive.ts\"\nimport type { StyleState } from \"../core/state.ts\"\nimport type { NodeActionMap } from \"../input/actions.ts\"\nimport type { RoutedKey } from \"../input/router.ts\"\nimport type { Size } from \"../layout/size.ts\"\nimport type { StyleBuilder } from \"../style/builder.ts\"\n\nimport { hash, isPromiseLike } from \"@zaly/shared\"\nimport { sliceAnsi, splitAnsi, stringWidth } from \"@zaly/shared/ansi\"\nimport { fileDetect } from \"@zaly/shared/detect\"\nimport { basename } from \"pathe\"\nimport { Node } from \"../core/node.ts\"\nimport { untrack, unwrap } from \"../core/reactive.ts\"\nimport { clipboard } from \"../input/clipboard.ts\"\nimport { formatText } from \"../layout/text.ts\"\n\nexport interface InputState extends StyleState {\n /** Current text content. May include `\\n` for multi-line input. */\n value?: string\n /** Shown dim when `value` is empty. */\n placeholder?: Reactive<string | undefined>\n /** Cursor position as a char index in `value`. Clamped to `[0, value.length]`. */\n cursor?: number\n /** Render width. Defaults to `fill`. */\n width?: Size\n /** Submitted input history, newest last. */\n history?: readonly string[]\n /** Threshold for showing pasted content as an attachment rather than raw text */\n pasteMaxLines?: number\n pasteMaxChars?: number\n canAttach?: (file: InputAttachment) => boolean\n format?: (value: string, ctx: { style: StyleBuilder }) => MaybePromise<string>\n validate?: (value: string) => boolean\n}\n\nexport type InputValue = { value: string; attachments: InputAttachment[] }\n\nconst PASTE_MAX_LINES = 5\nconst PASTE_MAX_CHARS = 1000\nconst graphemes = new Intl.Segmenter(undefined, { granularity: \"grapheme\" })\n\n/** A clipboard or bracketed paste that exceeds the `pasteMax*` thresholds. **/\ntype Paste = { type: \"paste\"; text: string }\n\n/** A file or image attachment detected from the clipboard. */\nexport type InputAttachment = DetectedFile & { path: string }\n\nexport type InputEvents = BaseEvents & {\n /** Fired when the submitted history changes. */\n history: { history: string[]; added: string }\n /** Fired when plain Enter is pressed. Payload is the current value. */\n submit: { value: string; attachments: InputAttachment[] }\n /** Fired when the user pastes a non-text resource via the\n * `input.paste` action. Images and file references both land here,\n * discriminated by `attachment.kind`. One event fires per image\n * paste; one per file in a multi-file paste.\n *\n * Wrapped under `attachment` because `InputAttachment` carries its\n * own `type` field (MIME) that would shadow the envelope's `type`\n * discriminator if spread directly. */\n attach: { attachment: InputAttachment }\n}\n\ntype FormatEntry = { promise?: Promise<void>; result?: string; input: string }\n\n/**\n * Multi-line text input with auto-grow.\n *\n * Editing commands (`cursorLeft`, `deleteWordBack`, `submit`, …) live on\n * `this.actions` as zero-arg methods. The input router maps keymap\n * bindings like `\"input.cursorLeft\"` onto those methods — users bind\n * different keys by configuring the keymap, not by patching the class.\n * The methods are also callable directly (`input.actions.submit()`),\n * which is handy in tests or for higher-level macros.\n *\n * What this class *still* handles via its `key` event listener:\n * - **Printable character insertion.** An unbound keystroke whose\n * `text` is set and has no ctrl/alt/meta modifier becomes a literal\n * insert at the cursor. There's no action for every printable char\n * in the keymap; the raw-key fallback handles them.\n * - **Paste events.** Whole payload inserted at cursor; always.\n *\n * Everything else (navigation, editing, submit/newline) goes through\n * the router → actions pipeline. Unbound non-printable keys fall back\n * to `emit(\"key\", …)` and bubble to the parent chain.\n *\n * Rendering word-wraps each logical line to `ctx.width`, so long\n * messages naturally grow vertically. The UI surface resizes its\n * reserved footer when the Input's row count changes, so the stream\n * above shifts accordingly.\n */\nexport class Input extends Node<InputState, InputEvents> {\n /** Class-level scope tag used by the input router to bind keymaps. */\n static readonly type = \"input\"\n\n override readonly type = Input.type\n #focused = false\n #history: string[] = []\n #historyDraft = \"\"\n #historyIndex: number | undefined\n #preferredCol: number | undefined\n #staged: ((InputAttachment | Paste) & { id: number; marker: string })[] = []\n #formatCache: Map<string, FormatEntry> & { version?: number } = new Map<string, FormatEntry>()\n #formatGen = 0\n\n /**\n * Editing actions. Parameterless — they close over `this` — so both\n * the router (via keymap dispatch) and application code can invoke\n * them. Add to the keymap in `src/input/actions.ts` to give them\n * default key bindings.\n */\n override actions = {\n \"input.cursorDown\": (): void => {\n const v = this.state.value ?? \"\"\n const { col, line } = posToLineCol(v, this.#cursor())\n if (line >= countLines(v) - 1) {\n this.#historyNext()\n return\n }\n this.#preferredCol ??= col\n this.state.cursor = lineColToPos(v, line + 1, this.#preferredCol)\n },\n \"input.cursorLeft\": (): void => {\n const c = this.#cursor()\n this.#preferredCol = undefined\n this.state.cursor = c > 0 ? c - 1 : c\n },\n \"input.cursorLineEnd\": (): void => {\n const v = this.state.value ?? \"\"\n this.#preferredCol = undefined\n this.state.cursor = lineEndPos(v, this.#cursor())\n },\n \"input.cursorLineStart\": (): void => {\n const v = this.state.value ?? \"\"\n const { line } = posToLineCol(v, this.#cursor())\n this.#preferredCol = undefined\n this.state.cursor = lineColToPos(v, line, 0)\n },\n \"input.cursorRight\": (): void => {\n const v = this.state.value ?? \"\"\n const c = this.#cursor()\n this.#preferredCol = undefined\n this.state.cursor = c < v.length ? c + 1 : c\n },\n \"input.cursorUp\": (): void => {\n const v = this.state.value ?? \"\"\n const { col, line } = posToLineCol(v, this.#cursor())\n if (line === 0) {\n this.#historyPrev()\n return\n }\n this.#preferredCol ??= col\n this.state.cursor = lineColToPos(v, line - 1, this.#preferredCol)\n },\n \"input.deleteCharBack\": (): void => {\n const v = this.state.value ?? \"\"\n const c = this.#cursor()\n this.#preferredCol = undefined\n if (c === 0) return\n this.#historyEdit()\n const marker = this.#markerRange(c, \"back\")\n if (marker) return this.#deleteRange(marker.start, marker.end)\n this.state.set({ cursor: c - 1, value: v.slice(0, c - 1) + v.slice(c) })\n },\n \"input.deleteCharForward\": (): void => {\n const v = this.state.value ?? \"\"\n const c = this.#cursor()\n this.#preferredCol = undefined\n if (c >= v.length) return\n this.#historyEdit()\n const marker = this.#markerRange(c, \"forward\")\n if (marker) return this.#deleteRange(marker.start, marker.end)\n this.state.value = v.slice(0, c) + v.slice(c + 1)\n },\n \"input.deleteWordBack\": (): void => {\n const v = this.state.value ?? \"\"\n const c = this.#cursor()\n this.#preferredCol = undefined\n let i = c\n while (i > 0 && isWhitespace(v[i - 1])) i--\n while (i > 0 && !isWhitespace(v[i - 1])) i--\n if (i === c) return\n this.#historyEdit()\n this.state.set({ cursor: i, value: v.slice(0, i) + v.slice(c) })\n },\n \"input.insertNewline\": (): void => {\n // Smart indent: copy the leading whitespace of the current\n // logical line onto the new line so continuations stay aligned\n // with the bullet / quote / prefix the user typed.\n const v = this.state.value ?? \"\"\n const c = this.#cursor()\n this.#preferredCol = undefined\n let lineStart = c\n while (lineStart > 0 && v[lineStart - 1] !== \"\\n\") lineStart--\n let indent = \"\"\n for (let i = lineStart; i < c; i++) {\n const ch = v[i]\n if (ch === \" \" || ch === \"\\t\") indent += ch\n else break\n }\n const insert = `\\n${indent}`\n this.#historyEdit()\n this.state.set({\n cursor: c + insert.length,\n value: v.slice(0, c) + insert + v.slice(c),\n })\n },\n \"input.insertTab\": (): void => {\n // Two spaces rather than `\\t` — terminal tab stops are\n // unpredictable (often 8 cells) and play badly with our per-row\n // layout. Soft tabs are the safer default; users wanting real\n // tabs can override the action.\n const v = this.state.value ?? \"\"\n const c = this.#cursor()\n this.#preferredCol = undefined\n const tab = \" \"\n this.#historyEdit()\n this.state.set({ cursor: c + tab.length, value: v.slice(0, c) + tab + v.slice(c) })\n },\n // `ctrl-v` queries the OS clipboard (via xclip / wl-paste / pbpaste /\n // PowerShell depending on platform). The richest content wins:\n //\n // - **image bytes** → one `attach` event with a temp PNG path.\n // - **file references** → one `attach` event per file.\n // - **plain text** → inserted at the cursor like a regular paste.\n //\n // Fire-and-forget async — we await the probe and commit the\n // result in a microtask.\n \"input.paste\": (): void => {\n void (async (): Promise<void> => {\n const content = await clipboard.read()\n if (!content) return\n\n // Route through the regular paste handler\n if (content.kind === \"text\") {\n void this.emit(\"paste\", {\n paste: {\n stop: () => void 0,\n stopped: false,\n text: content.text,\n },\n })\n return\n }\n\n const paths: string[] = content.kind === \"image\" ? [content.path] : content.paths\n const files = await Promise.all(paths.map((path) => fileDetect({ path })))\n for (let i = 0; i < paths.length; i++) {\n const path = paths[i]\n const file = files[i]\n if (!file) continue\n this.attach({ ...file, path })\n }\n })()\n },\n \"input.submit\": (): void => {\n const value = this.state.value ?? \"\"\n if (this.state.validate?.(value) === false) return\n const ret = this.consume()\n this.#historyAdd(ret.value)\n void this.emit(\"submit\", ret)\n },\n } satisfies NodeActionMap\n\n constructor(initial: InputState = {}) {\n const value = initial.value ?? \"\"\n super({ cursor: value.length, value, ...initial })\n this.#history = [...(initial.history ?? [])]\n this.on(\"key\", ({ key }) => {\n this.#handleKey(key)\n })\n this.on(\"paste\", ({ paste }) => {\n this.paste(paste.text)\n paste.stop()\n })\n this.on(\"focus\", () => {\n this.#focused = true\n this.invalidate()\n })\n this.on(\"blur\", () => {\n this.#focused = false\n this.invalidate()\n })\n\n this.on(\"mount\", () => {\n this.ctx?.input.events.on(\"terminal-focus\", () => this.invalidate(), {\n signal: this.mountSignal,\n })\n })\n }\n\n get history(): readonly string[] {\n return this.#history\n }\n\n set history(v: readonly string[]) {\n this.#history = [...v]\n this.#historyIndex = undefined\n }\n\n attach(att: InputAttachment | Paste): void {\n if (att.type !== \"paste\" && this.state.canAttach?.(att) === false) {\n // Caller doesn't want this attachment (e.g. model doesn't support it) — fall\n // back to pasting the path as plain text\n return this.insert(att.path)\n }\n\n const id = this.#staged.length + 1\n let prefix = att.type[0].toUpperCase() + att.type.slice(1)\n let suffix = \"\"\n if (att.type === \"paste\") {\n prefix = \"Pasted text\"\n suffix = ` +${countLines(att.text)} lines`\n } else if (![\"image\", \"pdf\"].includes(att.type)) suffix = ` ${basename(att.path)}`\n const marker = `[${prefix} #${id}${suffix}]`\n this.#staged.push({ ...att, id, marker })\n this.insert(marker)\n\n if (att.type !== \"paste\") void this.emit(\"attach\", { attachment: att })\n }\n\n paste(text: string): void {\n if (\n countLines(text) > (this.state.pasteMaxLines ?? PASTE_MAX_LINES) ||\n stringWidth(text) > (this.state.pasteMaxChars ?? PASTE_MAX_CHARS)\n )\n return this.attach({ text, type: \"paste\" })\n this.insert(text)\n }\n\n insert(text: string): void {\n const v = this.state.value ?? \"\"\n const c = this.#cursor()\n this.#historyEdit()\n this.#preferredCol = undefined\n this.state.set({\n cursor: c + text.length,\n value: v.slice(0, c) + text + v.slice(c),\n })\n }\n\n replace(text: string): void {\n this.state.value = \"\"\n this.insert(text)\n }\n\n /** Consume the input's current value and attachments.\n * Pastes are replaced inline with their text; file/image\n * attachments are returned in the `attachments` array and removed from the value. */\n consume(): InputValue {\n let value = this.state.value ?? \"\"\n const atts: InputAttachment[] = []\n for (const att of this.#staged) {\n if (!value.includes(att.marker)) continue\n if (att.type === \"paste\") {\n value = value.replace(att.marker, att.text)\n } else atts.push(att)\n }\n this.#staged = []\n this.#preferredCol = undefined\n this.state.set({ cursor: 0, value: \"\" })\n return { attachments: atts, value }\n }\n\n #cursor(): number {\n const v = this.state.value ?? \"\"\n return Math.max(0, Math.min(v.length, this.state.cursor ?? 0))\n }\n\n #deleteRange(start: number, end: number): void {\n const v = this.state.value ?? \"\"\n this.#historyEdit()\n this.#preferredCol = undefined\n this.state.set({ cursor: start, value: v.slice(0, start) + v.slice(end) })\n }\n\n historyAdd(value: string): void {\n this.#historyAdd(value)\n }\n\n #historyAdd(value: string): void {\n if (value.trim() === \"\") return\n if (this.#history.at(-1) !== value) {\n this.#history.push(value)\n void this.emit(\"history\", { added: value, history: [...this.#history] })\n }\n this.#historyDraft = \"\"\n this.#historyIndex = undefined\n }\n\n #historyEdit(): void {\n this.#historyDraft = \"\"\n this.#historyIndex = undefined\n }\n\n #historyNext(): void {\n if (this.#historyIndex === undefined) return\n const next = this.#historyIndex + 1\n if (next >= this.#history.length) {\n this.#historySet(this.#historyDraft)\n this.#historyDraft = \"\"\n this.#historyIndex = undefined\n return\n }\n this.#historyIndex = next\n this.#historySet(this.#history[next])\n }\n\n #historyPrev(): void {\n if (this.#history.length === 0) return\n if (this.#historyIndex === undefined) {\n this.#historyDraft = this.state.value ?? \"\"\n this.#historyIndex = this.#history.length - 1\n } else if (this.#historyIndex > 0) this.#historyIndex--\n this.#historySet(this.#history[this.#historyIndex])\n }\n\n #historySet(value: string): void {\n this.#preferredCol = undefined\n this.state.set({ cursor: value.length, value })\n }\n\n #markerRange(pos: number, dir: \"back\" | \"forward\"): { start: number; end: number } | undefined {\n const value = this.state.value ?? \"\"\n for (const att of this.#staged) {\n const start = value.indexOf(att.marker)\n if (start === -1) continue\n const end = start + att.marker.length\n if (dir === \"back\" && pos > start && pos <= end) return { end, start }\n if (dir === \"forward\" && pos >= start && pos < end) return { end, start }\n }\n }\n\n // Fallback path for anything the router couldn't resolve to a named\n // action: printable characters go straight into the value. Any other\n // unbound key (f7, unclaimed ctrl-combos, etc.) bubbles untouched.\n #handleKey(ev: RoutedKey): void {\n if (ev.text === undefined || ev.ctrl || ev.alt || ev.meta) return\n this.insert(ev.text)\n ev.stop()\n }\n\n #format(value: string, ctx: RenderCtx): string {\n const format = this.state.format\n if (!format) return value\n\n if (ctx.version !== this.#formatCache.version) {\n this.#formatCache.clear()\n this.#formatCache.version = ctx.version\n }\n\n const key = hash(value)\n const cached = this.#formatCache.get(key)\n if (cached?.result !== undefined) return cached.result\n const streaming = this.#streamingFormat(value)\n if (cached?.promise) return streaming ?? value\n\n const ret = format(value, { style: ctx.style })\n if (!isPromiseLike(ret)) return ret\n\n const gen = ++this.#formatGen\n const entry: FormatEntry = { input: value }\n entry.promise = ret.then(\n (result) => {\n if (this.#formatCache.get(key) !== entry) return\n entry.result = result\n entry.promise = undefined\n if (this.#formatGen === gen && result !== value) untrack(() => this.invalidate())\n },\n () => {\n if (this.#formatCache.get(key) === entry) this.#formatCache.delete(key)\n }\n )\n this.#formatCache.set(key, entry)\n while (this.#formatCache.size > 100)\n this.#formatCache.delete(this.#formatCache.keys().next().value!)\n return streaming ?? value\n }\n\n /** Reuse a highlighted prefix while async formatting catches up. */\n #streamingFormat(value: string): string | undefined {\n let ret: [string, FormatEntry] | undefined\n for (const [key, cached] of this.#formatCache) {\n if (cached.result === undefined) continue\n if (value.startsWith(cached.input) && cached.input.length > (ret?.[1].input.length ?? 0))\n ret = [key, cached]\n }\n if (ret === undefined) return\n this.#formatCache.delete(ret[0])\n this.#formatCache.set(ret[0], ret[1]) // bump to end of LRU\n return ret[1].result + value.slice(ret[1].input.length)\n }\n\n protected async _render(ctx: RenderCtx): Promise<string[]> {\n const value = this.state.value ?? \"\"\n const placeholder = this.state.placeholder ?? \"\"\n const cursor = this.#cursor()\n const focused = this.#focused\n\n // Build the styled content string. Layout (word-wrap, split into\n // rows, pad to width) is delegated to `Text` below — wrap-ansi knows\n // how to close+reopen SGR across line breaks, so the inverse-cursor\n // span survives wrapping.\n let content: string\n if (value === \"\") {\n const ph = unwrap(placeholder)\n content = ph ? ` ${ctx.style.quiet(ph)}` : \"\"\n } else {\n content = value\n for (const att of this.#staged) {\n if (!content.includes(att.marker)) continue\n content = content.replace(att.marker, ctx.style.accent(att.marker))\n }\n content = this.#format(content, ctx)\n }\n\n // Fake cursor. `cursor` is an absolute index in the raw value, while\n // rendering is line-oriented: newlines consume string indices but not\n // cells in any rendered row. Project the cursor to line/column before\n // slicing the formatted line.\n if (focused && this.ctx?.input.terminalFocus) {\n const { col, line } = posToLineCol(value, cursor)\n const lines = splitAnsi(content)\n const target = Math.max(0, Math.min(line, lines.length - 1))\n const row = lines[target] ?? \"\"\n const head = sliceAnsi(row, 0, col)\n const at = sliceAnsi(row, col, col + 1) || \" \"\n const tail = sliceAnsi(row, col + 1)\n lines[target] = head + ctx.style.inverse(at) + tail\n content = lines.join(\"\\n\")\n }\n\n return formatText(content, {\n style: ctx.style.add(this.state),\n width: ctx.width,\n wrap: \"word\",\n })\n }\n}\n\n/**\n * Factory for `Input`. All fields optional; default is an empty,\n * focusable input that fills `ctx.width` and has no placeholder.\n *\n * ```ts\n * input({ placeholder: \"type a message…\" })\n * const i = input({ value: \"draft\" })\n * i.on(\"submit\", (text) => console.log(\"user typed\", text))\n * ```\n */\nexport function input(state: InputState = {}): Input {\n return new Input(state)\n}\n\n// ---------- helpers ----------\n\nfunction isWhitespace(ch: string | undefined): boolean {\n return ch !== undefined && (ch === \" \" || ch === \"\\t\" || ch === \"\\n\")\n}\n\nfunction posToLineCol(value: string, cursor: number): { line: number; col: number } {\n const prefix = value.slice(0, cursor)\n const lineStart = prefix.lastIndexOf(\"\\n\") + 1\n return {\n col: stringWidth(prefix.slice(lineStart)),\n line: prefix.split(\"\\n\").length - 1,\n }\n}\n\nfunction lineColToPos(value: string, line: number, col: number): number {\n const lines = value.split(\"\\n\")\n const clampLine = Math.max(0, Math.min(line, lines.length - 1))\n let abs = 0\n for (let i = 0; i < clampLine; i++) abs += lines[i].length + 1\n\n let cells = 0\n let offset = 0\n for (const { segment } of graphemes.segment(lines[clampLine])) {\n const width = stringWidth(segment)\n if (cells + width > col) break\n cells += width\n offset += segment.length\n }\n return abs + offset\n}\n\nfunction lineEndPos(value: string, cursor: number): number {\n let i = cursor\n while (i < value.length && value[i] !== \"\\n\") i++\n return i\n}\n\nfunction countLines(value: string): number {\n let n = 1\n for (const ch of value) if (ch === \"\\n\") n++\n return n\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"input-5ktO2fvO.mjs","names":[],"sources":["../src/widgets/input.ts"],"sourcesContent":["import type { MaybePromise } from \"@zaly/shared\"\nimport type { DetectedFile } from \"@zaly/shared/detect\"\nimport type { RenderCtx } from \"../core/ctx.ts\"\nimport type { BaseEvents } from \"../core/node.ts\"\nimport type { Reactive } from \"../core/reactive.ts\"\nimport type { StyleState } from \"../core/state.ts\"\nimport type { NodeActionMap } from \"../input/actions.ts\"\nimport type { RoutedKey } from \"../input/router.ts\"\nimport type { Size } from \"../layout/size.ts\"\nimport type { StyleBuilder } from \"../style/builder.ts\"\n\nimport { hash, isPromiseLike } from \"@zaly/shared\"\nimport { sliceAnsi, splitAnsi, stringWidth } from \"@zaly/shared/ansi\"\nimport { fileDetect } from \"@zaly/shared/detect\"\nimport { basename } from \"pathe\"\nimport { Node } from \"../core/node.ts\"\nimport { untrack, unwrap } from \"../core/reactive.ts\"\nimport { clipboard } from \"../input/clipboard.ts\"\nimport { formatText } from \"../layout/text.ts\"\n\nexport interface InputState extends StyleState {\n /** Current text content. May include `\\n` for multi-line input. */\n value?: string\n /** Shown dim when `value` is empty. */\n placeholder?: Reactive<string | undefined>\n /** Cursor position as a char index in `value`. Clamped to `[0, value.length]`. */\n cursor?: number\n /** Render width. Defaults to `fill`. */\n width?: Size\n /** Submitted input history, newest last. */\n history?: readonly string[]\n /** Threshold for showing pasted content as an attachment rather than raw text */\n pasteMaxLines?: number\n pasteMaxChars?: number\n canAttach?: (file: InputAttachment) => boolean\n format?: (value: string, ctx: { style: StyleBuilder }) => MaybePromise<string>\n validate?: (value: string) => boolean\n}\n\nexport type InputValue = { value: string; attachments: InputAttachment[] }\n\nconst PASTE_MAX_LINES = 5\nconst PASTE_MAX_CHARS = 1000\nconst graphemes = new Intl.Segmenter(undefined, { granularity: \"grapheme\" })\n\n/** A clipboard or bracketed paste that exceeds the `pasteMax*` thresholds. **/\ntype Paste = { type: \"paste\"; text: string }\n\n/** A file or image attachment detected from the clipboard. */\nexport type InputAttachment = DetectedFile & { path: string }\n\nexport type InputEvents = BaseEvents & {\n /** Fired when the submitted history changes. */\n history: { history: string[]; added: string }\n /** Fired when plain Enter is pressed. Payload is the current value. */\n submit: { value: string; attachments: InputAttachment[] }\n /** Fired when the user pastes a non-text resource via the\n * `input.paste` action. Images and file references both land here,\n * discriminated by `attachment.kind`. One event fires per image\n * paste; one per file in a multi-file paste.\n *\n * Wrapped under `attachment` because `InputAttachment` carries its\n * own `type` field (MIME) that would shadow the envelope's `type`\n * discriminator if spread directly. */\n attach: { attachment: InputAttachment }\n}\n\ntype FormatEntry = { promise?: Promise<void>; result?: string; input: string }\n\n/**\n * Multi-line text input with auto-grow.\n *\n * Editing commands (`cursorLeft`, `deleteWordBack`, `submit`, …) live on\n * `this.actions` as zero-arg methods. The input router maps keymap\n * bindings like `\"input.cursorLeft\"` onto those methods — users bind\n * different keys by configuring the keymap, not by patching the class.\n * The methods are also callable directly (`input.actions.submit()`),\n * which is handy in tests or for higher-level macros.\n *\n * What this class *still* handles via its `key` event listener:\n * - **Printable character insertion.** An unbound keystroke whose\n * `text` is set and has no ctrl/alt/meta modifier becomes a literal\n * insert at the cursor. There's no action for every printable char\n * in the keymap; the raw-key fallback handles them.\n * - **Paste events.** Whole payload inserted at cursor; always.\n *\n * Everything else (navigation, editing, submit/newline) goes through\n * the router → actions pipeline. Unbound non-printable keys fall back\n * to `emit(\"key\", …)` and bubble to the parent chain.\n *\n * Rendering word-wraps each logical line to `ctx.width`, so long\n * messages naturally grow vertically. The UI surface resizes its\n * reserved footer when the Input's row count changes, so the stream\n * above shifts accordingly.\n */\nexport class Input extends Node<InputState, InputEvents> {\n /** Class-level scope tag used by the input router to bind keymaps. */\n static readonly type = \"input\"\n\n override readonly type = Input.type\n #focused = false\n #history: string[] = []\n #historyDraft = \"\"\n #historyIndex: number | undefined\n #preferredCol: number | undefined\n #staged: ((InputAttachment | Paste) & { id: number; marker: string })[] = []\n #formatCache: Map<string, FormatEntry> & { version?: number } = new Map<string, FormatEntry>()\n #formatGen = 0\n\n /**\n * Editing actions. Parameterless — they close over `this` — so both\n * the router (via keymap dispatch) and application code can invoke\n * them. Add to the keymap in `src/input/actions.ts` to give them\n * default key bindings.\n */\n override actions = {\n \"input.cursorDown\": (): void => {\n const v = this.state.value ?? \"\"\n const { col, line } = posToLineCol(v, this.#cursor())\n if (line >= countLines(v) - 1) {\n this.#historyNext()\n return\n }\n this.#preferredCol ??= col\n this.state.cursor = lineColToPos(v, line + 1, this.#preferredCol)\n },\n \"input.cursorLeft\": (): void => {\n const c = this.#cursor()\n this.#preferredCol = undefined\n this.state.cursor = c > 0 ? c - 1 : c\n },\n \"input.cursorLineEnd\": (): void => {\n const v = this.state.value ?? \"\"\n this.#preferredCol = undefined\n this.state.cursor = lineEndPos(v, this.#cursor())\n },\n \"input.cursorLineStart\": (): void => {\n const v = this.state.value ?? \"\"\n const { line } = posToLineCol(v, this.#cursor())\n this.#preferredCol = undefined\n this.state.cursor = lineColToPos(v, line, 0)\n },\n \"input.cursorRight\": (): void => {\n const v = this.state.value ?? \"\"\n const c = this.#cursor()\n this.#preferredCol = undefined\n this.state.cursor = c < v.length ? c + 1 : c\n },\n \"input.cursorUp\": (): void => {\n const v = this.state.value ?? \"\"\n const { col, line } = posToLineCol(v, this.#cursor())\n if (line === 0) {\n this.#historyPrev()\n return\n }\n this.#preferredCol ??= col\n this.state.cursor = lineColToPos(v, line - 1, this.#preferredCol)\n },\n \"input.deleteCharBack\": (): void => {\n const v = this.state.value ?? \"\"\n const c = this.#cursor()\n this.#preferredCol = undefined\n if (c === 0) return\n this.#historyEdit()\n const marker = this.#markerRange(c, \"back\")\n if (marker) return this.#deleteRange(marker.start, marker.end)\n this.state.set({ cursor: c - 1, value: v.slice(0, c - 1) + v.slice(c) })\n },\n \"input.deleteCharForward\": (): void => {\n const v = this.state.value ?? \"\"\n const c = this.#cursor()\n this.#preferredCol = undefined\n if (c >= v.length) return\n this.#historyEdit()\n const marker = this.#markerRange(c, \"forward\")\n if (marker) return this.#deleteRange(marker.start, marker.end)\n this.state.value = v.slice(0, c) + v.slice(c + 1)\n },\n \"input.deleteWordBack\": (): void => {\n const v = this.state.value ?? \"\"\n const c = this.#cursor()\n this.#preferredCol = undefined\n let i = c\n while (i > 0 && isWhitespace(v[i - 1])) i--\n while (i > 0 && !isWhitespace(v[i - 1])) i--\n if (i === c) return\n this.#historyEdit()\n this.state.set({ cursor: i, value: v.slice(0, i) + v.slice(c) })\n },\n \"input.insertNewline\": (): void => {\n // Smart indent: copy the leading whitespace of the current\n // logical line onto the new line so continuations stay aligned\n // with the bullet / quote / prefix the user typed.\n const v = this.state.value ?? \"\"\n const c = this.#cursor()\n this.#preferredCol = undefined\n let lineStart = c\n while (lineStart > 0 && v[lineStart - 1] !== \"\\n\") lineStart--\n let indent = \"\"\n for (let i = lineStart; i < c; i++) {\n const ch = v[i]\n if (ch === \" \" || ch === \"\\t\") indent += ch\n else break\n }\n const insert = `\\n${indent}`\n this.#historyEdit()\n this.state.set({\n cursor: c + insert.length,\n value: v.slice(0, c) + insert + v.slice(c),\n })\n },\n \"input.insertTab\": (): void => {\n // Two spaces rather than `\\t` — terminal tab stops are\n // unpredictable (often 8 cells) and play badly with our per-row\n // layout. Soft tabs are the safer default; users wanting real\n // tabs can override the action.\n const v = this.state.value ?? \"\"\n const c = this.#cursor()\n this.#preferredCol = undefined\n const tab = \" \"\n this.#historyEdit()\n this.state.set({ cursor: c + tab.length, value: v.slice(0, c) + tab + v.slice(c) })\n },\n // `ctrl-v` queries the OS clipboard (via xclip / wl-paste / pbpaste /\n // PowerShell depending on platform). The richest content wins:\n //\n // - **image bytes** → one `attach` event with a temp PNG path.\n // - **file references** → one `attach` event per file.\n // - **plain text** → inserted at the cursor like a regular paste.\n //\n // Fire-and-forget async — we await the probe and commit the\n // result in a microtask.\n \"input.paste\": (): void => {\n void (async (): Promise<void> => {\n const content = await clipboard.read()\n if (!content) return\n\n // Route through the regular paste handler\n if (content.kind === \"text\") {\n void this.emit(\"paste\", {\n paste: {\n stop: () => void 0,\n stopped: false,\n text: content.text,\n },\n })\n return\n }\n\n const paths: string[] = content.kind === \"image\" ? [content.path] : content.paths\n const files = await Promise.all(paths.map((path) => fileDetect({ path })))\n for (let i = 0; i < paths.length; i++) {\n const path = paths[i]\n const file = files[i]\n if (!file) continue\n this.attach({ ...file, path })\n }\n })()\n },\n \"input.submit\": (): void => {\n const value = this.state.value ?? \"\"\n if (this.state.validate?.(value) === false) return\n const ret = this.consume()\n this.#historyAdd(ret.value)\n void this.emit(\"submit\", ret)\n },\n } satisfies NodeActionMap\n\n constructor(initial: InputState = {}) {\n const value = initial.value ?? \"\"\n super({ cursor: value.length, value, ...initial })\n this.#history = [...(initial.history ?? [])]\n this.on(\"key\", ({ key }) => {\n this.#handleKey(key)\n })\n this.on(\"paste\", ({ paste }) => {\n this.paste(paste.text)\n paste.stop()\n })\n this.on(\"focus\", () => {\n this.#focused = true\n this.invalidate()\n })\n this.on(\"blur\", () => {\n this.#focused = false\n this.invalidate()\n })\n\n this.on(\"mount\", () => {\n this.ctx?.input.events.on(\"terminal-focus\", () => this.invalidate(), {\n signal: this.mountSignal,\n })\n })\n }\n\n get history(): readonly string[] {\n return this.#history\n }\n\n set history(v: readonly string[]) {\n this.#history = [...v]\n this.#historyIndex = undefined\n }\n\n attach(att: InputAttachment | Paste): void {\n if (att.type !== \"paste\" && this.state.canAttach?.(att) === false) {\n // Caller doesn't want this attachment (e.g. model doesn't support it) — fall\n // back to pasting the path as plain text\n return this.insert(att.path)\n }\n\n const id = this.#staged.length + 1\n let prefix = att.type[0].toUpperCase() + att.type.slice(1)\n let suffix = \"\"\n if (att.type === \"paste\") {\n prefix = \"Pasted text\"\n suffix = ` +${countLines(att.text)} lines`\n } else if (![\"image\", \"pdf\"].includes(att.type)) suffix = ` ${basename(att.path)}`\n const marker = `[${prefix} #${id}${suffix}]`\n this.#staged.push({ ...att, id, marker })\n this.insert(marker)\n\n if (att.type !== \"paste\") void this.emit(\"attach\", { attachment: att })\n }\n\n paste(text: string): void {\n if (\n countLines(text) > (this.state.pasteMaxLines ?? PASTE_MAX_LINES) ||\n stringWidth(text) > (this.state.pasteMaxChars ?? PASTE_MAX_CHARS)\n )\n return this.attach({ text, type: \"paste\" })\n this.insert(text)\n }\n\n insert(text: string): void {\n const v = this.state.value ?? \"\"\n const c = this.#cursor()\n this.#historyEdit()\n this.#preferredCol = undefined\n this.state.set({\n cursor: c + text.length,\n value: v.slice(0, c) + text + v.slice(c),\n })\n }\n\n replace(text: string): void {\n this.state.value = \"\"\n this.insert(text)\n }\n\n /** Consume the input's current value and attachments.\n * Pastes are replaced inline with their text; file/image\n * attachments are returned in the `attachments` array and removed from the value. */\n consume(): InputValue {\n let value = this.state.value ?? \"\"\n const atts: InputAttachment[] = []\n for (const att of this.#staged) {\n if (!value.includes(att.marker)) continue\n if (att.type === \"paste\") {\n value = value.replace(att.marker, att.text)\n } else atts.push(att)\n }\n this.#staged = []\n this.#preferredCol = undefined\n this.state.set({ cursor: 0, value: \"\" })\n return { attachments: atts, value }\n }\n\n #cursor(): number {\n const v = this.state.value ?? \"\"\n return Math.max(0, Math.min(v.length, this.state.cursor ?? 0))\n }\n\n #deleteRange(start: number, end: number): void {\n const v = this.state.value ?? \"\"\n this.#historyEdit()\n this.#preferredCol = undefined\n this.state.set({ cursor: start, value: v.slice(0, start) + v.slice(end) })\n }\n\n historyAdd(value: string): void {\n this.#historyAdd(value)\n }\n\n #historyAdd(value: string): void {\n if (value.trim() === \"\") return\n if (this.#history.at(-1) !== value) {\n this.#history.push(value)\n void this.emit(\"history\", { added: value, history: [...this.#history] })\n }\n this.#historyDraft = \"\"\n this.#historyIndex = undefined\n }\n\n #historyEdit(): void {\n this.#historyDraft = \"\"\n this.#historyIndex = undefined\n }\n\n #historyNext(): void {\n if (this.#historyIndex === undefined) return\n const next = this.#historyIndex + 1\n if (next >= this.#history.length) {\n this.#historySet(this.#historyDraft)\n this.#historyDraft = \"\"\n this.#historyIndex = undefined\n return\n }\n this.#historyIndex = next\n this.#historySet(this.#history[next])\n }\n\n #historyPrev(): void {\n if (this.#history.length === 0) return\n if (this.#historyIndex === undefined) {\n this.#historyDraft = this.state.value ?? \"\"\n this.#historyIndex = this.#history.length - 1\n } else if (this.#historyIndex > 0) this.#historyIndex--\n this.#historySet(this.#history[this.#historyIndex])\n }\n\n #historySet(value: string): void {\n this.#preferredCol = undefined\n this.state.set({ cursor: value.length, value })\n }\n\n #markerRange(pos: number, dir: \"back\" | \"forward\"): { start: number; end: number } | undefined {\n const value = this.state.value ?? \"\"\n for (const att of this.#staged) {\n const start = value.indexOf(att.marker)\n if (start === -1) continue\n const end = start + att.marker.length\n if (dir === \"back\" && pos > start && pos <= end) return { end, start }\n if (dir === \"forward\" && pos >= start && pos < end) return { end, start }\n }\n }\n\n // Fallback path for anything the router couldn't resolve to a named\n // action: printable characters go straight into the value. Any other\n // unbound key (f7, unclaimed ctrl-combos, etc.) bubbles untouched.\n #handleKey(ev: RoutedKey): void {\n if (ev.text === undefined || ev.ctrl || ev.alt || ev.meta) return\n this.insert(ev.text)\n ev.stop()\n }\n\n #format(value: string, ctx: RenderCtx): string {\n const format = this.state.format\n if (!format) return value\n\n if (ctx.version !== this.#formatCache.version) {\n this.#formatCache.clear()\n this.#formatCache.version = ctx.version\n }\n\n const key = hash(value)\n const cached = this.#formatCache.get(key)\n if (cached?.result !== undefined) return cached.result\n const streaming = this.#streamingFormat(value)\n if (cached?.promise) return streaming ?? value\n\n const ret = format(value, { style: ctx.style })\n if (!isPromiseLike(ret)) return ret\n\n const gen = ++this.#formatGen\n const entry: FormatEntry = { input: value }\n entry.promise = ret.then(\n (result) => {\n if (this.#formatCache.get(key) !== entry) return\n entry.result = result\n entry.promise = undefined\n if (this.#formatGen === gen && result !== value) untrack(() => this.invalidate())\n },\n () => {\n if (this.#formatCache.get(key) === entry) this.#formatCache.delete(key)\n }\n )\n this.#formatCache.set(key, entry)\n while (this.#formatCache.size > 100)\n this.#formatCache.delete(this.#formatCache.keys().next().value!)\n return streaming ?? value\n }\n\n /** Reuse a highlighted prefix while async formatting catches up. */\n #streamingFormat(value: string): string | undefined {\n let ret: [string, FormatEntry] | undefined\n for (const [key, cached] of this.#formatCache) {\n if (cached.result === undefined) continue\n if (value.startsWith(cached.input) && cached.input.length > (ret?.[1].input.length ?? 0))\n ret = [key, cached]\n }\n if (ret === undefined) return\n this.#formatCache.delete(ret[0])\n this.#formatCache.set(ret[0], ret[1]) // bump to end of LRU\n return ret[1].result + value.slice(ret[1].input.length)\n }\n\n protected async _render(ctx: RenderCtx): Promise<string[]> {\n const value = this.state.value ?? \"\"\n const placeholder = this.state.placeholder ?? \"\"\n const cursor = this.#cursor()\n const focused = this.#focused\n\n // Build the styled content string. Layout (word-wrap, split into\n // rows, pad to width) is delegated to `Text` below — wrap-ansi knows\n // how to close+reopen SGR across line breaks, so the inverse-cursor\n // span survives wrapping.\n let content: string\n if (value === \"\") {\n const ph = unwrap(placeholder)\n content = ph ? ` ${ctx.style.quiet(ph)}` : \"\"\n } else {\n content = value\n for (const att of this.#staged) {\n if (!content.includes(att.marker)) continue\n content = content.replace(att.marker, ctx.style.accent(att.marker))\n }\n content = this.#format(content, ctx)\n }\n\n // Fake cursor. `cursor` is an absolute index in the raw value, while\n // rendering is line-oriented: newlines consume string indices but not\n // cells in any rendered row. Project the cursor to line/column before\n // slicing the formatted line.\n if (focused && this.ctx?.input.terminalFocus) {\n const { col, line } = posToLineCol(value, cursor)\n const lines = splitAnsi(content)\n const target = Math.max(0, Math.min(line, lines.length - 1))\n const row = lines[target] ?? \"\"\n const head = sliceAnsi(row, 0, col)\n const at = sliceAnsi(row, col, col + 1) || \" \"\n const tail = sliceAnsi(row, col + 1)\n lines[target] = head + ctx.style.inverse(at) + tail\n content = lines.join(\"\\n\")\n }\n\n return formatText(content, {\n style: ctx.style.add(this.state),\n width: ctx.width,\n wrap: \"word\",\n })\n }\n}\n\n/**\n * Factory for `Input`. All fields optional; default is an empty,\n * focusable input that fills `ctx.width` and has no placeholder.\n *\n * ```ts\n * input({ placeholder: \"type a message…\" })\n * const i = input({ value: \"draft\" })\n * i.on(\"submit\", (text) => console.log(\"user typed\", text))\n * ```\n */\nexport function input(state: InputState = {}): Input {\n return new Input(state)\n}\n\n// ---------- helpers ----------\n\nfunction isWhitespace(ch: string | undefined): boolean {\n return ch !== undefined && (ch === \" \" || ch === \"\\t\" || ch === \"\\n\")\n}\n\nfunction posToLineCol(value: string, cursor: number): { line: number; col: number } {\n const prefix = value.slice(0, cursor)\n const lineStart = prefix.lastIndexOf(\"\\n\") + 1\n return {\n col: stringWidth(prefix.slice(lineStart)),\n line: prefix.split(\"\\n\").length - 1,\n }\n}\n\nfunction lineColToPos(value: string, line: number, col: number): number {\n const lines = value.split(\"\\n\")\n const clampLine = Math.max(0, Math.min(line, lines.length - 1))\n let abs = 0\n for (let i = 0; i < clampLine; i++) abs += lines[i].length + 1\n\n let cells = 0\n let offset = 0\n for (const { segment } of graphemes.segment(lines[clampLine])) {\n const width = stringWidth(segment)\n if (cells + width > col) break\n cells += width\n offset += segment.length\n }\n return abs + offset\n}\n\nfunction lineEndPos(value: string, cursor: number): number {\n let i = cursor\n while (i < value.length && value[i] !== \"\\n\") i++\n return i\n}\n\nfunction countLines(value: string): number {\n let n = 1\n for (const ch of value) if (ch === \"\\n\") n++\n return n\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { $ as StyleState, Ft as Size, I as RenderCtx, h as Node, it as Reactive, m as BaseEvents } from "./queries-Dmd3IjSd.mjs";
1
+ import { $ as StyleState, Ft as Size, I as RenderCtx, h as Node, it as Reactive, m as BaseEvents } from "./queries-BqFMpQAf.mjs";
2
2
  import { a as StyleBuilder } from "./inspect-hSl3U2BD.mjs";
3
3
  import { MaybePromise } from "@zaly/shared";
4
4
  import { DetectedFile } from "@zaly/shared/detect";
@@ -136,4 +136,4 @@ declare class Input extends Node<InputState, InputEvents> {
136
136
  */
137
137
  declare function input(state?: InputState): Input;
138
138
  export { InputValue as a, InputState as i, InputAttachment as n, input as o, InputEvents as r, Input as t };
139
- //# sourceMappingURL=input-C3kfYoEJ.d.mts.map
139
+ //# sourceMappingURL=input-C8MEpAg6.d.mts.map