@zaly/tui 0.0.4 → 0.0.6

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 (156) hide show
  1. package/dist/actions-CnVzKVID.d.mts +41 -0
  2. package/dist/actions-CnVzKVID.d.mts.map +1 -0
  3. package/dist/actions-DEIKF2ts.mjs +48 -0
  4. package/dist/actions-DEIKF2ts.mjs.map +1 -0
  5. package/dist/{box-DyaByEiG.mjs → box-DepqUtuo.mjs} +2 -2
  6. package/dist/{box-DyaByEiG.mjs.map → box-DepqUtuo.mjs.map} +1 -1
  7. package/dist/{builder-D49ECBEf.mjs → builder-DTz5mppV.mjs} +2 -2
  8. package/dist/{builder-D49ECBEf.mjs.map → builder-DTz5mppV.mjs.map} +1 -1
  9. package/dist/{callbacks-CNjGNxVw.mjs → callbacks-DcMBGQEO.mjs} +2 -2
  10. package/dist/{callbacks-CNjGNxVw.mjs.map → callbacks-DcMBGQEO.mjs.map} +1 -1
  11. package/dist/{client-D6C_uczF.mjs → client-BBdtbLp4.mjs} +5 -10
  12. package/dist/client-BBdtbLp4.mjs.map +1 -0
  13. package/dist/client-cIckDgAj.mjs +2 -0
  14. package/dist/clipboard-l_TWxxTf.mjs +404 -0
  15. package/dist/clipboard-l_TWxxTf.mjs.map +1 -0
  16. package/dist/clipboard.mjs +1 -403
  17. package/dist/ctx-Bt4VlRPg.mjs +2 -0
  18. package/dist/ctx-DjJ1zSbs.mjs +43 -0
  19. package/dist/ctx-DjJ1zSbs.mjs.map +1 -0
  20. package/dist/divider-cclEAdPx.mjs +9 -0
  21. package/dist/divider-cclEAdPx.mjs.map +1 -0
  22. package/dist/files-Aw3iWpXD.d.mts +29 -0
  23. package/dist/files-Aw3iWpXD.d.mts.map +1 -0
  24. package/dist/files-BvThQpOf.mjs +42 -0
  25. package/dist/files-BvThQpOf.mjs.map +1 -0
  26. package/dist/github-Cy2hfWJ7.mjs +137 -0
  27. package/dist/github-Cy2hfWJ7.mjs.map +1 -0
  28. package/dist/github-DS00VoOj.d.mts +64 -0
  29. package/dist/github-DS00VoOj.d.mts.map +1 -0
  30. package/dist/image-BTxDPPiT.d.mts +37 -0
  31. package/dist/image-BTxDPPiT.d.mts.map +1 -0
  32. package/dist/index.mjs +6 -202
  33. package/dist/index.mjs.map +1 -1
  34. package/dist/input-CRBuTrsL.mjs +497 -0
  35. package/dist/input-CRBuTrsL.mjs.map +1 -0
  36. package/dist/{inspect-D1d9t-ni.mjs → inspect-DE5goDzB.mjs} +2 -2
  37. package/dist/{inspect-D1d9t-ni.mjs.map → inspect-DE5goDzB.mjs.map} +1 -1
  38. package/dist/log-Djvrxqk0.mjs +130 -0
  39. package/dist/log-Djvrxqk0.mjs.map +1 -0
  40. package/dist/logger-DDrt03So.mjs +71 -0
  41. package/dist/logger-DDrt03So.mjs.map +1 -0
  42. package/dist/markdown-CTlPe5ks.mjs +124 -0
  43. package/dist/markdown-CTlPe5ks.mjs.map +1 -0
  44. package/dist/markdown.mjs +1 -1
  45. package/dist/overlay-9xNgsuuy.mjs +18 -0
  46. package/dist/overlay-9xNgsuuy.mjs.map +1 -0
  47. package/dist/{picker-ndVVt6qF.mjs → picker-cH-rc7-X.mjs} +5 -5
  48. package/dist/{picker-ndVVt6qF.mjs.map → picker-cH-rc7-X.mjs.map} +1 -1
  49. package/dist/queries-B_lOwEiE.mjs +200 -0
  50. package/dist/queries-B_lOwEiE.mjs.map +1 -0
  51. package/dist/{registry-U654j61Y.mjs → registry-w_w7gZr_.mjs} +3 -5
  52. package/dist/registry-w_w7gZr_.mjs.map +1 -0
  53. package/dist/render-F8pVXN7h.mjs +33 -0
  54. package/dist/render-F8pVXN7h.mjs.map +1 -0
  55. package/dist/renderer-Dgj_qEOA.mjs +2 -0
  56. package/dist/{renderer-CdLCkJB-.mjs → renderer-qSMVeNSV.mjs} +3 -5
  57. package/dist/{renderer-CdLCkJB-.mjs.map → renderer-qSMVeNSV.mjs.map} +1 -1
  58. package/dist/{renderer-CxL4xbkl.mjs → renderer-uMeDzF9B.mjs} +8 -7
  59. package/dist/{renderer-CxL4xbkl.mjs.map → renderer-uMeDzF9B.mjs.map} +1 -1
  60. package/dist/select-m7NE6Rwa.mjs +195 -0
  61. package/dist/select-m7NE6Rwa.mjs.map +1 -0
  62. package/dist/services/logger.mjs +1 -70
  63. package/dist/services/notifier.mjs +2 -2
  64. package/dist/services/picker.d.mts +1 -1
  65. package/dist/services/picker.mjs +6 -6
  66. package/dist/services/prompt.d.mts +1 -1
  67. package/dist/services/prompt.mjs +5 -5
  68. package/dist/shiki/server.d.mts +1 -0
  69. package/dist/shiki/server.mjs +129 -0
  70. package/dist/shiki/server.mjs.map +1 -0
  71. package/dist/{shiki-BrjVUkeh.mjs → shiki-B5NwIiot.mjs} +5 -10
  72. package/dist/shiki-B5NwIiot.mjs.map +1 -0
  73. package/dist/shiki-BK-v9CtF.mjs +12 -0
  74. package/dist/shiki-BK-v9CtF.mjs.map +1 -0
  75. package/dist/show-DR2x1YFc.mjs +85 -0
  76. package/dist/show-DR2x1YFc.mjs.map +1 -0
  77. package/dist/style.mjs +2 -2
  78. package/dist/text-9lwQeqYr.d.mts +33 -0
  79. package/dist/text-9lwQeqYr.d.mts.map +1 -0
  80. package/dist/text-DILrol9j.mjs +32 -0
  81. package/dist/text-DILrol9j.mjs.map +1 -0
  82. package/dist/text-q26V6_Sc.mjs +111 -0
  83. package/dist/text-q26V6_Sc.mjs.map +1 -0
  84. package/dist/text.d.mts +2 -33
  85. package/dist/text.mjs +1 -110
  86. package/dist/{theme.config-CU3B50HN.mjs → theme.config-BptYUq4K.mjs} +4 -4
  87. package/dist/{theme.config-CU3B50HN.mjs.map → theme.config-BptYUq4K.mjs.map} +1 -1
  88. package/dist/themes.mjs +1 -1
  89. package/dist/tree-Bo1afYhe.mjs +73 -0
  90. package/dist/tree-Bo1afYhe.mjs.map +1 -0
  91. package/dist/widget-BkMwhY2p.mjs +40 -0
  92. package/dist/widget-BkMwhY2p.mjs.map +1 -0
  93. package/dist/widget-M7jD8Rgg.d.mts +49 -0
  94. package/dist/widget-M7jD8Rgg.d.mts.map +1 -0
  95. package/dist/widgets/autocomplete.mjs +2 -2
  96. package/dist/widgets/box.mjs +1 -1
  97. package/dist/widgets/code.mjs +7 -7
  98. package/dist/widgets/completions/actions.d.mts +2 -41
  99. package/dist/widgets/completions/actions.mjs +1 -47
  100. package/dist/widgets/completions/files.d.mts +2 -29
  101. package/dist/widgets/completions/files.mjs +1 -41
  102. package/dist/widgets/completions/github.d.mts +2 -64
  103. package/dist/widgets/completions/github.mjs +1 -136
  104. package/dist/widgets/completions/index.d.mts +3 -3
  105. package/dist/widgets/completions/index.mjs +3 -3
  106. package/dist/widgets/diff.d.mts +1 -1
  107. package/dist/widgets/diff.mjs +3 -3
  108. package/dist/widgets/divider.mjs +1 -8
  109. package/dist/widgets/image.d.mts +2 -37
  110. package/dist/widgets/image.mjs +1 -1
  111. package/dist/widgets/input.mjs +1 -496
  112. package/dist/widgets/log.mjs +1 -129
  113. package/dist/widgets/markdown.d.mts +2 -2
  114. package/dist/widgets/markdown.mjs +1 -123
  115. package/dist/widgets/overlay.mjs +1 -17
  116. package/dist/widgets/picker.mjs +1 -1
  117. package/dist/widgets/select.mjs +1 -194
  118. package/dist/widgets/show.mjs +1 -84
  119. package/dist/widgets/text.mjs +1 -31
  120. package/dist/widgets/tree.mjs +1 -72
  121. package/dist/widgets/widget.d.mts +2 -49
  122. package/dist/widgets/widget.mjs +1 -39
  123. package/dist/worker-BvRm4OsW.mjs +2 -0
  124. package/dist/worker-T6SdPzUH.mjs +52 -0
  125. package/dist/worker-T6SdPzUH.mjs.map +1 -0
  126. package/package.json +7 -3
  127. package/dist/client-D6C_uczF.mjs.map +0 -1
  128. package/dist/clipboard.mjs.map +0 -1
  129. package/dist/registry-U654j61Y.mjs.map +0 -1
  130. package/dist/render-DxVLqqB9.mjs +0 -74
  131. package/dist/render-DxVLqqB9.mjs.map +0 -1
  132. package/dist/rolldown-runtime-D7D4PA-g.mjs +0 -13
  133. package/dist/services/logger.mjs.map +0 -1
  134. package/dist/shiki-BrjVUkeh.mjs.map +0 -1
  135. package/dist/text.d.mts.map +0 -1
  136. package/dist/text.mjs.map +0 -1
  137. package/dist/widgets/completions/actions.d.mts.map +0 -1
  138. package/dist/widgets/completions/actions.mjs.map +0 -1
  139. package/dist/widgets/completions/files.d.mts.map +0 -1
  140. package/dist/widgets/completions/files.mjs.map +0 -1
  141. package/dist/widgets/completions/github.d.mts.map +0 -1
  142. package/dist/widgets/completions/github.mjs.map +0 -1
  143. package/dist/widgets/divider.mjs.map +0 -1
  144. package/dist/widgets/image.d.mts.map +0 -1
  145. package/dist/widgets/input.mjs.map +0 -1
  146. package/dist/widgets/log.mjs.map +0 -1
  147. package/dist/widgets/markdown.mjs.map +0 -1
  148. package/dist/widgets/overlay.mjs.map +0 -1
  149. package/dist/widgets/select.mjs.map +0 -1
  150. package/dist/widgets/show.mjs.map +0 -1
  151. package/dist/widgets/text.mjs.map +0 -1
  152. package/dist/widgets/tree.mjs.map +0 -1
  153. package/dist/widgets/widget.d.mts.map +0 -1
  154. package/dist/widgets/widget.mjs.map +0 -1
  155. package/dist/worker-DjHuQSbY.mjs +0 -27
  156. package/dist/worker-DjHuQSbY.mjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-T6SdPzUH.mjs","names":[],"sources":["../src/shiki/worker.ts"],"sourcesContent":["// oxlint-disable typescript/method-signature-style unicorn/require-post-message-target-origin\nimport type { MaybePromise } from \"@zaly/shared\"\n\nexport const workerHost = {\n onMessage: (handler: (message: any) => MaybePromise): void => {\n if (typeof globalThis.addEventListener === \"function\") {\n globalThis.addEventListener(\"message\", (event: MessageEvent) => void handler(event.data))\n return\n }\n void import(\"node:worker_threads\").then(({ parentPort }) => {\n parentPort?.on(\"message\", (message) => void handler(message))\n })\n },\n\n postMessage: (message: unknown): void => {\n if (typeof globalThis.postMessage === \"function\") {\n // oxlint-disable-next-line unicorn/require-post-message-target-origin\n globalThis.postMessage(message)\n return\n }\n void import(\"node:worker_threads\").then(({ parentPort }) => {\n // oxlint-disable-next-line unicorn/require-post-message-target-origin\n parentPort?.postMessage(message)\n })\n },\n}\n\nexport type WorkerInstance<In = unknown, Out = unknown> = {\n on(event: \"message\", handler: (message: Out) => MaybePromise): void\n on(event: \"error\", handler: (error: Error) => MaybePromise): void\n postMessage(message: In): void\n terminate(): void\n}\n\nexport async function createWorker<In = unknown, Out = unknown>(): Promise<\n WorkerInstance<In, Out>\n> {\n const scriptURL = new URL(import.meta.resolve(\"#shiki-worker\"))\n if (typeof globalThis.Worker === \"function\") {\n const w = new Worker(scriptURL, { type: \"module\" })\n // oxlint-disable-next-line typescript/no-unnecessary-condition\n w.unref?.()\n return {\n on: (event, handler) => {\n if (event === \"error\")\n w.addEventListener(event, (e) => void handler(e.error ?? new Error(e.message)))\n else w.addEventListener(event, (e) => void handler(e.data))\n // Registering a listener can re-reference the worker.\n // oxlint-disable-next-line typescript/no-unnecessary-condition\n w.unref?.()\n },\n postMessage: (message) => w.postMessage(message),\n terminate: () => w.terminate(),\n }\n }\n const { Worker: NodeWorker } = await import(\"node:worker_threads\")\n const worker = new NodeWorker(scriptURL)\n worker.unref()\n return {\n on: (event, handler) => {\n worker.on(event, (e) => void handler(e))\n // Registering a listener re-references the worker's MessagePort.\n worker.unref()\n },\n postMessage: (message) => worker.postMessage(message),\n terminate: () => void worker.terminate(),\n }\n}\n"],"mappings":";AAGA,MAAa,aAAa;CACxB,YAAY,YAAkD;EAC5D,IAAI,OAAO,WAAW,qBAAqB,YAAY;GACrD,WAAW,iBAAiB,YAAY,UAAwB,KAAK,QAAQ,MAAM,IAAI,CAAC;GACxF;EACF;EACA,OAAY,sBAAsB,CAAC,MAAM,EAAE,iBAAiB;GAC1D,YAAY,GAAG,YAAY,YAAY,KAAK,QAAQ,OAAO,CAAC;EAC9D,CAAC;CACH;CAEA,cAAc,YAA2B;EACvC,IAAI,OAAO,WAAW,gBAAgB,YAAY;GAEhD,WAAW,YAAY,OAAO;GAC9B;EACF;EACA,OAAY,sBAAsB,CAAC,MAAM,EAAE,iBAAiB;GAE1D,YAAY,YAAY,OAAO;EACjC,CAAC;CACH;AACF;AASA,eAAsB,eAEpB;CACA,MAAM,YAAY,IAAI,IAAI,OAAO,KAAK,QAAQ,eAAe,CAAC;CAC9D,IAAI,OAAO,WAAW,WAAW,YAAY;EAC3C,MAAM,IAAI,IAAI,OAAO,WAAW,EAAE,MAAM,SAAS,CAAC;EAElD,EAAE,QAAQ;EACV,OAAO;GACL,KAAK,OAAO,YAAY;IACtB,IAAI,UAAU,SACZ,EAAE,iBAAiB,QAAQ,MAAM,KAAK,QAAQ,EAAE,SAAS,IAAI,MAAM,EAAE,OAAO,CAAC,CAAC;SAC3E,EAAE,iBAAiB,QAAQ,MAAM,KAAK,QAAQ,EAAE,IAAI,CAAC;IAG1D,EAAE,QAAQ;GACZ;GACA,cAAc,YAAY,EAAE,YAAY,OAAO;GAC/C,iBAAiB,EAAE,UAAU;EAC/B;CACF;CACA,MAAM,EAAE,QAAQ,eAAe,MAAM,OAAO;CAC5C,MAAM,SAAS,IAAI,WAAW,SAAS;CACvC,OAAO,MAAM;CACb,OAAO;EACL,KAAK,OAAO,YAAY;GACtB,OAAO,GAAG,QAAQ,MAAM,KAAK,QAAQ,CAAC,CAAC;GAEvC,OAAO,MAAM;EACf;EACA,cAAc,YAAY,OAAO,YAAY,OAAO;EACpD,iBAAiB,KAAK,OAAO,UAAU;CACzC;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zaly/tui",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Terminal UI framework used by zaly.",
5
5
  "keywords": [
6
6
  "ansi",
@@ -25,6 +25,10 @@
25
25
  ],
26
26
  "type": "module",
27
27
  "imports": {
28
+ "#shiki-worker": {
29
+ "default": "./dist/shiki/server.mjs"
30
+ },
31
+ "#assets/*": "./assets/*",
28
32
  "#ansi": {
29
33
  "bun": "./dist/ansi.bun.mjs",
30
34
  "default": "./dist/ansi.node.mjs"
@@ -79,7 +83,7 @@
79
83
  "marked": "^18.0.6",
80
84
  "pathe": "^2.0.3",
81
85
  "shiki": "^4.3.1",
82
- "@zaly/shared": "0.0.3"
86
+ "@zaly/shared": "0.0.4"
83
87
  },
84
88
  "devDependencies": {
85
89
  "@typia/transform": "^12.1.1",
@@ -95,7 +99,7 @@
95
99
  },
96
100
  "engines": {
97
101
  "bun": ">=1.2",
98
- "node": ">=24"
102
+ "node": ">=22.11"
99
103
  },
100
104
  "inlinedDependencies": {
101
105
  "typia": "13.0.2"
@@ -1 +0,0 @@
1
- {"version":3,"file":"client-D6C_uczF.mjs","names":["#id","#cache","#queue","#rejectAll","#request","#running","#createWorker","#worker","#workerPromise","#results","#updateScheduled","#update"],"sources":["../src/shiki/client.ts"],"sourcesContent":["import type { ShikiJob, ShikiOpts, ShikiRequest, ShikiResult, ShikiWorkerRequest } from \"./types.ts\"\nimport type { WorkerInstance } from \"./worker.ts\"\n\nimport { hash } from \"@zaly/shared\"\nimport { hasColors } from \"@zaly/shared/env\"\nimport { isShikiLang } from \"../schemas/gen/shiki.ts\"\n\nfunction isJob(s: ShikiJob | ShikiResult): s is ShikiJob {\n return \"key\" in s && \"lang\" in s\n}\n\nconst MAX_CACHE_SIZE = 100\n// Max jobs posted to the worker. Worker still processes serially.\nconst MAX_RUNNING = 2\n\nexport class ShikiWorkerClient {\n #id = 0\n #cache = new Map<string, ShikiJob>()\n #queue = new Map<number, ShikiJob>()\n #running = new Map<number, ShikiJob>()\n #results: ShikiResult[] = []\n #worker?: WorkerInstance<ShikiWorkerRequest, ShikiResult>\n #workerPromise?: Promise<WorkerInstance<ShikiWorkerRequest, ShikiResult>>\n // oxlint-disable-next-line no-unused-private-class-members\n #updateScheduled: Promise<void> | undefined = undefined\n\n key(req: ShikiRequest): string {\n return req.key ?? hash(`${req.lang}:${req.code}:${req.theme ?? \"default\"}`)\n }\n\n toJob(req: ShikiRequest): ShikiJob | ShikiResult {\n const id = ++this.#id\n const key = this.key(req)\n if (!hasColors) return { error: \"terminal does not support colors\", id, key, value: req.code }\n if (!isShikiLang(req.lang))\n return { error: `unsupported language: ${req.lang}`, id, key, value: req.code }\n const cached = this.#cache.get(key)\n if (cached) {\n this.#cache.delete(key)\n this.#cache.set(key, cached) // bump to end of LRU\n return cached\n }\n while (this.#cache.size > MAX_CACHE_SIZE) this.#cache.delete(this.#cache.keys().next().value!)\n const ret: ShikiJob = { ...req, id, key, lang: req.lang, ...Promise.withResolvers() }\n this.#cache.set(key, ret)\n return ret\n }\n\n highlight(code: string, lang: string, opts: ShikiOpts = {}): Promise<ShikiResult> {\n const req: ShikiRequest = { code, lang, signal: opts.signal, theme: opts.theme }\n const job = this.toJob(req)\n if (!isJob(job)) return Promise.resolve(job)\n if (!job.scheduled) {\n this.#queue.set(job.id, job)\n job.signal?.addEventListener(\"abort\", () => this.update(), { once: true })\n this.update()\n }\n return job.promise\n }\n\n dispose(): void {\n const error = new Error(\"Shiki worker disposed\")\n this.#rejectAll(error)\n }\n\n async #request(job: ShikiJob): Promise<void> {\n job.scheduled = true\n this.#running.set(job.id, job)\n const worker = await this.#createWorker()\n const { signal: _s, promise: _prom, resolve: _res, reject: _rej, ...msg } = job\n // oxlint-disable-next-line unicorn/require-post-message-target-origin\n worker.postMessage(msg)\n }\n\n async #createWorker(): Promise<WorkerInstance<ShikiWorkerRequest, ShikiResult>> {\n if (this.#worker) return this.#worker\n this.#workerPromise ??= (async () => {\n const { createWorker } = await import(\"./worker.ts\")\n const worker = await createWorker<ShikiWorkerRequest, ShikiResult>(\n new URL(\"server.ts\", import.meta.url)\n )\n worker.on(\"message\", (event) => {\n this.#running.delete(event.id)\n this.#results.push(event)\n this.update()\n })\n worker.on(\"error\", (error) => {\n this.#rejectAll(error)\n })\n return worker\n })()\n return (this.#worker = await this.#workerPromise)\n }\n\n update() {\n this.#updateScheduled ??= this.#update().finally(() => {\n this.#updateScheduled = undefined\n })\n }\n\n async #update() {\n // Resolve pending results\n while (this.#results.length) {\n const result = this.#results.shift()!\n const job = this.#queue.get(result.id)\n if (job) {\n if (result.error) job.reject(new Error(result.error))\n else job.resolve(result)\n\n // oxlint-disable-next-line no-await-in-loop\n await new Promise((resolve) => setImmediate(resolve)) // yield to event loop between batches\n this.#queue.delete(job.id)\n }\n }\n\n // Clean up aborted jobs\n for (const job of this.#queue.values()) {\n if (job.signal?.aborted && !job.scheduled) {\n job.reject(new Error(\"Job aborted\"))\n this.#queue.delete(job.id)\n this.#cache.delete(job.key)\n }\n }\n\n if (this.#running.size >= MAX_RUNNING || this.#queue.size === 0) return\n\n // Schedule new jobs up to concurrency limit, prioritizing the most recently requested ones (LIFO)\n const todo = [...this.#queue.values()]\n .filter((j) => !j.scheduled)\n .slice(-(MAX_RUNNING - this.#running.size))\n await Promise.all(todo.map((j) => this.#request(j)))\n }\n\n #rejectAll(error: unknown): void {\n for (const { reject } of this.#queue.values()) reject(error)\n this.#cache.clear()\n this.#queue.clear()\n this.#running.clear()\n this.#results = []\n this.#worker?.terminate()\n this.#worker = undefined\n }\n}\n\nexport const shikiWorker = new ShikiWorkerClient()\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"clipboard.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 +0,0 @@
1
- {"version":3,"file":"registry-U654j61Y.mjs","names":["moonJson"],"sources":["../assets/themes/tokyonight-moon.json","../src/themes/builtin.ts","../src/themes/default.ts","../src/themes/registry.ts"],"sourcesContent":["","// AUTO-GENERATED by `scripts/gen-themes.ts`. DO NOT EDIT.\n// Re-run `bun scripts/gen-themes.ts` after adding/renaming a theme.\n\nimport type { ThemeLoader } from \"./registry.ts\"\nimport type { Theme } from \"./types.ts\"\n\nfunction importTheme(path: string): Promise<Partial<Theme>> {\n const url = new URL(`../../assets/themes/${path}.json`, import.meta.url).href\n return import(url, { with: { type: \"json\" } }).then((m) => m.default as Partial<Theme>)\n}\n\n/**\n * Async loader map for every built-in theme. Each entry returns the\n * raw JSON; `themes/index.ts` wraps these with `resolveTheme` to\n * produce a fully-resolved `Theme` from the registry. Bundlers\n * code-split each `import(\"…json\")` into its own chunk, so apps only\n * pull the theme they actually load.\n *\n * `as const satisfies Record<string, ThemeLoader>` keeps the keys\n * narrow (so `BuiltinThemeName` is a literal union) but lets the\n * value type widen to `Partial<Theme>` — themes have no per-key\n * narrow types worth preserving, and the satisfies clause means\n * `themes/index.ts` can pass this map straight to `themeRegistry`\n * with no cast.\n */\nexport const builtin = {\n \"ayu-dark\": () => importTheme(\"ayu-dark\"),\n \"catppuccin-frappe\": () => importTheme(\"catppuccin-frappe\"),\n \"catppuccin-latte\": () => importTheme(\"catppuccin-latte\"),\n \"catppuccin-macchiato\": () => importTheme(\"catppuccin-macchiato\"),\n \"catppuccin-mocha\": () => importTheme(\"catppuccin-mocha\"),\n dracula: () => importTheme(\"dracula\"),\n \"dracula-soft\": () => importTheme(\"dracula-soft\"),\n \"everforest-dark\": () => importTheme(\"everforest-dark\"),\n \"everforest-light\": () => importTheme(\"everforest-light\"),\n \"github-dark\": () => importTheme(\"github-dark\"),\n \"github-light\": () => importTheme(\"github-light\"),\n \"gruvbox-dark-medium\": () => importTheme(\"gruvbox-dark-medium\"),\n \"kanagawa-dragon\": () => importTheme(\"kanagawa-dragon\"),\n \"kanagawa-wave\": () => importTheme(\"kanagawa-wave\"),\n \"material-theme-palenight\": () => importTheme(\"material-theme-palenight\"),\n \"min-dark\": () => importTheme(\"min-dark\"),\n \"min-light\": () => importTheme(\"min-light\"),\n monokai: () => importTheme(\"monokai\"),\n \"night-owl\": () => importTheme(\"night-owl\"),\n nord: () => importTheme(\"nord\"),\n \"one-dark-pro\": () => importTheme(\"one-dark-pro\"),\n \"one-light\": () => importTheme(\"one-light\"),\n poimandres: () => importTheme(\"poimandres\"),\n \"rose-pine\": () => importTheme(\"rose-pine\"),\n \"solarized-dark\": () => importTheme(\"solarized-dark\"),\n \"solarized-light\": () => importTheme(\"solarized-light\"),\n \"synthwave-84\": () => importTheme(\"synthwave-84\"),\n \"tokyonight-day\": () => importTheme(\"tokyonight-day\"),\n \"tokyonight-moon\": () => importTheme(\"tokyonight-moon\"),\n \"tokyonight-night\": () => importTheme(\"tokyonight-night\"),\n \"tokyonight-storm\": () => importTheme(\"tokyonight-storm\"),\n vesper: () => importTheme(\"vesper\"),\n \"vitesse-dark\": () => importTheme(\"vitesse-dark\"),\n \"vitesse-light\": () => importTheme(\"vitesse-light\"),\n} as const satisfies Record<string, ThemeLoader>\n","// oxlint-disable typescript/no-deprecated\nimport type { Theme } from \"./types.ts\"\n\n/**\n * Slot-level fallbacks. Every Theme value is shallow-merged on top of\n * this record by `resolveTheme`, so partial JSON themes only need to\n * declare slots that diverge from these defaults.\n *\n * Distinct from `defaultTheme` — that's the precomputed `tokyonight-moon`\n * Theme bundled as the default palette for `createCtx()` etc.\n */\n// oxlint-disable-next-line sort-keys\nexport const defaults: Omit<Theme, \"id\"> = {\n // base colors\n primary: \"blue\",\n accent: \"brightMagenta\",\n\n // text\n text: \"inherit\",\n muted: { dim: true },\n quiet: { dim: true, fg: \"muted\", italic: true },\n comment: { fg: \"muted\", italic: true },\n title: { bold: true, fg: \"primary\" },\n delim: \"muted\",\n\n // surface & structure\n subtle: \"brightBlack\",\n ui: { bg: \"subtle\" },\n overlay: { bg: \"subtle\" },\n highlight: { bg: \"subtle\" },\n\n // ui primitives\n divider: \"subtle\",\n selection: { bg: \"brightBlue\", fg: \"black\" },\n gutter: \"subtle\",\n prompt: \"inherit\",\n border: \"black\",\n borderTitle: \"title\",\n\n // code\n code: { bg: \"subtle\" },\n codeTitle: \"title\",\n\n // log levels\n success: \"green\",\n info: \"cyan\",\n warn: \"yellow\",\n error: \"red\",\n\n // syntax\n syntaxNumber: \"brightBlue\",\n syntaxString: \"green\",\n syntaxBoolean: \"yellow\",\n syntaxFunction: \"blue\",\n syntaxField: \"white\",\n syntaxConstant: \"magenta\",\n syntaxSpecial: \"red\",\n syntaxDelimiter: \"delim\",\n syntaxBracket: \"delim\",\n\n // markdown\n mdBold: { bold: true },\n mdCode: { bg: \"primary+10\", fg: \"black\" },\n mdCodeBlock: { bg: \"highlight\", fg: \"primary\" },\n mdCodeBlockTitle: \"title\",\n mdHeading1: \"mdHeading\",\n mdHeading2: { bold: true, fg: \"accent\" },\n mdHeading3: \"mdHeading2\",\n mdHeading4: \"mdHeading2\",\n mdHeading5: \"mdHeading2\",\n mdHeading6: \"mdHeading2\",\n mdHeading: \"title\",\n mdHr: \"accent\",\n mdItalic: { italic: true },\n mdLink: { fg: \"primary\", underline: true },\n mdListBullet: \"accent\",\n mdListChecked: \"primary\",\n mdListUnchecked: \"primary\",\n mdQuote: \"muted\",\n mdStrikethrough: { strikethrough: true },\n mdTable: \"accent\",\n mdTableHeader: \"title\",\n\n // menu\n optionActive: \"selection\",\n optionDesc: \"muted\",\n optionName: \"primary\",\n\n // diff\n diffAdd: { bg: \"success-50\", fg: \"success\" },\n diffContext: \"muted\",\n diffDel: { bg: \"error-50\", fg: \"error\" },\n diffLine: \"gutter\",\n diffTitle: \"title\",\n}\n\n// oxlint-disable-next-line sort-keys\nexport const ansi: Theme = {\n id: \"ansi\",\n name: \"ANSI\",\n ...defaults,\n subtle: \"inherit\",\n divider: \"brightBlue\",\n border: \"brightBlue\",\n highlight: { bg: \"brightBlack\" },\n ui: { bg: \"brightBlack\" },\n selection: { inverse: true },\n diffAdd: { fg: \"success\" },\n diffDel: { fg: \"error\" },\n}\n","import type { Theme } from \"./types.ts\"\n\nimport { withError } from \"@zaly/shared\"\nimport { createRegistry } from \"@zaly/shared/registry\"\nimport { readFileSync } from \"node:fs\"\nimport { basename } from \"pathe\"\nimport moonJson from \"../../assets/themes/tokyonight-moon.json\" with { type: \"json\" }\nimport { builtin } from \"./builtin.ts\"\nimport { defaults, ansi } from \"./default.ts\"\n\n/** Built-in theme names plus the synthetic `\"ansi\"` palette (no JSON\n * — falls back to `defaults` only). */\nexport type BuiltinTheme = keyof typeof builtin | \"ansi\"\nexport type AnyTheme = BuiltinTheme | (string & {})\nexport type ThemeLoader = () => Promise<Partial<Theme>>\n\nconst DEFAULT_THEME: BuiltinTheme = \"tokyonight-moon\"\n\n/**\n * Registry of built-in themes plus any registered at runtime. The\n * registry never awaits — loaders return `Promise<Partial<Theme>>` so\n * callers `await` explicitly.\n */\nexport const themeRegistry = createRegistry<ThemeLoader>(\"theme\").from(builtin)\n// NOTE: remove the ansi theme for now, since it looks kinda broken and not really possible\n// to fix properly\nthemeRegistry.register(\"ansi\", async () => ({ ...ansi }))\n\n/**\n * Load a theme by name or path.\n */\nexport async function loadTheme(name?: string): Promise<Theme>\nexport async function loadTheme(opts: { name?: string; path?: string }): Promise<Theme>\nexport async function loadTheme(o?: string | { name?: string; path?: string }): Promise<Theme> {\n o ??= DEFAULT_THEME\n const opts = typeof o === \"string\" ? { name: o } : o\n\n if (opts.name?.endsWith(\".json\")) return loadTheme({ path: opts.name })\n if (opts.path) return loadThemeFile(opts.path)\n if (opts.name) {\n if (opts.name === DEFAULT_THEME) return defaultTheme\n if (themeRegistry.has(opts.name))\n return resolveTheme(await themeRegistry.load(opts.name), opts.name)\n throw new Error(`Theme \"${opts.name}\" not found.`)\n }\n\n return defaultTheme\n}\n\n/**\n * Fill defaults on a raw theme object. Built-in JSON is dev-controlled\n * so we don't pay typia's ~3MB of generated assertions at startup;\n * user-supplied JSON goes through `loadThemeFile` which validates.\n */\nfunction resolveTheme(theme: unknown, id: string): Theme {\n const { $schema: _, ...rest } = theme as Partial<Theme> & { $schema?: unknown }\n return { ...defaults, id, name: id, ...rest }\n}\n\n/**\n * Load a theme directly from a file path. No directory search happens —\n * this is the escape hatch for CLIs that accept an explicit `--theme\n * /path/to/theme.json` argument.\n *\n * Async because typia's generated `validateTheme` (a few thousand\n * unrolled assertions) is dynamically imported here — it's only needed\n * for user-supplied JSON and should stay off the main startup path.\n */\nasync function loadThemeFile(path: string): Promise<Theme> {\n const raw = withError(() => readFileSync(path, \"utf8\"), `Failed to read theme file at ${path}`)\n const data = withError(() => JSON.parse(raw), `Failed to parse theme JSON at ${path}`)\n const { validateTheme } = await import(\"../schemas/gen/theme.config.ts\")\n return resolveTheme(validateTheme(data), basename(path, \".json\"))\n}\n\n/** TokyoNight Moon — the default. Sourced from `assets/themes/tokyonight-moon.json`. */\nexport const defaultTheme = resolveTheme(moonJson, \"tokyonight-moon\")\n"],"mappings":"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"}
@@ -1,74 +0,0 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { d as createSuspenseBoundary, i as createContext, l as createRoot, n as SuspenseContext, v as provideContext } from "./reactive-f7NpAi69.mjs";
3
- import { t as defaultTheme } from "./registry-U654j61Y.mjs";
4
- import { t as styleBuilder } from "./builder-D49ECBEf.mjs";
5
- //#region src/core/ctx.ts
6
- var ctx_exports = /* @__PURE__ */ __exportAll({ createCtx: () => createCtx });
7
- /**
8
- * Current terminal width in cells. Returns `undefined` when stdout isn't a
9
- * TTY (piped output, tests, non-Node/Bun runtimes) — callers should fall
10
- * back to a sensible default when this is `undefined`.
11
- *
12
- * @internal
13
- */
14
- function termWidth() {
15
- if (typeof process === "undefined") return void 0;
16
- const cols = process.stdout.columns;
17
- return typeof cols === "number" && cols > 0 ? cols : void 0;
18
- }
19
- /**
20
- * Build a fresh `RenderCtx` from a resolved theme + width. Binds a `style`
21
- * builder to the theme so components can use `ctx.style.primary(...)`
22
- * without constructing their own.
23
- *
24
- * Width is clamped to the terminal's current column count (falling back to
25
- * 80 when stdout isn't a TTY). No width passed → terminal width is used
26
- * directly; an explicit `width` wider than the terminal is capped so
27
- * components never produce rows that wrap the display. Narrower explicit
28
- * widths (e.g. for fixed-layout tests) pass through unchanged.
29
- *
30
- * Dynamic theme loading (from a string name) lives in `themes/loadTheme`
31
- * — resolve the name first, then pass the `Theme` object here.
32
- */
33
- function createCtx(opts = {}) {
34
- const tw = termWidth() ?? 80;
35
- return {
36
- style: opts.style ?? styleBuilder(opts.theme ?? defaultTheme),
37
- transmit: (data) => process.stdout.write(data),
38
- version: opts.version ?? 0,
39
- width: Math.min(opts.width ?? tw, tw),
40
- ...opts
41
- };
42
- }
43
- //#endregion
44
- //#region src/core/render.ts
45
- const RenderContext = createContext();
46
- async function createRender(node, ctx = createCtx()) {
47
- if (ctx.boundary) return renderWithDrain(node, ctx.boundary, ctx);
48
- const boundary = createSuspenseBoundary();
49
- const nodeFn = node;
50
- return createRoot(async () => {
51
- provideContext(SuspenseContext, boundary);
52
- return renderWithDrain(nodeFn(), boundary, ctx);
53
- });
54
- }
55
- /** Wait for any pending work first, then render. On warm caches the
56
- * initial `whenIdle` resolves on the same microtask tick — no real
57
- * wait — and we render exactly once with final values. On cold the
58
- * wait blocks until `createAsync` settles. The follow-up `while`
59
- * catches the in-render case: a `_render` that fires a fresh
60
- * `createAsync` (signal write subscribes a new effect) bumps the
61
- * boundary back above zero, requiring another pass. */
62
- async function renderWithDrain(node, boundary, ctx) {
63
- if (boundary.active()) await boundary.whenIdle();
64
- let rows = await node.render(ctx);
65
- while (boundary.active()) {
66
- await boundary.whenIdle();
67
- rows = await node.render(ctx);
68
- }
69
- return rows;
70
- }
71
- //#endregion
72
- export { ctx_exports as i, createRender as n, createCtx as r, RenderContext as t };
73
-
74
- //# sourceMappingURL=render-DxVLqqB9.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"render-DxVLqqB9.mjs","names":[],"sources":["../src/core/ctx.ts","../src/core/render.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","import type { TerminalQueries } from \"../input/queries.ts\"\nimport type { StyleBuilder } from \"../style/builder.ts\"\nimport type { Theme } from \"../themes/types.ts\"\nimport type { RenderCtx } from \"./ctx.ts\"\n// oxlint-disable no-await-in-loop\nimport type { Node } from \"./node.ts\"\nimport type { Accessor, SuspenseBoundary } from \"./reactive.ts\"\n\nimport { createCtx } from \"./ctx.ts\"\nimport {\n createContext,\n createRoot,\n createSuspenseBoundary,\n provideContext,\n SuspenseContext,\n} from \"./reactive.ts\"\n\n/** Components-facing slice of the render environment — reactive\n * accessors a widget body reads via `useContext(RenderContext)`. Use\n * this when a widget needs ambient theme/style outside an `_render`\n * call (the place where `RenderCtx` is threaded in). Width is\n * deliberately omitted — that's a per-render allotted-size concept,\n * not an ambient one. */\nexport type RenderContextValue = {\n /** Active theme. Tracked — widgets that read it re-fire on swap. */\n theme: Accessor<Theme>\n /** Theme-bound chainable style builder. Derived memo over `theme`. */\n style: Accessor<StyleBuilder>\n /** Whether to render images (when supported) */\n images: Accessor<boolean>\n queries: TerminalQueries\n}\nexport const RenderContext = createContext<RenderContextValue>()\n\n/**\n * Render a Node, draining `createAsync` work in its subtree before\n * returning final rows. Two shapes:\n *\n * - `createRender(() => node, ctx?)` — factory form. Creates a fresh\n * `SuspenseBoundary` inside a new `createRoot` and runs the\n * factory in that scope so descendants find the boundary via\n * `useContext(SuspenseContext)`. For tests, benches, REPL.\n *\n * - `createRender(node, { ...ctx, boundary })` — pre-built Node\n * with a caller-provided boundary. Used by `Stream.render`: the\n * boundary was installed at `append` time, so we just render +\n * drain without a new root scope.\n *\n * `ctx` defaults to `createCtx()` in the factory form.\n *\n * ```ts\n * const rows = await createRender(() => markdown(\"**hi**\"))\n * ```\n */\nexport async function createRender(\n node: Node,\n ctx: RenderCtx & { boundary: SuspenseBoundary }\n): Promise<string[]>\nexport async function createRender(node: () => Node, ctx?: RenderCtx): Promise<string[]>\nexport async function createRender(\n node: Node | (() => Node),\n ctx: RenderCtx & { boundary?: SuspenseBoundary } = createCtx()\n): Promise<string[]> {\n if (ctx.boundary) return renderWithDrain(node as Node, ctx.boundary, ctx)\n const boundary = createSuspenseBoundary()\n const nodeFn = node as () => Node\n return createRoot(async () => {\n provideContext(SuspenseContext, boundary)\n return renderWithDrain(nodeFn(), boundary, ctx)\n })\n}\n\n/** Wait for any pending work first, then render. On warm caches the\n * initial `whenIdle` resolves on the same microtask tick — no real\n * wait — and we render exactly once with final values. On cold the\n * wait blocks until `createAsync` settles. The follow-up `while`\n * catches the in-render case: a `_render` that fires a fresh\n * `createAsync` (signal write subscribes a new effect) bumps the\n * boundary back above zero, requiring another pass. */\nasync function renderWithDrain(\n node: Node,\n boundary: SuspenseBoundary,\n ctx: RenderCtx\n): Promise<string[]> {\n if (boundary.active()) await boundary.whenIdle()\n let rows = await node.render(ctx)\n while (boundary.active()) {\n await boundary.whenIdle()\n rows = await node.render(ctx)\n }\n return rows\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;;;AChGA,MAAa,gBAAgB,cAAkC;AA2B/D,eAAsB,aACpB,MACA,MAAmD,UAAU,GAC1C;CACnB,IAAI,IAAI,UAAU,OAAO,gBAAgB,MAAc,IAAI,UAAU,GAAG;CACxE,MAAM,WAAW,uBAAuB;CACxC,MAAM,SAAS;CACf,OAAO,WAAW,YAAY;EAC5B,eAAe,iBAAiB,QAAQ;EACxC,OAAO,gBAAgB,OAAO,GAAG,UAAU,GAAG;CAChD,CAAC;AACH;;;;;;;;AASA,eAAe,gBACb,MACA,UACA,KACmB;CACnB,IAAI,SAAS,OAAO,GAAG,MAAM,SAAS,SAAS;CAC/C,IAAI,OAAO,MAAM,KAAK,OAAO,GAAG;CAChC,OAAO,SAAS,OAAO,GAAG;EACxB,MAAM,SAAS,SAAS;EACxB,OAAO,MAAM,KAAK,OAAO,GAAG;CAC9B;CACA,OAAO;AACT"}
@@ -1,13 +0,0 @@
1
- //#region \0rolldown/runtime.js
2
- var __defProp = Object.defineProperty;
3
- var __exportAll = (all, no_symbols) => {
4
- let target = {};
5
- for (var name in all) __defProp(target, name, {
6
- get: all[name],
7
- enumerable: true
8
- });
9
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
- return target;
11
- };
12
- //#endregion
13
- export { __exportAll as t };
@@ -1 +0,0 @@
1
- {"version":3,"file":"logger.mjs","names":["#opts","#factory","#defaultFactory","#stream","#writeFallback"],"sources":["../../src/services/logger.ts"],"sourcesContent":["import type { LogEntry, LogLevel, LogReporter } from \"@zaly/shared/logger\"\nimport type { InspectOpts } from \"../style/inspect.ts\"\nimport type { LogState } from \"../widgets/log.ts\"\n\nimport { Node } from \"../core/node.ts\"\nimport { inspectFormat, isMarkdown } from \"../style/inspect.ts\"\nimport { log } from \"../widgets/log.ts\"\n\n/** Minimal stream surface the logger needs. Stream satisfies this. */\nexport interface LoggerStream {\n append: (node: () => Node) => unknown\n}\n\nexport type LogEntryFactory = (level: LogLevel, msg: unknown[]) => Node\n\nexport type LogStyleOverride = Omit<LogState, \"level\" | \"content\">\n\nexport interface TuiReporterOpts extends InspectOpts {\n /** Render string messages as markdown when they look like it.\n * Default: `true`. */\n markdown?: boolean\n /** Per-level overrides for the default `log()` widget style fields. */\n styles?: Partial<Record<LogLevel, LogStyleOverride>>\n /** Override the node produced per entry. Default uses `log()`. */\n factory?: LogEntryFactory\n wrap?: (node: Node) => Node\n /** Fallback writer for when no stream is attached. Defaults to\n * `process.stdout/stderr`. Injectable for tests. */\n write?: (text: string, kind: \"stdout\" | \"stderr\") => void\n}\n\nconst ERR_LEVELS = new Set<LogLevel>([\"error\", \"fatal\", \"warn\"])\n\nfunction defaultWrite(text: string, kind: \"stdout\" | \"stderr\"): void {\n if (typeof process === \"undefined\") return\n const stream = kind === \"stderr\" ? process.stderr : process.stdout\n stream.write(text)\n}\n\nexport class TuiReporter implements LogReporter {\n #stream?: LoggerStream\n #opts: TuiReporterOpts\n #factory: LogEntryFactory\n\n constructor(opts: TuiReporterOpts = {}) {\n this.#opts = opts\n this.#factory = opts.factory ?? ((level, msg) => this.#defaultFactory(level, msg))\n }\n\n attach(stream: LoggerStream): this {\n this.#stream = stream\n return this\n }\n\n detach(): this {\n this.#stream = undefined\n return this\n }\n\n $log({ level, msg }: LogEntry): void {\n if (this.#stream) {\n this.#stream.append(() => {\n const node = this.#factory(level, msg)\n return this.#opts.wrap?.(node) ?? node\n })\n return\n }\n this.#writeFallback(level, msg)\n }\n\n #defaultFactory(level: LogLevel, msg: unknown[]): Node {\n const nodes = msg.filter((m): m is Node => m instanceof Node)\n msg = msg.filter((m) => !(m instanceof Node))\n\n // Convert Error objects to their message unless stacktrace is requested.\n msg = this.#opts.stacktrace ? msg : msg.map((v) => (v instanceof Error ? v.message : v))\n\n // If all remaining messages are strings, join them into one string for the log content.\n if (msg.length && msg.every((m) => typeof m === \"string\")) msg = [msg.join(\" \")]\n\n // If there's exactly one string message, use it as the log content;\n // otherwise, leave it undefined to be formatted with `inspect()`.\n // Text allow markdown if the message looks like markdown and the option is enabled.\n let text = msg.length === 1 && typeof msg[0] === \"string\" ? msg[0] : undefined\n if (msg.length === 0) text = \"\"\n const markdown = (this.#opts.markdown ?? true) && text !== undefined && isMarkdown(text)\n const overrides = this.#opts.styles?.[level] ?? {}\n return log(\n {\n content: text ?? ((ctx) => inspectFormat(msg, { ...this.#opts, style: ctx.style })),\n level,\n markdown,\n ...overrides,\n },\n ...nodes\n )\n }\n\n /** String path for the no-stream fallback — builds a plain inspected\n * line (no prefix chrome) and writes it to stdout/stderr. Keeps the\n * fallback synchronous and ordered. */\n #writeFallback(level: LogLevel, msg: unknown[]): void {\n const str = inspectFormat(msg, this.#opts)\n const kind: \"stdout\" | \"stderr\" = ERR_LEVELS.has(level) ? \"stderr\" : \"stdout\"\n const write = this.#opts.write ?? defaultWrite\n write(`${str}\\n`, kind)\n }\n}\n"],"mappings":";;;;AA+BA,MAAM,6BAAa,IAAI,IAAc;CAAC;CAAS;CAAS;AAAM,CAAC;AAE/D,SAAS,aAAa,MAAc,MAAiC;CACnE,IAAI,OAAO,YAAY,aAAa;CAEpC,CADe,SAAS,WAAW,QAAQ,SAAS,QAAQ,OAAA,CACrD,MAAM,IAAI;AACnB;AAEA,IAAa,cAAb,MAAgD;CAC9C;CACA;CACA;CAEA,YAAY,OAAwB,CAAC,GAAG;EACtC,KAAKA,QAAQ;EACb,KAAKC,WAAW,KAAK,aAAa,OAAO,QAAQ,KAAKC,gBAAgB,OAAO,GAAG;CAClF;CAEA,OAAO,QAA4B;EACjC,KAAKC,UAAU;EACf,OAAO;CACT;CAEA,SAAe;EACb,KAAKA,UAAU,KAAA;EACf,OAAO;CACT;CAEA,KAAK,EAAE,OAAO,OAAuB;EACnC,IAAI,KAAKA,SAAS;GAChB,KAAKA,QAAQ,aAAa;IACxB,MAAM,OAAO,KAAKF,SAAS,OAAO,GAAG;IACrC,OAAO,KAAKD,MAAM,OAAO,IAAI,KAAK;GACpC,CAAC;GACD;EACF;EACA,KAAKI,eAAe,OAAO,GAAG;CAChC;CAEA,gBAAgB,OAAiB,KAAsB;EACrD,MAAM,QAAQ,IAAI,QAAQ,MAAiB,aAAa,IAAI;EAC5D,MAAM,IAAI,QAAQ,MAAM,EAAE,aAAa,KAAK;EAG5C,MAAM,KAAKJ,MAAM,aAAa,MAAM,IAAI,KAAK,MAAO,aAAa,QAAQ,EAAE,UAAU,CAAE;EAGvF,IAAI,IAAI,UAAU,IAAI,OAAO,MAAM,OAAO,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,KAAK,GAAG,CAAC;EAK/E,IAAI,OAAO,IAAI,WAAW,KAAK,OAAO,IAAI,OAAO,WAAW,IAAI,KAAK,KAAA;EACrE,IAAI,IAAI,WAAW,GAAG,OAAO;EAC7B,MAAM,YAAY,KAAKA,MAAM,YAAY,SAAS,SAAS,KAAA,KAAa,WAAW,IAAI;EACvF,MAAM,YAAY,KAAKA,MAAM,SAAS,UAAU,CAAC;EACjD,OAAO,IACL;GACE,SAAS,UAAU,QAAQ,cAAc,KAAK;IAAE,GAAG,KAAKA;IAAO,OAAO,IAAI;GAAM,CAAC;GACjF;GACA;GACA,GAAG;EACL,GACA,GAAG,KACL;CACF;;;;CAKA,eAAe,OAAiB,KAAsB;EACpD,MAAM,MAAM,cAAc,KAAK,KAAKA,KAAK;EACzC,MAAM,OAA4B,WAAW,IAAI,KAAK,IAAI,WAAW;EAErE,CADc,KAAKA,MAAM,SAAS,aAAA,CAC5B,GAAG,IAAI,KAAK,IAAI;CACxB;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"shiki-BrjVUkeh.mjs","names":[],"sources":["../src/schemas/gen/shiki.ts","../src/shiki/shiki.ts"],"sourcesContent":["import type { ShikiLanguage, ShikiTheme } from \"../../shiki/types.ts\";\nconst lang = (() => { const _iv1 = new Set([\"1c\", \"1c-query\", \"abap\", \"actionscript-3\", \"ada\", \"adoc\", \"angular-html\", \"angular-ts\", \"apache\", \"apex\", \"apl\", \"applescript\", \"ara\", \"asciidoc\", \"asm\", \"astro\", \"awk\", \"ballerina\", \"bash\", \"bat\", \"batch\", \"be\", \"beancount\", \"berry\", \"bibtex\", \"bicep\", \"bird\", \"bird2\", \"blade\", \"bsl\", \"c\", \"c#\", \"c++\", \"c3\", \"cadence\", \"cairo\", \"cdc\", \"cjs\", \"clarity\", \"clj\", \"clojure\", \"closure-templates\", \"cmake\", \"cmd\", \"cobol\", \"codeowners\", \"codeql\", \"coffee\", \"coffeescript\", \"common-lisp\", \"console\", \"coq\", \"cpp\", \"cql\", \"crystal\", \"cs\", \"csharp\", \"css\", \"csv\", \"cts\", \"cue\", \"cypher\", \"d\", \"dart\", \"dax\", \"desktop\", \"diff\", \"docker\", \"dockerfile\", \"dotenv\", \"dream-maker\", \"edge\", \"elisp\", \"elixir\", \"elm\", \"emacs-lisp\", \"erb\", \"erl\", \"erlang\", \"f\", \"f#\", \"f03\", \"f08\", \"f18\", \"f77\", \"f90\", \"f95\", \"fennel\", \"fish\", \"fluent\", \"for\", \"fortran-fixed-form\", \"fortran-free-form\", \"fs\", \"fsharp\", \"fsl\", \"ftl\", \"gd\", \"gdresource\", \"gdscript\", \"gdshader\", \"genie\", \"gherkin\", \"git-commit\", \"git-rebase\", \"gjs\", \"gleam\", \"glimmer-js\", \"glimmer-ts\", \"glsl\", \"gn\", \"gnuplot\", \"go\", \"gql\", \"graphql\", \"groovy\", \"gts\", \"hack\", \"haml\", \"handlebars\", \"haskell\", \"haxe\", \"hbs\", \"hcl\", \"hjson\", \"hlsl\", \"hs\", \"html\", \"html-derivative\", \"http\", \"hurl\", \"hxml\", \"hy\", \"imba\", \"ini\", \"jade\", \"java\", \"javascript\", \"jinja\", \"jison\", \"jl\", \"js\", \"json\", \"json5\", \"jsonc\", \"jsonl\", \"jsonnet\", \"jssm\", \"jsx\", \"julia\", \"just\", \"kdl\", \"kotlin\", \"kql\", \"kt\", \"kts\", \"kusto\", \"latex\", \"lean\", \"lean4\", \"less\", \"liquid\", \"lisp\", \"lit\", \"llvm\", \"log\", \"logo\", \"lua\", \"luau\", \"make\", \"makefile\", \"markdown\", \"marko\", \"matlab\", \"mbt\", \"mbti\", \"md\", \"mdc\", \"mdx\", \"mediawiki\", \"mermaid\", \"mips\", \"mipsasm\", \"mjs\", \"mmd\", \"mojo\", \"moonbit\", \"move\", \"mts\", \"nar\", \"narrat\", \"nextflow\", \"nextflow-groovy\", \"nf\", \"nginx\", \"nim\", \"nix\", \"nu\", \"nushell\", \"objc\", \"objective-c\", \"objective-cpp\", \"ocaml\", \"odin\", \"openscad\", \"pascal\", \"perl\", \"perl6\", \"php\", \"pkl\", \"plsql\", \"po\", \"polar\", \"postcss\", \"pot\", \"potx\", \"powerquery\", \"powershell\", \"prisma\", \"prolog\", \"properties\", \"proto\", \"protobuf\", \"ps\", \"ps1\", \"pug\", \"puppet\", \"purescript\", \"py\", \"python\", \"ql\", \"qml\", \"qmldir\", \"qss\", \"r\", \"racket\", \"raku\", \"razor\", \"rb\", \"reg\", \"regex\", \"regexp\", \"rel\", \"riscv\", \"ron\", \"rosmsg\", \"rs\", \"rst\", \"ruby\", \"rust\", \"sas\", \"sass\", \"scad\", \"scala\", \"scheme\", \"scss\", \"sdbl\", \"sh\", \"shader\", \"shaderlab\", \"shell\", \"shellscript\", \"shellsession\", \"smalltalk\", \"solidity\", \"soy\", \"sparql\", \"spl\", \"splunk\", \"sql\", \"ssh-config\", \"stata\", \"styl\", \"stylus\", \"surql\", \"surrealql\", \"svelte\", \"swift\", \"system-verilog\", \"systemd\", \"talon\", \"talonscript\", \"tasl\", \"tcl\", \"templ\", \"terraform\", \"tex\", \"tf\", \"tfvars\", \"toml\", \"tres\", \"ts\", \"ts-tags\", \"tscn\", \"tsp\", \"tsv\", \"tsx\", \"turtle\", \"twig\", \"typ\", \"typescript\", \"typespec\", \"typst\", \"v\", \"vala\", \"vb\", \"verilog\", \"vhdl\", \"vim\", \"viml\", \"vimscript\", \"vue\", \"vue-html\", \"vue-vine\", \"vy\", \"vyper\", \"wasm\", \"wenyan\", \"wgsl\", \"wiki\", \"wikitext\", \"wit\", \"wl\", \"wolfram\", \"xml\", \"xsl\", \"yaml\", \"yml\", \"zenscript\", \"zig\", \"zsh\", \"\\u6587\\u8A00\"]); return (input: any): input is ShikiLanguage => true === _iv1.has(input); })();\nconst theme = (() => { const _iv1 = new Set([\"andromeeda\", \"aurora-x\", \"ayu-dark\", \"ayu-light\", \"ayu-mirage\", \"catppuccin-frappe\", \"catppuccin-latte\", \"catppuccin-macchiato\", \"catppuccin-mocha\", \"dark-plus\", \"dracula\", \"dracula-soft\", \"everforest-dark\", \"everforest-light\", \"github-dark\", \"github-dark-default\", \"github-dark-dimmed\", \"github-dark-high-contrast\", \"github-light\", \"github-light-default\", \"github-light-high-contrast\", \"gruvbox-dark-hard\", \"gruvbox-dark-medium\", \"gruvbox-dark-soft\", \"gruvbox-light-hard\", \"gruvbox-light-medium\", \"gruvbox-light-soft\", \"horizon\", \"horizon-bright\", \"houston\", \"kanagawa-dragon\", \"kanagawa-lotus\", \"kanagawa-wave\", \"laserwave\", \"light-plus\", \"material-theme\", \"material-theme-darker\", \"material-theme-lighter\", \"material-theme-ocean\", \"material-theme-palenight\", \"min-dark\", \"min-light\", \"monokai\", \"night-owl\", \"night-owl-light\", \"nord\", \"one-dark-pro\", \"one-light\", \"plastic\", \"poimandres\", \"red\", \"rose-pine\", \"rose-pine-dawn\", \"rose-pine-moon\", \"slack-dark\", \"slack-ochin\", \"snazzy-light\", \"solarized-dark\", \"solarized-light\", \"synthwave-84\", \"tokyo-night\", \"vesper\", \"vitesse-black\", \"vitesse-dark\", \"vitesse-light\"]); return (input: any): input is ShikiTheme => true === _iv1.has(input); })();\nexport function isShikiLang(input: unknown): input is ShikiLanguage {\n return lang(input);\n}\nexport function isShikiTheme(input: unknown): input is ShikiTheme {\n return theme(input);\n}\n","import type { ShikiOpts } from \"./types.ts\"\n\nimport { hasColors } from \"@zaly/shared/env\"\nimport { isShikiLang } from \"../schemas/gen/shiki.ts\"\n\nexport async function codeToAnsi(code: string, lang: string, opts?: ShikiOpts): Promise<string> {\n if (!hasColors || !lang || !isShikiLang(lang)) return code\n const { shikiWorker } = await import(\"../shiki/client.ts\")\n const ret = await shikiWorker.highlight(code, lang, opts)\n return ret.value\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"text.d.mts","names":[],"sources":["../src/layout/text.ts"],"mappings":";;;KAeY;iBAKI,WACd,cACA;EACE,OAAO;EACP;EACA,QAAQ;;EAER;;;;EAIA;;iBAmDY,WAAW,WAAW;iBAsBtB,YACd,yBACA;EACE;EACA;EACA;EACA;EACA;EACA,iBAAiB,cAAc;EAC/B,QAAQ;;iBA0DI,WAAW,cAAc;EAAQ,OAAO;IAAkB;iBAS1D,WAAW;iBAUX,WAAW"}
package/dist/text.mjs.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"text.mjs","names":[],"sources":["../src/layout/text.ts"],"sourcesContent":["import type { Layout } from \"../core/state.ts\"\nimport type { StyleBuilder } from \"../style/builder.ts\"\n\nimport { clamp } from \"@zaly/shared\"\nimport {\n hasAnsi,\n sliceAnsi,\n splitAnsi,\n stringWidth,\n stripAnsi,\n truncateAnsi,\n wrapAnsi,\n} from \"@zaly/shared/ansi\"\nimport { ansiBg, styleAnsi } from \"../style/ansi.ts\"\n\nexport type WrapMode = \"word\" | \"char\" | \"none\"\n\nconst ELLIPSIS = \"…\"\nconst TAB_WIDTH = 4\n\nexport function formatText(\n text: string,\n opts: {\n wrap?: WrapMode\n width: number\n style?: StyleBuilder\n /** When wrapping, preserve the indentation of the original text. */\n indent?: boolean\n /** When wrapping ANSI-styled text, pad wrapped fragments to `width`\n * using the background color active at the fragment's last cell.\n * Natural lines that already fit are left unchanged. */\n wrapBg?: boolean\n }\n): string[] {\n const mode = opts.wrap ?? \"word\"\n const lines = splitAnsi(expandTabs(text))\n const ret: string[] = []\n if (mode === \"none\") ret.push(...lines)\n else {\n for (const line of lines) {\n const sw = stringWidth(line)\n\n // Fast path for short lines that don't need wrapping\n if (sw <= opts.width) {\n ret.push(line)\n continue\n }\n\n const stripped = stripAnsi(line)\n\n // Long line that ends in (ansi) whitespace, so just strip\n if (stripped.slice(opts.width).trim() === \"\") {\n ret.push(sliceAnsi(line, 0, opts.width))\n continue\n }\n\n const indentWidth = opts.indent ? stringWidth(stripped.match(/^\\s*/)?.[0] ?? \"\") : 0\n const indent = indentWidth > 0 ? sliceAnsi(line, 0, indentWidth) : \"\"\n const bare = indent ? sliceAnsi(line, indentWidth) : line\n const wrapped = splitAnsi(wrapAnsi(bare, opts.width - indentWidth, { mode }))\n\n // * strip single leading space from wrapped lines after the first\n // * preserve indentation when enabled\n // * pad wrapped lines with bg color if padding is enabled and the line is short\n for (let r = 0; r < wrapped.length; r++) {\n let row = r > 0 && wrapped[r].startsWith(\" \") ? wrapped[r].slice(1) : wrapped[r]\n if (row === \"\" && r > 0 && r < wrapped.length - 1) continue\n if (indent) row = `${indent}${row}`\n if (opts.wrapBg) {\n const rowWidth = stringWidth(row)\n const bg = rowWidth > 0 ? ansiBg(row, rowWidth - 1) : undefined\n if (bg && rowWidth < opts.width) {\n row = `${row}${styleAnsi(\" \".repeat(opts.width - rowWidth), { bg })}`\n }\n }\n ret.push(row)\n }\n }\n }\n return ret.map((line) => (opts.style ? opts.style(line) : line))\n}\n\nexport function expandTabs(s: string, tabSize = TAB_WIDTH): string {\n if (!s.includes(\"\\t\")) return s\n const plain = hasAnsi(s) ? stripAnsi(s) : s\n const spaces: string[] = []\n let col = 0\n\n for (const ch of plain) {\n if (ch === \"\\t\") {\n const n = tabSize - (col % tabSize)\n spaces.push(\" \".repeat(n))\n col += n\n } else if (ch === \"\\n\" || ch === \"\\r\") {\n col = 0\n } else {\n col += stringWidth(ch)\n }\n }\n\n let i = 0\n return s.replaceAll(\"\\t\", () => spaces[i++] ?? \" \".repeat(tabSize))\n}\n\nexport function formatLines(\n text: string | string[],\n opts: {\n numbered?: boolean\n numberOffset?: number\n maxLineLength?: number\n offset?: number\n limit?: number\n more?: false | ((more: number, msg: string) => string)\n style?: StyleBuilder\n } = {}\n): string[] {\n const lines = (typeof text === \"string\" ? splitAnsi(text) : text).map((line) => expandTabs(line))\n const offset = clamp(opts.offset ?? 0, 0, lines.length - 1)\n const limit = clamp(opts.limit ?? lines.length, 1, lines.length - offset)\n const ellipsis = opts.style?.(ELLIPSIS) ?? ELLIPSIS\n let slice = lines\n .slice(offset, offset + limit)\n .map((line) => (opts.maxLineLength ? truncateAnsi(line, opts.maxLineLength, ellipsis) : line))\n\n const numberOffset = opts.numberOffset ?? offset + 1\n const gutterWidth = String(numberOffset + slice.length - 1).length + 2\n\n if (opts.numbered) {\n slice = slice.map((line, i) => {\n const n = numberOffset + i\n const lineo = `${String(n).padStart(gutterWidth - 2)} │`\n return `${opts.style?.(lineo) ?? lineo} ${line}`\n })\n }\n\n if (slice.length < lines.length && opts.more !== false) {\n const moreN = lines.length - slice.length\n let more = `+${moreN} more line${moreN > 1 ? \"s\" : \"\"}`\n more = opts.more?.(moreN, more) ?? more\n if (opts.numbered) {\n const moreGutter = `${\"…\".padStart(gutterWidth - 2)} │`\n slice.push(`${opts.style?.(moreGutter) ?? moreGutter} ${opts.style?.(more) ?? more}`)\n } else slice.push(opts.style?.(more) ?? more)\n }\n\n return slice\n}\n\nfunction maxContent(text: string): number {\n return text.split(\"\\n\").reduce((m, line) => Math.max(m, stringWidth(line)), 0)\n}\n\nfunction minContent(text: string, mode: WrapMode): number | undefined {\n switch (mode) {\n case \"none\": {\n return\n } // can't break, same as max\n case \"char\": {\n return 1\n } // can break anywhere → 1cell floor\n case \"word\": {\n // longest unbreakable run\n return text.split(/\\s+/).reduce((m, word) => Math.max(m, stringWidth(word)), 0)\n }\n default: {\n mode satisfies never // future modes get caught at type-check time\n return undefined\n }\n }\n}\n\nexport function calcLayout(text: string, opts: { wrap?: WrapMode } = {}): Layout {\n text = expandTabs(text)\n const width = maxContent(text)\n return {\n minWidth: minContent(text, opts.wrap ?? \"word\") ?? width,\n width,\n }\n}\n\nexport function countLines(text: string): number {\n let count = 0\n let pos = text.indexOf(\"\\n\")\n while (pos !== -1) {\n count++\n pos = text.indexOf(\"\\n\", pos + 1)\n }\n return count\n}\n\nexport function capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"actions.d.mts","names":[],"sources":["../../../src/widgets/completions/actions.ts"],"mappings":";;;;;;;;KAQY,uBAAuB,YAAY;EAAe;EAAY;;UAEzD;;EAEf,SAAS;;EAET,UAAU;;;EAGV,UAAU,YAAY,MAAM;;;;;;;;;;;;;;;;;;iBAmBd,cAAc,MAAM,uBAAuB,iBAAiB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"actions.mjs","names":[],"sources":["../../../src/widgets/completions/actions.ts"],"sourcesContent":["import type { ActionDef, Actions } from \"../../input/actions.ts\"\nimport type { CompletionSource } from \"../autocomplete.ts\"\nimport type { PickerItem } from \"../picker.ts\"\n\n/** Completion item produced by `actionsSource`. Carries the full\n * `ActionInfo` plus the action `id` so the source's `accept` can\n * dispatch without a secondary lookup. Items match the `MenuItem`\n * contract loosely via the overlapping `name`/... shape. */\nexport type ActionCompletionItem = ActionDef & PickerItem & { id: string; score: number }\n\nexport interface ActionsSourceOptions {\n /** Registry to read from. Usually `renderer.actions`. */\n actions: Actions\n /** Trigger regex. Default: `/^\\s*\\//` (slash at start of line). */\n trigger?: RegExp\n /** Keep predicate. Default skips `info.hidden`. Return `false` to\n * drop the entry from the completion list. */\n filter?: (id: string, info: ActionDef) => boolean\n}\n\n/**\n * Completion source backed by the `Actions` registry — typical slash\n * command ergonomics. Items are raw `ActionInfo` objects augmented\n * with their `id`; `accept` dispatches the action through the registry\n * and clears the trigger+query range, so the user's typed `/foo` is\n * removed once the action fires (no stale text left in the input).\n *\n * ```ts\n * autocomplete({\n * input: \"chat-input\",\n * sources: {\n * slash: actionsSource({ actions: renderer.actions }),\n * },\n * })\n * ```\n */\nexport function actionsSource(opts: ActionsSourceOptions): CompletionSource<ActionCompletionItem> {\n const trigger = opts.trigger ?? /^\\//\n const filter = opts.filter ?? ((_id, info): boolean => !info.hidden && !!info.cmd)\n\n return {\n accept(item): undefined {\n opts.actions.dispatch(item.id, { source: \"autocomplete\" })\n return undefined\n },\n complete(_query, match): ActionCompletionItem[] {\n const out: ActionCompletionItem[] = []\n for (const info of opts.actions.list()) {\n if (!filter(info.id, info)) continue\n const name = info.cmd ?? info.id\n const score = match(name)\n if (!score) continue\n out.push({ ...info, id: info.id, name, score, text: name })\n }\n return out\n },\n triggers: [trigger],\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAoCA,SAAgB,cAAc,MAAoE;CAChG,MAAM,UAAU,KAAK,WAAW;CAChC,MAAM,SAAS,KAAK,YAAY,KAAK,SAAkB,CAAC,KAAK,UAAU,CAAC,CAAC,KAAK;CAE9E,OAAO;EACL,OAAO,MAAiB;GACtB,KAAK,QAAQ,SAAS,KAAK,IAAI,EAAE,QAAQ,eAAe,CAAC;EAE3D;EACA,SAAS,QAAQ,OAA+B;GAC9C,MAAM,MAA8B,CAAC;GACrC,KAAK,MAAM,QAAQ,KAAK,QAAQ,KAAK,GAAG;IACtC,IAAI,CAAC,OAAO,KAAK,IAAI,IAAI,GAAG;IAC5B,MAAM,OAAO,KAAK,OAAO,KAAK;IAC9B,MAAM,QAAQ,MAAM,IAAI;IACxB,IAAI,CAAC,OAAO;IACZ,IAAI,KAAK;KAAE,GAAG;KAAM,IAAI,KAAK;KAAI;KAAM;KAAO,MAAM;IAAK,CAAC;GAC5D;GACA,OAAO;EACT;EACA,UAAU,CAAC,OAAO;CACpB;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"files.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 +0,0 @@
1
- {"version":3,"file":"files.mjs","names":[],"sources":["../../../src/widgets/completions/files.ts"],"sourcesContent":["import type { FindOptions } from \"@zaly/shared/find\"\nimport type { Progressive } from \"../../core/reactive.ts\"\nimport type { CompletionSource } from \"../autocomplete.ts\"\nimport type { PickerItem } from \"../picker.ts\"\n\nimport { normPath } from \"@zaly/shared\"\nimport { createIterable, signal } from \"../../core/reactive.ts\"\n\nexport interface FilesSourceOptions extends FindOptions {\n /** Base directory relative queries resolve against. Default:\n * `process.cwd()`. */\n cwd?: string\n /** Trigger regex. Default: `/(?<=^|\\s)@/` — `@` preceded by start-of-\n * string or whitespace. Lookbehind keeps `match.start` on the `@`\n * itself so the leading space isn't eaten on insertion. */\n trigger?: RegExp\n /** Prefix prepended to accepted values so the trigger character stays\n * in the input after a pick (letting users keep typing to drill into\n * subdirectories). Default: `\"@\"` — matches the default trigger.\n * When customizing `trigger`, set this to the character your trigger\n * represents. */\n prefix?: string\n refreshInterval?: number\n}\n\ntype File = PickerItem & { file: string }\n\n/** Completion source for files. Uses `@zaly/shared/find` under the hood, so\n * supports all the same options and respects .gitignore by default. */\nexport function filesSource(opts: FilesSourceOptions = {}): CompletionSource<File> {\n const cwd = normPath(opts.cwd)\n const trigger = opts.trigger ?? /(?<=^|\\s)@/\n const prefix = opts.prefix ?? \"@\"\n let results: Progressive<readonly File[]> | undefined\n const [gen, setGen] = signal(0)\n let lastRefresh = 0\n return {\n accept: (item) => {\n // Dirs keep the popup open (trigger still matches, user can\n // drill in). Files close it — a trailing space makes `#detect`\n // see whitespace in the query and bail.\n const v = item.file\n return `${prefix}${v}${v.endsWith(\"/\") ? \"\" : \" \"}`\n },\n get complete() {\n const now = performance.now()\n if (!results?.loading() && now - lastRefresh > (opts.refreshInterval ?? 10_000)) {\n setGen((g) => g + 1) // Refresh results\n lastRefresh = now\n }\n return (results ??= createIterable<File>(async function* iterFiles() {\n gen() // Re-run when `gen` changes\n const { find } = await import(\"@zaly/shared/find\")\n for await (const f of find({ ...opts, cwd })) {\n const ff = Array.isArray(f) ? f : [f]\n yield ff.map((file) => ({ file, text: file }))\n }\n }))\n },\n triggers: [trigger],\n }\n}\n"],"mappings":";;;;;AA6BA,SAAgB,YAAY,OAA2B,CAAC,GAA2B;CACjF,MAAM,MAAM,SAAS,KAAK,GAAG;CAC7B,MAAM,UAAU,KAAK,WAAW;CAChC,MAAM,SAAS,KAAK,UAAU;CAC9B,IAAI;CACJ,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC;CAC9B,IAAI,cAAc;CAClB,OAAO;EACL,SAAS,SAAS;GAIhB,MAAM,IAAI,KAAK;GACf,OAAO,GAAG,SAAS,IAAI,EAAE,SAAS,GAAG,IAAI,KAAK;EAChD;EACA,IAAI,WAAW;GACb,MAAM,MAAM,YAAY,IAAI;GAC5B,IAAI,CAAC,SAAS,QAAQ,KAAK,MAAM,eAAe,KAAK,mBAAmB,MAAS;IAC/E,QAAQ,MAAM,IAAI,CAAC;IACnB,cAAc;GAChB;GACA,OAAQ,YAAY,eAAqB,gBAAgB,YAAY;IACnE,IAAI;IACJ,MAAM,EAAE,SAAS,MAAM,OAAO;IAC9B,WAAW,MAAM,KAAK,KAAK;KAAE,GAAG;KAAM;IAAI,CAAC,GAEzC,OADW,MAAM,QAAQ,CAAC,IAAI,IAAI,CAAC,CAAC,EAAA,CAC3B,KAAK,UAAU;KAAE;KAAM,MAAM;IAAK,EAAE;GAEjD,CAAC;EACH;EACA,UAAU,CAAC,OAAO;CACpB;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"github.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 +0,0 @@
1
- {"version":3,"file":"github.mjs","names":[],"sources":["../../../src/widgets/completions/github.ts"],"sourcesContent":["import type { ScoredItem } from \"../../search/matcher.ts\"\nimport type { CompletionSource } from \"../autocomplete.ts\"\nimport type { PickerItem } from \"../picker.ts\"\nimport type { OptionRender } from \"../select.ts\"\n\nimport { stringWidth } from \"@zaly/shared/ansi\"\nimport { spawn } from \"node:child_process\"\n\n/** A GitHub issue or pull request returned by `githubSource`. Shape\n * mirrors the `gh` CLI's `--json number,title,state,author,url`\n * output, with `type` added to distinguish issues from PRs. */\nexport type GithubItem = PickerItem & {\n number: number\n title: string\n /** `\"open\"` / `\"closed\"` for issues; `\"open\"` / `\"closed\"` / `\"merged\"`\n * for PRs. Kept as a string so we don't trip if gh evolves. */\n state: string\n type: \"issue\" | \"pr\"\n url: string\n author?: { login?: string }\n}\n\nexport type GithubState = \"open\" | \"closed\" | \"all\"\n\nexport type GithubFetcher = (cwd: string, state: GithubState) => Promise<GithubItem[]>\n\nexport interface GithubSourceOptions {\n /** Working directory used to locate the repo. `gh` resolves the repo\n * from the git remote of this dir. Default: `process.cwd()`. */\n cwd?: string\n /** Trigger regex. Default: `/(?<=^|\\s)#/` — `#` preceded by start-of-\n * string or whitespace (same lookbehind trick as `filesSource`). */\n trigger?: RegExp\n /** Prepended to the accepted value so the trigger character stays in\n * the input. Default: `\"#\"`. */\n prefix?: string\n /** Which issues / PRs to include. Default: `\"open\"`. */\n state?: GithubState\n /** Per-kind cap passed to `gh`. Default: `50`. */\n limit?: number\n /** Override the fetch implementation. Mainly useful for tests — the\n * default shells out to `gh`. The fetcher is called at most once per\n * `githubSource` instance; results are cached thereafter. */\n fetcher?: GithubFetcher\n}\n\n/**\n * Completion source for GitHub issues and pull requests from the repo\n * at `cwd`. Shells out to the `gh` CLI on first use, caches the result,\n * and fuzzy-filters locally on subsequent calls so typing stays snappy.\n *\n * Accepts insert `#123 ` into the input — ready for a markdown-style\n * reference in a chat message or commit subject.\n *\n * ```ts\n * autocomplete({\n * input: \"chat-input\",\n * sources: {\n * gh: githubSource(),\n * },\n * })\n * ```\n *\n * Silently returns no items when `gh` isn't installed, the user isn't\n * authenticated, or the directory isn't a GitHub repo — so it's safe\n * to wire up unconditionally.\n */\nexport function githubSource(opts: GithubSourceOptions = {}): CompletionSource<GithubItem> {\n const cwd = opts.cwd ?? process.cwd()\n const trigger = opts.trigger ?? /(?<=^|\\s)#/\n const prefix = opts.prefix ?? \"#\"\n const state: GithubState = opts.state ?? \"open\"\n const limit = opts.limit ?? 50\n const fetcher = opts.fetcher ?? defaultFetcher(limit)\n\n // Lazy, single-fetch cache. The Promise is reused by concurrent\n // callers so rapid keystrokes while the first fetch is in flight\n // don't spawn duplicate `gh` processes.\n let cache: Promise<GithubItem[]> | undefined\n\n return {\n accept: (item) => `${prefix}${item.number} `,\n async complete(_query: string, match): Promise<ScoredItem<GithubItem>[]> {\n cache ??= fetcher(cwd, state).catch(() => [])\n const items = await cache\n const out: ScoredItem<GithubItem>[] = []\n for (const item of items) {\n // Match on \"#<num> <title>\" so users can type either digits or\n // words and both hit. Keeps ranking implicit — source order\n // (freshest first from gh) wins when scores tie.\n const target = `#${item.number} ${item.title}`\n const score = match(target)\n if (!score) continue\n out.push({ ...item, score })\n }\n return out\n },\n render: defaultRender,\n triggers: [trigger],\n }\n}\n\nconst defaultRender: OptionRender<GithubItem> = (item, ctx) => {\n const { style } = ctx\n const num = `#${item.number}`\n const stateSlot = stateStyleSlot(item.state)\n const typeLabel = item.type === \"pr\" ? \"pr\" : \"issue\"\n const author = item.author?.login ? `@${item.author.login}` : \"\"\n\n // Layout: [#num] [state/type] title ............. @author\n // All chrome is left-aligned; author is the right-column hint.\n const lead = `${style.add(\"optionName\")(num)} ${style.add(stateSlot)(`${item.state}·${typeLabel}`)} ${item.title}`\n\n if (author === \"\") return lead\n const gap = 2\n const leadW = stringWidth(lead)\n const hintW = stringWidth(author)\n const padded = ctx.width - leadW - hintW - gap\n if (padded < 1) return lead\n return lead + \" \".repeat(padded + gap) + style.add(\"optionDesc\")(author)\n}\n\nfunction stateStyleSlot(state: string): string {\n if (state === \"open\") return \"success\"\n if (state === \"merged\") return \"accent\"\n // \"closed\" and anything unknown → dim.\n return \"muted\"\n}\n\n/** Default fetcher — shells out to `gh issue list` + `gh pr list` in\n * parallel. Either failing is swallowed so a repo with only issues\n * (or only PRs) still works. */\nfunction defaultFetcher(limit: number): GithubFetcher {\n const fields = \"number,title,state,author,url\"\n return async (cwd, state) => {\n const [issues, prs] = await Promise.all([\n runGh(cwd, [\"issue\", \"list\", \"--state\", state, \"--json\", fields, \"--limit\", String(limit)]),\n runGh(cwd, [\"pr\", \"list\", \"--state\", state, \"--json\", fields, \"--limit\", String(limit)]),\n ])\n return [\n ...parse(issues).map((x) => Object.assign(x, { type: \"issue\" as const })),\n ...parse(prs).map((x) => Object.assign(x, { type: \"pr\" as const })),\n ].map((item) => Object.assign(item, { text: `#${item.number} ${item.title}` }))\n }\n}\n\nfunction parse(out: string): Omit<GithubItem, \"type\">[] {\n try {\n return JSON.parse(out) as Omit<GithubItem, \"type\">[]\n } catch {\n return []\n }\n}\n\nfunction runGh(cwd: string, args: string[]): Promise<string> {\n return new Promise((resolve) => {\n try {\n const child = spawn(\"gh\", args, { cwd, stdio: [\"ignore\", \"pipe\", \"ignore\"] })\n let stdout = \"\"\n child.stdout.on(\"data\", (chunk: Buffer) => {\n stdout += chunk.toString(\"utf8\")\n })\n child.on(\"close\", (code) => {\n resolve(code === 0 ? stdout : \"\")\n })\n child.on(\"error\", () => {\n resolve(\"\")\n })\n } catch {\n resolve(\"\")\n }\n })\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"divider.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 & {\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 +0,0 @@
1
- {"version":3,"file":"image.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 +0,0 @@
1
- {"version":3,"file":"input.mjs","names":["#cursor","#historyNext","#preferredCol","#historyPrev","#historyEdit","#markerRange","#deleteRange","#historyAdd","#history","#handleKey","#focused","#historyIndex","#staged","#historyDraft","#historySet","#formatCache","#streamingFormat","#formatGen","#format"],"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\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 let line = 0\n let col = 0\n for (let i = 0; i < cursor; i++) {\n if (value[i] === \"\\n\") {\n line++\n col = 0\n } else col++\n }\n return { col, line }\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 return abs + Math.min(col, lines[clampLine].length)\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 +0,0 @@
1
- {"version":3,"file":"log.mjs","names":[],"sources":["../../src/widgets/log.ts"],"sourcesContent":["import type { LogLevel } from \"@zaly/shared/logger\"\nimport type { RenderCtx } from \"../core/ctx.ts\"\nimport type { Node } from \"../core/node.ts\"\nimport type { State } from \"../core/state.ts\"\nimport type { Color } from \"../style/types.ts\"\nimport type { TextContent } from \"./text.ts\"\n\nimport { stringWidth } from \"@zaly/shared/ansi\"\nimport { hasColors } from \"@zaly/shared/env\"\nimport { box } from \"./box.ts\"\nimport { markdown } from \"./markdown.ts\"\nimport { text } from \"./text.ts\"\nimport { widget } from \"./widget.ts\"\n\nexport type LogStyle = \"badge\" | \"icon\" | \"prompt\" | \"title\" | \"text\" | \"notif\"\n\nexport interface LogState {\n level: LogLevel\n content: TextContent\n title?: string\n /** Rendering of the per-level prefix chunk. Defaults by level. */\n style?: LogStyle\n /** Single-glyph icon for `icon` / `prompt` styles. */\n icon?: string\n /** Theme slot or color name used to tint the prefix. */\n color?: Color\n /** Extra plain text prepended to the auto prefix (unstyled). */\n prefix?: string\n /** Theme slot or color name for the body text. Defaults by level, except */\n textColor?: Color\n markdown?: boolean\n}\n\ninterface LevelDefaults {\n style: LogStyle\n icon?: string\n color?: Color\n textColor?: Color\n}\n\n/** Per-level default prefix style + icon + color. Matches rekal's\n * `defaultStyles` table, adapted to zaly's theme slots. */\nconst defaultLogStyles: Record<LogLevel, LevelDefaults> = {\n cancel: { color: \"warn\", icon: \"✖ \", style: \"icon\" },\n debug: { color: \"info\", icon: \"⚙ \", style: \"prompt\" },\n error: { color: \"error\", icon: \"✖ \", style: \"badge\", textColor: \"error\" },\n fatal: { color: \"error\", icon: \"☢ \", style: \"badge\" },\n info: { color: \"info\", icon: \"ℹ \", style: \"icon\" },\n log: { color: \"muted\", icon: \"●\", style: \"icon\" },\n success: { color: \"success\", icon: \"✔ \", style: \"icon\" },\n trace: { color: \"muted\", icon: \"⠿\", style: \"icon\", textColor: \"muted\" },\n warn: { color: \"warn\", icon: \"⚠\", style: \"badge\", textColor: \"warn\" },\n}\n\nconst noColorStyles: Record<LogStyle, LogStyle> = {\n badge: \"title\",\n icon: \"text\",\n notif: \"text\",\n prompt: \"text\",\n text: \"text\",\n title: \"text\",\n}\n\nfunction renderPrefix(s: LogState, ctx: RenderCtx): string {\n const style = ctx.style\n\n const base = defaultLogStyles[s.level]\n let ls: LogStyle = s.style ?? base.style\n if (!hasColors) ls = noColorStyles[ls]\n\n let icon = s.icon ?? base.icon ?? \"\"\n icon = icon === \"\" ? \"\" : `${icon}${\" \".repeat(2 - stringWidth(icon))}`\n const color: Color = s.color ?? base.color ?? \"inherit\"\n\n // text doesn't get a prefix\n let styledPrefix = \"\"\n if (ls === \"badge\") {\n styledPrefix = style.bg(color).fg(\"black\").bold(` ${s.level} `)\n } else if (ls === \"icon\") {\n styledPrefix = style.fg(color)(icon)\n } else if (ls === \"prompt\") {\n styledPrefix = style.fg(color).bold(`${icon} ${s.level}`)\n } else if (ls === \"title\") {\n styledPrefix = style.fg(color).bold(`${s.level}:`)\n } else if (ls === \"notif\") {\n styledPrefix = style.fg(color).bold(`${icon} ${s.title ?? s.level}`)\n }\n\n if (s.prefix) styledPrefix = s.prefix + styledPrefix\n if (s.title && ls !== \"notif\") styledPrefix += style.bold(` ${s.title}`)\n return styledPrefix\n}\n\n/**\n * A single log entry — level-styled prefix (icon / badge / prompt / title /\n * plain text) followed by the body content. The body can be any Node, so\n * callers can stack markdown, code, images, etc. inside a log line.\n *\n * ```ts\n * log({ level: \"info\", content: \"hello\" })\n * log({ level: \"error\", content: markdown(\"**boom**\") })\n * ```\n */\nexport const log = widget((state: State<LogState>, ...children: Node[]) => {\n const base = defaultLogStyles[state.level]\n const color: Color = state.color ?? base.color ?? \"inherit\"\n const s = state\n const textColor = state.textColor ?? (s.style === \"notif\" ? \"inherit\" : base.textColor)\n const style = {\n style: textColor\n ? {\n fg: textColor,\n }\n : undefined,\n width: \"fill\",\n } as const\n if (state.content !== \"\") {\n children = [\n (state.markdown ?? true) ? markdown(state.content, style) : text(state.content, style),\n ...children,\n ]\n }\n return box(\n {\n ...(s.style === \"notif\"\n ? {\n border: \"single\",\n borderStyle: { fg: color },\n borderTitle: (ctx) => renderPrefix(state, ctx),\n borderTitleAlign: \"center\",\n padding: [0, 1],\n //style: \"overlay\",\n width: \"fill\",\n }\n : undefined),\n flexDirection: \"row\",\n gap: 1,\n visible: state.visible,\n },\n s.style === \"notif\"\n ? undefined\n : text((ctx) => renderPrefix(state, ctx), { flexShrink: 0, wrap: \"none\" }),\n box({ flexDirection: \"column\", flexGrow: 1, gap: 1 }, ...children)\n )\n})\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"markdown.mjs","names":["#images","#worker","#update","#code","#gen","#streamingHighlight","#renderer","#highlight"],"sources":["../../src/widgets/markdown.ts"],"sourcesContent":["import type { RenderCtx } from \"../core/ctx.ts\"\nimport type { Accessor } from \"../core/reactive.ts\"\nimport type { State } from \"../core/state.ts\"\nimport type { WrapMode } from \"../layout/text.ts\"\nimport type { MarkdownRenderer } from \"../markdown/renderer.ts\"\nimport type { MdOptions } from \"../markdown/types.ts\"\nimport type { ShikiTheme } from \"../shiki/types.ts\"\nimport type { AnyStyle } from \"../style/types.ts\"\nimport type { Image } from \"./image.ts\"\nimport type { TextContent } from \"./text.ts\"\n\nimport { hasColors } from \"@zaly/shared/env\"\nimport { Node } from \"../core/node.ts\"\nimport { createAsync, signal, unwrap, useContext } from \"../core/reactive.ts\"\nimport { RenderContext } from \"../core/render.ts\"\nimport { calcLayout, expandTabs, formatText } from \"../layout/text.ts\"\nimport { shikiWorker } from \"../shiki/client.ts\"\nimport { codeToAnsi } from \"../shiki/shiki.ts\"\nimport { textContent } from \"./text.ts\"\n\nexport interface MarkdownState {\n /** Markdown source. Accepts a plain string or a reactive accessor —\n * pass `signal()` / `memo()` for streaming content that re-parses on\n * each render and subscribes the node to the signal. */\n content: TextContent\n /** Options forwarded to `renderMarkdown`. */\n options?: MdOptions\n /**\n * Enable shiki-backed syntax highlighting for fenced code blocks. Defaults\n * to `true` — code blocks get per-token colors from the shiki theme, with\n * `mdCodeBlock`'s bg applied as a backdrop. Unknown languages fall through\n * as plain text.\n */\n syntax?: boolean\n wrap?: WrapMode\n style?: AnyStyle\n}\n\ntype ShikiCode = {\n key: string\n code: string\n lang: string\n theme?: ShikiTheme\n inflight?: Promise<string>\n ac?: AbortController\n result?: string\n gen: number\n}\n\nexport class Markdown extends Node<MarkdownState> {\n // Image nodes cached per-src per-Markdown-instance. Re-rendering the\n // same markdown (streaming updates) reuses the same `Image` — same\n // `placementId`, which the KGP spec guarantees is a flicker-free\n // move/resize of the existing placement.\n readonly images = new Map<string, Image>()\n\n #renderer?: MarkdownRenderer\n #code = new Map<string, ShikiCode>()\n #update = signal(0)\n #gen = 0\n #worker: Accessor<number>\n #images?: Accessor<boolean>\n\n constructor(state: State<MarkdownState>) {\n super(state)\n\n const context = useContext(RenderContext)\n this.#images = context?.images\n\n this.#worker = createAsync(\n async () => {\n const update = this.#update.get() // track\n const todo = [...this.#code.values()].filter((c) => c.gen === this.#gen && !c.result)\n if (!todo.length) return update // nothing to do, skip\n await Promise.all(\n todo.map(async (req) => {\n req.ac ??= new AbortController()\n req.inflight ??= codeToAnsi(req.code, req.lang, {\n signal: req.ac.signal,\n theme: req.theme,\n })\n try {\n req.result = await req.inflight\n } catch (error) {\n if (req.ac.signal.aborted) {\n if (this.#code.get(req.key) === req) this.#code.delete(req.key)\n } else throw error\n }\n })\n )\n return update + 1\n },\n { initialValue: 0 }\n )\n }\n\n /** Find a highlighted result for a prefix of `code` in the cache. This\n * handles streaming updates where the full code block isn't available yet\n * and prevents flashing un-highlighted text on each update. */\n #streamingHighlight(code: string, lang: string, theme?: ShikiTheme) {\n let ret: ShikiCode | undefined\n for (const c of this.#code.values()) {\n if (!c.result || c.lang !== lang || c.theme !== theme) continue\n if (code.startsWith(c.code) && c.code.length > (ret?.code.length ?? 0)) ret = c\n }\n if (ret) ret.gen = this.#gen // bump gen to keep it alive in cache\n return ret?.result ? ret.result + code.slice(ret.code.length) : code\n }\n\n #highlight(code: string, lang?: string, theme?: ShikiTheme): string {\n if (!lang) return code\n const key = shikiWorker.key({ code, lang, theme })\n const ret = this.#code.get(key)\n if (ret) ret.gen = this.#gen // bump gen to keep it alive in cache\n if (ret?.result) return ret.result\n else if (!ret) this.#code.set(key, { code, gen: this.#gen, key, lang, theme })\n return this.#streamingHighlight(code, lang, theme)\n }\n\n protected async _render(ctx: RenderCtx): Promise<string[]> {\n if (!this.#renderer) {\n const { MarkdownRenderer } = await import(\"../markdown/renderer.ts\")\n this.#renderer = new MarkdownRenderer({ ...this.state.options, parent: this })\n }\n let source = await textContent(this.state.content, ctx)\n source = expandTabs(source)\n let formatted: string\n\n if (!hasColors) formatted = this.#renderer.normalizeEol(source, source)\n else {\n this.#gen++ // bump generation to track what is still used\n\n formatted = await this.#renderer.render(source, {\n ...ctx,\n highlight: (code, lang) => this.#highlight(code, lang, ctx.style.theme.shiki),\n images: unwrap(this.#images) ?? true,\n })\n\n // prune old entries\n for (const [k, c] of this.#code.entries())\n if (c.gen !== this.#gen) {\n c.ac?.abort()\n if (c.result !== undefined) this.#code.delete(k)\n }\n\n // Trigger the worker if needed\n if (this.#code.values().some((c) => !c.inflight)) {\n this.#update.set(this.#gen) // trigger worker\n this.#worker() // tracked\n }\n }\n\n return formatText(formatted, {\n indent: true,\n style: this.state.style ? ctx.style.add(this.state.style) : undefined,\n width: ctx.width,\n wrap: this.state.wrap,\n wrapBg: true,\n })\n }\n\n override async layout(ctx: RenderCtx) {\n return calcLayout(await textContent(this.state.content, ctx))\n }\n}\n\n/**\n * Render a markdown string as a TUI node. Produces ANSI-styled text that\n * resolves per-element styling through theme slots (`mdHeading1`, `mdCode`,\n * `mdLink`, …). Links become clickable in terminals that support OSC 8.\n *\n * ```ts\n * markdown(\"# Hello\\n\\nThis is **bold** and *italic*.\")\n * markdown({ content, wrap: \"word\", fg: \"fg\" })\n * ```\n */\nexport function markdown(\n content: TextContent,\n style?: Omit<State<MarkdownState>, \"content\">\n): Markdown\nexport function markdown(state: State<MarkdownState>): Markdown\nexport function markdown(\n first: TextContent | MarkdownState,\n style?: Omit<State<MarkdownState>, \"content\">\n): Markdown {\n // Plain strings and accessor functions go through the content path;\n // a non-function object is the full state form.\n if (typeof first === \"string\" || typeof first === \"function\") {\n return new Markdown({ content: first, ...style })\n }\n return new Markdown(first)\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"overlay.mjs","names":[],"sources":["../../src/widgets/overlay.ts"],"sourcesContent":["import type { Emitter } from \"@zaly/shared\"\nimport type { Node } from \"../core/node.ts\"\nimport type { State } from \"../core/state.ts\"\nimport type { Box, BoxStyle } from \"./box.ts\"\n\nimport { box } from \"./box.ts\"\n\nexport interface OverlayState {\n /** Absolute column (1-based) of the overlay's left edge. */\n x: number\n /** Absolute row (1-based) of the overlay's top edge. */\n y: number\n /** Higher zIndex paints on top. Default: 0. */\n zIndex?: number\n verticalAnchor?: \"top\" | \"center\" | \"bottom\"\n horizontalAnchor?: \"left\" | \"center\" | \"right\"\n relative?: \"screen\" | \"ui\" | \"stream\"\n /** If true, the overlay will be automatically removed from the renderer when closed.\n * When false, the overlay will remain in the renderer but will be hidden.\n * Default: true. */\n removeOnClose?: boolean\n}\n\ntype OverlayEvents = {\n close: {}\n}\n\nexport type Overlay<T extends Node[] = Node[]> = Box<OverlayState> & {\n children: T\n} & Emitter<OverlayEvents>\n\nexport function overlay<T extends Node[] = Node[]>(\n state: State<OverlayState & BoxStyle>,\n ...children: T\n): Overlay<T> {\n const node = box({ visible: false, ...state }, ...children) as Overlay<T>\n node.withActions({\n \"overlay.close\": {\n desc: \"Close overlay\",\n fn: () => ((node.state.removeOnClose ?? true) ? node.ctx?.overlay.remove(node) : node.hide()),\n keys: [\"esc\", \"ctrl-c\"],\n },\n })\n return node\n}\n"],"mappings":";;AA+BA,SAAgB,QACd,OACA,GAAG,UACS;CACZ,MAAM,OAAO,IAAI;EAAE,SAAS;EAAO,GAAG;CAAM,GAAG,GAAG,QAAQ;CAC1D,KAAK,YAAY,EACf,iBAAiB;EACf,MAAM;EACN,UAAY,KAAK,MAAM,iBAAiB,OAAQ,KAAK,KAAK,QAAQ,OAAO,IAAI,IAAI,KAAK,KAAK;EAC3F,MAAM,CAAC,OAAO,QAAQ;CACxB,EACF,CAAC;CACD,OAAO;AACT"}